diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c2f5f63d..e5df320c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,16 +12,12 @@ jobs: uses: actions/checkout@v3 - name: Install dependencies run: | - composer global require couscous/couscous pip install -r requirements.txt - - curl -OSL https://couscous.io/couscous.phar - sudo chmod +x couscous.phar - sudo mv couscous.phar /usr/local/bin/couscous - name: Build the website run: | - bash ./build.sh + mkdocs build swift fixSearchIndex.swift + cp googlefc012e5d94cfa05f.html site/googlefc012e5d94cfa05f.html; - name: Configure AWS credentials id: cred uses: aws-actions/configure-aws-credentials@v1 diff --git a/.gitignore b/.gitignore index 4c0cd70b..c8e2441f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ -/.couscous .sass-cache .DS_Store -leaf-pygment/dist /site -/2.0/site -/3.0/site -/4.0/site diff --git a/1.5/.gitignore b/1.5/.gitignore deleted file mode 100755 index e7aad410..00000000 --- a/1.5/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/.couscous -.sass-cache -.DS_Store diff --git a/1.5/CONTRIBUTING.md b/1.5/CONTRIBUTING.md deleted file mode 100755 index 20663c07..00000000 --- a/1.5/CONTRIBUTING.md +++ /dev/null @@ -1,7 +0,0 @@ -# Contributing to Vapor Docs - -Found a mistake or want to add something? Fork the documentation, fix it, and submit a pull request. - -We'll merge it as soon as we can. - -Thanks! \ No newline at end of file diff --git a/1.5/README.es.md b/1.5/README.es.md deleted file mode 100755 index b1b9b5f3..00000000 --- a/1.5/README.es.md +++ /dev/null @@ -1,89 +0,0 @@ -# Documentación de Vapor - -[![Stack Overflow](https://img.shields.io/stackexchange/stackoverflow/t/vapor.svg)](http://stackoverflow.com/questions/tagged/vapor) - -Esta es la documentación de Vapor, el _framework web_ para Swift que funciona sobre iOS, macOS y ubuntu; y sobre todos los _paquetes_ que Vapor ofrece. - -Vapor es el _framework web_ más utilizado para Swift. Proporciona una base maravillosamente expresiva y fácil de usar para tu próximo sitio web o API. - -Para ver el código fuente y la documentación del código visita [Vapor's GitHub](https://github.com/vapor/vapor). - -Para leer esto en [正體中文](https://github.com/vapor/documentation/1.5/README.zh-hant.md) - -Para leer esto en [简体中文](https://github.com/vapor/documentation/blob/README.zh-cn.md) - -Para leer esto en [English](https://github.com/vapor/documentation/1.5/README.md) - -## Cómo leer esta documentación. - -Puedes leer esta guía haciendo clic en las carpetas y los archivos de [GitHub](https://github.com/vapor/documentation) o a través de las páginas generadas [GitHub Pages](https://vapor.github.io/documentation/). - -## API - -La documentación de la API generada automáticamente se encuentra en [api.vapor.codes](http://api.vapor.codes). - -## Paquetes - -Aquí hay una lista de todos los paquetes y módulos incluidos con Vapor (también _utilizables_ individualmente). - -- [Vapor](https://github.com/vapor/vapor): Swift el _framework web_ más utilizado. - - Auth: Autenticación y persistencia de usuarios. - - Sessions: Almacenamiento de datos seguro y _efímero_ basado en cookies. - - Cookies: Cookies HTTP. - - Routing: Enrutador avanzado con parametrización segura. -- [Fluent](https://github.com/vapor/fluent): Modelos, relaciones y consulta de bases de datos NoSQL y SQL. -- [Engine](https://github.com/vapor/engine): Capas de transporte principales. - - HTTP: Cliente y servidor HTTP completamente en Swift. - - URI: Parseo y _serialización_ completamente en Swift. - - WebSockets: Canales de comunicación full-duplex a través de una sola conexión TCP. - - SMTP: Envío de correo electrónico con SendGrill y Gmail. -- [Leaf](https://github.com/vapor/leaf): Un lenguaje de plantillas extensible. -- [JSON](https://github.com/vapor/json): Mapas Jay JSON a tipos de Vapor. -- [Console](https://github.com/vapor/console): Wrapper en Swift para E/S de consola y comandos. -- [TLS](https://github.com/vapor/tls): Wrapper en Swift para el nuevo TLS de CLibreSSL. -- [Crypto](https://github.com/vapor/crypto): Criptografía de LibreSSL y Swift. - - Digests: _Hashing_ con y sin autenticación. - - Ciphers: Encriptación y descifrado. - - Random: Pseudo aleatoriedad criptográficamente segura. - - BCrypt: Implementación completamente en Swift. -- [Node](https://github.com/vapor/node): Estructura de datos para fáciles conversiones de tipo. - - [Polymorphic](https://github.com/vapor/polymorphic): Sintaxis para acceder fácilmente a valores de tipos comunes como JSON. - - [Path Indexable](https://github.com/vapor/path-indexable): Un protocolo para un acceso poderoso via _subscript_ a tipos comunes como JSON. -- [Core](https://github.com/vapor/core): Extensiones básicas, _alias_ de tipos, y funciones que facilitan tareas comunes. -- [Socks](https://github.com/vapor/socks): _API Wrapper_ para acceder a sockets en C. - -## Proveedores y otros. - -Aquí hay una lista de proveedores y paquetes de terceros que funcionan muy bien con Vapor. - -- [MySQL](https://github.com/vapor/mysql): Interface robusta MySQL para Swift. - - [MySQL Driver](https://github.com/vapor/mysql-driver): _Driver_ MySQL para Fluent. - - [MySQL Provider](https://github.com/vapor/mysql-provider): Proveedor MySQL para Vapor. -- [SQLite](https://github.com/vapor/sqlite): _Wrapper_ SQLite 3 para Swift - - [SQLite Driver](https://github.com/vapor/sqlite-driver): _Driver_ SQLite para Fluent. - - [SQLite Provider](https://github.com/vapor/sqlite-provider): Proveedor SQLite provider para Vapor. -- [PostgreSQL](https://github.com/vapor/postgresql): Interface PostgreSQL robusta para Swift. - - [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): _Driver_ PostgreSQL para Fluent. - - [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): Proveedor PostgreSQL para Vapor. -- [MongoKitten*](https://github.com/OpenKitten/MongoKitten): _Driver_ nativo MongoDB, escrito en Swift - - [Mongo Driver](https://github.com/vapor/mongo-driver): _Driver_ MongoKitten para Fluent. - - [Mongo Provider](https://github.com/vapor/mongo-provider): Proveedor MongoKitten para Vapor. - - [MainecoonVapor](https://github.com/OpenKitten/MainecoonVapor): MongoKitten ORM para Vapor. -- [Redbird](https://github.com/vapor/redbird): Un cliente Redis completamente en Swift implementado directamente desde la especificación del protocolo. - - [Redis Provider](https://github.com/vapor/redis-provider): Proveedor del _cache_ de Redis para Vapor. -- [Kitura Provider](https://github.com/vapor/kitura-provider): Permite usar el servidor HTTP de IBM (Kitura) en Vapor. -- [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor): Agrega el potente _logging_ de SwiftyBeaver a Vapor. -- [APNS](https://github.com/matthijs2704/vapor-apns): Sencilla biblioteca APNS para Vapor (Swift). -- [VaporFCM](https://github.com/mdab121/vapor-fcm): Sencilla biblioteca FCM para Vapor. -- [JWT](https://github.com/siemensikkema/vapor-jwt): Implementación JWT para Vapor. -- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): Gerera _V4 Auth Header/Pre-Signed URL_ para _AWS S3 REST API_. -- [Flock](https://github.com/jakeheis/Flock): _Despliegue_ automatizado de proyectos Swift en servidores. - - [VaporFlock](https://github.com/jakeheis/VaporFlock): Utiliza Flock para _desplegar_ aplicaciones de vapor -- [VaporForms](https://github.com/bygri/vapor-forms): Brinda a Vapor un manejo de formularios web simple, dinámico y _reutilizable_. -- [Jobs](https://github.com/BrettRToomey/Jobs): Un sistema minimalista para ejecutar _jobs_/tareas en _2o plano_ para Swift. -- [Heimdall](https://github.com/himani93/heimdall): Un _logger_ de _requet's_ HTTP fácil de usar. - - -## Autores - -[Tanner Nelson](mailto:tanner@qutheory.io), [Logan Wright](mailto:logan@qutheory.io), y los cientos de miembros de Vapor. diff --git a/1.5/README.md b/1.5/README.md deleted file mode 100755 index f81e41b6..00000000 --- a/1.5/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Vapor Documentation - -[![Stack Overflow](https://img.shields.io/stackexchange/stackoverflow/t/vapor.svg)](http://stackoverflow.com/questions/tagged/vapor) - -This is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers. - -Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API. - -To view the framework's source code and code documentation, visit [Vapor's GitHub](https://github.com/vapor/vapor). - -Read this in [Spanish](https://github.com/vapor/documentation/1.5/README.es.md) - -Read this in [正體中文](https://github.com/vapor/documentation/1.5/README.zh-hant.md) - -Read this in [简体中文](https://github.com/vapor/documentation/1.5/README.zh-cn.md) - -## How To Read - -You can read this guide by clicking through the folders and markdown files on [GitHub](https://github.com/vapor/documentation) or through the rendered [GitHub Pages](https://vapor.github.io/documentation/). - -## API - -Auto-generated API documentation is located at [api.vapor.codes](http://api.vapor.codes). - -## Packages - -Here are a list of all the packages and modules included with Vapor (also useable individually). - -- [Vapor](https://github.com/vapor/vapor): Swift's most used web framework. - - Auth: User authentication and persistance. - - Sessions: Secure, ephemeral cookie based data storage. - - Cookies: HTTP cookies. - - Routing: Advanced router with type-safe parameterization. -- [Fluent](https://github.com/vapor/fluent): Models, relationships, and querying for NoSQL and SQL databases. -- [Engine](https://github.com/vapor/engine): Core transport layers. - - HTTP: Pure Swift HTTP client and server. - - URI: Pure Swift URI parsing and serializing. - - WebSockets: Full-duplex communication channels over a single TCP connection. - - SMTP: Send email using Sendgrid and Gmail. -- [Multipart](https://github.com/vapor/multipart): Fast, streaming, non-blocking multipart parser and serializer. - - Multipart: Parses and serializes `multipart/mixed`. - - FormData: Parses and serializes `multipart/form-data`. -- [Leaf](https://github.com/vapor/leaf): An extensible templating language. -- [JSON](https://github.com/vapor/json): Maps Jay JSON to Vapor types. -- [Console](https://github.com/vapor/console): Swift wrapper for console IO and commands. -- [TLS](https://github.com/vapor/tls): Swift wrapper for CLibreSSL's new TLS. -- [Crypto](https://github.com/vapor/crypto): Cryptography from LibreSSL and Swift. - - Digests: Hashing with and without authentication. - - Ciphers: Encryption and decryption - - Random: Pseudo and cryptographically secure randomness. - - BCrypt: Pure Swift implementation. -- [Node](https://github.com/vapor/node): Data structure for easy type conversions. - - [Polymorphic](https://github.com/vapor/polymorphic): Syntax for easily accessing values from common types like JSON. - - [Path Indexable](https://github.com/vapor/path-indexable): A protocol for powerful subscript access of common types like JSON. -- [Core](https://github.com/vapor/core): Core extensions, type-aliases, and functions that facilitate common tasks. -- [Socks](https://github.com/vapor/socks): Swift C Socket API wrapper. - -## Providers & Other - -Here are a list of providers and third party packages that work great with Vapor. - -- [MySQL](https://github.com/vapor/mysql): Robust MySQL interface for Swift. - - [MySQL Driver](https://github.com/vapor/mysql-driver): MySQL driver for Fluent. - - [MySQL Provider](https://github.com/vapor/mysql-provider): MySQL provider for Vapor. -- [SQLite](https://github.com/vapor/sqlite): SQLite 3 wrapper for Swift - - [SQLite Driver](https://github.com/vapor/sqlite-driver): SQLite driver for Fluent. - - [SQLite Provider](https://github.com/vapor/sqlite-provider): SQLite provider for Vapor. -- [PostgreSQL](https://github.com/vapor/postgresql): Robust PostgreSQL interface for Swift. - - [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): PostgreSQL driver for Fluent. - - [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): PostgreSQL provider for Vapor. -- [MongoKitten*](https://github.com/OpenKitten/MongoKitten): Native MongoDB driver for Swift, written in Swift - - [Mongo Driver](https://github.com/vapor/mongo-driver): MongoKitten driver for Fluent. - - [Mongo Provider](https://github.com/vapor/mongo-provider): MongoKitten provider for Vapor. - - [MainecoonVapor](https://github.com/OpenKitten/MainecoonVapor): MongoKitten ORM for Vapor. -- [Redbird](https://github.com/vapor/redbird): Pure-Swift Redis client implemented from the original protocol spec.. - - [Redis Provider](https://github.com/vapor/redis-provider): Redis cache provider for Vapor. -- [Kitura Provider](https://github.com/vapor/kitura-provider): Use IBM's Kitura HTTP server in Vapor. -- [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor): Adds the powerful logging of SwiftyBeaver to Vapor. -- [APNS](https://github.com/matthijs2704/vapor-apns): Simple APNS Library for Vapor (Swift). -- [VaporFCM](https://github.com/mdab121/vapor-fcm): Simple FCM (iOS + Android Push Notifications) library built for Vapor in Swift. -- [JWT](https://github.com/siemensikkema/vapor-jwt): JWT implementation for Vapor. -- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): Generate V4 Auth Header/Pre-Signed URL for AWS S3 REST API -- [Flock](https://github.com/jakeheis/Flock): Automated deployment of Swift projects to servers - - [VaporFlock](https://github.com/jakeheis/VaporFlock): Use Flock to deploy Vapor applications -- [VaporForms](https://github.com/bygri/vapor-forms): Brings simple, dynamic and re-usable web form handling to Vapor. -- [Jobs](https://github.com/BrettRToomey/Jobs): A minimalistic job/background-task system for Swift. -- [Heimdall](https://github.com/himani93/heimdall): An easy to use HTTP request logger. - - -## Authors - -[Tanner Nelson](mailto:tanner@qutheory.io), [Logan Wright](mailto:logan@qutheory.io),and the hundreds of members of Vapor. diff --git a/1.5/README.zh-cn.md b/1.5/README.zh-cn.md deleted file mode 100755 index 246ba6dc..00000000 --- a/1.5/README.zh-cn.md +++ /dev/null @@ -1,84 +0,0 @@ -# Vapor 文档 - -[![Stack Overflow](https://img.shields.io/stackexchange/stackoverflow/t/vapor.svg)](http://stackoverflow.com/questions/tagged/vapor) - -这是 Vapor 的说明文档, Vapor 是一个可以在 iOS, macOS 及 Ubuntu 上执行的 Web framework,以及其他相关的组件。 - -Vapor 是一个在 Swift 上很受欢迎的 Web framework。它提供了清晰易用的 API 及许多方便的基础功能,方便我们用它建立网站或是后台。 - -我们可以在 [Vapor's GitHub](https://github.com/vapor/vapor) 查看源码及说明文档。 - -阅读 [繁体中文](https://github.com/vapor/documentation/1.5/README.zh-hant.md) - -阅读 [Spanish](https://github.com/vapor/documentation/1.5/README.es.md) - -阅读 [English](https://github.com/vapor/documentation/1.5/README.md) - -## 说明文档 - -可在 [GitHub](https://github.com/vapor/documentation) 上浏览说明文档,特別是 markdown 文档(后缀名为 .md 的文档)。或是查看 [GitHub Pages](https://vapor.github.io/documentation/) 上的文件。 - -## 组件 -以下是 Vapor 提供的组件及模板(我们也可以不通过 Vapor,而直接使用它们。) - -- [Vapor](https://github.com/vapor/vapor): Swift 上最常用到 web framework。 - - Auth: 使用认证及存储控制(persistance)。 - - Sessions: 建立在 cookie 机制上安全、短暂的资料存储。 - - Cookies: HTTP cookies. - - Routing: 可通过变量确定(type-safe)的参数来设定路径。 -- [Fluent](https://github.com/vapor/fluent): 用来操作 SQL 或 NoSQL 资料库。 -- [Engine](https://github.com/vapor/engine): 传输的核心层。 - - HTTP: HTTP 用戶端及服务端。 - - URI: URI 的解析及组成。 - - WebSockets: TCB 连线双向沟通管道。 - - SMTP: 通过 Sendgrid 及 Gmail 发送邮件。 -- [Leaf](https://github.com/vapor/leaf): 一种可扩展的脚本语言(extensible templating language)。(注: 可以用来建立使用界面。) -- [JSON](https://github.com/vapor/json): 用 [Jay JSON]((https://github.com/dantoml/jay)) 解析工具生成 Vapor 物件。 -- [Console](https://github.com/vapor/console): 用來处理 console 的输入、输出指令的 Swift 工具。 -- [TLS](https://github.com/vapor/tls): 用來处理 CLibreSSL 的新型 TLS 的 Swift 工具。 -- [Crypto](https://github.com/vapor/crypto): 在 LibreSSL 及 Swift 上进行加密的工具。 - - Digests: 哈希与认证。 - - Ciphers: 编码及解码。 - - Random: 安全的随机数。 - - BCrypt: 完全用 Swift 所写。 -- [Node](https://github.com/vapor/node): 可以轻易地进行类型转换。 - - [Polymorphic](https://github.com/vapor/polymorphic): 如同 JSON 一般可以轻易调用数据。 - - [Path Indexable](https://github.com/vapor/path-indexable): 如同 JSON 一样可以用来处理复杂的资料结构。 -- [Core](https://github.com/vapor/core): 核心扩展,类型别名和一些常见任务的功能。 -- [Socks](https://github.com/vapor/socks): 将 C 语言的 Socket API 包装成 Swift 语言。 - -## 可组合使用的框架 - -以下是可以和 Vapor 同时使用的组件列表。(译:原文里这里还有个东西叫 Provider,是一种 protocol,让我们可以在 Vapor 中像第三方组件一样使用。) - -- [MySQL](https://github.com/vapor/mysql): 可通过 Swift 操作 MySQL 的框架。 - - [MySQL Driver](https://github.com/vapor/mysql-driver): 通过 Fluent 操作 MySQL 的框架。 - - [MySQL Provider](https://github.com/vapor/mysql-provider): 让 MySQL 可以在 Vapor 上运作的 provider。 -- [SQLite](https://github.com/vapor/sqlite): 可通过 Swift 操作 SQLite 3 的框架。 - - [SQLite Driver](https://github.com/vapor/sqlite-driver): 通过 Fluent 操作 SQLite 的工具。 - - [SQLite Provider](https://github.com/vapor/sqlite-provider): 让 SQLite 可以在 Vapor 上运作的 provider。 -- [PostgreSQL](https://github.com/vapor/postgresql): 用 Swift 操作 PostgreSQL 的工具。 - - [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): 用 Fluent 操作 PostgreSQL 的框架。 - - [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): 让 PostgreSQL 可以运行在 Vapor 上的 provider。 -- [MongoKitten*](https://github.com/OpenKitten/MongoKitten): 用 Swift 写的 MongoDB driver。 - - [Mongo Driver](https://github.com/vapor/mongo-driver): Fluent 用的 MongoKitten driver。 - - [Mongo Provider](https://github.com/vapor/mongo-provider): Vapor 用的 MongoKitten provider. - - [MainecoonVapor](https://github.com/OpenKitten/MainecoonVapor): Vapor 的 MongoKitten 组件关联管理。 -- [Redbird](https://github.com/vapor/redbird): 遵循原始协定创造的 Swift Redis client 端。 - - [Redis Provider](https://github.com/vapor/redis-provider): Vapor 的 Redis cache provider。 -- [Kitura Provider](https://github.com/vapor/kitura-provider): 在 Vapor 中使用 IBM 的 Kitura HTTP Server。 -- [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor): 在 Vapor 中使用 SwiftBeaver 的框架。(译注: 像强化版的 NSLog() 或 print()) -- [APNS](https://github.com/matthijs2704/vapor-apns): 用来操作 Apple 推送的工具。 -- [VaporFCM](https://github.com/mdab121/vapor-fcm): 用于发送FCM通知的简单库。 -- [JWT](https://github.com/siemensikkema/vapor-jwt): 让我们可以设定一些规则以取得特定资源的工具。 -- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): 用来产生 HTTP request 的 headers 及已经签证过的 URL,用来 request AWS S3 的 REST API。 -- [Flock](https://github.com/jakeheis/Flock): 自动将 Swift 专案发布上主机。 - - [VaporFlock](https://github.com/jakeheis/VaporFlock): 利用 Flock 发布 Vapor applications。 -- [VaporForms](https://github.com/bygri/vapor-forms): 让我们在处理前端送来的 form request 时可以轻松一点的框架。 -- [Jobs](https://github.com/BrettRToomey/Jobs): 在某个特定的时间点执行某些程式码的框架。 -- [Heimdall](https://github.com/himani93/heimdall): 用来将收到的 http request 记录到某个档案的框架,且可以用试算表类型(ex: excel, google sheets)的软件开启。 - - -## 作者 - -[Tanner Nelson](mailto:tanner@qutheory.io), [Logan Wright](mailto:logan@qutheory.io), [Jinxiansen](mailto:hi@jinxiansen.com), 以及其他上百位 Vapor 的贡献者们。 diff --git a/1.5/README.zh-hant.md b/1.5/README.zh-hant.md deleted file mode 100755 index cac79db2..00000000 --- a/1.5/README.zh-hant.md +++ /dev/null @@ -1,84 +0,0 @@ -# Vapor Documentation - -[![Stack Overflow](https://img.shields.io/stackexchange/stackoverflow/t/vapor.svg)](http://stackoverflow.com/questions/tagged/vapor) - -這是 Vapor 的說明文件, Vapor 是一個可以在 iOS, macOS 及 Ubuntu 上執行的 Web framework,以及其他相關的套件。 - -Vapor 是一個在 Swift 上很受歡迎的 Web framework。它提供了清楚易用的 API 及許多方便的基礎功能,方便我們用它建立網站或是後台。 - -我們可以在 [Vapor's GitHub](https://github.com/vapor/vapor) 查看原始碼及說明文件。 - -閱讀 [English](https://github.com/vapor/documentation/1.5/README.md) - -閱讀 [Spanish](https://github.com/vapor/documentation/1.5/README.es.md) - -閱讀 [简体中文](https://github.com/vapor/documentation/1.5/README.zh-cn.md) - -## 如何閱讀說明文件 - -在 [GitHub](https://github.com/vapor/documentation) 上瀏覽每個資料夾,特別是 markdown 檔(副檔名為 .md 的檔案)。或是看 [GitHub Pages](https://vapor.github.io/documentation/) 上的文件。 - -## 套件 -以下是 Vapor 提供的套件及模組(我們也可以直接使用它們,不透過 Vapor。) - -- [Vapor](https://github.com/vapor/vapor): Swift 上最常被使用到的 web framework。 - - Auth: 使用者的認證及存續控制(persistance)。 - - Sessions: 建立在 cookie 機制上安全、短暫的資料儲存。 - - Cookies: HTTP cookies. - - Routing: 可透過變數類型確定(type-safe)的參數設定來設定路徑。 -- [Fluent](https://github.com/vapor/fluent): 用來操作 SQL 或 NoSQL 資料庫。 -- [Engine](https://github.com/vapor/engine): 傳輸的核心層。 - - HTTP: HTTP 用戶端及主機端。 - - URI: URI 的分解及組成。 - - WebSockets: 在一個 TCB 連線中進行雙向的溝通管道。 - - SMTP: 透過 Sendgrid 及 Gmail 發送郵件。 -- [Leaf](https://github.com/vapor/leaf): 一種可擴張的樣本語言(extensible templating language)。(譯註: 這可以用來建立使用者介面。) -- [JSON](https://github.com/vapor/json): 用 [Jay JSON]((https://github.com/dantoml/jay)) 解析工具產生Vapor物件。 -- [Console](https://github.com/vapor/console): 用來處理 console 的輸入、輸出及指令的 Swift 工具。 -- [TLS](https://github.com/vapor/tls): 用來處理 CLibreSSL 的新型 TLS 的 Swift 工具。 -- [Crypto](https://github.com/vapor/crypto): 在 LibreSSL 及 Swift 上進行加密的工具。 - - Digests: 無論有沒有認證(authentication)都可以進行雜湊(hash)。 - - Ciphers: 編碼及解碼。 - - Random: 安全的隨機性。 - - BCrypt: 完全用 Swift 實作。 -- [Node](https://github.com/vapor/node): 可以輕易地進行類型轉換的資料結構。 - - [Polymorphic](https://github.com/vapor/polymorphic): 如同 JSON 一般可以輕易取用資料的語法。 - - [Path Indexable](https://github.com/vapor/path-indexable): 如同 JSON 一樣可以用來處理複雜的資料結構。 -- [Core](https://github.com/vapor/core): 主要的 extension 群,例如: 變數類型的重新命名、在許多地方都會被使用的 function 等。 -- [Socks](https://github.com/vapor/socks): 將 C 語言的 Socket API 包裝成 Swift 語言。 - -## 可合併使用的套件 - -以下是可以和 Vapor 合併運用的套件列表。(譯註:原文裡這裡還有個東西叫 Provider,那是一種 protocol,讓我們可以掛到 Vapor 中如同第三方元件一樣使用。) - -- [MySQL](https://github.com/vapor/mysql): 可透過 Swift 操作 MySQL 的套件。 - - [MySQL Driver](https://github.com/vapor/mysql-driver): 透過 Fluent 操作 MySQL 的套件。 - - [MySQL Provider](https://github.com/vapor/mysql-provider): 讓 MySQL 可以在 Vapor 上運作的 provider。 -- [SQLite](https://github.com/vapor/sqlite): 可透過 Swift 操作 SQLite 3 的套件。 - - [SQLite Driver](https://github.com/vapor/sqlite-driver): 透迥 Fluent 操作 SQLite 的工具。 - - [SQLite Provider](https://github.com/vapor/sqlite-provider): 讓 SQLite 可以在 Vapor 上運作的 provider。 -- [PostgreSQL](https://github.com/vapor/postgresql): 用 Swift 操作 PostgreSQL 的工具。 - - [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): 用 Fluent 操作 PostgreSQL 的套件。 - - [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): 讓 PostgreSQL 可以運作在 Vapor 上的 provider。 -- [MongoKitten*](https://github.com/OpenKitten/MongoKitten): 用 Swift 寫的 MongoDB driver。 - - [Mongo Driver](https://github.com/vapor/mongo-driver): Fluent 用的 MongoKitten driver。 - - [Mongo Provider](https://github.com/vapor/mongo-provider): Vapor 用的 MongoKitten provider. - - [MainecoonVapor](https://github.com/OpenKitten/MainecoonVapor): Vapor 的 MongoKitten 物件關聯管理。 -- [Redbird](https://github.com/vapor/redbird): 遵循原始協定的規格實作出來的 Swift Redis client 端。 - - [Redis Provider](https://github.com/vapor/redis-provider): Vapor 的 Redis cache provider。 -- [Kitura Provider](https://github.com/vapor/kitura-provider): 在 Vapor 中使用 IBM 的 Kitura HTTP Server。 -- [SwiftyBeaver](https://github.com/SwiftyBeaver/SwiftyBeaver-Vapor): 在 Vapor 中使用 SwiftBeaver 的套件。(譯註: 就像強化版的 NSLog() 或 print()) -- [APNS](https://github.com/matthijs2704/vapor-apns): 用來操作 Apple 推播的工具。 -- [VaporFCM](https://github.com/mdab121/vapor-fcm): 用于发送FCM通知的简单库。 -- [JWT](https://github.com/siemensikkema/vapor-jwt): 讓我們可以設定一些規則以取得特定資源的工具。 -- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): 用來產生 HTTP request 的 headers 及已經簽證過的 URL,用來 request AWS S3 的 REST API。 -- [Flock](https://github.com/jakeheis/Flock): 自動將 Swift 專案發佈上主機。 - - [VaporFlock](https://github.com/jakeheis/VaporFlock): 利用 Flock 發佈 Vapor applications。 -- [VaporForms](https://github.com/bygri/vapor-forms): 讓我們在處理前端送來的 form request 時可以輕鬆一點的套件。 -- [Jobs](https://github.com/BrettRToomey/Jobs): 在某個特定的時間點執行某些程式碼的套件。 -- [Heimdall](https://github.com/himani93/heimdall): 用來將收到的 http request 記錄到某個檔案的套件,且這個寫好的檔可以用試算表類型(ex: excel, google sheets)的軟體開啟。 - - -## 作者們 - -[Tanner Nelson](mailto:tanner@qutheory.io), [Logan Wright](mailto:logan@qutheory.io),以及其他上百位 Vapor 的貢獻者們。 diff --git a/1.5/auth/middleware.md b/1.5/auth/middleware.md deleted file mode 100755 index 389bf35b..00000000 --- a/1.5/auth/middleware.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -currentMenu: auth-middleware ---- - -# Middleware - -`AuthMiddleware` is at the core of adding authorization to your project. It is responsible for initializing dependencies, checking credentials, and handling sessions. - -## Create - -Once you have something that conforms to `Auth.User`, you can create an `AuthMiddleware`. Let's assume we have a class `User` that conforms to `Auth.User`. - -> Note: You may need to include a module name before `User` to disambiguate. - -```swift -import Auth - -let auth = AuthMiddleware(user: User.self) -``` - -Creating the `AuthMiddleware` can be that simple, or you can customize it with additional initialization arguments. - -### Cookie - -Customize the type of cookie the `AuthMiddleware` creates by passing a `CookieFactory`. - -```swift -let auth = AuthMiddleware(user: User.self) { value in - return Cookie( - name: "vapor-auth", - value: value, - expires: Date().addingTimeInterval(60 * 60 * 5), // 5 hours - secure: true, - httpOnly: true - ) -} -``` - -### Cache - -A custom `CacheProtocol` can be passed as well. The `MemoryCache` used by default is not persisted between server restarts and does not allow for sharing between multiple running instances. - -```swift -import VaporRedis - -let redis = RedisCache() -let auth = AuthMiddleware(user: User.self, cache: redis) -``` - -> Note: This example uses the [redis-provider](https://github.com/vapor/redis-provider) package. - -### Realm - -To customize the `AuthMiddleware` even further, you can use a custom `Realm`. The `Realm` takes the responsibility of registering and authenticating the user away from the `Auth.User` protocol. - -```swift -let facebook = FacebookRealm() -let auth = AuthMiddleware(user: User.self, realm: facebook) -``` - -> Note: `FacebookRealm` is hypothetical. - -## Add - -Once you've created the `AuthMiddleware`, you can add it to the `Droplet`. - -```swift -let drop = Droplet() -drop.middleware.append(auth) -``` - -> Note: If you'd like to enable or disable the middleware based on config files, check out [middleware](../guide/middleware.md). - -### Sharing Cache - -If you'd like the `Droplet` and the `AuthMiddleware` to share the same `CacheProtocol`, pass the same instance to both. - -``` -import Vapor -import VaporRedis - -let redis = RedisCache() -let auth = AuthMiddleware(user: User.self, cache: redis) - -let drop = Droplet() - -drop.cache = redis -drop.middleware.append(auth) -``` diff --git a/1.5/auth/protect.md b/1.5/auth/protect.md deleted file mode 100755 index d606fe3c..00000000 --- a/1.5/auth/protect.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -currentMenu: auth-protect ---- - -# Protect - -Once the `AuthMiddleware` has been enabled, you can use `ProtectMiddleware` to prevent certain routes from being accessed without authorization. - -## Create - -To create a `ProtectMiddleware`, you must give it the error to throw in case authorization fails. - -```swift -let error = Abort.custom(status: .forbidden, message: "Invalid credentials.") -let protect = ProtectMiddleware(error: error) -``` - -Here we pass it a simple 403 response. - -## Route Group - -Once the middleware has been created, you can add it to route groups. Learn more about middleware and routing in [route groups](../routing/group.md). - -``` -drop.grouped(protect).group("secure") { secure in - secure.get("about") { req in - let user = try req.user() - return user - } -} -``` - -Visiting `GET /secure/about` will return the authorized user, or an error if no user is authorized. diff --git a/1.5/auth/request.md b/1.5/auth/request.md deleted file mode 100755 index 50285926..00000000 --- a/1.5/auth/request.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -currentMenu: auth-request ---- - -# Request - -The `auth` property on `Request` lets you authenticate users and also provides some convenience methods for accessing common authorization headers. - -## Authorization - -The authorization header is a great place to send credentials from a client. - -``` -Authorization: xxxxxxxxxx -``` - -You can access the authorization header through `req.auth.header`. Two common patterns are basic and bearer. - -### Basic - -Basic authorization consists of a username and password concatenated into a string and base64 encoded. - -``` -Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l -``` - -This is what an example header looks like. You can read more about basic auth on [wikipedia](https://en.wikipedia.org/wiki/Basic_access_authentication). - -Below is how you access this header using `req.auth`. - -```swift -guard let credentials = req.auth.header?.basic else { - throw Abort.badRequest -} -``` - -The basic header returns an `APIKey` credential. - -``` -class APIKey: Credentials { - let id: String - let secret: String -} -``` - -### Bearer - -Another common method is bearer which consists of a single API key. - -``` -Authorization: Bearer apikey123 -``` - -It is accessed similarly to the basic header and returns an `AccessToken` credential. - -``` -class AccessToken: Credentials { - let string: String -} -``` - -### Raw - -To access the raw authorization header, use `req.auth.header?.header`. - -## Credentials - -Both Basic and Bearer return something that conforms to `Credentials`. You can always create a custom `Credentials` object for authentication by conforming your own class to `Credentials` or by manually creating an `APIKey`, `AccessToken`, or `Identifier`. - -```swift -let key = AccessToken(string: "apikey123") -``` - -### Input - -You can also create credentials from form or JSON data. - -```swift -guard - let username = req.data["username"]?.string, - let password = req.data["password"]?.string -else { - throw Abort.badRequest -} - -let key = APIKey(id: username, secret: password) -``` - -## Login - -Once you have some object that conforms to `Credentials`, you can try to login the user. - -```swift -try req.auth.login(credentials) -``` - -If this call succeeds, the user is logged in and a session has been started. They will stay logged in as long as their cookie is valid. - -### Authenticate - -Logging in calls the `authenticate` method on `Auth.User` model you supplied to the `AuthMiddleware`. Make sure you add support for all the credential types you may want to use. - -> Note: If you used a custom Realm, it will be called instead. - -### Identifier - -Another important credential type is the `Identifier` type. This is used by Vapor when fetching the `User` object from the `vapor-auth` cookie. It is also a convenient way to log a user in manually. - -```swift -static func authenticate(credentials: Credentials) throws -> Auth.User { - switch credentials { - ... - case let id as Identifier: - guard let user = try User.find(id.id) else { - throw Abort.custom(status: .badRequest, message: "Invalid identifier.") - } - - return user - ... - } -} -``` - -Adding the `Identifier` case for `Credentials` is easy, just look up the user by the identifier. - -```swift -let id = Identifier(id: 42) -try req.auth.login(id) -``` - -Now you can manually log users in with just their identifiers. - -### Ephemeral - -If you just want to log the user in for a single request, disable persistance. - -```swift -req.auth.login(credentials, persist: false) -``` - -> Note: Supporting `Identifier` credentials is required for persisted authentication to work properly. - -## User - -By default, `request.auth.user()` returns the authorized `Auth.User`. This will need to be casted to your internal `User` type for use. - -Adding a convenience method on `Request` is a great way to simplify this. - -```swift -extension Request { - func user() throws -> User { - guard let user = try auth.user() as? User else { - throw Abort.custom(status: .badRequest, message: "Invalid user type.") - } - - return user - } -} -``` - -Now you can access your `User` type with `try req.user()`. diff --git a/1.5/auth/user.md b/1.5/auth/user.md deleted file mode 100755 index e881d833..00000000 --- a/1.5/auth/user.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -currentMenu: auth-user ---- - -# Auth - -Authentication and authorization is focused around the `Auth.User` protocol. Authentication is analagous to asking: "Who is this?", while authorization is analagous to asking: "What can they do?". Vapor includes an extensible authentication system that you can use as a base for more sophisticated authorization. - -> Note: An [auth-example](https://github.com/vapor/auth-example) project is available on GitHub. - -## User Protocol - -Any type can conform to the `Auth.User` protocol, but they are commonly added onto Fluent `Model`s. - -```swift -import Vapor -import Auth - -final class User: Model { - var id: Node? - var name: String - - ... -} - -extension User: Auth.User { - static func authenticate(credentials: Credentials) throws -> Auth.User { - - } - - static func register(credentials: Credentials) throws -> Auth.User { - - } -} -``` - -Here is an example `User` class with the `Auth.User` protocol requirements stubbed. Note that the name of our class and the protocol are the same. This is why we use the `Auth.` prefix to differentiate the protocol from the `Auth` module from our `User` class. - -### Authenticate - -A user is authenticated when a set of credentials is passed to the static `authenticate` method and the matching user is returned. - -#### Credentials - -```swift -protocol Credentials { } -``` - -The credentials protocol is an empty protocol that any type can conform to. This gives great flexibility to your authentication model, but also requires that you properly handle the case of unsupported credential types. - -#### Access Token - -One of the simplest credential types included is `AccessToken`. It carries a `String` based token that will be used to authenticate the user. - -Let's look at how we might support the access token type. - -```swift -static func authenticate(credentials: Credentials) throws -> Auth.User { - switch credentials { - case let accessToken as AccessToken: - guard let user = try User.query().filter("access_token", accessToken.string).first() else { - throw Abort.custom(status: .forbidden, message: "Invalid access token.") - } - - return user - default: - let type = type(of: credentials) - throw Abort.custom(status: .forbidden, message: "Unsupported credential type: \(type).") - } -} -``` - -The first step is to cast the credentials to the type we want to support--in this case, `AccessToken`. If we do not have an access token, we will inform the client that the credentials are invalid. - -Once we have the access token, we will use it to query the `User` model for an entry with a matching access token. This is assuming the `users` table or collection has the access tokens stored on it. You may opt to store them somewhere else. - -Once we have found the user associated with the supplied access token, we simply return it. - -#### Identifier - -Vapor uses the `Identifier` credential type internally to lookup users from sessions. You can read more in the [Request](request.md) section. - -### Register - -Similar to the authenticate method, the register method takes credentials. But instead of fetching the user from the data store, it provides a convenient way to create the user. You are not required to register your users through this method. - -## Example - -Here is an example of a User that supports multiple credentials. - -```swift -extension User: Auth.User { - static func authenticate(credentials: Credentials) throws -> Auth.User { - let user: User? - - switch credentials { - case let id as Identifier: - user = try User.find(id.id) - case let accessToken as AccessToken: - user = try User.query().filter("access_token", accessToken.string).first() - case let apiKey as APIKey: - user = try User.query().filter("email", apiKey.id).filter("password", apiKey.secret).first() - default: - throw Abort.custom(status: .badRequest, message: "Invalid credentials.") - } - - guard let u = user else { - throw Abort.custom(status: .badRequest, message: "User not found.") - } - - return u - } - - static func register(credentials: Credentials) throws -> Auth.User { - ... - } -} -``` - -> Note: Try not to store passwords. If you must, hash and salt them. diff --git a/1.5/couscous.yml b/1.5/couscous.yml deleted file mode 100755 index 8982abcd..00000000 --- a/1.5/couscous.yml +++ /dev/null @@ -1,196 +0,0 @@ -template: - directory: template - -title: Vapor Documentation -subTitle: A web framework and server for Swift that works on macOS and Ubuntu. - -baseUrl: /1.5 - -menu: - sections: - getting-started: - name: Getting Started - items: - getting-started-install-swift-3-macos: - text: "Install Swift 3: macOS" - relativeUrl: getting-started/install-swift-3-macos.html - getting-started-install-swift-3-ubuntu: - text: "Install Swift 3: Ubuntu" - relativeUrl: getting-started/install-swift-3-ubuntu.html - getting-started-install-toolbox: - text: Install Toolbox - relativeUrl: getting-started/install-toolbox.html - getting-started-hello-world: - text: Hello, World - relativeUrl: getting-started/hello-world.html - getting-started-manual: - text: Manual - relativeUrl: getting-started/manual.html - getting-started-xcode: - text: Xcode - relativeUrl: getting-started/xcode.html - guide: - name: Guide - items: - guide-droplet: - text: Droplet - relativeUrl: guide/droplet.html - guide-folder-structure: - text: Folder Structure - relativeUrl: guide/folder-structure.html - guide-json: - text: JSON - relativeUrl: guide/json.html - guide-config: - text: Config - relativeUrl: guide/config.html - guide-views: - text: Views - relativeUrl: guide/views.html - guide-leaf: - text: Leaf - relativeUrl: guide/leaf.html - guide-controllers: - text: Controllers - relativeUrl: guide/controllers.html - guide-middleware: - text: Middleware - relativeUrl: guide/middleware.html - guide-validation: - text: Validation - relativeUrl: guide/validation.html - guide-provider: - text: Provider - relativeUrl: guide/provider.html - guide-sessions: - text: Sessions - relativeUrl: guide/sessions.html - guide-hash: - text: Hash - relativeUrl: guide/hash.html - guide-commands: - text: Commands - relativeUrl: guide/commands.html - routing: - name: Routing - items: - routing-basic: - text: Basic - relativeUrl: routing/basic.html - routing-parameters: - text: Route Parameters - relativeUrl: routing/parameters.html - routing-query-parameters: - text: Query Parameters - relativeUrl: routing/query-parameters.html - routing-group: - text: Group - relativeUrl: routing/group.html - routing-collection: - text: Collection - relativeUrl: routing/collection.html - fluent: - name: Fluent - items: - fluent-driver: - text: Driver - relativeUrl: fluent/driver.html - fluent-model: - text: Model - relativeUrl: fluent/model.html - fluent-query: - text: Query - relativeUrl: fluent/query.html - fluent-relation: - text: Relation - relativeUrl: fluent/relation.html - auth: - name: Auth - items: - auth-user: - text: User - relativeUrl: auth/user.html - auth-middleware: - text: Middleware - relativeUrl: auth/middleware.html - auth-request: - text: Request - relativeUrl: auth/request.html - auth-protect: - text: Protect - relativeUrl: auth/protect.html - http: - name: HTTP - items: - http-request: - text: Request - relativeUrl: http/request.html - http-response: - text: Response - relativeUrl: http/response.html - http-body: - text: Body - relativeUrl: http/body.html - http-response-representable: - text: ResponseRepresentable - relativeUrl: http/response-representable.html - http-responder: - text: Responder - relativeUrl: http/responder.html - http-client: - text: Client - relativeUrl: http/client.html - http-server: - text: Server - relativeUrl: http/server.html - http-cors: - text: CORS - relativeUrl: http/cors.html - - web-sockets: - name: WebSockets - items: - websockets-droplet: - text: Droplet - relativeUrl: websockets/droplet.html - websockets-custom: - text: Custom - relativeUrl: websockets/custom.html - - testing: - name: Testing - items: - testing-modules: - text: Modules - relativeUrl: testing/modules.html - testing-basic: - text: Basic - relativeUrl: testing/basic.html - - - deploy: - name: Deploy - items: - deploy-nginx: - text: Nginx - relativeUrl: deploy/nginx.html - deploy-supervisor: - text: Supervisor - relativeUrl: deploy/supervisor.html - - - versions: - name: Version (1.5) - items: - 1_5: - text: '1.5' - relativeUrl: switch/1_5.html - 2_0: - text: '2.0' - relativeUrl: switch/2_0.html - 3_0: - text: '3.0' - relativeUrl: switch/3_0.html - 4_0: - text: '4.0' - relativeUrl: switch/4_0.html diff --git a/1.5/deploy/nginx.md b/1.5/deploy/nginx.md deleted file mode 100755 index c2398554..00000000 --- a/1.5/deploy/nginx.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -currentMenu: deploy-nginx ---- - -# Deploying with Nginx - -Nginx is an extremely fast, battle tested, and easy-to-configure HTTP server and proxy. While Vapor supports directly serving HTTP requests with or without TLS, proxying behind Nginx can provide increased performance, security, and ease-of-use. - -> Note: We recommend proxying Vapor HTTP servers behind Nginx. - -## Overview - -What does it mean to proxy an HTTP server? In short, a proxy acts as a middleman between the public internet and your HTTP server. Requests come to the proxy and then it sends them to Vapor. - -An important feature of this middleman proxy is that it can alter or even redirect the requests. For instance, the proxy can require that the client use TLS (https), rate limit requests, or even serve public files without talking to your Vapor application. - -![nginx-proxy](https://cloud.githubusercontent.com/assets/1342803/20184965/5d9d588a-a738-11e6-91fe-28c3a4f7e46b.png) - -### More Detail - -The default port for receiving HTTP requests is port `80` (and `443` for HTTPS). When you bind a Vapor server to port `80`, it will directly receive and respond to the HTTP requests that come to your server. When adding a proxy like Nginx, you bind Vapor to an internal port, like port `8080`. - -> Note: Ports greater than 1024 do not require `sudo` to bind. - -When Vapor is bound to a port besides `80` or `443`, it will not be accessible to the outside internet. You then bind Nginx to port `80` and configure it to route requests to your Vapor server bound at port `8080` (or whichever port you've chosen). - -And that's it. If Nginx is properly configured, you will see your Vapor app responding to requests on port `80`. Nginx proxies the requests and responses invisibly. - -## Install Nginx - -The first step is installing Nginx. One of the great parts of Nginx is the tremendous amount of community resources and documentation surrounding it. Because of this, we will not go into great detail here about installing Nginx as there is almost definitely a tutorial for your specific platform, OS, and provider. - -Tutorials: -- [How To Install Nginx on Ubuntu 14.04 LTS](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts) -- [How To Install Nginx on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04) -- [How to Deploy Nginx on Heroku](https://blog.codeship.com/how-to-deploy-nginx-on-heroku/) -- [How To Run Nginx in a Docker Container on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-run-nginx-in-a-docker-container-on-ubuntu-14-04) - - -### APT - -Nginx can be installed through APT. - -```sh -sudo apt-get update -sudo apt-get install nginx -``` - -Check whether Nginx was installed correctly by visiting your server's IP address in a browser - -```sh -http://server_domain_name_or_IP -``` - -### Service - -Ther service an be started or stopped. - -```sh -sudo service nginx stop -sudo service nginx start -sudo service nginx restart -``` - -## Booting Vapor - -Nginx can be started an stopped with the `sudo service nginx ...` commands. You will need something similar to start and stop your Vapor server. - -There are many ways to do this, and they depend on which platform you are deploying to. Check out the [Supervisor](supervisor.md) instructions to add commands for starting and stopping your Vapor app. - -## Configure Proxy - -The configuration files for enabled sites can be found in `/etc/nginx/sites-enabled/`. - -Create a new file or copy the example template from `/etc/nginx/sites-available/` to get started. - -Here is an example configuration file for a Vapor project called `Hello` in the home directory. - -```sh -server { - server_name hello.com; - listen 80; - - root /home/vapor/Hello/Public/; - - location @proxy { - proxy_pass http://127.0.0.1:8080; - proxy_pass_header Server; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass_header Server; - proxy_connect_timeout 3s; - proxy_read_timeout 10s; - } -} -``` - -This configuration file assumes the `Hello` project binds to port `8080` when started in production mode. - -### Serving Files - -Nginx can also serve public files without asking your Vapor app. This can improve performance by freeing up the Vapor process for other tasks under heavy load. - -```sh -server { - ... - - # Serve all public/static files via nginx and then fallback to Vapor for the rest - try_files $uri @proxy; - - location @proxy { - ... - } -} -``` - -### TLS - -Adding TLS is relatively straightforward as long as the certificates have been properly generated. To generate TLS certificates for free, check out [Let's Encrypt](https://letsencrypt.org/getting-started/). - -```sh -server { - ... - - listen 443 ssl; - - ssl_certificate /etc/letsencrypt/live/hello.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/hello.com/privkey.pem; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:50m; - ssl_stapling on; - ssl_stapling_verify on; - add_header Strict-Transport-Security max-age=15768000; - - ... - - location @proxy { - ... - } -} -``` - -The configuration above are the relatively strict settings for TLS with Nginx. Some of the settings here are not required, but enhance security. diff --git a/1.5/deploy/supervisor.md b/1.5/deploy/supervisor.md deleted file mode 100755 index c84b7217..00000000 --- a/1.5/deploy/supervisor.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -currentMenu: deploy-supervisor ---- - - -# Supervisor - -[Supervisor](http://supervisord.org) is a process control system that makes it easy to start, stop, and restart your Vapor app. - -## Install - -```sh -sudo apt-get update -sudo apt-get install supervisor -``` - -## Configure - -Each Vapor app on your server should have its own configuration file. For an example `Hello` project, the configuration file would be located at `/etc/supervisor/conf.d/hello.conf` - -```sh -[program:hello] -command=/home/vapor/hello/.build/release/App serve --env=production -directory=/home/vapor/hello/ -user=www-data -stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log -stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log -``` - -As specified in our configuration file the `Hello` project is located in the home folder for the user `vapor`. Make sure `directory` points to the root directory of your project where the `Config/` folder is. - -The `--env=production` flag will disable verbose logging and prioritize the `Config/production` sub folder of your configuration files. - -### Environment - -You can export variables to your Vapor app with supervisor. - -```sh -environment=PORT=8123 -``` - -Exported variables can be used in Vapor's configuration files with the `$` prefix. - -`Config/production/servers.json ` -```json -{ - "my-server": { - "port": "$PORT" - } -} -``` - -The above config file will start a server named `my-server` on the port number exported by supervisor. This is a great way to control how Vapor starts from the supervisor config scripts. Feel free to name the server whatever you like. - -## Start - -You can now load and start your app. - -```sh -supervisorctl reread -supervisorctl add hello -supervisorctl start hello -``` - -> Note: The `add` command may have already started your app. diff --git a/1.5/fluent/driver.md b/1.5/fluent/driver.md deleted file mode 100755 index 062899a4..00000000 --- a/1.5/fluent/driver.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -currentMenu: fluent-driver ---- - -# Driver - -Drivers are what power Fluent under the hood. Fluent comes with a memory driver by default and there are many providers for databases like MySQL, SQLite, Mongo, PostgreSQL, and more available as providers. - -![Drivers and Providers](https://cloud.githubusercontent.com/assets/1342803/17418823/73f1d1d2-5a68-11e6-9bed-90f42ce7781d.png) - -This graphic shows the relation between Drivers and Providers using MySQL as an example. This distinction allows Fluent to be used independently from Vapor. - -If you want to use Fluent without Vapor, you will import Drivers into your package. If you are using Vapor, you will import Providers. - -Search GitHub for: -- [Fluent Drivers](https://github.com/vapor?utf8=✓&q=-driver) -- [Vapor Providers](https://github.com/vapor?utf8=✓&q=-provider) - -Not all drivers have providers yet, and not all drivers or providers are up to date with the latest Vapor 1.0. This is a great way to contribute! - -## Creating a Driver - -Fluent is a powerful, database agnostic package for persisting your models. It was designed from the beginning to work with both SQL and NoSQL databases alike. - -Any database that conforms to `Fluent.Driver` will be able to power the models in Fluent and Vapor. - -The protocol itself is fairly simple: - -```swift -public protocol Driver { - var idKey: String { get } - func query(_ query: Query) throws -> Node - func schema(_ schema: Schema) throws - func raw(_ raw: String, _ values: [Node]) throws -> Node -} -``` - -### ID Key - -The ID key will be used to power functionality like `User.find()`. In SQL, this is often `id`. In MongoDB, it is `_id`. - -### Query - -This method will be called for every query made by Fluent. It is the drivers job to properly understand all of the properties on `Query` and return the desired rows, document, or other data as represented by `Node`. - -### Schema - -The schema method will be called before the database is expected to accept queries for a schema. For some NoSQL databases like MongoDB, this can be ignored. For SQL, this is where `CREATE` and other such commands should be called according to `Schema`. - -### Raw - -This is an optional method that can be used by any Fluent driver that accepts string queries. If your database does not accept such queries, an error can be thrown. diff --git a/1.5/fluent/fixtures.md b/1.5/fluent/fixtures.md deleted file mode 100755 index ea6b8a0e..00000000 --- a/1.5/fluent/fixtures.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -currentMenu: fluent-fixtures ---- - -# Fixtures -When you want to prepopulate a database with existing data, you will need to create one or several fixtures. For example, in your table `users`, it's very common to create admin account and a test account just after the creation of the table. The following snippets of code show you how to deal with it. - -In a separate file (Database+Fixture.swift): -```swift -extension Database { - func insertFixtures(_ data: S) throws where S.Iterator.Element == T { - let context = DatabaseContext(self) - try data.forEach { model in - let query = Query(self) - query.action = .create - query.data = try model.makeNode(context: context) - try driver.query(query) - } - } -} -``` - -In your model User.swift file: -```swift -static func prepare(_ database: Database) throws { - try database.create("users") { users in - users.id() - users.string("email") - users.string("password") - } - let seedData: [AppUser] = [ - try User(email: "admin@admin.com", rawPassword: "Def4ultPassword?!"), - try User(email: "test@test.com", rawPassword: "Def4ultPassword?!") - ] - - try database.insertFixtures(seedData) - } - ``` - diff --git a/1.5/fluent/model.md b/1.5/fluent/model.md deleted file mode 100755 index cb9028b7..00000000 --- a/1.5/fluent/model.md +++ /dev/null @@ -1,246 +0,0 @@ ---- -currentMenu: fluent-model ---- - -# Model - -`Model` is the base protocol for any of your application's models, especially those you want to persist. - -> `Model` is only available in Vapor, the Fluent equivalent is `Entity` - -## Example - -Let's create a simple `User` model. - -```swift -final class User { - var name: String - - init(name: String) { - self.name = name - } -} -``` - -The first step to conforming to `Model` is to import Vapor and Fluent. - -```swift -import Vapor -import Fluent -``` - -Then add the conformance to your class. - -```swift -final class User: Model { - ... -} -``` - -The compiler will inform you that some methods need to be implemented to conform. - -### ID - -The first required property is an identifier. This property will contain the identifier when the model is fetched from the database. If it is `nil`, it will be set when the model is saved. - -```swift -final class User: Model { - var id: Node? - ... -} -``` - -### Node Initializable - -The next requirement is a way to create the model from the persisted data. Model uses `NodeInitializable` to achieve this. - -```swift -final class User: Model { - init(node: Node, in context: Context) throws { - id = try node.extract("id") - name = try node.extract("name") - } - ... -} -``` - -The keys `id` and `name` are what we expect the columns or fields in the database to be named. The `extract` call is marked with a `try` because it will throw an error if the value is not present or is the wrong type. - -### Node Representable - -Now that we have covered initializing the model, we need to show how to save it back into the database. Model uses `NodeRepresentable` to achieve this. - -```swift -final class User: Model { - func makeNode(context: Context) throws -> Node { - return try Node(node: [ - "id": id, - "name": name - ]) - } - ... -} -``` - -When a `User` is saved, the `makeNode()` method will be called and the resulting `Node` will be saved to the database. The keys `id` and `name` are what we expect the columns or fields in the database to be named. - -> In most of the cases you do not need to be concerned about `context` argument of the `makeNode(context:)` method. It’s a part of the protocol that allows extensibility in more advanced or specific scenarios. - -## Preparations - -Some databases, like MySQL, need to be prepared for a new schema. In MySQL, this means creating a new table. Preparations are also equatable to migrations, as they can be used to alter schemas after they've already been created. - -### Prepare - -Let's assume we are using a SQL database. To prepare the database for our `User` class, we need to create a table. If you are using a database like Mongo, you can leave this method unimplemented. - -```swift -final class User { - static func prepare(_ database: Database) throws { - try database.create("users") { users in - users.id() - users.string("name") - } - } - ... -} -``` - -Here we create a table named `users` that has an identifier field and a string field with the key `name`. This matches both our `init(node: Node)` and `makeNode() -> Node` methods. - -### Revert - -An optional preparation reversion can be created. This will be run if `vapor run prepare --revert` is called. - -```swift -final class User { - static func revert(_ database: Database) throws { - try database.delete("users") - } - ... -} -``` - -Here we are deleting the table named `users`. - -### Preparations as Migrations - -If you want to add a field to your table after you've already created the initial schema, you can create a struct or class that conforms to `Preparation` like so: - -```swift - -struct AddFooToBar: Preparation { - static func prepare(_ database: Database) throws { - try database.modify("bars", closure: { bar in - bar.string("foo", length: 150, optional: false, unique: false, default: nil) - }) - } - - static func revert(_ database: Database) throws { - - } -} -``` - -Then, in your Droplet setup, add this line: `drop.preparations.append(AddFooToBar.self)` - -### Droplet - -To run these prepations when the applications boots, you must add the Model to the `Droplet`. - -```swift -let drop = Droplet() - -drop.preparations.append(User.self) -``` - -> Note: Preparations must be appended before the Droplet is run. - -## Full Model - -This is what our final `User` model looks like: - -```swift -import Vapor -import Fluent - -final class User: Model { - var id: Node? - var name: String - - init(name: String) { - self.name = name - } - - - init(node: Node, in context: Context) throws { - id = try node.extract("id") - name = try node.extract("name") - } - - func makeNode(context: Context) throws -> Node { - return try Node(node: [ - "id": id, - "name": name - ]) - } - - static func prepare(_ database: Database) throws { - try database.create("users") { users in - users.id() - users.string("name") - } - } - - static func revert(_ database: Database) throws { - try database.delete("users") - } -} -``` - -## Interacting - -Now that `User` conforms to `Model`, it has a plethora of new methods like `find()`, `query()`, `makeJSON()` and more. - -### Fetch - -Models can be fetched by their database identifier. - -```swift -let user = try User.find(42) -``` - -### Save - -Newly created models can be saved to the database. - -```swift -var user = User(name: "Vapor") -try user.save() -print(user.id) // prints the new id -``` - -### Delete - -Persisted models with identifiers can be deleted. - -```swift -try user.delete() -``` - -## Model vs. Entity - -Model has a couple of extra conformances that a pure Fluent entity doesn't have. - -```swift -public protocol Model: Entity, JSONRepresentable, StringInitializable, ResponseRepresentable {} -``` - -As can be seen in the protocol, Vapor models can automatically convert to `JSON`, `Response`, and even be used in type-safe routing. - -## Options - -Change the table/collection name -```swift -static var entity = "new_name" -``` diff --git a/1.5/fluent/query.md b/1.5/fluent/query.md deleted file mode 100755 index 6702bf38..00000000 --- a/1.5/fluent/query.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -currentMenu: fluent-query ---- - -# Query - -The `Query` class is what powers every interaction with Fluent. Whether you're fetching a model with `.find()` or saving to the database, there is a `Query` involved somewhere. - -## Querying Models - -Every type that conforms to [Model](model.md) gets a static `.query()` method. - -```swift -let query = try User.query() -``` - -This is how you can create a `Query`. - -### No Database - -The `.query()` method is marked with `try` because it can throw an error if the Model has not had its static database property set. - -```swift -User.database = drop.database -``` - -This property is set automatically when you pass the Model as a preparation. - -## Filter - -The most common types of queries involve filtering data. - -```swift -let smithsQuery = try User.query().filter("last_name", "Smith") -``` - -Here is the short hand for adding an `equals` filter to the query. As you can see, queries can be chained together. - -In additional to `equals`, there are many other types of `Filter.Comparison`. - -```swift -let over21 = try User.query().filter("age", .greaterThanOrEquals, 21) -``` - -### Scope - -Filters can also be run on sets. - -```swift -let coolPets = try Pet.query().filter("type", .in, ["Dog", "Ferret"]) -``` - -Here only Pets of type Dog _or_ Ferret are returned. The opposite works for `notIn`. - - -### Contains - -Partially matching filters can also be applied. - -```swift -let statesWithNew = try State.query().filter("name", contains: "New") -``` - -## Retrieving - -There are two methods for running a query. - -### All - -All of the matching entities can be fetched. This returns an array of `[Model]`, in this case users. - -```swift -let usersOver21 = try User.query().filter("age", .greaterThanOrEquals, 21).all() -``` - -### First - -The first matching entity can be fetched. This returns an optional `Model?`, in this case a user. - -```swift -let firstSmith = try User.query().filter("last_name", "Smith").first() -``` - -## Union - -Other Models can be joined onto your query to assist in filtering. The results must still be either `[Model]` or `Model?` for whichever type created the query. - -```swift -let usersWithCoolPets = try User.query() - .union(Pet.self) - .filter(Pet.self, "type", .in, ["Dog", "Ferret"]) -``` - -Here the `User` collection is unioned to the `Pet` collection. Only `User`s who have a dog or a ferret will be returned. - -### Keys - -The `union` method assumes that the querying table has a foreign key identifier to the joining table. - -The above example with users and pets assumes the following schema. - -``` -users -- id -pets -- id -- user_id -``` - -Custom foreign keys can be provided through overloads to `union`. - -## Raw Queries - -Since Fluent is focused on interacting with models, each Query requires a model type. If you want to do raw database queries that aren't based on a model, you should use the underlying Fluent Driver to do so. - -```swift -if let mysql = drop.database?.driver as? MySQLDriver { - let version = try mysql.raw("SELECT @@version") -} -``` diff --git a/1.5/fluent/relation.md b/1.5/fluent/relation.md deleted file mode 100755 index ec1146ba..00000000 --- a/1.5/fluent/relation.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -currentMenu: fluent-relation ---- - -# Relation - -Relations allow foreign key based connections between database entities. This is common in SQL-based databases, but can also be used with NoSQL. - -Fluent's relations are named as follows: -- Parent (BelongsTo) -- Children (HasMany, HasOne) -- Siblings (ManyToMany, BelongsToMany) - -## Parent - -The parent relation should be called on an entity that has a foreign key to another entity. For example, assume the following schema: - -``` -pets -- id -- owner_id -- name -- type - -owner -- id -- name -``` - -Here each pet can have one owner. To access that owner from the pet, call `.parent()`. - -```swift -let pet: Pet = ... -let owner = try pet.parent(pet.ownerId, Owner.self).get() -``` - -The parent method requires the foreign key for the parent as well as the type. - -### Convenience - -To make requesting a parent easier, a method can be added to the model. - -```swift -extension Pet { - func owner() throws -> Parent { - return try parent(ownerId) - } -} -``` - -Since we are extending `Pet`, we no longer need to use `pet.` before the `ownerId`. Furthermore, because we are providing the type information about `Owner` in the return type of the method, we no longer need to pass that as an argument. - -The `Parent` type is a queryable object, meaning you could `delete()` the parent, `filter()`, etc. - -```swift -try pet.owner().delete() -``` - -To fetch the parent, you must call `get()`. - -```swift -let owner = try pet.owner().get() -``` - -## Children - -`Children` are the opposite side of the `Parent` relationship. Assuming the schema from the previous example, the pets could be retrieved from an owner like so: - -```swift -let owner: Owner = ... -let pets = owner.children(Pet.self).all() -``` - -Here only the type of child is required. - -### Convenience - -Similarly to `Parent`, convenience methods can be added for children. - -```swift -extension Owner { - func pets() throws -> Children { - return try children() - } -} -``` - -Since the type information is clear from the return value, `Pet.self` does not need to be passed to `children()`. - -The `Children` is also a queryable object like `Parent`. You can call `first()`, `all()`, `filter()`, etc. - -```swift -let coolPets = try owner.pets().filter("type", .in, ["Dog", "Ferret"]).all() -``` - -## Siblings - -`Siblings` work differently from `Children` or `Parent` since they require a `Pivot`. - -For an example, let's say we want to allow our pets to have multiple toys. But we also want the toys to be shared by multiple pets. We need a pivot entity for this. - -``` -pets -- id -- type -- owner_id - -toys -- id -- name - -pets_toys -- id -- pet_id -- toy_id -``` - -Here you can see the pivot entity, `pets_toys`, or `Pivot`. - -### Convenience - -Let's add the convenience methods to `Pet`. - -```swift -extension Pet { - func toys() throws -> Siblings { - return try siblings() - } -} -``` - -And the opposite for `Toy`. - -```swift -extension Toy { - func pets() throws -> Siblings { - return try siblings() - } -} -``` - -Now you are free to query pets and toys similarly to children. - -```swift -let pet: Pet = ... -let toys = pet.toys().all() -``` - -To create a new many-to-many relationship you can do the following. - -```swift - -var toy: Toy = ... // Create a new toy -try toy.save() // Save the toy to the db - - -var pet: Pet = ... // Create a new pet -try pet.save() // Save the pet to the db - -// Link them together in the db -var pivot = Pivot(toy, pet) // Create the relationship -try pivot.save() // Save the relationship to the db -``` - -### Preparation - -To prepare for a relationship with a `Pivot`, simply add the pivot to the `Droplet`'s preparations. - -```swift -let drop = Droplet() -drop.preparations += [ - Toy.self, - Pet.self, - Pivot.self -] -``` diff --git a/1.5/getting-started/hello-world.md b/1.5/getting-started/hello-world.md deleted file mode 100755 index 908189e0..00000000 --- a/1.5/getting-started/hello-world.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -currentMenu: getting-started-hello-world ---- - -# Hello, World - -This section assumes you have installed Swift 3 and the Vapor Toolbox and have verified they are working. - -> Note: If you don't want to use the Toolbox, follow the [manual guide](manual.md). - -## New Project - -Let's start by creating a new project called Hello, World - -```sh -vapor new Hello -``` - -Vapor's folder structure will probably look familiar to you if you have worked with other web frameworks. - -``` -Hello -├── Sources -│ └── App -│ └── Controllers -│ └── Middleware -│ └── Models -│ └── main.swift -├── Public -├── Resources -│ └── Views -└── Package.swift -``` - -For our Hello, World project, we will be focusing on the `main.swift` file. - -``` -Hello -└── Sources - └── App - └── main.swift -``` - -Note: The `vapor new` command creates a new project with examples and comments about how to use the framework. You can delete these if you want. - -## Droplet - -Look for the following line in the `main.swift` file. - -```swift -let drop = Droplet() -``` - -This is where the one and only `Droplet `for this example will be created. The `Droplet` class has a plethora of useful functions on it, and is used extensively. - -## Routing - -Right after the creation of `drop`, add the following code snippet. - -```swift -drop.get("hello") { request in - return "Hello, world!" -} -``` - -This creates a new route on the `Droplet` that will match all `GET` requests to `/hello`. - -All route closures are passed an instance of [Request](../http/request.md) that contains information such as the URI requested and data sent. - -This route simply returns a string, but anything that is [ResponseRepresentable](../http/response-representable.md) can be returned. Learn more in the [Routing](../routing/basic.md) section of the guide. - -Note: Xcode autocomplete may add extraneous type information to your closure's input arguments. This can be deleted to keep the code clean. If you'd like to keep the type information add `import HTTP` to the top of the file. - -## Running - -At the bottom of the main file, make sure to serve your `Droplet`. - -```swift -drop.run() -``` - -Save the file, and switch back to the terminal. - -## Compiling - -A big part of what makes Vapor so great is Swift's state of the art compiler. Let's fire it up. Make sure you are in the root directory of the project and run the following command. - -```swift -vapor build -``` - -Note: `vapor build` runs `swift build` in the background. - -The Swift Package Manager will first start by downloading the appropriate dependencies from git. It will then compile and link these dependencies together. - -When the process has completed, you will see `Building Project [Done]` - -Note: If you see a message like `unable to execute command: Killed`, you need to increase your swap space. This can happen if you are running on a machine with limited memory. - -## Run - -Boot up the server by running the following command. - -```swift -vapor run serve -``` - -You should see a message `Server starting...`. You can now visit `http://localhost:8080/hello` in your browser. - -Note: Certain port numbers require super user access to bind. Simply run `sudo vapor run` to allow access. If you decide to run on a port besides `80`, make sure to direct your browser accordingly. - -## Note for sudo usage - -On some Linux based systems, you might get an error while using sudo. In that case, if you need to run the server as root, at first switch the user using this command: - -``` -sudo -i -``` -Then either add the previously installed path of Swift to the root users $PATH variable. - -``` -PATH=$PATH:/your_path_to_swift -# Example command can be like this -# PATH=$PATH:/swift-3.0/usr/bin -# In this case /swift-3.0/usr/bin is the location of my swift installation. - -``` - - -## Hello, World - -You should see the following output in your browser window. - -``` -Hello, world! -``` diff --git a/1.5/getting-started/install-swift-3-macos.md b/1.5/getting-started/install-swift-3-macos.md deleted file mode 100755 index 817ab369..00000000 --- a/1.5/getting-started/install-swift-3-macos.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -currentMenu: getting-started-install-swift-3-macos ---- - -# Install Swift 3: macOS - -To use Swift 3 on macOS, you just need to have Xcode 8 installed. - -## Install Xcode - -Install [Xcode 8](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) from the Mac App Store. - -[![Xcode 8](https://cloud.githubusercontent.com/assets/1342803/18537674/2ddd8e9c-7ad5-11e6-9bc2-7155d57d20ec.png)](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) - -### Open Xcode - -After Xcode 8 has been downloaded, you must open it to finish the installation. This may take a while. - -## Check - -Double check the installation was successful by running: - -```sh -curl -sL check.vapor.sh | bash -``` - -## Toolbox - -You can now move on to [Install Toolbox](install-toolbox.md). - -## Swift.org - -Check out [Swift.org](https://swift.org)'s extensive guides if you need more detailed instructions for installing Swift 3.0. diff --git a/1.5/getting-started/install-swift-3-ubuntu.md b/1.5/getting-started/install-swift-3-ubuntu.md deleted file mode 100755 index da43a952..00000000 --- a/1.5/getting-started/install-swift-3-ubuntu.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -currentMenu: getting-started-install-swift-3-ubuntu ---- - -# Install Swift 3: Ubuntu - -Installing Swift 3 on Ubuntu only takes a couple of minutes. - -## Quick - -Don't want to type? Run the following script to quickly install Swift 3.0. - -```sh -curl -sL swift.vapor.sh/ubuntu | bash -``` - -> Note: The install script adds Swift to your `~/.bashrc` profile automatically. - -## Manual - -### Dependencies - -Depending on your version of Ubuntu, you may need some additional tools for the compiler. We'll err on the safe side and install everything you should need - -```sh -sudo apt-get update -sudo apt-get install clang libicu-dev binutils git libpython2.7-dev libcurl3 -``` - -### Download - -Download the Swift 3 toolchain for your Ubuntu version. - -```sh -# Ubuntu 14.04 -wget https://swift.org/builds/swift-3.0-release/ubuntu1404/swift-3.0-RELEASE/swift-3.0-RELEASE-ubuntu14.04.tar.gz - -# Ubuntu 15.10 -wget https://swift.org/builds/swift-3.0-release/ubuntu1510/swift-3.0-RELEASE/swift-3.0-RELEASE-ubuntu15.10.tar.gz -``` - -### Decompress - -After Swift 3 has downloaded, decompress it. - -```sh -# Ubuntu 14.04 -tar zxf swift-3.0-RELEASE-ubuntu14.04.tar.gz - -# Ubuntu 15.10 -tar zxf swift-3.0-RELEASE-ubuntu15.10.tar.gz -``` - -### Install - -Move Swift 3.0 to a safe, permanent place on your computer. We'll use `/swift-3.0`, but feel free to choose wherever you like. - -```sh -# Ubuntu 14.04 -mv swift-3.0-RELEASE-ubuntu14.04 /swift-3.0 - -# Ubuntu 15.10 -mv swift-3.0-RELEASE-ubuntu15.10 /swift-3.0 -``` - -> Note: You may need to use `sudo`. - -### Export - -Edit your bash profile using your text editor of choice. - -```sh -vim ~/.bashrc -``` - -Add the following line: - -```sh -export PATH=/swift-3.0/usr/bin:"${PATH}" -``` - -> Note: If you moved Swift 3.0 to a folder other than `/swift-3.0`, your path will be different. - -## Check - -Double check the installation was successful by running: - -```sh -curl -sL check.vapor.sh | bash -``` - -## Toolbox - -You can now move on to [Install Toolbox](install-toolbox.md) - -## Swift.org - -Check out [Swift.org](https://swift.org)'s extensive guides if you need more detailed instructions for installing Swift 3.0. \ No newline at end of file diff --git a/1.5/getting-started/install-toolbox.md b/1.5/getting-started/install-toolbox.md deleted file mode 100755 index 506825f4..00000000 --- a/1.5/getting-started/install-toolbox.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -currentMenu: getting-started-install-toolbox ---- - -# Install Toolbox - -Vapor's command line interface provides shortcuts and assistance for common tasks. - -![Vapor Toolbox](https://cloud.githubusercontent.com/assets/1342803/17454691/97e549e2-5b6d-11e6-979a-f0cd6b6f1b0a.png) - -> If you do not want to install the Toolbox, checkout the [Manual](manual.md) quickstart. - -### Install - -Run the following script to install the [Toolbox](https://github.com/vapor/toolbox). - -```sh -curl -sL toolbox.vapor.sh | bash -``` - -> Note: You must have the correct version of Swift 3 installed. - -### Verify - -Make sure the Toolbox installed successfully by running the help query. You should see a print out of the available commands. You can run the `--help` option on any Toolbox command. - -```sh -vapor --help -``` -## Create A Project - -Now that you have installed the Toolbox, you can create your first Vapor project following the [Hello, World guide](hello-world.md). - -### Updating - -The toolbox can update itself. This may be useful if you experience any issues in the future. - -```sh -vapor self update -``` - -### Templates - -The toolbox can create a project from the Vapor basic-template or any other git repo. - -```sh -vapor new [--template=] -``` - -The toolbox will build an absolute URL based on what you pass as the template option. If you do not specify a template option, the project will be built from the Vapor basic-template. - -```sh -Default(no template option specified) => https://github.com/vapor/basic-template -http(s)://example.com/repo-path => http(s)://example.com/repo-path -user/repo => https://github.com/user/repo -light => https://github.com/vapor/light-template -``` diff --git a/1.5/getting-started/manual.md b/1.5/getting-started/manual.md deleted file mode 100755 index ba91aa2b..00000000 --- a/1.5/getting-started/manual.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -currentMenu: getting-started-manual ---- - -# Manual Quickstart - -Learn how to create a Vapor project _without_ the Toolbox using just Swift 3 and the Swift Package Manager. - -> If you'd prefer to use the Toolbox, learn how to install it [here](install-toolbox.md). - -This document assumes that you have Swift 3 installed. - -> Note: If you've installed the Toolbox, follow the toolbox guide [here](hello-world.md). - -## Check - -To check that your environment is compatible, run the following script: - -```bash -curl -sL check.vapor.sh | bash -``` - -## Make new project using SwiftPM - -Open your terminal - -> For our example, we'll be using the Desktop folder. - -```bash -cd ~/Desktop -mkdir Hello -cd Hello -swift package init --type executable -``` - -Your folder should look like this: - -``` -├── Package.swift -├── Sources -│   └── main.swift -└── Tests -``` - -## Edit `Package.swift` - -Open your `Package.swift` file: - -```bash -open Package.swift -``` - -And add Vapor as a dependency. Here's how your file will look. - -#### Package.swift - -```swift -import PackageDescription - -let package = Package( - name: "Hello", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 1) - ] -) -``` - -> We try to keep this document up to date, however, you can view latest releases [here](https://github.com/vapor/vapor/releases) - -## Edit `main.swift` - -A simple hello world: - -``` -import Vapor - -let drop = Droplet() - -drop.get("/hello") { _ in - return "Hello Vapor" -} - -drop.run() -``` - -## Build and Run - -The first `build` command can take a while to fetch dependencies. - -``` -swift build -.build/debug/Hello -``` - -> If different, replace `Hello` above with the name of your executable. - -## View - -Go to your favorite browser and visit `http://localhost:8080/hello` diff --git a/1.5/getting-started/xcode.md b/1.5/getting-started/xcode.md deleted file mode 100755 index 3a54d4fa..00000000 --- a/1.5/getting-started/xcode.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -currentMenu: getting-started-xcode ---- - -# Xcode - -The first thing you'll probably notice about Vapor and SwiftPM projects in general is that we don't include an Xcode project. In fact, when SwiftPM generates packages, the `.xcodeproj` file is gitignored by default. - -This means we don't have to worry about pbxproj conflicts, and it's easy for different platforms to utilize their own editors. - -## Generate Project - -### Vapor Toolbox - -To generate a new Xcode project for a project, use: - -```bash -vapor xcode -``` - -> If you'd like to automatically open the Xcode project, use `vapor xcode -y` - -### Manual - -To generate a new Xcode project manually. - -```bash -swift package generate-xcodeproj -``` - -Open the project and continue normally. - -## Flags - -For many packages with underlying c-dependencies, users will need to pass linker flags during **build** AND **project generation**. Make sure to consult the guides associated with those dependencies. For example: - -``` -vapor xcode --mysql -``` - -or - -``` -swift package generate-xcodeproj -Xswiftc -I/usr/local/include/mysql -Xlinker -L/usr/local/lib -``` diff --git a/1.5/guide/commands.md b/1.5/guide/commands.md deleted file mode 100755 index 42aaa0c1..00000000 --- a/1.5/guide/commands.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -currentMenu: guide-commands ---- - -# Commands -Custom console commands on Vapor are a breeze. - -## Example -To make a custom console command we must first create a new `.swift` file, import `Vapor` and `Console`, and implement the `Command` protocol. - -```swift -import Vapor -import Console - -final class MyCustomCommand: Command { - public let id = "command" - public let help = ["This command does things, like foo, and bar."] - public let console: ConsoleProtocol - - public init(console: ConsoleProtocol) { - self.console = console - } - - public func run(arguments: [String]) throws { - console.print("running custom command...") - } -} -``` - - - The **id** property is the string you will type in the console to access the command. `.build/debug/App command` will run the Custom Command. - - The **help** property is the help message that will give your custom command's users some idea of how to access it. - - The **console** property is the object passed to your custom command that adheres to the console protocol, allowing manipulation of the console. - - The **run** method is where you put the logic relating to your command. - -After we work our magic in the Custom Command file, we switch over to our `main.swift` file and add the custom command to the droplet like so. -```swift -drop.commands.append(MyCustomCommand(console: drop.console)) -``` -This allows Vapor access to our custom command and lets it know to display it in the `--help` section of the program. - -After compiling the application we can run our custom command like so. - -``` -.build/debug/App command -``` diff --git a/1.5/guide/config.md b/1.5/guide/config.md deleted file mode 100755 index 38e448b7..00000000 --- a/1.5/guide/config.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -currentMenu: guide-config ---- - -# Config - -An application's configuration settings. Cloud applications generally require complex configurations that can adjust based on their environment. Vapor intends to provide a flexible configuration interaction that can be customized for a given user. - -## QuickStart - -For Vapor applications, configuration files are expected to be nested under a top level folder named `Config`. Here's an example of a basic config featuring a single `servers` configuration. - -```bash -./ -├── Config/ -│ ├── servers.json -``` - -And an example of how this might look: - -```JSON -{ - "http": { - "host": "0.0.0.0", - "port": 8080 - } -} -``` - -What that's saying, is that our application should start a single server named 'http' serving port `8080` on host `0.0.0.0`. This represents the following url: `http://localhost:8080`. - -### Custom Keys - -Let's add a custom key to the `servers.json` file: - -```JSON -{ - "http": { - "host": "0.0.0.0", - "port": 8080, - "custom-key": "custom value" - } -} -``` - -This can be accessed from your application's config using the following. - -```swift -let customValue = drop.config["servers", "http", "custom-key"]?.string ?? "default" -``` - -That's it, feel free to add and utilize keys as necessary to make your application configuration easier. - -## Config Syntax - -You can access your config directory with the following syntax. `app.config[<#file-name#>, <#path#>, <#to#>, <#file#>]`. For example, let's hypothesize that in addition to the `servers.json` file we mentioned earlier, there is also a `keys.json` that looks like this: - -```JSON -{ - "test-names": [ - "joe", - "jane", - "sara" - ], - "mongo": { - "url" : "www.customMongoUrl.com" - } -} -``` - -We can access this file by making sure the first argument in our subscript is keys. To get the first name in our list: - -```swift -let name = drop.config["keys", "test-names", 0]?.string ?? "default" -``` - -Or our mongo url: - -```swift -let mongoUrl = drop.config["keys", "mongo", "url"]?.string ?? "default" -``` - -## Advanced Configurations - -Having the default `servers.json` is great, but what about more complex scenarios. For example, what if we want a different host in production and in development? These complex scenarios can be achieved by adding additional folders to our `Config/` directory. Here's an example of a folder structure that's setup for production and development environments. - -```bash -WorkingDirectory/ -├── Config/ -│ ├── servers.json -│ ├── production/ -│ │ └── servers.json -│ ├── development/ -│ │ └── servers.json -│ └── secrets/ -│ └── servers.json -``` - -> You can specify the environment through the command line by using --env=. Custom environments are also available, a few are provided by default: production, development, and testing. - -```bash -vapor run --env=production -``` - -### PRIORITY - -Config files will be accessed in the following priority. - -1. CLI (see below) -2. Config/secrets/ -3. Config/name-of-environment/ -4. Config/ - -What this means is that if a user calls `app.config["servers", "host"]`, the key will be searched in the CLI first, then the `secrets/` directory, then the top level default configs. - -> `secrets/` directory should very likely be added to the gitignore. - -### EXAMPLE - -Let's start with the following JSON files. - -#### `servers.json` - -```JSON -{ - "http": { - "host": "0.0.0.0", - "port": 9000 - } -} -``` - -#### `production/servers.json` - -```JSON -{ - "http": { - "host": "127.0.0.1", - "port": "$PORT" - } -} -``` - -> The `"$NAME"` syntax is available for all values to access environment variables. - -Please notice that `servers.json`, and `production/servers.json` both declare the same keys: `host`, and `port`. In our application, we'll call: - -```swift -// will load 0.0.0.0 or 127.0.0.1 based on above config -let host = drop.config["servers", "http", "host"]?.string ?? "0.0.0.0" -// will load 9000, or environment variable port. -let port = drop.config["servers", "http", "port"]?.int ?? 9000 -``` - -## COMMAND LINE - -In addition to json files nested within the `Config/` directory, we can also use the command line to pass arguments into our config. By default, these values will be set as the "cli" file, but more complex options are also available. - -#### 1. `--KEY=VALUE` - -Arguments set through the command line can be accessed through config's cli file. For example, the following CLI command: - -```bash ---mongo-password=$MONGO_PASSWORD -``` - -would be accessible within your application by using the following: - -```swift -let mongoPassword = drop.config["cli", "mongo-password"]?.string -``` - -#### 2. `--CONFIG:FILE-NAME.KEY=CUSTOM-VALUE` - -If you want command line arguments set to a file besides "cli", you can use this more advanced specification. For example, the following CLI command: - -```bash ---config:keys.analytics=124ZH61F -``` - -would be accessible within your application by using the following: - -```swift -let analyticsKey = drop.config["keys", "analytics"]?.string -``` diff --git a/1.5/guide/controllers.md b/1.5/guide/controllers.md deleted file mode 100755 index 42d3c3ca..00000000 --- a/1.5/guide/controllers.md +++ /dev/null @@ -1,139 +0,0 @@ ---- -currentMenu: guide-controllers ---- - -# Controllers - -Controllers help you organize related functionality into a single place. They can also be used to create RESTful resources. - -## Basic - -A basic controller looks like the following: - -```swift -final class HelloController { - func sayHello(_ req: Request) throws -> ResponseRepresentable { - guard let name = req.data["name"] else { - throw Abort.badRequest - } - return "Hello, \(name)" - } -} -``` - -Simple controllers don't need to conform to any protocols. You are free to design them however you see fit. - -### Registering - -The only required structure is the signature of each method in the controller. In order to register this method into the router, it must have a signature like `(Request) throws -> ResponseRepresentable`. `Request` and `ResponseRepresentable` are made available by importing the `HTTP` module. - -```swift -let hc = HelloController() -drop.get("hello", handler: hc.sayHello) -``` - -Since the signature of our `sayHello` method matches the signature of the closure for the `drop.get` method, we can pass it directly. - -### Type Safe - -You can also use controller methods with type-safe routing. - -```swift -final class HelloController { - ... - - func sayHelloAlternate(_ req: Request, _ name: String) -> ResponseRepresentable { - return "Hello, \(name)" - } -} -``` - -We add a new method called `sayHelloAlternate` to the `HelloController` that accepts a second parameter `name: String`. - -```swift -let hc = HelloController() -drop.get("hello", String.self, handler: hc.sayHelloAlternate) -``` - -Since this type-safe `drop.get` accepts a signature `(Request, String) throws -> ResponseRepresentable`, our method can now be used as the closure for this route. - -## Resources - -Controllers that conform to `ResourceRepresentable` can be easily registered into a router as a RESTful resource. Let's look at an example of a `UserController`. - -```swift -final class UserController { - func index(_ request: Request) throws -> ResponseRepresentable { - return try User.all().makeNode().converted(to: JSON.self) - } - - func show(_ request: Request, _ user: User) -> ResponseRepresentable { - return user - } -} -``` - -Here is a typical user controller with an `index` and `show` route. Indexing returns a JSON list of all users and showing returns a JSON representation of a single user. - -We _could_ register the controller like so: - -```swift -let users = UserController() -drop.get("users", handler: users.index) -drop.get("users", User.self, handler: users.show) -``` - -But `ResourceRepresentable` makes this standard RESTful structure easy. - -```swift -extension UserController: ResourceRepresentable { - func makeResource() -> Resource { - return Resource( - index: index, - show: show - ) - } -} -``` - -Conforming `UserController` to `ResourceRepresentable` requires that the signatures of the `index` and `show` methods match what the `Resource` is expecting. - -Here is a peek into the `Resource` class. - -```swift -final class Resource { - typealias Multiple = (Request) throws -> ResponseRepresentable - typealias Item = (Request, Model) throws -> ResponseRepresentable - - var index: Multiple? - var store: Multiple? - var show: Item? - var replace: Item? - var modify: Item? - var destroy: Item? - var clear: Multiple? - var aboutItem: Item? - var aboutMultiple: Multiple? - - ... -} -``` - -Now that `UserController` conforms to `ResourceRepresentable`, registering the routes is easy. - -```swift -let users = UserController() -drop.resource("users", users) -``` - - `drop.resource` will take care of registering only the routes that have been supplied by the call to `makeResource()`. In this case, only the `index` and `show` routes will be supplied. - -> Note: `drop.resource` also adds useful defaults for OPTIONS requests. These can be overriden. - -## Folder - -Controllers can go anywhere in your application, but they are most often stored in the `Controllers/` directory. - -### Modules - -If you are building a large application, you may want to create your controllers in a separate module. This will allow you to perform unit tests on your controllers. For more information on creating modules, visit the documentation for the [Swift Package Manager](https://swift.org/package-manager/). diff --git a/1.5/guide/droplet.md b/1.5/guide/droplet.md deleted file mode 100755 index 20ebfba5..00000000 --- a/1.5/guide/droplet.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -currentMenu: guide-droplet ---- - -# Droplet - -The `Droplet` is a service container that gives you access to many of Vapor's facilities. It is responsible for registering routes, starting the server, appending middleware, and more. - -## Initialization - -As you have probably already seen, the only thing required to create an instance of `Droplet` is to import Vapor. - -```swift -import Vapor - -let drop = Droplet() - -// your magic here - -drop.run() -``` - -Creation of the `Droplet` normally happens in the `main.swift` file. - -## Environment - -The `environment` property contains the current environment your application is running in. Usually development, testing, or production. - -```swift -if drop.environment == .production { - ... -} -``` - -The environment affects [Config](config.md) and [Logging](log.md). The environment is `development` by default. To change it, pass the `--env=` flag as an argument. - -```sh -vapor run serve --env=production -``` - -If you are in Xcode, you can pass arguments through the scheme editor. - -> Note: Debug logs can reduce the number of requests your application can handle per second. Enabling the production environment can improve performance. - -## Working Directory - -The `workDir` property contains a path to the current working directory of the application relative to where it was started. By default, this property assumes you started the Droplet from its root directory. - -```swift -drop.workDir // "/var/www/my-project/" -``` - -You can override the working directory through the `Droplet`'s initializer, or by passing the `--workdir` argument. - -```sh -vapor run serve --workdir="/var/www/my-project" -``` - -## Modifying Properties - -The `Droplet`'s properties can be changed programmatically or through configuration. - -### Programmatic - -Properties on the `Droplet` can be changed after it is initialized. - -```swift -let drop = Droplet() - -drop.server = MyServerType.self -``` - -Here the type of server the `Droplet` uses is changed to a custom type. When the `Droplet` is run, this custom server type will be booted instead of the default server. - -### Configurable - -If you want to modify a property of the `Droplet` only in certain cases, you can use `addConfigurable`. Say for example you want to email error logs to yourself in production, but you don't want to spam your inbox while developing. - -```swift -let drop = Droplet() - -drop.addConfigurable(log: MyEmailLogger.self, name: "email") -``` - -The `Droplet` will continue to use the default logger until you modify the `Config/droplet.json` file to point to your email logger. If this is done in `Config/production/droplet.json`, then your logger will only be used in production. - -```json -{ - "log": "email" -} -``` - -## Initialization - -The `Droplet` init method is fairly simple since most properties are variable and can be changed after initialization. - -Most plugins for Vapor come with a [Provider](provider.md), these take care of configuration details for you. - -```swift -Droplet( - arguments: [String]?, - workDir workDirProvided: String?, - config configProvided: Config?, - localization localizationProvided: Localization?, -) -``` - -> Note: Remember that the Droplet's properties are initialized with usable defaults. This means that if you change a property, you must be sure to change it _before_ other parts of your code use it. Otherwise, you may end up with confusing results as the defaults are used sometimes, and your overrides are used other times. - diff --git a/1.5/guide/folder-structure.md b/1.5/guide/folder-structure.md deleted file mode 100755 index 434792ca..00000000 --- a/1.5/guide/folder-structure.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -currentMenu: guide-folder-structure ---- - -# Folder Structure - -The first step to creating an awesome application is knowing where things are. If you created your project using the [Toolbox](../getting-started/toolbox.md) or from a template, you will already have the folder structure created. - -If you are making a Vapor application from scratch, this will show you exactly how to set it up. - -## Minimum Folder Structure - -We recommend putting all of your Swift code inside of the `App/` folder. This will allow you to create subfolders in `App/` to organize your models and resources. - -This works best with the Swift package manager's restrictions on how packages should be structured. - -``` -. -├── App -│ └── main.swift -├── Public -└── Package.swift -``` - -The `Public` folder is where all publicly accessible files should go. This folder will be automatically checked every time a URL is requested that is not found in your routes. - -> Note: The `FileMiddleware` is responsible for accessing files from the `Public` folder. - -## Models - -The `Models` folder is a recommendation of where you can put your database and other models, following the MVC pattern. - -``` -. -├── App -. └── Models -. └── User.swift -``` - -## Controllers - -The `Controllers` folder is a recommendation of where you can put your route controllers, following the MVC pattern. - -``` -. -├── App -. └── Controllers -. └── UserController.swift -``` - -## Views - -The `Views` folder in `Resources` is where Vapor will look when you render views. - -``` -. -├── App -└── Resources - └── Views - └── user.html -``` - -The following code would load the `user.html` file. - -```swift -drop.view.make("user.html") -``` - -## Config - -Vapor has a sophisticated configuration system that involves a hierarchy of configuration importance. - -``` -. -├── App -└── Config - └── app.json // default app.json - └── development - └── app.json // overrides app.json when in development environment - └── production - └── app.json // overrides app.json when in production environment - └── secrets - └── app.json // overrides app.json in all environments, ignored by git -``` - -`.json` files are structured in the `Config` folder as shown above. The configuration will be applied dependant on where the `.json` file exists in the hierarchy. Learn more in [Config](config.md). - -Learn about changing environments (the `--env=` flag) in the [Droplet](droplet.md) section. diff --git a/1.5/guide/hash.md b/1.5/guide/hash.md deleted file mode 100755 index b28bd299..00000000 --- a/1.5/guide/hash.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -currentMenu: guide-hash ---- - -# Hash - -Vapor makes hashing strings easy. - -## Example - -To hash a string, use the `hash` class on `Droplet`. - -```swift -let hashed = drop.hash.make("vapor") -``` - -## SHA2Hasher - -By default, Vapor uses a SHA2Hasher with 256 bits. You can change this by giving the `Droplet` a different hasher. - -```swift -let sha512 = SHA2Hasher(variant: .sha512) - -let drop = Droplet(hash: sha512) -``` - -### Protocol - -You can also create your own hasher. You just need to conform to the `Hash` protocol. - -```swift -public protocol Hash: class { - var key: String { get set } - func make(_ string: String) -> String -} -``` diff --git a/1.5/guide/json.md b/1.5/guide/json.md deleted file mode 100755 index 8b6d4fd9..00000000 --- a/1.5/guide/json.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -currentMenu: guide-json ---- - -# JSON - -JSON is an integral part of Vapor. It powers Vapor's [Config](config.md) and is incredibly easy to use in both requests and responses. - -## Request - -JSON is automatically available in `request.data` alongside form-urlencoded data and query data. This allows you to focus on making a great API, not worrying about what content types data will be sent in. - -```swift -drop.get("hello") { request in - guard let name = request.data["name"]?.string else { - throw Abort.badRequest - } - return "Hello, \(name)!" -} -``` - -This will return a greeting for any HTTP method or content type that the `name` is sent as, including JSON. - -### JSON Only - -To specifically target JSON, use the `request.json` property. - -```swift -drop.post("json") { request in - guard let name = request.json?["name"]?.string else { - throw Abort.badRequest - } - - return "Hello, \(name)!" -} -``` -The above snippet will only work if the request is sent with JSON data. - -## Response - -To respond with JSON, simply wrap your data structure with `JSON(node: )` - -```swift -drop.get("version") { request in - return try JSON(node: [ - "version": "1.0" - ]) -} -``` - -## Middleware - -The `JSONMiddleware` is included in the `Droplet`'s middleware by default. You can remove it if you don't want JSON to be parsed. diff --git a/1.5/guide/leaf.md b/1.5/guide/leaf.md deleted file mode 100755 index 7cd8ae77..00000000 --- a/1.5/guide/leaf.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -currentMenu: guide-leaf ---- - -# Leaf - -Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There's a lot of great templating languages, use what's best for you, maybe that's Leaf! The goals of Leaf are as follows: - -- Small set of strictly enforced rules -- Consistency -- Parser first mentality -- Extensibility - -## Syntax -### Structure - -Leaf Tags are made up of 4 Elements: - - Token: `#` is the Token - - Name: A `string` that identifies the tag - - Parameter List: `()` May accept 0 or more arguments - - Body (optional): `{}` Must be separated from the Parameter List by a space - -There can be many different usages of these 4 elements depending on the Tag's implementation. Let's look at a few examples of how Leaf's built-in Tags might be used: - - - `#()` - - `#(variable)` - - `#import("template")` - - `#export("link") { }` - - `#index(friends, "0")` - - `#loop(friends, "friend") {
  • #(friend.name)
  • }` - - `#raw() { Anything goes!@#$%^&* }` - -### Using the `#` token in HTML - -The `#` token cannot be escaped. Use the `#()` or `#raw() {}` Tag to output a `#` in a Leaf Template. `#()` => `#` - -### Raw HTML - -All Leaf output is escaped by default. Use the `#raw() {}` Tag for unescaped output. -`#raw() { Link }` => `Link` -> IMPORTANT! Make sure you are not using the `#raw() {}` Tag with user input. - -### Chaining - -The double token: `##` indicates a chain. It can be applied to any standard Tag. If the previous Tag fails, the chained Tag will be given an opportunity to run. - -``` -#if(hasFriends) ##embed("getFriends") -``` - -### Leaf's built-in Tags - -#### Token: `#()` - -``` -#() #()hashtags #()FTW => # #Hashtags #FTW -``` - -#### Raw: `#raw() {}` - -``` -#raw() { - Do whatever w/ #'s here, this code won't be rendered as leaf document and is not escaped. - It's a great place for things like Javascript or large HTML sections. -} -``` - -#### Equal: `#equal(lhs, rhs) {}` - -``` -#equal(leaf, leaf) { Leaf == Leaf } => Leaf == Leaf -#equal(leaf, mustache) { Leaf == Mustache } => -``` - -#### Variable: `#(variable)` - -``` -Hello, #(name)! -``` - -#### Loop: `#loop(object, "index")` - -``` -#loop(friends, "friend") { - Hello, #(friend.name)! -} -``` -#### Index: `#index(object, _ index: Int|String)` - -``` -Hello, #index(friends, 0)! -Hello, #index(friends, "best")! -``` - -#### If - Else: `#if(bool) ##else() { this }` - -``` -#if(entering) { - Hello, there! -} ##if(leaving) { - Goodbye! -} ##else() { - I've been here the whole time. -} -``` - -#### Import: `#import("template")` -#### Export: `#export("template") { Leaf/HTML }` -#### Extend: `#extend("template")` -#### Embed: `#embed("template")` - -> When using these Layout Tags, omit the template file's .leaf extension. - -``` -/// base.leaf - -#import("html") - -/// html.leaf -#extend("base") - -#export("html") { #embed("body") } - -/// body.leaf - -``` - -Leaf renders `html.leaf` as: - -``` - - -``` - -### Custom Tags - -Look at the existing tags for advanced scenarios, let's look at a basic example by creating `Index` together. This tag will take two arguments, an array, and an index to access. - -```swift -class Index: BasicTag { - let name = "index" - - func run(arguments: [Argument]) throws -> Node? { - guard - arguments.count == 2, - let array = arguments[0].value?.nodeArray, - let index = arguments[1].value?.int, - index < array.count - else { return nil } - return array[index] - } -} -``` - -We can now register this Tag in our `main.swift` file with: - -```swift -if let leaf = drop.view as? LeafRenderer { - leaf.stem.register(Index()) -} -``` - -And use it just like we did [above](#index). - -> Note: Use of non-alphanumeric characters in Tag Names is **strongly discouraged** and may be disallowed in future versions of Leaf. - -## Syntax Highlighting - -### Atom - -[language-leaf](https://atom.io/packages/language-leaf) by ButkiewiczP - -### Xcode - -It is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when `vapor xcode` is run. - -There appears to be a way to [make Xcode file associations persist](http://stackoverflow.com/questions/9050035/how-to-make-xcode-recognize-a-custom-file-extension-as-objective-c-for-syntax-hi) but that requires a bit more kung-fu. - -### VS Code - -[html-leaf](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf) by FranciscoAmado - -### CLion & AppCode - -Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on [Vapor Slack](http://vapor.team) diff --git a/1.5/guide/middleware.md b/1.5/guide/middleware.md deleted file mode 100755 index 00ac5c91..00000000 --- a/1.5/guide/middleware.md +++ /dev/null @@ -1,251 +0,0 @@ ---- -currentMenu: guide-middleware ---- - -# Middleware - -Middleware is an essential part of any modern web framework. It allows you to modify requests and responses as they pass between the client and your server. - -You can imagine middleware as a chain of logic connecting your server to the client requesting your web app. - -## Basic - -As an example, let's create a middleware that will add the version of our API to each response. The middleware would look something like this: - -```swift -final class VersionMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - let response = try next.respond(to: request) - - response.headers["Version"] = "API v1.0" - - return response - } -} -``` - -We then supply this middleware to our `Droplet`. - -```swift -let drop = Droplet() -drop.middleware.append(VersionMiddleware()) -``` - -You can imagine our `VersionMiddleware` sitting in the middle of a chain that connects the client and our server. Every request and response that hits our server must go through this chain of middleware. - -![Middleware](https://cloud.githubusercontent.com/assets/1342803/17382676/0b51d6d6-59a0-11e6-9cbb-7585b9ab9803.png) - - -## Breakdown - -Let's break down the middleware line by line. - -```swift -let response = try next.respond(to: request) -``` - -Since the `VersionMiddleware` in this example is not interested in modifying the request, we immediately ask the next middleware in the chain to respond to the request. This goes all the way down the chain to the `Droplet` and comes back with the response that should be sent to the client. - -```swift -response.headers["Version"] = "API v1.0" -``` - -We then _modify_ the response to contain a Version header. - -```swift -return response -``` - -The response is returned and will chain back up any remaining middleware and back to the client. - -## Request - -The middleware can also modify or interact with the request. - -```swift -func respond(to request: Request, chainingTo next: Responder) throws -> Response { - guard request.cookies["token"] == "secret" else { - throw Abort.badRequest - } - - return try next.respond(to: request) -} -``` - -This middleware will require that the request has a cookie named `token` that equals `secret` or else the request will be aborted. - -## Errors - -Middleware is the perfect place to catch errors thrown from anywhere in your application. When you let the middleware catch errors, you can remove a lot of duplicated logic from your route closures. Take a look at the following example: - -```swift -enum FooError: Error { - case fooServiceUnavailable -} -``` - -Say there is a custom error that either you defined or one of the APIs you are using `throws`. This error must be caught when thrown, or else it will end up as a server error which may be unexpected to a user. The most obvious solution is to catch the error in the route closure. - -```swift -app.get("foo") { request in - let foo: Foo - do { - foo = try getFooFromService() - } catch { - throw Abort.badRequest - } - - // continue with Foo object -} -``` - -This solution works, but it would get repetitive if repeated throughout multiple routes. It can also easily lead to code duplication. Luckily, this error could be caught in a middleware instead. - -```swift -final class FooErrorMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - do { - return try next.respond(to: request) - } catch FooError.fooServiceUnavailable { - throw Abort.custom( - status: .badRequest, - message: "Sorry, we were unable to query the Foo service." - ) - } - } -} -``` - -We just need to append this middleware to the `Droplet`. - -```swift -drop.middleware.append(FooErrorMiddleware()) -``` - -Now our route closures look a lot better and we don't have to worry about code duplication. - -```swift -app.get("foo") { request in - let foo = try getFooFromService() - - // continue with Foo object -} -``` - -Interestingly, this is how `Abort` itself is implemented in Vapor. `AbortMiddleware` catches any `Abort` errors and returns a JSON response. Should you want to customize how `Abort` errors appear, you can remove this middleware and add your own. - -## Configuration - -Appending middleware to the `drop.middleware` array is the simplest way to add middleware--it will be used every time the application starts. - -You can also use the [configuration](config.md) files to enabled or disable middleware for more control. This is especially useful if you have middleware that should, for example, run only in production. - -Appending configurable middleware looks like the following: - -```swift -let drop = Droplet() -drop.addConfigurable(middleware: myMiddleware, name: "my-middleware") -``` - -Then, in the `Config/droplet.json` file, add `my-middleware` to the appropriate `middleware` array. - -```json -{ - ... - "middleware": { - "server": [ - ... - "my-middleware", - ... - ], - "client": [ - ... - ] - }, - ... -} -``` - -If the name of the added middleware appears in the `server` array for the loaded configuration, it will be added to the server's middleware when the application boots. - -Likewise, if the middleware appears in the `client` array for the loaded configuration, it will be added to the client's middleware. - -One middleware can be appended to both the Client and the Server, and can be added multiple times. The ordering of middleware is respected. - -## Extensions (Advanced) - -Middleware pairs great with request/response extensions and storage. - -```swift -final class PokemonMiddleware: Middleware { - let drop: Droplet - init(drop: Droplet) { - self.drop = drop - } - - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - let response = try next.respond(to: request) - - if let pokemon = response.pokemon { - if request.accept.prefers("html") { - response.view = try drop.view("pokemon.mustache", context: pokemon) - } else { - response.json = try pokemon.makeJSON() - } - } - - return response - } -} -``` - -And the extension to `Response`. - -```swift -extension Response { - var pokemon: Pokemon? { - get { return storage["pokemon"] as? Pokemon } - set { storage["pokemon"] = newValue } - } -} -``` - -In this example, we added a new property to response capable of holding a Pokémon object. If the middleware finds a response with one of these Pokémon objects, it will dynamically check whether the client prefers HTML. If the client is a browser like Safari and prefers HTML, it will return a Mustache view. If the client does not prefer HTML, it will return JSON. - -Your closures can now look something like this: - -```swift -import HTTP - -drop.get("pokemon", Pokemon.self) { request, pokemon in - let response = Response() - response.pokemon = pokemon - return response -} -``` - -Or, if you want to go a step further, you can make `Pokemon` conform to `ResponseRepresentable`. - -```swift -import HTTP - -extension Pokemon: ResponseRepresentable { - func makeResponse() throws -> Response { - let response = Response() - response.pokemon = self - return response - } -} -``` - -Now your route closures are greatly simplified and you don't need to `import HTTP`. - -```swift -drop.get("pokemon", Pokemon.self) { request, pokemon in - return pokemon -} -``` - -Middleware is incredibly powerful. Combined with extensions, it allows you to add functionality that feels native to the framework. - -For those that are curious, this is how Vapor manages JSON internally. Whenever you return JSON in a closure, it sets the `json: JSON?` property on `Response`. The `JSONMiddleware` then detects this property and serializes the JSON into the body of the response. diff --git a/1.5/guide/provider.md b/1.5/guide/provider.md deleted file mode 100755 index 2a4b5920..00000000 --- a/1.5/guide/provider.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -currentMenu: guide-provider ---- - -# Provider - -The `Provider` protocol creates a simple and predictable way for adding functionality and third party packages to your Vapor project. - -## Adding a Provider - -Adding a provider to your application takes 2-3 steps. - -### Add Package - -All of Vapor's providers end with the `-provider` syntax. You can see a list of [available providers](https://github.com/vapor?utf8=✓&q=-provider) by searching on our GitHub. - -To add the provider to your package, add it as a dependency in your `Package.swift` file. - -```swift -let package = Package( - name: "MyApp", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 0), - .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 1, minor: 0) - ] -) -``` - -> It's important to `vapor clean` or `vapor build --clean` after adding new packages. - -### Import - -Once the provider has been added, you can import it using `import VaporFoo` where `Foo` is the name of the provider. - -Here is what importing the MySQL provider looks like: - -```swift -import Vapor -import VaporMySQL - -let drop = Droplet() - -try drop.addProvider(VaporMySQL.Provider.self) - -// ... - -drop.run() -``` - -Every provider comes with a class named `Provider`. Append the `Type` of this class to your `providers` array in the `Droplet`'s init method. - -### Config - -Some drivers may require a configuration file. For example, `VaporMySQL` requires a `Config/mysql.json` file like the following: - -```json -{ - "host": "localhost", - "user": "root", - "password": "", - "database": "vapor" -} -``` - -You will receive an error during the `Droplet`'s initialization if a configuration file is required. - -## Advanced - -You may choose to initialize the provider yourself. - -```swift -import Vapor -import VaporMySQL - -let drop = Droplet() - -let mysql = try VaporMySQL.Provider(host: "localhost", user: "root", password: "", database: "vapor") -drop.addProvider(mysql) - -... - -drop.run() -``` - -## Create a Provider - -Creating a provider is easy, you just need to create a package with a class `Provider` that conforms to `Vapor.Provider`. - -### Example - -Here is what a provider for an example `Foo` package would look like. All the provider does is take a message, then print the message when the `Droplet` starts. - -```swift -import Vapor - -public final class Provider: Vapor.Provider { - public let message: String - public let provided: Providable - - public convenience init(config: Config) throws { - guard let message = config["foo", "message"].string else { - throw SomeError - } - - try self.init(message: message) - } - - public init(message: String) throws { - self.message = message - } - - public func afterInit(_ drop: Droplet) { - - } - - public func beforeServe(_ drop: Droplet) { - drop.console.info(message) - } -} -``` - -This provider wil require a `Config/foo.json` file that looks like: - -```json -{ - "message": "The message to output" -} -``` - -The provider can also be initialized manually with the `init(message: String)` init. diff --git a/1.5/guide/sessions.md b/1.5/guide/sessions.md deleted file mode 100755 index efac34cc..00000000 --- a/1.5/guide/sessions.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -currentMenu: guide-sessions ---- - -# Sessions - -Sessions help you store information about a user between requests. As long as the client supports cookies, sessions are easy to create. - -## Middleware - -Enable sessions on your `Droplet` by adding an instance of `SessionMiddleware`. - -```swift -import Sessions - -let memory = MemorySessions() -let sessions = SessionsMiddleware(sessions: memory) -``` - -Then add to the `Droplet`. - -``` -let drop = Droplet() -drop.middleware.append(sessions) -``` - -> Note: If you'd like to enable or disable the middleware based on config files, check out [middleware](../guide/middleware.md). - -## Request - -After `SessionMiddleware` has been enabled, you can access the `req.sessions()` method to get access to session data. - -```swift -let data = try req.session().data -``` - -## Example - -Let's create an example that remembers the user's name. - -### Store - -```swift -drop.post("remember") { req in - guard let name = req.data["name"]?.string else { - throw Abort.badRequest - } - - try req.session().data["name"] = Node.string(name) - - return "Remebered name." -} -``` - -On `POST /remember`, fetch a `name` from the request input, then store this name into the session data. - -### Fetch - -On `GET /remember`, fetch the `name` from the session data and return it. - -```swift -drop.get("remember") { req in - guard let name = try req.session().data["name"]?.string else { - return throw Abort.custom(status: .badRequest, message: "Please POST the name first.") - } - - return name -} -``` - -## Cookie - -The session will be stored using the `vapor-session` cookie. - - - - diff --git a/1.5/guide/validation.md b/1.5/guide/validation.md deleted file mode 100755 index 5f2ce467..00000000 --- a/1.5/guide/validation.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -currentMenu: guide-validation ---- - -# Validation - -Vapor provides a few different ways to validate data coming into your application. Let's start by looking at the most common. - -## Common Usage - -Several useful convenience validators are included by default. You can use these to validate data coming into your application, or combine them and create your own. - -Let's look at the most common way to validate data. - -```swift -class Employee { - var email: Valid - var name: Valid - - init(request: Request) throws { - name = try request.data["name"].validated() - email = try request.data["email"].validated() - } -} -``` - -Here we have a typical Employee model with an `email` and `name` property. By declaring both of these properties as `Valid<>`, you are ensuring that these properties can only ever contain valid data. The Swift type checking system will prevent anything that does not pass validation from being stored. - -To store something in a `Valid<>` property, you must use the `.validated()` method. This is available for any data returned by `request.data`. - -`Email` is a real `validator` included with Vapor, but `Name` is not. Let's take a look at how you can create a Validator. - -```swift -Valid -Valid -Valid> -Valid> -Valid> -Valid> -Valid> -``` - -## Validators vs. ValidationSuites - -Validators, like `Count` or `Contains` can have multiple configurations. For example: - -```swift -let name: Valid> = try "Vapor".validated(by: Count.max(5)) -``` - -Here we are validating that the `String` is at most 5 characters long. The type of `Valid` tells us that the string has been validated to be a certain count, but it does not tell us exactly what that count was. The string could have been validated to be less than three characters or more than one million. - -Because of this, `Validators` themselves are not as type safe as some applications might desire. `ValidationSuites` fix this. They combine multiple `Validators` and/or `ValidationSuites` together to represent exactly what type of data should be considered valid. - -## Custom Validator - -Here is how to create a custom `ValidationSuite`. - -```swift -class Name: ValidationSuite { - static func validate(input value: String) throws { - let evaluation = OnlyAlphanumeric.self - && Count.min(5) - && Count.max(20) - - try evaluation.validate(input: value) - } -} -``` - -You only have to implement one method. In this method, use any other validators or logic to create your custom validator. Here we are defining a `Name` as only accepting alphanumeric Strings that are between 5 and 20 characters. - -Now we can be sure that anything of type `Valid` follows these rules. - -## Combining Validators - -In the `Name` validator, you can see that `&&` is being used to combine validators. You can use `&&` as well as `||` to combine any validator as you would boolean values with an `if` statement. - -You can also use `!` to invert the validator. - -```swift -let symbols = input.validated(by: !OnlyAlphanumeric.self) -``` - -## Testing Validity - -While `validated() throw` is the most common method for validating, there are two others. - -```swift -let passed = input.passes(Count.min(5)) -let valid = try input.tested(Count.min(5)) -``` - -`passes()` returns a boolean indicating whether or not the test passed. `tested()` will throw if the validation does not pass. But unlike `validated()` which returns a `Valid<>` type, `tested()` returns the original type of the item it was called on. - -## Validation Failures - -Vapor will automatically catch validation failures in the `ValidationMiddleware`. But you can catch them on your own, or customize responses for certain types of failures. - -```swift -do { - //validation here -} catch let error as ValidationErrorProtocol { - print(error.message) -} -``` diff --git a/1.5/guide/views.md b/1.5/guide/views.md deleted file mode 100755 index a9053c0c..00000000 --- a/1.5/guide/views.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -currentMenu: guide-views ---- - -# Views - -Views return HTML data from your application. They can be created from pure HTML documents or passed through renderers such as Mustache or Stencil. - -## Views Directory - -Views are stored in `Resources/Views`. They are created by calling the `view` method on `Droplet`. - -## HTML - -Returning HTML, or any other non-rendered document, is simple. Just use the path of the document relative to the views directory. - -```swift -drop.get("html") { request in - return try drop.view.make("index.html") -} -``` - -## Templating - -Templated documents like [Leaf](./leaf.html), Mustache, or Stencil can take a `Context`. - -```swift -drop.get("template") { request in - return try drop.view.make("welcome", [ - "message": "Hello, world!" - ]) -} -``` - -## Public Resources - -Any resources that your views need, such as images, styles, and scripts, should be placed in the `Public` folder at the root of your application. - -## View Renderer - -Any class that conforms to `ViewRenderer` can be added to our droplet. - -```swift -let drop = Droplet() - -drop.view = LeafRenderer(viewsDir: drop.viewsDir) -``` - -## Available Renderers - -These renderers can be added to your application through [Providers](provider.md). - -- [Leaf](https://github.com/vapor/leaf) -- [Mustache](https://github.com/vapor/mustache-provider) diff --git a/1.5/http/body.md b/1.5/http/body.md deleted file mode 100755 index 3d867dce..00000000 --- a/1.5/http/body.md +++ /dev/null @@ -1,141 +0,0 @@ ---- -currentMenu: http-body ---- - -> Module: `import HTTP` - -# Body - -The `HTTP.Body` represents the payload of an `HTTP.Message`, and is used to pass the underlying data. Some examples of this in practice would be `JSON`, `HTML` text, or the bytes of an image. Let's look at the implementation: - -```swift -public enum Body { - case data(Bytes) - case chunked((ChunkStream) throws -> Void) -} -``` - -## Data Case - -The `data` case is by far the most common use for a `Body` in an `HTTP.Message`. It is simply an array of bytes. The serialization protocol or type associated with these bytes is usually defined by the `Content-Type` header. Let's look at some examples. - -### Application/JSON - -If our `Content-Type` header contains `application/json`, then the underlying bytes represent serialized JSON. - -```swift -if let contentType = req.headers["Content-Type"], contentType.contains("application/json"), let bytes = req.body.bytes { - let json = try JSON(bytes: bytes) - print("Got JSON: \(json)") -} -``` - -### Image/PNG - -If our `Content-Type` contains `image/png`, then the underlying bytes represent an encoded png. - -```swift -if let contentType = req.headers["Content-Type"], contentType.contains("image/png"), let bytes = req.body.bytes { - try database.save(image: bytes) -} -``` - -## Chunked Case - -The `chunked` case only applies to outgoing `HTTP.Message`s in Vapor. It is traditionally a responder's role to collect an entire chunked encoding before passing it on. We can use this to send a body asynchronously. - -```swift -let body: Body = Body.chunked(sender) -return Response(status: .ok, body: body) -``` - -We can implement this manually, or use Vapor's built in convenience initializer for chunked bodies: - -```swift -return Response(status: .ok) { chunker in - for name in ["joe", "pam", "cheryl"] { - sleep(1) - try chunker.send(name) - } - - try chunker.close() -} -``` - -> Make sure to call `close()` before the chunker leaves scope. - -## BodyRepresentable - -In addition to the concrete `Body` type, as is common in Vapor, we also have wide support for `BodyRepresentable`. This means objects that we're commonly converting to `Body` type can be used interchangeably. For example: - -```swift -return Response(body: "Hello, World!") -``` - -In the above example, string is converted to bytes and added to the body. - -> In practice, it is better to use `return "Hello, World!"`. Vapor will automatically be able to set the `Content-Type` to appropriate values. - -Let's look at how it's implemented: - -```swift -public protocol BodyRepresentable { - func makeBody() -> Body -} -``` - -### Custom - -We can conform our own types to this as well where applicable. Let's pretend we have a custom data type, `.vpr`. Let's conform our `VPR` file type model: - -```swift -extension VPRFile: HTTP.BodyRepresentable { - func makeBody() -> Body { - // collect bytes - return .data(bytes) - } -} -``` - -> You may have noticed above, that the protocol throws, but our implementation does not. This is completely valid in Swift and will allow you to not throw if you're ever calling the function manually. - -Now we're able to include our `VPR` file directly in our `Responses`. - -```swift -drop.get("files", ":file-name") { request in - let filename = try request.parameters.extract("file-name") as String - let file = VPRFileManager.fetch(filename) - return Response(status: .ok, headers: ["Content-Type": "file/vpr"], body: file) -} -``` - -In practice, if we're repeating this often, we'll probably conform `VPRFile` directly to `ResponseRepresentable` - -```swift -extension VPRFile: HTTP.ResponseRepresentable { - func makeResponse() -> Response { - return Response( - status: .ok, - headers: ["Content-Type": "file/vpr"], - body: file - ) - } -} -``` - -Here's our above example now: - -```swift -drop.get("files", ":file-name") { request in - let filename = try request.parameters.extract("file-name") as String - return VPRFileManager.fetch(filename) -} -``` - -We could also use type-safe routing to make this even more concise: - -```swift -drop.get("files", String.self) { request, filename in - return VPRFileManager.fetch(filename) -} -``` diff --git a/1.5/http/client.md b/1.5/http/client.md deleted file mode 100755 index 9b4ae503..00000000 --- a/1.5/http/client.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -currentMenu: http-client ---- - -> Module: `import HTTP` - -# Client - -The client provided by `HTTP` is used to make outgoing requests to remote servers. Let's look at a simple outgoing request. - -## QuickStart - -Let's jump right in to make a simple HTTP Request. Here's a basic `GET` request using your Vapor `Droplet`. - -```swift -let query = ... -let spotifyResponse = try drop.client.get("https://api.spotify.com/v1/search?type=artist&q=\(query)") -print(spotifyR) -``` - -### Clean Up - -The url above can be a little tricky to read, so let's use the query parameter to clean it up a little bit: - -```swift -try drop.client.get("https://api.spotify.com/v1/search", query: ["type": "artist", "q": query]) -``` - -### Continued - -In addition to `GET` requests, Vapor's client provides support for most common HTTP functions. `GET`, `POST`, `PUT`, `PATCH`, `DELETE` - -### POST as json -```swift -try drop.client.post("http://some-endpoint/json", headers: ["Content-Type": "application/json"], body: myJSON.makeBody()) -``` - -### POST as x-www-form-urlencoded -```swift -try drop.client.post("http://some-endpoint", headers: [ - "Content-Type": "application/x-www-form-urlencoded" -], body: Body.data( Node(node: [ - "email": "mymail@vapor.codes" -]).formURLEncoded())) -``` - -### Full Request - -To access additional functionality or custom methods, use the underlying `request` function directly. - -```swift -public static func get(_ method: Method, - _ uri: String, - headers: [HeaderKey: String] = [:], - query: [String: CustomStringConvertible] = [:], - body: Body = []) throws -> Response -``` - -For example: - -```swift -try drop.client.request(.other(method: "CUSTOM"), "http://some-domain", headers: ["My": "Header"], query: ["key": "value"], body: []) -``` - -## Config - -The `Config/clients.json` file can be used to modify the client's settings. - -### TLS - -Host and certificate verification can be disabled. - -> Note: Use extreme caution when modifying these settings. - -```json -{ - "tls": { - "verifyHost": false, - "verifyCertificates": false - } -} -``` - -### Mozilla - -The Mozilla certificates are included by default to make fetching content from secure sites easy. - -```json -{ - "tls": { - "certificates": "mozilla" - } -} -``` - -## Advanced - -In addition to our Droplet, we can also use and interact with the `Client` manually. Here's how our default implementation in Vapor looks: - -```swift -let response = try Client.get("http://some-endpoint/mine") -``` - -The first thing we likely noticed is `TCPClientStream` being used as a Generic value. This will be the underlying connection that the `HTTP.Client` can use when performing the request. By conforming to the underlying `ClientStream`, an `HTTP.Client` can accept custom stream implementations seamlessly. - -## Save Connection - -Up to this point, we've been interacting with the Client via `class` or `static` level functions. This allows us to end the connection upon a completed request and is the recommended interaction for most use cases. For some advanced situations, we may want to reuse a connection. For these, we can initialize our client and perform multiple requests like this. - -```swift -let pokemonClient = try drop?.client.make(scheme: "http", host: "pokeapi.co") -for i in 0...1 { - let response = try pokemonClient?.get(path: "/api/v2/pokemon/", query: ["limit": 20, "offset": i]) - print("response: \(response)") -} -``` - -## ClientProtocol - -Up to this point, we've focused on the built in `HTTP.Client`, but users can also include their own customized clients by conforming to `HTTP.ClientProtocol`. Let's look at the implementation: - -```swift -public protocol Responder { - func respond(to request: Request) throws -> Response -} - -public protocol Program { - var host: String { get } - var port: Int { get } - var securityLayer: SecurityLayer { get } - // default implemented - init(host: String, port: Int, securityLayer: SecurityLayer) throws -} - -public protocol ClientProtocol: Program, Responder { - var scheme: String { get } - var stream: Stream { get } - init(scheme: String, host: String, port: Int, securityLayer: SecurityLayer) throws -} -``` - -By conforming to these underlying functions, we immediately gain access to the public `ClientProtocol` apis we viewed above. - -## Customize Droplet - -If we've introduced a custom conformance to `HTTP.ClientProtocol`, we can pass this into our droplet without changing the underlying behavior in our application. - -For example: - -```swift -let drop = Droplet() - -drop.client = MyCustomClient.self -``` - -Going forward, all of your calls to `drop.client` will use `MyCustomClient.self`: - -```swift -drop.client.get(... // uses `MyCustomClient` -``` diff --git a/1.5/http/cors.md b/1.5/http/cors.md deleted file mode 100755 index f30d3bb7..00000000 --- a/1.5/http/cors.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -currentMenu: http-cors ---- - -# CORS - -Vapor by default provides a middleware for implementing proper support for Cross-Origin Resource Sharing (CORS) named `CORSMiddleware`. - -"Cross-Origin Resource Sharing (CORS) is a specification that enables truly open access across domain-boundaries. If you serve public content, please consider using CORS to open it up for universal JavaScript / browser access." - [http://enable-cors.org/](http://enable-cors.org/) - -To learn more about middlewares, please visit the Middleware section of the documentation [here](https://vapor.github.io/documentation/guide/middleware.html). - -![](https://upload.wikimedia.org/wikipedia/commons/c/ca/Flowchart_showing_Simple_and_Preflight_XHR.svg) -*Image Author: [Wikipedia](https://commons.wikimedia.org/wiki/File:Flowchart_showing_Simple_and_Preflight_XHR.svg)* - -## Basic - -First of all, add the CORS middleware into your droplet middlewares array. - -```swift -# Insert CORS before any other middlewares -drop.middleware.insert(CORSMiddleware(), at: 0) -``` - -> Note: Make sure you insert CORS middleware before any other throwing middlewares, like the AbortMiddleware or similar. Otherwise the proper headers might not be added to the response. - -`CORSMiddleware` has a default configuration which should suit most users, with values as follows: - -- **Allowed Origin** - - Value of origin header in the request. -- **Allowed Methods** - - `GET`, `POST`, `PUT`, `OPTIONS`, `DELETE`, `PATCH` -- **Allowed Headers** - - `Accept`, `Authorization`, `Content-Type`, `Origin`, `X-Requested-With` - -## Advanced - -All settings and presets can be customized by advanced users. There's two ways of doing this, either you programatically create and configure a `CORSConfiguration` object or you can put your configuration into a Vapor's JSON config file. - -See below for how to set up both and what are the options. - -### Configuration - -The `CORSConfiguration` struct is used to configure the `CORSMiddleware`. You can instanitate one like this: - -```swift -let configuration = CORSConfiguration(allowedOrigin: .custom("https://vapor.codes"), - allowedMethods: [.get, .post, .options], - allowedHeaders: ["Accept", "Authorization"], - allowCredentials: false, - cacheExpiration: 600, - exposedHeaders: ["Cache-Control", "Content-Language"]) -``` - -After creating a configuration you can add the CORS middleware. - -```swift -drop.middleware.insert(CORSMiddleware(configuration: configuration), at: 0) -``` - -> Note: Please consult the documentation in the source code of the `CORSConfiguration` for more information about available values for the settings. - - -### JSON Config - -Optionally, `CORSMiddleware` can be configured using the Vapor's `Config` which is created out of the json files contained in your Config folder. You will need to create a file called `cors.json` or `CORS.json` in your Config folder in your project and add the required keys. - -Example of how such a file could look as follows: - -```swift -{ - "allowedOrigin": "origin", - "allowedMethods": "GET,POST,PUT,OPTIONS,DELETE,PATCH", - "allowedHeaders": ["Accept", "Authorization", "Content-Type", "Origin", "X-Requested-With"] -} - -``` - -> Note: Following keys are required: `allowedOrigin`, `allowedMethods`, `allowedHeaders`. If they are not present an error will be thrown while instantiating the middleware. -> -> Optionally you can also specify the keys `allowCredentials` (Bool), `cacheExpiration` (Int) and `exposedHeaders` ([String]). - -Afterwards you can add the middleware using the a throwing overload of the initialiser that accepts Vapor's `Config`. - -```swift -let drop = Droplet() - -do { - drop.middleware.insert(try CORSMiddleware(configuration: drop.config), at: 0) -} catch { - fatalError("Error creating CORSMiddleware, please check that you've setup cors.json correctly.") -} -``` - diff --git a/1.5/http/request.md b/1.5/http/request.md deleted file mode 100755 index f3d5b60e..00000000 --- a/1.5/http/request.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -currentMenu: http-request ---- - -> Module: `import HTTP` - -# Request - -The most common part of the `HTTP` library we'll be interacting with is the `Request` type. Here's a look at some of the most commonly used attributes in this type. - -```swift -public var method: Method -public var uri: URI -public var parameters: Node -public var headers: [HeaderKey: String] -public var body: Body -public var data: Content -``` - -### Method - -The HTTP `Method` associated with the `Request`, ie: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. - -### URI - -The associated `URI` of the request. We will use this to access attributes about the `uri` the request was sent to. - -For example, given the following uri: `http://vapor.codes/example?query=hi#fragments-too` - -```swift -let scheme = request.uri.scheme // http -let host = request.uri.host // vapor.codes - -let path = request.uri.path // /example -let query = request.uri.query // query=hi -let fragment = request.uri.fragment // fragments-too -``` - -### Route Parameters - -The url parameters associated with the request. For example, if we have a path registered as `hello/:name/age/:age`, we would be able to access those in our request, like so: - -```swift -let name = request.parameters["name"] // String? -let age = request.parameters["age"]?.int // Int? -``` - -Or, to automatically throw on `nil` or invalid variable, you can also `extract` - -```swift -let name = try request.parameters.extract("name") as String -let age = try request.parameters.extract("age") as Int -``` - -These extract functions can cast to any `NodeInitializable` type, including your own custom types. Make sure to check out [Node](https://github.com/vapor/node) for more info. - -> Note: Vapor also provides type safe routing in the routing section of our docs. - - -### Headers - -These are the headers associated with the request. If you are preparing an outgoing request, this can be used to add your own keys. - -```swift -let contentType = request.headers["Content-Type"] -``` - -Or for outgoing requests: - -```swift -let request = Request ... -request.headers["Content-Type"] = "application/json" -request.headers["Authorization"] = ... my auth token -``` - -#### Extending Headers - -We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. - -```swift -extension HTTP.KeyAccessible where Key == HeaderKey, Value == String { - var customKey: String? { - get { - return self["Custom-Key"] - } - set { - self["Custom-Key"] = newValue - } - } -} -``` - -With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: - -```swift -let customKey = request.headers.customKey - -// or - -let request = ... -request.headers.customKey = "my custom value" -``` - -### Body - -This is the body associated with the request and represents the general data payload. You can view more about body in the associated [docs](./body.md) - -For incoming requests, we'll often pull out the associated bytes like so: - -```swift -let rawBytes = request.body.bytes -``` - -## Content - -Generally when we're sending or receiving requests, we're using them as a way to transport content. For this, Vapor provides a convenient `data` variable associated with the request that prioritizes content in a consistent way. - -For example, say I receive a request to `http://vapor.codes?hello=world`. - -```swift -let world = request.data["hello"]?.string -``` - -This same code will work if I receive a JSON request, for example: - -```json -{ - "hello": "world" -} -``` - -Will still be accessible through data. - -```swift -let world = request.data["hello"]?.string -``` -> Note: Force unwrap should never be used. - -This also applies to multi-part requests and can even be extended to new types such as XML or YAML through middleware. - -If you'd prefer to access given types more explicitly, that's totally fine. The `data` variable is purely opt-in convenience for those who want it. - -## JSON - -To access JSON directly on a given request, use the following: - -```swift -let json = request.json["hello"] -``` - -## Query Parameters - -The same applies to query convenience: - -```swift -let query = request.query?["hello"] // String? -let name = request.query?["name"]?.string // String? -let age = request.query?["age"]?.int // Int? -let rating = request.query?["rating"]?.double // Double? -``` - -## Key Paths - -Key paths work on most Vapor types that can have nested key value objects. Here's a couple examples of how to access given the following json: - -```json -{ - "metadata": "some metadata", - "artists" : { - "href": "http://someurl.com", - "items": [ - { - "name": "Van Gogh", - }, - { - "name": "Mozart" - } - ] - } -} -``` - -We could access the data in the following ways: - -### Metadata - -Access top level values - -```swift -let type = request.data["metadata"].string // "some metadata" -``` - -### Items - -Access nested values - -```swift -let items = request.data["artists", "items"] // [["name": "Van Gogh"], ["name": "Mozart"]] -``` - -### Mixing Arrays and Objects - -Get first artists - -```swift -let first = request.data["artists", "items", 0] // ["name": "Van Gogh"] -``` - -### Array Item - -Get key from array item - -```swift -let firstName = request.data["artists", "items", 0, "name"] // "Van Gogh" -``` - -### Array Comprehension - -We can also smartly map an array of keys, for example, to just get the names of all of the artists, we could use the following - -```swift -let names = request.data["artists", "items", "name"] // ["Van Gogh", "Mozart"] -``` diff --git a/1.5/http/responder.md b/1.5/http/responder.md deleted file mode 100755 index 8533e4dd..00000000 --- a/1.5/http/responder.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -currentMenu: http-responder ---- - -> Module: `import HTTP` - -# Responder - -The `Responder` is a simple protocol defining the behavior of objects that can accept a `Request` and return a `Response`. Most notably in Vapor, it is the core API endpoint that connects the `Droplet` to the `Server`. Let's look at the definition: - -```swift -public protocol Responder { - func respond(to request: Request) throws -> Response -} -``` - -> The responder protocol is most notably related to Droplet and it's relationship with a server. Average users will not likely interact with it much. - -## Simple - -Of course, Vapor provides some conveniences for this, and in practice, we will often call: - -```swift -try drop.run() -``` - -## Manual - -As we just mentioned, the Vapor `Droplet` itself conforms to `Responder`, connecting it to the `Server`. This means if we wanted to serve our droplet manually, we could do: - -```swift -let server = try Server, Serializer>(port: port) -try server.start(responder: droplet) { error in - print("Got error: \(error)") -} -``` - -## Advanced - -We can conform our own objects to `Responder` and pass them to `Servers`. Let's look at an example: - -```swift -final class Responder: HTTP.Responder { - func respond(to request: Request) throws -> Response { - let body = "Hello World".makeBody() - return Response(body: body) - } -} -``` - -This only returns `"Hello World"` for every request, it's most commonly going to be linked with a router of some type. - - -```swift -final class Responder: HTTP.Responder { - let router: Router = ... - - func respond(to request: Request) throws -> Response { - return try router.route(request) - } -} -``` - -We'll then pass this responder to a server and let it go. - -```swift -let server = try Server, Serializer>(port: port) - -print("visit http://localhost:\(port)/") -try server.start(responder: Responder()) { error in - print("Got error: \(error)") -} -``` - -This can be used as a jumping off point for applications looking to implement features manually. - -## Client - -The `HTTP.Client` is itself a `Responder` although, instead of handling the `Request` itself, it passes it on to the underlying URI. diff --git a/1.5/http/response-representable.md b/1.5/http/response-representable.md deleted file mode 100755 index 87f747b5..00000000 --- a/1.5/http/response-representable.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -currentMenu: http-response-representable ---- - -> Module: `import HTTP` - -# ResponseRepresentable - -Traditionally HTTP servers take a `Request` and return a `Response`. Vapor is no different, but we can take advantage of Swift's powerful protocols to be a bit more flexible to the user facing API. - -Let's start with the definition of `ResponseRepresentable` - -```swift -public protocol ResponseRepresentable { - func makeResponse() throws -> Response -} -``` - -By conforming to this protocol, we can more flexibly return things that conform instead of creating the response manually each time. Vapor provides some of these by default. Including (but not limited to): - -### String - -Because string conforms to `ResponseRepresentable`, we can return it directly in a Vapor route handler. - -```swift -drop.get("hello") { request in - return "Hello, World!" -} -``` - -### JSON - -`JSON` can be returned directly instead of recreating a response each time. - -```swift -drop.get("hello") { request in - return try JSON(node: [ - "hello": "world", - "some-numbers": [ - 1, - 2, - 3 - ] - ] - ) -} -``` - -### Response - -Of course, we can also return Responses for anything not covered: - -```swift -drop.get("hello") { request in - return Response(status: .ok, headers: ["Content-Type": "text/plain"], body: "Hello, World!") -} -``` - -## Conforming - -All we need to do to return our own objects is conform them to `ResponseRepresentable`. Let's look at an example type, a simple blog post model: - -```swift -import Foundation - -struct BlogPost { - let id: String - let content: String - let createdAt: NSDate -} -``` - -And now, let's conform it to response representable. - -```swift -import HTTP -import Foundation - -extension BlogPost: ResponseRepresentable { - func makeResponse() throws -> Response { - let json = try JSON(node: - [ - "id": id, - "content": content, - "created-at": createdAt.timeIntervalSince1970 - ] - ) - return try json.makeResponse() - } -} -``` - -> Don't forget to import HTTP. - -Now that we've modeled our BlogPost, we can return it directly in route handlers. - -```swift -drop.post("post") { req in - guard let content = request.data["content"] else { throw Error.missingContent } - let post = Post(content: content) - try post.save(to: database) - return post -} -``` diff --git a/1.5/http/response.md b/1.5/http/response.md deleted file mode 100755 index cd92f05d..00000000 --- a/1.5/http/response.md +++ /dev/null @@ -1,126 +0,0 @@ ---- -currentMenu: http-response ---- - -> Module: `import HTTP` - -# Response - -When building endpoints, we'll often be returning responses for requests. If we're making outgoing requests, we'll be receiving them. - -```swift -public let status: Status -public var headers: [HeaderKey: String] -public var body: Body -public var data: Content -``` - -#### Status - -The http status associated with the event, for example `.ok` == 200 ok. - -#### Headers - -These are the headers associated with the request. If you are preparing an outgoing response, this can be used to add your own keys. - -```swift -let contentType = response.headers["Content-Type"] -``` - -Or for outgoing response: - -```swift -let response = response ... -response.headers["Content-Type"] = "application/json" -response.headers["Authorization"] = ... my auth token -``` - -##### Extending Headers - -We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. - -```swift -extension HTTP.KeyAccessible where Key == HeaderKey, Value == String { - var customKey: String? { - get { - return self["Custom-Key"] - } - set { - self["Custom-Key"] = newValue - } - } -} -``` - -With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: - -```swift -let customKey = response.headers.customKey - -// or - -let request = ... -response.headers.customKey = "my custom value" -``` - -#### Body - -This is the body associated with the response and represents the general data payload. You can view more about body in the associated [docs](./body.md) - -For responses, the body is most commonly set at initialization. With two main types. - -##### BodyRepresentable - -Things that can be converted to bytes, ie: - -```swift -let response = Response(status: .ok, body: "some string") -``` - -In the above example, the `String` will be automatically converted to a body. Your own types can do this as well. - -##### Bytes Directly - -If we already have our bytes array, we can pass it into the body like so: - -```swift -let response = Response(status: .ok, body: .data(myArrayOfBytes)) -``` - -##### Chunked - -To send an `HTTP.Response` in chunks, we can pass a closure that we'll use to send our response body in parts. - -```swift -let response = Response(status: .ok) { chunker in - for name in ["joe", "pam", "cheryl"] { - sleep(1) - try chunker.send(name) - } - - try chunker.close() -} -``` - -> Make sure to call `close()` before the chunker leaves scope. - -## Content - -We can access content the same we do in a [request](./request.md). This most commonly applies to outgoing requests. - -```swift -let pokemonResponse = try drop.client.get("http://pokeapi.co/api/v2/pokemon/") -let names = pokemonResponse.data["results", "name"]?.array -``` - -## JSON - -To access JSON directly on a given response, use the following: - -```swift -let json = request.response["hello"] -``` - -## Key Paths - -For more on KeyPaths, visit [here](./request.md#key-paths) diff --git a/1.5/http/server.md b/1.5/http/server.md deleted file mode 100755 index eef6ae6a..00000000 --- a/1.5/http/server.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -currentMenu: http-server ---- - -# Server - -The server is responsible for accepting connections from clients, parsing their requests, and delivering them a response. - -## Default - -Starting your Droplet with a default server is simple. - -```swift -import Vapor - -let drop = Droplet() - -drop.run() -``` - -The default server will bind to host `0.0.0.0` at port `8080`. - -## Config - -If you are using a `Config/servers.json` file, this is where you can easily change your host and port or even boot multiple servers. - -```json -{ - "default": { - "port": "$PORT:8080", - "host": "0.0.0.0", - "securityLayer": "none" - } -} -``` - -The default `servers.json` is above. The port with try to resolve the environment variable `$PORT` or fallback to `8080`. - -### Multiple - -You can start multiple servers in the same application. This is especially useful if you want to boot an `HTTP` and `HTTPS` server side by side. - -```json -{ - "plaintext": { - "port": "80", - "host": "vapor.codes", - "securityLayer": "none" - }, - "secure": { - "port": "443", - "host": "vapor.codes", - "securityLayer": "tls", - "tls": { - "certificates": "none", - "signature": "selfSigned" - } - }, -} -``` - -## TLS - -TLS (formerly SSL) can be configured with a variety of different certificate and signature types. - -### Verify - -Verificiation of hosts and certificates can be disabled. They are enabled by default. - -> Note: Be extremely careful when disabling these options. - -```json -"tls": { - "verifyHost": false, - "verifyCertificates": false -} -``` - -### Certificates - -#### None - -```json -"tls": { - "certificates": "none" -} -``` - -#### Chain - -```json -"tls": { - "certificates": "chain", - "chainFile": "/path/to/chainfile" -} -``` - -#### Files - -```json -"tls": { - "certificates": "files", - "certificateFile": "/path/to/cert.pem", - "privateKeyFile": "/path/to/key.pem" -} -``` - -#### Certificate Authority - -```json -"tls": { - "certificates": "ca" -} -``` - -### Signature - -#### Self Signed - -```json -"tls": { - "signature": "selfSigned" -} -``` - -#### Signed File - -```json -"tls": { - "signature": "signedFile", - "caCertificateFile": "/path/to/file" -} -``` - -#### Signed Directory - -```json -"tls": { - "signature": "signedDirectory", - "caCertificateDirectory": "/path/to/dir" -} -``` - -## Example - -Here is an example `servers.json` file using certificate files with a self signed signature and host verification redundantly set to `true`. - -```json -{ - "secure": { - "port": "8443", - "host": "0.0.0.0", - "securityLayer": "tls", - "tls": { - "verifyHost": true, - "certificates": "files", - "certificateFile": "/vapor/certs/cert.pem", - "privateKeyFile": "/vapor/certs/key.pem", - "signature": "selfSigned" - } - } -} -``` - -## Manual - -Servers can also be configured manually, without configuration files. - -> Note: If servers are configured programatically, they override any config settings. - -### Simple - -The `run` method on the Droplet takes a dictionary of server configuration objects. The key is the name of the server. - -```swift -import Vapor - -let drop = Droplet() - -drop.run(servers: [ - "default": (host: "vapor.codes", port: 8080, securityLayer: .none) -] -``` - -### TLS - -TLS can also be configured manually, and works similarly to the `servers.json` config files described above. - -```swift -import Vapor -import TLS - -let drop = Droplet() - -let config = try TLS.Config( - mode: .server, - certificates: .files( - certificateFile: "/Users/tanner/Desktop/certs/cert.pem", - privateKeyFile: "/Users/tanner/Desktop/certs/key.pem", - signature: .selfSigned - ), - verifyHost: true, - verifyCertificates: true -) - -drop.run(servers: [ - "plaintext": ("vapor.codes", 8080, .none), - "secure": ("vapor.codes", 8443, .tls(config)), -]) -```` \ No newline at end of file diff --git a/1.5/routing/basic.md b/1.5/routing/basic.md deleted file mode 100755 index 447ebcf4..00000000 --- a/1.5/routing/basic.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -currentMenu: routing-basic ---- - -# Basic Routing - -Routing is one of the most critical parts of a web framework. The router decides which requests get which responses. - -Vapor has a plethora of functionality for routing including route builders, groups, and collections. In this section, we will look at the basics of routing. - -## Register - -The most basic route includes a method, path, and closure. - -```swift -drop.get("welcome") { request in - return "Hello" -} -``` - -The standard HTTP methods are available including `get`, `post`, `put`, `patch`, `delete`, and `options`. - -```swift -drop.post("form") { request in - return "Submitted with a POST request" -} -``` - -You can also use `any` to match all methods. - -## Nesting - -To nest paths (adding `/`s in the URL), simply add commas. - -```swift -drop.get("foo", "bar", "baz") { request in - return "You requested /foo/bar/baz" -} -``` - -You can also use `/`, but commas are often easier to type and work better with type safe route [parameters](parameters.md). - -## Alternate - -An alternate syntax that accepts a `Method` as the first parameter is also available. - -```swift -drop.add(.trace, "welcome") { request in - return "Hello" -} -``` - -This may be useful if you want to register routes dynamically or use a less common method. - -## Request - -Each route closure is given a single [Request](../http/request.md). This contains all of the data associated with the request that led to your route closure being called. - -## Response Representable - -A route closure can return in three ways: - -- `Response` -- `ResponseRepresentable` -- `throw` - -### Response - -A custom [Response](../http/response.md) can be returned. - -```swift -drop.get("vapor") { request in - return Response(redirect: "http://vapor.codes") -} -``` - -This is useful for creating special responses like redirects. It is also useful for cases where you want to add cookies or other items to the response. - -### Response Representable - -As you have seen in the previous examples, `String`s can be returned in route closures. This is because they conform to [ResponseRepresentable](../http/response-representable.md) - -A lot of types in Vapor conform to this protocol by default: -- String -- Int -- JSON -- Model - -```swift -drop.get("json") { request in - return try JSON(node: [ - "number": 123, - "text": "unicorns", - "bool": false - ]) -} -``` - -> If you are curious about what `node:` means, read more about [Node](https://github.com/vapor/node) - -### Throwing - -If you are unable to return a response, you may `throw` any object that conforms to `Error`. Vapor comes with a default error enum `Abort`. - -```swift -drop.get("404") { request in - throw Abort.notFound -} -``` - -You can customize the message of these errors by using `Abort` - -```swift -drop.get("error") { request in - throw Abort.custom(status: .badRequest, message: "Sorry 😱") -} -``` - -These errors are caught by default in the `AbortMiddleware` where they are turned into a JSON response like the following. - -```json -{ - error: true, - message: "" -} -``` - -If you want to override this behavior, remove the `AbortMiddleware` from the `Droplet`'s middleware and add your own. - -## Fallback - -Fallback routes allow you to match multiple layers of nesting slashes. - -```swift -app.get("anything", "*") { request in - return "Matches anything after /anything" -} -``` - -For example, the above route matches all of the following and more: - -- /anything -- /anything/foo -- /anything/foo/bar -- /anything/foo/bar/baz -- ... diff --git a/1.5/routing/collection.md b/1.5/routing/collection.md deleted file mode 100755 index e8aad659..00000000 --- a/1.5/routing/collection.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -currentMenu: routing-collection ---- - -# Route Collections - -Route collections allow multiple routes and route groups to be organized in different files or modules. - -## Example - -Here is an example of a route collection for the `v1` portion of an API. - -```swift -import Vapor -import HTTP -import Routing - -class V1Collection: RouteCollection { - typealias Wrapped = HTTP.Responder - func build(_ builder: B) where B.Value == Wrapped { - let v1 = builder.grouped("v1") - let users = v1.grouped("users") - let articles = v1.grouped("articles") - - users.get { request in - return "Requested all users." - } - - articles.get(Article.self) { request, article in - return "Requested \(article.name)" - } - } -} -``` - -This class could be place in any file, and we could add it to our droplet or even another route group. - -```swift -let v1 = V1Collection() -drop.collection(v1) -``` - -The `Droplet` will then be passed to the `build(_:)` method of your route collection and have the various routes added to it. - -### Breakdown - -For those that are curious, let's break down the route collection line by line to better understand what is going on. - -```swift -typealias Wrapped = HTTP.Responder -``` - -This limits our route collection to adding HTTP responders. While the underlying router is capable of routing any type, Vapor routes HTTP responders exclusively. If we want to use this route collection with Vapor, it's wrapped type needs to match. - -```swift -func build(_ builder: B) where B.Value == Wrapped { -``` - -This method accepts a route builder and also verifies that the route builder accepts `Wrapped` or, as defined in the last line, `HTTP.Responder`s. Vapor's `Droplet` and any route [group](group.md) created with Vapor are `RouteBuilder`s that accept HTTP responders. - -```swift -let v1 = builder.grouped("v1") -``` - -From there you can create routes with the `builder` as usual. The `builder: B` will work exactly like a `Droplet` or route [group](group.md). Any methods that work there will work on this builder. - - -## Empty Initializable - -You can even add `EmptyInitializable` to your route collection if it has an empty `init` method. This will allow you to add the route collection via its type name. - -```swift -class V1Collection: RouteCollection, EmptyInitializable { - init() { } - ... -``` - -Now we can add the collection without initializing it. - -```swift -drop.collection(V1Collection.self) -``` diff --git a/1.5/routing/controller b/1.5/routing/controller deleted file mode 100755 index 6abc2dd9..00000000 --- a/1.5/routing/controller +++ /dev/null @@ -1,74 +0,0 @@ ---- -currentMenu: routing-controller ---- - -# Introduction - -Instead of defining all of your request handling logic as Closures in route files, you may wish to organize this behavior -using Controller classes. Controllers can group related request handling logic into a single class. Controllers are stored -in the `Sources/App/Controllers` directory. - -# Basic Controller - -## Defining Controllers - -```swift -import Vapor -import HTTP - -final class FirstController { - func index(request: Request) throws -> ResponseRepresentable { - return try JSON(node: [ - "message": "This is FirstController's index method" - ]) - } -} -``` -You can define a route to this controller action like so: - -```swift -drop.get("getindex") {request in - return try FirstController().index(request: request) -} -``` -Now, when a request matches the specified route URI, the Index method on the FirstController -class will be executed. Of course, the route parameters will also be passed to the method. - --------- - -# Resource Controllers - -Vapor resource routing assigns the typical "CRUD" routes to a controller with a single line of code. - -```swift -drop.resource("URI", Controller()) -``` -This single route declaration creates multiple routes to handle a variety of actions on the resource. -The generated controller will already have methods stubbed for each of these actions, including -notes informing you of the HTTP verbs and URIs they handle. - -| Verb | URI | Action | -| :-------------: | :-------------: | :-----------: | -| GET | test/index | test.index | -| POST | test/create | test.create | -| GET | test/show | test.show | -| PUT | test/replace | test.replace | -| PATCH | test/destroy | test.destroy | -| DELETE | test/destroy | test.destroy | -| DELETE | test/clear | test.clear | - -You can also custom method name, add `makeResource` method in the controller - -```swift - func makeResource() -> Resource { - return Resource( - index: index, - store: create, - show: show, - replace: replace, - modify: update, - destroy: delete, - clear: clear - ) - } -``` diff --git a/1.5/routing/group.md b/1.5/routing/group.md deleted file mode 100755 index fe04601a..00000000 --- a/1.5/routing/group.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -currentMenu: routing-group ---- - -# Route Groups - -Grouping routes together makes it easy to add common prefixes, middleware, or hosts to multiple routes. - -Route groups have two different forms: Group and Grouped. - -### Group - -Group (without the "ed" at the end) takes a closure that is passed a `GroupBuilder`. - -```swift -drop.group("v1") { v1 in - v1.get("users") { request in - // get the users - } -} -``` - -### Grouped - -Grouped returns a `GroupBuilder` that you can pass around. - -```swift -let v1 = drop.grouped("v1") -v1.get("users") { request in - // get the users -} -``` - -### Middleware - -You can add middleware to a group of routes. This is especially useful for authentication. - -```swift -drop.group(AuthMiddleware()) { authorized in - authorized.get("token") { request in - // has been authorized - } -} -``` - -### Host - -You can limit the host for a group of routes. - -```swift -drop.group(host: "vapor.codes") { vapor - vapor.get { request in - // only responds to requests to vapor.codes - } -} -``` - -### Chaining - -Groups can be chained together. - -```swift -drop.grouped(host: "vapor.codes").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in - // add routes here -} -``` - diff --git a/1.5/routing/parameters.md b/1.5/routing/parameters.md deleted file mode 100755 index a0886167..00000000 --- a/1.5/routing/parameters.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -currentMenu: routing-parameters ---- - -# Routing Parameters - -Traditional web frameworks leave room for error in routing by using strings for route parameter names and types. Vapor takes advantage of Swift's closures to provide a safer and more intuitive method for accessing route parameters. - -## Type Safe - -To create a type safe route simply replace one of the parts of your path with a `Type`. - -```swift -drop.get("users", Int.self) { request, userId in - return "You requested User #\(userId)" -} -``` - -This creates a route that matches `users/:id` where the `:id` is an `Int`. Here's what it would look like using manual route parameters. - -```swift -drop.get("users", ":id") { request in - guard let userId = request.parameters["id"]?.int else { - throw Abort.badRequest - } - - return "You requested User #\(userId)" -} -``` - -Here you can see that type safe routing saves ~3 lines of code and also prevents runtime errors like misspelling `:id`. - -## String Initializable - -Any type that conforms to `StringInitializable` can be used as a type-safe routing parameter. By default, the following types conform: - -- String -- Int -- Model - -`String` is the most generic and always matches. `Int` only matches when the string supplied can be turned into an integer. `Model` only matches when the string, used as an identifier, can be used to find the model in the database. - -Our previous example with users can be further simplified. - -```swift -drop.get("users", User.self) { request, user in - return "You requested \(user.name)" -} -``` - -Here the identifier supplied is automatically used to lookup a user. For example, if `/users/5` is requested, the `User` model will be asked for a user with identifier `5`. If one is found, the request succeeds and the closure is called. If not, a not found error is thrown. - -Here is what this would look like if model didn't conform to `StringInitializable`. - -```swift -drop.get("users", Int.self) { request, userId in - guard let user = try User.find(userId) else { - throw Abort.notFound - } - - return "You requested User #\(userId)" -} -``` - -Altogether, type safe routing can save around 6 lines of code from each route. - -### Protocol - -Conforming your own types to `StringInitializable` is easy. - -```swift -public protocol StringInitializable { - init?(from string: String) throws -} -``` - -Here is what `Model`'s conformance looks like for those who are curious. - -```swift -extension Model { - public init?(from string: String) throws { - if let model = try Self.find(string) { - self = model - } else { - return nil - } - } -} -``` - -The `init` method can both `throw` and return `nil`. This allows you to `throw` your own errors. Or, if you want the default error and behavior, just return `nil`. - -### Limits - -Type safe routing is currently limited to three path parts. This is usually remedied by adding route [groups](group.md). - -```swift -drop.group("v1", "users") { users in - users.get(User.self, "posts", Post.self) { request, user, post in - return "Requested \(post.name) for \(user.name)" - } -} -``` - -The resulting path for the above example is `/v1/users/:userId/posts/:postId`. If you are clamoring for more type safe routing, please let us know and we can look into increasing the limit of three. - -## Manual - -As shown briefly above, you are still free to do traditional routing. This can be useful for especially complex situations. - -```swift -drop.get("v1", "users", ":userId", "posts", ":postId", "comments", ":commentId") { request in - let userId = try request.parameters.extract("userId") as Int - let postId = try request.parameters.extract("postId") as Int - let commentId = try request.parameters.extract("commentId") as Int - - return "You requested comment #\(commentId) for post #\(postId) for user #\(userId)" -} -``` - -> Property `request.parameters` is used to extract parameters encoded in the URI _path_ (for example, `/v1/users/1` has a parameter `:userId` equal to `"1"`). In case of parameters passed as a part of a _query_ (e.g. `/v1/search-user?userId=1`), the `request.data` should be used (e.g. `let userId = request.data["userId"]?.string`). - -Request parameters can be accessed either as a dictionary or using the `extract` syntax which throws instead of returning an optional. - -### Groups - -Manual request parameters also work with [groups](group.md). - -```swift -let userGroup = drop.grouped("users", ":userId") -userGroup.get("messages") { req in - let user = try req.parameters.extract("userId") as User -} -``` diff --git a/1.5/routing/query-parameters.md b/1.5/routing/query-parameters.md deleted file mode 100755 index 62c0cc0c..00000000 --- a/1.5/routing/query-parameters.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -currentMenu: routing-query-parameters ---- - -# Query Parameters - -Request query parameters can be accessed either as a dictionary or using the `extract` syntax which throws instead of returning an optional. - -## Optional Syntax - -Optional syntax is the easiest way to handle optional query parameters. - -```swift -drop.get("comments") { request in - if let rating = request.query?["rating"]?.int { - return "You requested comments with rating greater than #\(rating)" - } - return "You requested all comments" -} -``` - -## Extract Syntax - -Extract syntax might be useful to *enforce* the presence of query parameters and throw an exception if they are not present. -To use this syntax first we need to ensure the query object is present with a `guard`. - -```swift -drop.get("comments") { request in - guard let rating = request.query?["rating"]?.int else { - throw Abort.custom(status: .preconditionFailed, message: "Please include a rating") - } - return "You requested comments with rating greater than #\(rating)" -} -``` diff --git a/1.5/switch/1_5.md b/1.5/switch/1_5.md deleted file mode 100644 index b296eea3..00000000 --- a/1.5/switch/1_5.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/1.5/switch/2_0.md b/1.5/switch/2_0.md deleted file mode 100644 index c5451988..00000000 --- a/1.5/switch/2_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/1.5/switch/3_0.md b/1.5/switch/3_0.md deleted file mode 100644 index a22a17f2..00000000 --- a/1.5/switch/3_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/1.5/switch/4_0.md b/1.5/switch/4_0.md deleted file mode 100644 index 1ec89cc1..00000000 --- a/1.5/switch/4_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/1.5/template/default.twig b/1.5/template/default.twig deleted file mode 100755 index f12e7f22..00000000 --- a/1.5/template/default.twig +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - {{ title }} - - - - - - - - -
    - - -
    - - - - - -
    - ✎ Edit on GitHub - {{ content | raw }} -
    - - - - - - - - - - - - diff --git a/1.5/template/images/droplet.svg b/1.5/template/images/droplet.svg deleted file mode 100755 index e60ae92d..00000000 --- a/1.5/template/images/droplet.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/1.5/template/screenshot.png b/1.5/template/screenshot.png deleted file mode 100755 index 31747767..00000000 Binary files a/1.5/template/screenshot.png and /dev/null differ diff --git a/1.5/template/scripts/highlight.pack.js b/1.5/template/scripts/highlight.pack.js deleted file mode 100755 index ed92ae6d..00000000 --- a/1.5/template/scripts/highlight.pack.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! highlight.js v9.8.0 | BSD3 License | git.io/hljslicense */ -!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/[&<>]/gm,function(e){return I[e]})}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return R(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||R(i))return i}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){l+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===s);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):E(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"===e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function l(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function g(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function h(e,n,t,r){var a=r?"":y.classPrefix,i='',i+n+o}function p(){var e,t,r,a;if(!E.k)return n(B);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(B);r;)a+=n(B.substr(t,r.index-t)),e=g(E,r),e?(M+=e[1],a+=h(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(B);return a+n(B.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!x[E.sL])return n(B);var t=e?l(E.sL,B,!0,L[E.sL]):f(B,E.sL.length?E.sL:void 0);return E.r>0&&(M+=t.r),e&&(L[E.sL]=t.top),h(t.language,t.value,!1,!0)}function b(){k+=null!=E.sL?d():p(),B=""}function v(e){k+=e.cN?h(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(B+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?B+=n:(t.eB&&(B+=n),b(),t.rB||t.eB||(B=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?B+=n:(a.rE||a.eE||(B+=n),b(),a.eE&&(B=n));do E.cN&&(k+=C),E.skip||(M+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return B+=n,n.length||1}var N=R(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var w,E=i||N,L={},k="";for(w=E;w!==N;w=w.parent)w.cN&&(k=h(w.cN,"",!0)+k);var B="",M=0;try{for(var I,j,O=0;;){if(E.t.lastIndex=O,I=E.t.exec(t),!I)break;j=m(t.substr(O,I.index-O),I[0]),O=I.index+j}for(m(t.substr(O)),w=E;w.parent;w=w.parent)w.cN&&(k+=C);return{r:M,value:k,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function f(e,t){t=t||y.languages||E(x);var r={r:0,value:n(e)},a=r;return t.filter(R).forEach(function(n){var t=l(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function g(e){return y.tabReplace||y.useBR?e.replace(M,function(e,n){return y.useBR&&"\n"===e?"
    ":y.tabReplace?n.replace(/\t/g,y.tabReplace):void 0}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n,t,r,o,s,p=i(e);a(p)||(y.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,s=n.textContent,r=p?l(p,s,!0):f(s),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),s)),r.value=g(r.value),e.innerHTML=r.value,e.className=h(e.className,p,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function d(e){y=o(y,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");w.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function N(){return E(x)}function R(e){return e=(e||"").toLowerCase(),x[e]||x[L[e]]}var w=[],E=Object.keys,x={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
    ",y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},I={"&":"&","<":"<",">":">"};return e.highlight=l,e.highlightAuto=f,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=R,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\._]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("swift",function(e){var t={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},i={cN:"type",b:"\\b[A-Z][\\wÀ-ʸ']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:t,c:[]},a={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[a],{k:t,c:[o,e.CLCM,n,i,a,{cN:"function",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{b://},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,c:["self",a,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:t,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{cN:"meta",b:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},{bK:"import",e:/$/,c:[e.CLCM,n]}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}}); \ No newline at end of file diff --git a/1.5/template/styles/main.css b/1.5/template/styles/main.css deleted file mode 100755 index 7170e42d..00000000 --- a/1.5/template/styles/main.css +++ /dev/null @@ -1,316 +0,0 @@ -html, body { - margin: 0; - padding: 0; - font-size: 14px; - line-height: 1.5; - font-family: -webkit-system-font, sans-serif; } - @media screen and (min-width: 720px) { - html, body { - width: 100%; - height: 100%; } } - -a { - color: #94a9d1; - text-decoration: none; - border-bottom: 1px dotted; } - -a:hover { - color: #f6cfcf; } - -h1, h2, h3, h4, h5, h6 { - color: #333; - margin: 0; - font-family: "Quicksand"; - letter-spacing: -1px; } - -h1 { - font-size: 38px; - font-weight: 300; } - @media screen and (min-width: 720px) { - h1 { - font-size: 42px; } } - -h2 { - font-size: 30px; - font-weight: 400; } - -h3 { - font-size: 24px; - font-weight: 400; - margin-bottom: 6px; } - -h4 { - font-size: 18px; - font-weight: 400; - margin-bottom: 6px; } - -:target { - background: #ffff99; } - -a.logo { - display: block; - position: relative; - border-bottom: none; - height: 100%; - width: 280px; - margin: 0 auto; - padding-left: 52px; } - @media screen and (min-width: 720px) { - a.logo { - padding-left: 72px; - width: 100%; } } - a.logo h1 { - margin: 0; - font-size: 36px; - line-height: 96px; - text-transform: uppercase; } - @media screen and (min-width: 720px) { - a.logo h1 { - font-size: 36px; } } - a.logo h1 em { - font-style: normal; - color: rgba(0, 0, 0, 0.4); - text-transform: none; } - a.logo img { - left: 6px; - top: 24px; - position: absolute; - height: 48px; } - @media screen and (min-width: 720px) { - a.logo img { - left: 24px; - top: 24px; - height: 48px; } } - -* { - box-sizing: border-box; } - -header { - overflow: hidden; - border-bottom: 3px solid #94a9d1; - height: 132px; - position: relative; } - @media screen and (min-width: 720px) { - header { - height: 96px; - position: absolute; - left: 0; - right: 0; - z-index: 10; - background: rgba(255, 255, 255, 0.95); } - .safari header { - background: rgba(255, 255, 255, 0.5); - -webkit-backdrop-filter: blur(10px); } } - header ul { - position: absolute; - margin: 0; - padding: 0; - bottom: 5px; - right: 0; - text-align: center; } - @media screen and (max-width: 720px) { - header ul { - left: 0; } } - @media screen and (min-width: 720px) { - header ul { - text-align: right; - right: 24px; - top: 24px; - height: 100%; } } - header ul li { - display: inline-block; - line-height: 48px; } - header ul li a { - padding: 0 6px; - display: block; - border-bottom: none; } - -a.toggle { - position: absolute; } - @media screen and (min-width: 720px) { - a.toggle { - display: none; } } - a.toggle.close { - top: 12px; - right: 0; - z-index: 20; - color: white; - font-size: 48px; - line-height: 48px; - height: 72px; - width: 72px; - padding: 12px; - border-bottom: none; - text-align: center; } - a.toggle.show { - text-align: center; - border-bottom: none; - background: #94a9d1; - color: white; - right: -36px; - top: 50%; - height: 48px; - width: 36px; - margin-top: -24px; - line-height: 48px; - border-bottom-right-radius: 5px; - border-top-right-radius: 5px; - z-index: 15; - transition: right 0.25s; } - a.toggle.show.hide { - right: -12px; } - body.toggled a.toggle.show { - right: -36px; } - -nav { - font-size: 14px; - border-right: 1px solid rgba(0, 0, 0, 0.05); - width: 100%; - position: fixed; - left: -100%; - transition: left 0.25s; - top: 0; - right: 0; - bottom: 0; - display: block; - background: #94a9d1; - z-index: 10; } - nav a { - border-bottom: none; - color: rgba(255, 255, 255, 0.9); - font-size: 16px; - line-height: 32px; - height: 32px; - display: block; } - nav h3 { - text-transform: uppercase; - margin-top: 12px; - font-size: 18px; - color: white; - font-weight: 600; } - body.toggled nav { - left: 0; } - @media screen and (min-width: 720px) { - nav { - background: white; - display: block; - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 216px; - z-index: 8; - padding-top: 96px; } - nav h3 { - color: #bbb; - font-size: 14px; - margin-bottom: 6px; - font-weight: 400; } - nav a { - border-bottom: none; - font-weight: 200; - font-size: 14px; - color: #777; - font-size: 12px; - height: 24px; } - nav a:hover { - color: #94a9d1; } } - nav ul, nav ul li { - list-style: none; - padding: 0; - margin: 0; } - nav ul.active a, nav ul li.active a { - font-weight: 600; } - @media screen and (min-width: 720px) { - nav ul.active a, nav ul li.active a { - color: #94a9d1; } } - nav ul { - margin-bottom: 18px; } - -div.scroll { - padding: 22px; - overflow-y: scroll; - height: 100%; - z-index: 10; - -webkit-overflow-scrolling: touch; } - -main { - -webkit-overflow-scrolling: touch; - color: #555; - font-weight: 200; - width: 100%; - padding: 24px; - overflow: hidden; } - @media screen and (min-width: 720px) { - main { - height: 100%; - position: absolute; - position: relative; - z-index: 6; - top: 0; - bottom: 0; - right: 0; - left: 0; - padding: 22px; - padding-top: 110px; - padding-left: 240px; - overflow-y: scroll; - max-width: 1440px; } } - main a.edit { - position: absolute; - top: 146px; - right: 24px; - border-bottom: none; } - @media screen and (min-width: 720px) { - main a.edit { - top: 102px; } } - main h1 { - margin-bottom: 12px; } - main h2 { - margin-top: 24px; - margin-bottom: 12px; - border-bottom: 1px dotted rgba(0, 0, 0, 0.1); } - main h3 { - margin-top: 18px; } - main p { - margin-top: 0; - margin-bottom: 12px; - line-height: 1.75; } - main blockquote { - margin: 0; - background: rgba(247, 202, 201, 0.2); - margin-top: 18px; - margin-bottom: 18px; - margin-left: 0; - padding: 6px; - padding-left: 12px; - border-left: 5px solid #f6cfcf; } - main blockquote p { - margin: 0; - padding: 0; } - -pre, code { - font-size: 12px; - font-family: "Source Code Pro", monospace; - -webkit-overflow-scrolling: touch; } - -p code, li code { - background: #fbfbfb; - border-radius: 5px; - padding: 3px 5px; - display: inline-block; - color: #6b7891; - box-shadow: 0 1px 0px 0px rgba(0, 0, 0, 0.1); } - -pre { - margin-top: 12px; - margin-bottom: 15px; - padding: 5px; - background: #fbfbfb; - border-radius: 10px; - box-shadow: 0 3px 0px 0px rgba(0, 0, 0, 0.1); } - -img { - max-width: 100%; } - -/*# sourceMappingURL=main.css.map */ diff --git a/1.5/template/styles/main.css.map b/1.5/template/styles/main.css.map deleted file mode 100755 index bd469dad..00000000 --- a/1.5/template/styles/main.css.map +++ /dev/null @@ -1,7 +0,0 @@ -{ -"version": 3, -"mappings": "AAGA,UAAU;EACN,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,CAAC;EACV,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,GAAG;EAChB,WAAW,EAAE,+BAA+B;EAE5C,oCAAsC;IAP1C,UAAU;MAQF,KAAK,EAAE,IAAI;MACX,MAAM,EAAE,IAAI;;AAEpB,CAAC;EACG,KAAK,EAAE,OAAO;EACd,eAAe,EAAE,IAAI;EACrB,aAAa,EAAE,UAAU;;AAE7B,OAAO;EACH,KAAK,EAAE,OAAO;;AAGlB,sBAAsB;EAClB,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,CAAC;EACT,WAAW,EAAE,WAAW;EACxB,cAAc,EAAE,IAAI;;AAExB,EAAE;EACE,SAAS,EAAE,IAAI;EAGf,WAAW,EAAE,GAAG;EAFhB,oCAAsC;IAF1C,EAAE;MAGM,SAAS,EAAE,IAAI;;AAGvB,EAAE;EACE,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,GAAG;;AAEpB,EAAE;EACE,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,GAAG;EAChB,aAAa,EAAE,GAAG;;AAEtB,EAAE;EACE,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,GAAG;EAChB,aAAa,EAAE,GAAG;;AAEtB,OAAQ;EACJ,UAAU,EAAE,OAAO;;AAEvB,MAAM;EACF,OAAO,EAAE,KAAK;EACd,QAAQ,EAAE,QAAQ;EAClB,aAAa,EAAE,IAAI;EACnB,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,MAAM;EACd,YAAY,EAAE,IAAI;EAElB,oCAAsC;IAT1C,MAAM;MAUE,YAAY,EAAE,IAAI;MAClB,KAAK,EAAE,IAAI;EAEf,SAAE;IACE,MAAM,EAAE,CAAC;IACT,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,cAAc,EAAE,SAAS;IAEzB,oCAAsC;MAN1C,SAAE;QAOM,SAAS,EAAE,IAAI;IAEnB,YAAE;MACE,UAAU,EAAE,MAAM;MAClB,KAAK,EAAE,kBAAkB;MACzB,cAAc,EAAE,IAAI;EAE5B,UAAG;IACC,IAAI,EAAE,GAAG;IACT,GAAG,EAAE,IAAI;IACT,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,IAAI;IAEZ,oCAAsC;MAN1C,UAAG;QAOK,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,IAAI;;AAExB,CAAC;EACG,UAAU,EAAE,UAAU;;AAE1B,MAAM;EACF,QAAQ,EAAE,MAAM;EAChB,aAAa,EAAE,iBAAiB;EAChC,MAAM,EAAE,KAAK;EACb,QAAQ,EAAE,QAAQ;EAElB,oCAAsC;IAN1C,MAAM;MAOE,MAAM,EAAE,IAAI;MACZ,QAAQ,EAAE,QAAQ;MAClB,IAAI,EAAE,CAAC;MACP,KAAK,EAAE,CAAC;MACR,OAAO,EAAE,EAAE;MAEX,UAAU,EAAE,yBAAyB;MAErC,cAAS;QACL,UAAU,EAAE,wBAAwB;QACpC,uBAAuB,EAAE,UAAU;EAI3C,SAAE;IACE,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,GAAG;IACX,KAAK,EAAE,CAAC;IACR,UAAU,EAAE,MAAM;IAElB,oCAAsC;MAR1C,SAAE;QASM,IAAI,EAAE,CAAC;IAEX,oCAAsC;MAX1C,SAAE;QAYM,UAAU,EAAE,KAAK;QACjB,KAAK,EAAE,IAAI;QACX,GAAG,EAAE,IAAI;QACT,MAAM,EAAE,IAAI;IAEhB,YAAE;MACE,OAAO,EAAE,YAAY;MACrB,WAAW,EAAE,IAAI;MAEjB,cAAC;QACG,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,KAAK;QACd,aAAa,EAAE,IAAI;;AAEnC,QAAQ;EAIJ,QAAQ,EAAE,QAAQ;EAHlB,oCAAsC;IAD1C,QAAQ;MAEA,OAAO,EAAE,IAAI;EAIjB,cAAO;IACH,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,EAAE;IACX,KAAK,EAAE,KAAK;IACZ,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;IACb,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,MAAM;EAEtB,aAAM;IACF,UAAU,EAAE,MAAM;IAClB,aAAa,EAAE,IAAI;IACnB,UAAU,EAjKX,OAAO;IAkKN,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,GAAG,EAAE,GAAG;IACR,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,KAAK;IACjB,WAAW,EAAE,IAAI;IACjB,0BAA0B,EAAE,GAAG;IAC/B,uBAAuB,EAAE,GAAG;IAC5B,OAAO,EAAE,EAAE;IAEX,UAAU,EAAE,WAAW;IAEvB,kBAAM;MACF,KAAK,EAAE,KAAK;IAEhB,0BAAc;MACV,KAAK,EAAE,KAAK;;AAGxB,GAAG;EACC,SAAS,EAAE,IAAI;EACf,YAAY,EAAE,6BAA6B;EAC3C,KAAK,EAAE,IAAI;EACX,QAAQ,EAAE,KAAK;EACf,IAAI,EAAE,KAAK;EACX,UAAU,EAAE,UAAU;EAEtB,GAAG,EAAE,CAAC;EACN,KAAK,EAAE,CAAC;EACR,MAAM,EAAE,CAAC;EACT,OAAO,EAAE,KAAK;EACd,UAAU,EAlMP,OAAO;EAmMV,OAAO,EAAE,EAAE;EAEX,KAAC;IACG,aAAa,EAAE,IAAI;IACnB,KAAK,EAAE,wBAAwB;IAC/B,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,IAAI;IACZ,OAAO,EAAE,KAAK;EAElB,MAAE;IACE,cAAc,EAAE,SAAS;IACzB,UAAU,EAAE,IAAI;IAChB,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,KAAK;IACZ,WAAW,EAAE,GAAG;EAEpB,gBAAc;IACV,IAAI,EAAE,CAAC;EAEX,oCAAsC;IAjC1C,GAAG;MAkCK,UAAU,EAAE,KAAK;MACjB,OAAO,EAAE,KAAK;MACd,QAAQ,EAAE,QAAQ;MAClB,IAAI,EAAE,CAAC;MACP,GAAG,EAAE,CAAC;MACN,MAAM,EAAE,CAAC;MACT,KAAK,EAAE,KAAK;MACZ,OAAO,EAAE,CAAC;MACV,WAAW,EAAE,IAAI;MAEjB,MAAE;QACE,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,aAAa,EAAE,GAAG;QAClB,WAAW,EAAE,GAAG;MAGpB,KAAC;QACG,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,GAAG;QAChB,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,IAAI;QACX,SAAS,EAAE,IAAI;QACf,MAAM,EAAE,IAAI;QAEZ,WAAO;UACH,KAAK,EAAE,OAAO;EAE1B,iBAAS;IACL,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;IAET,mCAAU;MAGN,WAAW,EAAE,GAAG;MAFhB,oCAAsC;QAD1C,mCAAU;UAEF,KAAK,EAAE,OAAO;EAG1B,MAAE;IACE,aAAa,EAAE,IAAI;;AAE3B,UAAU;EACN,OAAO,EAAE,IAAI;EACb,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,IAAI;EACZ,OAAO,EAAE,EAAE;EACX,0BAA0B,EAAE,KAAK;;AAErC,IAAI;EACA,0BAA0B,EAAE,KAAK;EACjC,KAAK,EAAE,IAAI;EACX,WAAW,EAAE,GAAG;EAChB,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,IAAI;EACb,QAAQ,EAAE,MAAM;EAEhB,oCAAsC;IAR1C,IAAI;MASI,MAAM,EAAE,IAAI;MACZ,QAAQ,EAAE,QAAQ;MAClB,QAAQ,EAAE,QAAQ;MAClB,OAAO,EAAE,CAAC;MAEV,GAAG,EAAE,CAAC;MACN,MAAM,EAAE,CAAC;MACT,KAAK,EAAE,CAAC;MACR,IAAI,EAAE,CAAC;MAEP,OAAO,EAAE,IAAI;MACb,WAAW,EAAE,KAAK;MAClB,YAAY,EAAE,KAAK;MACnB,UAAU,EAAE,MAAM;MAClB,SAAS,EAAE,MAAM;EAErB,WAAM;IACF,QAAQ,EAAE,QAAQ;IAClB,GAAG,EAAE,KAAK;IAGV,KAAK,EAAE,IAAI;IACX,aAAa,EAAE,IAAI;IAHnB,oCAAsC;MAH1C,WAAM;QAIE,GAAG,EAAE,KAAK;EAIlB,OAAE;IACE,aAAa,EAAE,IAAI;EAEvB,OAAE;IACE,UAAU,EAAE,IAAI;IAChB,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,6BAA8B;EAEjD,OAAE;IACE,UAAU,EAAE,IAAI;EAEpB,MAAC;IACG,UAAU,EAAE,CAAC;IACb,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,IAAI;EAErB,eAAU;IACN,MAAM,EAAE,CAAC;IACT,UAAU,EAAE,wBAAwB;IACpC,UAAU,EAAE,IAAI;IAChB,aAAa,EAAE,IAAI;IACnB,WAAW,EAAE,CAAC;IACd,OAAO,EAAE,GAAG;IACZ,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,iBAAiB;IAE9B,iBAAC;MACG,MAAM,EAAE,CAAC;MACT,OAAO,EAAE,CAAC;;AAEtB,SAAS;EACL,SAAS,EAAE,IAAI;EACf,WAAW,EAAE,4BAA4B;EACzC,0BAA0B,EAAE,KAAK;;AAErC,eAAe;EACX,UAAU,EAAE,OAAO;EACnB,aAAa,EAAE,GAAG;EAClB,OAAO,EAAE,OAAO;EAChB,OAAO,EAAE,YAAY;EACrB,KAAK,EAAE,OAAO;EACd,UAAU,EAAE,gCAAgC;;AAEhD,GAAG;EACC,UAAU,EAAE,IAAI;EAChB,aAAa,EAAE,IAAI;EACnB,OAAO,EAAE,GAAG;EACZ,UAAU,EAAE,OAAO;EACnB,aAAa,EAAE,IAAI;EACnB,UAAU,EAAE,gCAAgC;;AAEhD,GAAG;EACC,SAAS,EAAE,IAAI", -"sources": ["main.sass"], -"names": [], -"file": "main.css" -} \ No newline at end of file diff --git a/1.5/template/styles/main.sass b/1.5/template/styles/main.sass deleted file mode 100755 index 185c934a..00000000 --- a/1.5/template/styles/main.sass +++ /dev/null @@ -1,351 +0,0 @@ -$tablet: 720px -$blue: #94a9d1 - -html, body - margin: 0 - padding: 0 - font-size: 14px - line-height: 1.5 - font-family: -webkit-system-font, sans-serif - - @media screen and (min-width: $tablet) - width: 100% - height: 100% - -a - color: #94a9d1 - text-decoration: none - border-bottom: 1px dotted - -a:hover - color: #f6cfcf - - -h1, h2, h3, h4, h5, h6 - color: #333 - margin: 0 - font-family: 'Quicksand' - letter-spacing: -1px - -h1 - font-size: 38px - @media screen and (min-width: $tablet) - font-size: 42px - font-weight: 300 - -h2 - font-size: 30px - font-weight: 400 - -h3 - font-size: 24px - font-weight: 400 - margin-bottom: 6px - -h4 - font-size: 18px - font-weight: 400 - margin-bottom: 6px - -\:target - background: #ffff99 - -a.logo - display: block - position: relative - border-bottom: none - height: 100% - width: 280px - margin: 0 auto - padding-left: 52px - - @media screen and (min-width: $tablet) - padding-left: 72px - width: 100% - - h1 - margin: 0 - font-size: 36px - line-height: 96px - text-transform: uppercase - - @media screen and (min-width: $tablet) - font-size: 36px - - em - font-style: normal - color: rgba(0, 0, 0, 0.4) - text-transform: none - - img - left: 6px - top: 24px - position: absolute - height: 48px - - @media screen and (min-width: $tablet) - left: 24px - top: 24px - height: 48px - -* - box-sizing: border-box - -header - overflow: hidden - border-bottom: 3px solid #94a9d1 - height: 132px - position: relative - - @media screen and (min-width: $tablet) - height: 96px - position: absolute - left: 0 - right: 0 - z-index: 10 - - background: rgba(255, 255, 255, 0.95) - - .safari & - background: rgba(255, 255, 255, 0.5) - -webkit-backdrop-filter: blur(10px) - - - - ul - position: absolute - margin: 0 - padding: 0 - bottom: 5px - right: 0 - text-align: center - - @media screen and (max-width: $tablet) - left: 0 - - @media screen and (min-width: $tablet) - text-align: right - right: 24px - top: 24px - height: 100% - - li - display: inline-block - line-height: 48px - - a - padding: 0 6px - display: block - border-bottom: none - -a.toggle - @media screen and (min-width: $tablet) - display: none - - position: absolute - - &.close - top: 12px - right: 0 - z-index: 20 - color: white - font-size: 48px - line-height: 48px - height: 72px - width: 72px - padding: 12px - border-bottom: none - text-align: center - - &.show - text-align: center - border-bottom: none - background: $blue - color: white - right: -36px - top: 50% - height: 48px - width: 36px - margin-top: -24px - line-height: 48px - border-bottom-right-radius: 5px - border-top-right-radius: 5px - z-index: 15 - - transition: right 0.25s - - &.hide - right: -12px - - body.toggled & - right: -36px - - -nav - font-size: 14px - border-right: 1px solid rgba(0, 0, 0, 0.05) - width: 100% - position: fixed - left: -100% - transition: left 0.25s - - top: 0 - right: 0 - bottom: 0 - display: block - background: $blue - z-index: 10 - - a - border-bottom: none - color: rgba(255, 255, 255, 0.9) - font-size: 16px - line-height: 32px - height: 32px - display: block - - h3 - text-transform: uppercase - margin-top: 12px - font-size: 18px - color: white - font-weight: 600 - - body.toggled & - left: 0 - - @media screen and (min-width: $tablet) - background: white - display: block - position: absolute - left: 0 - top: 0 - bottom: 0 - width: 216px - z-index: 8 - padding-top: 96px - - h3 - color: #bbb - font-size: 14px - margin-bottom: 6px - font-weight: 400 - - - a - border-bottom: none - font-weight: 200 - font-size: 14px - color: #777 - font-size: 12px - height: 24px - - &:hover - color: #94a9d1 - - ul, ul li - list-style: none - padding: 0 - margin: 0 - - &.active a - @media screen and (min-width: $tablet) - color: #94a9d1 - font-weight: 600 - - ul - margin-bottom: 18px - -div.scroll - padding: 22px - overflow-y: scroll - height: 100% - z-index: 10 - -webkit-overflow-scrolling: touch - -main - -webkit-overflow-scrolling: touch - color: #555 - font-weight: 200 - width: 100% - padding: 24px - overflow: hidden - - @media screen and (min-width: $tablet) - height: 100% - position: absolute - position: relative - z-index: 6 - - top: 0 - bottom: 0 - right: 0 - left: 0 - - padding: 22px - padding-top: 110px - padding-left: 240px - overflow-y: scroll - max-width: 1440px - - a.edit - position: absolute - top: 146px - @media screen and (min-width: $tablet) - top: 102px - right: 24px - border-bottom: none - - h1 - margin-bottom: 12px - - h2 - margin-top: 24px - margin-bottom: 12px - border-bottom: 1px dotted rgba(0, 0, 0, 0.10) - - h3 - margin-top: 18px - - p - margin-top: 0 - margin-bottom: 12px - line-height: 1.75 - - blockquote - margin: 0 - background: rgba(247, 202, 201, 0.2) - margin-top: 18px - margin-bottom: 18px - margin-left: 0 - padding: 6px - padding-left: 12px - border-left: 5px solid #f6cfcf - - p - margin: 0 - padding: 0 - -pre, code - font-size: 12px - font-family: 'Source Code Pro', monospace - -webkit-overflow-scrolling: touch - -p code, li code - background: #fbfbfb - border-radius: 5px - padding: 3px 5px - display: inline-block - color: #6b7891 - box-shadow: 0 1px 0px 0px rgba(0, 0, 0, 0.1) - -pre - margin-top: 12px - margin-bottom: 15px - padding: 5px - background: #fbfbfb - border-radius: 10px - box-shadow: 0 3px 0px 0px rgba(0, 0, 0, 0.1) - -img - max-width: 100% diff --git a/1.5/template/styles/vapor-code.css b/1.5/template/styles/vapor-code.css deleted file mode 100755 index 9afb04ca..00000000 --- a/1.5/template/styles/vapor-code.css +++ /dev/null @@ -1,71 +0,0 @@ -/** - * GitHub Gist Theme - * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro - */ - -.hljs { - display: block; - padding: 0.5em; - color: #6b7891; - background: none; - overflow-x: auto; -} - -.hljs-comment, -.hljs-meta { - color: #969896; -} - -.hljs-string, -.hljs-variable, -.hljs-template-variable, -.hljs-strong, -.hljs-emphasis, -.hljs-quote { - color: #9cc074; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-type { - color: #9f69c4; -} - -.hljs-literal, -.hljs-symbol, -.hljs-bullet, -.hljs-attribute { - color: #0086b3; -} - -.hljs-section, -.hljs-name { - color: #63a35c; -} - -.hljs-tag { - color: #333333; -} - -.hljs-title, -.hljs-attr, -.hljs-selector-id, -.hljs-selector-class, -.hljs-selector-attr, -.hljs-selector-pseudo { - color: #795da3; -} - -.hljs-addition { - color: #55a532; - background-color: #eaffea; -} - -.hljs-deletion { - color: #bd2c00; - background-color: #ffecec; -} - -.hljs-link { - text-decoration: underline; -} diff --git a/1.5/testing/basic.md b/1.5/testing/basic.md deleted file mode 100755 index 5824f40b..00000000 --- a/1.5/testing/basic.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -currentMenu: testing-basic ---- - -# Basic Testing - -Testing is a critical part of any software application, and Vapor apps should be no different. In this documentation, we'll cover some of the basic setup required to be able to test against our `Droplet`. - -## Displacing Droplet Creation Logic - -Up to this point, a lot of our documentation has centered around putting our `Droplet` creation logic in `main.swift`. Unfortunately, when testing against our application, this code becomes largely inaccessible. The first thing we'll need to do is break this out and move the running into a new `Run` module. - -Here's an example of my setup file in `App`. I name mine `Droplet+Setup.swift`. Here's how it might look: - -```swift -import Vapor - -public func load(_ drop: Droplet) throws { - drop.preparations.append(Todo.self) - - drop.get { _ in return "put my droplet's logic in this `load` function" } - - drop.post("form") { req in - ... - return Response(body: "Successfully posted form.") - } - - // etc. -} -``` - -> [WARNING] Do **not** call `run()` anywhere within the `load` function as `run()` is a blocking call. - -## Updated `main.swift` - -Now that we've abstracted our loading logic, we'll need to move our `main.swift` into the `Run` module. - -Next, we need to update `main.swift` it to reflect those changes. Here's how it should look after: - -```swift -import Vapor -import App - -let drop = Droplet(...) -try load(drop) -drop.run() -``` - -> The reason we still initialize `Droplet` outside of the scope of `load` is so that we can have the option to initialize differently for testing. We'll cover that soon. - -## Testable Droplet - -The first thing we'll do is in my testing target `AppTests`, add a file called `Droplet+Test.swift`. It will look like this: - -```swift -@testable import Vapor -import App - -func makeTestDroplet() throws -> Droplet { - let drop = Droplet(arguments: ["dummy/path/", "prepare"]) - try load(drop) - try drop.runCommands() - return drop -} -``` - -This looks a lot like our initializer in `main.swift`, but there are 3 very key differences. - -### Droplet(arguments: ["dummy/path/", "prepare"], ... - -The `arguments:` parameter in our `Droplet` creation. This is rarely used except for advanced situations, but we'll use it here in testing to ensure that our `Droplet` doesn't try to automatically `serve` and block our thread. You can use arguments besides `"prepare"`, but unless you're doing something specific for an advanced situation, these arguments should suffice. - -### try drop.runCommands() - -You'll notice here that we're calling `runCommands()` instead of `run()`. This allows the `Droplet` to do all the setup it would normally do before booting without actually binding to a socket or exiting. - -### `@testable import Vapor` - -We'll need to import the testable compilation of Vapor to access the `runCommands` function. This is currently not public as a protection against accidental bugs in live apps. - -## Test Our Droplet - -Now that all of this has been created, we're ready to start testing our application's `Droplet` by adding tests under `AppLogicTests`. Here's how a really basic test might look: - -```swift -import XCTest -import HTTP -@testable import App - -class SmokeTest: XCTestCase { - func testEndpoint() throws { - let drop = try makeTestDroplet() - let request = try Request(method: .get, uri: "/") - let expectedBody = "It works." - - let response = try drop.respond(to: request) - let responseBody = try response.body.bytes!.string() - XCTAssertTrue(responseBody.contains(expectedBody)) - } -} -``` - -Notice that now you can use `CMD-U` to run your tests in Xcode with in-line results. In addition, you can run `vapor test` to test your code from the command line. If you choose to use `swift build` instead and you are using MySQL in your app, make sure you add the correct build flags to the call. - -Good luck, and happy testing! diff --git a/1.5/testing/modules.md b/1.5/testing/modules.md deleted file mode 100755 index f9eacae0..00000000 --- a/1.5/testing/modules.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -currentMenu: testing-modules ---- - -# Using Multiple Modules For Testing - -Testing a Vapor app gets tricky, and requires some maneuvering of your app targets. - -> [WARNING] Technically this is only necessary if you plan to run your tests on Linux. You can keep your tests in the same module if you want to only run your tests from the command line using `vapor test` - -## **Step 1:** Update Package.swift - -To start, you need to split up your Vapor project into a target called `App`, and a target called `Run`. The `Run` module will only include a `main.swift`, and your `App` will contain the actual logic for the app. - -Add a `Sources/Run` folder to your project, then add `targets` to your `Package.swift`: - -```swift -import PackageDescription - -let package = Package( - name: “ProjectName”, - targets: [ - Target(name: "App"), - Target(name: "Run", dependencies: ["App"]) - ], - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 5) - ], - exclude: [ - "Config", - "Database", - "Localization", - "Public", - "Resources" - ] -) -``` - -## **Step 2:** Update Tests Folder - -If you don't already have a `Tests` folder at the root of your project, add one. - -Make sure that your tests folder has a file called `LinuxMain.swift` and a folder called `AppTests`. In your `AppTests`, you can add your testing files like `UserTests.swift`. - -As always, make sure that you regenerate with `vapor xcode -y`. - -As long as there is at least one test file under `AppTests`, your generated Xcode project will have an `AppTests` target that you can run as usual. You can also run the tests from the command line with `vapor test`. diff --git a/1.5/websockets/custom.md b/1.5/websockets/custom.md deleted file mode 100755 index df323e6d..00000000 --- a/1.5/websockets/custom.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -currentMenu: websockets-custom ---- - -# Custom WebSockets - -Below are some examples of WebSockets using the underlying Engine package. - -## Client - -```Swift -import WebSockets - -try WebSocket.connect(to: url) { ws in - print("Connected to \(url)") - - ws.onText = { ws, text in - print("[event] - \(text)") - } - - ws.onClose = { ws, _, _, _ in - print("\n[CLOSED]\n") - } -} -``` - -## Server - -```Swift -import HTTP -import WebSockets -import Transport - -final class MyResponder: Responder { - func respond(to request: Request) throws -> Response { - return try request.upgradeToWebSocket { ws in - print("[ws connected]") - - ws.onText = { ws, text in - print("[ws text] \(text)") - try ws.send("🎙 \(text)") - } - - ws.onClose = { _, code, reason, clean in - print("[ws close] \(clean ? "clean" : "dirty") \(code?.description ?? "") \(reason ?? "")") - } - } - } -} - -let port = 8080 -let server = try Server, Serializer>(port: port) - -print("Connect websocket to http://localhost:\(port)/") -try server.start(responder: MyResponder()) { error in - print("Got server error: \(error)") -} -``` diff --git a/1.5/websockets/droplet.md b/1.5/websockets/droplet.md deleted file mode 100755 index 672cdb45..00000000 --- a/1.5/websockets/droplet.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -currentMenu: websockets-droplet ---- - -# Droplet WebSockets - -Creating a WebSocket server with the Droplet is easy. WebSockets work by upgrading an HTTP request to a WebSocket connection. - -Because of this, you should pick a URL for your WebSocket server to reside at. In this case, we use `/ws`. - -```swift -import Vapor - -let drop = Droplet() - -drop.socket("ws") { req, ws in - print("New WebSocket connected: \(ws)") - - // ping the socket to keep it open - try background { - while ws.state == .open { - try? ws.ping() - drop.console.wait(seconds: 10) // every 10 seconds - } - } - - ws.onText = { ws, text in - print("Text received: \(text)") - - // reverse the characters and send back - let rev = String(text.characters.reversed()) - try ws.send(rev) - } - - ws.onClose = { ws, code, reason, clean in - print("Closed.") - } -} - -drop.run() -``` - -To connect with a WebSocket client, you would open a connection to `ws:///ws`. - -Here is an example using JavaScript. - -```swift -var ws = new WebSocket("ws://0.0.0.0:8080/ws") - -ws.onmessage = function(msg) { - console.log(msg) -} - -ws.onopen = function(event) { - ws.send("test") -} -``` - -The above will log `tset` (`test` reversed). diff --git a/2.0/docs/auth/getting-started.md b/2.0/docs/auth/getting-started.md deleted file mode 100644 index 569e3d15..00000000 --- a/2.0/docs/auth/getting-started.md +++ /dev/null @@ -1,229 +0,0 @@ -# Getting Started - -Vapor's [Auth Provider](https://github.com/vapor/auth-provider) package makes implementing authentication and -authorization easy and secure. It supports common auth patterns such as: - -- Token (bearer) authentication -- Username + password (basic) authentication -- Permission-based authorization -- Session-based persistance - -Auth's modular, protocol-based nature also makes it a great foundation for custom auth needs. - -!!! tip - Use `vapor new --template=vapor/auth-template` to create a new [project template](https://github.com/vapor/auth-template) with AuthProvider and samples included. - -## Package - -To use Auth, you will need to have the [Auth Provider](https://github.com/vapor/auth-provider) added to your project. -This is as simple as adding the following line to your `Package.swift` file. - -```swift -.Package(url: "https://github.com/vapor/auth-provider.git", ...) -``` - -Check out the [Package](package.md) section for more information. - -## Example - -Let's take a look at how we can implement a simple, token-based authentication system using Vapor and Auth. - - -### User - -We will start by creating a model to represent our user. If you already have a user class, you can skip this step. - -```swift -import Vapor -import FluentProvider - -final class ExampleUser: Model { - let name: String - - ... -} - -extension ExampleUser: Preparation { ... } -``` - -Here we create a very simple user with just one property: a name. - -!!! seealso - We're omitting most of `Model` and `Preparation` protocol requirements. Check out Fluent's - [Getting Started](../fluent/getting-started.md) for more information about these protocols. - - -### Token - -Next let's create a model to represent our authentication tokens. These will be stored in a separate database -table or collection called "tokens". - -When a user logs in, we will create a new token for them. They will then use this token on subsequent requests -instead of their username and password. - -For now, here's what our simple token model will look like. - -```swift -import Vapor -import FluentProvider - -final class ExampleToken: Model { - let token: String - let userId: Identifier - - var user: Parent { - return parent(id: userId) - } - - ... -} - -extension ExampleToken: Preparation { ... } -``` - -This token has two properties: - -- token: a unique, random string that we will send in requests -- userId: the identifier for the user to whom this token belongs - -!!! seealso - We're using Fluent relations here. Check out Fluent's [Relations](../fluent/relations.md) - section for more information. - -### Token Authenticatable - -Now that we have our example user and token, we can make our user authenticatable with the token. - -This might sound complicated, but it's actually pretty easy: - -```swift -import AuthProvider - -extension ExampleUser: TokenAuthenticatable { - // the token model that should be queried - // to authenticate this user - public typealias TokenType = ExampleToken -} - -``` - -Now that our example user is `TokenAuthenticatable`, we can move on to the next step! - -### User Helper - -Let's add a simple convenience method on request for accessing the authenticated user. - -```swift -extension Request { - func user() throws -> ExampleUser { - return try auth.assertAuthenticated() - } -} -``` - -This is a nice shortcut that will come in handy in a few steps. - -### Middleware - -To require authentication we need to add the `TokenAuthenticationMiddleware`. You can apply this middleware -to individual routes or to the entire Droplet. For simplicity, we'll apply it to the Droplet. - -```swift -import Vapor -import AuthProvider -import FluentProvider - -let config = try Config() - -config.preparations.append(ExampleUser.self) -config.preparations.append(ExampleToken.self) - -let drop = try Droplet(config) - - -let tokenMiddleware = TokenAuthenticationMiddleware(ExampleUser.self) - -/// use this route group for protected routes -let authed = drop.grouped(tokenMiddleware) -``` - -Since our `ExampleUser` class is `TokenAuthenticatable`, we can pass it into the middleware's init method. - -!!! seealso - If you only want to require authentication for certain routes, look at our - [Route Group](../routing/group.md) section in the routing docs. - -### Route - -Now that we have a route group protected by our TokenMiddleware, let's add a route to -return the authenticated user's name. - -```swift -authed.get("me") { req in - // return the authenticated user's name - return try req.user().name -} -``` - -!!! tip - We're using the `.user()` convenience we added to `Request` here. It is a shortcut - for `let user = try req.auth.assertAuthenticated(ExampleUser.self)` - - -### Database - -That's it! We now have a functioning authentication system. Let's add a couple of entries -to our database and test it out. - -#### Users - -| id | name | -|----|------| -| 1 | Bob | - -#### Tokens - -| id | token | user_id | -|----|-------|---------| -| 1 | foo | 1 | - -### Request - -Now we can make a request to our Vapor app. - -```http -GET /me HTTP/1.1 -Authorization: Bearer foo -``` - -And we should get a response like. - -```http -HTTP/1.1 200 OK -Content-Type: text/plain - -Bob -``` - -#### Bad Token - -To make sure it's secure, let's test using a token that's not in our database. - -```http -GET /me HTTP/1.1 -Authorization: Bearer not-a-token -``` - -And we should get a response like. - -```http -HTTP/1.1 403 Forbidden -``` - -### Next Steps - -To build this out into a production-ready authentication system, you will need to build some -additional routes for creating users and creating tokens. - -Continue on in the Auth section to learn more about different types of authentication. - diff --git a/2.0/docs/auth/helper.md b/2.0/docs/auth/helper.md deleted file mode 100644 index fbae82d8..00000000 --- a/2.0/docs/auth/helper.md +++ /dev/null @@ -1,81 +0,0 @@ -# Auth Helper - -The Auth package adds a convenience property on every request that makes it -easy to authenticate, persist, and unauthenticate users. - - -## Authentication - -### Checking - -You can get the currently authenticated user. - -```swift -let user = req.auth.authenticated(User.self) -``` - -You can check to see if the user is authenticated. - -```swift -if req.auth.isAuthenticated(User.self) { - ... -} -``` - -You can also assert that the user is authenticated. - -```swift -let user = try req.auth.assertAuthenticated(User.self) - -``` - -!!! note: - A 403 Forbidden error will be thrown if the user is not authenticated. - -### Manual - -You can manually authenticate a user. - -```swift -if let user = try User.find(1) { - req.auth.authenticate(user) -} -``` - -You can also unauthenticate the currently authenticated user. - - -```swift -try req.auth.unauthenticate() -``` - -!!! note: - If the user is `Persistable`, they will also be unpersisted. - - -## Headers - -The helper can be used to access common authorization headers. - -```swift -print(req.auth.header) -``` - -### Token - -The header has additional conveniences for parsing out bearer tokens. - -```swift -print(req.auth.header?.bearer) -``` - -!!! tip - You can use `_authorizationBasic` and `_authorizationBearer` to send tokens in the URL string. - -### Password - -And basic auth username + password. - -```swift -print(req.auth.header?.basic) -``` \ No newline at end of file diff --git a/2.0/docs/auth/package.md b/2.0/docs/auth/package.md deleted file mode 100644 index 517a22cc..00000000 --- a/2.0/docs/auth/package.md +++ /dev/null @@ -1,55 +0,0 @@ -# Using Auth - -This section outlines how to import the Auth package both with or without a Vapor project. - -## With Vapor - -The easiest way to use Auth with Vapor is to include the Auth provider. - -You can achieve this by running: - -```bash -vapor provider add auth -``` - -or by manually modifying your `Package.swift` file: - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/auth-provider.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -The Auth provider package adds Auth to your project and adds some additional, vapor-specific conveniences like auth middleware. - -After you added the dependency, fetch it using `vapor update`. - -Using `import AuthProvider` will import all of the auth middleware and the Authentication and Authorization modules. - -## Without Vapor - -At the core of the Vapor Auth provider is an Authentication and Authorization module based on Fluent, which you can use as a stand-alone package by including the Auth in your `Package.swift` files: - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/auth.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -After you added the dependency, fetch it using `vapor update`. - -Use `import Auth` to access the core auth classes. diff --git a/2.0/docs/auth/password.md b/2.0/docs/auth/password.md deleted file mode 100644 index 9cb7d32a..00000000 --- a/2.0/docs/auth/password.md +++ /dev/null @@ -1,125 +0,0 @@ -# Username + Password (Basic) Auth - -The `Authorization: Basic ...` header can be used to send username and password credentials -for authentication. - -This page will show you how to use this type of authentication in your web app. - -!!! note: - Sending and storing passwords should be avoided wherever possible. Use tokens or - sessions persistance to prevent the need for sending the password in every request. - -## Password Authenticatable - -Start by conforming your user model to the `PasswordAuthenticatable` protocol. - -```swift -import AuthProvider - -extension User: PasswordAuthenticatable { } -``` - -### Custom - -If your user conforms to `Model`, all of the required methods will be implemented automatically. However, -you can implement them if you want to do something custom. - - -```swift -extension User: PasswordAuthenticatable { - /// Return the user matching the supplied - /// username and password - static func authenticate(_: Password) throws -> Self { - // something custom - } - - /// The entity's hashed password used for - /// validating against Password credentials - /// with a PasswordVerifier - var hashedPassword: String? { - // something custom - } - - /// The key under which the user's username, - /// email, or other identifing value is stored. - static var usernameKey: String { - // something custom - } - - /// The key under which the user's password - /// is stored. - static var passwordKey: String { - // something custom - } - - /// Optional password verifier to use when - /// comparing plaintext passwords from the - /// Authorization header to hashed passwords - /// in the database. - static var passwordVerifier: PasswordVerifier? { - // some hasher - } -} -``` - -## Middleware - -Once your model conforms to the `PasswordAuthenticatable` protocol, you can create the middleware. - - -```swift -import Vapor -import AuthProvider - -let drop = try Droplet() - -let passwordMiddleware = PasswordAuthenticationMiddleware(User.self) - -let authed = try drop.grouped(passwordMiddleware) - -try drop.run() -``` - -All routes added to the `authed` route group will be protected by the password middleware. - -!!! seealso - If you only want to globally require the password middleware, checkout the - [Middleware Config](../http/middleware.md/#config) section in the HTTP docs. - -### Route - -Now you can add a route to return the authenticated user. - -```swift -authed.get("me") { req in - // return the authenticated user - return try req.auth.assertAuthenticated(User.self) -} -``` - -Call `req.user.authenticated(User.self)` to get access to the authenticated user. - - -## Request - -Now we can make a request to our Vapor app. - -```http -GET /me HTTP/1.1 -Authorization: Basic dmFwb3I6Zm9v -``` - -!!! note - `dmFwb3I6Zm9v` is "vapor:foo" base64 encoded where "vapor" is the username and - "foo" is the password. This is the format of Basic authorization headers. - -And we should get a response like. - -```http -HTTP/1.1 200 OK -Content-Type: text/plain - -Vapor -``` - - diff --git a/2.0/docs/auth/persist.md b/2.0/docs/auth/persist.md deleted file mode 100644 index d8da2cab..00000000 --- a/2.0/docs/auth/persist.md +++ /dev/null @@ -1,140 +0,0 @@ -# Persisting Auth - -Persisting authentication means that a user does not need to provide their credentials with every request. -This is useful for web apps where a user should only have to log in once. - -!!! note - For APIs, it's recommended that the user send a token with every request. - See [Getting Started](getting-started.md) for an example about Token auth. - - -## Sessions - -Sessions are built into Vapor by default and are an easy way to persist users in your web app. - -### SessionPersistable - -The first step is to conform your user model to the `SessionPersistable` protocol. - -```swift -import AuthProvider - -extension User: SessionPersistable {} -``` - -If your user is a Model, the protocol methods will be implemented automatically. However, -you can implement them if you want to do something custom. - -```swift -import AuthProvider -import HTTP - -extension User: SessionPersistable { - func persist(for: Request) throws { - // something custom - } - - static func fetchPersisted(for: Request) throws -> Self? { - // something custom - } -} - -``` - -### Middleware - -Now that the user is `SessionPersistable`, we can create our middleware. - -#### Sessions - -First let's start by creating `SessionsMiddleware`. We'll use the `MemorySessions()` to get started. - -```swift -let memory = MemorySessions() -let sessionsMiddleware = SessionsMiddleware(memory) -``` - -#### Persist - -Now let's create the `PersistMiddleware`. This will take care of persisting our user once they've -been authenticated. - -```swift -let persistMiddleware = PersistMiddleware(User.self) -``` - -Since our user conforms to `SessionPersistable` (and thus `Persistable`), we can pass it -into this middleware's init. - -#### Authentication - -Now to create the authentication middleware of your choice. We'll use `PasswordAuthenticationMiddleware` -which requires an `Authorization: Basic ...` header with the user's username and password. - - -```swift -let passwordMiddleware = PasswordAuthenticationMiddleware(User.self) -``` - -!!! note: - `User` must conform to `PasswordAuthenticatable` to be used with this middleware. - See the [Password](password.md) section to learn more. - -### Droplet - -Now we can create a Droplet and add all of our middleware. - -```swift -import Vapor -import Sessions -import AuthProvider - -let drop = try Droplet() - -let authed = drop.grouped([sessionsMiddleware, persistMiddleware, passwordMiddleware]) -``` - -!!! seealso - If you only want to globally require the password middleware, checkout the - [Middleware Config](../http/middleware.md/#config) section in the HTTP docs. - - -### Route - -Now you can add a route to return the authenticated user. - -```swift -authed.get("me") { req in - // return the authenticated user - return try req.auth.assertAuthenticated(User.self) -} -``` - -### Request - -Now we can make a request to our Vapor app. - -```http -GET /me HTTP/1.1 -Authorization: Basic dmFwb3I6Zm9v -``` - -!!! note - `dmFwb3I6Zm9v` is "vapor:foo" base64 encoded where "vapor" is the username and - "foo" is the password. This is the format of Basic authorization headers. - -And we should get a response like. - -```http -HTTP/1.1 200 OK -Content-Type: text/plain -Set-Cookie: vapor-session=... - -Vapor -``` - -Notice the `vapor-session` in the response. This can be used in subsequent requests instead of -the username and password. - - - diff --git a/2.0/docs/auth/provider.md b/2.0/docs/auth/provider.md deleted file mode 100644 index 58c8530e..00000000 --- a/2.0/docs/auth/provider.md +++ /dev/null @@ -1,23 +0,0 @@ -# Auth Provider - -After you've [added the Auth Provider package](package.md) to your project, setting the provider up in code is easy. - -## Add to Droplet - -Register the `AuthProvider.Provider` with your Droplet in your `Config+Setup.swift` file: - -```swift -import App -import AuthProvider - -let config = try Config() -try config.addProvider(AuthProvider.Provider.self) - -let drop = try Droplet(config) - -... -``` - -## Done - -You are now ready to start using the Auth package. diff --git a/2.0/docs/auth/redirect-middleware.md b/2.0/docs/auth/redirect-middleware.md deleted file mode 100644 index 64cc68ec..00000000 --- a/2.0/docs/auth/redirect-middleware.md +++ /dev/null @@ -1,121 +0,0 @@ -# Redirect Middlewares - -Included in the [AuthProvider](package.md) package are `RedirectMiddleware` and `InverseRedirectMiddleware` classes that will help you -redirect unauthenticated or authenticated requests to a given path. This is especially useful for redirecting users away from secure -pages to a login page and vice versa. - -## Redirect Middleware - -Let's take a look at how to add a `RedirectMiddleware` to your application. - -### Existing Auth - -Since we only want this middleware to apply to secure pages, we'll apply it using route groups. - -You should already have a protected area in your application using one of the authentication middlewares. - -```swift -import Vapor -import AuthProvider - -let drop = try Droplet() - -drop.get("login") { req in - return // some login form -} - -let auth = TokenAuthenticationMiddleware(User.self) -let protected = drop.grouped([auth]) -protected.get("secure") { req in - let user = try req.auth.assertAuthenticated(User.self) - return "Welcome to the secure page, \(user.name)" -} -``` - -The above snippet protects access to the page at `GET /secure` using the `TokenAuthenticationMiddleware`. - -Since we've applied `TokenAuthenticationMiddleware`, this page cannot be accessed by anyone not authenticated. -Although this is perfectly secure, we should provide a better experience for unauthenticated users. Instead of -just showing them an error message, we can redirect them to the login page. - -### Add Redirect - -Creating a redirect middleware is very simple. We'll use one of the presets for redirecting a user to `/login`. - -```swift -let redirect = RedirectMiddleware.login() -``` - -Now we just need to add this redirect middleware to our `protected` route group mentioned previously. - -```swift -let protected = drop.grouped([redirect, auth]) -``` - -!!! warning - Make sure the redirect middleware comes _before_ the auth middleware. - -### Complete Example - -Now whenever an unauthenticated user attemps to visit `GET /secure`, they will be redirected to `GET /login`. - -```swift -import Vapor -import AuthProvider - -let drop = try Droplet() - -let redirect = RedirectMiddleware.login() -let auth = TokenAuthenticationMiddleware(TestUser.self) - -let protected = drop.grouped([redirect, auth]) -protected.get { req in - let user = try req.auth.assertAuthenticated(TestUser.self) - return "Welcome to the dashboard, \(user.name)" -} -``` - -### Custom Route - -If your login page is not `/login` or you'd like the redirect middleware to redirect to a different type of page, -simply use the full initializer. - -```swift -let redirect = RedirectMiddleware(path: "/foo") -``` - -## Inverse Redirect Middleware - -Complementary to the `RedirectMiddleware` is the `InverseRedirectMiddleware`. Just like you want to redirect unauthenticated -users away from secure pages, you also might want to redirect _authenticated_ users away from certain pages. - -For example, if a user is already authenticated and they visit the login page, they might be confused and attempt to login again. - -### Example - -Here is an example of the `InverseRedirectMiddleware` being used to redirect authenticated `User`s away from the login page. - -We are using the preset `.home()` convenience, which redirects the user to `GET /`. - -```swift -import Vapor -import AuthProvider - -let drop = try Droplet() - -let redirect = InverseRedirectMiddleware.home(User.self) -let group = drop.grouped([redirect]) -group.get("login") { req in - return "Please login" -} -``` - -### Custom Route - -If your desired page is not `/` or you'd like the inverse redirect middleware to redirect to a different type of page, -simply use the full initializer. - -```swift -let redirect = InverseRedirectMiddleware(User.self, path: "/foo") -``` - diff --git a/2.0/docs/bits/overview.md b/2.0/docs/bits/overview.md deleted file mode 100644 index e2088d8a..00000000 --- a/2.0/docs/bits/overview.md +++ /dev/null @@ -1,49 +0,0 @@ -# Bits - -The bits package is included in Vapor by default and provides a convenient API for working with bytes. - -## Typealias - -The bits package provides two type-aliases for bytes. - -```swift -typealias Byte = UInt8 -typealias Bytes = [Byte] -``` - -## BytesConvertible - -It's quite often that we want to convert objects to and from byte arrays when we're working. The BytesConvertible helps define objects that have these capabilities. This is implemented already on most objects in Vapor that can/should be converted to and from byte arrays. - -```Swift -let hello = String(bytes: [72, 101, 108, 108, 111]) -let bytes = hello.makeBytes() -``` - -## String - -Converting from bytes to string using the UTF-8 encoding is easy. - -```swift -let bytes = "hello".makeBytes() -let string = bytes.makeString() -print(string) // "hello" -``` - -## Byte - -The upper and lowercase latin alphabet and some additional control characters are statically typed on the `Byte`. - -```swift -let bytes: Bytes = [.h, .e, .l, .l, .o] -print(bytes.makeString()) // "hello" -``` - -This makes byte manipulation and comparison easy and is useful for building things like parsers and serializers. - -```swift -let byte: Byte = 65 -if byte == .A { - print("found A!") -} -``` diff --git a/2.0/docs/bits/package.md b/2.0/docs/bits/package.md deleted file mode 100644 index fe13a1d7..00000000 --- a/2.0/docs/bits/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using Bits - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import Bits -``` - -## Without Vapor - -Bits provides a lot of byte-manipulation conveniences for any server-side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/bits.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -Use `import Bits` to access Bits' APIs. \ No newline at end of file diff --git a/2.0/docs/cache/overview.md b/2.0/docs/cache/overview.md deleted file mode 100644 index 6c2b84a0..00000000 --- a/2.0/docs/cache/overview.md +++ /dev/null @@ -1,78 +0,0 @@ -# Cache - -Vapor's `CacheProtocol` allows you to store and fetch items from a cache using optional expiration dates. - -By default, the Droplet's cache is set to `MemoryCache`. See the various [providers](#providers) below. - -## Store - -Storing data into the cache is straightforward. - -```swift -try drop.cache.set("hello", "world") -``` - -### Expiration - -When storing data, you can also supply an expiration date. - -```swift -try drop.cache.set("ephemeral", 42, expiration: Date(timeIntervalSinceNow: 30)) -``` - -In the above example, the supplied key value pair will expire after 30 seconds. - -## Fetch - -You can retreive data from the cache using the `.get()` method. - -```swift -try drop.cache.get("hello") // "world" -``` - -## Delete - -Keys can be deleted from the cache using the `.delete()` method. - -```swift -try drop.cache.delete("hello") -``` - -## Providers - -Here is a list of official cache providers. You can [search GitHub](https://github.com/search?utf8=✓&q=topic%3Avapor-provider+topic%3Acache&type=Repositories) for additional packages. - -| Type | Key | Description | Package | Class | -|--------|--------|---------------------------------|-----------------------------------------|-------------| -| Memory | memory | In-memory cache. Not persisted. | Vapor | MemoryCache | -| Fluent | fluent | Uses Fluent database. | [Fluent Provider](../fluent/package.md) | FluentCache | -| Redis | redis | Uses Redis database. | [RedisProvider](../redis/package.md) | RedisCache | - -### How to Use - -To use a different cache provider besides the default `MemoryCache`, make sure you have added the provider to your Package. - -```swift -import Vapor -import Provider - -let config = try Config() -try config.addProvider(Provider.Provider.self) - -let drop = try Droplet(config) - - -... -``` - - -Then change the Droplet's configuration file. - -`Config/droplet.json` - -```json -{ - "cache": "" -} -``` - diff --git a/2.0/docs/cache/package.md b/2.0/docs/cache/package.md deleted file mode 100644 index 75a29aee..00000000 --- a/2.0/docs/cache/package.md +++ /dev/null @@ -1,7 +0,0 @@ -# Using Cache - -This package is included with the Vapor dependency, use - -```Swift -import Cache -``` diff --git a/2.0/docs/configs/config.md b/2.0/docs/configs/config.md deleted file mode 100644 index 57f236b5..00000000 --- a/2.0/docs/configs/config.md +++ /dev/null @@ -1,221 +0,0 @@ -# Config - -An application's configuration settings. Cloud applications generally require complex configurations that can adjust based on their environment. Vapor intends to provide a flexible configuration interaction that can be customized for a given user. - -## QuickStart - -For Vapor applications, configuration files are expected to be nested under a top level folder named `Config`. Here's an example of a basic config featuring a single `servers` configuration. - -```bash -./ -├── Config/ -│ ├── server.json -``` - -And an example of how this might look: - -```JSON -{ - "host": "0.0.0.0", - "port": 8080, - "securityLayer": "none" -} -``` - -What that's saying, is that our application should start a server on port `8080` and host `0.0.0.0`. This represents the following url: `http://localhost:8080`. - -### Custom Keys - -Let's add a custom key to the `server.json` file: - -```JSON -{ - "host": "0.0.0.0", - "port": 8080, - "securityLayer": "none", - "custom-key": "custom value" -} -``` - -This can be accessed from your application's config using the following. - -```swift -let customValue = drop.config["server", "custom-key"]?.string ?? "default" -``` - -That's it, feel free to add and utilize keys as necessary to make your application configuration easier. - -## Config Syntax - -You can access your config directory with the following syntax. `app.config[fileName, path, to, key]`. For example, let's hypothesize that in addition to the `server.json` file we mentioned earlier, there is also a `keys.json` that looks like this: - -```JSON -{ - "test-names": [ - "joe", - "jane", - "sara" - ], - "mongo": { - "url" : "www.customMongoUrl.com" - } -} -``` - -We can access this file by making sure the first argument in our subscript is keys. To get the first name in our list: - -```swift -let name = drop.config["keys", "test-names", 0]?.string ?? "default" -``` - -Or our mongo url: - -```swift -let mongoUrl = drop.config["keys", "mongo", "url"]?.string ?? "default" -``` - -## Advanced Configurations - -Having the default `server.json` is great, but what about more complex scenarios. For example, what if we want a different host in production and in development? These complex scenarios can be achieved by adding additional folders to our `Config/` directory. Here's an example of a folder structure that's setup for production and development environments. - -```bash -WorkingDirectory/ -├── Config/ -│ ├── server.json -│ ├── production/ -│ │ └── server.json -│ ├── development/ -│ │ └── server.json -│ └── secrets/ -│ └── server.json -``` - -> You can specify the environment through the command line by using --env=. Custom environments are also available, a few are provided by default: production, development, and testing. - -```bash -vapor run --env=production -``` - -### PRIORITY - -Config files will be accessed in the following priority. - -1. CLI (see below) -2. Config/secrets/ -3. Config/name-of-environment/ -4. Config/ - -What this means is that if a user calls `app.config["server", "host"]`, the key will be searched in the CLI first, then the `secrets/` directory, then the top level default configs. - -> `secrets/` directory should very likely be added to the gitignore. - -### EXAMPLE - -Let's start with the following JSON files. - -#### `server.json` - -```JSON -{ - "host": "0.0.0.0", - "port": 9000 -} -``` - -#### `production/server.json` - -```JSON -{ - "host": "127.0.0.1", - "port": "$PORT" -} -``` - -> The `"$NAME"` syntax is available for all values to access environment variables. - -Please notice that `server.json`, and `production/server.json` both declare the same keys: `host`, and `port`. In our application, we'll call: - -```swift -// will load 0.0.0.0 or 127.0.0.1 based on above config -let host = drop.config["server", "host"]?.string ?? "0.0.0.0" -// will load 9000, or environment variable port. -let port = drop.config["server", "port"]?.int ?? 9000 -``` -## Configuration file Options - -#### `droplet.json` -```JSON -{ - "server": "engine", - "client": "engine", - "console": "terminal", - "log": "console", - "hash": "crypto", - "cipher": "crypto", - "middleware": [ - "error", - "date", - "file" - ], - "commands": [ - "prepare" - ] -} -``` - -#### `server.json` -```JSON -{ - "port": "$PORT:8080", - "host": "0.0.0.0", - "securityLayer": "none" -} -``` - -#### `fluent.json` -```JSON -{ - "driver": "memory", - "keyNamingConvention": "snake_case", - "migrationEntityName": "fluent", - "pivotNameConnector": "_", - "autoForeignKeys": true, - "defaultPageKey": "page", - "defaultPageSize": 10, - "log": false, - "maxConnections":10 -} -``` - -#### `crypto.json` -```JSON -{ - "hash": { - "method": "sha256", - "encoding": "hex", - "key": "0000000000000000" - }, - - "cipher": { - "method": "aes256", - "encoding": "base64", - "key": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - } -} -``` - -## COMMAND LINE - -In addition to json files nested within the `Config/` directory, we can also use the command line to pass arguments into our config. By default, these values will be set as the "cli" file, but more complex options are also available. - -If you want command line arguments set to a file besides "cli", you can use this more advanced specification. For example, the following CLI command: - -```bash ---config:keys.analytics=124ZH61F -``` - -would be accessible within your application by using the following: - -```swift -let analyticsKey = drop.config["keys", "analytics"]?.string -``` diff --git a/2.0/docs/core/overview.md b/2.0/docs/core/overview.md deleted file mode 100644 index 550696b4..00000000 --- a/2.0/docs/core/overview.md +++ /dev/null @@ -1,43 +0,0 @@ -# Core - -Core provides some conveniences for common tasks. - -## Background - -Easily create a background thread using `background()` - -```swift -print("hello") - -try background { - print("world") -} -``` - -## Portal - -Portals allow you to make async tasks blocking. - -```swift -let result = try Portal.open { portal in - someAsyncTask { result in - portal.close(with: result) - } -} - -print(result) // the result from the async task -``` - -## RFC1123 - -Create RFC1123 type dates. - -```swift -let now = Date().rfc1123 // string -``` - -You can also parse RFC1123 strings. - -``` -let parsed = Date(rfc1123: "Mon, 10 Apr 2017 11:26:13 GMT") -``` diff --git a/2.0/docs/core/package.md b/2.0/docs/core/package.md deleted file mode 100644 index 498fd714..00000000 --- a/2.0/docs/core/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using Core - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import Core -``` - -## Without Vapor - -Core provides a lot of conveniences for any server-side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/core.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Core` to access Core's APIs. \ No newline at end of file diff --git a/2.0/docs/debugging/overview.md b/2.0/docs/debugging/overview.md deleted file mode 100644 index d38db94c..00000000 --- a/2.0/docs/debugging/overview.md +++ /dev/null @@ -1,33 +0,0 @@ -# Debugging - -Conforming your error types to `Debuggable` allows Vapor to create richer error messages and makes debugging easier. - -```swift -import Debugging - -extension FooError: Debuggable { - // conform here -} -``` - -Now when a `FooError` is thrown, you will get a nice message in your console. - -```sh -Foo Error: You do not have a `foo`. - -Identifier: DebuggingTests.FooError.noFoo - -Here are some possible causes: -- You did not set the flongwaffle. -- The session ended before a `Foo` could be made. -- The universe conspires against us all. -- Computers are hard. - -These suggestions could address the issue: -- You really want to use a `Bar` here. -- Take up the guitar and move to the beach. - -Vapor's documentation talks about this: -- http://documentation.com/Foo -- http://documentation.com/foo/noFoo -``` diff --git a/2.0/docs/debugging/package.md b/2.0/docs/debugging/package.md deleted file mode 100644 index 57e96e8f..00000000 --- a/2.0/docs/debugging/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using Debugging - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import Debugging -``` - -## Without Vapor - -Debugging is a convenient protocol for providing more information about error messages. You can use it in any of your Swift projects. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/debugging.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -Use `import Debugging` to access Debugging' APIs. diff --git a/2.0/docs/deploy/apache2.md b/2.0/docs/deploy/apache2.md deleted file mode 100644 index 622fa262..00000000 --- a/2.0/docs/deploy/apache2.md +++ /dev/null @@ -1,130 +0,0 @@ -# Deploying with Apache2 - -[Apache2](https://httpd.apache.org/) is an effort to develop and maintain an open-source HTTP server for modern operating systems including UNIX and Windows. While Vapor supports directly serving HTTP requests with or without TLS, proxying behind Apache2 can provide increased performance, security, and ease-of-use. - -!!! note - This page is for proxying behind Apache2. The recommened method is proxying Vapor HTTP servers behind [Nginx](nginx.md). - -## Overview - -What does it mean to proxy an HTTP server? In short, a proxy acts as a middleman between the public internet and your HTTP server. Requests come to the proxy and then it sends them to Vapor. - -An important feature of this middleman proxy is that it can alter or even redirect the requests. For instance, the proxy can require that the client use TLS (https), rate limit requests, or even serve public files without talking to your Vapor application. - -![apache2-proxy](https://user-images.githubusercontent.com/2223276/28477961-5e32bafc-6e24-11e7-94f1-a09c59673d1f.png) - -### More Detail - -The default port for receiving HTTP requests is port `80` (and `443` for HTTPS). When you bind a Vapor server to port `80`, it will directly receive and respond to the HTTP requests that come to your server. When adding a proxy like Apache2, you bind Vapor to an internal port, like port `8080`. - -!!! note - Ports greater than 1024 do not require `sudo` to bind. - -When Vapor is bound to a port besides `80` or `443`, it will not be accessible to the outside internet. You then bind Apache2 to port `80` and configure it to route requests to your Vapor server bound at port `8080` (or whichever port you've chosen). - -And that's it. If Apache2 is properly configured, you will see your Vapor app responding to requests on port `80`. Apache2 proxies the requests and responses invisibly. - -## Install Apache2 - -The first step is installing Apache2. One of the great parts of Apache2 is the tremendous amount of community resources and documentation surrounding it. Because of this, we will not go into great detail here about installing Apache2 as there is almost definitely a tutorial for your specific platform, OS, and provider. - -Tutorials: - -- [How To Set Up Apache Virtual Hosts on Ubuntu 14.04 LTS](https://www.digitalocean.com/community/tutorials/how-to-set-up-apache-virtual-hosts-on-ubuntu-14-04-lts) -- [How To Set Up Apache Virtual Hosts on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-apache-virtual-hosts-on-ubuntu-16-04) - -### APT - -Apache2 can be installed through APT. - -```sh -sudo apt-get update -sudo apt-get install apache2 -``` - -Check whether Apache2 was installed correctly by visiting your server's IP address in a browser - -```sh -http://server_domain_name_or_IP -``` - -### Service - -The service can be started or stopped. - -```sh -sudo service apache2 stop -sudo service apache2 start -sudo service apache2 restart -``` - -## Booting Vapor - -Apache2 can be started an stopped with the `sudo service apache2 ...` commands. You will need something similar to start and stop your Vapor server. - -There are many ways to do this, and they depend on which platform you are deploying to. Check out the [Supervisor](supervisor.md) and [Nginx](nginx.md) instructions to add commands for starting and stopping your Vapor app. - -## Configure Proxy - -The configuration files for enabled sites can be found in `/etc/apache2/sites-enabled/`. - -Create a new file or copy the example template from `/etc/apache2/sites-available/` to get started. - -Here is an example configuration file for a Vapor project called `Hello` in the home directory. - -```apache -# example.com Configuration - - - DocumentRoot /home/vapor/Hello/Public/ - ServerName hello.com - - # Using ProxyPass will send the following headers: - # X-Forwarded-For: The IP address of the client. - # X-Forwarded-Host: The original host requested by the client in the Host HTTP request header. - # X-Forwarded-Server The hostname of the proxy server. - - ProxyPreserveHost On - ProxyPass / http://127.0.0.1:8080/ - ProxyPassReverse / http://127.0.0.1:8080/ - - ProxyTimeout 3 - -``` - -This configuration file assumes the `Hello` project binds to port `8080` when started in production mode. - -### Serving Files - -Apache2 can also serve public files without asking your Vapor app. This can improve performance by freeing up the Vapor process for other tasks under heavy load. - -```apache - - ... - - ProxyPreserveHost On - # Serve all files in Public folder directly, bypassing proxy (this must be before ProxyPass /) - ProxyPass /Public ! - ProxyPass / http://127.0.0.1:8080/ - - ... - -``` - -### TLS - -Adding TLS is relatively straightforward as long as the certificates have been properly generated. To generate TLS certificates for free, check out [Let's Encrypt](https://letsencrypt.org/getting-started/). - -```apache - - - ... - - SSLCertificateFile /etc/letsencrypt/live/hello.com/fullchain.pem - SSLCertificateKeyFile /etc/letsencrypt/live/hello.com/privkey.pem - Include /etc/letsencrypt/options-ssl-apache.conf - - -``` - -The configuration above match the settings for TLS generated by Let's Encrypt for Apache2. diff --git a/2.0/docs/deploy/cloud.md b/2.0/docs/deploy/cloud.md deleted file mode 100644 index f6744f50..00000000 --- a/2.0/docs/deploy/cloud.md +++ /dev/null @@ -1,25 +0,0 @@ -# Vapor Cloud - -The best way to deploy your Vapor application is [Vapor Cloud](https://vapor.cloud). - -![Vapor Cloud Website](https://user-images.githubusercontent.com/1342803/31300830-fa6497b6-aac3-11e7-8852-33b30aa13186.png) - -## Quick Start - -If you already have the [Vapor Toolbox](../getting-started/toolbox.md) installed, then you can deploy your -Vapor app to the cloud with just one command. - -```sh -vapor cloud deploy -``` - -![Vapor Toolbox Deploy](https://user-images.githubusercontent.com/1342803/31300850-14d7d8b0-aac4-11e7-8e42-84d406ffb76b.png) - - -!!! note: - Run the deploy command inside the root directory of your Vapor project (the one with the `Package.swift` file). - -## Step-by-step Guide - -Visit the [step-by-step guide](https://docs.vapor.cloud/quick-start/) on Vapor Cloud's docs for detailed -instructions on how to deploy your app to Vapor Cloud! diff --git a/2.0/docs/deploy/nginx.md b/2.0/docs/deploy/nginx.md deleted file mode 100644 index 3ea6a466..00000000 --- a/2.0/docs/deploy/nginx.md +++ /dev/null @@ -1,148 +0,0 @@ -# Deploying with Nginx - -Nginx is an extremely fast, battle tested, and easy-to-configure HTTP server and proxy. While Vapor supports directly serving HTTP requests with or without TLS, proxying behind Nginx can provide increased performance, security, and ease-of-use. - -!!! note - We recommend proxying Vapor HTTP servers behind Nginx. - -## Overview - -What does it mean to proxy an HTTP server? In short, a proxy acts as a middleman between the public internet and your HTTP server. Requests come to the proxy and then it sends them to Vapor. - -An important feature of this middleman proxy is that it can alter or even redirect the requests. For instance, the proxy can require that the client use TLS (https), rate limit requests, or even serve public files without talking to your Vapor application. - -![nginx-proxy](https://cloud.githubusercontent.com/assets/1342803/20184965/5d9d588a-a738-11e6-91fe-28c3a4f7e46b.png) - -### More Detail - -The default port for receiving HTTP requests is port `80` (and `443` for HTTPS). When you bind a Vapor server to port `80`, it will directly receive and respond to the HTTP requests that come to your server. When adding a proxy like Nginx, you bind Vapor to an internal port, like port `8080`. - -!!! note - Ports greater than 1024 do not require `sudo` to bind. - -When Vapor is bound to a port besides `80` or `443`, it will not be accessible to the outside internet. You then bind Nginx to port `80` and configure it to route requests to your Vapor server bound at port `8080` (or whichever port you've chosen). - -And that's it. If Nginx is properly configured, you will see your Vapor app responding to requests on port `80`. Nginx proxies the requests and responses invisibly. - -## Install Nginx - -The first step is installing Nginx. One of the great parts of Nginx is the tremendous amount of community resources and documentation surrounding it. Because of this, we will not go into great detail here about installing Nginx as there is almost definitely a tutorial for your specific platform, OS, and provider. - -Tutorials: - -- [How To Install Nginx on Ubuntu 14.04 LTS](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts) -- [How To Install Nginx on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04) -- [How to Deploy Nginx on Heroku](https://blog.codeship.com/how-to-deploy-nginx-on-heroku/) -- [How To Run Nginx in a Docker Container on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-run-nginx-in-a-docker-container-on-ubuntu-14-04) - - -### APT - -Nginx can be installed through APT. - -```sh -sudo apt-get update -sudo apt-get install nginx -``` - -Check whether Nginx was installed correctly by visiting your server's IP address in a browser - -```sh -http://server_domain_name_or_IP -``` - -### Service - -The service can be started or stopped. - -```sh -sudo service nginx stop -sudo service nginx start -sudo service nginx restart -``` - -## Booting Vapor - -Nginx can be started an stopped with the `sudo service nginx ...` commands. You will need something similar to start and stop your Vapor server. - -There are many ways to do this, and they depend on which platform you are deploying to. Check out the [Supervisor](supervisor.md) instructions to add commands for starting and stopping your Vapor app. - -## Configure Proxy - -The configuration files for enabled sites can be found in `/etc/nginx/sites-enabled/`. - -Create a new file or copy the example template from `/etc/nginx/sites-available/` to get started. - -Here is an example configuration file for a Vapor project called `Hello` in the home directory. - -```sh -server { - server_name hello.com; - listen 80; - - root /home/vapor/Hello/Public/; - - location @proxy { - proxy_pass http://127.0.0.1:8080; - proxy_pass_header Server; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass_header Server; - proxy_connect_timeout 3s; - proxy_read_timeout 10s; - } -} -``` - -This configuration file assumes the `Hello` project binds to port `8080` when started in production mode. - -### Serving Files - -Nginx can also serve public files without asking your Vapor app. This can improve performance by freeing up the Vapor process for other tasks under heavy load. - -```sh -server { - ... - - # Serve all public/static files via nginx and then fallback to Vapor for the rest - try_files $uri @proxy; - - location @proxy { - ... - } -} -``` - -### TLS - -Adding TLS is relatively straightforward as long as the certificates have been properly generated. To generate TLS certificates for free, check out [Let's Encrypt](https://letsencrypt.org/getting-started/). - -```sh -server { - ... - - listen 443 ssl; - - ssl_certificate /etc/letsencrypt/live/hello.com/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/hello.com/privkey.pem; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; - ssl_session_timeout 1d; - ssl_session_cache shared:SSL:50m; - ssl_stapling on; - ssl_stapling_verify on; - add_header Strict-Transport-Security max-age=15768000; - - ... - - location @proxy { - ... - } -} -``` - -The configuration above are the relatively strict settings for TLS with Nginx. Some of the settings here are not required, but enhance security. diff --git a/2.0/docs/deploy/supervisor.md b/2.0/docs/deploy/supervisor.md deleted file mode 100644 index b343d555..00000000 --- a/2.0/docs/deploy/supervisor.md +++ /dev/null @@ -1,59 +0,0 @@ -# Supervisor - -[Supervisor](http://supervisord.org) is a process control system that makes it easy to start, stop, and restart your Vapor app. - -## Install - -```sh -sudo apt-get update -sudo apt-get install supervisor -``` - -## Configure - -Each Vapor app on your server should have its own configuration file. For an example `Hello` project, the configuration file would be located at `/etc/supervisor/conf.d/hello.conf` - -```sh -[program:hello] -command=/home/vapor/hello/.build/release/Run serve --env=production -directory=/home/vapor/hello/ -user=www-data -stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log -stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log -``` - -As specified in our configuration file the `Hello` project is located in the home folder for the user `vapor`. Make sure `directory` points to the root directory of your project where the `Config/` folder is. - -The `--env=production` flag will disable verbose logging and prioritize the `Config/production` sub folder of your configuration files. - -### Environment - -You can export variables to your Vapor app with supervisor. - -```sh -environment=PORT=8123 -``` - -Exported variables can be used in Vapor's configuration files with the `$` prefix. - -`Config/production/servers.json ` -```json -{ - "port": "$PORT" -} -``` - -The above config file will start a server named `my-server` on the port number exported by supervisor. This is a great way to control how Vapor starts from the supervisor config scripts. Feel free to name the server whatever you like. - -## Start - -You can now load and start your app. - -```sh -supervisorctl reread -supervisorctl add hello -supervisorctl start hello -``` - -!!! note - The `add` command may have already started your app. diff --git a/2.0/docs/fluent/database.md b/2.0/docs/fluent/database.md deleted file mode 100644 index 59bf11a7..00000000 --- a/2.0/docs/fluent/database.md +++ /dev/null @@ -1,241 +0,0 @@ -# Database - -A Fluent database is responsible for managing connections to your underlying data store and sending queries to the implementation-specific driver you have chosen. - -## Drivers - -By default, Fluent includes in-memory and SQLite drivers. There are several drivers available to add to your Vapor application. - -### Available - -| Type | Key | Package | Class | Official | -|------------|------------|------------------------------------------------------------------------------|-------------------------|----------| -| Memory | memory | [Fluent Provider](../fluent/package.md) | Fluent.MemoryDriver | Yes | -| SQlite | sqlite | [Fluent Provider](../fluent/package.md) | Fluent.SQLiteDriver | Yes | -| MySQL | mysql | [MySQLProvider](../mysql/package.md) | MySQLDriver.Driver | Yes | -| PostgreSQL | postgresql | [PostgreSQLProvider](https://github.com/vapor-community/postgresql-provider) | PostgreSQLDriver.Driver | No | -| MongoDB | N/A | [MongoProvider](https://github.com/vapor-community/mongo-provider) | N/A | No | - -Click on the provider package for more information about how to use it. - -You can search for a list of available [Vapor database providers](https://github.com/search?utf8=✓&q=topic%3Avapor-provider+topic%3Adatabase&type=Repositories) on GitHub. - -## Droplet - -You can access the database from the Droplet. - -```swift -drop.database // Database? -``` - -## Preparations - -Most databases, like SQL databases, require the schema for a model to be created before it is stored. -Adding a preparation to your model will allow you to prepare the database while your app boots. - -```swift -extension User: Preparation { - /// Prepares a table/collection in the database - /// for storing Users - static func prepare(_ database: Database) throws { - try database.create(self) { builder in - builder.id() - builder.string("name") - builder.int("age") - } - } - - /// Undoes what was done in `prepare` - static func revert(_ database: Database) throws { - try database.delete(self) - } -} -``` - -The above prepare statement results in SQL similar to the following: - -```sql -CREATE TABLE `users` (`id` INTEGER PRIMARY KEY NOT NULL, `name` TEXT NOT NULL, `age` INTEGER NOT NULL) -``` - -Once you have created you preparation, add it to the Config's prepratations array. - -```swift -config.preparations.append(User.self) -``` - -### Create - -The following methods are available on while creating and modifing the database. - -| Method | Type | -|-----------|--------------------| -| id | Primary Identifier | -| foreignId | Foreign Identifier | -| int | Integer | -| string | String | -| double | Double | -| bool | Boolean | -| bytes | Data | -| date | Date + Time | - -You can use any of these methods on a builder inside `.create()`. - -```swift -try database.create(self) { builder in - builder.double("latitude") - builder.double("longitude") -} -``` - -### Foreign Keys - -Foreign keys are automatically added with `.foreignId()`. To add a foreign key manually, use the -`.foreignKey` method. - -```swift -try database.create(self) { builder in - builder.foreignKey("user_id", references: "id", on: User.self) -} -``` - -To disable automatic foreign keys, set `autoForeignKeys` to false in the `Config/fluent.json` file. - -```json -{ - "autoForeignKeys": false -} - -``` - -### Modifier - -Existing schema can be modified using the `.modify()` property. All of the methods from `.create()` -are available here as well. - -```swift -try database.modify(self) { builder in - builder.string("name") - builder.delete("age") -} -``` - -### Migrations - -Other times, you may want to make some modifications to your data set while migrating to a new version or -just performing general cleanup. - -```swift -struct DeleteOldEntries: Preparation { - static func prepare(_ database: Database) throws { - try Log.makeQuery().filter(...).delete() - } - - ... -} -``` - -### Run - -Your preparations will run every time you run your application. You can run your preparations without booting -your server by calling: - - -```sh -vapor run prepare -``` - -### Revert - -Use the revert method to undo any work you did in the prepare method. - -```swift -extension User: Preparation { - ... - - - static func revert(_ database: Database) throws { - try database.delete(self) - } -} -``` - -You can run the reversions by calling: - -``` -vapor run prepare --revert -``` - -This will revert the latest batch of preparations. To revert the entire database, run the following: - -``` -vapor run prepare --revert --all -``` - -## Log - -Logging queries is a great way to find optimizations for your application and track down bugs. - -The easiest way to log queries is to enable logging in your `fluent.json` file. - -`Config/fluent.json` -```json -{ - ..., - "log": true, - ... -} -``` - -This will emit info-level logs for all database queries. - -### Custom - -You can also hook into the database's query logging callback to execute custom logic. - -```swift -drop.database?.log = { query in - print(query) -} -``` - -You can assign a closure to the `log` property on the database. Any time a query is run, the closure -will be called with a `QueryLog` object containing a string describing the statement and the time it ran. - -## Transactions - -Transactions allow you to group multiple queries into one single unit of work. If any one of the -queries experiences a problem, the entire transaction will be rolled back. - -```swift -drop.database?.transaction { conn in - try user.pets.makeQuery(conn).delete() - try user.makeQuery(conn).delete() -} -``` - -Drivers that do not support transactions will throw an error if this method is called. - -You can use the `.makeQuery(_: Executor)` method to create queries that will run on the -connection supplied to the closure. - -!!! warning - You must use the connection supplied to the closure for queries you want to include - in the transaction. - - -## Indexes - -An index is a copy of selected columns of data from a table that can be searched very efficiently. - -You can add them to your database by calling `.index()` - -```swift -try database.index("name", for: User.self) -``` - -You can delete them by calling `.deleteIndex()` - -```swift -try database.deleteIndex("name", for: User.self) -``` diff --git a/2.0/docs/fluent/getting-started.md b/2.0/docs/fluent/getting-started.md deleted file mode 100644 index 76f0caa4..00000000 --- a/2.0/docs/fluent/getting-started.md +++ /dev/null @@ -1,167 +0,0 @@ -# Getting Started with Fluent - -Fluent provides an easy, simple, and safe API for working with your persisted data. Each database table/collection is represented by a `Model` that can be used to interact with the data. Fluent supports common operations like creating, reading, updating, and deleting models. It also supports more advanced operations like joining, relating, and soft deleting. - -!!! note - Don't forget to add `import FluentProvider` (or your other database provider) to the top of your Swift files. - -Fluent ships with SQLite by default. You can use SQLite to quickly scaffold your application with the in-memory database it provides. This is enabled by default in Vapor's default template. To learn more about configuring your database, check out the available [drivers](#drivers). - -## Creating a Model - -Models are the Swift representations of the data in your database. As such, they are central to most of Fluent's APIs. - -Let's take a look at what a simple model looks like. - -```swift -final class Pet: Model { - var name: String - var age: Int - let storage = Storage() - - init(row: Row) throws { - name = try row.get("name") - age = try row.get("age") - } - - init(name: String, age: Int) { - self.name = name - self.age = age - } - - func makeRow() throws -> Row { - var row = Row() - try row.set("name", name) - try row.set("age", age) - return row - } -} -``` - -Here we are creating a simple class `Pet` with a name and an age. We will add a simple init method for creating new pets. - -### Storage - -The `storage` property is there to allow Fluent to store extra information on your model--things like the model's database id. - -### Row - -The `Row` struct represents a database row. Your models should be able to parse from and serialize to database rows. - -#### Parse - -Here's the code for parsing the Pet from the database. - -```swift -final class Pet: Model { - ... - - init(row: Row) throws { - name = try row.get("name") - age = try row.get("age") - } -} -``` - -#### Serialize - -Here's the code for serializing the Pet to the database. - -```swift -final class Pet: Model { - ... - - func makeRow() throws -> Row { - var row = Row() - try row.set("name", name) - try row.set("age", age) - return row - } -} -``` - -## Preparing the Database - -In order to use your model, you may need to prepare your database with an appropriate schema. - -### Preparation - -You can do this by conforming your model to `Preparation`. - -```swift -extension Pet: Preparation { - static func prepare(_ database: Database) throws { - try database.create(self) { pets in - pets.id() - pets.string("name") - pets.int("age") - } - } - - static func revert(_ database: Database) throws { - try database.delete(self) - } -} -``` - -Here we are creating a simple table that will look like this: - -| id | name | age | -|--------------------------|--------|-----| -| <database id type> | string | int | - -### Add to Droplet - -Now you can add your model to the config's preparations so the database is prepared when your application boots. - -```swift -import Vapor -import FluentProvider - -let config = try Config() -config.preparations.append(Pet.self) -let drop = try Droplet(config) - -... -``` - -## Using Models - -Now that we have created our model and prepared the database, we can use it to save and fetch data from the database. - -### Save - -To save a model, call `.save()`. A new identifier for the model will automatically be created. - -```swift -let dog = Pet(name: "Spud", age: 5) -try dog.save() -print(dog.id) // the newly saved pet's id -``` - -### Find - -You can fetch a model from the database using it's ID. - -```swift -guard let dog = try Pet.find(42) else { - throw Abort.notFound -} - -print(dog.name) // the name of the dog with id 42 -``` - -### Filter - -You can also search for models using filters. - -```swift -let dogs = try Pet.makeQuery().filter("age", .greaterThan, 2).all() -print(dogs) // all dogs older than 2 - -``` - -## Drivers - -Check out the [database](database.md) section for more information about different database drivers you can use with Fluent. - diff --git a/2.0/docs/fluent/model.md b/2.0/docs/fluent/model.md deleted file mode 100644 index 19f6cd07..00000000 --- a/2.0/docs/fluent/model.md +++ /dev/null @@ -1,365 +0,0 @@ -# Model - -Models are the Swift representations of the data in your database. As such, they are central to most of Fluent's APIs. - -This guide is an overview of the protocol requirements and methods associated with models. - -!!! seealso - Check out the [getting started](getting-started.md) guide for an introductory overview on using models. - -## CRUD - -Models have several basic methods for creating, reading, updating, and deleting. - -### Save - -Persists the entity into the data store and sets the `id` property. - -```swift -let pet = Pet(name: "Spud", age: 2) -try pet.save() -``` - -### Find - -Finds the model with the supplied identifier or returns `nil`. - -```swift -guard let pet = try Pets.find(42) else { - throw Abort.notFound -} -print(pet.name) -``` - -### Delete - -Deletes the entity from the data store if the entity has previously been fetched or saved. - -```swift -try pet.delete() -``` - -### All - -Returns all entities for this model. - -```swift -for pet in try Pets.all() { - print(pet.name) -} -``` - -### Count - -Returns a count of all entities for this model. - -```swift -let count = try Pets.count() -``` - -### Chunk - -Returns chunked arrays of a supplied size for all of the entities for this model. - -This is a great way to parse through all models of a large data set. - -```swift -try Pets.chunk(20) { pets in - // -} -``` - -### Query - -Creates a `Query` instance for this `Model`. - -```swift -let query = try Pet.makeQuery() -``` - -To learn more about crafting complex queries, see the [query](query.md) section. - -## Timestamps - -To add timestamps to your model, simply conform it to `Timestampable`. - -```swift -extension User: Timestampable { } -``` - -You can access the updated at and created at times on any model instance. - -```swift -user.updatedAt // Date? -user.createdAt // Date? -``` - -When filtering or sorting on the timestamp data, you can use the timestamp keys from the class. - -```swift -let newUsers = try User - .makeQuery() - .filter(User.createdAtKey, .greaterThan, ...) - .all() -``` - -You can also override the timestamp keys if you have custom needs. - -```swift -extension User: Timestampable { - static var updatedAtKey: String { return "custom_updated_at" } - static var createdAtKey: String { return "custom_created_at" } -} -``` - -### Migration - -`Timestampable` models will automatically have created at and updated at keys added during -[database create](database.md#Create) calls. - -Should you need to manually add `Timestampable` to an existing model, you can use the `date()` method -in a [migration](database.md#Migrations). - -```swift -database.modify(User.self) { builder in - builder.date(User.createdAtKey) - builder.date(User.updatedAtKey) -} -``` - -## Soft Delete - -Soft delete is a way of "deleting" a model from all fetch and update queries to Fluent but not actually deleting the model from the database. Soft deleted models can also be restored. - -To make your model soft deletable, simply conform it to `SoftDeletable`. - -```swift -extension User: SoftDeletable { } -``` - -Once your model is soft deletable, all calls to `delete()` will set the deleted at flag instead of actually -deleting the model. - -To restore a model, call `.restore()`. To actually delete a model from the database, call `.forceDelete()`. - -You can also override the soft delete key if you have custom needs. - -```swift -extension User: SoftDeletable { - static var deletedAtKey: String { return "custom_deleted_at" } -} -``` - -### Including Deleted - -When a model is soft deleted, it will not be affected by any queries made with the Fluent query builder. - -To include soft deleted models, for instance if you want to restore them, use the `.withSoftDeleted()` method -on the query builder. - -```swift -let allUsers = try User.makeQuery().withSoftDeleted().all() -``` - -### Lifecycle - -You can hook into the soft delete events of a model. - -```swift -extension User: SoftDeletable { - func willSoftDelete() throws { ... } - func didSoftDelete() { ... } - func willForceDelete() throws { ... } - func didForceDelete() { ... } - func willRestore() throws { ... } - func didRestore() { ... } -} -``` - -!!! note: - Throwing during a `will` hook will prevent the action from happening. - -### Migration - -`SoftDeletable` models will automatically have a deleted at key added during -[database create](database.md#create) calls. - -Should you need to manually add `SoftDeletable` to an existing model, you can use the `date()` method -in a [migration](database.md#migrations). - -```swift -database.modify(User.self) { builder in - builder.date(User.deletedAtKey, optional: true) -} -``` - -## Convenience - -### Assert Exists - -The identifier property of a model is optional since models may not have been saved yet. - -You can get the identifier or throw an error if the model has not been saved yet by calling `assertExists()`. - -```swift -let id = try pet.assertExists() -print(id) // not optional -``` - -## Life Cycle - -The following life-cycle methods can be implemented on your model to hook into internal operations. - -```swift -/// Called before the entity will be created. -/// Throwing will cancel the creation. -func willCreate() throws - -/// Called after the entity has been created. -func didCreate() - -/// Called before the entity will be updated. -/// Throwing will cancel the update. -func willUpdate() throws - -/// Called after the entity has been updated. -func didUpdate() - -/// Called before the entity will be deleted. -/// Throwing will cancel the deletion. -func willDelete() throws - -/// Called after the entity has been deleted. -func didDelete() -``` - -!!! note - Throwing in a `willFoo()` method will cancel the operation. - -Here's an example of implementing the `didDelete` method. - -```swift -final class Pet: Model { - ... - - func didDelete() { - print("Deleted \(name)") - } -} -``` - -## Entity - -Entity is the base Fluent protocol that Model conforms to. It is responsible for providing all information the -database or query may need when saving, fetching, or deleting your models. - -### Name - -The singular relational name of this model. Also used for internal storage. Example: Pet = "pet". - -This value should usually not be overriden. - -```swift -final class Pet: Model { - static let name = "pet" -} -``` - -### Entity - -The plural relational name of this model. Used as the collection or table name. - -Example: Pet = "pets". - -This value should be overriden if the table name for your model is non-standard. - -```swift -final class Pet: Model { - static let entity = "pets" -} -``` - -### ID Type - -The type of identifier used for both the local and foreign id keys. - -Example: uuid, integer, etc. - -This value should be overriden if a particular model in your database uses a different ID type. - -```swift -final class Pet: Model { - static let idType: IdentifierType = .uuid -} -``` - -This can also be overridden at the database level using config. - -`Config/fluent.json` - -```json -{ - "idType": "uuid" -} -``` - -Or programatically. - -```swift -drop.database?.idType = .uuid -``` - -### Key Naming Convention - -The naming convetion to use for foreign id keys, table names, etc. - -Example: snake_case vs. camelCase. - -This value should be overridden if a particular model in your database uses a different key naming convention. - -```swift -final class Pet: Model { - static let keyNamingConvention = .snake_case -} -``` - -This can also be overridden at the database level using config. - -`Config/fluent.json` - -```json -{ - "keyNamingConvention": "snake_case" -} -``` - -Or programatically. - -```swift -drop.database?.keyNamingConvention = .snake_case -``` - -### ID Key - -The name of the column that corresponds to this entity's identifying key. - -The default is 'database.driver.idKey', and then "id" - - -```swift -final class Pet: Model { - static let idKey = "id" -} -``` - -### Foreign ID Key - -The name of the column that points to this entity's id when referenced from other tables or collections. - -Example: "foo_id". - -```swift -final class Pet: Model { - static let foreignIdKey = "pet_id" -} -``` diff --git a/2.0/docs/fluent/package.md b/2.0/docs/fluent/package.md deleted file mode 100644 index 0fc120fa..00000000 --- a/2.0/docs/fluent/package.md +++ /dev/null @@ -1,50 +0,0 @@ -# Using Fluent - -This section outlines how to import the Fluent package both with or without a Vapor project. - -## With Vapor - -Fluent comes included with most Vapor templates. However, if you have created a project from scratch you will need to add the provider to your `Package.swift` file. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/fluent-provider.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -The Fluent provider package adds Fluent to your project and adds some additional, Vapor-specific conveniences like HTTP conformances. - -Using `import FluentProvider` will import both Fluent and Fluent's Vapor-specific APIs. - -## Without Vapor - -Fluent is a powerful, pure-Swift ORM that can be used with any Server-Side Swift framework. To include it in your package, add it to your `Package.swift` file. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/fluent.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Fluent` to access Fluent's APIs. - -!!! warning - `Model` is a Vapor + Fluent type, use `Entity` instead. - -## Drivers - -Fluent drivers allow Fluent models and queries to communicate with various database technologies like MySQL or Mongo. For a full list of drivers, check out the [`fluent-driver`](https://github.com/search?utf8=✓&q=topic%3Afluent-driver&type=Repositories) tag on GitHub. diff --git a/2.0/docs/fluent/query.md b/2.0/docs/fluent/query.md deleted file mode 100644 index 8bb73b6d..00000000 --- a/2.0/docs/fluent/query.md +++ /dev/null @@ -1,273 +0,0 @@ -# Query - -Fluent's query builder provides a simple interface for creating complex database queries. The `Query` class itself (raw queries excluded) is the sole method by which Fluent communicates with your database. - -## Make - -You can create a new query builder from any model class. - -```swift -let query = try Post.makeQuery() -``` - -You can also create queries from an instance. This is especially useful if you need to use a special -database connection (like for [transactions](database.md#Transactions)) to save or update a model. - -```swift -guard let post = try Post.find(42) else { ... } -post.content = "Updated" -let query = try post.makeQuery(conn).save() -``` - -## Fetch - -You have multiple options for fetching the results of a query. - -### All - -The simplest option, `.all()` returns all rows relevant to the query. - -``` -let users = try User.makeQuery().filter(...).all() -``` - -### First - -You can take only the first row as well with `.first()`. - -``` -let user = try User.makeQuery().filter(...).first() -``` - -Fluent will automatically limit the results to `1` to increase -the performance of the query. - -### Chunk - -If you want to fetch a large amount of models from the database, using `.chunk()` can help reduce the -amount of memory required for the query by fetching chunks of data at a time. - -``` -User.makeQuery().filter(...).chunk(32) { users in - print(users) -} -``` - -## Filter - -Filters allow you to choose exactly what subset of data you want to modify or fetch. There are three different -types of filters. - -### Compare - -Compare filters perform a comparison between a field on your model in the database and a supplied value. - -```swift -try query.filter("age", .greaterThanOrEquals, 21) -``` - -You can also use operators. - -```swift -try query.filter("age" >= 21) -``` - -| Case | Operator | Type | -|----------------------|----------|------------------------| -| .equals | == | Equals | -| .greaterThan | > | Greater Than | -| .lessThan | < | Less Than | -| .greaterThanOrEquals | >= | Greater Than Or Equals | -| .lessThanOrEquals | <= | Less Than Or Equals | -| .notEquals | != | Not Equals | -| .hasSuffix | | Has Suffix | -| .hasPrefix | | Has Prefix | -| .contains | | Contains | -| .custom(String) | | Custom | - -!!! tip - You can omit the comparison type for `.equals`, e.g., `query.filter("age", 23)` - - -### Subset - -You can also filter by fields being in a set of data. - -```swift -try query.filter("favoriteColor", in: ["pink", "blue"]) -``` - -Or the opposite. - - -```swift -try query.filter("favoriteColor", notIn: ["brown", "black"]) -``` - -### Group - -By default, all query filters are joined by AND logic. You can create groups of filters within -your query that are joined with AND or OR logic. - -```swift -try query.or { orGroup in - try orGroup.filter("age", .greaterThan, 75) - try orGroup.filter("age", .lessThan, 18) -} -``` - -This will result in SQL similar to the following: - - -```sql -SELECT * FROM `users` WHERE (`age` > 75 OR `age` < 18); -``` - -`.and()` is also available in case you need to switch back to joining filters with AND nested inside of an OR. - -#### Complex Example - -```swift -let users = try User - .makeQuery() - .filter("planetOfOrigin", .greaterThan, "Earth") - .or { orGroup in - orGroup.and { andGroup in - andGroup.filter("name", "Rick") - andGroup.filter("favoriteFood", "Beer") - } - orGroup.and { andGroup in - andGroup.filter("name", "Morty") - andGroup.filter("favoriteFood", "Eyeholes") - } - } - .all() -``` - -This will result in SQL similar to the following: - -```sql -SELECT * FROM `users` - WHERE `planetOfOrigin` = 'Earth' AND ( - (`name` = 'Rick' AND `favoriteFood` = 'Beer') - OR (`name` = 'Morty' AND `favoriteFood` = 'Eyeholes') - ) -``` - -!!! note - Keep in mind that the AND/OR logic for a group applies only to the filters added _within_ the group. All filters outside of a filter group will be joined by AND. - -### Raw - -Raw filters can be used to filter by values that should not be parameterized. - -```swift -try query.filter(raw: "date >= CURRENT_TIMESTAMP") -``` - -### NodeConvertible Conformance - -Filters can be converted to and from Node objects, which allows filters to be specified via JSON and other NodeRepresentable formats. This makes it very easy if you want to allow a consumer API to filter your entities. - -Example: -```json -{ - "entity":"MyApp.User", - "method":{ - "type":"group", - "relation":"and", - "filters":[ - { - "entity":"MyApp.User", - "method":{ - "type":"compare", - "comparison":"greaterThanOrEquals", - "field":"age", - "value":18 - } - }, - { - "entity":"MyApp.User", - "method":{ - "type":"compare", - "comparison":"equals", - "field":"gender", - "value":"male" - } - } - ] - } -} -``` - -!!! note - You must include the module name in the entity field. "MyModule.MyEntity" - -## Distinct - -To select only distinct models from the database, add `.distinct()` to your query. - -```swift -try query.distinct() -``` - -## Limit / Offset - -To limit or offset your query, use the `.limit()` method. - -```swift -try query.limit(20, offset: 5) -``` - -## Sort - -To sort the results of your query, use the `.sort()` method. - -```swift -try query.sort("age", .descending) -``` - -You can sort on multiple columns at once by chaining your `.sort()` calls. - -```swift -try query.sort("age", .descending).sort("shoe_size") -``` - -## Join - -You can join two model tables together, which is useful if you want to filter one model by a property of another. -For example, let's say you have a table of Employees which belong to Departments. You want to know which -Departments contain Employees who have completed ten years of service. - -First you use the `.join()` method on a Department query to join it with the Employee table. Next you chain a -`.filter()` on to the query. Bear in mind you need to explicitly pass the 'joined' model to the filter, otherwise -Fluent will try to filter on the 'base' model. - -```swift -let departments = try Department.makeQuery() - .join(Employee.self) - .filter(Employee.self, "years_of_service" >= 10) -``` - -Fluent will work out the relationship fields for you, but you can also specify them yourself with the `baseKey` -and `joinedKey` method parameters, where `baseKey` is the identifier field on the 'base' model (the Department) -and `joinedKey` is the foreign key field on the 'joined' model (the Employee) which relates back to the 'base' model. - -!!! tip - Fluent supports both inner and outer joins; use the invocation `.join(kind: .outer, MyModel.self)` - -## Raw - -Should you need to perform a query that the query builder does not support, you can use the raw query. - -```swift -try drop.database?.raw("SELECT @@version") -``` - -You can also use the database of a given model. - -```swift -User.database?.raw("SELECT * FROM `users`") -``` - -Besides providing a more expressive interface for querying your database, the query builder also takes measures to increase security by automatically sanitizing input. Because of this, try to use the query class wherever you can over performing raw queries. diff --git a/2.0/docs/fluent/relations.md b/2.0/docs/fluent/relations.md deleted file mode 100644 index 605b3dd7..00000000 --- a/2.0/docs/fluent/relations.md +++ /dev/null @@ -1,245 +0,0 @@ -# Relations - -Fluent relations allow you to relate your models in three different ways: - -| Type | Relations | -|--------------|-------------------| -| One to One | Parent / Child | -| One to Many | Parent / Children | -| Many to Many | Siblings | - - -## One to Many - -We'll start with one-to-many since it's the easiest type of relation to understand. - -Take the following database schema: - -`users` - -| id | name | -|-----------------|--------| -| <id type> | string | - -`pets` - -| id | name | user_id | -|-----------------|--------|-----------------| -| <id type> | string | <id type> | - -!!! seealso - Visit the [database preparations](database.md#preparations) guide for more information - on how to create schema. - -Here each pet has exactly one owner (a user) and each owner can have multiple pets. -This is a one-to-many relationship. One owner has many pets. - -!!! tip - Use the `builder.foreignId()` to create foreign ids like `user_id`. This will automatically - create foreign key constraints and follow pre-set key naming conventions. - -### Children - -To access the user's pets, we will use the `Children` relation. - -```swift -extension User { - var pets: Children { - return children() - } -} -``` - -Imagine the children relation as `Children` or `Children`. -Here we are relating _from_ the user type _to_ the pet type. - -We can now use this relation to get all of the user's pets. - -```swift -let pets = try user.pets.all() // [Pet] -``` - -This will create SQL similar to: - -```sql -SELECT * FROM `pets` WHERE `user_id` = '...'; -``` - -Relations work similarly to [queries](query.md). - -```swift -let pet = try user.pets.filter("name", "Spud").first() -``` - -### Parent - -To access a pet's owner from the pet, we will use the `Parent` relation. - -```swift -extension Pet { - let userId: Identifier - - ... - - var owner: Parent { - return parent(id: userId) - } -} -``` - -Imagine the parent relation as `Parent` or `Parent`. -Here we are relating _from_ the pet type _to_ the parent type. - -!!! note - Notice the `Parent` relation requires an identifier to be passed in. - Make sure to load this identifier in your model's `init(row:)` method. - -We can now use this relation to get the pet's owner. - -```swift -let owner = try pet.owner.get() // User? -``` - -#### Migration - -Adding a parent identifier to the child table can be done using the `.parent()` method on -the schema builder. - -```swift -try database.create(Pet.self) { builder in - ... - builder.parent(User.self) -} -``` - -## One to One - -One-to-one relations work exactly the same as one-to-many relations. You can use the -code from the previous example and simply call `.first()` and all calls from the parent type. - -However, you can add a convenience for doing this. Let's assume we wanted to change the previous -example from one-to-many to one-to-one. - -```swift -extension User { - func pet() throws -> Pet? { - return try children().first() - } -} -``` - -## Many to Many - -Many to many relations require a table in between to store which model is related to which. -This table is called a pivot table. - -You can use any entity you want as a pivot, but Fluent provides a default one called `Pivot`. - -Take the following schema. - -`pets` - -| id | name | -|-----------------|--------| -| <id type> | string | - -`pet_toy` - -| id | pet_id | toy_id | -|-----------------|-----------------|-----------------| -| <id type> | <id type> | <id type> | - - -`toys` - -| id | name | -|-----------------|--------| -| <id type> | string | - -Here each pet can own many toys and each toy can belong to many pets. This is a many-to-many relationship. - -### Siblings - -To represent this many-to-many relationship, we will use the `Siblings` relation. - -```swift -extension Pet { - var toys: Siblings> { - return siblings() - } -} -``` - -Imagine the siblings relations as `Siblings`. -Here we are relating _from_ the pet type _to_ the toy type _through_ the pet/toy pivot. - -!!! note - The generic syntax might look a little intimidating at first, but it allows for a very powerful API. - -With this relation added on pets, we can fetch a pet's toys. - -```swift -let toys = pet.toys.all() // [Toy] -``` - -The siblings relation works similarly to [queries](query.md) and parent/children relations. - -#### Migration - -If you are using a `Pivot` type, you can simply add it to your Droplet's preparation array. - -```swift -drop.preparations.append(Pivot.self) -``` - -If you are using a `Pivot` for your "through" model, it will also have methods for adding and removing models from the relation. - -#### Add - -To add a new model to the relation, use the `.add()` method. - -```swift -try pet.toys.add(toy) -``` - -!!! note - The newly created pivot will be returned. - -#### Remove - -To remove a model from being related, use the `.remove()` method. - -```swift -try pet.toys.remove(toy) -``` - -#### Is Attached - -To check if a model is related, use the `.isAttached()` method. - -```swift -if try pet.toys.isAttached(to: toy) { - // it is attached -} -``` - -#### Custom Through - -You can use any entity type as the "through" entity in your siblings relation. - -```swift -extension User { - var posts: Siblings { - return siblings() - } -} -``` - -In the above example we are pivoting on the comments entity to retreive all posts the user -has commented on. - -As long as the "through" entity has a `user_id` and `post_id`, the siblings relation will work. - -!!! note - If the `Comment `entity does not conform to `PivotProtocol`, the - `add`, `remove`, and `isAttached` methods will not be available. diff --git a/2.0/docs/getting-started/hello-world.md b/2.0/docs/getting-started/hello-world.md deleted file mode 100644 index cd56a9e9..00000000 --- a/2.0/docs/getting-started/hello-world.md +++ /dev/null @@ -1,169 +0,0 @@ -# Hello, World - -This section assumes you have installed Swift 3.1 and the Vapor Toolbox and have verified they are working. - -!!! tip - Note: If you don't want to use the Toolbox, follow the [manual guide](manual.md). - -## New Project - -Let's start by creating a new project called "Hello, World". - -```sh -vapor new Hello --template=api -``` - -Vapor's folder structure will probably look familiar to you if you have worked with other web frameworks. - -``` -Hello -├── Config -│   ├── app.json -│   ├── crypto.json -│   ├── droplet.json -│   ├── fluent.json -│   └── server.json -├── Package.pins -├── Package.swift -├── Public -├── README.md -├── Sources -│   ├── App -│   │   ├── Config+Setup.swift -│   │   ├── Controllers -│   │   │   └── PostController.swift -│   │   ├── Droplet+Setup.swift -│   │   ├── Models -│   │   │   └── Post.swift -│   │   └── Routes -│   │   └── Routes.swift -│   └── Run -│   └── main.swift -├── Tests -│   ├── AppTests -│   │   ├── PostControllerTests.swift -│   │   ├── RouteTests.swift -│   │   └── Utilities.swift -│   └── LinuxMain.swift -├── circle.yml -└── license -``` - -For our Hello, World project, we will be focusing on the `Routes.swift` file. - -``` -Hello -└── Sources - └── App - └── Routes.swift -``` -!!! tip - The `vapor new` command creates a new project with examples and comments about how to use the framework. You can delete these if you want. - -## Code - -### Droplet - -Look for the following line in the `Routes.swift` file. - -```swift -func setupRoutes() throws -``` - -This method is where all the routes for our application will be added. - -### Routing - -In the scope of the `setupRoutes` method, look for the following statement. - -```swift -get("plaintext") { req in - return "Hello, world!" -} -``` - -This creates a new route that will match all `GET` requests to `/plaintext`. - -All route closures are passed an instance of [Request](../http/request.md) that contains information such as the URI requested and data sent. - -This route simply returns a string, but anything that is [ResponseRepresentable](../http/response-representable.md) can be returned. Learn more in the [Routing](../routing/overview.md) section of the guide. - -!!! tip - Xcode autocomplete may add extraneous type information to your closure's input arguments. This can be deleted to keep the code clean. If you'd like to keep the type information add `import HTTP` to the top of the file. - -## Compile & Run - -### Building - -A big part of what makes Vapor so great is Swift's state of the art compiler. Let's fire it up. Make sure you are in the root directory of the project and run the following command. - -```swift -vapor build -``` - -!!! note - `vapor build` runs `swift build` in the background. - -The Swift Package Manager will first start by downloading the appropriate dependencies from git. It will then compile and link these dependencies together. - -When the process has completed, you will see `Building Project [Done]` - -!!! tip - If you see a message like `unable to execute command: Killed`, you need to increase your swap space. This can happen if you are running on a machine with limited memory. - -#### Release - -Building your application in release mode takes longer, but increases performance. - -```sh -vapor build --release -``` - -### Serving - -Boot up the server by running the following command. - -```sh -vapor run serve -``` - -You should see a message `Server starting...`. - -You can now visit `localhost:8080/plaintext` in your browser or run - -```sh -curl localhost:8080/plaintext -``` - -!!! note - Certain port numbers require super user access to bind. Simply run `sudo vapor run` to allow access. If you decide to run on a port besides `80`, make sure to direct your browser accordingly. - -### Hello, World - -You should see the following output in your browser window. - -``` -Hello, world! -``` - -!!! success - Like Vapor so far? Click the button below and star the repo to help spread the word! - - - - -#### Production - -Serving your application in the production environment increases its security and performance. - -```sh -vapor run serve --env=production -``` - -Some debug messages will be silenced while in the production environment, so make sure to check your logs for errors. - -!!! warning - If you compiled your application with `--release`, make sure to add that flag to the `vapor run` command as well. e.g., `vapor run serve --env=production --release`. - -For more information on deploying your code, check out the [deploy section](../deploy/nginx.md). - diff --git a/2.0/docs/getting-started/install-on-macos.md b/2.0/docs/getting-started/install-on-macos.md deleted file mode 100644 index 89f9db6f..00000000 --- a/2.0/docs/getting-started/install-on-macos.md +++ /dev/null @@ -1,73 +0,0 @@ -# Install on macOS - -To use Vapor on macOS, you just need to have Xcode 8 or later installed. - -## Install Xcode - -Install [Xcode 9](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) from the Mac App Store. - -[![Xcode 9](https://cloud.githubusercontent.com/assets/1342803/18537674/2ddd8e9c-7ad5-11e6-9bc2-7155d57d20ec.png)](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) - -(Xcode 8 is the minimum required to use Vapor 2.0 on macOS. If you have an Apple Developer membership you can download older versions of Xcode from Apple's developer [downloads page](https://developer.apple.com/download/more/)) - -### Open Xcode - -After Xcode has been downloaded, you must open it to finish the installation. This may take a while. - -## Verify Swift Installation - -Double check the installation was successful by opening Terminal and running: - -```sh -eval "$(curl -sL check.vapor.sh)" -``` - -## Install Vapor - -Now that you have Swift 4 (or Swift 3.1 if you installed Xcode 8), let's install the Vapor toolbox. - -The toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects. - -### Install Homebrew - -If you don't already have Homebrew installed, install it! It's incredibly useful for installing software dependencies like OpenSSL, MySQL, Postgres, Redis, SQLite, and more. - -```sh -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -``` - -For more information on installing Homebrew, visit [brew.sh](https://brew.sh). - -### Add Homebrew Tap - -Vapor's Homebrew tap will give your Homebrew installation access to all of Vapor's macOS packages. - -```sh -brew tap vapor/homebrew-tap -brew update -``` - -### Install - -Now that you've added Vapor's tap, you can install Vapor's toolbox and dependencies. - -```sh -brew install vapor -``` - -### Upgrade - -If you've previously installed Vapor upgrades to Homebrew and Vapor may be required to work with the latest versions of macOS, Swift, or the instructions in this guide. - -```sh -brew update -brew upgrade vapor -``` - -## Next - -Learn more about the Vapor toolbox CLI in the [Toolbox section](toolbox.md) of the Getting Started section. - -## Swift.org - -Check out [Swift.org](https://swift.org)'s extensive guides if you need more detailed instructions for installing Swift. diff --git a/2.0/docs/getting-started/install-on-ubuntu.md b/2.0/docs/getting-started/install-on-ubuntu.md deleted file mode 100644 index b74f89d3..00000000 --- a/2.0/docs/getting-started/install-on-ubuntu.md +++ /dev/null @@ -1,68 +0,0 @@ -# Install on Ubuntu - -Installing Vapor on Ubuntu only takes a couple of minutes. - -## Supported - -Vapor supports the same versions of Ubuntu that Swift supports. - -| Version | Codename | -|---------|--------------| -| 16.10 | Yakkety Yak | -| 16.04 | Xenial Xerus | -| 14.04 | Trusty Tahr | - -## APT Repo - -Add Vapor's APT repo to get access to all of Vapor's system packages. - -### Quick Script - -Easily add Vapor's APT repo with this handy script. - -```sh -eval "$(curl -sL https://apt.vapor.sh)" -``` - -!!! note - This command requires `curl` which can be installed using `sudo apt-get install curl` - -### Dockerfile -When configuring Ubuntu from a Dockerfile, adding the APT repo can be done via this command: -```sh - RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)" -``` - -### Manual - -Or add the repo manually. - -```sh -wget -q https://repo.vapor.codes/apt/keyring.gpg -O- | sudo apt-key add - -echo "deb https://repo.vapor.codes/apt $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/vapor.list -sudo apt-get update -``` - -## Install Vapor - -Now that you have added Vapor's APT repo, you can install the required dependencies. - -```sh -sudo apt-get install swift vapor -``` - -## Verify Installation - -Double check the installation was successful by running: - -```sh -eval "$(curl -sL check.vapor.sh)" -``` - -## Next - -Learn more about the Vapor toolbox CLI in the [Toolbox section](toolbox.md) of the Getting Started section. - -## Swift.org - -Check out [Swift.org](https://swift.org)'s guide to [using downloads](https://swift.org/download/#using-downloads) if you need more detailed instructions for installing Swift 3.1. diff --git a/2.0/docs/getting-started/manual.md b/2.0/docs/getting-started/manual.md deleted file mode 100644 index d4472ee8..00000000 --- a/2.0/docs/getting-started/manual.md +++ /dev/null @@ -1,98 +0,0 @@ -# Manual Quickstart - -Learn how to create a Vapor project _without_ the Toolbox using just Swift 3.1 and the Swift Package Manager (SPM). - -This document assumes that you have Swift 3.1 installed, if not please refer to [Swift.org](https://swift.org/getting-started/#installing-swift) before you can continue. - -!!! tip - If you'd prefer to use the Toolbox, follow the toolbox guide [here](hello-world.md). - -## Make new project using SwiftPM - -Open your terminal - -!!! note - For our example, we'll be using the Desktop folder. - -```bash -cd ~/Desktop -mkdir Hello -cd Hello -swift package init --type executable -``` - -Your folder structure should look like this: - -``` -├── Package.swift -├── Sources -│   └── main.swift -└── Tests -``` - -### Edit `Package.swift` - -Open your `Package.swift` file: - -```bash -open Package.swift -``` - -And add Vapor as a dependency. Here's how your `Package.swift` should look like. - -```swift -// swift-tools-version:3.1 - -import PackageDescription - -let package = Package( - name: "Hello", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2) - ] -) -``` - -!!! warning - We try to keep this document up to date, however, you can view latest releases [here](https://github.com/vapor/vapor/releases). - -### Edit `main.swift` - -A simple hello world: - -```swift -import Vapor - -let drop = try Droplet() - -drop.get("hello") { req in - return "Hello Vapor" -} - -try drop.run() -``` - -### Compile & Run (Development) - -The first `build` command can take a while to fetch dependencies. - -```bash -swift build -.build/debug/Hello serve -``` - -!!! warning - If different, replace `Hello` above with the name of your executable (as defined in `Package.swift`). - -### Production - -Compiling in Swift's release mode and setting Vapor's environment to production will make your app more secure and performant. - -```sh -swift build --configuration release -.build/release/Hello serve --env=production -``` - -### View - -Go to your favorite browser and visit `http://localhost:8080/hello` diff --git a/2.0/docs/getting-started/toolbox.md b/2.0/docs/getting-started/toolbox.md deleted file mode 100644 index e0e9c788..00000000 --- a/2.0/docs/getting-started/toolbox.md +++ /dev/null @@ -1,72 +0,0 @@ -# Install Toolbox - -Vapor's command line interface provides shortcuts and assistance for common tasks. - -Vapor Toolbox - -!!! tip - If you do not want to use the Toolbox or templates, checkout the [Manual](manual.md) quickstart. - - -## Help - -Help prints useful information about available commands and flags. You can also run the `--help` option on any Toolbox command. - -```sh -vapor --help -``` - -### Application Commands - -The `vapor run` command is a special toolbox command that forwards to your Vapor application. - -Once you've built your application with `vapor build` you can use `vapor run serve` to boot your application, or `vapor run help` to view all available application-level commands. This includes custom commands you may have added to your application. - -!!! warning - Using `vapor run --help` will provide information about the `run` command itself and will not forward to your Vapor application. - -## Updating - -The toolbox should be updated by the package manager it was installed with. - -### Homebrew - -```sh -brew upgrade vapor -``` - -### APT - -``` -sudo apt-get update -sudo apt-get install vapor -``` - -## Templates - -The toolbox can create a project from the Vapor basic-template or any other git repo. - -```sh -vapor new [--template] -``` - -| Name | Flag | Description | -|------|----------------|-----------------------------------| -| API | --template=api | JSON API with Fluent database. | -| Web | --template=web | HTML website with Leaf templates. | - -View a list of all [templates](https://github.com/search?utf8=✓&q=topic%3Avapor+topic%3Atemplate&type=Repositories) on GitHub. - -!!! note - If you do not specify a template option, the API template will be used. - This may change in the future. - -### Options - -The toolbox will build an absolute URL based on what you pass as the template option. - -- `--template=web` clones `http://github.com/vapor/web-template` -- `--template=user/repo` clones `http://github.com/user/repo`. -- `--template=http://example.com/repo-path` clones the full url given. -- `--branch=foo` can be used to specify a branch besides `master`. - diff --git a/2.0/docs/getting-started/xcode.md b/2.0/docs/getting-started/xcode.md deleted file mode 100644 index c26dad71..00000000 --- a/2.0/docs/getting-started/xcode.md +++ /dev/null @@ -1,38 +0,0 @@ -# Xcode - - -If you're on a Mac, you can develop your Vapor project using Xcode. -You can build, run, and stop your server from within Xcode, as well as use breakpoints to debug your code. - -screen shot 2017-05-15 at 7 14 48 pm - -To use Xcode, you will first need to generate a `*.xcodeproj` file. - -## Generate Project - -### Vapor Toolbox - -To generate a new Xcode project for a project, use: - -```sh -vapor xcode -``` - -!!! tip - If you'd like to automatically open the Xcode project, use `vapor xcode -y` - -### Select 'Run' - -Make sure after generating your Xcode project that you properly select the executable if you're trying to run your application. - -select 'run' from dropdown - -### Manual - -To generate a new Xcode project manually. - -```sh -swift package generate-xcodeproj -``` - -Open the project and continue normally. diff --git a/2.0/docs/http/body.md b/2.0/docs/http/body.md deleted file mode 100644 index a4ce792c..00000000 --- a/2.0/docs/http/body.md +++ /dev/null @@ -1,135 +0,0 @@ -# Body - -The `HTTP.Body` represents the payload of an `HTTP.Message`, and is used to pass the underlying data. Some examples of this in practice would be `JSON`, `HTML` text, or the bytes of an image. Let's look at the implementation: - -```swift -public enum Body { - case data(Bytes) - case chunked((ChunkStream) throws -> Void) -} -``` - -## Data Case - -The `data` case is by far the most common use for a `Body` in an `HTTP.Message`. It is simply an array of bytes. The serialization protocol or type associated with these bytes is usually defined by the `Content-Type` header. Let's look at some examples. - -### Application/JSON - -If our `Content-Type` header contains `application/json`, then the underlying bytes represent serialized JSON. - -```swift -if let contentType = req.headers["Content-Type"], contentType.contains("application/json"), let bytes = req.body.bytes { - let json = try JSON(bytes: bytes) - print("Got JSON: \(json)") -} -``` - -### Image/PNG - -If our `Content-Type` contains `image/png`, then the underlying bytes represent an encoded png. - -```swift -if let contentType = req.headers["Content-Type"], contentType.contains("image/png"), let bytes = req.body.bytes { - try database.save(image: bytes) -} -``` - -## Chunked Case - -The `chunked` case only applies to outgoing `HTTP.Message`s in Vapor. It is traditionally a responder's role to collect an entire chunked encoding before passing it on. We can use this to send a body asynchronously. - -```swift -let body: Body = Body.chunked(sender) -return Response(status: .ok, body: body) -``` - -We can implement this manually, or use Vapor's built in convenience initializer for chunked bodies: - -```swift -return Response(status: .ok) { chunker in - for name in ["joe", "pam", "cheryl"] { - sleep(1) - try chunker.send(name) - } - - try chunker.close() -} -``` - -> Make sure to call `close()` before the chunker leaves scope. - -## BodyRepresentable - -In addition to the concrete `Body` type, as is common in Vapor, we also have wide support for `BodyRepresentable`. This means objects that we're commonly converting to `Body` type can be used interchangeably. For example: - -```swift -return Response(body: "Hello, World!") -``` - -In the above example, string is converted to bytes and added to the body. - -> In practice, it is better to use `return "Hello, World!"`. Vapor will automatically be able to set the `Content-Type` to appropriate values. - -Let's look at how it's implemented: - -```swift -public protocol BodyRepresentable { - func makeBody() -> Body -} -``` - -### Custom - -We can conform our own types to this as well where applicable. Let's pretend we have a custom data type, `.vpr`. Let's conform our `VPR` file type model: - -```swift -extension VPRFile: HTTP.BodyRepresentable { - func makeBody() -> Body { - // collect bytes - return .data(bytes) - } -} -``` - -> You may have noticed above, that the protocol throws, but our implementation does not. This is completely valid in Swift and will allow you to not throw if you're ever calling the function manually. - -Now we're able to include our `VPR` file directly in our `Responses`. - -```swift -drop.get("files", ":file-name") { request in - let filename = try request.parameters.extract("file-name") as String - let file = VPRFileManager.fetch(filename) - return Response(status: .ok, headers: ["Content-Type": "file/vpr"], body: file) -} -``` - -In practice, if we're repeating this often, we'll probably conform `VPRFile` directly to `ResponseRepresentable` - -```swift -extension VPRFile: HTTP.ResponseRepresentable { - func makeResponse() -> Response { - return Response( - status: .ok, - headers: ["Content-Type": "file/vpr"], - body: file - ) - } -} -``` - -Here's our above example now: - -```swift -drop.get("files", ":file-name") { request in - let filename = try request.parameters.extract("file-name") as String - return VPRFileManager.fetch(filename) -} -``` - -We could also use type-safe routing to make this even more concise: - -```swift -drop.get("files", String.self) { request, filename in - return VPRFileManager.fetch(filename) -} -``` diff --git a/2.0/docs/http/client.md b/2.0/docs/http/client.md deleted file mode 100644 index c67c338c..00000000 --- a/2.0/docs/http/client.md +++ /dev/null @@ -1,94 +0,0 @@ -# Client - -The client provided by `HTTP` is used to make outgoing requests to remote servers. Let's look at a simple outgoing request. - -## QuickStart - -Let's jump right in to make a simple HTTP Request. Here's a basic `GET` request using your Vapor `Droplet`. - -```swift -let query = "..." -let res = try drop.client.get("https://api.spotify.com/v1/search?type=artist&q=\(query)") -print(res) -``` - -### Clean Up - -The url above can be a little tricky to read, so let's use the query parameter to clean it up a little bit: - -```swift -let res = try drop.client.get("https://api.spotify.com/v1/search", query: [ - "type": "artist", - "q": query -]) -``` - -### Continued - -In addition to `GET` requests, Vapor's client provides support for most common HTTP functions. `GET`, `POST`, `PUT`, `PATCH`, `DELETE` - -### Headers - -You can also add additional headers to the request. - -```swift -try drop.client.get("http://some-endpoint/json", headers: [ - "API-Key": "vapor123" -]) -``` - -### Custom Request - -You can ask the client to respond to any `Request` that you create. -This is useful if you need to add JSON or FormURLEncoded data to the request. - -```swift -let req = Request(method: .post, uri: "http://some-endpoint") -req.formURLEncoded = Node(node: [ - "email": "mymail@vapor.codes" -]) - -try drop.client.respond(to: req) -``` - -## Re-usable Connection - -Up to this point, we've been using `drop.client` which is a `ClientFactory`. This creates a new client and TCP connection for each request. - -For more better performance, you can create an re-use a single client. - -```swift -let pokemonClient = try drop.client.makeClient( - scheme: "http", - host: "pokeapi.co", - securityLayer: .none -) - -for i in 0...1 { - let response = try pokemonClient.get("/api/v2/pokemon/", query: [ - "limit": 20, - "offset": i - ]) - print("response: \(response)") -} -``` - -!!! note - Clients created using `.makeClient` can not connect to a different server after initialization. (Proxy servers are an exception) - -## Proxy - -The `drop.client` can be configured to use a proxy by default. - -`Config/client.json` -```json -{ - "proxy": { - "hostname": "google.com", - "port": 80, - "securityLayer": "none" - } -} -``` - -For the above example, all requests sent to `drop.client.get(...)` would be proxied through google.com. diff --git a/2.0/docs/http/cors.md b/2.0/docs/http/cors.md deleted file mode 100644 index 09aa0e5a..00000000 --- a/2.0/docs/http/cors.md +++ /dev/null @@ -1,98 +0,0 @@ -# CORS - -Vapor by default provides a middleware for implementing proper support for Cross-Origin Resource Sharing (CORS) named `CORSMiddleware`. - -"Cross-Origin Resource Sharing (CORS) is a specification that enables truly open access across domain-boundaries. If you serve public content, please consider using CORS to open it up for universal JavaScript / browser access." - [http://enable-cors.org/](http://enable-cors.org/) - -To learn more about middlewares, please visit the Middleware section of the documentation [here](https://vapor.github.io/documentation/guide/middleware.html). - -![](https://upload.wikimedia.org/wikipedia/commons/c/ca/Flowchart_showing_Simple_and_Preflight_XHR.svg) -*Image Author: [Wikipedia](https://commons.wikimedia.org/wiki/File:Flowchart_showing_Simple_and_Preflight_XHR.svg)* - -## Basic - -First of all, add the CORS middleware into your droplet middlewares array. - -`Config/droplet.json` -```json -{ - ..., - "middleware": [ - ..., - "cors", - ..., - ], - ..., -} -``` - -Next time you boot your application, you will be prompted to add a `Config/cors.json` file. - - -`Config/cors.json` -```json -{ - "allowedOrigin": "*", - "allowedMethods": ["GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH"], - "allowedHeaders": [ - "Accept", - "Authorization", - "Content-Type", - "Origin", - "X-Requested-With" - ] -} -``` - - -> Note: Make sure you insert CORS middleware before any other throwing middlewares, like the AbortMiddleware or similar. Otherwise the proper headers might not be added to the response. - -`CORSMiddleware` has a default configuration which should suit most users, with values as follows: - -- **Allowed Origin** - - Value of origin header in the request. -- **Allowed Methods** - - `GET`, `POST`, `PUT`, `OPTIONS`, `DELETE`, `PATCH` -- **Allowed Headers** - - `Accept`, `Authorization`, `Content-Type`, `Origin`, `X-Requested-With` - -## Advanced - -All settings and presets can be customized by advanced users. There's two ways of doing this, either you programatically create and configure a `CORSConfiguration` object or you can put your configuration into a Vapor's JSON config file. - -See below for how to set up both and what are the options. - -### Configuration - -The `CORSConfiguration` struct is used to configure the `CORSMiddleware`. You can instanitate one like this: - -```swift -let config = try Config() -config.addConfigurable(middleware: { config in - return CORSConfiguration( - allowedOrigin: .custom("https://vapor.codes"), - allowedMethods: [.get, .post, .options], - allowedHeaders: ["Accept", "Authorization"], - allowCredentials: false, - cacheExpiration: 600, - exposedHeaders: ["Cache-Control", "Content-Language"] - ) -}, name: "custom-cors") -``` - -Then set the `custom-cors` in your Droplet's middleware array. - -`Config/droplet.json` -```json -{ - ..., - "middleware": [ - ..., - "custom-cors", - ..., - ], - ..., -} -``` - -> Note: Please consult the documentation in the source code of the `CORSConfiguration` for more information about available values for the settings. diff --git a/2.0/docs/http/middleware.md b/2.0/docs/http/middleware.md deleted file mode 100644 index 93e02eab..00000000 --- a/2.0/docs/http/middleware.md +++ /dev/null @@ -1,301 +0,0 @@ -# Middleware - -Middleware is an essential part of any modern web framework. It allows you to modify requests and responses as they pass between the client and your server. - -You can imagine middleware as a chain of logic connecting your server to the client requesting your web app. - -## Version Middleware - -As an example, let's create a middleware that will add the version of our API to each response. The middleware would look something like this: - -```swift -import HTTP - -final class VersionMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - let response = try next.respond(to: request) - - response.headers["Version"] = "API v1.0" - - return response - } -} -``` - -We then supply this middleware to our `Droplet`. - -```swift -import Vapor - -let config = try Config() - -config.addConfigurable(middleware: VersionMiddleware(), name: "version") - -let drop = try Droplet(config) -``` - -!!! tip - You can now dynamically enable and disable this middleware from your configuration files. - Simply add `"version"` to the `"middleware"` array in your `droplet.json` file. - See the [configuration](#configuration) section for more information. - -You can imagine our `VersionMiddleware` sitting in the middle of a chain that connects the client and our server. Every request and response that hits our server must go through this chain of middleware. - -![Middleware](https://cloud.githubusercontent.com/assets/1342803/17382676/0b51d6d6-59a0-11e6-9cbb-7585b9ab9803.png) - - -### Breakdown - -Let's break down the middleware line by line. - -```swift -let response = try next.respond(to: request) -``` - -Since the `VersionMiddleware` in this example is not interested in modifying the request, we immediately ask the next middleware in the chain to respond to the request. This goes all the way down the chain to the `Droplet` and comes back with the response that should be sent to the client. - -```swift -response.headers["Version"] = "API v1.0" -``` - -We then _modify_ the response to contain a Version header. - -```swift -return response -``` - -The response is returned and will chain back up any remaining middleware and back to the client. - -## Request - -The middleware can also modify or interact with the request. - -```swift -func respond(to request: Request, chainingTo next: Responder) throws -> Response { - guard request.cookies["token"] == "secret" else { - throw Abort(.badRequest) - } - - return try next.respond(to: request) -} -``` - -This middleware will require that the request has a cookie named `token` that equals `secret` or else the request will be aborted. - -## Errors - -Middleware is the perfect place to catch errors thrown from anywhere in your application. When you let the middleware catch errors, you can remove a lot of duplicated logic from your route closures. Take a look at the following example: - -```swift -enum FooError: Error { - case fooServiceUnavailable -} -``` - -Say there is a custom error that either you defined or one of the APIs you are using `throws`. This error must be caught when thrown, or else it will end up as an internal server error (500) which may be unexpected to a user. The most obvious solution is to catch the error in the route closure. - -```swift -app.get("foo") { request in - let foo: Foo - do { - foo = try getFooFromService() - } catch { - throw Abort(.badRequest) - } - - // continue with Foo object -} -``` - -This solution works, but it would get repetitive if multiple routes need to handle the error. Luckily, this error could be caught in a middleware instead. - -```swift -final class FooErrorMiddleware: Middleware { - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - do { - return try next.respond(to: request) - } catch FooError.fooServiceUnavailable { - throw Abort( - .badRequest, - reason: "Sorry, we were unable to query the Foo service." - ) - } - } -} -``` - -We just need to add this middleware to our droplet's config. - -```swift -config.addConfigurable(middleware: FooErrorMiddleware(), name: "foo-error") -``` - -!!! tip - Don't forget to enable the middleware in your `droplet.json` file. - -Now our route closures look a lot better and we don't have to worry about code duplication. - -```swift -app.get("foo") { request in - let foo = try getFooFromService() - - // continue with Foo object -} -``` - -## Route Groups - -For more granularity, Middleware can be applied to specific route groups. - -```swift -let authed = drop.grouped(AuthMiddleware()) -authed.get("secure") { req in - return Secrets.all().makeJSON() -} -``` - -Anything added to the `authed` group must pass through `AuthMiddleware`. Because of this, we can assume all traffic to `/secure` has been authorized. Learn more in [Routing](../routing/group). - -## Configuration - -You can use the [configuration](../configs/config.md) files to enabled or disable middleware dynamically. This is especially useful if you have middleware that should, for example, run only in production. - -Appending configurable middleware looks like the following: - -```swift -let config = try Config() - -config.addConfigurable(middleware: myMiddleware, name: "my-middleware") - -let drop = Droplet(config) -``` - -Then, in the `Config/droplet.json` file, add `my-middleware` to the `middleware` array. - -```json -{ - ... - "middleware": { - ... - "my-middleware", - ... - }, - ... -} -``` - -If the name of the added middleware appears in the middleware array it will be added to the server's middleware when the application boots. - -The ordering of middleware is respected. - -## Manual - -You can also hardcode your middleware if you don't want to use configuration files. - -```swift -import Vapor - -let versionMiddleware = VersionMiddleware() -let drop = try Droplet(middleware: [versionMiddleware]) -``` - -## Advanced - -### Extensions - -Middleware pairs great with request/response extensions and storage. This example shows you how to dynamically return either HTML or JSON responses for a Model depending on the type of client. - -#### Middleware - -```swift -final class PokemonMiddleware: Middleware { - let view: ViewProtocol - init(_ view: ViewProtocol) { - self.view = view - } - - func respond(to request: Request, chainingTo next: Responder) throws -> Response { - let response = try next.respond(to: request) - - if let pokemon = response.pokemon { - if request.accept.prefers("html") { - response.view = try view.make("pokemon.mustache", pokemon) - } else { - response.json = try pokemon.makeJSON() - } - } - - return response - } -} - -extension PokemonMiddleware: ConfigInitializable { - convenience init(config: Config) throws { - let view = try config.resolveView() - self.init(view) - } -} -``` - -#### Response - -And the extension to `Response`. - -```swift -extension Response { - var pokemon: Pokemon? { - get { return storage["pokemon"] as? Pokemon } - set { storage["pokemon"] = newValue } - } -} -``` - -In this example, we added a new property to response capable of holding a Pokémon object. If the middleware finds a response with one of these Pokémon objects, it will dynamically check whether the client prefers HTML. If the client is a browser like Safari and prefers HTML, it will return a Mustache view. If the client does not prefer HTML, it will return JSON. - -#### Usage - -Your closures can now look something like this: - -```swift -import Vapor - -let config = try Config() -config.addConfigurable(middleware: PokemonMiddleware.init, name: "pokemon") - -let drop = try Droplet(config) - -drop.get("pokemon", Pokemon.self) { request, pokemon in - let response = Response() - response.pokemon = pokemon - return response -} -``` - -!!! tip - Don't forget to add `"pokemon"` to your `droplet.json` middleware array. - -#### Response Representable - -If you want to go a step further, you can make `Pokemon` conform to `ResponseRepresentable`. - -```swift -import HTTP - -extension Pokemon: ResponseRepresentable { - func makeResponse() throws -> Response { - let response = Response() - response.pokemon = self - return response - } -} -``` - -Now your route closures are greatly simplified. - -```swift -drop.get("pokemon", Pokemon.self) { request, pokemon in - return pokemon -} -``` - -Middleware is incredibly powerful. Combined with extensions, it allows you to add functionality that feels native to the framework. diff --git a/2.0/docs/http/package.md b/2.0/docs/http/package.md deleted file mode 100644 index 22058f85..00000000 --- a/2.0/docs/http/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using HTTP - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import HTTP -``` - -## Without Vapor - -HTTP provides everything you need to create an HTTP-based application for any server-side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```Swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/engine.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import HTTP` to access HTTP's APIs diff --git a/2.0/docs/http/request.md b/2.0/docs/http/request.md deleted file mode 100644 index 2f3a9662..00000000 --- a/2.0/docs/http/request.md +++ /dev/null @@ -1,234 +0,0 @@ -# Request - -The most common part of the `HTTP` library we'll be interacting with is the `Request` type. Here's a look at some of the most commonly used attributes in this type. - -```swift -public var method: Method -public var uri: URI -public var parameters: Node -public var headers: [HeaderKey: String] -public var body: Body -public var data: Content -``` - -### Method - -The HTTP `Method` associated with the `Request`, ie: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. - -### URI - -The associated `URI` of the request. We will use this to access attributes about the `uri` the request was sent to. - -For example, given the following uri: `http://vapor.codes/example?query=hi#fragments-too` - -```swift -let scheme = request.uri.scheme // http -let host = request.uri.host // vapor.codes - -let path = request.uri.path // /example -let query = request.uri.query // query=hi -let fragment = request.uri.fragment // fragments-too -``` - -### Route Parameters - -The url parameters associated with the request. For example, if we have a path registered as `hello/:name/age/:age`, we would be able to access those in our request, like so: - -```swift -let name = request.parameters["name"] // String? -let age = request.parameters["age"]?.int // Int? -``` - -Or, to automatically throw on `nil` or invalid variable, you can also `extract` - -```swift -let name = try request.parameters.extract("name") as String -let age = try request.parameters.extract("age") as Int -``` - -These extract functions can cast to any `NodeInitializable` type, including your own custom types. Make sure to check out [Node](https://github.com/vapor/node) for more info. - -> Note: Vapor also provides type safe routing in the routing section of our docs. - - -### Headers - -These are the headers associated with the request. If you are preparing an outgoing request, this can be used to add your own keys. - -```swift -let contentType = request.headers["Content-Type"] -``` - -Or for outgoing requests: - -```swift -let request = Request ... -request.headers["Content-Type"] = "application/json" -request.headers["Authorization"] = ... my auth token -``` - -#### Extending Headers - -We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. - -```swift -extension HTTP.KeyAccessible where Key == HeaderKey, Value == String { - var customKey: String? { - get { - return self["Custom-Key"] - } - set { - self["Custom-Key"] = newValue - } - } -} -``` - -With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: - -```swift -let customKey = request.headers.customKey - -// or - -let request = ... -request.headers.customKey = "my custom value" -``` - -### Body - -This is the body associated with the request and represents the general data payload. You can view more about body in the associated [docs](./body.md) - -For incoming requests, we'll often pull out the associated bytes like so: - -```swift -let rawBytes = request.body.bytes -``` - -## Content - -Generally when we're sending or receiving requests, we're using them as a way to transport content. For this, Vapor provides a convenient `data` variable associated with the request that prioritizes content in a consistent way. - -For example, say I receive a request to `http://vapor.codes?hello=world`. - -```swift -let world = request.data["hello"]?.string -``` - -This same code will work if I receive a JSON request, for example: - -```json -{ - "hello": "world" -} -``` - -Will still be accessible through data. - -```swift -let world = request.data["hello"]?.string -``` -> Note: Force unwrap should never be used. - -This also applies to multi-part requests and can even be extended to new types such as XML or YAML through middleware. - -If you'd prefer to access given types more explicitly, that's totally fine. The `data` variable is purely opt-in convenience for those who want it. - -## Form Data - -It is common in many applications to receive forms submitted from a Web browser. Vapor provides support for several common encodings: - -```swift -// Node? from application/x-www-form-urlencoded -let formData = request.formURLEncoded - -// [String:Field]? from multipart/form-data -let multipartFormData = request.formData - -// [Part]? from multipart/mixed -let multipartMixedData = request.multipart -``` - -These accessors will return `nil` if the request's `Content-Type` does not match what they expect. - -## JSON - -To access JSON directly on a given request, use the following: - -```swift -let json = request.json["hello"] -``` - -## Query Parameters - -The same applies to query convenience: - -```swift -let query = request.query?["hello"] // String? -let name = request.query?["name"]?.string // String? -let age = request.query?["age"]?.int // Int? -let rating = request.query?["rating"]?.double // Double? -``` - -## Key Paths - -Key paths work on most Vapor types that can have nested key value objects. Here's a couple examples of how to access given the following json: - -```json -{ - "metadata": "some metadata", - "artists" : { - "href": "http://someurl.com", - "items": [ - { - "name": "Van Gogh", - }, - { - "name": "Mozart" - } - ] - } -} -``` - -We could access the data in the following ways: - -### Metadata - -Access top level values - -```swift -let type = request.data["metadata"].string // "some metadata" -``` - -### Items - -Access nested values - -```swift -let items = request.data["artists", "items"] // [["name": "Van Gogh"], ["name": "Mozart"]] -``` - -### Mixing Arrays and Objects - -Get first artists - -```swift -let first = request.data["artists", "items", 0] // ["name": "Van Gogh"] -``` - -### Array Item - -Get key from array item - -```swift -let firstName = request.data["artists", "items", 0, "name"] // "Van Gogh" -``` - -### Array Comprehension - -We can also smartly map an array of keys, for example, to just get the names of all of the artists, we could use the following - -```swift -let names = request.data["artists", "items", "name"] // ["Van Gogh", "Mozart"] -``` diff --git a/2.0/docs/http/responder.md b/2.0/docs/http/responder.md deleted file mode 100644 index 0ee2902b..00000000 --- a/2.0/docs/http/responder.md +++ /dev/null @@ -1,73 +0,0 @@ -# Responder - -The `Responder` is a simple protocol defining the behavior of objects that can accept a `Request` and return a `Response`. Most notably in Vapor, it is the core API endpoint that connects the `Droplet` to the `Server`. Let's look at the definition: - -```swift -public protocol Responder { - func respond(to request: Request) throws -> Response -} -``` - -> The responder protocol is most notably related to Droplet and it's relationship with a server. Average users will not likely interact with it much. - -## Simple - -Of course, Vapor provides some conveniences for this, and in practice, we will often call: - -```swift -try drop.run() -``` - -## Manual - -As we just mentioned, the Vapor `Droplet` itself conforms to `Responder`, connecting it to the `Server`. This means if we wanted to serve our droplet manually, we could do: - -```swift -let server = try Server, Serializer>(port: port) -try server.start(responder: droplet) { error in - print("Got error: \(error)") -} -``` - -## Advanced - -We can conform our own objects to `Responder` and pass them to `Servers`. Let's look at an example: - -```swift -final class Responder: HTTP.Responder { - func respond(to request: Request) throws -> Response { - let body = "Hello World".makeBody() - return Response(body: body) - } -} -``` - -This only returns `"Hello World"` for every request, it's most commonly going to be linked with a router of some type. - - -```swift -final class Responder: HTTP.Responder { - let router: Router = ... - - func respond(to request: Request) throws -> Response { - return try router.route(request) - } -} -``` - -We'll then pass this responder to a server and let it go. - -```swift -let server = try Server, Serializer>(port: port) - -print("visit http://localhost:\(port)/") -try server.start(responder: Responder()) { error in - print("Got error: \(error)") -} -``` - -This can be used as a jumping off point for applications looking to implement features manually. - -## Client - -The `HTTP.Client` is itself a `Responder` although, instead of handling the `Request` itself, it passes it on to the underlying URI. diff --git a/2.0/docs/http/response-representable.md b/2.0/docs/http/response-representable.md deleted file mode 100644 index 5e17a700..00000000 --- a/2.0/docs/http/response-representable.md +++ /dev/null @@ -1,93 +0,0 @@ -# ResponseRepresentable - -Traditionally HTTP servers take a `Request` and return a `Response`. Vapor is no different, but we can take advantage of Swift's powerful protocols to be a bit more flexible to the user facing API. - -Let's start with the definition of `ResponseRepresentable` - -```swift -public protocol ResponseRepresentable { - func makeResponse() throws -> Response -} -``` - -By conforming to this protocol, we can more flexibly return things that conform instead of creating the response manually each time. Vapor provides some of these by default. Including (but not limited to): - -### String - -Because string conforms to `ResponseRepresentable`, we can return it directly in a Vapor route handler. - -```swift -drop.get("hello") { request in - return "Hello, World!" -} -``` - -### JSON - -`JSON` can be returned directly instead of recreating a response each time. - -```swift -drop.get("hello") { request in - var json = JSON() - try json.set("hello", "world") - try json.set("some-numbers", [1, 2, 3]) - return json -} -``` - -### Response - -Of course, we can also return Responses for anything not covered: - -```swift -drop.get("hello") { request in - return Response( - status: .ok, - headers: ["Content-Type": "text/plain"], - body: "Hello, World!" - ) -} -``` - -## Conforming - -All we need to do to return our own objects is conform them to `ResponseRepresentable`. Let's look at an example type, a simple blog post model: - -```swift -import Foundation - -struct BlogPost { - let id: String - let content: String - let createdAt: NSDate -} -``` - -And now, let's conform it to response representable. - -```swift -import HTTP - -extension BlogPost: ResponseRepresentable { - func makeResponse() throws -> Response { - var json = JSON() - try json.set("id", id) - try json.set("content", content) - try json.set("created-at", createdAt.timeIntervalSince1970) - return try json.makeResponse() - } -} -``` - -Now that we've modeled our BlogPost, we can return it directly in route handlers. - -```swift -drop.post("post") { req in - guard let content = request.data["content"] else { - throw Error.missingContent - } - let post = Post(content: content) - try post.save(to: database) - return post -} -``` diff --git a/2.0/docs/http/response.md b/2.0/docs/http/response.md deleted file mode 100644 index 1f00a021..00000000 --- a/2.0/docs/http/response.md +++ /dev/null @@ -1,144 +0,0 @@ -# Response - -When building endpoints, we'll often be returning responses for requests. If we're making outgoing requests, we'll be receiving them. - -```swift -public let status: Status -public var headers: [HeaderKey: String] -public var body: Body -public var data: Content -``` - -#### Status - -The http status associated with the event, for example `.ok` == 200 ok. - -#### Headers - -These are the headers associated with the request. If you are preparing an outgoing response, this can be used to add your own keys. - -```swift -let contentType = response.headers["Content-Type"] -``` - -Or for outgoing response: - -```swift -let response = response ... -response.headers["Content-Type"] = "application/json" -response.headers["Authorization"] = ... my auth token -``` - -##### Extending Headers - -We generally seek to improve code bases by removing stringly typed code where possible. We can add variables to the headers using generic extensions. - -```swift -extension HTTP.KeyAccessible where Key == HeaderKey, Value == String { - var customKey: String? { - get { - return self["Custom-Key"] - } - set { - self["Custom-Key"] = newValue - } - } -} -``` - -With this pattern implemented, our string `"Custom-Key"` is contained in one section of our code. We can now access like this: - -```swift -let customKey = response.headers.customKey - -// or - -let request = ... -response.headers.customKey = "my custom value" -``` - -#### Body - -This is the body associated with the response and represents the general data payload. You can view more about body in the associated [docs](./body.md) - -For responses, the body is most commonly set at initialization. With two main types. - -##### BodyRepresentable - -Things that can be converted to bytes, ie: - -```swift -let response = Response(status: .ok, body: "some string") -``` - -In the above example, the `String` will be automatically converted to a body. Your own types can do this as well. - -##### Bytes Directly - -If we already have our bytes array, we can pass it into the body like so: - -```swift -let response = Response(status: .ok, body: .data(myArrayOfBytes)) -``` - -##### Chunked - -To send an `HTTP.Response` in chunks, we can pass a closure that we'll use to send our response body in parts. - -```swift -let response = Response(status: .ok) { chunker in - for name in ["joe", "pam", "cheryl"] { - sleep(1) - try chunker.send(name) - } - - try chunker.close() -} -``` - -> Make sure to call `close()` before the chunker leaves scope. - -## Content - -We can access content the same we do in a [request](./request.md). This most commonly applies to outgoing requests. - -```swift -let pokemonResponse = try drop.client.get("http://pokeapi.co/api/v2/pokemon/") -let names = pokemonResponse.data["results", "name"]?.array -``` - -## JSON - -To access JSON directly on a given response, use the following: - -```swift -let json = request.response["hello"] -``` - -## Key Paths - -For more on KeyPaths, visit [here](./request.md#key-paths) - -## Serving Files - -If you are simply looking to serve files from your public directory, -it may be useful to look at 'FileMiddleware' instead. - -```swift -let res = try Response(filePath: "/path/to/file.txt") -``` - -Use this to initialize a file response for the exact file path. -If using from a public folder for example, the file name should be appended -to the public directory, ie: `drop.publicDir + "myFile.cool"` - -```swift -Response(filePath: String, ifNoneMatch: String? = nil, chunkSize: Int = 2048) throws -``` - -If none match represents an ETag that will be used to check if the file has -changed since the last load by the client. This allows clients like browsers -to cache their files and avoid downloading resources unnecessarily. -Most often calculated w/ https://tools.ietf.org/html/rfc7232#section-3.2 - -For an example of how this is used, look at 'FileMiddleware'. diff --git a/2.0/docs/http/server.md b/2.0/docs/http/server.md deleted file mode 100644 index a886acfb..00000000 --- a/2.0/docs/http/server.md +++ /dev/null @@ -1,135 +0,0 @@ -# Server - -The server is responsible for accepting connections from clients, parsing their requests, and delivering them a response. - -## Default - -Starting your Droplet with a default server is simple. - -```swift -import Vapor - -let drop = try Droplet() -try drop.run() -``` - -The default server will bind to host `0.0.0.0` at port `8080`. - -## Config - -If you are using a `Config/server.json` file, this is where you can easily change your host and port. - -```json -{ - "port": "$PORT:8080", - "host": "0.0.0.0", - "securityLayer": "none" -} -``` - -The default `server.json` is above. The port with try to resolve the environment variable `$PORT` or fallback to `8080`. - -## TLS - -TLS (formerly SSL) can be configured with a variety of different certificate and signature types. - -### Verify - -Verificiation of hosts and certificates can be disabled. They are enabled by default. - -> Note: Be extremely careful when disabling these options. - -```json -"tls": { - "verifyHost": false, - "verifyCertificates": false -} -``` - -### Certificates - -#### None - -```json -"tls": { - "certificates": "none" -} -``` - -#### Chain - -```json -"tls": { - "certificates": "chain", - "chainFile": "/path/to/chainfile" -} -``` - -#### Files - -```json -"tls": { - "certificates": "files", - "certificateFile": "/path/to/cert.pem", - "privateKeyFile": "/path/to/key.pem" -} -``` - -#### Certificate Authority - -```json -"tls": { - "certificates": "ca" -} -``` - -### Signature - -#### Self Signed - -```json -"tls": { - "signature": "selfSigned" -} -``` - -#### Signed File - -```json -"tls": { - "signature": "signedFile", - "caCertificateFile": "/path/to/file" -} -``` - -#### Signed Directory - -```json -"tls": { - "signature": "signedDirectory", - "caCertificateDirectory": "/path/to/dir" -} -``` - -## Example - -Here is an example `server.json` file using certificate files with a self signed signature and host verification redundantly set to `true`. - -```json -{ - "port": "8443", - "host": "0.0.0.0", - "securityLayer": "tls", - "tls": { - "verifyHost": true, - "certificates": "files", - "certificateFile": "/vapor/certs/cert.pem", - "privateKeyFile": "/vapor/certs/key.pem", - "signature": "selfSigned" - } -} -``` - -## Nginx - -It is highly recommended that you serve your Vapor project behind Nginx in production. Read more in the [deploy Nginx](../deploy/nginx.md) section. \ No newline at end of file diff --git a/2.0/docs/images/droplet-color.svg b/2.0/docs/images/droplet-color.svg deleted file mode 100644 index 11c540fb..00000000 --- a/2.0/docs/images/droplet-color.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - Vapor - The Vapor droplet logo in pink and blue. - - - - - - - - - - \ No newline at end of file diff --git a/2.0/docs/images/droplet-white.svg b/2.0/docs/images/droplet-white.svg deleted file mode 100644 index 9f645196..00000000 --- a/2.0/docs/images/droplet-white.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - Vapor - The Vapor droplet logo in white. - - - - \ No newline at end of file diff --git a/2.0/docs/index.md b/2.0/docs/index.md deleted file mode 100644 index bb379ec6..00000000 --- a/2.0/docs/index.md +++ /dev/null @@ -1,140 +0,0 @@ -# Vapor Documentation - -This is the documentation for Vapor, a Web Framework for Swift that works on iOS, macOS, and Ubuntu; and all of the packages that Vapor offers. - -Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API. - - -## Getting Started - -If this is your first time using Vapor, head to the [Getting Started](getting-started/install-on-macos.md) section to install Swift and create your first app. - -## Like Vapor? - -Our small team works hard to make Vapor awesome (and free). Support the framework by starring Vapor on GitHub or donating $1 monthly--it helps us a lot. Thanks! - - - - - - - - - -## Other Sources - -Here are some other great places to find information about Vapor. - -### API - -Auto-generated API documentation is located at [api.vapor.codes](http://api.vapor.codes). - -### Stack Overflow - -View or ask questions related to Vapor on Stack Overflow using the [`vapor`](http://stackoverflow.com/questions/tagged/vapor) tag. - -### GitHub - -#### Source Code - -To view the framework's source code and code documentation, visit [Vapor's GitHub](https://github.com/vapor/vapor). - -#### Issues - -To view open bug reports and feature requests, or to create one, visit the [issues](https://github.com/vapor/vapor/issues) tab on [Vapor's GitHub](https://github.com/vapor/vapor). - -## Packages - -Vapor is a modular framework built for a modular language. Code is split up into modules which are grouped to form packages. Packages can be added to your project by adding the package's Git url to your `Package.swift` file. Once a package is included, all of its modules will be available to `import`. You can read more about packages and modules in the Swift Package Manager [conceptual overview](https://swift.org/package-manager/). - -Below is a list of packages and modules that come with or can be used by Vapor projects. Packages will have a link to their respective GitHub page. - -### Core - -Core packages are maintained by the Vapor team. - -#### Included - -The following packages are included with Vapor by default. - -!!! tip - These packages can also be used individually - -- [Vapor](https://github.com/vapor/vapor): Swift's most used web framework. - - Auth: User authentication and persistance. - - Sessions: Secure, ephemeral cookie based data storage. - - Cookies: HTTP cookies. - - Routing: Advanced router with type-safe parameterization. -- [Engine](https://github.com/vapor/engine): Core transport layers. - - HTTP: HTTP client and server. - - URI: URI parsing and serializing. - - WebSockets: Full-duplex communication channels over a single TCP connection. - - SMTP: Send email using Sendgrid and Gmail. -- [Multipart](https://github.com/vapor/multipart): Fast, streaming, non-blocking multipart parser and serializer. - - Multipart: Parses and serializes `multipart/mixed`. - - FormData: Parses and serializes `multipart/form-data`. -- [JSON](https://github.com/vapor/json): Conveniences for working with JSON in Swift. -- [Console](https://github.com/vapor/console): Swift wrapper for console IO and commands. -- [TLS](https://github.com/vapor/tls): Swift wrapper for CLibreSSL's new TLS. -- [Crypto](https://github.com/vapor/crypto): Cryptography from LibreSSL and Swift. - - Digests: Hashing with and without authentication. - - Ciphers: Encryption and decryption - - Random: Pseudo and cryptographically secure randomness. - - BCrypt: Pure Swift implementation. -- [Node](https://github.com/vapor/node): Data structure for easy type conversions. - - [Polymorphic](https://github.com/vapor/polymorphic): Syntax for easily accessing values from common types like JSON. - - [Path Indexable](https://github.com/vapor/path-indexable): A protocol for powerful subscript access of common types like JSON. -- [Core](https://github.com/vapor/core): Core extensions, type-aliases, and functions that facilitate common tasks. -- [Socks](https://github.com/vapor/socks): Swift C Socket API wrapper. -- [Bits](https://github.com/vapor/bits): Low level byte manipulation helpers - -#### Providers - -These are officially supported packages for Vapor that are not included by default. - -- [Fluent](https://github.com/vapor/fluent): Models, relationships, and querying for NoSQL and SQL databases. - - [Fluent Provider](https://github.com/vapor/fluent-provider): Fluent provider for Vapor. -- [MySQL](https://github.com/vapor/mysql): Robust MySQL interface for Swift. - - [MySQL Driver](https://github.com/vapor/mysql-driver): MySQL driver for Fluent. - - [MySQL Provider](https://github.com/vapor/mysql-provider): MySQL provider for Vapor. -- [Leaf](https://github.com/vapor/leaf): An extensible templating language. - - [Leaf Provider](https://github.com/vapor/leaf-provider): Leaf provider for Vapor. -- [Redis](https://github.com/vapor/redbird): Pure-Swift Redis client implemented from the original protocol spec. - - [Redis Provider](https://github.com/vapor/redis-provider): Redis cache provider for Vapor. -- [JWT](https://github.com/vapor/jwt): JSON Web Tokens in Swift. - - [JWT Provider](https://github.com/vapor/jwt-provider): JWT conveniences for Vapor. - -### Community - -These are packages maintained by community members that work great with Vapor. - -- [APNS](https://github.com/matthijs2704/vapor-apns): Simple APNS Library for Vapor (Swift). -- [Flock](https://github.com/jakeheis/Flock): Automated deployment of Swift projects to servers - - [VaporFlock](https://github.com/jakeheis/VaporFlock): Use Flock to deploy Vapor applications -- [Heimdall](https://github.com/himani93/heimdall): An easy to use HTTP request logger. -- [Jobs](https://github.com/BrettRToomey/Jobs): A minimalistic job/background-task system for Swift. -- [Kitura Provider](https://github.com/vapor/kitura-provider): Use IBM's Kitura HTTP server in Vapor. -- [Leaf Error Middleware](https://github.com/brokenhandsio/leaf-error-middleware) - Custom 404 and error views for your website -- [MarkdownProvider](https://github.com/vapor-community/markdown-provider) - Easily use Markdown from Leaf. -- [MongoKitten](https://github.com/OpenKitten/MongoKitten): Native MongoDB driver for Swift, written in Swift - - [Mongo Driver](https://github.com/vapor/mongo-driver): MongoKitten driver for Fluent. - - [Mongo Provider](https://github.com/vapor/mongo-provider): MongoKitten provider for Vapor. -- [PostgreSQL](https://github.com/vapor/postgresql): Robust PostgreSQL interface for Swift. - - [PostgreSQL Driver](https://github.com/vapor/postgresql-driver): PostgreSQL driver for Fluent. - - [PostgreSQL Provider](https://github.com/vapor/postgresql-provider): PostgreSQL provider for Vapor. -- [Sanitize](https://github.com/gperdomor/sanitize): Powerful model extraction from JSON requests. -- [SteamPress](https://github.com/brokenhandsio/SteamPress): A blogging engine for Vapor. -- [SwiftyBeaver](https://github.com/vapor-community/swiftybeaver-provider): Adds the powerful logging of SwiftyBeaver to Vapor. -- [VaporFCM](https://github.com/mdab121/vapor-fcm): Simple FCM (iOS + Android Push Notifications) library built for Vapor in Swift. -- [VaporForms](https://github.com/bygri/vapor-forms): Brings simple, dynamic and re-usable web form handling to Vapor. -- [VaporS3Signer](https://github.com/JustinM1/VaporS3Signer): Generate V4 Auth Header/Pre-Signed URL for AWS S3 REST API -- [Vapor OAuth](https://github.com/brokenhandsio/vapor-oauth) - An OAuth2 Provider library for Vapor -- [Vapor Security Headers](https://github.com/brokenhandsio/VaporSecurityHeaders): Add common security headers to your Vapor Application. - -### Providers - -Vapor providers are a convenient way to add functionality to your Vapor projects. For a full list of providers, check out the [`vapor-provider`](https://github.com/search?utf8=✓&q=topic%3Avapor-provider&type=Repositories) tag on GitHub. - -## Authors - -[Tanner Nelson](mailto:tanner@vapor.codes), [Logan Wright](mailto:logan@vapor.codes), and the hundreds of members of Vapor. diff --git a/2.0/docs/json/overview.md b/2.0/docs/json/overview.md deleted file mode 100644 index 4e9efe54..00000000 --- a/2.0/docs/json/overview.md +++ /dev/null @@ -1,119 +0,0 @@ -# JSON - -JSON is an integral part of Vapor. It powers Vapor's [Config](../configs/config.md) and is easy to use in both requests and responses. - -## Request - -JSON is automatically available in `request.data` alongside Form URL Encoded, Form Data, and Query data. This allows you to focus on making a great API, not worrying about what content types data will be sent in. - -```swift -drop.get("hello") { request in - guard let name = request.data["name"]?.string else { - throw Abort(.badRequest) - } - return "Hello, \(name)!" -} -``` - -This will return a greeting for any HTTP method or content type that the `name` is sent as, including JSON. - -### JSON Only - -To specifically target JSON, use the `request.json` property. - -```swift -drop.post("json") { request in - guard let name = request.json?["name"]?.string else { - throw Abort(.badRequest) - } - - return "Hello, \(name)!" -} -``` -The above snippet will only work if the request is sent with JSON data. - -## Response - -To respond with JSON, simply create a `JSON` object and add values to it. - -```swift -drop.get("version") { request in - var json = JSON() - try json.set("version", 1.0) - return json -} -``` - -## Convertible - -Making your classes and structs JSON convertible is a great way to interact with APIs in an organized and DRY way. - -### Representable - -When something conforms to `JSONRepresentable`, it can be converted into JSON. - -```swift -extension User: JSONRepresentable { - func makeJSON() throws -> JSON { - var json = JSON() - try json.set("id", id) - try json.set("name", name) - try json.set("age", age) - return json - } -} -``` - -Now you can simply return `user.makeJSON()` in your routes. - -```swift -drop.get("users", User.parameter) { req in - let user = try req.parameters.next(User.self) - return try user.makeJSON() -} -``` - -You can even go a step further and conform your model to `ResponseRepresentable`. Since it's already `JSONRepresentable` -you will get the conformance for free. - -```swift -extension User: ResponseRepresentable { } -``` - -Now you can return the model by itself. It will automatically call `.makeJSON()`. - -```swift -drop.get("users", User.parameter) { req in - let user = try req.parameters.next(User.self) - return try user -} -``` - -### Initializable - -When something conforms to `JSONInitializable`, it can be created from JSON. - -```swift -extension User: JSONInitializable { - convenience init(json: JSON) throws { - try self.init( - name: json.get("name"), - age: json.get("age") - ) - } -} -``` - -Now you can simply call `User(json: ...)` to create a user from JSON. - -```swift -drop.post("users") { req in - guard let json = req.json else { - throw Abort(.badRequest) - } - - let user = try User(json: json) - try user.save() - return user -} -``` diff --git a/2.0/docs/json/package.md b/2.0/docs/json/package.md deleted file mode 100644 index 17710ac0..00000000 --- a/2.0/docs/json/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using JSON - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import JSON -``` - -## Without Vapor - -JSON provides easy-to-use JSON support for any server-side, or client side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```Swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/json.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import JSON` to access JSON's APIs diff --git a/2.0/docs/jwt/overview.md b/2.0/docs/jwt/overview.md deleted file mode 100644 index 6bd9e7df..00000000 --- a/2.0/docs/jwt/overview.md +++ /dev/null @@ -1,76 +0,0 @@ -# JWT Overview - -This guide gives an overview of using the JWT provider package. - -## Configuration - -`JWTProvider` can be configured in **3** different ways: - -- Custom signers defined in `jwt.json` - - Supports (private/public): `hmac`, `rsa`, `esdca`. -- Legacy custom signer defined in `jwt.json`. - - Supports (private/public): `hmac`, `rsa`, `esdca`. -- Remote JSON Web Key Set (`jwks.json`) URL - - Supports (private/public): `rsa`. - -If your Vapor app is acting as an Authentication Provider, you may want to use either the `Legacy custom signer` setup, or the `Custom signers` setup, which is great if you want to perform certificates rotation. - -The only difference is that with `Custom signers` the `kid` value in the `JWT` header is not ignored, and it must match an associated signer in order to verify the signature. - -If your Vapor app is a Resource Provider that delegates Authentication to a 3rd party (auth0, stormpath, etc), you may want to use the `Remote JSON Web Key Set` setup. In this configuration the JWT token is generated by a 3rd party that provides the public key in JSON Web Key Set format. -The Vapor app is only in charge to verify the `JWT` signature using the key set provided by the 3rd party. - -### Remote JSON Web Key Set - -`Config/jwt.json` -```json -{ - "jwks-url": "http://my-domain.com/well-known/jwks.json" -} -``` - -### Custom Signers - -This allows to specify an array of signers and is particularly useful for rotating certificates. -Custom signers are not backward compatible and must specify an additional `kid` in the configuration. - -- type: `unsigned`, `hmac`, `rsa`, `esdca` -- kid: an unique identifier -- algorithm: - - type[`hmac`]: `hs256`, `hs384`, `hs512` - - type[`rsa`]: `rs256`, `rs384`, `rs512` - - type[`esdca`]: `es256`, `es384`, `es512` - -`Config/jwt.json` -```json -{ - "signers": { - "1234": { - "type": "rsa", - "algorithm": "rs256", - "key": "yourkeyhere" - } - } -} -``` - -### Legacy Custom Signer - -This is backwards compatible with the previous implementation. - -- type: `unsigned`, `hmac`, `rsa`, `esdca` -- algorithm: - - type[`hmac`]: `hs256`, `hs384`, `hs512` - - type[`rsa`]: `rs256`, `rs384`, `rs512` - - type[`esdca`]: `es256`, `es384`, `es512` - -`Config/jwt.json` -```json -{ - "signer": { - "type": "rsa", - "algorithm": "rs256", - "key": "yourkeyhere" - } -} -``` diff --git a/2.0/docs/jwt/package.md b/2.0/docs/jwt/package.md deleted file mode 100644 index 9ff992de..00000000 --- a/2.0/docs/jwt/package.md +++ /dev/null @@ -1,43 +0,0 @@ -# Using JWT - -This section outlines how to import the JWT package both with or without a Vapor project. - -## With Vapor - -The easiest way to use JWT with Vapor is to include the JWT provider. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/jwt-provider.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -The JWT provider package adds JWT to your project and adds some additional, Vapor-specific conveniences like `drop.signers`. - -Use `import JWTProvider`. - -## Just JWT - -At the core of the JWT provider is a fast, pure-Swift JWT implementation for parsing, serializing, and verifying JSON Web Tokens. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/jwt.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import JWT` to access the `JWT` class. diff --git a/2.0/docs/leaf/leaf.md b/2.0/docs/leaf/leaf.md deleted file mode 100644 index 335ad88c..00000000 --- a/2.0/docs/leaf/leaf.md +++ /dev/null @@ -1,201 +0,0 @@ -!!! warning - This section may contain outdated information. - -# Leaf - -Welcome to Leaf. Leaf's goal is to be a simple templating language that can make generating views easier. There are plenty of great templating languages, so use what's best for you -- maybe that's Leaf! The goals of Leaf are: - -- Small set of strictly enforced rules -- Consistency -- Parser first mentality -- Extensibility - -## Syntax -### Structure - -Leaf Tags are made up of 4 Elements: - - Token: `#` is the Token - - Name: A `string` that identifies the tag - - Parameter List: `()` May accept 0 or more arguments - - Body (optional): `{}` Must be separated from the Parameter List by a space - -There can be many different usages of these 4 elements depending on the Tag's implementation. Let's look at a few examples of how Leaf's built-in Tags might be used: - - - `#()` - - `#(variable)` - - `#import("template")` - - `#export("link") { }` - - `#index(friends, "0")` - - `#loop(friends, "friend") {
  • #(friend.name)
  • }` - - `#raw() { Anything goes!@#$%^&* }` - -### Using the `#` token in HTML - -The `#` token cannot be escaped. Use the `#()` or `#raw() {}` Tag to output a `#` in a Leaf Template. `#()` => `#` - -### Raw HTML - -All Leaf output is escaped by default. Use the `#raw() {}` Tag for unescaped output. -`#raw() { Link }` => `Link` -> IMPORTANT! Make sure you are not using the `#raw() {}` Tag with user input. - -### Chaining - -The double token: `##` indicates a chain. It can be applied to any standard Tag. If the previous Tag fails, the chained Tag will be given an opportunity to run. - -``` -#if(hasFriends) ##embed("getFriends") -``` - -### Leaf's built-in Tags - -#### Token: `#()` - -``` -#() #()hashtags #()FTW => # #hashtags #FTW -``` - -#### Raw: `#raw() {}` - -``` -#raw() { - Do whatever w/ #'s here, this code won't be rendered as leaf document and is not escaped. - It's a great place for things like Javascript or large HTML sections. -} -``` - -#### Equal: `#equal(lhs, rhs) {}` - -``` -#equal(leaf, leaf) { Leaf == Leaf } => Leaf == Leaf -#equal(leaf, mustache) { Leaf == Mustache } => -``` - -#### Variable: `#(variable)` - -``` -Hello, #(name)! -``` - -#### Loop: `#loop(array, "item") {}` - -``` -#loop(friends, "friend") { - #(offset). #(friend.name) -} -``` - -The body of a `#loop` can access an `index` variable corresponding to the index of the array. There is also an `offset` variable which is the index plus 1. - -#### Index: `#index(array, _ index: String)` - -``` -Hello, #index(friends, "0")! -Hello, #index(friends, "best")! -``` - -Note: array indexes are always strings. - -#### If - Else: `#if(bool) ##else() { this }` - -``` -#if(entering) { - Hello, there! -} ##if(leaving) { - Goodbye! -} ##else() { - I've been here the whole time. -} -``` - -Note that `#if` requires a boolean variable. If you need to do a comparison then `#equal` is more appropriate. You can chain `#equal` in the same way as `#if`: - -``` -#equal(state, "0") { - Normal -} ##equal(state, "1") { - Warning -} ##else() { - Alert -} -``` - -#### Import: `#import("template")` -#### Export: `#export("template") { Leaf/HTML }` -#### Extend: `#extend("template")` -#### Embed: `#embed("template")` - -> When using these Layout Tags, omit the template file's .leaf extension. - -``` -/// base.leaf - -#import("html") - -/// html.leaf -#extend("base") - -#export("html") { #embed("body") } - -/// body.leaf - -``` - -Leaf renders `html.leaf` as: - -``` - - -``` - -### Custom Tags - -Look at the existing tags for advanced scenarios, let's look at a basic example by creating `Index` together. This tag will take two arguments, an array, and an index to access. - -```swift -class Index: BasicTag { - let name = "index" - - func run(arguments: [Argument]) throws -> Node? { - guard - arguments.count == 2, - let array = arguments[0].value?.nodeArray, - let index = arguments[1].value?.int, - index < array.count - else { return nil } - return array[index] - } -} -``` - -We can now register this Tag in our `main.swift` file with: - -```swift -if let leaf = drop.view as? LeafRenderer { - leaf.stem.register(Index()) -} -``` - -And use it just like we did [above](#index). - -> Note: Use of non-alphanumeric characters in Tag Names is **strongly discouraged** and may be disallowed in future versions of Leaf. - -## Syntax Highlighting - -### Atom - -[language-leaf](https://atom.io/packages/language-leaf) by ButkiewiczP - -### Xcode - -It is not currently possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when `vapor xcode` is run. - -There appears to be a way to [make Xcode file associations persist](http://stackoverflow.com/questions/9050035/how-to-make-xcode-recognize-a-custom-file-extension-as-objective-c-for-syntax-hi) but that requires a bit more kung-fu. - -### VS Code - -[html-leaf](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf) by FranciscoAmado - -### CLion & AppCode - -Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on [Vapor Slack](http://vapor.team) diff --git a/2.0/docs/leaf/package.md b/2.0/docs/leaf/package.md deleted file mode 100644 index d4526eee..00000000 --- a/2.0/docs/leaf/package.md +++ /dev/null @@ -1,84 +0,0 @@ -# Using Leaf - -This section outlines how to import the Leaf package both with or without a Vapor project. - -## Example Folder Structure - -``` -Hello -├── Config -│   ├── app.json -│   ├── crypto.json -│   ├── droplet.json -│   ├── fluent.json -│   └── server.json -├── Package.pins -├── Package.swift -├── Public -├── README.md -├── Resources -│   ├── Views -│   │   │   └── hello.leaf -├── Public -│   ├── images (images resources) -│   ├── styles (css resources) -├── Sources -│   ├── App -│   │   ├── Config+Setup.swift -│   │   ├── Controllers -│   │   │   └── PostController.swift -│   │   ├── Droplet+Setup.swift -│   │   ├── Models -│   │   │   └── Post.swift -│   │   └── Routes.swift -│   └── Run -│   └── main.swift -├── Tests -│   ├── AppTests -│   │   ├── PostControllerTests.swift -│   │   ├── RouteTests.swift -│   │   └── Utilities.swift -│   └── LinuxMain.swift -├── circle.yml -└── license -``` - -## With Vapor - -The easiest way to use Leaf with Vapor is to include the Leaf provider. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/leaf-provider.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -The Leaf provider package adds Leaf to your project and adds some additional, Vapor-specific conveniences like `drop.stem()`. - -Use `import LeafProvider`. - -## Just Leaf - -At the core of the Leaf provider is a fast, pure Swift templating engine. You can use it with any of your Swift packages or server-side Swift applications. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/leaf.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Leaf` to access the `Leaf.Stem` class. diff --git a/2.0/docs/leaf/provider.md b/2.0/docs/leaf/provider.md deleted file mode 100644 index 2b6466aa..00000000 --- a/2.0/docs/leaf/provider.md +++ /dev/null @@ -1,52 +0,0 @@ -# Leaf Provider - -After you've [added the Leaf Provider package](package.md) to your project, setting the provider up in code is easy. - -## Add to Droplet - -First, register the `LeafProvider.Provider` with your Droplet. - -```swift -import Vapor -import LeafProvider - -let config = try Config() -try config.addProvider(LeafProvider.Provider.self) - -let drop = try Droplet(config) - -... -``` - -## Configure Droplet - -Once the provider is added to your Droplet, you can configure your Droplet to use the Leaf view renderer. - -`Config/droplet.json` - -```json -{ - ..., - "view": "leaf", - ... -} -``` - -!!! seealso - Learn more about configuration files in the [Settings guide](../configs/config.md). - -## Manual - -You can also set the `drop.view` property manually if you want to hardcode your view renderer. - -```swift -import Vapor -import LeafProvider - -let view = LeafRenderer(viewsDir: drop.viewsDir) -let drop = try Droplet(view: view) -``` - -## Done - -Next time you boot your application, your views will be rendered using Leaf. diff --git a/2.0/docs/mysql/driver.md b/2.0/docs/mysql/driver.md deleted file mode 100644 index dacd3ae1..00000000 --- a/2.0/docs/mysql/driver.md +++ /dev/null @@ -1,43 +0,0 @@ -# MySQL Driver - -Fluent uses the MySQL driver to talk to your MySQL database. Although you won't need to use it most of the time, it does -have some handy features. - -## Raw - -Sometimes you need to bypass Fluent and send raw queries to the database. - -```swift -let result = try mysqlDriver.raw("SELECT @@version") -``` - -!!! note: - If you are using Vapor, you can get access to the MySQL Driver with `drop.mysql()` - -## Transaction - -If you are performing multiple queries that depend on eachother, you can use transactions to make sure nothing gets saved -to the database if one of the queries fails. - -```swift -try mysqlDriver.transaction { conn in - // delete user's pets, then delete user - // if one of these fails, the transaction will rollback - try user.pets.makeQuery(conn).delete() - try user.makeQuery(conn).delete() -} -``` - -!!! warning - Make sure to use the connection supplied to the closure for all queries you want included in the transaction. - -## Manual - -You can also manually send a query to the driver without going through Fluent. - -```swift -let query = try User.makeQuery() -... - -let results = try mysqlDriver.query(query) -``` diff --git a/2.0/docs/mysql/package.md b/2.0/docs/mysql/package.md deleted file mode 100644 index d7184875..00000000 --- a/2.0/docs/mysql/package.md +++ /dev/null @@ -1,85 +0,0 @@ -# Using MySQL - -This section outlines how to import the MySQL package both with or without a Vapor project. - -## Install MySQL - -To use MySQL, you need to have the C MySQL library installed on your computer. - -```sh -# macOS -brew tap vapor/homebrew-tap -brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/a340bfac3b7abff408a6b6fe6fdc39a38ab94871/Formula/mysql.rb -brew install mysql -brew install cmysql -brew switch mysql 5.7.22 -``` - -> Note for Ubuntu -> * [Add Vapor's APT repo to get access to all of Vapor's system packages.](https://docs.vapor.codes/2.0/getting-started/install-on-ubuntu/#apt-repo) - -```sh -# Ubuntu - -sudo apt-get install cmysql -``` - -## With Vapor + Fluent - -The easiest way to use MySQL with Vapor is to include the MySQL provider. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -The MySQL provider package adds MySQL to your project and adds some additional, Vapor-specific conveniences like `drop.mysql()`. - -Using `import MySQLProvider` will import both Fluent and Fluent's Vapor-specific APIs. - -## With Fluent - -Fluent is a powerful, pure-Swift ORM that can be used with any Server-Side Swift framework. The MySQL driver allows you to use a MySQL database to power your models and queries. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/fluent.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/mysql-driver.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import MySQLDriver` to access the `MySQLDriver` class which you can use to initialize a Fluent `Database`. - -## Just MySQL - -At the core of the MySQL provider and MySQL driver is a Swift wrapper around the C MySQL client. This package can be used by itself to send raw, parameterized queries to your MySQL database. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/mysql.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import MySQL` to access the `MySQL.Database` class. diff --git a/2.0/docs/mysql/provider.md b/2.0/docs/mysql/provider.md deleted file mode 100644 index 6ccd55b3..00000000 --- a/2.0/docs/mysql/provider.md +++ /dev/null @@ -1,122 +0,0 @@ -# MySQL Provider - -After you've [added the MySQL Provider package](package.md) to your project, setting the provider up in code is easy. - -## Add to Droplet - -First, register the `MySQLProvider.Provider` with your Droplet. - -```swift -import Vapor -import MySQLProvider - -let config = try Config() -try config.addProvider(MySQLProvider.Provider.self) - -let drop = try Droplet(config) - -... -``` - -## Configure Fluent - -Once the provider is added to your Droplet, you can configure Fluent to use the MySQL driver. - -`Config/fluent.json` - -```json -{ - "driver": "mysql" -} -``` - -!!! seealso - Learn more about configuration files in the [Settings guide](../configs/config.md). - -## Configure MySQL - -If you run your application now, you will likely see an error that the MySQL configuration file is missing. Let's add that now. - -### Basic - -Here is an example of a simple MySQL configuration file. - -`Config/mysql.json` -```json -{ - "hostname": "127.0.0.1", - "user": "root", - "password": "password", - "database": "hello" -} -``` - -!!! note - It's a good idea to store the MySQL configuration file in the `Config/secrets` folder since it contains sensitive information. - -### URL - -You can also pass the MySQL credentials as a URL. - -`Config/mysql.json` -```json -{ - "url": "http://root:password@127.0.0.1/hello" -} -``` - -### Read Replicas - -Read replicas can be supplied by passing a single `master` hostname and an array of `readReplicas` hostnames. - -`Config/mysql.json` -```json -{ - "master": "master.mysql.foo.com", - "readReplicas": ["read01.mysql.foo.com", "read02.mysql.foo.com"], - "user": "root", - "password": "password", - "database": "hello" -} -``` - -!!! tip - You can also provide the `readReplicas` as a comma-separated string. - -## Driver - -You can get access to the [MySQL Driver](driver.md) on the droplet. - -```swift -import Vapor -import MySQLProvider - -let mysqlDriver = try drop.mysql() -``` - -## Configure Cache - -You can also choose to use your Fluent database (now set to MySQL) for caching. - -`Config/droplet.json` - -```json -{ - "driver": "fluent" -} -``` - -Learn more about [caching here](../cache/package.md). - -## Done - -Next time you boot your Droplet, you should see: - -```sh -Database prepared -``` - -You are now ready to [start using Fluent](../fluent/getting-started) with your MySQL database. - - - diff --git a/2.0/docs/node/getting-started.md b/2.0/docs/node/getting-started.md deleted file mode 100644 index 7fe248c0..00000000 --- a/2.0/docs/node/getting-started.md +++ /dev/null @@ -1,126 +0,0 @@ -# Getting Started - -## Why do we have Node? - -The web is very stringy, Swift is very type-safe, this is a major problem when doing web development in Swift. Node is our attempt at providing a solution to this problem. - -## What is Node? - -Node is a data abstraction with an emphasis on being an intermediary between distinct types. For example, json from a client might use node to convert between the JSON and itself. - -## How do I use it? - -Node can be a little different to work with at first if you're familiar with less type-safe languages, let's look at a couple of examples and how we might start using Node in our projects. Most often, we'll be working with Node conversions. - -## NodeInitializable - -NodeInitializable can be read and understood as `An object that can be initialized with a Node`. Let's look at a simple implementation. - -```Swift -struct Person: NodeInitializable { - let name: String - let age: Int - - init(node: Node) throws { - name = try node.get("name") - age = try node.get("age") - } -} -``` - -Now that we have this, we can easily convert abstract data to a Person. Here's how that might look: - -```swift -let person = try Person(node: json) -``` - -> Note: There are some more advanced functionality options for JSON and database Row types in particular, we'll cover that later. - -By conforming our `Person` object to `NodeInitializable`, we can also use more advanced cases such as arrays: - -```swift -let people = try [Person](node: jsonArray) -``` - -## NodeRepresentable - -NodeRepresentable can be read and understood as `An object that can be represented as a Node`. Let's take a look at a simple implementation. We'll stick with the `Person` example above - -```swift -extension Person: NodeRepresentable { - func makeNode(in context: Context) throws -> Node { - var node = Node(context) - try node.set("name", name) - try node.set("age", age) - return node - } -} -``` - -Now that we've done this, we can easily convert our `person` or a collection of `Person` objects into a `Node`. - -```swift -let node = try person.makeNode(in: nil) -``` - -And also for collections, like arrays - -```swift -let node = try [kim, joe, jan].makeNode(in: nil) -``` - -## Context - -Up to this point, we've seen `Context` a lot, but what's it for. When we're serializing or mapping an object, we might have a lot of different situations we're mapping differently for. Maybe one is for the database, one is for the view, one is for JSON, etc. - -If you're using Vapor, we provide a lot of contexts and more native integration options, but here's how one might define their own. - -```swift -import Node - -final class MyContext: Context { -} - -let myContext = MyContext() - -extension Context { - var isMyContext: Bool { - return self is MyContext - } -} -``` - -Now inside our object, we could add special behavior. - -```swift -extension Person: NodeRepresentable { - func makeNode(in context: Context) throws -> Node { - var node = Node(context) - try node.set("name", name) - try node.set("age", age) - if context.isMyContext { - try node.set("special-attribute", special) - } - return node - } -} -``` - -We might call it like this: - -```swift -let specialNode = person.makeNode(in: myContext) -``` - -This is a common usage, but can be adapted for any scenario where we require special metadata to help us properly serialize or map our object. - -## `NodeConvertible` - -NodeConvertible is simply the combination of Representable and Initializable. These objects can be converted easily to and from node. Taking our Person object from earlier, we should be able to do this: - -```swift -// .. -let node = person.makeNode(in: myContext) -let back = try Person(node: node) -print("\(person) went to node and back to \(back)") -``` diff --git a/2.0/docs/node/package.md b/2.0/docs/node/package.md deleted file mode 100644 index 46cdf212..00000000 --- a/2.0/docs/node/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using Node - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import Node -``` - -## Without Vapor - -Node provides a lot of conveniences for any server-side, or client side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```Swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/node.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Node` to access Node's APIs diff --git a/2.0/docs/redis/package.md b/2.0/docs/redis/package.md deleted file mode 100644 index 88287f68..00000000 --- a/2.0/docs/redis/package.md +++ /dev/null @@ -1,43 +0,0 @@ -# Using Redis - -This section outlines how to import the Redis package both with or without a Vapor project. - -## With Vapor - -The easiest way to use Redis with Vapor is to include the Redis provider. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/redis-provider.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -The Redis provider package adds Redis to your project and conforms it to Vapor's `CacheProtocol`. - -Use `import RedisProvider`. - -## Just Redis - -At the core of the Redis provider is a pure Swift Redis client. This package can be used by itself to send raw cache queries to your Redis database. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/redis.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Redis`. diff --git a/2.0/docs/redis/provider.md b/2.0/docs/redis/provider.md deleted file mode 100644 index 82b901a1..00000000 --- a/2.0/docs/redis/provider.md +++ /dev/null @@ -1,76 +0,0 @@ -# Redis Provider - -After you've [added the Redis Provider package](package.md) to your project, setting the provider up in code is easy. - -## Add to Droplet - -First, register the `RedisProvider.Provider` with your Droplet. - -```swift -import Vapor -import RedisProvider - -let config = try Config() -try config.addProvider(RedisProvider.Provider.self) - -let drop = try Droplet(config) - -... -``` - -## Configure Vapor - -Once the provider is added to your Droplet, you can configure Vapor to use Redis for caching. - -`Config/droplet.json` - -```json -{ - "cache": "redis" -} -``` - -!!! seealso - Learn more about configuration files in the [Settings guide](../configs/config.md). - -## Configure Redis - -If you run your application now, you will likely see an error that the Redis configuration file is missing. Let's add that now. - -### Basic - -Here is an example of a simple Redis configuration file. - -`Config/redis.json` -```json -{ - "hostname": "127.0.0.1", - "port": 6379, - "password": "secret", - "database": 2 -} -``` - -Both password and database are optional. - -!!! note - It's a good idea to store the Redis configuration file in the `Config/secrets` folder since it may contain sensitive information. - -### URL - -You can also pass the Redis credentials as a URL. - -`Config/redis.json` -```json -{ - "url": "redis://:secret@127.0.0.1:6379/2" -} -``` - -Both password and database are optional. - - -## Done - -You are now ready to [start using Cache](../cache/package.md) with your Redis database. - diff --git a/2.0/docs/routing/collection.md b/2.0/docs/routing/collection.md deleted file mode 100644 index 349321e2..00000000 --- a/2.0/docs/routing/collection.md +++ /dev/null @@ -1,55 +0,0 @@ -# Route Collections - -Route collections allow multiple routes and route groups to be organized in different files or modules. - -## Example - -Here is an example of a route collection for the `v1` portion of an API. - -```swift -import Vapor -import HTTP -import Routing - -class V1Collection: RouteCollection { - func build(_ builder: RouteBuilder) { - let v1 = builder.grouped("v1") - let users = v1.grouped("users") - let articles = v1.grouped("articles") - - users.get { request in - return "Requested all users." - } - - articles.get(Article.init) { request, article in - return "Requested \(article.name)" - } - } -} -``` - -This class could be placed in any file, and we could add it to our droplet or even another route group. - -```swift -let v1 = V1Collection() -drop.collection(v1) -``` - -The `Droplet` will then be passed to the `build(_:)` method of your route collection and have the various routes added to it. - - -## Empty Initializable - -You can add `EmptyInitializable` to your route collection if it has an empty `init` method. This will allow you to add the route collection via its type name. - -```swift -class V1Collection: RouteCollection, EmptyInitializable { - init() { } - ... -``` - -Now we can add the collection without initializing it. - -```swift -drop.collection(V1Collection.self) -``` diff --git a/2.0/docs/routing/group.md b/2.0/docs/routing/group.md deleted file mode 100644 index 97c6aadd..00000000 --- a/2.0/docs/routing/group.md +++ /dev/null @@ -1,62 +0,0 @@ -# Route Groups - -Grouping routes together makes it easy to add common prefixes, middleware, or hosts to multiple routes. - -Route groups have two different forms: Group and Grouped. - -### Group - -Group (without the "ed" at the end) takes a closure that is passed a `RouteBuilder`. - -```swift -drop.group("v1") { v1 in - v1.get("users") { request in - // get the users - } -} -``` - -### Grouped - -Grouped returns a `RouteBuilder` that you can pass around. - -```swift -let v1 = drop.grouped("v1") -v1.get("users") { request in - // get the users -} -``` - -### Middleware - -You can add middleware to a group of routes. This is especially useful for authentication. - -```swift -drop.group(AuthMiddleware()) { authorized in - authorized.get("token") { request in - // has been authorized - } -} -``` - -### Host - -You can limit the host for a group of routes. - -```swift -drop.group(host: "vapor.codes") { vapor in - vapor.get { request in - // only responds to requests to vapor.codes - } -} -``` - -### Chaining - -Groups can be chained together. - -```swift -drop.grouped(host: "vapor.codes").grouped(AuthMiddleware()).group("v1") { authedSecureV1 in - // add routes here -} -``` diff --git a/2.0/docs/routing/overview.md b/2.0/docs/routing/overview.md deleted file mode 100644 index 6d1124e0..00000000 --- a/2.0/docs/routing/overview.md +++ /dev/null @@ -1,138 +0,0 @@ -# Basic Routing - -Routing is one of the most critical parts of a web framework. The router decides which requests get which responses. - -Vapor has a plethora of functionality for routing including route builders, groups, and collections. In this section, we will look at the basics of routing. - -## Register - -The most basic route includes a method, path, and closure. - -```swift -drop.get("welcome") { request in - return "Hello" -} -``` - -The standard HTTP methods are available including `get`, `post`, `put`, `patch`, `delete`, and `options`. - -```swift -drop.post("form") { request in - return "Submitted with a POST request" -} -``` - -## Nesting - -To nest paths (adding `/`s in the URL), simply add commas. - -```swift -drop.get("foo", "bar", "baz") { request in - return "You requested /foo/bar/baz" -} -``` - -You can also use `/`, but commas are often easier to type and work better with type safe route [parameters](parameters.md). - -## Alternate - -An alternate syntax that accepts a `Method` as the first parameter is also available. - -```swift -drop.add(.trace, "welcome") { request in - return "Hello" -} -``` - -This may be useful if you want to register routes dynamically or use a less common method. - -## Request - -Each route closure is given a single [Request](../http/request.md). This contains all of the data associated with the request that led to your route closure being called. - -## Response Representable - -A route closure can return in three ways: - -- `Response` -- `ResponseRepresentable` -- `throw` - -### Response - -A custom [Response](../http/response.md) can be returned. - -```swift -drop.get("vapor") { request in - return Response(redirect: "http://vapor.codes") -} -``` - -This is useful for creating special responses like redirects. It is also useful for cases where you want to add cookies or other items to the response. - -### Response Representable - -As you have seen in the previous examples, `String`s can be returned in route closures. This is because they conform to [ResponseRepresentable](../http/response-representable.md) - -A lot of types in Vapor conform to this protocol by default: -- String -- Int -- [JSON](../json/package.md) -- [Model](../fluent/model.md) - -```swift -drop.get("json") { request in - var json = JSON() - try json.set("number", 123) - try json.set("text", "unicorns") - try json.set("bool", false) - return json -} -``` - -### Throwing - -If you are unable to return a response, you may `throw` any object that conforms to `Error`. Vapor comes with a default error enum `Abort`. - -```swift -drop.get("404") { request in - throw Abort(.notFound) -} -``` - -You can customize the message of these errors by using `Abort` - -```swift -drop.get("error") { request in - throw Abort(.badRequest, reason: "Sorry 😱") -} -``` - -These errors are caught by default in the `ErrorMiddleware` where they are turned into a JSON response like the following. - -```json -{ - error: true, - message: "" -} -``` - -If you want to override this behavior, remove the `ErrorMiddleware` (key: `"error"`) from the `Droplet`'s middleware and add your own. - -## Fallback - -Fallback routes allow you to match multiple layers of nesting slashes. - -```swift -app.get("anything", "*") { request in - return "Matches anything after /anything" -} -``` - -For example, the above route matches all of the following and more: - -- /anything -- /anything/foo -- /anything/foo/bar -- /anything/foo/bar/baz -- ... diff --git a/2.0/docs/routing/package.md b/2.0/docs/routing/package.md deleted file mode 100644 index e7b0aab4..00000000 --- a/2.0/docs/routing/package.md +++ /dev/null @@ -1,28 +0,0 @@ -# Using Routing - -## With Vapor - -This package is included with Vapor by default, just add: - -```Swift -import Routing -``` - -## Without Vapor - -Routing providers a performant, pure-Swift router to use in any server-side Swift project. To include it in your package, add the following to your `Package.swift` file. - -```Swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/routing.git", majorVersion: 2) - ], - exclude: [ ... ] -) -``` - -Use `import Routing` to access Routing's APIs diff --git a/2.0/docs/routing/parameters.md b/2.0/docs/routing/parameters.md deleted file mode 100644 index 2d2ec06d..00000000 --- a/2.0/docs/routing/parameters.md +++ /dev/null @@ -1,106 +0,0 @@ -# Routing Parameters - -Traditional web frameworks leave room for error in routing by using strings for route parameter names and types. Vapor takes advantage of Swift's closures to provide a safer and more intuitive method for accessing route parameters. - -!!! seealso - Route parameters refer to segments of the URL path (e.g., `/users/:id`). For query parameters (e.g., `?foo=bar`) see [request query parameters](../http/request/#query-parameters). - -## Type Safe - -To create a type safe route simply replace one of the parts of your path with a `Type`. - -```swift -drop.get("users", Int.parameter) { req in - let userId = try req.parameters.next(Int.self) - return "You requested User #\(userId)" -} -``` - -This creates a route that matches `users/:id` where the `:id` is an `Int`. Here's what it would look like using manual route parameters. - -```swift -drop.get("users", ":id") { request in - guard let userId = request.parameters["id"]?.int else { - throw Abort.badRequest - } - - return "You requested User #\(userId)" -} -``` - -Here you can see that type safe routing saves ~3 lines of code and also prevents runtime errors like misspelling `:id`. - -## Parameterizable - -Any type conforming to `Parameterizable` can be used as a parameter. By default, all Vapor [Models](../fluent/model.md) conform. - -Using this, our previous example with users can be further simplified. - -```swift -drop.get("users", User.parameter) { req in - let user = try req.parameters.next(User.self) - - return "You requested \(user.name)" -} -``` - -Here the identifier supplied is automatically used to lookup a user. For example, if `/users/5` is requested, the `User` model will be asked for a user with identifier `5`. If one is found, the request succeeds and the closure is called. If not, a not found error is thrown. - -Here is what this would look like if we looked the model up manually. - -```swift -drop.get("users", Int.parameter) { req in - let userId = try req.parameters.next(Int.self) - guard let user = try User.find(userId) else { - throw Abort.notFound - } - - return "You requested \(user.name)" -} -``` - -### Protocol - -You can conform your own types to `Parameterizable`. - -```swift -import Routing - -extension Foo: Parameterizable { - /// This unique slug is used to identify - /// the parameter in the router - static var uniqueSlug: String { - return "foo" - } - - - static func make(for parameter: String) throws -> Foo { - /// custom lookup logic here - /// the parameter string contains the information - /// parsed from the URL. - ... - } -} -``` - -Now you can use this type for type safe routing. - -```swift -drop.get("users", "nickname", Foo.parameter) { req in - let foo = try req.parameters.next(Foo.self) - ... -} -``` - -### Groups - -Type-safe parameters also work with [groups](group.md). - -```swift -let userGroup = drop.grouped("users", User.parameter) -userGroup.get("messages") { req in - let user = try req.parameters.next(User.self) - - ... -} -``` diff --git a/2.0/docs/sessions/package.md b/2.0/docs/sessions/package.md deleted file mode 100644 index 67e2e210..00000000 --- a/2.0/docs/sessions/package.md +++ /dev/null @@ -1,7 +0,0 @@ -# Using Sessions - -This module is a part of Vapor, just add: - -```Swift -import Sessions -``` diff --git a/2.0/docs/sessions/sessions.md b/2.0/docs/sessions/sessions.md deleted file mode 100644 index a1bb06e2..00000000 --- a/2.0/docs/sessions/sessions.md +++ /dev/null @@ -1,84 +0,0 @@ -# Sessions - -Sessions help you store information about a user between requests. As long as the client supports cookies, sessions are easy to create. - -## Middleware - -Enable sessions on your `Droplet` by adding `"sessions"` to your middleware array. - -`Config/droplet.json` -```json -{ - ..., - "middleware": [ - ..., - "sessions", - ..., - ], - ..., -} -``` - -By default, the memory sessions driver will be used. You can change this with the `droplet.sessions` key. - - -`Config/droplet.json` -```json -{ - ..., - "sessions": "memory", - ..., -} -``` - -## Request - -After `SessionMiddleware` has been enabled, you can access the `req.assertSession()` method to get access to session. - -```swift -import Sessions - -let session = try req.assertSession() -print(session.data) -``` - -## Example - -Let's create an example that remembers the user's name. - -### Store - -```swift -drop.post("remember") { req in - guard let name = req.data["name"]?.string else { - throw Abort(.badRequest) - } - - let session = try req.assertSession() - try session.data.set("name", name) - - return "Remebered name." -} -``` - -On `POST /remember`, fetch a `name` from the request input, then store this name into the session data. - -### Fetch - -On `GET /remember`, fetch the `name` from the session data and return it. - -```swift -drop.get("remember") { req in - let session = try req.assertSession() - - guard let name = session.data["name"]?.string else { - return throw Abort(.badRequest, reason: "Please POST the name first.") - } - - return name -} -``` - -## Cookie - -The session will be stored using the `vapor-session` cookie. diff --git a/2.0/docs/testing/basic.md b/2.0/docs/testing/basic.md deleted file mode 100644 index c63e1edb..00000000 --- a/2.0/docs/testing/basic.md +++ /dev/null @@ -1,93 +0,0 @@ -!!! warning - This section may contain outdated information. - -# Basic Testing - -Testing is a critical part of any software application, and Vapor apps should be no different. In this documentation, we'll cover some of the basic setup required to be able to test against our `Droplet`. - -## Displacing Droplet Creation Logic - -Up to this point, a lot of our documentation has centered around putting our `Droplet` creation logic in `main.swift`. Unfortunately, when testing against our application, this code becomes largely inaccessible. The first thing we'll need to do is break this out into the `AppLogic` module. - -Here's an example of my setup file. I name mine `Droplet+Setup.swift`. Here's how it might look: - -```swift -import Vapor - -public func load(_ drop: Droplet) throws { - drop.preparations.append(Todo.self) - - drop.get { _ in return "put my droplet's logic in this `load` function" } - - drop.post("form") { req in - ... - return Response(body: "Successfully posted form.") - } - - // etc. -} -``` - -> [WARNING] Do **not** call `run()` anywhere within the `load` function as `run()` is a blocking call. - -## Updated `main.swift` - -Now that we've abstracted our loading logic, we'll need to update our `main.swift` **in the `App` module** to reflect those changes. Here's how it should look after: - -```swift -let drop = Droplet(...) -try load(drop) -drop.run() -``` - -> The reason we still initialize `Droplet` outside of the scope of `load` is so that we can have the option to initialize differently for testing. We'll cover that soon. - -## Testable Droplet - -The first thing we'll do is in my testing target, add a file called `Droplet+Test.swift`. It will look like this: - -```swift -@testable import Vapor - -func makeTestDroplet() throws -> Droplet { - let drop = Droplet(arguments: ["dummy/path/", "prepare"], ...) - try load(drop) - try drop.runCommands() - return drop -} -``` - -This looks a lot like our initializer in `main.swift`, but there are 3 very key differences. - -### Droplet(arguments: ["dummy/path/", "prepare"], ... - -The `arguments:` parameter in our `Droplet` creation. This is rarely used except for advanced situations, but we'll use it here in testing to ensure that our `Droplet` doesn't try to automatically `serve` and block our thread. You can use arguments besides `"prepare"`, but unless you're doing something specific for an advanced situation, these arguments should suffice. - -### try drop.runCommands() - -You'll notice here that we're calling `runCommands()` instead of `run()`. This allows the `Droplet` to do all the setup it would normally do before booting without actually binding to a socket or exiting. - -### `@testable import Vapor` - -We'll need to import the testable compilation of Vapor to access the `runCommands` function. This is currently not public as a protection against accidental bugs in live apps. - -## Test Our Droplet - -Now that all of this has been created, we're ready to start testing our application's `Droplet`. Here's how a really basic test might look: - -```swift -@testable import AppLogic - -func testEndpoint() throws { - let drop = try makeTestDroplet() - let request = ... - let expectedBody = ... - - let response = try drop.respond(to: request) - XCTAssertEqual(expectedBody, response.body.bytes) -} -``` - -Notice that now you can use `CMD-U` to run your tests in Xcode with in-line results. In addition, you can run `vapor test` to test your code from the command line. If you choose to use `swift build` instead and you are using MySQL in your app, make sure you add the correct build flags to the call. - -Good luck, and happy testing! diff --git a/2.0/docs/testing/modules.md b/2.0/docs/testing/modules.md deleted file mode 100644 index 7704b983..00000000 --- a/2.0/docs/testing/modules.md +++ /dev/null @@ -1,39 +0,0 @@ -!!! warning - This section may contain outdated information. - -# Using Multiple Modules For Testing - -Testing a Vapor app gets tricky, and requires some maneuvering of your app targets. - -> [WARNING] Technically this is only necessary if you plan to run your tests on Linux. You can keep your tests in the same module if you want to only run your tests from the command line using `vapor test` - -## **Step 1:** Update Package.swift - -To start, you need to split up your Vapor project into a target called `App`, and a target called `AppLogic`. The App module will only include a `main.swift`, and your `AppLogic` will contain the actual logic for the app. - -```swift -import PackageDescription - -let package = Package( - name: “ProjectName”, - targets: [ - Target(name: "App", dependencies: ["AppLogic"]) - ], - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 1, minor: 3) - ], - exclude: [ - "Config", - "Database", - "Localization", - "Public", - "Resources" - ] -) -``` - -## **Step 2:** Update Tests Folder - -Make sure that your tests folder has a file called `LinuxMain.swift` and a folder called `AppLogicTests`. In your `AppLogicTests`, you can add your testing files like `UserTests.swift`. - -As always, make sure that you regenerate with `vapor xcode -y` diff --git a/2.0/docs/validation/overview.md b/2.0/docs/validation/overview.md deleted file mode 100644 index 91ce128d..00000000 --- a/2.0/docs/validation/overview.md +++ /dev/null @@ -1,108 +0,0 @@ -!!! error "Work in Progress" - The subject of this page is Work in Progress and is not recommended for Production use. - -!!! error "Outdated" - This page contains outdated information. - -# Validation - -Vapor provides a few different ways to validate data coming into your application. Let's start by looking at the most common. - -## Common Usage - -Several useful convenience validators are included by default. You can use these to validate data coming into your application, or combine them and create your own. - -Let's look at the most common way to validate data. - -```swift -class Employee { - var email: Valid - var name: Valid - - init(request: Request) throws { - name = try request.data["name"].validated() - email = try request.data["email"].validated() - } -} -``` - -Here we have a typical Employee model with an `email` and `name` property. By declaring both of these properties as `Valid<>`, you are ensuring that these properties can only ever contain valid data. The Swift type checking system will prevent anything that does not pass validation from being stored. - -To store something in a `Valid<>` property, you must use the `.validated()` method. This is available for any data returned by `request.data`. - -`Email` is a real `validator` included with Vapor, but `Name` is not. Let's take a look at how you can create a Validator. - -```swift -Valid -Valid -Valid> -Valid> -Valid> -Valid> -Valid> -``` - -## Validators vs. ValidationSuites - -Validators, like `Count` or `Contains` can have multiple configurations. For example: - -```swift -let name: Valid> = try "Vapor".validated(by: Count.max(5)) -``` - -Here we are validating that the `String` is at most 5 characters long. The type of `Valid` tells us that the string has been validated to be a certain count, but it does not tell us exactly what that count was. The string could have been validated to be less than three characters or more than one million. - -Because of this, `Validators` themselves are not as type safe as some applications might desire. `ValidationSuites` fix this. They combine multiple `Validators` and/or `ValidationSuites` together to represent exactly what type of data should be considered valid. - -## Custom Validator - -Here is how to create a custom `ValidationSuite`. - -```swift -class Name: ValidationSuite { - static func validate(input value: String) throws { - let evaluation = OnlyAlphanumeric.self - && Count.min(5) - && Count.max(20) - - try evaluation.validate(input: value) - } -} -``` - -You only have to implement one method. In this method, use any other validators or logic to create your custom validator. Here we are defining a `Name` as only accepting alphanumeric Strings that are between 5 and 20 characters. - -Now we can be sure that anything of type `Valid` follows these rules. - -## Combining Validators - -In the `Name` validator, you can see that `&&` is being used to combine validators. You can use `&&` as well as `||` to combine any validator as you would boolean values with an `if` statement. - -You can also use `!` to invert the validator. - -```swift -let symbols = input.validated(by: !OnlyAlphanumeric.self) -``` - -## Testing Validity - -While `validated() throw` is the most common method for validating, there are two others. - -```swift -let passed = input.passes(Count.min(5)) -let valid = try input.tested(Count.min(5)) -``` - -`passes()` returns a boolean indicating whether or not the test passed. `tested()` will throw if the validation does not pass. But unlike `validated()` which returns a `Valid<>` type, `tested()` returns the original type of the item it was called on. - -## Validation Failures - -Vapor will automatically catch validation failures in the `ValidationMiddleware`. But you can catch them on your own, or customize responses for certain types of failures. - -```swift -do { - //validation here -} catch let error as ValidationErrorProtocol { - print(error.message) -} -``` diff --git a/2.0/docs/validation/package.md b/2.0/docs/validation/package.md deleted file mode 100644 index f0773c98..00000000 --- a/2.0/docs/validation/package.md +++ /dev/null @@ -1,49 +0,0 @@ -!!! error "Work in Progress" - The subject of this page is Work in Progress and is not recommended for Production use. - -!!! error "Outdated" - This page contains outdated information. - -# Using Validation - -This section outlines how to import the Validation package both with or without a Vapor project. - -## With Vapor - -The easiest way to use Validation with Vapor is to include the Validation provider. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/validation-provider.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -The Validation provider package adds Validation to your project and adds some additional, vapor-specific conveniences like validation middleware. - -Using `import ValidationProvider` will import the Validation middleware and the Validation module. - -## Just Validation - -At the core of the Validation provider is a Validation module. - -```swift -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .Package(url: "https://github.com/vapor/validation.git", majorVersion: 1) - ], - exclude: [ ... ] -) -``` - -Use `import Validation` to access the core validation class. diff --git a/2.0/docs/vapor/commands.md b/2.0/docs/vapor/commands.md deleted file mode 100644 index 7bd56b81..00000000 --- a/2.0/docs/vapor/commands.md +++ /dev/null @@ -1,78 +0,0 @@ -In addition to the commands provided by Vapor (like `serve`, and `routes`) you can build your own custom commands. - -!!! note - Commands are a great way to script your application with CRON jobs. - -## Example -To make a custom console command we must first create a new `.swift` file, import `Vapor` and `Console`, and implement the `Command` protocol. - -```swift -import Vapor -import Console - -final class MyCustomCommand: Command { - public let id = "my-command" - public let help = ["This command does things, like foo, and bar."] - public let console: ConsoleProtocol - - public init(console: ConsoleProtocol) { - self.console = console - } - - public func run(arguments: [String]) throws { - console.print("running custom command...") - } -} -``` - -- The **id** property is the string you will type in the console to access the command. `.build/debug/App command` will run the Custom Command. -- The **help** property is the help message that will give your custom command's users some idea of how to access it. -- The **console** property is the object passed to your custom command that adheres to the console protocol, allowing manipulation of the console. -- The **run** method is where you put the logic relating to your command. - -## Config Initializable - -To make our command configurable, conform it to `ConfigInitializable` - -```swift -extension MyCustomCommand: ConfigInitializable { - public convenience init(config: Config) throws { - let console = try config.resolveConsole() - self.init(console: console) - } -} -``` - -## Add to Droplet - -After we work our magic in the Custom Command file, we switch over to our `main.swift` file and add the custom command to the Droplet like so. - -```swift -import Vapor - -let config = try Config() -try config.addConfigurable(command: MyCustomCommand.init, name: "my-command") - -let drop = try Droplet(config) -``` - -This allows Vapor access to our custom command and lets it know to display it in the `--help` section of the program. - -### Configure - -Now that you've made the command configurable, just add it to the `commands` array in your `Config/droplet.json` file. - -`Config/droplet.json` -```json -{ - ..., - "commands": ["my-command"], - ..., -} -``` - -After compiling the application we can run our custom command like so. - -``` -vapor run my-command -``` diff --git a/2.0/docs/vapor/controllers.md b/2.0/docs/vapor/controllers.md deleted file mode 100644 index 11a2d69d..00000000 --- a/2.0/docs/vapor/controllers.md +++ /dev/null @@ -1,152 +0,0 @@ -# Controllers - -Controllers help you organize related functionality into a single place. They can also be used to create RESTful resources. - -## Basic - -A basic controller looks like the following: - -```swift -import Vapor -import HTTP - -final class HelloController { - func sayHello(_ req: Request) throws -> ResponseRepresentable { - guard let name = req.data["name"]?.string else { - throw Abort(.badRequest) - } - - return "Hello, \(name)" - } -} -``` - -Simple controllers don't need to conform to any protocols. You are free to design them however you see fit. - -### Registering - -The only required structure is the signature of each method in the controller. In order to register this method into the router, it must have a signature like `(Request) throws -> ResponseRepresentable`. `Request` and `ResponseRepresentable` are made available by importing the `HTTP` module. - -```swift -import Vapor -let drop = try Droplet() - -let hc = HelloController() -drop.get("hello", handler: hc.sayHello) -``` - -Since the signature of our `sayHello` method matches the signature of the closure for the `drop.get` method, we can pass it directly. If you want to test it locally simply open [http://localhost:8080/hello?name=John](http://localhost:8080/hello?name=John). - -### Type Safe - -You can also use controller methods with type-safe routing. - -```swift -final class HelloController { - ... - - func sayHelloAlternate(_ req: Request) throws -> ResponseRepresentable { - let name: String = try req.parameters.next(String.self) - return "Hello, \(name)" - } -} -``` - -We add a new method called `sayHelloAlternate` to the `HelloController` that fetches a `String` from the request's parameters. - -```swift -let hc = HelloController() -drop.get("hello", String.parameter, handler: hc.sayHelloAlternate) -``` - -Since `drop.get` accepts a signature `(Request) throws -> ResponseRepresentable`, our method can now be used as the closure for this route. In this case to test it locally open [http://localhost:8080/hello/John](http://localhost:8080/hello/John). - -!!! note - Read more about type safe routing in the [Routing Parameters](https://docs.vapor.codes/2.0/routing/parameters/#type-safe) section. - -## Resources - -Controllers that conform to `ResourceRepresentable` can be easily registered into a router as a RESTful resource. Let's look at an example of a `UserController`. - -```swift -final class UserController { - func index(_ req: Request) throws -> ResponseRepresentable { - return try User.all().makeJSON() - } - - func show(_ req: Request) throws -> ResponseRepresentable { - let user = try req.parameters.next(User.self) - return user - } -} -``` - -Here is a typical user controller with an `index` and `show` route. Indexing returns a JSON list of all users and showing returns a JSON representation of a single user. - -We _could_ register the controller like so: - -```swift -let users = UserController() -drop.get("users", handler: users.index) -drop.get("users", User.self, handler: users.show) -``` - -But `ResourceRepresentable` makes this standard RESTful structure easy. - -```swift -extension UserController: ResourceRepresentable { - func makeResource() -> Resource { - return Resource( - index: index, - show: show - ) - } -} -``` - -Conforming `UserController` to `ResourceRepresentable` requires that the signatures of -the `index` and `show` methods match what the `Resource` is expecting. - - -Now that `UserController` conforms to `ResourceRepresentable`, registering the routes is easy. - -```swift -let users = UserController() -drop.resource("users", users) -``` - - `drop.resource` will take care of registering only the routes that have been supplied by the call to `makeResource()`. In this case, only the `index` and `show` routes will be supplied. - -### Actions - -Below is a table describing all of the actions available. - -| Action | Method | Path | Note | -|---------------|---------|-----------------|------------------------------------------------------------------------------------------| -| index | GET | /users | Returns all users, optionally filtered by the request data. | -| store | POST | /users | Creates a new user from the request data. | -| show | GET | /users/:id | Returns the user with the ID supplied in the path. | -| replace | PUT | /users/:id | Updates the specified user, setting any fields not present in the request data to `nil`. | -| update | PATCH | /users/:id | Updates the specified user, only modifying fields present in the request data. | -| destroy | DELETE | /users/:id | Deletes the specified user. | -| clear | DELETE | /users | Deletes all users, optionally filtered by the request data. | -| create | GET | /users/create | Displays a form for creating a new user. | -| edit | GET | /users/:id/edit | Displays a form for editing the specified user. | -| aboutItem | OPTIONS | /users/:id | Meta action. Displays information about which actions are supported. | -| aboutMultiple | OPTIONS | /users | Meta action. Displays information about which actions are supported. | - -!!! note - The `aboutItem` and `aboutMultiple` meta actions are implemented automatically if not overridden. - -!!! tip - The difference between `replace` and `update` is subtle but important: - If a field does not exist in the request data (for example, the user's age is missing), - `update` should simply not update that field where as `replace` should set it to `nil`. - If required data is missing from a `replace` request, an error should be thrown. - -## Folder - -Controllers can go anywhere in your application, but they are most often stored in the `App/Controllers/` directory. - -!!! tip - If you are building a large application, you may want to create your controllers in a separate module. This will allow you to perform unit tests on your controllers. diff --git a/2.0/docs/vapor/droplet.md b/2.0/docs/vapor/droplet.md deleted file mode 100644 index 4cd498fd..00000000 --- a/2.0/docs/vapor/droplet.md +++ /dev/null @@ -1,211 +0,0 @@ -# Droplet - -The `Droplet` is a service container that gives you access to many of Vapor's facilities. It is responsible for registering routes, starting the server, appending middleware, and more. - -!!! tip - Normally applications will only have one Droplet. However, for advanced use cases, it is possible to create more than one. - -## Initialization - -As you have probably already seen, the only thing required to create an instance of `Droplet` is to import Vapor. - -```swift -import Vapor - -let drop = try Droplet() - -// your magic here - -try drop.run() -``` - -Creation of the `Droplet` normally happens in the `main.swift` file. - -!!! note - For the sake of simplicity, most of the documentations sample code uses just the `main.swift` file. You can read more about packages and modules in the Swift Package Manager [conceptual overview](https://swift.org/package-manager/). - -## Environment -The `environment` is accessible via the config of the droplet. -It contains the current environment your application is running in. Usually development, testing, or production. - -```swift -if drop.config.environment == .production { - ... -} -``` - -The environment affects [Config](../configs/config.md) and [Logging](log.md). The environment is `development` by default. To change it, pass the `--env=` flag as an argument. - -```sh -vapor run serve --env=production -``` - -If you are in Xcode, you can pass arguments through the scheme editor. - -!!! warning - Debug logs can reduce the number of requests your application can handle per second. Enable production mode to silence non-critical logs. - -## Config Directory - -The `workDir` property contains a path to the current working directory of the application. Vapor uses this property to find the folders related to your project, such as `Resources`, `Public`, and `Config`. - -```swift -print(drop.config.workDir) // /var/www/my-project/ -``` - -Vapor automatically determines the working directory in most situations. However, you may need to manually set it for advanced use cases. - -You can override the working directory through the `Droplet`'s initializer, or by passing the `--workdir` argument. - -```sh -vapor run serve --workdir="/var/www/my-project" -``` - -## Modifying Properties - -The `Droplet`'s properties can be changed programmatically or through configuration. - -### Programmatic - -Properties on the `Droplet` are constant and can be overridden through the init method. - -```swift -let drop = try Droplet(server: MyServerType.self) -``` - -Here the type of server the `Droplet` uses is changed to a custom type. When the `Droplet` is run, this custom server type will be booted instead of the default server. - -!!! warning - Using the init method manually can override configured properties. - -### Configurable - -If you want to modify a property of the `Droplet` only in certain cases, you can use `addConfigurable`. Say for example you want to email error logs to yourself in production, but you don't want to spam your inbox while developing. - -```swift -let config = try Config() -config.addConfigurable(log: MyEmailLogger.init, name: "email") - -let drop = Droplet(config) - -``` - -The `Droplet` will continue to use the default logger until you modify the `Config/droplet.json` file to point to your email logger. If this is done in `Config/production/droplet.json`, then your logger will only be used in production. - -```json -{ - "log": "email" -} -``` - -#### Supported Properties - -| Property | Type | `droplet.json` key | Config Initializable | -|------------|---------------------|----------------------------|----------------------| -| server | ServerProtocol.Type | server | no | -| client | ClientProtocol.Type | client | no | -| log | LogProtocol | log | yes | -| hash | HashProtocol | hash | yes | -| cipher | CipherProtocol | cipher | yes | -| middleware | Middleware | middleware.[server,client] | no | -| console | ConsoleProtocol | console | yes | -| cache | CacheProtocol | cache | yes | - -#### Example - -Let's create a custom logger to demonstrate Vapor's configurable properties. - -`AllCapsLogger.swift` -```swift -final class AllCapsLogger: LogProtocol { - var enabled: [LogLevel] = [] - func log(_ level: LogLevel, message: String, file: String, function: String, line: Int) { - print(message.uppercased() + "!!!") - } -} - -extension AllCapsLogger: ConfigInitializable { - convenience init(config: Config) throws { - self.init() - } -} -``` - -Now add the logger to the Droplet using the `addConfigurable` method for logs. - -`main.swift` -```swift -let config = try Config() -config.addConfigurable(log: AllCapsLogger.init, name: "all-caps") - -let drop = try Droplet(config) - -``` - -Whenever the `"log"` property is set to `"all-caps"` in the `droplet.json`, our new logger will be used. - -`Config/development/droplet.json` -```json -{ - "log": "all-caps" -} -``` - -Here we are setting our logger only in the `development` environment. All other environments will use Vapor's default logger. - -#### Config Initializable - -For an added layer of convenience, you can allow your custom types to be initialized from configuration files. - -In our previous example, we initialized an `AllCapsLogger` before adding it to the Droplet. - -Let's say we want to allow our project to configure how many exclamation points get added with each log message. - -`AllCapsLogger.swift` -```swift -final class AllCapsLogger: LogProtocol { - var enabled: [LogLevel] = [] - let exclamationCount: Int - - init(exclamationCount: Int) { - self.exclamationCount = exclamationCount - } - - func log(_ level: LogLevel, message: String, file: String, function: String, line: Int) { - print(message.uppercased() + String(repeating: "!", count: exclamationCount)) - } -} - -extension AllCapsLogger: ConfigInitializable { - convenience init(config: Config) throws { - let count = config["allCaps", "exclamationCount"]?.int ?? 3 - self.init(exclamationCount: count) - } -} -``` - -!!! note - The first parameter to `config` is the name of the file. - -Now that we have conformed our logger to `ConfigInitializable`, we can pass just the type name to `addConfigurable`. - - -`main.swift` -```swift -let config = try Config() -config.addConfigurable(log: AllCapsLogger.init, name: "all-caps") - -let drop = try Droplet(config) - -``` - -Now if you add a file named `allCaps.json` to the `Config` folder, you can configure the logger. - -`allCaps.json` -```json -{ - "exclamationCount": 5 -} -``` - -With this configurable abstraction, you can easily change how your application functions in different environments without needing to hard code these values into your source code. diff --git a/2.0/docs/vapor/folder-structure.md b/2.0/docs/vapor/folder-structure.md deleted file mode 100644 index 591c0ad1..00000000 --- a/2.0/docs/vapor/folder-structure.md +++ /dev/null @@ -1,85 +0,0 @@ -# Folder Structure - -The first step to creating an awesome application is knowing where things are. If you created your project using the [Toolbox](../getting-started/toolbox.md) or from a template, you will already have the folder structure created. - -If you are making a Vapor application from scratch, this will show you exactly how to set it up. - -## Minimum Folder Structure - -We recommend putting all of your Swift code inside of the `App/` folder. This will allow you to create subfolders in `App/` to organize your models and resources. - -This works best with the Swift package manager's restrictions on how packages should be structured. - -``` -. -├── App -│ └── main.swift -├── Public -└── Package.swift -``` - -The `Public` folder is where all publicly accessible files should go. This folder will be automatically checked every time a URL is requested that is not found in your routes. - -!!! note - The `FileMiddleware` is responsible for accessing files from the `Public` folder. - -## Models - -The `Models` folder is a recommendation of where you can put your database and other models. - -``` -. -├── App -. └── Models -. └── User.swift -``` - -## Controllers - -The `Controllers` folder is a recommendation of where you can put your route controllers. - -``` -. -├── App -. └── Controllers -. └── UserController.swift -``` - -## Views - -The `Views` folder in `Resources` is where Vapor will look when you render views. - -``` -. -├── App -└── Resources - └── Views - └── user.html -``` - -The following code would load the `user.html` file. - -```swift -drop.view.make("user.html") -``` - -## Config - -Vapor has a sophisticated configuration system that involves a hierarchy of configuration importance. - -``` -. -├── App -└── Config - └── app.json // default app.json - └── development - └── app.json // overrides app.json when in development environment - └── production - └── app.json // overrides app.json when in production environment - └── secrets - └── app.json // overrides app.json in all environments, ignored by git -``` - -`.json` files are structured in the `Config` folder as shown above. The configuration will be applied dependant on where the `.json` file exists in the hierarchy. Learn more in [Config](../configs/config.md). - -Learn about changing environments (the `--env=` flag) in the [Droplet](droplet.md) section. diff --git a/2.0/docs/vapor/hash.md b/2.0/docs/vapor/hash.md deleted file mode 100644 index f39f42b1..00000000 --- a/2.0/docs/vapor/hash.md +++ /dev/null @@ -1,175 +0,0 @@ -# Hash - -Hashing is a one way method of converting arbitrary data into a fixed size format. Unlike ciphers, data that is hashed cannot be retrieved from the resulting digest. Hashes can be used to create keys, file identifiers, or store credentials. - -Hash function diagram - -> Hash function diagram from [Wikipedia](https://en.wikipedia.org/wiki/Hash_function). - -!!! warning - Avoid storing password hashes if possible. If you must, please make sure to research the state of the art before continuing. - -## Make - -To hash a string, use the `hash` property on `Droplet`. - -```swift -let digest = try drop.hash.make("vapor") -print(digest.makeString()) -``` - -### Checking - -Some hashing algorithms create different hash digests for the same message. Because of this, it is necessary to check your hashes using the `check` method. - -```swift -let matches = try drop.hash.check("vapor", matchesHash: digest) -``` - -## CryptoHasher - -By default, Vapor uses a SHA-256 hasher. You can change this in the configuration files or by giving the `Droplet` a different hasher. - -### Configuration - -`Config/droplet.json` -```json -{ - ..., - "hash": "crypto", - ... -} -``` - -`Config/crypto.json` -```json -{ - "hash": { - "method": "sha256", - "encoding": "hex", - "key": "password" - }, - ... -} -``` - -#### Encoding - -The CryptoHasher supports three methods of encoding. - -- `hex` -- `base64` -- `plain` - -#### Key - -Supplying a key will cause the hasher to produce _keyed_ hashes using HMAC. Some hashers require a key. - -#### Supported - -| Name | Method | Requires Key | -|--------------|-------------|--------------| -| SHA-1 | sha1 | no | -| SHA-224 | sha224 | no | -| SHA-256 | sha256 | no | -| SHA-384 | sha384 | no | -| SHA-512 | sha512 | no | -| MD4 | md4 | no | -| MD5 | md5 | no | -| RIPEMD-160 | ripemd160 | no | -| Whirlpool | whirlpool | yes | -| Streebog-256 | streebog256 | yes | -| Streebog-512 | streebog512 | yes | -| GostR341194 | gostr341194 | yes | - -### Manual - -Hashers can be swapped without the use of configuration files. - -#### Hash - -```swift -let hash = CryptoHasher( - hash: .sha256, - encoding: .hex -) - -let drop = try Droplet(hash: hash) - -``` - -#### HMAC - -```swift -let hash = CryptoHasher( - hmac: .sha256, - encoding: .hex, - key: "password".makeBytes() -) - -let drop = try Droplet(hash: hash) -``` - -## BCryptHasher - -BCrypt is a password hashing function that automatically incorporates salts and offers a configurable cost. The cost can be used to increase the computation required to generate a hash. - -!!! seealso - Learn more about [key stretching](https://en.wikipedia.org/wiki/Key_stretching) on Wikipedia. - -### Configuration - -To use the BCryptHasher change the `"hash"` key in the `droplet.json` configuration file. - -`Config/droplet.json` -```json -{ - ..., - "hash": "bcrypt", - ... -} -``` - -To configure the cost, add a `bcrypt.json` file. - -`Config/bcrypt.json` -```json -{ - "cost": 8 -} -``` - -!!! tip - You can use different BCrypt costs for production and development modes to keep password hashing fast while working on a project. - -### Manual - -You can manually assign a `BCryptHasher` to `drop.hash`. - -```swift -let hash = BCryptHasher(cost: 8) - -let drop = try Droplet(hash: hash) -``` - -## Advanced - -### Custom - -You can also create your own hasher. You just need to conform to the `Hash` protocol. - -```swift -/// Creates hash digests -public protocol HashProtocol { - /// Given a message, this method - /// returns a hashed digest of that message. - func make(_ message: Bytes) throws -> Bytes - - /// Checks whether a given digest was created - /// by the supplied message. - /// - /// Returns true if the digest was created - /// by the supplied message, false otherwise. - func check(_ message: Bytes, matchesHash: Bytes) throws -> Bool -} -``` diff --git a/2.0/docs/vapor/log.md b/2.0/docs/vapor/log.md deleted file mode 100644 index 20828adb..00000000 --- a/2.0/docs/vapor/log.md +++ /dev/null @@ -1,37 +0,0 @@ -Log information using `drop.log`. - -```swift -drop.log.info("Informational log") -``` - -## Types - -Below are the following methods you can call on the log protocol. Only `error` and `fatal` will be shown in `production` mode. - -| Method | Production | -|---------|------------| -| info | No | -| warning | No | -| verbose | No | -| debug | No | -| error | Yes | -| fatal | Yes | - -## Protocol - -Create your own logger by conforming to `LogProtocol`. - -```swift -/// Logger protocol. Custom loggers must conform -/// to this protocol -public protocol LogProtocol: class { - /// Enabled log levels. Only levels in this - /// array should be logged. - var enabled: [LogLevel] { get set } - - /// Log the given message at the passed filter level. - /// file, function and line of the logging call - /// are automatically injected in the convenience function. - func log(_ level: LogLevel, message: String, file: String, function: String, line: Int) -} -``` \ No newline at end of file diff --git a/2.0/docs/vapor/provider.md b/2.0/docs/vapor/provider.md deleted file mode 100644 index 245d383b..00000000 --- a/2.0/docs/vapor/provider.md +++ /dev/null @@ -1,123 +0,0 @@ -# Provider - -The `Provider` protocol creates a simple and predictable way for adding functionality and third party packages to your Vapor project. - -## Adding a Provider - -Adding a provider to your application takes 2-3 steps. - -### Add Package - -All of Vapor's providers end with the `-provider` syntax. You can see a list of [available providers](https://github.com/vapor?utf8=✓&q=-provider) by searching on our GitHub. - -To add the provider to your package, add it as a dependency in your `Package.swift` file. - -```swift -let package = Package( - name: "MyApp", - dependencies: [ - .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2), - .Package(url: "https://github.com/vapor/mysql-provider.git", majorVersion: 2) - ] -) -``` - -!!! warning - Always run `vapor update` or `vapor clean` after editing your `Package.swift` file. - -### Import - -Once the provider has been added, you can import it using `import VaporFoo` where `Foo` is the name of the provider. - -Here is what importing the MySQL provider looks like: - -```swift -import Vapor -import MySQLProvider -``` - -### Add to Droplet - -Every provider comes with a class named `Provider`. Add this class to your Droplet using the `addProvider` method. - -```swift -let config = try Config() -try config.addProvider(MySQLProvider.Provider.self) - -let drop = try Droplet(config) - -// ... - -drop.run() -``` - -### Configuration - -Some drivers may require a configuration file. For example, `MySQLProvider` requires a `Config/mysql.json` file like the following: - -```json -{ - "hostname": "localhost", - "user": "root", - "password": "", - "database": "vapor" -} -``` - -You will receive an error during the `Droplet`'s initialization if a configuration file is required. - -!!! tip - Storing sensitive configuration files (ones that contain passwords) in the `Config/secrets` folder will prevent them from being tracked by git. - -### Manual - -Some providers can be configured manually by using the Provider's init method. This method can be used instead of configuration files. - -```swift -let mysqlProvider = VaporMySQL.Provider(host: "localhost", user: "root", password: "", database: "vapor") -try config.addProvider(mysqlProvider) -``` - -## Create a Provider - -Creating a provider is easy, you just need to create a package with a class `Provider` that conforms to `Vapor.Provider`. - -### Example - -Here is what a provider for an example `Foo` package would look like. All the provider does is take a message, then print the message when the `Droplet` starts. - -```swift -import Vapor - -public final class Provider: Vapor.Provider { - public let message: String - - public convenience init(config: Config) throws { - guard let message = config["foo", "message"].string else { - throw ConfigError.missing(key: ["message"], file: "foo", desiredType: String.self) - } - - self.init(message: message) - } - - public init(message: String) { - self.message = message - } - - public func boot(_ drop: Droplet) { } - - public func beforeRun(_ drop: Droplet) { - drop.console.info(message) - } -} -``` - -This provider wil require a `Config/foo.json` file that looks like: - -```json -{ - "message": "The message to output" -} -``` - -The provider can also be initialized manually with the `init(message: String)` init. diff --git a/2.0/docs/vapor/views.md b/2.0/docs/vapor/views.md deleted file mode 100644 index 39cebf6d..00000000 --- a/2.0/docs/vapor/views.md +++ /dev/null @@ -1,52 +0,0 @@ -# Views - -Views return HTML data from your application. They can be created from pure HTML documents or passed through renderers such as [Leaf](../leaf/leaf.md). - -## Views Directory - -Views are stored in `Resources/Views`. They are created by calling the `view` method on `Droplet`. - -## HTML - -Returning HTML, or any other non-rendered document, is simple. Just use the path of the document relative to the views directory. - -```swift -drop.get("html") { request in - return try drop.view.make("index.html") -} -``` - -## Templating - -Templated documents like [Leaf](../leaf/leaf.md) can take a `Context`. - -```swift -drop.get("template") { request in - return try drop.view.make("welcome", [ - "message": "Hello, world!" - ]) -} -``` - -This context will be rendered in the view dynamically based on the `ViewRenderer` used. - -## Public Resources - -Any resources that your views need, such as images, styles, and scripts, should be placed in the `Public` folder at the root of your application. - -## View Renderer - -Any class that conforms to `ViewRenderer` can be added to our droplet. - -```swift -import Vapor -import VaporLeaf - -let drop = Droplet() - -drop.view = LeafRenderer(viewsDir: drop.viewsDir) -``` - -## Available Renderers - -[Search GitHub](https://github.com/search?utf8=✓&q=topic%3Avapor-provider+topic%3Aviews&type=Repositories) for Vapor view [providers](provider.md) that can be added to your application. diff --git a/2.0/docs/version/1_5.md b/2.0/docs/version/1_5.md deleted file mode 100644 index b296eea3..00000000 --- a/2.0/docs/version/1_5.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/2.0/docs/version/2_0.md b/2.0/docs/version/2_0.md deleted file mode 100644 index c5451988..00000000 --- a/2.0/docs/version/2_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/2.0/docs/version/3_0.md b/2.0/docs/version/3_0.md deleted file mode 100644 index a22a17f2..00000000 --- a/2.0/docs/version/3_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/2.0/docs/version/4_0.md b/2.0/docs/version/4_0.md deleted file mode 100644 index 1ec89cc1..00000000 --- a/2.0/docs/version/4_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/2.0/docs/version/support.md b/2.0/docs/version/support.md deleted file mode 100644 index ab74774a..00000000 --- a/2.0/docs/version/support.md +++ /dev/null @@ -1,38 +0,0 @@ -# Version Support - -![Support Matrix](https://cloud.githubusercontent.com/assets/1342803/26206235/24c6ebb4-3bdc-11e7-9cea-1405030f20b0.png) - -Vapor 3.0 is currently in beta. - -Vapor 2.0 is currently active. - -Vapor 1.5 is being maintained until November 2017. - -!!! warning - Dashed blocks and lines represent the team's goals and are not yet guaranteed. - Vapor 3's release and the beginning of Vapor 2's maintenance phase have not yet been decided. - -## Core - -All packages in the [Vapor GitHub](https://github.com/vapor) are maintained according to the following rules. - -### Active - -While a version is active, reported security issues and bugs are fixed. - -Additionally, new features and optimizations may be added. If new API is added, the minor version number will be incremented. At no point can existing API be removed or broken during an active version. Semver is strictly followed and tested. - -### Maintenance - -When a new version of Vapor is released, the previous version will enter a maintenance phase which lasts for six months. During this phase, critical security issues and bugs will be fixed. No new features will be added. - -!!! note - Only the latest minor version will be maintained. - -### Unstable - -The master branch is the latest development version of Vapor and is constantly changing. Before a new version of Vapor is released, there may be unstable alpha and beta phases in which you are welcome to test and provide feedback on the changes. - -## Community - -All packages in the [Vapor Community GitHub](https://github.com/vapor-community) are maintained in strict accordance of semver. Violations of semver should be reported as issues to the offending package's GitHub page. diff --git a/2.0/docs/websockets/custom.md b/2.0/docs/websockets/custom.md deleted file mode 100644 index 6dac1b57..00000000 --- a/2.0/docs/websockets/custom.md +++ /dev/null @@ -1,57 +0,0 @@ -!!! warning - This section may contain outdated information. - -# Custom WebSockets - -Below are some examples of WebSockets using the underlying Engine package. - -## Client - -```Swift -import WebSockets - -try WebSocket.connect(to: url) { ws in - print("Connected to \(url)") - - ws.onText = { ws, text in - print("[event] - \(text)") - } - - ws.onClose = { ws, _, _, _ in - print("\n[CLOSED]\n") - } -} -``` - -## Server - -```Swift -import HTTP -import WebSockets -import Transport - -final class MyResponder: Responder { - func respond(to request: Request) throws -> Response { - return try request.upgradeToWebSocket { ws in - print("[ws connected]") - - ws.onText = { ws, text in - print("[ws text] \(text)") - try ws.send("🎙 \(text)") - } - - ws.onClose = { _, code, reason, clean in - print("[ws close] \(clean ? "clean" : "dirty") \(code?.description ?? "") \(reason ?? "")") - } - } - } -} - -let port = 8080 -let server = try Server, Serializer>(port: port) - -print("Connect websocket to http://localhost:\(port)/") -try server.start(responder: MyResponder()) { error in - print("Got server error: \(error)") -} -``` diff --git a/2.0/docs/websockets/droplet.md b/2.0/docs/websockets/droplet.md deleted file mode 100644 index aa57d206..00000000 --- a/2.0/docs/websockets/droplet.md +++ /dev/null @@ -1,58 +0,0 @@ -!!! warning - This section may contain outdated information. - -# Droplet WebSockets - -Creating a WebSocket server with the Droplet is easy. WebSockets work by upgrading an HTTP request to a WebSocket connection. - -Because of this, you should pick a URL for your WebSocket server to reside at. In this case, we use `/ws`. - -```swift -import Vapor - -let drop = Droplet() - -drop.socket("ws") { req, ws in - print("New WebSocket connected: \(ws)") - - // ping the socket to keep it open - try background { - while ws.state == .open { - try? ws.ping() - drop.console.wait(seconds: 10) // every 10 seconds - } - } - - ws.onText = { ws, text in - print("Text received: \(text)") - - // reverse the characters and send back - let rev = String(text.characters.reversed()) - try ws.send(rev) - } - - ws.onClose = { ws, code, reason, clean in - print("Closed.") - } -} - -drop.run() -``` - -To connect with a WebSocket client, you would open a connection to `ws:///ws`. - -Here is an example using JavaScript. - -```swift -var ws = new WebSocket("ws://0.0.0.0:8080/ws") - -ws.onmessage = function(msg) { - console.log(msg) -} - -ws.onopen = function(event) { - ws.send("test") -} -``` - -The above will log `tset` (`test` reversed). diff --git a/2.0/mkdocs.yml b/2.0/mkdocs.yml deleted file mode 100644 index 06ada2fa..00000000 --- a/2.0/mkdocs.yml +++ /dev/null @@ -1,136 +0,0 @@ -site_name: 'Vapor Docs' -copyright: 'Copyright © 2017 Qutheory, LLC' - -nav: -- Overview: 'index.md' -- Getting started: - - 'Install: macOS': 'getting-started/install-on-macos.md' - - 'Install: Ubuntu': 'getting-started/install-on-ubuntu.md' - - 'Toolbox': 'getting-started/toolbox.md' - - 'Hello, World': 'getting-started/hello-world.md' - - 'Manual': 'getting-started/manual.md' - - 'Xcode': 'getting-started/xcode.md' -- Vapor: - - 'Folder Structure': 'vapor/folder-structure.md' - - 'Droplet': 'vapor/droplet.md' - - 'Views': 'vapor/views.md' - - 'Controllers': 'vapor/controllers.md' - - 'Provider': 'vapor/provider.md' - - 'Hash': 'vapor/hash.md' - - 'Log': 'vapor/log.md' - - 'Commands': 'vapor/commands.md' -- Configs: - - 'Config': 'configs/config.md' -- JSON: - - 'Package': 'json/package.md' - - 'Overview': 'json/overview.md' -- Routing: - - 'Package': 'routing/package.md' - - 'Overview': 'routing/overview.md' - - 'Parameters': 'routing/parameters.md' - - 'Group': 'routing/group.md' - - 'Collection': 'routing/collection.md' -- Fluent: - - 'Package': 'fluent/package.md' - - 'Getting Started': 'fluent/getting-started.md' - - 'Model': 'fluent/model.md' - - 'Database': 'fluent/database.md' - - 'Query': 'fluent/query.md' - - 'Relations': 'fluent/relations.md' -- Cache: - - 'Package': 'cache/package.md' - - 'Overview': 'cache/overview.md' -- MySQL: - - 'Package': 'mysql/package.md' - - 'Provider': 'mysql/provider.md' - - 'Driver': 'mysql/driver.md' -- Redis: - - 'Package': 'redis/package.md' - - 'Provider': 'redis/provider.md' -- Auth: - - 'Package': 'auth/package.md' - - 'Provider': 'auth/provider.md' - - 'Getting Started': 'auth/getting-started.md' - - 'Helper': 'auth/helper.md' - - 'Password': 'auth/password.md' - - 'Persist': 'auth/persist.md' - - 'Redirect Middleware': 'auth/redirect-middleware.md' -- JWT: - - 'Package': 'jwt/package.md' - - 'Overview': 'jwt/overview.md' -- Sessions: - - 'Package': 'sessions/package.md' - - 'Sessions': 'sessions/sessions.md' -- HTTP: - - 'Package': 'http/package.md' - - 'Request': 'http/request.md' - - 'Response': 'http/response.md' - - 'Middleware': 'http/middleware.md' - - 'Body': 'http/body.md' - - 'ResponseRepresentable': 'http/response-representable.md' - - 'Responder': 'http/responder.md' - - 'Client': 'http/client.md' - - 'Server': 'http/server.md' - - 'CORS': 'http/cors.md' -- Leaf: - - 'Package': 'leaf/package.md' - - 'Provider': 'leaf/provider.md' - - 'Overview': 'leaf/leaf.md' -- Validation (WIP): - - 'Package': 'validation/package.md' - - 'Overview': 'validation/overview.md' -- Node: - - 'Package': 'node/package.md' - - 'Getting Started': 'node/getting-started.md' -- Core: - - 'Package': 'core/package.md' - - 'Overview': 'core/overview.md' -- Bits: - - 'Package': 'bits/package.md' - - 'Overview': 'bits/overview.md' -- Debugging: - - 'Package': 'debugging/package.md' - - 'Overview': 'debugging/overview.md' -- Deploy: - - 'Cloud': 'deploy/cloud.md' - - 'Nginx': 'deploy/nginx.md' - - 'Apache2': 'deploy/apache2.md' - - 'Supervisor': 'deploy/supervisor.md' -- Version (2.0): - - '1.5': 'version/1_5.md' - - '2.0': 'version/2_0.md' - - '3.0': 'version/3_0.md' - - '4.0': 'version/4_0.md' - - 'Support': 'version/support.md' - -markdown_extensions: - - admonition - - codehilite: - guess_lang: false - - footnotes - - meta - - toc: - permalink: true - -theme: - name: 'material' - palette: - primary: 'grey' - accent: 'grey' - logo: 'images/droplet-white.svg' - -repo_url: http://github.com/vapor/vapor -edit_uri: https://github.com/vapor/documentation/edit/master/2.0/docs/ - -extra: - logo: 'images/droplet-white.svg' - social: - - icon: fontawesome/brands/twitter - link: 'https://twitter.com/@codevapor' - - icon: fontawesome/brands/discord - link: 'http://vapor.team/' - - icon: fontawesome/brands/github - link: 'https://github.com/vapor' - font: - text: 'Roboto Slab' - code: 'Source Code Pro' diff --git a/3.0/docs/async/getting-started.md b/3.0/docs/async/getting-started.md deleted file mode 100644 index 7b3d9d84..00000000 --- a/3.0/docs/async/getting-started.md +++ /dev/null @@ -1,43 +0,0 @@ -# Getting Started with Async - -The Async module is provided as a part of Vapor Core ([vapor/core](https://github.com/vapor/core)). It is a collection of convenience APIs (mostly extensions) built on top of [SwiftNIO](https://github.com/apple/swift-nio). - -!!! tip - You can read more about SwiftNIO's async types (`Future`, `Promise`, `EventLoop`, and more) in its GitHub [README](https://github.com/apple/swift-nio/blob/master/README.md) or its [API Docs](https://apple.github.io/swift-nio/docs/current/NIO/index.html). - -## Usage - -This package is included with Vapor and exported by default. You will have access to all `Async` APIs when you import `Vapor`. - -```swift -import Vapor // implies `import Async` -``` - -### Standalone - -The Async module, part of the larger Vapor Core package, can also be used on its own with any Swift project. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/core.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Async", ... ]) - ] -) -``` - -Use `import Async` to access the APIs. - -## Overview - -Continue to [Async → Overview](overview.md) for an overview of Async's features. - diff --git a/3.0/docs/async/overview.md b/3.0/docs/async/overview.md deleted file mode 100644 index 4d8690d8..00000000 --- a/3.0/docs/async/overview.md +++ /dev/null @@ -1,273 +0,0 @@ -# Async Overview - -You may have noticed some APIs in Vapor expect or return a generic `Future` type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use. - -Promises and futures are related, but distinct, types. Promises are used to _create_ futures. Most of the time, you will be working with futures returned by Vapor's APIs and you will not need to worry about creating promises. - -|type |description |mutability|methods | -|---------|-----------------------------------------------------|----------|----------------------------------------------------| -|`Future` |Reference to an object that may not be available yet.|read-only |`.map(to:_:)` `.flatMap(to:_:)` `do(_:)` `catch(_:)`| -|`Promise` |A promise to provide some object asynchronously. |read/write|`succeed(_:)` `fail(_:)` | - -Futures are an alternative to callback-based asynchronous APIs. Futures can be chained and transformed in ways that simple closures cannot, making them quite powerful. - -## Transforming - -Just like optionals in Swift, futures can be mapped and flat-mapped. These are the most common operations you will perform on futures. - -|method |signature |description| -|---------|------------------|-----| -|`map` |`to: U.Type, _: (T) -> U` |Maps a future value to a different value. -|`flatMap`|`to: U.Type, _: (T) -> Future`|Maps a future value to different _future_ value.| -|`transform` |`to: U` |Maps a future to an already available value.| - -If you look at the method signatures for `map` and `flatMap` on `Optional` and `Array`, you will see that they are very similar to the methods available on `Future`. - -### Map - -The `.map(to:_:)` method allows you to transform the future's value to another value. Because the future's value may not be available yet (it may be the result of an asynchronous task) we must provide a closure to accept the value. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Map the future string to an integer -let futureInt = futureString.map(to: Int.self) { string in - print(string) // The actual String - return Int(string) ?? 0 -} - -/// We now have a future integer -print(futureInt) // Future -``` - -### Flat Map - -The `.flatMap(to:_:)` method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., `Future>`). In other words, it helps you keep your generic futures flat. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Assume we have created an HTTP client -let client: Client = ... - -/// Flat-map the future string to a future response -let futureResponse = futureString.flatMap(to: Response.self) { string in - return client.get(string) // Future -} - -/// We now have a future response -print(futureResponse) // Future -``` - -!!! info - If we instead used `.map(to:_:)` in the above example, we would have ended up with a `Future>`. Yikes! - -### Transform - -The `.transform(_:)` method allows you to modify a future's value, ignoring the existing value. This is especially useful for transforming the results of `Future` where the actual value of the future is not important. - -!!! tip - `Future`, sometimes called a signal, is a future whose sole purpose is to notify you of completion or failure of some async operation. - -```swift -/// Assume we get a void future back from some API -let userDidSave: Future = ... - -/// Transform the void future to an HTTP status -let futureStatus = userDidSave.transform(to: HTTPStatus.ok) -print(futureStatus) // Future -``` - -Even though we have supplied an already-available value to `transform`, this is still a _transformation_. The future will not complete until all previous futures have completed (or failed). - -### Chaining - -The great part about transformations on futures is that they can be chained. This allows you to express many conversions and subtasks easily. - -Let's modify the examples from above to see how we can take advantage of chaining. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Assume we have created an HTTP client -let client: Client = ... - -/// Transform the string to a url, then to a response -let futureResponse = futureString.map(to: URL.self) { string in - guard let url = URL(string: string) else { - throw Abort(.badRequest, reason: "Invalid URL string: \(string)") - } - return url -}.flatMap(to: Response.self) { url in - return client.get(url) -} - -print(futureResponse) // Future -``` - -After the initial call to map, there is a temporary `Future` created. This future is then immediately flat-mapped to a `Future` - -!!! tip - You can `throw` errors inside of map and flat-map closures. This will result in the future failing with the error thrown. - -## Future - -Let's take a look at some other, less commonly used methods on `Future`. - -### Do / Catch - -Similar to Swift's `do` / `catch` syntax, futures have a `do` and `catch` method for awaiting the future's result. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -futureString.do { string in - print(string) // The actual String -}.catch { error in - print(error) // A Swift Error -} -``` - -!!! info - `.do` and `.catch` work together. If you forget `.catch`, the compiler will warn you about an unused result. Don't forget to handle the error case! - -### Always - -You can use `always` to add a callback that will be executed whether the future succeeds or fails. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -futureString.always { - print("The future is complete!") -} -``` - -!!! note - You can add as many callbacks to a future as you want. - -### Wait - -You can use `.wait()` to synchronously wait for the future to be completed. Since a future may fail, this call is throwing. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Block until the string is ready -let string = try futureString.wait() -print(string) /// String -``` - -!!! warning - Do not use this method in route closures or controllers. Read the section about [Blocking](#blocking) for more information. - - -## Promise - -Most of the time, you will be transforming futures returned by calls to Vapor's APIs. However, at some point you may need to create a promise of your own. - -To create a promise, you will need access to an `EventLoop`. All containers in Vapor have an `eventLoop` property that you can use. Most commonly, this will be the current `Request`. - -```swift -/// Create a new promise for some string -let promiseString = req.eventLoop.newPromise(String.self) -print(promiseString) // Promise -print(promiseString.futureResult) // Future - -/// Completes the associated future -promiseString.succeed(result: "Hello") - -/// Fails the associated future -promiseString.fail(error: ...) -``` - -!!! info - A promise can only be completed once. Any subsequent completions will be ignored. - -### Thread Safety - -Promises can be completed (`succeed(result:)` / `fail(error:)`) from any thread. This is why promises require an event-loop to be initialized. Promises ensure that the completion action gets returned to its event-loop for execution. - -## Event Loop - -When your application boots, it will usually create one event loop for each core in the CPU it is running on. Each event loop has exactly one thread. If you are familiar with event loops from Node.js, the ones in Vapor are very similar. The only difference is that Vapor can run multiple event loops in one process since Swift supports multi-threading. - -Each time a client connects to your server, it will be assigned to one of the event loops. From that point on, all communication between the server and that client will happen on that same event loop (and by association, that event loop's thread). - -The event loop is responsible for keeping track of each connected client's state. If there is a request from the client waiting to be read, the event loop trigger a read notification, causing the data to be read. Once the entire request is read, any futures waiting for that request's data will be completed. - -### Worker - -Things that have access to an event loop are called `Workers`. Every container in Vapor is a worker. - -The most common containers you will interact with in Vapor are: - -- `Application` -- `Request` -- `Response` - -You can use the `.eventLoop` property on these containers to gain access to the event loop. - -```swift -print(app.eventLoop) // EventLoop -``` - -There are many methods in Vapor that require the current worker to be passed along. It will usually be labeled like `on: Worker`. If you are in a route closure or a controller, pass the current `Request` or `Response`. If you need a worker while booting your app, use the `Application`. - -### Blocking - -An absolutely critical rule is the following: - -!!! danger - Never make blocking calls directly on an event loop. - -An example of a blocking call would be something like `libc.sleep(_:)`. - -```swift -router.get("hello") { req in - /// Puts the event loop's thread to sleep. - sleep(5) - - /// Returns a simple string once the thread re-awakens. - return "Hello, world!" -} -``` - -`sleep(_:)` is a command that blocks the current thread for the number of seconds supplied. If you do blocking work directly on an event loop, the event loop will be unable to respond to any other clients assigned to it for the duration of the blocking work. In other words, if you do `sleep(5)` on an event loop, all of the other clients connected to that event loop (possibly hundreds or thousands) will be delayed for at least 5 seconds. - -Make sure to run any blocking work in the background. Use promises to notify the event loop when this work is done in a non-blocking way. - -```swift -router.get("hello") { req -> Future in - /// Create a new void promise - let promise = req.eventLoop.newPromise(Void.self) - - /// Dispatch some work to happen on a background thread - DispatchQueue.global().async { - /// Puts the background thread to sleep - /// This will not affect any of the event loops - sleep(5) - - /// When the "blocking work" has completed, - /// complete the promise and its associated future. - promise.succeed() - } - - /// Wait for the future to be completed, - /// then transform the result to a simple String - return promise.futureResult.transform(to: "Hello, world!") -} -``` - -Not all blocking calls will be as obvious as `sleep(_:)`. If you are suspicious that a call you are using may be blocking, research the method itself or ask someone. Chances are if the function is doing disk or network IO and uses a synchronous API (no callbacks or futures) it is blocking. - -!!! info - If doing blocking work is a central part of your application, you should consider using a `BlockingIOThreadPool` to control the number of threads you create to do blocking work. This will help you avoid starving your event loops from CPU time while blocking work is being done. - - diff --git a/3.0/docs/auth/api.md b/3.0/docs/auth/api.md deleted file mode 100644 index 64c5375e..00000000 --- a/3.0/docs/auth/api.md +++ /dev/null @@ -1,211 +0,0 @@ -# API Authentication - -This guide will introduce you to stateless authentication—a method of authentication commonly used for protecting API endpoints. - -## Concept - -In Computer Science (especially web frameworks), the concept of Authentication means verifying the _identity_ of a user. This is not to be confused with Authorization which verifies _privileges_ to a given resource - -This package allows you to implement stateless authorization using the following tools: - -- *`"Authorization"` header*: Used to send credentials in an HTTP request. -- *Middleware*: Detects credentials in request and fetches authenticated user. -- *Model*: Represents an authenticated user and its identifying information. - -### Authorization Header - -This packages makes use of two common authorization header formats: basic and bearer. - -#### Basic - -Basic authorization contains a username and password. They are joined together by a `:` and then base64 encoded. - -A basic authorization header containing the username `Alladin` and password `OpenSesame` would look like this: - -```http -Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l -``` - -Although basic authorization can be used to authenticate each request to your server, most web applications usually create an ephemeral token for this purpose instead. - -#### Bearer - -Bearer authorization simply contains a token. A bearer authorization header containing the token `cn389ncoiwuencr` would look like this: - -```http -Authorization: Bearer cn389ncoiwuencr -``` - -The bearer authorization header is very common in APIs since it can be sent easily with each request and contain an ephemeral token. - - -### Middleware - -The usage of Middleware is critical to this package. If you are not familiar with how Middleware works in Vapor, feel free to brush up by reading [Vapor → Middleware](../vapor/middleware.md). - -Authentication middleware is responsible for reading the credentials from the request and fetching the identifier user. This usually means checking the `"Authorization"` header, parsing the credentials, and doing a database lookup. - -For each model / authentication method you use, you will add one middleware to your application. All of this package's middlewares are composable, meaning you can add multiple middlewares to one route and they will work together. If one middleware fails to authorize a user, it will simply forward the request for the next middleware to try. - -If you would like to ensure that a certain model's authentication has succeeded _before_ running your route, you must add an instance of [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html). - -### Model - -Fluent models are _what_ the middlewares authenticate. Learn more about models by reading [Fluent → Models](../fluent/models.md). If authentication is successful, the middleware will have fetched your model from the database and stored it on the request. This means you can access an authenticated model synchronously in your route. - -In your route closure, you use the following methods to check for authentication: - -- `authenticated(_:)`: Returns type if authenticated, `nil` if not. -- `isAuthenticated(_:)`: Returns `true` if supplied type is authenticated. -- `requireAuthenticated(_:)`: Returns type if authenticated, `throws` if not. - -Typical usage looks like the following: - -```swift -// use middleware to protect a group -let protectedGroup = router.group(...) - -// add a protected route -protectedGroup.get("test") { req in - // require that a User has been authed by middleware or throw - let user = try req.requireAuthenticated(User.self) - - // say hello to the user - return "Hello, \(user.name)." - -} -``` - -## Methods - -This package supports two basic types of stateless authentication. - -- _Token_: Uses the bearer authorization header. -- _Password_: Uses the basic authorization header. - -For each authentication type, there is a separate middleware and model protocol. - -### Password Authentication - -Password authentication uses the basic authorization header (username and password) to verify a user. With this method, the username and password must be sent with each request to a protected endpoint. - -To use password authentication, you will first need to conform your Fluent model to `PasswordAuthenticatable`. - -```swift -extension User: PasswordAuthenticatable { - /// See `PasswordAuthenticatable`. - static var usernameKey: WritableKeyPath { - return \.email - } - - /// See `PasswordAuthenticatable`. - static var passwordKey: WritableKeyPath { - return \.passwordHash - } -} -``` - -Note that the `passwordKey` should point to the _hashed_ password. Never store passwords in plaintext. - -Once you have created an authenticatable model, the next step is to add middleware to your protected route. - -```swift -// Use user model to create an authentication middleware -let password = User.basicAuthMiddleware(using: BCryptDigest()) - -// Create a route closure wrapped by this middleware -router.grouped(password).get("hello") { req in - /// -} -``` - -Here we are using `BCryptDigest` as the [`PasswordVerifier`](https://api.vapor.codes/auth/latest/Authentication/Protocols/PasswordVerifier.html) since we are assuming the user's password is stored as a BCrypt hash. - -Now, to fetch the authenticated user in the route closure, you can use [`requireAuthenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE20requireAuthenticatedxxmKAD15AuthenticatableRzlF). - -```swift -let user = try req.requireAuthenticated(User.self) -return "Hello, \(user.name)." -``` - -The `requireAuthenticated` method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect the route from unauthenticated access is not required. - -### Token Authentication - -Token authentication uses the bearer authorization header (token) to lookup a token and its related user. With this method, the token must be sent with each request to a protected endpoint. - -Unlike password authentication, token authentication relies on _two_ Fluent models. One for the token and one for the user. The token model should be a _child_ of the user model. - -Here is an example of a very basic `User` and associated `UserToken`. - -```swift -struct User: Model { - var id: Int? - var name: String - var email: String - var passwordHash: String - - var tokens: Children { - return children(\.userID) - } -} - -struct UserToken: Model { - var id: Int? - var string: String - var userID: User.ID - - var user: Parent { - return parent(\.userID) - } -} -``` - -The first step to using token authentication is to conform your user and token models to their respective `Authenticatable` protocols. - -```swift -extension UserToken: Token { - /// See `Token`. - typealias UserType = User - - /// See `Token`. - static var tokenKey: WritableKeyPath { - return \.string - } - - /// See `Token`. - static var userIDKey: WritableKeyPath { - return \.userID - } -} -``` - -Once the token is conformed to `Token`, setting up the user model is easy. - -```swift -extension User: TokenAuthenticatable { - /// See `TokenAuthenticatable`. - typealias TokenType = UserToken -} -``` - -Once you have conformed your models, the next step is to add middleware to your protected route. - -```swift -// Use user model to create an authentication middleware -let token = User.tokenAuthMiddleware() - -// Create a route closure wrapped by this middleware -router.grouped(token).get("hello") { - // -} -``` - -Now, to fetch the authenticated user in the route closure, you can use [`requireAuthenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE20requireAuthenticatedxxmKAD15AuthenticatableRzlF). - -```swift -let user = try req.requireAuthenticated(User.self) -return "Hello, \(user.name)." -``` - -The `requireAuthenticated` method will automatically throw an appropriate unauthorized error if the valid credentials were not supplied. Because of this, using [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect the route from unauthenticated access is not required. diff --git a/3.0/docs/auth/getting-started.md b/3.0/docs/auth/getting-started.md deleted file mode 100644 index 7aebb232..00000000 --- a/3.0/docs/auth/getting-started.md +++ /dev/null @@ -1,49 +0,0 @@ -# Getting Started with Auth - -Auth ([vapor/auth](https://github.com/vapor/auth)) is a framework for adding authentication to your application. It builds on top of [Fluent](../fluent/getting-started.md) by using models as the basis of authentication. - -!!! tip - There is a Vapor API template with Auth pre-configured available. - See [Getting Started → Toolbox → Templates](../getting-started/toolbox.md#templates). - -Let's take a look at how you can get started using Auth. - -## Package - -The first step to using Auth is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // 👤 Authentication and Authorization framework for Fluent. - .package(url: "https://github.com/vapor/auth.git", from: "2.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["Authentication", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Auth currently provides one module `Authentication`. In the future, there will be a separate module named `Authorization` for performing more advanced auth. - - -## Provider - -Once you have succesfully added the Auth package to your project, the next step is to configure it in your application. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import Authentication - -// register Authentication provider -try services.register(AuthenticationProvider()) -``` - -That's it for basic setup. The next step is to create an authenticatable model. \ No newline at end of file diff --git a/3.0/docs/auth/web.md b/3.0/docs/auth/web.md deleted file mode 100644 index 6a554f54..00000000 --- a/3.0/docs/auth/web.md +++ /dev/null @@ -1,117 +0,0 @@ -# Web Authentication - -This guide will introduce you to session-based authentication—a method of authentication commonly used for protecting web (front-end) pages. - -## Concept - -In Computer Science (especially web frameworks), the concept of Authentication means verifying the _identity_ of a user. This is not to be confused with Authorization which verifies _privileges_ to a given resource - -Session-based authentication uses cookies to re-authenticate users with each request to your website. It performs this logic via a middleware that you add to your application or specific routes. - -You are responsible for initially authenticating the user to your application (either manually or by using methods from the [Stateless (API)](api.md) section). Once you have authenticated the user once, the middleware will use cookies to re-authenticate the user on subsequent requests automatically. - -## Example - -Let's take a look at a simple session-based authentication example. - -### Pre-requisites - -In order to do session-based authentication, you must have a way to initially authenticate your user. In other words, you need a method for logging them in. The [Stateless (API)](api.md) section covers some of these methods, but it's entirely up to you. - -You will also need to have sessions configured for your application. You can learn more about this in [Vapor → Sessions](../vapor/sessions.md). Usually this will require adding the `SessionsMiddleware` and choosing a `KeyedCache`. - -```swift -config.prefer(MemoryKeyedCache.self, for: KeyedCache.self) - -var middlewares = MiddlewareConfig() -middlewares.use(SessionsMiddleware.self) -// ... -services.register(middlewares) -``` - -### Model - -Once you are ready to enable session-based authentication, the first step is to conform your user model to [`SessionAuthenticatable`](https://api.vapor.codes/auth/latest/Authentication/Protocols/SessionAuthenticatable.html). - -```swift -extension User: SessionAuthenticatable { } -``` - -The conformance is empty since all of the required methods have default implementations. - - -### Middleware - -Once your model is conformed, you can use it to create an `AuthenticationSessionsMiddleware`. - -```swift -// create auth sessions middleware for user -let session = User.authSessionsMiddleware() - -// create a route group wrapped by this middleware -let auth = router.grouped(session) - -// create new route in this route group -auth.get("hello") { req -> String in - // -} -``` - -Create a route group wrapped by this middleware using the route grouping methods. Any routes you want to support session-based authentication should use this route group. - -You can also apply this middleware globally to your application if you'd like. - -### Route - -Inside of any route closure wrapped by the session auth middleware, we can access our authenticated model using the [`authenticated(_:)`](https://api.vapor.codes/auth/latest/Authentication/Extensions/Request.html#/s:5Vapor7RequestC14AuthenticationE13authenticatedxSgxmKAD15AuthenticatableRzlF) methods. - -```swift -let user = try req.requireAuthenticated(User.self) -return "Hello, \(user.name)!" -``` - -Here we are using the method prefixed with `require` to throw an error if the user was not succesfully authenticated. - -If you visit this route now, you should see a message saying no user has been authenticated. Let's resolve this by creating a way for our user to login! - -!!! note - Use [`GuardAuthenticationMiddleware`](https://api.vapor.codes/auth/latest/Authentication/Classes/GuardAuthenticationMiddleware.html) to protect routes that do not call `requireAuthenticated(_:)` or otherwise require authentication. - -### Login - -For the sake of this example, we will just log in a pre-defined user with a fixed ID. - -```swift -auth.get("login") { req -> Future in - return User.find(1, on: req).map { user in - guard let user = user else { - throw Abort(.badRequest) - } - try req.authenticate(user) - return "Logged in" - } -} -``` - -Remember that this login route must go through the `AuthenticationSessionsMiddleware`. The middleware is what will detect that we have authenticated a user and later restore the authentication automatically. - -Upon visiting `/hello`, you should recieve an error message stating that you are not logged in. If you then visit `/login` first, followed by `/hello` you should see that you are now successfully logged in! - -If you open the inspector, you should notice a new cookie named `"vapor-session"` has been added to your browser. - -### Redirecting unauthenticated users to the Login page - -To redirect unauthenticated users from protected routes to the login page, add the `RedirectMiddleware` to your protected routes. - -```swift -func boot(router Router) throws { - let auth = User.authSessionsMiddleware() // The Authentication Middleware - let redirect = RedirectMiddleware(path: "login") // Create the redirect middleware providing the path to redirect to (the login page) if the user is not logged in - - let loginRoutes = router.grouped([auth]) // The Login page should not be protected - loginRoutes.get("login", use: renderLogin) - - let protectedRoutes = router.grouped([redirect, auth]) // Add routes to this group if they should redirect an unauthenticated user to the loggin page - protectedRoutes.get("protected", use: renderProtected) -} -``` diff --git a/3.0/docs/command/getting-started.md b/3.0/docs/command/getting-started.md deleted file mode 100644 index da685799..00000000 --- a/3.0/docs/command/getting-started.md +++ /dev/null @@ -1,43 +0,0 @@ -# Getting Started with Command - -The Command module is provided as a part of Vapor's Console package ([vapor/console](https://github.com/vapor/console)). This module provides APIs for creating command-line interfaces (CLIs). It's what powers the [Vapor Toolbox](../getting-started/toolbox.md). - -!!! tip - For an in-depth look at all of Command's APIs, check out the [Command API docs](https://api.vapor.codes/console/latest/Command/index.html). - -## Usage - -This package is included with Vapor and exported by default. You will have access to all `Command` APIs when you import `Vapor`. - -```swift -import Vapor // implies import Command -``` - -### Standalone - -The Command module, part of the larger Vapor Console package, can also be used on its own with any Swift project. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - /// 💻 APIs for creating interactive CLI tools. - .package(url: "https://github.com/vapor/console.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Command", ... ]) - ] -) -``` - -Use `import Command` to access the APIs. - -## Overview - -Continue to [Command → Overview](overview.md) for an overview of Command's features. \ No newline at end of file diff --git a/3.0/docs/command/overview.md b/3.0/docs/command/overview.md deleted file mode 100644 index 02849776..00000000 --- a/3.0/docs/command/overview.md +++ /dev/null @@ -1,168 +0,0 @@ -# Command Overview - -This guide will introduce you to the Command module by showing you how to create your own CLI. For this example, we will implement [`cowsay`](https://en.wikipedia.org/wiki/Cowsay), a command that prints an ASCII picture of a cow with a message. - -!!! tip - You can install the real `cowsay` program using `brew install cowsay`. - -```sh -$ cowsay Hello - ----- -< Hello > - ----- - \ ^__^ - \ (oo\_______ - (__)\ )\/\ - ||----w | - || || -``` - -## Command - -The first step is to create a type that conforms to [`Command`](https://api.vapor.codes/console/latest/Command/Protocols/Command.html). - -```swift -/// Generates ASCII picture of a cow with a message. -struct CowsayCommand: Command { - ... -} -``` - -Now let's implement the required methods. - -### Arguments - -Commands can have zero or more [`CommandArgument`](https://api.vapor.codes/console/latest/Command/Structs/CommandArgument.html)s. These arguments will be required for the command to run. - -```swift -/// Generates ASCII picture of a cow with a message. -struct CowsayCommand: Command { - /// See `Command` - var arguments: [CommandArgument] { - return [.argument(name: "message")] - } - - ... -} -``` - -Here we are defining one argument, the `message` that the cow will say. This is required to run the `cowsay` command. - -### Options - -Commands can have zero or more [`CommandOption`](https://api.vapor.codes/console/latest/Command/Structs/CommandOption.html)s. These options are not required for the command to run and can be passed using `--` or `-` syntax. - -```swift -/// Generates ASCII picture of a cow with a message. -struct CowsayCommand: Command { - ... - /// See `Command` - var options: [CommandOption] { - return [ - .value(name: "eyes", short: "e", default: "oo", help: ["Change cow's eyes"]), - .value(name: "tongue", short: "t", default: " ", help: ["Change cow's tongue"]), - ] - } - ... -} -``` - -Here we are defining two options, `eyes` and `tongue`. These will let the user optionally change how the cow looks. - -### Help - -Next we can define an optional help message to display when the user passes `--help`. - -```swift -/// Generates ASCII picture of a cow with a message. -struct CowsayCommand: Command { - ... - /// See `Command` - var help: [String] { - return ["Generates ASCII picture of a cow with a message."] - } - ... -} -``` - -Let's take a look at how this will look once our command is complete: - -```sh -Usage: cowsay [--eyes,-e] [--tongue,-t] - -Generates ASCII picture of a cow with a message. - -Arguments: - message n/a - -Options: - eyes Change cow's eyes - tongue Change cow's tongue -``` - -### Run - -Finally, we need to write our implementation: - -```swift -/// Generates ASCII picture of a cow with a message. -struct CowsayCommand: Command { - ... - - /// See `Command`. - func run(using context: CommandContext) throws -> Future { - let message = try context.argument("message") - /// We can use requireOption here since both options have default values - let eyes = try context.requireOption("eyes") - let tongue = try context.requireOption("tongue") - let padding = String(repeating: "-", count: message.count) - let text: String = """ - \(padding) - < \(message) > - \(padding) - \\ ^__^ - \\ (\(eyes)\\_______ - (__)\\ )\\/\\ - \(tongue) ||----w | - || || - """ - context.console.print(text) - return .done(on: context.container) - } -} -``` - -The [`CommandContext`](https://api.vapor.codes/console/latest/Command/Structs/CommandContext.html) gives you access to everything you will need, including a `Container`. Now that we have a complete `Command`, the next step is to configure it. - -## Config - -Use the [`CommandConfig`](https://api.vapor.codes/console/latest/Command/Structs/CommandConfig.html) struct to register commands to your container. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift) - -```swift -/// Create a `CommandConfig` with default commands. -var commandConfig = CommandConfig.default() -/// Add the `CowsayCommand`. -commandConfig.use(CowsayCommand(), as: "cowsay") -/// Register this `CommandConfig` to services. -services.register(commandConfig) -``` - -Check that your command was properly configured using `--help`. - -```swift -swift run Run cowsay --help -``` - -That's it! - -```swift -$ swift run Run cowsay 'Good job!' -e ^^ -t U - --------- -< Good job! > - --------- - \ ^__^ - \ (^^\_______ - (__)\ )\/\ - U ||----w | - || || -``` \ No newline at end of file diff --git a/3.0/docs/console/getting-started.md b/3.0/docs/console/getting-started.md deleted file mode 100644 index 1ed2b977..00000000 --- a/3.0/docs/console/getting-started.md +++ /dev/null @@ -1,43 +0,0 @@ -# Getting Started with Console - -The Console module is provided as a part of Vapor's Console package ([vapor/console](https://github.com/vapor/console)). This module provides APIs for performing console I/O including things like outputting stylized text, requesting user input, and displaying activity indicators like loading bars. - -!!! tip - For an in-depth look at all of Console's APIs, check out the [Console API docs](https://api.vapor.codes/console/latest/Console/index.html). - -## Usage - -This package is included with Vapor and exported by default. You will have access to all `Console` APIs when you import `Vapor`. - -```swift -import Vapor // implies import Console -``` - -### Standalone - -The Console module, part of the larger Vapor Console package, can also be used on its own with any Swift project. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - /// 💻 APIs for creating interactive CLI tools. - .package(url: "https://github.com/vapor/console.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Console", ... ]) - ] -) -``` - -Use `import Console` to access the APIs. - -## Overview - -Continue to [Console → Overview](overview.md) for an overview of Console's features. \ No newline at end of file diff --git a/3.0/docs/console/overview.md b/3.0/docs/console/overview.md deleted file mode 100644 index cd1b1e5a..00000000 --- a/3.0/docs/console/overview.md +++ /dev/null @@ -1,82 +0,0 @@ -# Console Overview - -This guide will give you a brief introduction to the Console module, showing you how to output stylized text and request user input. - -## Terminal - -A default implementation of the [`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) protocol called [`Terminal`](https://api.vapor.codes/console/latest/Console/Classes/Terminal.html) is provided for you to use. - -```swift -let terminal = Terminal() -print(terminal is Console) // true -terminal.print("Hello") -``` -The rest of this guide will assume a generic `Console`, but using `Terminal` directly will also work fine. You can use any available [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html) to create a console. - -```swift -let console = try req.make(Console.self) -console.print("Hello") -``` - -## Output - -[`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) provides several convenience methods for outputting strings, like `print(_:)` and `warning(_:)`. All of these methods eventually call `output(_:)` which is the most powerful output method. This method accepts [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html) which supports independently styled string components. - -```swift -/// Prints "Hello, world", but the word 'world' is blue. -console.output("Hello, " + "world".consoleText(color: .blue)) -``` - -You can combine as many differently styled fragments to a [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html) as you like. All [`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) methods that output text should have an overload for accepting [`ConsoleText`](https://api.vapor.codes/console/latest/Console/Structs/ConsoleText.html). - -## Input - -[`Console`](https://api.vapor.codes/console/latest/Console/Protocols/Console.html) offers several methods for requesting input from the user, the most basic of which is `input(isSecure:)`. - -```swift -/// Accepts input from the terminal until the first newline. -let input = console.input() -console.print("You wrote: \(input)") -``` - -### Ask - -Use `ask(_:)` to supply a prompt and input indicator to the user. - -```swift -/// Outputs the prompt then requests input. -let name = console.ask("What is your name?") -console.print("You said: \(name)") -``` - -The above code will output: - -```sh -What is your name? -> Vapor -You said: Vapor -``` - -### Confirm - -Use `confirm(_:)` to prompt the user for yes / no input. - -```swift -/// Prompts the user for yes / no input. -if console.confirm("Are you sure?") { - // they are sure -} else { - // don't do it! -} -``` - -The above code will output: - -```swift -Are you sure? -y/n> yes -``` - -!!! note - `confirm(_:)` will continue to prompt the user until they respond with something recognized as yes or no. - \ No newline at end of file diff --git a/3.0/docs/crypto/asymmetric.md b/3.0/docs/crypto/asymmetric.md deleted file mode 100644 index 774a673a..00000000 --- a/3.0/docs/crypto/asymmetric.md +++ /dev/null @@ -1,38 +0,0 @@ -# Asymmetric Cryptography - -Asymmetric cryptography (also called public-key cryptography) is a cryptographic system that uses multiple keys—usually a "public" and "private" key. - -Read more about [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography) on Wikipedia. - -## RSA - -A popular asymmetric cryptography algorithm is RSA. RSA has two key types: public and private. - -RSA can create signatures from any data using a private key. - -```swift -let privateKey: String = ... -let signature = try RSA.SHA512.sign("vapor", key: .private(pem: privateKey)) -``` - -!!! info - Only private keys can _create_ signatures. - -These signatures can be verified against the same data later using either the public or private key. - -```swift -let publicKey: String = ... -try RSA.SHA512.verify(signature, signs: "vapor", key: .public(pem: publicKey)) // true -``` - -If RSA verifies that a signature matches input data for a public key, you can be sure that whoever generated that signature had access to that key's private key. - -### Algorithms - -RSA supports any of the Crypto module's [`DigestAlgorithm`](https://api.vapor.codes/crypto/latest/Crypto/Classes/DigestAlgorithm.html). - -```swift -let privateKey: String = ... -let signature512 = try RSA.SHA512.sign("vapor", key: .private(pem: privateKey)) -let signature256 = try RSA.SHA256.sign("vapor", key: .private(pem: privateKey)) -``` diff --git a/3.0/docs/crypto/ciphers.md b/3.0/docs/crypto/ciphers.md deleted file mode 100644 index 52ca5759..00000000 --- a/3.0/docs/crypto/ciphers.md +++ /dev/null @@ -1,40 +0,0 @@ -# Cipher Algorithms - -Ciphers allow you to encrypt plaintext data with a key yielding ciphertext. This ciphertext can be later decrypted by the same cipher using the same key. - -Read more about [ciphers](https://en.wikipedia.org/wiki/Cipher) on Wikipedia. - -## Encrypt - -Use the global convenience variables for encrypting data with common algorithms. - -```swift -let ciphertext = try AES128.encrypt("vapor", key: "secret") -print(ciphertext) /// Data -``` - -## Decrypt - -Decryption works very similarly to [encryption](#encrypt). The following snippet shows how to decrypt the ciphertext from our previous example. - -```swift -let plaintext = try AES128.decrypt(ciphertext, key: "secret") -print(plaintext) /// "vapor" -``` - -See the Crypto module's [global variables](https://api.vapor.codes/crypto/latest/Crypto/Global%20Variables.html#/Ciphers) for a list of all available cipher algorithms. - -## Streaming - -Both encryption and decryption can work in a streaming mode that allows data to be chunked. This is useful for controlling memory usage while encrypting large amounts of data. - -```swift -let key: Data // 16-bytes -let aes128 = Cipher(algorithm: .aes128ecb) -try aes128.reset(key: key, mode: .encrypt) -var buffer = Data() -try aes128.update(data: "hello", into: &buffer) -try aes128.update(data: "world", into: &buffer) -try aes128.finish(into: &buffer) -print(buffer) // Completed ciphertext -``` diff --git a/3.0/docs/crypto/digests.md b/3.0/docs/crypto/digests.md deleted file mode 100644 index 700edfb0..00000000 --- a/3.0/docs/crypto/digests.md +++ /dev/null @@ -1,71 +0,0 @@ -# Message Digests - -Cryptographic hash functions (also known as message digest algorithms) convert data of arbitrary size to a fixed-size digest. These are most often used for generating checksums or identifiers for large data blobs. - -Read more about [Cryptographic hash functions](https://en.wikipedia.org/wiki/Cryptographic_hash_function) on Wikipedia. - -## Hash - -Use the global convenience variables to create hashes using common algorithms. - -```swift -import Crypto - -let digest = try SHA1.hash("hello") -print(digest.hexEncodedString()) // aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d -``` - -See the Crypto module's [global variables](https://api.vapor.codes/crypto/latest/Crypto/Global%20Variables.html#/Digests) for a list of all available hash algorithms. - -### Streaming - -You can create a [`Digest`](https://api.vapor.codes/crypto/latest/Crypto/Classes/Digest.html) manually and use its instance methods to create a hash for one or more data chunks. - -```swift -var sha256 = try Digest(algorithm: .sha256) -try sha256.reset() -try sha256.update(data: "hello") -try sha256.update(data: "world") -let digest = try sha256.finish() -print(digest) /// Data -``` - -## BCrypt - -BCrypt is a popular hashing algorithm that has configurable complexity and handles salting automatically. - -### Hash - -Use the `hash(_:cost:salt:)` method to create BCrypt hashes. - -```swift -let digest = try BCrypt.hash("vapor", cost: 4) -print(digest) /// data -``` - -Increasing the `cost` value will make hashing and verification take longer. - -### Verify - -Use the `verify(_:created:)` method to verify that a BCrypt hash was created by a given plaintext input. - -```swift -let hash = try BCrypt.hash("vapor", cost: 4) -try BCrypt.verify("vapor", created: hash) // true -try BCrypt.verify("foo", created: hash) // false -``` - -## HMAC - -HMAC is an algorithm for creating _keyed_ hashes. HMAC will generate different hashes for the same input if different keys are used. - -```swift -let digest = try HMAC.SHA1.authenticate("vapor", key: "secret") -print(digest.hexEncodedString()) // digest -``` - -See the [`HMAC`](https://api.vapor.codes/crypto/latest/Crypto/Classes/HMAC.html) class for a list of all available hash algorithms. - -### Streaming - -HMAC hashes can also be streamed. The API is identical to [hash streaming](#streaming). diff --git a/3.0/docs/crypto/getting-started.md b/3.0/docs/crypto/getting-started.md deleted file mode 100644 index d67c3c0a..00000000 --- a/3.0/docs/crypto/getting-started.md +++ /dev/null @@ -1,37 +0,0 @@ -# Using Crypto - -Crypto ([vapor/crypto](https://github.com/vapor/crypto)) is a library containing common APIs related to cryptography and data generation. The package contains two modules: - -- `Crypto` -- `Random` - -## With Vapor - -This package is included with Vapor by default, just add: - -```swift -import Crypto -import Random -``` - -## Without Vapor - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/crypto.git", .upToNextMajor(from: "x.0.0")), - ], - targets: [ - .target(name: "Project", dependencies: ["Crypto", "Random", ... ]) - ] -) -``` - -Use `import Crypto` to access Crypto's APIs and `import Random` to access Random's APIs. diff --git a/3.0/docs/crypto/otp.md b/3.0/docs/crypto/otp.md deleted file mode 100644 index c42e44f5..00000000 --- a/3.0/docs/crypto/otp.md +++ /dev/null @@ -1,41 +0,0 @@ -# TOTP and HOTP - -One-time passwords (OTPs) are commonly used as a form of [two-factor authentication](https://en.wikipedia.org/wiki/Multi-factor_authentication). Crypto can be used to generate both TOTP and HOTP in accordance with [RFC 6238](https://tools.ietf.org/html/rfc6238) and [RFC 4226](https://tools.ietf.org/html/rfc4226 -) respectively. - -- **TOTP**: Time-based One-Time Password. Generates password by combining shared secret with unix timestamp. -- **HOTP**: HMAC-Based One-Time Password. Similar to TOTP, except an incrementing counter is used instead of a timestamp. Each time a new OTP is generated, the counter increments. - -## Generating OTP - -OTP generation is similar for both TOTP and HOTP. The only difference is that HOTP requires the current counter to be passed. - -```swift -import Crypto - -// Generate TOTP -let code = TOTP.SHA1.generate(secret: "hi") -print(code) "123456" - -// Generate HOTP -let code = HOTP.SHA1.generate(secret: "hi", counter: 0) -print(code) "208503" -``` - -View the API docs for [`TOTP`](https://api.vapor.codes/crypto/latest/Crypto/Structs/TOTP.html) and [`HOTP`](https://api.vapor.codes/crypto/latest/Crypto/Structs/HOTP.html) for more information. - -## Base 32 - -TOTP and HOTP shared secrets are commonly transferred using Base32 encoding. Crypto provides conveniences for converting to/from Base32. - -```swift -import Crypto - -// shared secret -let secret: Data = ... - -// base32 encoded secret -let encodedSecret = secret.base32EncodedString() -``` - -See Crypto's [`Data`](https://api.vapor.codes/crypto/latest/Crypto/Extensions/Data.html) extensions for more information. diff --git a/3.0/docs/crypto/random.md b/3.0/docs/crypto/random.md deleted file mode 100644 index 2abc02cd..00000000 --- a/3.0/docs/crypto/random.md +++ /dev/null @@ -1,32 +0,0 @@ -# Random - -The `Random` module deals with random data generation including random number generation. - -## Data Generator - -The [`DataGenerator`]() class powers all of the random data generators. - -### Implementations - -- [`OSRandom`](https://api.vapor.codes/crypto/latest/Random/Classes/OSRandom.html): Provides a random data generator using a platform-specific method. - - -- [`URandom`](https://api.vapor.codes/crypto/latest/Random/Classes/URandom.html) provides random data generation based on the `/dev/urandom` file. - - -- [`CryptoRandom`](https://api.vapor.codes/crypto/latest/Crypto/Classes/CryptoRandom.html) from the `Crypto` module provides cryptographically-secure random data using OpenSSL. - - -```swift -let random: DataGenerator ... -let data = try random.generateData(bytes: 8) -``` - -### Generate - -`DataGenerator`s are capable of generating random primitive types using the `generate(_:)` method. - -```swift -let int = try OSRandom().generate(Int.self) -print(int) // Int -``` diff --git a/3.0/docs/database-kit/getting-started.md b/3.0/docs/database-kit/getting-started.md deleted file mode 100644 index f9c5710d..00000000 --- a/3.0/docs/database-kit/getting-started.md +++ /dev/null @@ -1,35 +0,0 @@ -# Getting Started with Database Kit - -Database Kit ([vapor/database-kit](https://github.com/vapor/database-kit)) is a framework for configuring and working with database connections. It includes core services like caching, logging, and connection pooling. - -!!! tip - If you use Fluent, you will usually not need to use Database Kit manually. - But learning the APIs may come in handy. - -## Package - -The Database Kit package is lightweight, pure Swift, and has few dependencies. This means it can be used as a core database framework for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["DatabaseKit", ... ]) - ] -) -``` - -Use `import DatabaseKit` to access the APIs. - -## API Docs - -The rest of this guide will give you an overview of what is available in the DatabaseKit package. As always, feel free to visit the [API docs](http://api.vapor.codes/database-kit/latest/DatabaseKit/index.html) for more in-depth information. diff --git a/3.0/docs/database-kit/overview.md b/3.0/docs/database-kit/overview.md deleted file mode 100644 index 52eb87a3..00000000 --- a/3.0/docs/database-kit/overview.md +++ /dev/null @@ -1,183 +0,0 @@ -# Using Database Kit - -Database Kit is a framework for configuring and working with database connections. It helps you do things like manage and pool connections, create keyed caches, and log queries. - -Many of Vapor's packages such as the Fluent drivers, Redis, and Vapor core are built on top of Database Kit. This guide will walk you through some of the common APIs you might encounter when using Database Kit. - -## Config - -Your first interaction with Database Kit will most likely be with the [`DatabasesConfig`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabasesConfig.html) struct. This type helps you configure one or more databases to your application and will ultimately yield a [`Databases`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/Databases.html) struct. This usually takes place in [`configure.swift`](../../getting-started/structure/#configureswift). - -```swift -// Create a SQLite database. -let sqliteDB = SQLiteDatabase(...) - -// Create a new, empty DatabasesConfig. -var dbsConfig = DatabasesConfig() - -// Register the SQLite database using '.sqlite' as an identifier. -dbsConfig.add(sqliteDB, as: .sqlite) - -// Register more DBs here if you want - -// Register the DatabaseConfig to services. -services.register(dbsConfig) -``` - -Using the [`add(...)`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabasesConfig.html) methods, you can register [`Database`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/Database.html)s to the config. You can register instances of a database, a database type, or a closure that creates a database. The latter two methods will be resolved when your container boots. - -You can also configure options on your databases, such as enabling logging. - -```swift -// Enable logging on the SQLite database -dbsConfig.enableLogging(for: .sqlite) -``` - -See the section on [logging](#logging) for more information. - -### Identifier - -Most database integrations will provide a default [`DatabaseIdentifier`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseIdentifier.html) to use. However, you can always create your own. This is usually done by creating a static extension. - -```swift -extension DatabaseIdentifier { - /// Test database. - static var testing: DatabaseIdentifier { - return "testing" - } -} -``` - -`DatabaseIdentifier` is `ExpressibleByStringLiteral` which allows you to create one with just a `String`. - -### Databases - -Once you have registered a `DatabasesConfig` to your services and booted a container, you can take advantage of the convenience extensions on [`Container`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Extensions/Container.html) to start creating connections. - -```swift -// Creates a new connection to `.sqlite` db -app.withNewConnection(to: .sqlite) { conn in - return conn.query(...) // do some db query -} -``` - -Read more about creating and managing connections in the next section. - -## Connections - -Database Kit's main focus is on creating, managing, and pooling connections. Creating new connections takes a non-trivial amount of time for your application and many cloud services limit the total number of connections to a service that can be open. Because of this, it is important for high-concurrency web applications to manage their connections carefully. - -### Pools - -A common solution to connection management is the use of connection pools. These pools usually have a set maximum number of connections that are allowed to be open at once. Each time the pool is asked for a connection, it will first check if one is available before creating a new connection. If none are available, it will create a new one. If no connections are available and the pool is already at its maximum, the request for a new connection will _wait_ for a connection to be returned. - -The easiest way to request and release a pooled connection is the method [`withPooledConnection(...)`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Extensions/Container.html#/s:11DatabaseKit20withPooledConnectionXeXeF). - -```swift -// Requests a pooled connection to `.psql` db -req.withPooledConnection(to: .psql) { conn in - return conn.query(...) // do some db query -} -``` - -This method will request a pooled connection to the identified database and call the provided closure when the connection is available. When the `Future` returned by the closure has completed, the connection will automatically be returned to the pool. - -If you need access to a connection outside of a closure, you can use the related request / release methods instead. - -```swift -// Request a connection from the pool and wait for it to be ready. -let conn = try app.requestPooledConnection(to: .psql).wait() - -// Ensure the connection is released when we exit this scope. -defer { app.releasePooledConnection(conn, to: .psql) } -``` - -You can configure your connection pools using the [`DatabaseConnectionPoolConfig`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseConnectionPoolConfig.html) struct. - -```swift -// Create a new, empty pool config. -var poolConfig = DatabaseConnectionPoolConfig() - -// Set max connections per pool to 8. -poolConfig.maxConnections = 8 - -// Register the pool config. -services.register(poolConfig) -``` - -To prevent race conditions, pools are never shared between event loops. There is usually one pool per database per event loop. This means that the amount of connections your application can potentially open to a given database is equal to `numThreads * maxConns`. - -### New - -You can always create a new connection to your databases if you need to. This will not affect your pooled connections. Creating new connections is especially useful during testing and app boot. But try not to do it in route closures since heavy traffic to your app could end up creating a lot of connections! - -Similar to pooled connections, opening and closing new connections can be done using [`withNewConnection(...)`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Extensions/Container.html#/s:11DatabaseKit17withNewConnectionXeXeF). - -```swift -// Creates a new connection to `.sqlite` db -app.withNewConnection(to: .sqlite) { conn in - return conn.query(...) // do some db query -} -``` - -This method will create a new connection, calling the supplied closure when the connection is open. When the `Future` returned in the closure completes, the connection will be closed automatically. - -You can also simply open a new connection with [`newConnection(...)`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Extensions/Container.html#/s:11DatabaseKit13newConnectionXeXeF). - -```swift -// Creates a new connection to `.sqlite` db -let conn = try app.newConnection(to: .sqlite).wait() - -// Ensure the connection is closed when we exit this scope. -defer { conn.close() } -``` - -## Logging - -Databases can opt into supporting query logging via the [`LogSupporting`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/LogSupporting.html) protocol. Databases that conform to this protocol can have loggers [configured](#config) via `DatabasesConfig`. - -```swift -// Enable logging on the SQLite database -dbsConfig.enableLogging(for: .sqlite) -``` - -By default, a simple print logger will be used, but you can pass a custom `DatabaseLogHandler`. - -```swift -// Create a custom log handler. -let myLogger: DatabaseLogHandler = ... - -// Enable logging on SQLite w/ custom logger. -dbsConfig.enableLogging(for: .sqlite, logger: myLogger) -``` - -Log handlers will receive an instance of [`DatabaseLog`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseLog.html) for each logged query. This contains information such as the query, parameterized values, database id, and time. - -## Keyed Cache - -Databases can opt into supporting keyed-caching via the [`KeyedCacheSupporting`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/KeyedCacheSupporting.html) protocol. Databases that conform to this protocol can be used to create instances of [`DatabaseKeyedCache`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Classes/DatabaseKeyedCache.html). - -Keyed caches are capable of getting, setting, and removing `Codable` values at keys. They are sometimes called "key value stores". - -To create a keyed cache, you can use the extensions on [`Container`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Extensions/Container.html#/s:11DatabaseKit10keyedCacheXeXeF). - -```swift -// Creates a DatabaseKeyedCache with .redis connection pool -let cache = try app.keyedCache(for: .redis) - -// Sets "hello" = "world" -try cache.set("hello", to: "world").wait() - -// Gets "hello" -let world = try cache.get("hello", as: String.self).wait() -print(world) // "world" - -// Removes "hello" -try cache.remove("hello").wait() -``` - -See the [`KeyedCache`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/KeyedCache.html) protocol for more information. - -## API Docs - -Check out the [API docs](https://api.vapor.codes/database-kit/latest/DatabaseKit/index.html) for more in-depth information about DatabaseKit's APIs. diff --git a/3.0/docs/deploy/heroku.md b/3.0/docs/deploy/heroku.md deleted file mode 100644 index 2f0bcefe..00000000 --- a/3.0/docs/deploy/heroku.md +++ /dev/null @@ -1,232 +0,0 @@ -# What is Heroku - -Heroku is a popular all in one hosting solution, you can find more at heroku.com - -## Signing Up - -You'll need a heroku account, if you don't have one, please sign up here: [https://signup.heroku.com/](https://signup.heroku.com/) - -## Installing CLI - -Make sure that you've installed the heroku cli tool. - -### HomeBrew - -```bash -brew install heroku/brew/heroku -``` - -### Other Install Options - -See alternative install options here: [https://devcenter.heroku.com/articles/heroku-cli#download-and-install](https://devcenter.heroku.com/articles/heroku-cli#download-and-install). - -### Logging in - -once you've installed the cli, login with the following: - -```bash -heroku login -``` - -verify that the correct email is logged in with: - -```bash -heroku auth:whoami -``` - -### Create an application - -Visit dashboard.heroku.com to access your account, and create a new application from the drop down in the upper right hand corner. Heroku will ask a few questions such as region and application name, just follow their prompts. - -### Git - -Heroku uses Git to deploy your app, so you’ll need to put your project into a Git repository, if it isn’t already. - -#### Initialize Git - -If you need to add Git to your project, enter the following command in Terminal: - -```bash -git init -``` - -#### Master - -By default, Heroku deploys the **master** branch. Make sure all changes are checked into this branch before pushing. - -Check your current branch with - -```bash -git branch -``` - -The asterisk indicates current branch. - -```bash -* master - commander - other-branches -``` - -> **Note**: If you don’t see any output and you’ve just performed `git init`. You’ll need to commit your code first then you’ll see output from the `git branch` command. - - -If you’re _not_ currently on **master**, switch there by entering: - -```bash -git checkout master -``` - -#### Commit changes - -If this command produces output, then you have uncommitted changes. - -```bash -git status --porcelain -``` - -Commit them with the following - -```bash -git add . -git commit -m "a description of the changes I made" -``` - -#### Connect with Heroku - -Connect your app with heroku (replace with your app's name). - -```bash -$ heroku git:remote -a your-apps-name-here -``` - -### Set Buildpack - -Set the buildpack to teach heroku how to deal with vapor. - -```bash -heroku buildpacks:set vapor/vapor -``` - -### Swift version file - -The buildpack we added looks for a **.swift-version** file to know which version of swift to use. (replace 5.1.3 with whatever version your project requires.) - -```bash -echo "5.1.3" > .swift-version -``` - -This creates **.swift-version** with `5.1.3` as its contents. - - -### Procfile - -Heroku uses the **Procfile** to know how to run your app, in our case it needs to look like this: - -``` -web: Run serve --env production --hostname 0.0.0.0 --port $PORT -``` - -we can create this with the following terminal command - -```bash -echo "web: Run serve --env production" \ - "--hostname 0.0.0.0 --port \$PORT" > Procfile -``` - -### Commit changes - -We just added these files, but they're not committed. If we push, heroku will not find them. - -Commit them with the following. - -```bash -git add . -git commit -m "adding heroku build files" -``` - -### Deploying to Heroku - -You're ready to deploy, run this from the terminal. It may take a while to build, this is normal. - -```none -git push heroku master -``` - -### Scale Up - -Once you've built successfully, you need to add at least one server, one web is free and you can get it with the following: - -```bash -heroku ps:scale web=1 -``` - -### Continued Deployment - -Any time you want to update, just get the latest changes into master and push to heroku and it will redeploy - -## Postgres - -### Add PostgreSQL database - -Visit your application at dashboard.heroku.com and go to the **Add-ons** section. - -From here enter `postgress` and you'll see an option for `Heroku Postgres`. Select it. - -Choose the hobby dev free plan, and provision. Heroku will do the rest. - -Once you finish, you’ll see the database appears under the **Resources** tab. - -### Configure the database - -We have to now tell our app how to access the database. In our app directory, let's run. - -```bash -heroku config -``` - -This will make output somewhat like this - -```none -=== today-i-learned-vapor Config Vars -DATABASE_URL: postgres://cybntsgadydqzm:2d9dc7f6d964f4750da1518ad71hag2ba729cd4527d4a18c70e024b11cfa8f4b@ec2-54-221-192-231.compute-1.amazonaws.com:5432/dfr89mvoo550b4 -``` - -**DATABASE_URL** here will represent out postgres database. **NEVER** hard code the static url from this, heroku will rotate it and it will break your application. It is also bad practice. - -Here is an example database configuration - -```swift -let databaseConfig: PostgreSQLDatabaseConfig -if let url = Environment.get("DATABASE_URL") { - // configuring database - databaseConfig = PostgreSQLDatabaseConfig(url: url, transport: .unverifiedTLS)! -} else { - // ... -} -``` - -Unverified TLS is required if you are using Heroku Postgres's standard plan. - -Don't forget to commit these changes - -```none -git add . -git commit -m "configured heroku database" -``` - -### Reverting your database - -You can revert or run other commmands on heroku with the `run` command. Vapor's project is by default also named `Run`, so it reads a little funny. - -To revert your database: - -```bash -heroku run Run -- revert --all --yes --env production -``` - -To migrate - -```bash -heroku run Run -- migrate --env production -``` \ No newline at end of file diff --git a/3.0/docs/extras/style-guide.md b/3.0/docs/extras/style-guide.md deleted file mode 100644 index e6b4364e..00000000 --- a/3.0/docs/extras/style-guide.md +++ /dev/null @@ -1,1091 +0,0 @@ -# Vapor Style Guide - -## Motivation - -The Vapor style guide is a perspective on how to write Vapor application code that is clean, readable, and maintainable. It can serve as a jumping off point within your organization (or yourself) for how to write code in a style that aligns with the Vapor ecosystem. We think this guide can help solidify common ideas that occur across most applications and will be a reference for maintainers when starting a new project. This style guide is opinionated, so you should adapt your code in places where you don’t agree. - -## Contributing - -To contribute to this guide, please submit a pull request that includes your proposed changes as well as logic to support your addition or modification. Pull requests will be reviewed by the maintainers and the rationale behind the maintainers’ decision to accept or deny the changes will be posted in the pull request. - -## Application Structure - -The structure of your Vapor application is important from a readability standpoint, but also in terms of functionality. Application structure refers to a few different aspects of the Vapor ecosystem, but in particular, it is the way in which you structure your file, folders, and assets. - -The preferred way to structure your application is by separating the application into a few main parts: - -- Controllers -- Middleware -- Models -- Setup -- Utilities -- Services - -The structure ensures that new members working on your project can easily find the file or asset they are looking for. - -#### Controllers Folder - -The controllers folder houses all of the controllers for your application which correspond to your routes. If you are building an application that serves both API responses and frontend responses, this folder should be further segmented into an `APIControllers` folder and a `ViewControllers` folder. - -#### Middleware Folder - -The middleware folder contains any custom middleware that you’ve written for your application. Each piece of middleware should be descriptively named and should only be responsible for one piece of functionality. - -#### Models Folder - -“Models” in this document means an object that can be used to store or return data throughout the application. Models are not specific to Fluent - Entities, however, include database information that make it possible to persist and query them. - -The Models folder should be broken down into four parts: Entities, Requests, Responses, and View Contexts (if applicable to your application). The `Requests` and `Responses` folder hold object files that are used to decode requests or encode responses. For more information on this, see the “File Naming” section. - -If your application handles view rendering via Leaf, you should also have a folder that holds all of your view contexts. These contexts are the same type of objects as the Request and Response objects, but are specifically for passing data to the view layer. - -The Entities folder is further broken up into a folder for each database model that exists within your application. For example, if you have a `User` model that represents a `users` table, you would have a `Users` folder that contains `User.swift` (the Fluent model representation) and then any other applicable files for this entity. Other common files found at this level include files to extend functionality of the object, repository protocols/implementations, and data transformation extensions. - -#### Setup Folder - -The setup folder has all of the necessary pieces that are called on application setup. This includes `app.swift`, `boot.swift`, `configure.swift`, `migrate.swift`, and `routes.swift`. For information on each of these files, see the “Configuration” section. - -#### Utilities Folder - -The utilities folder serves as a general purpose location for any objects or helpers that don’t fit the other folders. For example, in your quest to eliminate stringly-typed code (see the “General Advice” section) you might place a `Constants.swift` file in this location. - -#### Services Folder -The services folder is used to hold any custom services that are created and registered. - -The final application structure (inside the Sources folder) looks like this: - -``` -├── Controllers -│ ├── APIControllers -│ └── ViewControllers -├── Middleware -├── Models -│ ├── Entities -│ │ └── User -│ ├── Requests -│ └── Responses -│ └── ViewContexts -├── Setup -│ ├── app.swift -│ ├── boot.swift -│ ├── commands.swift -│ ├── configure.swift -│ ├── content.swift -│ ├── databases.swift -│ ├── middlewares.swift -│ ├── migrate.swift -│ ├── repositories.swift -│ └── routes.swift -├── Utilities -├── Services -``` - -## Configuration - -Configuring your application correctly is one of the most important parts of a successful Vapor application. The main function of the configuring a Vapor application is correctly registering all of your services and 3rd party providers. - -**Note**: For more information on registering credentials and secrets, see the “Credentials” section. - -#### Files - -Depending on your application you should have some or all of the following files: - -- app.swift (use the default template version) -- boot.swift (use the default template version) -- commands.swift (Optional) -- configure.swift -- content.swift -- databases.swift (Optional) -- middlewares.swift -- migrate.swift (Optional) -- repositories.swift (Optional) -- routes.swift - -#### configure.swift - -Use this file to register your services, providers, and any other code that needs to run as part of the Vapor application setup process. - -We recommend registering all services (with some exceptions, like `BlockingIOThreadPool`, that have internal synchronization code) using the closure method. The closure gets called each time a container requests that service. There's one container per thread, meaning that you get one service per thread. As a result, you don't need to think about synchronizing access and state in the object, which is otherwise difficult. The tradeoff to this method is memory usage, which is typically negligible for a small class, but you gain performance. - -#### routes.swift - -The routes.swift file is used to declare route registration for your application. Typically, the routes.swift file looks like this: - -```swift -import Vapor - -public func routes(_ router: Router, _ container: Container) throws { - try router.register(collection: MyControllerHere(db: container.connectionPool(to: .mysql))) -} -``` - -You should call this function from `configure.swift` like this: - -```swift - services.register(Router.self) { container -> EngineRouter in - let router = EngineRouter.default() - try routes(router, container) - return router - } -``` - -For more information on routes, see the “Routes and Controllers” section. - -#### commands.swift - -Use this file to add your custom commands to your application. For example: - -```swift -import Vapor - -public func commands(config: inout CommandConfig) { - config.useFluentCommands() - - config.use(MyCustomCommand(), as: "my-custom-command") - ... -} -``` - -You should call this function from `configure.swift` like this: - -```swift - /// Command Config - var commandsConfig = CommandConfig.default() - commands(config: &commandsConfig) - services.register(commandsConfig) -``` - -> If your app doesn't use custom `Command`s you can omit this file. - -#### content.swift - -In this file you can customize the content encoding/decoding configuration for your data models. For example: - -```swift -import Vapor - -public func content(config: inout ContentConfig) throws { - let encoder = JSONEncoder() - let decoder = JSONDecoder() - - encoder.dateEncodingStrategy = .millisecondsSince1970 - decoder.dateDecodingStrategy = .millisecondsSince1970 - - config.use(encoder: encoder, for: .json) - config.use(decoder: decoder, for: .json) -} -``` - -You should call this function from `configure.swift` like this: - -```swift - /// Register Content Config - var contentConfig = ContentConfig.default() - try content(config: &contentConfig) - services.register(contentConfig) -``` - -> If you don't customize the content configuration you can omit this file. - -#### databases.swift - -Use this file to add the databases used in your application. Extracting this logic to a separate file keeps the configure.swift code clean, as it can often get quite long. This file should look something like this: - -```swift -import Vapor -import FluentMySQL //use your database driver here - -public func databases(config: inout DatabasesConfig) throws { - guard let databaseUrl = Environment.get("DATABASE_URL") else { - throw Abort(.internalServerError) - } - - guard let dbConfig = MySQLDatabaseConfig(url: databaseUrl) else { throw Abort(.internalServerError) } - - /// Register the databases - config.add(database: MySQLDatabase(config: dbConfig), as: .mysql) - - ... -} -``` - -And then call this function from `configure.swift` like this: - -```swift - /// Register the configured SQLite database to the database config. - var databasesConfig = DatabasesConfig() - try databases(config: &databasesConfig) - services.register(databasesConfig) -``` - -> If your app doesn't use `Fluent` you can omit this file. - -#### middlewares.swift - -In this file you can customize the middlewares of your application. For example: - -```swift -import Vapor - -public func middlewares(config: inout MiddlewareConfig) throws { - // config.use(FileMiddleware.self) // Serves files from `Public/` directory - config.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response - // Other Middlewares... -} -``` - -You should call this function from `configure.swift` like this: - -```swift - /// Register middlewares - var middlewaresConfig = MiddlewareConfig() - try middlewares(config: &middlewaresConfig) - services.register(middlewaresConfig) -``` - -#### migrate.swift - -Use this file to add the migrations to your database. Extracting this logic to a separate file keeps the configure.swift code clean, as it can often get quite long. This file should look something like this: - -```swift -import Vapor -import FluentMySQL //use your database driver here - -public func migrate(migrations: inout MigrationConfig) throws { - migrations.add(model: User.self, database: .mysql) //update this with your database driver -} -``` - -And then call this function from `configure.swift` like this: - -```swift - services.register { container -> MigrationConfig in - var migrationConfig = MigrationConfig() - try migrate(migrations: &migrationConfig) - return migrationConfig - } -``` - -As you continue to add models to your application, make sure that you add them to the migration file as well. - -> If your app doesn't use `Fluent` you can omit this file. - -#### repositories.swift - -The `repositories.swift` file is responsible for registering each repository during the configuration stage. This file should look like this: - -```swift -import Vapor - -public func setupRepositories(services: inout Services, config: inout Config) { - services.register(MySQLUserRepository.self) - preferDatabaseRepositories(config: &config) -} - -private func preferDatabaseRepositories(config: inout Config) { - config.prefer(MySQLUserRepository.self, for: UserRepository.self) -} -``` - -Call this function from `configure.swift` like this: - -```swift -setupRepositories(services: &services, config: &config) -``` - -For more information on the repository pattern, see the “Architecture” section. - -> If your app doesn't use `Fluent` you can omit this file. - -## Credentials - -Credentials are a crucial part to any production-ready application. The preferred way to manage secrets in a Vapor application is via environment variables. These variables can be set via the Xcode scheme editor for testing, the shell, or in the GUI of your hosting provider. - -**Credentials should never, under any circumstances, be checked into a source control repository.** - -Assuming we have the following credential storage service: - -```swift -import Vapor -struct APIKeyStorage: Service { - let apiKey: String -} -``` - -**Bad:** - -```swift -services.register { container -> APIKeyStorage in - return APIKeyStorage(apiKey: "MY-SUPER-SECRET-API-KEY") -} -``` - -**Good:** - -```swift -guard let apiKey = Environment.get("api-key") else { throw Abort(.internalServerError) } -services.register { container -> APIKeyStorage in - return APIKeyStorage(apiKey: apiKey) -} -``` - -## File Naming - -As the old saying goes, “the two hardest problems in computer science are naming things, cache invalidation, and off by one errors.” To minimize confusion and help increase readability, files should be named succinctly and descriptively. - -Files that contain objects used to decode body content from a request should be appended with `Request`. For example, `LoginRequest`. Files that contain objects used to encode body content to a response should be appended with `Response`. For example, `LoginResponse`. - -Controllers should also be named descriptively for their purpose. If your application contains logic for frontend responses and API responses, each controller’s name should denote their responsibility. For example, `LoginViewController` and `LoginAPIController`. If you combine the login functionality into one controller, opt for the more generic name: `LoginController`. - -## Architecture - -One of the most important decisions to make up front about your app is the style of architecture it will follow. It is incredibly time consuming and expensive to retroactively change your architecture. We recommend that production-level Vapor applications use the repository pattern. - -The basic idea behind the repository pattern is that it creates another abstraction between Fluent and your application code. Instead of using Fluent queries directly in controllers, this pattern encourages abstracting those queries into a more generic protocol and using that instead. - -There are a few benefits to this method. First, it makes testing a lot easier. This is because during the test environment you can easily utilize Vapor’s configuration abilities to swap out which implementation of the repository protocol gets used. This makes unit testing much faster because the unit tests can use a memory version of the protocol rather than the database. The other large benefit to this pattern is that it makes it really easy to switch out the database layer if needed. Because all of the ORM logic is abstracted to this piece of the application (and the controllers don’t know it exists) you could realistically swap out Fluent with a different ORM with minimal changes to your actual application/business logic code. This also means that you could switch out the specific database used during testing (i.e. using SQLite instead of MySQL). However, this strategy requires extra work to make your models generic as they can no longer conform the the specific database model types (e.g. `MySQLModel`). - -Here’s an example of a `UserRepository`: - -```swift -import Vapor -import FluentMySQL -import Foundation - -protocol UserRepository: ServiceType { - func find(id: Int) -> Future - func all() -> Future<[User]> - func find(email: String) -> Future - func findCount(email: String) -> Future - func save(user: User) -> Future -} - -final class MySQLUserRepository: UserRepository { - let db: MySQLDatabase.ConnectionPool - - init(_ db: MySQLDatabase.ConnectionPool) { - self.db = db - } - - func find(id: Int) -> EventLoopFuture { - return db.withConnection { conn in - return User.find(id, on: conn) - } - } - - func all() -> EventLoopFuture<[User]> { - return db.withConnection { conn in - return User.query(on: conn).all() - } - } - - func find(email: String) -> EventLoopFuture { - return db.withConnection { conn in - return User.query(on: conn).filter(\.email == email).first() - } - } - - func findCount(email: String) -> EventLoopFuture { - return db.withConnection { conn in - return User.query(on: conn).filter(\.email == email).count() - } - } - - func save(user: User) -> EventLoopFuture { - return db.withConnection { conn in - return user.save(on: conn) - } - } -} - -//MARK: - ServiceType conformance -extension MySQLUserRepository { - static let serviceSupports: [Any.Type] = [UserRepository.self] - - static func makeService(for worker: Container) throws -> Self { - return .init(try worker.connectionPool(to: .mysql)) - } -} - -extension Database { - public typealias ConnectionPool = DatabaseConnectionPool> -} -``` - -Then, in the controller: - -```swift -let repository = try req.make(UserRepository.self) -let userQuery = repository - .find(email: content.email) - .unwrap(or: Abort(.unauthorized, reason: "Invalid Credentials")) -``` - -In this example, the controller has no idea where the data is coming from, it only knows that it exists. This model has proven to be incredibly effective with Vapor and it is our recommended architecture. - -## Entities - -Oftentimes entities that come from the database layer need to be transformed to make them appropriate for a JSON response or for sending to the view layer. Sometimes these data transformations require database queries as well. If the transformation is simple, use a property and not a function. - -**Bad:** - -```swift -func publicUser() -> PublicUser { - return PublicUser(user: self) -} -``` - -**Good:** - -```swift -var `public`: PublicUser { - return PublicUser(user: self) -} -``` - -Transformations that require more complex processing (fetching siblings and add them to the object) should be functions that accept a DatabaseConnectable object: - -```swift -func userWithSiblings(on connectable: DatabaseConnectable) throws -> Future { - //do the processing here -} -``` - -
    - -We also recommend documenting all functions that exist on entities. - -Unless your entity needs to be database-generic, always conform the model to the most specific model type. - -**Bad:** - -```swift -extension User: Model { } -``` - -**Good:** - -```swift -extension User: MySQLModel { } -``` - -
    - -Extending the model with other conformances (Migration, Parameter, etc) should be done at the file scope via an extension. - -**Bad:** - -```swift -public final class User: Model, Parameter, Content, Migration { - //.. -} -``` - -**Good:** - -```swift -public final class User { - //.. -} - -extension User: MySQLModel { } -extension User: Parameter { } -extension User: Migration { } -extension User: Content { } -``` - -
    - -Property naming styles should remain consistent throughout all models. - -**Bad:** - -```swift -public final class User { - var id: Int? - var firstName: String - var last_name: String -} -``` - -**Good:** - -```swift -public final class User { - var id: Int? - var firstName: String - var lastName: String -} -``` - -As a general rule, try to abstract logic into functions on the models to keep the controllers clean. - -## Routes and Controllers - -We suggest combining your routes into your controller to keep everything central. Controllers serve as a jumping off point for executing logic from other places, namely repositories and model functions. - -Routes should be separated into functions in the controller that take a `Request` parameter and return a `ResponseEncodable` type. - -**Bad:** - -```swift -final class LoginViewController: RouteCollection { - func boot(router: Router) throws { - router.get("/login") { (req) -> ResponseEncodable in - return "" - } - } -} -``` - -**Good:** - -```swift -final class LoginViewController: RouteCollection { - func boot(router: Router) throws { - router.get("/login", use: login) - } - - func login(req: Request) throws -> String { - return "" - } -} -``` - -
    - -When creating these route functions, the return type should always be as specific as possible. - -**Bad:** - -```swift -func login(req: Request) throws -> ResponseEncodable { - return "string" -} -``` - -**Good:** - -```swift -func login(req: Request) throws -> String { - return "string" -} -``` - -
    - -When creating a path like `/user/:userId`, always use the most specific `Parameter` instance available. - -**Bad:** - -```swift -router.get("/user", Int.parameter, use: user) -``` - -**Good:** - -```swift -router.get("/user", User.parameter, use: user) -``` - -
    - -When decoding a request, opt to decode the `Content` object when registering the route instead of in the route. - -**Bad:** - -```swift -router.post("/update", use: update) - -func update(req: Request) throws -> Future { - return req.content.decode(User.self).map { user in - //do something with user - - return user - } -} -``` - -**Good:** - -```swift -router.post(User.self, at: "/update", use: update) - -func update(req: Request, content: User) throws -> Future { - return content.save(on: req) -} -``` - -Controllers should follow the thread-safe architecture when possible. This means passing necessary `Service`s to the controller on initialization instead of making them in the routes. - -**Bad:** - -```swift -final class LoginViewController: RouteCollection { - func boot(router: Router) throws { - router.get("/login", use: login) - } - - func login(req: Request) throws -> String { - let userRepository = try req.make(UserRepository.self) - //do something with it - - return "" - } -} -``` - -**Good:** - -```swift -final class LoginViewController: RouteCollection { - private let userRepository: UserRepository - - init(userRepository: UserRepository) { - self.userRepository = userRepository - } - - func boot(router: Router) throws { - router.get("/login", use: login) - } - - func login(req: Request) throws -> String { - //use `self.userRepository` - - return "" - } -} -``` - -Controllers should only cover one idea/feature at a time. If a feature grows to encapsulate a large amount of functionality, routes should be split up into multiple controllers and organized under one common feature folder in the `Controllers` folder. For example, an app that handles generating a lot of analytical/reporting views should break up the logic by specific report to avoid cluttering a generic `ReportsViewController.swift` - -## Async - -Where possible, avoid specifying the type information in flatMap and map calls. - -**Bad:** - -```swift -let stringFuture: Future -return stringFuture.map(to: Response.self) { string in - return req.redirect(to: string) -} -``` - -**Good:** - -```swift -let stringFuture: Future -return stringFuture.map { string in - return req.redirect(to: string) -} -``` - -
    - -When returning two objects from a chain to the next chain, use the `and(result: )` function to automatically create a tuple instead of manually creating it (the Swift compiler will most likely require return type information in this case) - -**Bad:** - -```swift -let stringFuture: Future -return stringFuture.flatMap(to: (String, String).self) { original in - let otherStringFuture: Future - - return otherStringFuture.map { other in - return (other, original) - } -}.map { other, original in - //do something -} -``` - -**Good:** - -```swift -let stringFuture: Future -return stringFuture.flatMap(to: (String, String).self) { original in - let otherStringFuture: Future - return otherStringFuture.and(result: original) -}.map { other, original in - //do something -} -``` - -
    - -When returning more than two objects from one chain to the next, do not rely on the `and(result )` method as it can only create, at most, a two object tuple. Use a nested `map` instead. - -**Bad:** - -```swift -let stringFuture: Future -let secondFuture: Future - -return flatMap(to: (String, (String, String)).self, stringFuture, secondFuture) { first, second in - let thirdFuture: Future - return thirdFuture.and(result: (first, second)) -}.map { other, firstSecondTuple in - let first = firstSecondTuple.0 - let second = firstSecondTuple.1 - //do something -} -``` - -**Good:** - -```swift -let stringFuture: Future -let secondFuture: Future - -return flatMap(to: (String, String, String).self, stringFuture, secondFuture) { first, second in - let thirdFuture: Future - return thirdFuture.map { third in - return (first, second, third) - } -}.map { first, second, third in - //do something -} -``` - -
    - -Always use the global `flatMap` and `map` methods to execute futures concurrently when the functions don’t need to wait on each other. - -**Bad:** - -```swift -let stringFuture: Future -let secondFuture: Future - -return stringFuture.flatMap { string in - print(string) - return secondFuture -}.map { second in - print(second) - //finish chain -} -``` - -**Good:** - -```swift -let stringFuture: Future -let secondFuture: Future - -return flatMap(to: Void.self, stringFuture, secondFuture) { first, second in - print(first) - print(second) - - return .done(on: req) -} -``` - -
    - -Avoid nesting async functions more than once per chain, as it becomes unreadable and unsustainable. - -**Bad:** - -```swift -let stringFuture: Future - -return stringFuture.flatMap { first in - let secondStringFuture: Future - - return secondStringFuture.flatMap { second in - let thirdStringFuture: Future - - return thirdStringFuture.flatMap { third in - print(first) - print(second) - print(third) - - return .done(on: req) - } - } -} -``` - -**Good:** - -```swift -let stringFuture: Future - -return stringFuture.flatMap(to: (String, String).self) { first in - let secondStringFuture: Future - return secondStringFuture.and(result: first) -}.flatMap { second, first in - let thirdStringFuture: Future - - //it's ok to nest once - return thirdStringFuture.flatMap { third in - print(first) - print(second) - print(third) - - return .done(on: req) - } -} -``` - -
    - -Use `transform(to: )` to avoid chaining an extra, unnecessary level. - -**Bad:** - -```swift -let stringFuture: Future - -return stringFuture.map { _ in - return .ok -} -``` - -**Good:** - -```swift -let stringFuture: Future -return stringFuture.transform(to: .ok) -``` - -
    - -Avoid synchronous throwing where possible as it can lead to undefined behavior in the route, specifically because running IO operations may not finish before the route ends. - -**Bad:** - -```swift -func index(_ req: Request) throws -> Future { - let models = SomeModel.query(on: req).all() - - guard someCondition else { throw SomeError(...) } - - return req.view().render("someView", ["models": models]) -} -``` - -**Good:** -```swift -func index(_ req: Request) throws -> Future { - let modelsFuture = SomeModel.query(on: req).all() - return modelsFuture.flatMap { models in - guard someCondition else { throw SomeError(...) } - return req.view().render("someView", ["models": models]) - } -} -``` - -## Testing - -Testing is a crucial part of Vapor applications that helps ensure feature parity across versions. We strongly recommend testing for all Vapor applications. - -While testing routes, avoid changing behavior only to accommodate for the testing environment. Instead, if there is functionality that should differ based on the environment, you should create a service and swap out the selected version during the testing configuration. - -**Bad:** - -```swift -func login(req: Request) throws -> Future { - if req.environment != .testing { - try req.verifyCSRF() - } - - //rest of the route -} -``` - -**Good:** - -```swift -func login(req: Request) throws -> Future { - let csrf = try req.make(CSRF.self) - try csrf.verify(req: req) - //rest of the route -} -``` - -Note how the correct way of handling this situation includes making a service - this is so that you can mock out fake functionality in the testing version of the service. - -Every test should setup and teardown your database. **Do not** try and persist state between tests. - -Tests should be separated into unit tests and integration. If using the repository pattern, the unit tests should use the memory version of the repositories while the integration tests should use the database version of the repositories. - -## Fluent - -ORMs are notorious for making it really easy to write bad code that works but is terribly inefficient or incorrect. Fluent tends to minimize this possibility thanks to the usage of features like KeyPaths and strongly-typed decoding, but there are still a few things to watch out for. - -Actively watch out for and avoid code that produces N+1 queries. Queries that have to be run for every instance of a model are bad and typically produce N+1 problems. Another identifying feature of N+1 code is the combination of a loop (or `map`) with `flatten`. - -**Bad:** - -```swift -//assume this is filled and that each owner can have one pet -let owners = [Owner]() -var petFutures = [Future]() - -for owner in owners { - let petFuture = try Pet.find(owner.petId, on: req).unwrap(or: Abort(.badRequest)) - petFutures.append(petFuture) -} - -let allPets = petFutures.flatten(on: req) -``` - -**Good:** - -```swift -//assume this is filled and that each owner can have one pet -let owners = [Owner]() -let petIds = owners.compactMap { $0.petId } -let allPets = try Pet.query(on: req).filter(\.id ~~ petIds).all() -``` - -Notice the use of the `~~` infix operator which creates an `IN` SQL query. - -
    - -In addition to reducing Fluent inefficiencies, opt for using native Fluent queries over raw queries unless your intended query is too complex to be created using Fluent. - -**Bad:** - -```swift -conn.raw("SELECT * FROM users;") -``` - -**Good:** - -```swift -User.query(on: req).all() -``` - -## Leaf - -Creating clean, readable Leaf files is important. One of the ways to go about doing this is through the use of base templates. Base templates allow you to specify only the different part of the page in the main leaf file for that view, and then base template will sub in the common components of the page (meta headers, the page footer, etc). For example: - -`base.leaf` - -```html - - - - - - - - - #get(title) - - - #get(body) - #embed("Views/footer") - - -``` - -Notice the calls to `#get` and `#embed` which piece together the supplied variables from the view and create the final HTML page. - -`login.leaf` - -```html -#set("title") { Login } - -#set("body") { -

    Add your login page here

    -} - -#embed("Views/base") -``` - -In addition to extracting base components to one file, you should also extract common components to their own file. For example, instead of repeating the snippet to create a bar graph, put it inside of a different file and then use `#embed()` to pull it into your main view. - -Always use `req.view()` to render the views for your frontend. This will ensure that the views will take advantage of caching in production mode, which dramatically speeds up your frontend responses. - -## Errors - -Depending on the type of application you are building (frontend, API-based, or hybrid) the way that you throw and handle errors may differ. For example, in an API-based system, throwing an error generally means you want to return it as a response. However, in a frontend system, throwing an error most likely means that you will want to handle it further down the line to give the user contextual frontend information. - -As a general rule of thumb, conform all of your custom error types to Debuggable. That helps `ErrorMiddleware` print better diagnostics and can lead to easier debugging. - -**Bad:** - -```swift -enum CustomError: Error { - case error -} -``` - -**Good:** - -```swift -enum CustomError: Debuggable { - case error - - //MARK: - Debuggable - var identifier: String { - switch self { - case .error: return "error" - } - } - - var reason: String { - switch self { - case .error: return "Specify reason here" - } - } -} -``` - -
    - -Include a `reason` when throwing generic `Abort` errors to indicate the context of the situation. - -**Bad:** - -```swift -throw Abort(.badRequest) -``` - -**Good:** - -```swift -throw Abort(.badRequest, reason: "Could not get data from external API.") -``` - -## 3rd Party Providers -When building third party providers for Vapor, it's important to have a certain consistency that users will be able to become familiar with when switching or adding new providers. Although Vapor is very young, there are already certain patterns that make sense when writing providers. - -When creating a provider library, you should omit phrases like `Provider` or `Package`. Take the StripeProvider for example, while the name of the project itself can be named `StripeProvider` the library name should be just the product itself: -```swift -let package = Package( - name: "StripeProvider", - products: [ - .library(name: "Stripe", targets: ["Stripe"]) - ], -) -``` -This allows for easy to read and clean import statements: -`import Stripe` rather than `import StripeProvider`. - - -## Overall Advice - -- Use `//MARK:` to denote sections of your controllers or configuration so that it is easier for other project members to find critically important areas. -- Only import modules that are needed for that specific file. Adding extra modules creates bloat and makes it difficult to deduce that controller’s responsibility. -- Where possible, use Swift doc-blocks to document methods. This is especially important for methods implements on entities so that other project members understand how the function affects persisted data. -- Do not retrieve environment variables on a repeated basis. Instead, use a custom service and register those variables during the configuration stage of your application (see “Configuration”) -- Reuse `DateFormatters` where possible (while also maintaining thread safety). In particular, don’t create a date formatter inside of a loop as they are expensive to make. -- Store dates in a computer-readable format until the last possible moment when they must be converted to human-readable strings. That conversion is typically very expensive and is unnecessary when passing dates around internally. Offloading this responsibility to JavaScript is a great tactic as well if you are building a front-end application. -- Eliminate stringly-typed code where possible by storing frequently used strings in a file like `Constants.swift`. - - -## Maintainers - -This style guide was written and is maintained by the following Vapor members: - -- Andrew ([@andrewangeta](https://github.com/andrewangeta)) -- Jimmy ([@mcdappdev](https://github.com/mcdappdev)) (Project manager) -- Jonas ([@joscdk](https://github.com/joscdk)) -- Tanner ([@tanner0101](https://github.com/tanner0101)) -- Tim ([@0xtim](https://github.com/0xtim)) -- Gustavo ([@gperdomor](https://github.com/gperdomor)) diff --git a/3.0/docs/extras/yeoman.md b/3.0/docs/extras/yeoman.md deleted file mode 100644 index 7fd9f90d..00000000 --- a/3.0/docs/extras/yeoman.md +++ /dev/null @@ -1,37 +0,0 @@ -# Yo Vapor - -Yeoman is a popular scaffolding tool distributed through NPM. There is a beta Vapor 3 generator maintained in Vapor Community ([vapor-community/yeoman](https://github.com/vapor-community/yeoman)) that you can use to create new projects. - -![screen shot 2018-10-30 at 2 19 59 pm](https://user-images.githubusercontent.com/1342803/47740945-777ce100-dc4f-11e8-9cde-4a09c518dbf4.png) - -## Installation - -First, install [Yeoman](http://yeoman.io) and generator-vapor using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)). - -```bash -npm install -g yo generator-vapor -``` - -## Create New Project - -Once you have installed Yeoman and the Vapor Generator, you can create a new project. - -First, create a new folder and `cd` into it: - -```bash -mkdir my-project -cd my-project -``` - -Then, use the `yo` command to generate your new project: - -```bash -yo vapor -``` - - -Follow the instructions to choose which packages you would like to use. - -## Contributing - -Help us improve this generator by contributing to [vapor-community/yeoman](https://github.com/vapor-community/yeoman). diff --git a/3.0/docs/fluent/getting-started.md b/3.0/docs/fluent/getting-started.md deleted file mode 100644 index 37b31dba..00000000 --- a/3.0/docs/fluent/getting-started.md +++ /dev/null @@ -1,205 +0,0 @@ -# Getting Started with Fluent - -Fluent ([vapor/fluent](https://github.com/vapor/fluent)) is a type-safe, fast, and easy-to-use ORM framework built for Swift. -It takes advantage of Swift's strong type system to provide an elegant foundation for building database integrations. - -## Choosing a Driver - -Fluent is a framework for building ORMs, not an ORM itself. To use Fluent, you will first need to choose a database driver to use. Fluent can support multiple databases and database drivers per application. - -### Official Fluent Drivers - -Below is a list of officially supported database drivers for Fluent. - -|database|repo|version|dbid|notes| -|-|-|-|-|-| -|PostgreSQL|[fluent-postgresql](https://github.com/vapor/fluent-postgresql.git)|1.0.0|`psql`|**Recommended**. Open source, standards compliant SQL database. Available on most cloud hosting providers.| -|MySQL|[fluent-mysql](https://github.com/vapor/fluent-mysql)|3.0.0|`mysql`|Popular open source SQL database. Available on most cloud hosting providers. This driver also supports MariaDB.| -|SQLite|[fluent-sqlite](https://github.com/vapor/fluent-sqlite)|3.0.0|`sqlite`|Open source, embedded SQL database. Its simplistic nature makes it a great candiate for prototyping and testing.| -|MongoDB|fluent-mongo|n/a|`mongo`|Coming soon. Popular NoSQL database.| - -### Community Drivers - -And here are community-maintained drivers. These are all open-source Fluent drivers, but you should reach out to these projects with questions and pull requests! - -|database|repo|version|dbid|notes| -|-|-|-|-|-| -|DynamoDB|[fluent-dynamodb](https://github.com/Yasumoto/fluent-dynamodb)|3.0.0|`dynamodb`|Amazon's hosted [key-value store](https://aws.amazon.com/dynamodb/) available through AWS| - -!!! note - Replace any Xcode placeholders (`<#...#>`) in the code snippets below with information from the above table. - -You can search GitHub for the tag [`fluent-database`](https://github.com/topics/fluent-database) for a full list of official and third-party Fluent database drivers. - -### Package - -Once you have decided which driver you want, the next step is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - .package(url: "https://github.com/vapor/<#repo#>.git", from: "<#version#>"), - ], - targets: [ - .target(name: "App", dependencies: ["Fluent<#Database#>", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -```sh -vapor xcode -``` - -## Creating a Model - -Now let's create your first model. Models represent tables in your database and they are the primary method of interacting with your data. - -Each driver provides convenience model protocols (`PostgreSQLModel`, `SQLiteModel`, etc) that extend Fluent's base [`Model`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html) protocol. These convenience types make declaring models more concise by using standard values for ID key and type. - -Fill in the Xcode placeholders below with the name of your chosen database, i.e., `PostgreSQL`. - -```swift -import Fluent<#Database#> -import Vapor - -/// A simple user. -final class User: <#Database#>Model { - /// The unique identifier for this user. - var id: ID? - - /// The user's full name. - var name: String - - /// The user's current age in years. - var age: Int - - /// Creates a new user. - init(id: ID? = nil, name: String, age: Int) { - self.id = id - self.name = name - self.age = age - } -} - -extension User: Content { } -``` - -The example above shows a simple model representing a user. You can make both structs and classes a model. You can even conform types that come from external modules. The only requirement is that these types conform to `Codable`, which must be declared on the base type for synthesized (automatic) conformance. - -*Note:* `Content` conformance will ensure that the object can be encoded and decoded from HTTP messages. This will be necessary when performing a query. - -Take a look at [Fluent → Model](models.md) for more information on creating models with custom ID types and keys. - -## Configuring the Database - -Now that you have a model, you can configure your database. This is done in [`configure.swift`](../getting-started/structure.md#configureswift). - -### Register Provider - -The first step is to register your database driver's provider. - -```swift -import Fluent<#Database#> -import Vapor - -// Register providers first -try services.register(Fluent<#Database#>Provider()) - -// Other services.... -``` - -Registering the provider will add all of the services required for your Fluent database to work properly. It also includes a default database config struct that uses typical development environment credentials. - -### Custom Credentials - -If you are using default configuration for your database (such as default credentials or other config) then this may be the only setup you need to perform. - -See the documentation for your specific database type for more information about custom configuration. - -|database|docs|api docs| -|-|-|-| -|PostgreSQL|[PostgreSQL → Getting Started](../postgresql/getting-started.md)|[`PostgreSQLDatabase`](https://api.vapor.codes/postgresql/latest/PostgreSQL/Classes/PostgreSQLDatabase.html)| -|MySQL|[MySQL → Getting Started](../mysql/getting-started.md)|[`MySQLDatabase`](https://api.vapor.codes/mysql/latest/MySQL/Classes/MySQLDatabase.html)| -|SQLite|[SQLite → Getting Started](../sqlite/getting-started.md)|[`SQLiteDatabase`](https://api.vapor.codes/sqlite/latest/SQLite/Classes/SQLiteDatabase.html)| - -## Creating a Migration - -If your database driver uses schemas (is a SQL database), you will need to create a [`Migration`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Migration.html) for your new model. Migrations allow Fluent to create a table for your model in a reliable, testable way. You can later create additional migrations to update or delete the model's table or even manipulate data in the table. - - To create a migration, you will normally first create a new struct or class to hold the migration. However, models can take advantage of a convenient shortcut. When you create a migration from an existing model type, Fluent can infer an appropriate schema from the model's codable properties. - -You can add the migration conformance to a model as an extension or on the base type declaration. - -```swift -import Fluent<#Database#> -import Vapor - -extension User: <#Database#>Migration { } -``` - -Take a look at [Fluent → Migration](../fluent/migrations.md) if you are interested in learning more about custom migrations. - -### Configuring Migrations - -Once you have created a migration, you must register it to Fluent using [`MigrationConfig`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html). This is done in [`configure.swift`](../getting-started/structure.md#configureswift). - -Fill in the database ID (`dbid`) from the table above, i.e., `psql`. - -```swift -import Fluent<#Database#> -import Vapor - -// Configure migrations -var migrations = MigrationConfig() -migrations.add(model: User.self, database: .<#dbid#>) -services.register(migrations) - -// Other services.... -``` - -!!! tip - If the migration you are adding is also a model, you can use the [`add(model:on:)`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html#/s:6Fluent15MigrationConfigV3add5model8databaseyxm_11DatabaseKit0G10IdentifierVy0G0AA0B0PQzGtAaKRzAA5ModelRzAjaOPQzAMRSlF) convenience to automatically set the model's [`defaultDatabase`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE15defaultDatabase0D3Kit0D10IdentifierVy0D0QzGSgvpZ) property. Otherwise, use the [`add(migration:on)`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html#/s:6Fluent15MigrationConfigV3add9migration8databaseyxm_11DatabaseKit0G10IdentifierVy0G0QzGtAA0B0RzlF) method. - -Once you have the `MigrationConfig` added, you should be able to run your application and see the following: - -```sh -Migrating <#dbid#> DB -Migrations complete -Server starting on http://localhost:8080 -``` - -## Performing a Query - -```swift -router.get("users") { req in - return User.query(on: req).all() -} -``` - -If you run your app, and query that route, you should see an empty array returned. Now you just need to add some users! Congratulations on getting your first Fluent model working. - -## Raw Queries - -With Fluent, you always have access to the underlying database driver. Using this underlying driver to perform a query is sometimes called a "raw query". - -To perform raw queries, you need access to a database connection. Vapor's [`Request`](https://api.vapor.codes/vapor/latest/Vapor/Classes/Request.html) type has a number of conveniences for creating new database connections. The recommended method is `withPooledConnection(to:)`. Learn about other methods in [DatabaseKit → Overview → Connections](../../database-kit/overview/#connections). - -```swift -router.get("raw") { req -> Future in - return req.withPooledConnection(to: .<#dbid#>) { conn in - // perform raw query using conn - } -} -``` - -Once you have the database connection, you can perform a query on it. You can learn more about the methods available in the database's documentation. - diff --git a/3.0/docs/fluent/migrations.md b/3.0/docs/fluent/migrations.md deleted file mode 100644 index 73c5dd93..00000000 --- a/3.0/docs/fluent/migrations.md +++ /dev/null @@ -1,263 +0,0 @@ -# Fluent Migrations - -Migrations allow you to make organized, testable, and reliable changes to your database's structure-- -even while it's in production. - -Migrations are often used for preparing a database schema for your models. However, they can also be used to -make normal queries to your database. - -In this guide we will cover creating both types of migrations. - -## Creating and Deleting Schemas - -Let's take a look at how we can use migrations to prepare a schema supporting database to store a theoretical `Galaxy` model. - -Fill in the Xcode placeholders below with your database's name from [Getting Started → Choosing a Driver](../getting-started/#choosing-a-driver). - -```swift -import Fluent<#Database#> - -struct Galaxy: <#Database#>Model { - var id: ID? - var name: String -} -``` - -### Automatic Model Migrations - -Models provide a shortcut for declaring database migrations. If you conform a type that conforms to [`Model`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html) to [`Migration`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Migration.html), Fluent can infer the model's properties and automatically implement the `prepare(...)` and `revert(...)` methods. - -```swift -import Fluent<#Database#> - -extension Galaxy: <#Database#>Migration { } -``` - -This method is especially useful for quick prototyping and simple setups. For most other situations you should consider creating a normal, custom migration. - -Add this automatic migration to your [`MigrationConfig`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html) using the `add(model:database:)` method. This is done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -var migrations = MigrationConfig() -migrations.add(model: Galaxy.self, database: .<#dbid#>) -services.register(migrations) -``` - -The `add(model:database:)` method will automatically set the model's [`defaultDatabase`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE15defaultDatabase0D3Kit0D10IdentifierVy0D0QzGSgvpZ) property. - -### Custom Migrations - -We can customize the table created for our model by creating a migration and using the static `create` and `delete` methods on [`Database`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/Database.html). - -```swift -import Fluent<#Database#> - -struct CreateGalaxy: <#Database#>Migration { - // ... -} -``` - -#### Creating a Schema - -The most important method in a migration is `prepare(...)`. This is responsible for effecting the migration's changes. For our `CreateGalaxy` migration, we will use our database's static `create` method to create a schema. - -```swift -import Fluent<#Database#> - -struct CreateGalaxy: <#Database#>Migration { - // ... - - static func prepare(on conn: <#Database#>Connection) -> Future { - return <#Database#>Database.create(Galaxy.self, on: conn) { builder in - builder.field(for: \.id, isIdentifier: true) - builder.field(for: \.name) - } - } -} -``` - -To create a schema, you must pass a model type and connection as the first two parameters. The third parameter is a closure that accepts the [`SchemaBuilder`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/SchemaBuilder.html). This builder has convenience methods for declaring fields in the schema. - -You can use the `field(for: <#KeyPath#>)` method to quickly create fields for each of your model's properties. Since this method accepts key paths to the model (indicated by `\.`), Fluent can see what type those properties are. For most common types (`String`, `Int`, `Double`, etc) Fluent will automatically be able to determine the best database field type to use. - -You can also choose to manually select which database field type to use for a given field. - -```swift -try builder.field(for: \.name, type: <#DataType#>) -``` - -Each database has it's own unique data types, so refer to your database's documentation for more information. - -|database|docs|api docs| -|-|-|-| -|PostgreSQL|[PostgreSQL → Getting Started](../postgresql/getting-started.md)|[`PostgreSQLDataType`](https://api.vapor.codes/postgresql/latest/PostgreSQL/Structs/PostgreSQLDataType.html)| -|MySQL|[MySQL → Getting Started](../mysql/getting-started.md)|[`MySQLDataType`](https://api.vapor.codes/mysql/latest/MySQL/Structs/MySQLDataType.html)| -|SQLite|[SQLite → Getting Started](../sqlite/getting-started.md)|[`SQLiteDataType`](https://api.vapor.codes/sqlite/latest/SQLite/Enums/SQLiteDataType.html)| - -#### Deleting a Schema - -Each migration should also include a method for _reverting_ the changes it makes. It is used when you boot your -app with the `--revert` option. - -For a migration that creates a table in the database, the reversion is quite simple: delete the table. - -To implement `revert` for our model, we can use our database's static `delete(...)` method to indicate that we would like to delete the schema. - -```swift -import Fluent<#Database#> - -struct CreateGalaxy: <#Database#>Migration { - // ... - static func revert(on connection: <#Database#>Connection) -> Future { - return <#Database#>Database.delete(Galaxy.self, on: connection) - } -} -``` - -To delete a schema, you pass a model type and connection as the two required parameters. That's it. - -You can always choose to skip a reversion by simplying returning `conn.future(())`. But note that they are especially useful when testing and debugging your migrations. - -Add this custom migration to your [`MigrationConfig`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html) using the `add(migration:database:)` method. This is done in your [`configure.swift`](../getting-started/structure.md#configureswift) file. - -```swift -var migrations = MigrationConfig() -migrations.add(migration: CreateGalaxy.self, database: .<#dbid#>) -services.register(migrations) -``` -Make sure to also set the `defaultDatabase` property on your model when using a custom migration. - -```swift -Galaxy.defaultDatabase = .<#dbid#> -``` - -## Updating a Schema - -After you deploy your application to production, you may find it necessary to add or remove fields on an existing model. You can achieve this by creating a new migration. - -For this example, let's assume we want to add a new property `mass` to the `Galaxy` model from the previous section. - -```swift -import Fluent<#Database#> - -struct Galaxy: <#Database#>Model { - var id: ID? - var name: String - var mass: Int -} -``` - -Since our previous migration created a table with fields for both `id` and `name`, we need to update that table and add a field for `mass`. We can do this by using the static `update` method on [`Database`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/Database.html). - -```swift -import Fluent<#Database#> - -struct AddGalaxyMass: <#Database#>Migration { - // ... -} -``` - -Our prepare method will look very similar to the prepare method for a new table, except it will only contain our newly added field. - -```swift -struct AddGalaxyMass: <#Database#>Migration { - // ... - - static func prepare(on conn: <#Database#>Connection) -> Future { - return <#Database#>Database.update(Galaxy.self, on: conn) { builder in - builder.field(for: \.mass) - } - } -} -``` - -*Note:* In SQLite and maybe other databases that have strict NULL / NOT NULL constraint, you may receive the following error: - -> Cannot add a NOT NULL column with default value NULL - -Since we declared our `mass` property non-optional (`Int`), we will have to add a special configuration that declares its default value as well. In SQL, this is equivalent to the `DEFAULT` keyword. Therefore, to fix the error, the above code would become: - -```swift -struct AddGalaxyMass: SQLiteMigration { - // ... - - static func prepare(on conn: SQLiteConnection) -> Future { - return SQLiteDatabase.update(Galaxy.self, on: conn) { builder in - let defaultValueConstraint = SQLiteColumnConstraint.default(.literal(0)) - builder.field(for: \.mass, type: .integer, defaultValueConstraint) - } - } -} -``` - -All methods available when creating a schema will be available while updating alongside some new methods for deleting fields. See [`SchemaUpdater`](https://api.vapor.codes/fluent/latest/Fluent/Classes/SchemaUpdater.html) for a list of all available methods. - -To revert this change, we must delete the `mass` field from the table. - -```swift -struct AddGalaxyMass: <#Database#>Migration { - // ... - - static func revert(on conn: <#Database#>Connection) -> Future { - return <#Database#>Database.update(Galaxy.self, on: conn) { builder in - builder.deleteField(for: \.mass) - } - } -} -``` - -Add this migration to your [`MigrationConfig`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html) using the `add(migration:database:)` method. This is done in your [`configure.swift`](../getting-started/structure.md#configureswift) file. - -```swift -var migrations = MigrationConfig() -// ... -migrations.add(migration: AddGalaxyMass.self, database: .<#dbid#>) -services.register(migrations) -``` - -## Migrating Data - -While migrations are useful for creating and updating schemas in SQL databases, they can also be used for more general purposes in any database. Migrations are passed a connection upon running which can be used to perform arbitrary database queries. - -For this example, let's assume we want to do a data cleanup migration on our `Galaxy` model and delete any galaxies with a mass of `0`. - -The first step is to create our new migration type. - -```swift -struct GalaxyMassCleanup: <#Database#>Migration { - // ... -} -``` - -In the prepare method of this migration, we will perform a query to delete all galaxies which have a mass equal to `0`. - -```swift -struct GalaxyMassCleanup: <#Database#>Migration { - static func prepare(on conn: <#Database#>Connection) -> Future { - return Galaxy.query(on: conn).filter(\.mass == 0).delete() - } - - // ... -} -``` - -There is no way to undo this migration since it is destructive. You can omit the `revert(...)` method by returning a pre-completed future. - -```swift -struct GalaxyMassCleanup: <#Database#>Migration { - // ... - - static func revert(on conn: <#Database#>Connection) -> Future { - return conn.future(()) - } -} -``` - -Add this migration to your [`MigrationConfig`](https://api.vapor.codes/fluent/latest/Fluent/Structs/MigrationConfig.html) using the `add(migration:database:)` method. This is done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -var migrations = MigrationConfig() -// ... -migrations.add(migration: GalaxyMassCleanup.self, database: .<#dbid#>) -services.register(migrations) -``` diff --git a/3.0/docs/fluent/models.md b/3.0/docs/fluent/models.md deleted file mode 100644 index 2c84a2a6..00000000 --- a/3.0/docs/fluent/models.md +++ /dev/null @@ -1,297 +0,0 @@ -# Fluent Models - -Models are the heart of Fluent. Unlike ORMs in other languages, Fluent doesn't return untyped arrays or dictionaries for queries. Instead, you query the database using models. This allows the Swift compiler to catch many errors that have burdened ORM users for ages. - -!!! info - This guide provides an overview of the `Model` protocol and its associated methods and properties. If you are just getting started, check out the guide for your database at [Fluent → Getting Started](getting-started.md). - -`Model` is a protocol in the `Fluent` module. It extends the `AnyModel` protocol which can be used for type-erasure. - -## Conformance - -Both `struct`s and `class`es can conform to `Model`, however you must pay special attention to Fluent's return types if you use a `struct`. Since Fluent works asynchronously, any mutations to a value-type (`struct`) model must return a new copy of the model as a future result. - -Normally, you will conform your model to one of the convenience models available in your database-specific package (i.e., `PostgreSQLModel`). However, if you want to customize additional properties, such as the model's `idKey`, you will want to use the `Model` protocol itself. - -Let's take a look at what a basic `Model` conformance looks like. - -```swift -/// A simple user. -final class User: Model { - /// See `Model.Database` - typealias Database = FooDatabase - - /// See `Model.ID` - typealias ID = Int - - /// See `Model.idKey` - static let idKey: IDKey = \.id - - /// The unique identifier for this user. - var id: Int? - - /// The user's full name. - var name: String - - /// The user's current age in years. - var age: Int - - /// Creates a new user. - init(id: Int? = nil, name: String, age: Int) { - self.id = id - self.name = name - self.age = age - } -} -``` - -!!! tip - Using `final` prevents your class from being sub-classed. This makes your life easier. - -## Associated Types - -`Model` defines a few associated types that help Fluent create type-safe APIs for you to use. Take a look at `AnyModel` if you need a type-erased version with no associated types. - -### Database - -This type indicates to Fluent which database you intend to use with this model. Using this information, Fluent can dynamically add appropriate methods and data types to any `QueryBuilder`s you create with this model. - -```swift -final class User: Model { - /// See `Model.Database` - typealias Database = FooDatabase - /// ... -} -``` - -It is possible to make this associated type generic by adding a generic type to your class or struct (i.e, `User`). This is useful for cases where you are attempting to create generic extensions to Fluent, like perhaps an additive service provider. - -```swift -final class User: Model where D: Database { - /// See `Model.Database` - typealias Database = D - /// ... -} -``` - -You can add further conditions to `D`, such as `QuerySupporting` or `SchemaSupporting`. You can also dynamically extend and conform your generic model using `extension User where D: ... { }`. - -That said, for most cases, you should stick to using a concrete type-alias wherever possible. Fluent 3 is designed to allow you to harness the power of your database by creating a strong connection between your models and the underlying driver. - -### ID - -This property defines the type your model will use for its unique identifier. - -```swift -final class User: Model { - /// See `Model.ID` - typealias ID = UUID - /// ... -} -``` - -This will usually be something like `Int`, `UUID`, or `String` although you can theoretically use any type you like. - -## Properties - -There are several overridable properties on `Model` that you can use to customize how Fluent interacts with your database. - -### Name - -This `String` will be used as a unique identifier for your model whenever Fluent needs one. - -```swift -final class User: Model { - /// See `Model.name` - static let name = "user" - /// ... -} -``` - -By default, this is the type name of your model. - -### Entity - -Entity is a generic word used to mean either "table" or "collection", depending on which type of backend you are using for Fluent. - -```swift -final class Goose: Model { - /// See `Model.entity` - static let entity = "geese" - /// ... -} -``` - -By default, this property will be [name](#name). - -### ID Key - -The ID key is a writeable [key path](https://github.com/apple/swift-evolution/blob/master/proposals/0161-key-paths.md) that points to your model's unique identifier property. - -Usually this will be a property named `id` (for some databases it is `_id`). However you can theoretically use any key you like. - -```swift -final class User: Model { - /// See `Model.ID` - typealias ID = String - - /// See `Model.entity` - static let idKey = \.username - - /// The user's unique username - var username: String? - - /// ... -} -``` - -The `idKey` property must point to an optional, writeable (`var`) property with type matching [ID](#id). - -## Lifecycle - -There are several lifecycle methods on `Model` that you can override to hook into Fluent events. - -|method |description |throwing | -|------------|-----------------------------------------------------------|----------------------------| -|`willCreate`|Called before Fluent saves your model (for the first time) |Cancels the save. | -|`didCreate` |Called after Fluent saves your model (for the first time) |Save completes. Query fails.| -|`willUpdate`|Called before Fluent saves your model (subsequent saves) |Cancels the save. | -|`didUpdate` |Called after Fluent saves your model (subsequent saves) |Save completes. Query fails.| -|`willRead` |Called before Fluent returns your model from a fetch query.|Cancels the fetch. | -|`willDelete`|Called before Fluent deletes your model. |Cancels the delete. | - -Here's an example of overriding the `willUpdate(on:)` method. - -```swift -final class User: Model { - /// ... - - /// See `Model.willUpdate(on:)` - func willUpdate(on connection: Database.Connection) throws -> Future { - /// Throws an error if the username is invalid - try validateUsername() - - /// Return the user. No async work is being done, so we must create a future manually. - return Future.map(on: connection) { self } - } -} -``` - -## CRUD - -The model offers basic CRUD method (create, read, update, delete). - -### Create - -This method creates a new row / item for an instance of your model in the database. - -If your model does not have an ID, calls to `.save(on:)` will redirect to this method. - -```swift -let didCreate = user.create(on: req) -print(didCreate) /// Future -``` -!!! info - If you are using a value-type (`struct`), the instance of your model returned by `.create(on:)` will contain the model's new ID. - -### Read - -Two methods are important for reading your model from the database, `find(_:on:)` and `query(on:)`. - -```swift -/// Finds a user with ID == 1 -let user = User.find(1, on: req) -print(user) /// Future -``` - -```swift -/// Finds all users with name == "Vapor" -let users = User.query(on: req).filter(\.name == "Vapor").all() -print(users) /// Future<[User]> -``` - -### Update - -This method updates the existing row / item associated with an instance of your model in the database. - -If your model already has an ID, calls to `.save(on:)` will redirect to this method. - -```swift -/// Updates the user -let didUpdate = user.update(on: req) -print(didUpdate) /// Future -``` - -### Delete - -This method deletes the existing row / item associated with an instance of your model from the database. - -```swift -/// Deletes the user -let didDelete = user.delete(on: req) -print(didDelete) /// Future -``` - -## Creation and Last Update Timestamps - -Often an application requires knowing when a row / item is created or gets updated. If the model specifies fields for storing creation and/or update timestamps, Fluent will automatically set these values. The model simply specifies the name of the field and a matching definition. - -```swift -static let createdAtKey: TimestampKey? = \.createdAt -static let updatedAtKey: TimestampKey? = \.updatedAt - -var createdAt: Date? -var updatedAt: Date? -``` - -The presence of `createdAtKey` causes the associated field to be set when the object is created. This field will not be subsequently changed. The presence of `updatedAtKey` causes the associated field to be set when the object is created and whenever any update operation is performed. A model may define one, both, or neither. - - -## Soft Delete - -Fluent provides support for a two step deletion process. A soft delete marks an existing row / item so that it does not appear in query results by default but does not remove it from the database. This row / item may be restored at a later date or deleted entirely from the database when desired. This can be a useful feature, but it requires some thought by the application developer. Foreign key and uniqueness constraints in the database are impacted and query performance may be reduced by the presence of soft deleted rows. Restoration of soft deleted rows, particularly when they are part of a complex graph, requires care to ensure that related data in other tables is in a proper state. - -Those warnings aside, Vapor makes soft delete easy to access and use. A model defines `deletedAtKey` to enable soft deletes in the same manner as for the creation and update timestamps. - -```swift -static let deletedAtKey: TimestampKey? = \.deletedAt - -var deletedAt: Date? -``` - -The delete operation is now a soft delete. The deletion timestamp is set and, if the model specifies `updatedAtKey`, the update timestamp is changed. Soft deleted rows will no longer appear in results by default, but can be included in queries by setting `withSoftDeleted:true`. - -```swift -User.query(on: conn).filter(…).delete() -``` - -Restoring these rows is straightforward. - -```swift -User.query(on: conn, withSoftDeleted:true).filter(…).restore() -``` - -The restore will clear the deletion timestamp and will also set the update timestamp. The restored rows will now appear in queries as normal. - -Rows, both normal and soft deleted, may be permanently deleted by indicating `force: true` on the delete. - -```swift -User.query(on: conn, withSoftDeleted:true).filter(…).delete(force: true) -``` - -!!! tip - If there is a desire to archive deleted rows, a better solution may be to create a separate table of deleted items and use the `willDelete` lifecycle method to copy the row into the archive table. - - -## Methods - -`Model` offers some convenience methods to make working with it easier. - -### Require ID - -This method return's the models ID or throws an error. - -```swift -let id = try user.requireID() -``` - diff --git a/3.0/docs/fluent/querying.md b/3.0/docs/fluent/querying.md deleted file mode 100644 index c61f164d..00000000 --- a/3.0/docs/fluent/querying.md +++ /dev/null @@ -1,198 +0,0 @@ -# Fluent Queries - -Once you have a [model](models.md) you can start querying your database to create, read, update, and delete data. - -## Connection - -The first thing you need to query your database, is a connection to it. Luckily, they are easy to get. - -### Request - -The easiest way to connect to your database is simply using the incoming `Request`. This will use the model's [`defaultDatabase`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE15defaultDatabase0D3Kit0D10IdentifierVy0D0QzGSgvpZ) property to automatically fetch a pooled connection to the database. - -```swift -router.get("galaxies") { req in - return Galaxy.query(on: req).all() -} -``` - -You can use convenience methods on a `Container` to create connections manually. Learn more about that in [DatabaseKit → Overview → Connections](../database-kit/overview.md#connections). - -## Create - -One of the first things you will need to do is save some data to your database. You do this by initializing an instance of your model then calling [`create(on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6create2on3NIO15EventLoopFutureCyxG11DatabaseKit0I11Connectable_p_tF). - -```swift -router.post("galaxies") { req in - let galaxy: Galaxy = ... - return galaxy.create(on: req) -} -``` - -The create method will return the saved model. The returned model will include any generated fields such as the ID or fields with default values. - -If your model also conforms to [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html) you can return the result of the Fluent query directly. - - -## Read - -To read models from the database, you can use [`query(on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6create2on3NIO15EventLoopFutureCyxG11DatabaseKit0I11Connectable_p_tF) or [`find(_:on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE4find_2on3NIO15EventLoopFutureCyxSgG2IDQz_11DatabaseKit0J11Connectable_ptFZ). - -### Find - -The easiest way to find a single model is by passing its ID to [`find(_:on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE4find_2on3NIO15EventLoopFutureCyxSgG2IDQz_11DatabaseKit0J11Connectable_ptFZ). - -```swift -Galaxy.find(42, on: conn) -``` - -The result will be a future containing an optional value. You can use [`unwrap(or:)`](https://api.vapor.codes/core/latest/Core/Extensions/Future.html#/s:3NIO15EventLoopFutureC4CoreAD12OptionalTypeRzlE6unwrap2orACy07WrappedG0QzGs5Error_pyXA_tF) to unwrap the future value or throw an error. - -```swift -Galaxy.find(42, on: conn).unwrap(or: Abort(...)) -``` - -### Query - -You can use the [`query(on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6create2on3NIO15EventLoopFutureCyxG11DatabaseKit0I11Connectable_p_tF) method to build database queries with filters, joins, sorts, and more. - -```swift -Galaxy.query(on: conn).filter(\.name == "Milky Way") -``` - -### Filter - -The [`filter(_:)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC6filteryACyxq_GXDAA14FilterOperatorVyxq_GF) method accepts filters created from Fluent's operators. This provides a concise, Swifty way for building Fluent queries. - -Calls to filter can be chained and even grouped. - -```swift -Galaxy.query(on: conn).filter(\.mass >= 500).filter(\.type == .spiral) -``` - -Below is a list of all supported operators. - -|operator|type| -|-|-| -|`==`|Equal| -|`!=`|Not equal| -|`>`|Greater than| -|`<`|Less than| -|`>=`|Greater than or equal| -|`<=`|Less than or equal| - -By default, all chained filters will be used to limit the result set. You can use filter groups to change this behavior. - -```swift -Galaxy.query(on: conn).group(.or) { - $0.filter(\.mass <= 250).filter(\.mass >= 500) -}.filter(\.type == .spiral) -``` - -The above query will include results where the galaxy's mass is below 250 _or_ above 500 _and_ the type is spiral. - -!!! tip - If you get an error that states your operator cannot be applied to two operands you are comparing in a filter, - ensure you have imported your specific Fluent ORM (FluentSQLite, FluentMySQL, etc). - -### Range - -You can apply Swift ranges to a query builder to limit the result set. - -```swift -Galaxy.query(on: conn).range(..<50) -``` - -The above query will include only the first 50 results. - -For more information on ranges, see docs for Swift's [Range](https://developer.apple.com/documentation/swift/range) type. - -### Sort - -Query results can be sorted by a given field. - -```swift -Galaxy.query(on: conn).sort(\.name, .descending) -``` - -You can sort by multiple fields to perform tie breaking behavior where there is duplicate information in the one of the sorted fields. - -### Join - -Other models can be joined to an existing query in order to further filter the results. - -```swift -Galaxy.query(on: conn).join(\Planet.galaxyID, to: \Galaxy.id) - .filter(\Planet.name == "Earth") -``` - -Once a table has been joined using [`join(_:to:)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderCA2A14JoinSupportingRzrlE4join_2to6methodACyxq_GXDs7KeyPathCyqd__qd_0_G_AJyqd_1_qd_2_G0bD6MethodQztr2_lF), you can use fully-qualified key paths to filter results based on data in the joined table. - -The above query fetches all galaxies that have a planet named Earth. - -You can even decode the joined models using [`alsoDecode(...)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC10alsoDecodeyACyxq__qd__tGqd__mAA5ModelRd__lF). - -```swift -Galaxy.query(on: conn) - // join Planet and filter - .alsoDecode(Planet.self).all() -``` - -The above query will decode an array of `(Galaxy, Planet)` tuples. - -### Fetch - -To fetch the results of a query, use [`all()`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC3all3NIO15EventLoopFutureCySayq_GGyF), [`chunk(max:closure:)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC5chunk3max7closure3NIO15EventLoopFutureCyytGSi_ySayq_GKctF), [`first()`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC5first3NIO15EventLoopFutureCyq_SgGyF) or an aggregate method. - -#### All - -The most common method for fetching results is with `all()`. This will return all matching results according to any fliters applied. - -```swift -Galaxy.query(on: conn).all() -``` - -When combined with [`range(_:)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC5rangeyACyxq_GXDSnySiGF), you can efficiently limit how many results are returned by the database. - -```swift -Galaxy.query(on: conn).range(..<50).all() -``` - -#### Chunk - -For situations where memory conservation is important, use [`chunk(...)`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC5chunk3max7closure3NIO15EventLoopFutureCyytGSi_ySayq_GKctF). This method returns the result set in multiple calls of a maximum chunk size. - -```swift -Galaxy.query(on: conn).chunk(max: 32) { galaxies in - print(galaxies) // Array of 32 or less galaxies -} -``` - -#### First - -The [`first()`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html#/s:6Fluent12QueryBuilderC5first3NIO15EventLoopFutureCyq_SgGyF) method is a convenience for fetching the first result of a query. It will automatically apply a range restriction to avoid transferring unnecessary data. - -```swift -Galaxy.query(on: conn).filter(\.name == "Milky Way").first() -``` - -This method is more efficient than calling `all` and getting the first item in the array. - -## Update - -After a model has been fetched from the database and mutated, you can use [`update(on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6update2on10originalID3NIO15EventLoopFutureCyxG11DatabaseKit0K11Connectable_p_0F0QzSgtF) to save the changes. - -```swift -var planet: Planet ... // fetched from database -planet.name = "Earth" -planet.update(on: conn) -``` - -## Delete - -After a model has been fetched from the database, you can use [`delete(on:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6delete5force2on3NIO15EventLoopFutureCyytGSb_11DatabaseKit0J11Connectable_ptF) to delete it. - -```swift -var planet: Planet ... // fetched from database -planet.delete(on: conn) -``` diff --git a/3.0/docs/fluent/relations.md b/3.0/docs/fluent/relations.md deleted file mode 100644 index da12dcf5..00000000 --- a/3.0/docs/fluent/relations.md +++ /dev/null @@ -1,196 +0,0 @@ -# Fluent Relations - -Fluent supports two methods for relating models: one-to-many (parent-child) and many-to-many (siblings). These relations help make working with a [normalized data structure](https://en.wikipedia.org/wiki/Database_normalization) easy. - -## Parent-Child - -The most common model relation is the one-to-many or _parent-child_ relation. In this relation, each child model stores at most one identifier of a parent model. In most cases, multiple child models can store the same parent identifier at the same time. This means that any given parent can have zero or more related child models. Hence the name, one (parent) to many (children). - -!!! note - If each child must store a _unique_ parent ID, this relation is called a one-to-one relation. - -Take a look at the following diagram in which an example parent-child relation between two models (`Galaxy` and `Planet`) is shown. - -![parent_child](https://user-images.githubusercontent.com/1342803/42603097-66b1640c-853a-11e8-99ba-63f1cac50c51.png) - -In the example above, `Galaxy` is the parent and `Planet` is the child. Planets store an identifier referencing exactly one galaxy (the galaxy they are in). In turn, each galaxy has zero or more planets that belong to it. - -Let's take a look at what these models would look like in Fluent. - -```swift -struct Galaxy: Model { - // ... - var id: Int? - var name: String -} -``` - -```swift -struct Planet: Model { - // ... - var id: Int? - var name: String - var galaxyID: Int -} -``` - -For more information on defining models see [Fluent → Models](models.md). - -Fluent provides two helpers for working with parent-child relations: [`Parent`](https://api.vapor.codes/fluent/latest/Fluent/Structs/Parent.html) and [`Children`](https://api.vapor.codes/fluent/latest/Fluent/Structs/Children.html). These helpers can be created using extensions on the related models for convenient access. - -```swift -extension Galaxy { - // this galaxy's related planets - var planets: Children { - return children(\.galaxyID) - } -} -``` - -Here the [`children(_:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE8childrenyAA8ChildrenVyxqd__Gs7KeyPathCyqd__2IDQzGAaBRd__8DatabaseQyd__AMRtzlF) method is used on `Galaxy` to create the relation. The resulting type has two generic arguments in the signature that can be thought of as `. Since this relation goes _from_ galaxy _to_ planet, they are ordered as such in the generic arguments. - -Note that this method is not static. That is because it must access the galaxy's identifier to perform the relation lookup. - -```swift -extension Planet { - // this planet's related galaxy - var galaxy: Parent { - return parent(\.galaxyID) - } -} -``` - -Here the [`parent(_:)`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/Model.html#/s:6Fluent5ModelPAAE6parentyAA6ParentVyxqd__Gs7KeyPathCyx2IDQyd__GAaBRd__8DatabaseQyd__AMRtzlF) method is used on `Planet` to create the inverse relation. The resulting type also has two generic arguments. In this case, they are reversed since this relation now goes _from_ planet _to_ galaxy. - -Note that this method is also not static. That is because it must access the referenced identifier to perform the relation lookup. - -Now that the models and relation properties are created, they can be used to create, read, update, and delete related data. - -```swift -let galaxy: Galaxy = ... -let planets = galaxy.planets.query(on: ...).all() -``` - -The `query(on:)` method on a relation creates an instance of [`QueryBuilder`](https://api.vapor.codes/fluent/latest/Fluent/Classes/QueryBuilder.html) filtered to the related models. See [Fluent → Querying](querying.md) for more information on working with the query builder. - -```swift -let planet: Planet = ... -let galaxy = planet.galaxy.get(on: ...) -``` - -Since the child can have at most _one_ parent, the most useful method is [`get(on:)`] which simply returns the parent model. - -## Siblings - -A more powerful (and complex) relation is the many-to-many or _siblings_ relation. In this relation, two models are related by a third model called a _pivot_. The pivot is a simple model that carries one identifier for each of the two related models. Because a third model (the pivot) stores identifiers, each model can be related to zero or more models on the other side of the relation. - -Take a look at the following diagram in which an example siblings relation between two models (`Planet` and `Tag`) and a pivot (`PlanetTag`) is shown. - -![siblings](https://user-images.githubusercontent.com/1342803/42603098-66c3214c-853a-11e8-84da-c228c5e90200.png) - -A siblings relation is required for the above example because: - -- Both **Earth** and **Venus** have the **Earth Sized** tag. -- **Earth** has both the **Earth Sized** and **Liquid Water** tag. - -In other words, two planets can share one tag _and_ two tags can share one planet. This is a many-to-many relation. - -Let's take a look at what these models would look like in Fluent. - - -```swift -struct Planet: Model { - // ... - var id: Int? - var name: String - var galaxyID: Int -} -``` - -```swift -struct Tag: Model { - // ... - var id: Int? - var name: String -} -``` - -For more information on defining models see [Fluent → Models](models.md). - -Now let's take a look at the pivot. It may seem a bit intimidating at first, but it's really quite simple. - -```swift -struct PlanetTag: Pivot { - // ... - - typealias Left = Planet - typealias Right = Tag - - static var leftIDKey: LeftIDKey = \.planetID - static var rightIDKey: RightIDKey = \.tagID - - var id: Int? - var planetID: Int - var tagID: Int -} -``` - -A pivot must have `Left` and `Right` model types. In this case, those model types are `Planet` and `Tag`. Although it is arbitrary which model is left vs. right, a good rule of thumb is to order things alphabetically for consistency. - -Once the left and right models are defined, we must supply Fluent with key paths to the stored properties for each ID. We can use the `LeftIDKey` and `RightIDKey` type-aliases to do this. - -A `Pivot` is also a `Model` itself. You are free to store any additional properties here if you like. Don't forget to create a migration for it if you are using a database that supports schemas. - -Once the pivot and your models are created, you can add convenience extensions for interacting with the relation just like the parent-child relation. - -```swift -extension Planet { - // this planet's related tags - var tags: Siblings { - return siblings() - } -} -``` - -Because the siblings relation requires three models, it has three generic arguments. You can think of the arguments as ``. This relation goes _from_ a planet _to_ tags _through_ the planet tag pivot. - -The other side of the relation (on tag) is similar. Only the first two generic arguments are flipped. - -```swift -extension Tag { - // all planets that have this tag - var planets: Siblings { - return siblings() - } -} -``` - -Now that the relations are setup, we can query a planet's tags. This works just like the `Children` type in the parent-child relationship. - -```swift -let planet: Planet = ... -planet.tags.query(on: ...).all() -``` - -### Modifiable Pivot - -If the pivot conforms to [`ModifiablePivot`](https://api.vapor.codes/fluent/latest/Fluent/Protocols/ModifiablePivot.html), then Fluent can help to create and delete pivots (called attaching and detaching). - -Conforming a pivot is fairly simple. Fluent just needs to be able to initialize the pivot from two related models. - -```swift -extension PlanetTag: ModifiablePivot { - init(_ planet: Planet, _ tag: Tag) throws { - planetID = try planet.requireID() - tagID = try tag.requireID() - } -} -``` - -Once the pivot type conforms, there will be extra methods available on the siblings relation. - -```swift -let planet: Planet = ... -let tag: Tag = ... -planet.tags.attach(tag, on: ...) -``` diff --git a/3.0/docs/fluent/transaction.md b/3.0/docs/fluent/transaction.md deleted file mode 100644 index 726d91d4..00000000 --- a/3.0/docs/fluent/transaction.md +++ /dev/null @@ -1,30 +0,0 @@ -# Fluent Transactions - -Transactions allow you to ensure multiple operations complete succesfully before saving data to your database. Once a transaction is started, you may run Fluent queries normally. However, no data will be saved to the database until the transaction completes. If an error is thrown at any point during the transaction (by you or the database), none of the changes will take effect. - -To perform a transaction, you need access to something that can connect to the database. This is usually an incoming HTTP request. Use the [`transaction(on:_:)`](https://api.vapor.codes/fluent/latest/Fluent/Extensions/DatabaseConnectable.html#/s:11DatabaseKit0A11ConnectableP6FluentE11transaction2on_3NIO15EventLoopFutureCyqd_0_GAA0A10IdentifierVyqd__G_AJ10ConnectionQyd__KctAD21TransactionSupportingRd__r0_lF) method. - -Fill in the Xcode placeholders below with your database's name from [Getting Started → Choosing a Driver](getting-started.md#choosing-a-driver). - -```swift -req.transaction(on: .<#dbid#>) { conn in - // use conn as your connection -} -``` - -Once inside the transaction closure, you must use the supplied connection (named `conn` in the example) to perform queries. - -The closure expects a generic future return value. Once this future completes succesfully, the transaction will be committed. - -```swift -var userA: User = ... -var userB: User = ... - -return req.transaction(on: .<#dbid#>) { conn in - return userA.save(on: conn).flatMap { _ in - return userB.save(on: conn) - }.transform(to: HTTPStatus.ok) -} -``` - -The above example will save User A _then_ User B before completing the transaction. If either user fails to save, neither will save. Once the transaction has completed, the result is transformed to a simple HTTP status response indicating completion. diff --git a/3.0/docs/getting-started/application.md b/3.0/docs/getting-started/application.md deleted file mode 100644 index 5e411a86..00000000 --- a/3.0/docs/getting-started/application.md +++ /dev/null @@ -1,43 +0,0 @@ -# Application - -Every Vapor project has an `Application`. You use the application to run your server and create any services you might need at boot time. - -The best place to access the application is in your project's [`boot.swift`](structure.md#bootswift) file. - -```swift -import Vapor - -public func boot(_ app: Application) throws { - // your code here -} -``` - -Unlike some other web frameworks, Vapor doesn't support statically accessing the application. If you need to access it from another class or struct, you should pass through a method or initializer. - -!!! info - Avoiding static access to variables helps make Vapor performant by preventing the need for thread-safe locks or semaphores. - - -## Services - -The application's main function is to boot your server. - -```swift -try app.run() -``` - -However, the application is also a container. You may use it to create services required to boot your application. - -!!! warning - Do not use the application, or any services created from it, inside a route closure. Use the `Request` to create services instead. - -```swift -let client = try app.make(Client.self) -let res = try client.get("http://vapor.codes").wait() -print(res) // Response -``` - -!!! tip - It's okay to use `.wait()` here instead of `.map`/`.flatMap` because we are not inside of a route closure. - -Learn more about services in [Getting Started → Services](services.md). diff --git a/3.0/docs/getting-started/async.md b/3.0/docs/getting-started/async.md deleted file mode 100644 index 7f093407..00000000 --- a/3.0/docs/getting-started/async.md +++ /dev/null @@ -1,105 +0,0 @@ -# Async - -You may have noticed some APIs in Vapor expect or return a generic `Future` type. If this is your first time hearing about futures, they might seem a little confusing at first. But don't worry, Vapor makes them easy to use. - -This guide will give you a quick introduction to working with Async. Check out [Async → Overview](../async/overview.md) for more information. - -## Futures - -Since `Future`s work asynchronously, we must use closures to interact with and transform their values. Just like optionals in Swift, futures can be mapped and flat-mapped. - -### Map - -The `.map(to:_:)` method allows you to transform the future's value to another value. The closure provided will be called once the `Future`'s data becomes available. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Map the future string to an integer -let futureInt = futureString.map(to: Int.self) { string in - print(string) // The actual String - return Int(string) ?? 0 -} - -/// We now have a future integer -print(futureInt) // Future -``` - -### Flat Map - -The `.flatMap(to:_:)` method allows you to transform the future's value to another future value. It gets the name "flat" map because it is what allows you to avoid creating nested futures (e.g., `Future>`). In other words, it helps you keep your futures flat. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Assume we have created an HTTP client -let client: Client = ... - -/// Flat-map the future string to a future response -let futureResponse = futureString.flatMap(to: Response.self) { string in - return client.get(string) // Future -} - -/// We now have a future response -print(futureResponse) // Future -``` - -!!! info - If we instead used `.map(to:_:)` in the above example, we would have ended up with a `Future>`. Yikes! - - -### Chaining - -The great part about transformations on futures is that they can be chained. This allows you to express many conversions and subtasks easily. - -Let's modify the examples from above to see how we can take advantage of chaining. - -```swift -/// Assume we get a future string back from some API -let futureString: Future = ... - -/// Assume we have created an HTTP client -let client: Client = ... - -/// Transform the string to a url, then to a response -let futureResponse = futureString.map(to: URL.self) { string in - guard let url = URL(string: string) else { - throw Abort(.badRequest, reason: "Invalid URL string: \(string)") - } - return url -}.flatMap(to: Response.self) { url in - return client.get(url) -} - -print(futureResponse) // Future -``` - -After the initial call to map, there is a temporary `Future` created. This future is then immediately flat-mapped to a `Future` - -!!! tip - You can `throw` errors inside of map and flat-map closures. This will result in the future failing with the error thrown. - -## Worker - -You may see methods in Vapor that have an `on: Worker` parameter. These are usually methods that perform asynchronous work and require access to the `EventLoop`. - -The most common `Worker`s you will interact with in Vapor are: - -- `Application` -- `Request` -- `Response` - -```swift -/// Assume we have a Request and some ViewRenderer -let req: Request = ... -let view: ViewRenderer = ... - -/// Render the view, using the Request as a worker. -/// This ensures the async work happens on the correct event loop. -/// -/// This assumes the signature is: -/// func render(_: String, on: Worker) -view.render("home.html", on: req) -``` \ No newline at end of file diff --git a/3.0/docs/getting-started/cloud.md b/3.0/docs/getting-started/cloud.md deleted file mode 100644 index b4bfcaaf..00000000 --- a/3.0/docs/getting-started/cloud.md +++ /dev/null @@ -1,24 +0,0 @@ -# Deployment - -Deploying code is the process of making your Vapor project publically available. -It can be one of the most difficult aspects of web development. Fortunately, there -are services to help. - -## Vapor Cloud - -The best way to deploy your application is through Vapor Cloud. It's a cloud platform built -specifically for the Vapor web framework. This means it's incredibly easy to deploy your -project quickly and be confident that it will be fast and stable. - -Deploying your project to Vapor Cloud is simple, it's built right into the [Vapor Toolbox](toolbox.md). -Just run this command from within the root directory of your project. - -```sh -vapor cloud deploy -``` - -For a detailed guide, visit [Vapor Cloud → Quick Start](https://docs.vapor.cloud/quick-start/). - -## Other Options - -Vapor can be deployed anywhere that supports Ubuntu (basically everywhere). Guides on deploying to other systems are coming soon (contributions welcome)! diff --git a/3.0/docs/getting-started/content.md b/3.0/docs/getting-started/content.md deleted file mode 100644 index b6f5b9c1..00000000 --- a/3.0/docs/getting-started/content.md +++ /dev/null @@ -1,89 +0,0 @@ -# Content - -In Vapor 3, all content types (JSON, protobuf, URLEncodedForm, [Multipart](../multipart/getting-started.md), etc) are treated the same. All you need to parse and serialize content is a `Codable` class or struct. - -For this introduction, we will use JSON as an example. But keep in mind the API is the same for any supported content type. - -## Request - -Let's take a look at how you would parse the following HTTP request. - -```http -POST /login HTTP/1.1 -Content-Type: application/json - -{ - "email": "user@vapor.codes", - "password": "don't look!" -} -``` - -First, create a struct or class that represents the data you expect. - -```swift -import Vapor - -struct LoginRequest: Content { - var email: String - var password: String -} -``` - -Then simply conform this struct or class to `Content`. -Now we are ready to decode that HTTP request. - -```swift -router.post("login") { req -> Future in - return req.content.decode(LoginRequest.self).map(to: HTTPStatus.self) { loginRequest in - print(loginRequest.email) // user@vapor.codes - print(loginRequest.password) // don't look! - return .ok - } -} -``` - -We use `.map(to:)` here since `req.content.decode(_:)` returns a [future](async.md). - -## Response - -Let's take a look at how you would create the following HTTP response. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "name": "Vapor User", - "email": "user@vapor.codes" -} -``` - -Just like decoding, first create a struct or class that represents the data that you are expecting. - -```swift -import Vapor - -struct User: Content { - var name: String - var email: String -} -``` - -Then just conform this struct or class to `Content`. Now we are ready to encode that HTTP response. - -```swift -router.get("user") { req -> User in - return User( - name: "Vapor User", - email: "user@vapor.codes" - ) -} -``` - -Great job! Now you know how to encode and decode data in Vapor. - -!!! tip - See [Vapor → Content](../vapor/content.md) for more in-depth information. - -The next section in this guide is [Async](async.md). - diff --git a/3.0/docs/getting-started/controllers.md b/3.0/docs/getting-started/controllers.md deleted file mode 100644 index d0677d34..00000000 --- a/3.0/docs/getting-started/controllers.md +++ /dev/null @@ -1,43 +0,0 @@ -# Controllers - -Controllers are a great way to organize your code. They are collections of methods that accept a request and return a response. - -A good place to put your controllers is in the [Controllers](structure.md#controllers) folder. - -## Methods - -Let's take a look at an example controller. - -```swift -import Vapor - -final class HelloController { - func greet(_ req: Request) throws -> String { - return "Hello!" - } -} -``` - -Controller methods should always accept a `Request` and return something `ResponseEncodable`. - -!!! note - [Futures](async.md) whose expectations are `ResponseEncodable` (i.e, `Future`) are also `ResponseEncodable`. - -To use this controller, we can simply initialize it, then pass the method to a router. - -```swift -let helloController = HelloController() -router.get("greet", use: helloController.greet) -``` - -## Using Services - -You will probably want to access your [services](services.md) from within your controllers. Just use the `Request` as a container to create services from within your route closures. Vapor will take care of caching the services. - -```swift -final class HelloController { - func greet(_ req: Request) throws -> String { - return try req.make(BCryptHasher.self).hash("hello") - } -} -``` diff --git a/3.0/docs/getting-started/hello-world.md b/3.0/docs/getting-started/hello-world.md deleted file mode 100644 index 263b8af6..00000000 --- a/3.0/docs/getting-started/hello-world.md +++ /dev/null @@ -1,55 +0,0 @@ -# Hello, world - -Now that you've installed Vapor, let's create your first Vapor app! -This guide will take you step by step through creating a new project, building, and running it. - -## New Project - -The first step is to create a new Vapor project on your computer. -For this guide, we will call the project `Hello`. - -Open up your terminal, and use [Vapor Toolbox's `new`](toolbox.md#new) command. - -```sh -vapor new Hello -``` - -Once that finishes, change into the newly created directory. - -```sh -cd Hello -``` - -## Generate Xcode Project - -Let's now use the [Vapor Toolbox's `xcode`](toolbox.md) command to generate an Xcode project. -This will allow us to build and run our app from inside of Xcode, just like an iOS app. - -```sh -vapor xcode -``` - -The toolbox will ask you if you'd like to open Xcode automatically, select `yes`. - -## Build & Run - -You should now have Xcode open and running. Select the [run scheme](xcode.md#run) from the scheme menu and **My Mac** as the deployment target, -then click the play button. - -You should see the terminal pop up at the bottom of the screen. - -```sh -Server starting on http://localhost:8080 -``` - -## Visit Localhost - -Open your web browser, and visit localhost:8080 → - -You should see the following page. - -```html -It works! -``` - -Congratulations on creating, building, and running your first Vapor app! 🎉 diff --git a/3.0/docs/getting-started/routing.md b/3.0/docs/getting-started/routing.md deleted file mode 100644 index b3f929ae..00000000 --- a/3.0/docs/getting-started/routing.md +++ /dev/null @@ -1,69 +0,0 @@ -# Routing - -Routing is the process of finding the appropriate response to an incoming request. - -## Making a Router - -In Vapor the default Router is the `EngineRouter`. You can implement custom routers by implementing one conforming to the `Router` protocol. - -```swift -let router = try EngineRouter.default() -``` - -This is usually done in your [`configure.swift`](structure.md#configureswift) file. - -## Registering a route - -Imagine you want to return a list of users when someone visits `GET /users`. Leaving authorization aside, that would look something like this. - -```swift -router.get("users") { req in - return // fetch the users -} -``` - -In Vapor, routing is usually done using the `.get`, `.put`, `.post`, `.patch` and `.delete` shorthands. You can supply the path as `/` or comma-separated strings. We recommend comma separated, as it's more readable. - -```swift -router.get("path", "to", "something") { ... } -``` - -## Routes - -The best place to add routes is in the [`routes.swift`](structure.md#routesswift) file. Use the router supplied as a parameter to this function to register your routes. - -```swift -import Vapor - -public func routes(_ router: Router) throws { - // Basic "Hello, world!" example - router.get("hello") { req in - return "Hello, world!" - } - - /// ... -} -``` - -See [Getting Started → Content](content.md) for more information about what can be returned in a route closure. - -## Parameters - -Sometimes you may want one of the components of your route path to be dynamic. This is often used when -you want to get an item with a supplied identifier, e.g., `GET /users/:userID` - -```swift -router.get("users", Int.parameter) { req -> String in - let userID = try req.parameters.next(Int.self) - return "requested user id #\(userID)" -} -``` - -Instead of passing a string, pass the _type_ of parameter you expect. In this case, our `User` has an `Int` ID. - -!!! tip - You can define your own [custom parameter types](../routing/overview.md#parameter) as well. - -## After registering your routes - -After registering your routes you must register the Router as a [Getting Started → Services](services.md) diff --git a/3.0/docs/getting-started/services.md b/3.0/docs/getting-started/services.md deleted file mode 100644 index 7c38bd47..00000000 --- a/3.0/docs/getting-started/services.md +++ /dev/null @@ -1,150 +0,0 @@ -# Services - -Services is a Service Locator (sometimes called inversion of control) framework for Vapor. The services framework allows you to register, configure, and initialize anything you might need in your application. - -## Container - -Most of your interaction with services will happen through a container. A container is a combination of the following: - -- [Services](#services): A collection of registered services. -- [Config](#config): Declared preferences for certain services over others. -- [Environment](#environment): The application's current environment type (testing, production, etc) -- [Worker](async.md#event-loop): The event loop associated with this container. - -The most common containers you will interact with in Vapor are: - -- `Application` -- `Request` -- `Response` - -You should use the `Application` as a container to create services required for booting your app. You should use the `Request` or `Response` containers to create services for responding to requests (in route closures and controllers). - -### Make - -Making services is simple, just call `.make(_:)` on a container and pass the type you want, usually a protocol like `Client`. - -```swift -let client = try req.make(Client.self) -``` - -You can also specify a concrete type if you know exactly what you want. - -```swift -let leaf = try req.make(LeafRenderer.self) -print(leaf) /// Definitely a LeafRenderer - -let view = try req.make(ViewRenderer.self) -print(view) /// ViewRenderer, might be a LeafRenderer -``` - -!!! tip - Try to rely on protocols over concrete types if you can. This will make testing your code easier (you can easily swap in dummy implementations) and it can help keep your code decoupled. - -## Services - -The `Services` struct contains all of the services you—or the service providers you have added—have registered. You will usually register and configure your services in [`configure.swift`](structure.md#configureswift). - -### Instance - -You can register initialized service instances using `.register(_:)`. - -```swift -/// Create an in-memory SQLite database -let sqlite = SQLiteDatabase(storage: .memory) - -/// Register to sevices. -services.register(sqlite) -``` - -After you register a service, it will be available for creation by a `Container`. - -```swift -let db = app.make(SQLiteDatabase.self) -print(db) // SQLiteDatabase (the one we registered earlier) -``` - -### Protocol - -When registering services, you can also declare conformance to a particular protocol. You might have noticed that this is how Vapor registers its main router. - -```swift -/// Register routes to the router -let router = EngineRouter.default() -try routes(router) -services.register(router, as: Router.self) -``` - -Since we register the `router` variable with `as: Router.self`, it can be created using either the concrete type or the protocol. - -```swift -let router = app.make(Router.self) -let engineRouter = app.make(EngineRouter.self) -print(router) // Router (actually EngineRouter) -print(engineRouter) // EngineRouter -print(router === engineRouter) // true -``` - -## Environment - -The environment is used to dynamically change how your Vapor app behaves in certain situations. For example, you probably want to use a different username and password for your database when your application is deployed. The `Environment` type makes managing this easy. - -When you run your Vapor app from the command line, you can pass an optional `--env` flag to specify the environment. By default, the environment will be `.development`. - -```sh -swift run Run --env prod -``` - -In the above example, we are running Vapor in the `.production` environment. This environment specifies `isRelease = true`. - -You can use the environment passed into [`configure.swift`](structure.md#configureswift) to dynamically register services. - -```swift -let sqlite: SQLiteDatabase -if env.isRelease { - /// Create file-based SQLite db using $SQLITE_PATH from process env - sqlite = try SQLiteDatabase(storage: .file(path: Environment.get("SQLITE_PATH")!)) -} else { - /// Create an in-memory SQLite database - sqlite = try SQLiteDatabase(storage: .memory) -} -services.register(sqlite) -``` - -!!! info - Use the static method `Environment.get(_:)` to fetch string values from the process environment. - -You can also dynamically register services based on environment using the factory `.register(_:)` method. - -```swift -services.register { container -> BCryptConfig in - let cost: Int - - switch container.environment { - case .production: cost = 12 - default: cost = 4 - } - - return BCryptConfig(cost: cost) -} -``` - -## Config - -If multiple services are available for a given protocol, you will need to use the `Config` struct to declare which service you prefer. - -```sh -ServiceError.ambiguity: Please choose which KeyedCache you prefer, multiple are available: MemoryKeyedCache, FluentCache. -``` - -This is also done in [`configure.swift`](structure.md#configureswift), just use the `config.prefer(_:for:)` method. - -```swift -/// Declare preference for MemoryKeyedCache anytime a container is asked to create a KeyedCache -config.prefer(MemoryKeyedCache.self, for: KeyedCache.self) - -/// ... - -/// Create a KeyedCache using the Request container -let cache = req.make(KeyedCache.self) -print(cache is MemoryKeyedCache) // true -``` diff --git a/3.0/docs/getting-started/spm.md b/3.0/docs/getting-started/spm.md deleted file mode 100644 index ee6b7116..00000000 --- a/3.0/docs/getting-started/spm.md +++ /dev/null @@ -1,96 +0,0 @@ -# Managing your project - -The Swift Package Manager (SPM for short) is used for building your project's source code and dependencies. -It's a similar idea to Cocoapods, Ruby gems, and NPM. Most of the time the [Vapor Toolbox](toolbox.md) will -interact with SPM on your behalf. However, it's important to understand the basics. - -!!! tip - Learn more about SPM on Swift.org → - -## Package Manifest - -The first place SPM looks in your project is the package manifest. This should always be located in the root -directory of your project and named `Package.swift`. - -### Dependencies - -Dependencies are other SPM packages that your package relies on. All Vapor applications rely on the Vapor package, -but you can add as many other dependencies as you want. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "VaporApp", - dependencies: [ - // 💧 A server-side Swift web framework. - .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), - ], - targets: [ ... ] -) -``` - -In the above example, you can see vapor/vapor → version 3.0 -or later is a dependency of this package. -When you add a dependency to your package, you must next signal which [targets](#targets) depend on -the newly available modules. - -!!! warning - Anytime you modify the package manifest, call `vapor update` to effect the changes. - -### Targets - -Targets are all of the modules, executables, and tests that your package contains. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "VaporApp", - dependencies: [ ... ], - targets: [ - .target(name: "App", dependencies: ["Vapor"]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Most Vapor apps will have three targets, although you can add as many as you like to organize your code. -Each target declares which modules it depends on. You must add module names here in order to `import` them in your code. -A target can depend on other targets in your project or any modules exposed by packages you've added to -the [main dependencies](#dependencies) array. - -!!! tip - Executable targets (targets that contain a `main.swift` file) cannot be imported by other modules. - This is why Vapor has both an `App` and a `Run` target. - Any code you include in `App` can be tested in the `AppTests`. - -## Folder Structure - -Below is the typical folder structure for an SPM package. - -``` -. -├── Sources -│ ├── App -│ │ └── (Source code) -│ └── Run -│ └── main.swift -├── Tests -│ └── AppTests -└── Package.swift -``` - -Each `.target` corresponds to a folder in the `Sources` folder. -Each `.testTarget` corresponds to a folder in the `Tests` folder. - -## Troubleshooting - -If you are experiencing problems with SPM, sometimes cleaning your project can help. - -```sh -vapor clean -``` diff --git a/3.0/docs/getting-started/structure.md b/3.0/docs/getting-started/structure.md deleted file mode 100644 index 2503251a..00000000 --- a/3.0/docs/getting-started/structure.md +++ /dev/null @@ -1,103 +0,0 @@ -# Structure - -This section explains the structure of a typical Vapor application to help get -you familiar with where things go. - -## Folder Structure - -Vapor's folder structure builds on top of [SPM's folder structure](spm.md#folder-structure). - -``` -. -├── Public -├── Sources -│ ├── App -│ │ ├── Controllers -│ │ ├── Models -│ │ ├── boot.swift -│ │ ├── configure.swift -│ │ └── routes.swift -│ └── Run -│ └── main.swift -├── Tests -│ └── AppTests -└── Package.swift -``` - -Let's take a look at what each of these folders and files does. - -## Public - -This folder contains any public files that will be served by your app. -This is usually images, style sheets, and browser scripts. - -Whenever Vapor responds to a request, it will first check if the requested -item is in this folder. If it is, it skips your application logic and returns -the file immediately. - -For example, a request to `localhost:8080/favicon.ico` will check to see -if `Public/favicon.ico` exists. If it does, Vapor will return it. - -You will need to enable `FileMiddleware` in your `configure.swift` file before Vapor can return public files. Make sure you've uncommented this line: - -`middlewares.use(FileMiddleware.self)` - -## Sources - -This folder contains all of the Swift source files for your project. -The top level folders (`App` and `Run`) reflect your package's modules, -as declared in the [package manifest](spm.md#targets). - -### App - -This is the most important folder in your application, it's where all of -the application logic goes! - -#### Controllers - -Controllers are great way of grouping together application logic. Most controllers -have many functions that accept a request and return some sort of response. - -!!! tip - Vapor supports, but does not enforce the MVC pattern - -#### Models - -The `Models` folder is a great place to store your [`Content`](content.md) structs or -Fluent [`Model`s](../fluent/models.md). - -#### boot.swift - -This file contains a function that will be called _after_ your application has booted, -but _before_ it has started running. This is a great place do things that should happen -every time your application starts. - -You have access to the [`Application`](application.md) here which you can use to create -any [services](application.md#services) you might need. - -#### configure.swift - -This file contains a function that receives the config, environment, and services for your -application as input. This is a great place to make changes to your config or register -[services](application.md#services) to your application. - -#### routes.swift - -This file contains a function for adding routes to your router. - -You will notice there's one example route in there that returns the "hello, world" response we saw earlier. - -You can create as many methods as you want to further organize your code. Just make sure to call them in this main route collection. - -## Tests - -Each non-executable module in your `Sources` folder should have a corresponding `...Tests` folder. - -### AppTests - -This folder contains the unit tests for code in your `App` module. -Learn more about testing in [Testing → Getting Started](../testing/getting-started.md). - -## Package.swift - -Finally is SPM's [package manifest](spm.md#package-manifest). diff --git a/3.0/docs/getting-started/toolbox.md b/3.0/docs/getting-started/toolbox.md deleted file mode 100644 index b88add0f..00000000 --- a/3.0/docs/getting-started/toolbox.md +++ /dev/null @@ -1,82 +0,0 @@ -# Install Toolbox - -Vapor's command line interface provides shortcuts and assistance for common tasks. - -Installation (macOS): [Install → macOS](../install/macos.md) - -Installation (Ubuntu): [Install → Ubuntu](../install/ubuntu.md) - -Vapor Toolbox - -Help prints useful information about available commands and flags. - -```sh -vapor --help -``` - -You can also run the `--help` option on any Toolbox command. - -```sh -vapor new --help -``` - -The `--help` flag should be your goto for learning about the toolbox as it is the most up-to-date. - -## New - -The Toolbox's most important feature is helping you create a new project. - -```sh -vapor new -``` - -Just pass the name of your project as the first argument to the `new` command. - -!!! note - Project names should be PascalCase →, like `HelloWorld` or `MyProject`. - -### Templates - -By default, Vapor will create your new project from the API template. You can choose -a different template by passing the `--template` flag. - -| Name | Flag | Description | -|------|------------------|-----------------------------------| -| API | `--template=api` | JSON API with Fluent database. | -| Web | `--template=web` | HTML website with Leaf templates. | -| Auth | `--template=auth-template`| JSON API with Fluent DB and Auth. | - -!!! info - There are lots of unofficial Vapor templates on GitHub under the `vapor` + `template` topics →. - You can use these by passing the full GitHub URL to the `--template` option. - -## Build & Run - -You can use the toolbox to build and run your Vapor app. - -```sh -vapor build -vapor run -``` - -!!! tip - We recommend building and running through [Xcode](xcode.md) if you have a Mac. - It's a bit faster and you can set breakpoints! - Just use `vapor xcode` to generate an Xcode project. - -## Updating - -The toolbox should be updated by the package manager it was installed with. - -### Homebrew - -```sh -brew upgrade vapor -``` - -### APT - -``` -sudo apt-get update -sudo apt-get install vapor -``` diff --git a/3.0/docs/getting-started/xcode.md b/3.0/docs/getting-started/xcode.md deleted file mode 100644 index aa412778..00000000 --- a/3.0/docs/getting-started/xcode.md +++ /dev/null @@ -1,36 +0,0 @@ -# Xcode - -If you're on a Mac, you can develop your Vapor project using Xcode. -You can build, run, and stop your server from within Xcode, as well as use breakpoints and instruments to debug your code. - -Xcode 9 running Vapor - -Xcode is a great way to develop Vapor apps, but you can use any text editor you like. - -## Generate Project - -To use Xcode, you just need to generate an Xcode project using [Vapor Toolbox](toolbox.md). - -```sh -vapor xcode -``` - -!!! tip - Don't worry about comitting the generated Xcode Project to git, just generate a new - one whenever you need it. - -## Run - -To build and run your Vapor app, first make sure you have the `Run` scheme selected from the schemes menu. -Also make sure to select "My Mac" as the device. - -Run Scheme - -Once that's selected, just click the play button or press `Command + R` on your keyboard. - -## Test - -To run your unit tests, select the scheme ending in `-Package` and press `Command + U`. - -!!! warning - There may be a few extraneous schemes in the dropdown menu. Ignore them! diff --git a/3.0/docs/http/client.md b/3.0/docs/http/client.md deleted file mode 100644 index 1ab897bb..00000000 --- a/3.0/docs/http/client.md +++ /dev/null @@ -1,34 +0,0 @@ -# Using HTTPClient - -HTTP clients send requests to remote HTTP servers which then generate and return responses. HTTP clients are usually only active for a matter of seconds to minutes and may send one or more requests. The [`HTTPClient`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPClient.html) type is what powers Vapor's higher-level client. This short guide will show you how to send HTTP requests to servers manually. - - -!!! tip - If you are using Vapor, you probably don't need to use HTTP's APIs directly. Refer to [Vapor → Client](../vapor/client.md) for the more convenient APIs. - -For this example, we will fetch Vapor's homepage. The first step is to create a connected HTTP client. Use the static [`connect(...)`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPClient.html#/s:4HTTP10HTTPClientC7connectXeXeFZ) method to do this. - -```swift -// Connect a new client to the supplied hostname. -let client = try HTTPClient.connect(hostname: "vapor.codes", on: ...).wait() -print(client) // HTTPClient -// Create an HTTP request: GET / -let httpReq = HTTPRequest(method: .GET, url: "/") -// Send the HTTP request, fetching a response -let httpRes = try client.send(httpReq).wait() -print(httpRes) // HTTPResponse -``` - -Take note that we are passing the _hostname_. This is different from a full URL. You can use `URL` and `URLComponents` from Foundation to parse out a hostname. Vapor's convenience APIs do this automatically. - -!!! tip - If you are creating a totally standalone client (not tying into the rest of Vapor) then you can create one of [`swift-nio`'s `MultiThreadedEventLoopGroup`](https://apple.github.io/swift-nio/docs/current/NIO/Classes/MultiThreadedEventLoopGroup.html) and pass it in to the `connect` method's `on` parameter. - -!!! warning - This guide assumes you are on the main thread. Don't use `wait()` if you are inside of a route closure. See [Async → Overview](../async/overview.md#blocking) for more information. - -After we have a connected HTTP client, we can send an [`HTTPRequest`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html) using [`send(...)`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPClient.html#/s:4HTTP10HTTPClientC4sendXeXeF). This will return an [`HTTPResponse`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPResponse.html) containing the headers and body sent back from the server. See [HTTP → Message](message.md) for more information on HTTP messages. - -## API Docs - -That's it! Congratulations on making your first HTTP request. Check out the [API docs](https://api.vapor.codes/http/latest/HTTP/index.html) for more in-depth information about all of the available parameters and methods. diff --git a/3.0/docs/http/getting-started.md b/3.0/docs/http/getting-started.md deleted file mode 100644 index d9e2081f..00000000 --- a/3.0/docs/http/getting-started.md +++ /dev/null @@ -1,41 +0,0 @@ -# Getting Started with HTTP - -HTTP ([vapor/http](https://github.com/vapor/http)) is a non-blocking, event-driven HTTP library built on SwiftNIO. It makes working with SwiftNIO's HTTP handlers easy and offers higher-level functionality like media types, client upgrading, streaming bodies, and more. Creating an HTTP echo server takes just a few lines of code. - -!!! tip - If you use Vapor, most of HTTP's APIs will be wrapped by more convenient methods. Usually the only HTTP type you - will interact with is the `http` property of `Request` or `Response`. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `HTTP` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The HTTP package is lightweight, pure Swift, and only depends on SwiftNIO. This means it can be used as an HTTP framework in any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/http.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["HTTP", ... ]) - ] -) -``` - -Use `import HTTP` to access the APIs. - -The rest of this guide will give you an overview of what is available in the HTTP package. As always, feel free to visit the [API docs](https://api.vapor.codes/http/latest/HTTP/index.html) for more in-depth information. diff --git a/3.0/docs/http/message.md b/3.0/docs/http/message.md deleted file mode 100644 index c9d6279d..00000000 --- a/3.0/docs/http/message.md +++ /dev/null @@ -1,136 +0,0 @@ -# Using HTTP Message - -There are two types of HTTP messages, [`HTTPRequest`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html) and [`HTTPResponse`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPResponse.html). For the most part they are very similar, but there are a couple of differences. - -## Request - -HTTP requests are sent by clients to a server and they should always receive exactly one HTTP response. HTTP requests contain two unique fields over a standard HTTP message: - -- [`method`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html#/s:4HTTP11HTTPRequestV6methodXev) -- [`url`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html#/s:4HTTP11HTTPRequestV3urlXev) - -The method and URL define what content on the server is being requested. - -```swift -/// GET /hello -let httpReq = HTTPRequest(method: .GET, url: "/hello") -``` - -You can define these when initializing an HTTP request, or set them later if the request is mutable. - -```swift -var httpReq: HTTPRequest = ... -httpReq.method = .POST -httpReq.url = URL(...) -``` - -You can use Foundation's `URLComponents` to create `URL`s from their base components. HTTP request also has a property [`urlString`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html#/s:4HTTP11HTTPRequestV9urlStringSSv) that you can use to set a custom URL `String` manually, without going through `URL`. - -Here is what a serialized HTTP request looks like. This one is querying `/hello`. - -```http -GET /hello HTTP/1.1 -Content-Length: 0 -``` - -## Response - -HTTP responses are generated by servers in response to an HTTP request. HTTP response only has one unique field over general HTTP messages: - -- [`status`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPResponse.html#/s:4HTTP12HTTPResponseV6statusXev) - -The HTTP status is used to inform the client of any errors. The status consists of a status code and a reason. The code is always a three digit number and the reason is a short string explaining the code. You can see all of the status codes on [httpstatuses.com](https://httpstatuses.com). You can find the the Swift struct and built-in status properties in the [Swift-NIO](https://apple.github.io/swift-nio/docs/current/NIOHTTP1/Enums/HTTPResponseStatus.html) repo. - -```swift -let httpRes = HTTPResponse(status: .ok, body: "hello") -``` - -All of the commonly used HTTP statuses will have pre-defined values you can use, like `.ok` for `200 OK`. You can also define your own custom status codes. - -You can define the status when initializing an HTTP response, or set it later if the response is mutable. - -```swift -var httpRes: HTTPResponse = ... -httpRes.status = .notFound -``` - -Here is an example of a serialized HTTP response. - -```http -HTTP/1.1 200 OK -Content-Length: 5 -Content-Type: text/plain - -hello -``` - -## Headers - -Every HTTP message has a collection of headers. Headers contain metadata about the message and help to explain what is in the message's body. - -```http -Content-Length: 5 -Content-Type: text/plain -``` - -There must be at least a `"Content-Length"` or `"Transfer-Encoding"` header to define how long the message's body is. There is almost always a `"Content-Type"` header that explains what _type_ of data the body contains. There are many other common headers such as `"Date"` which specifies when the message was created, and more. - -You can access an HTTP message's headers using the `headers` property. - -```swift -var message: HTTPMessage ... -message.headers.firstValue(for: .contentLength) // 5 -``` - -If you are interacting with common HTTP headers, you can use the convenience HTTP names instead of a raw `String`. - -## Body - -HTTP messages can have an [`HTTPBody`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPBody.html) containing arbitrary data. This data can be either static or streaming and can be in whatever format you want. Use the [`contentType`](https://api.vapor.codes/http/latest/HTTP/Protocols/HTTPMessage.html#/s:4HTTP11HTTPMessagePAAE11contentTypeXev) header to describe the type of data. - -```swift -var message: HTTPMessage = ... -message.body = HTTPBody(string: "Hello, world!") -message.contentType = .plainText -``` - -!!! tip - Setting the `body` property will automatically update the `"Content-Length"` or `"Transfer-Encoding"` headers if required. - -```swift -var message: HTTPMessage = ... -message.body = HTTPBody(string: """ -{"message": "Hello, world!"} -""") -message.contentType = .json -``` - -## Codable - -Two protocols are defined for making it easy to use `Codable` with HTTP: - -- [`HTTPMessageEncoder`](https://api.vapor.codes/http/latest/HTTP/Protocols/HTTPMessageEncoder.html) -- [`HTTPMessageDecoder`](https://api.vapor.codes/http/latest/HTTP/Protocols/HTTPMessageDecoder.html) - -These two coders allow you to encode and decode your custom `Codable` types into an HTTP body, setting the appropriate content type headers. - -By default, HTTP provides conformance for `JSONEncoder` and `JSONDecoder`, but Vapor includes coders for many more types. - -Here is an example of encoding a `Codable` struct to an HTTP response. - -```swift -struct Greeting: Codable { - var message: String -} -// Create an instance of Greeting -let greeting = Greeting(message: "Hello, world!") -// Create a 200 OK response -var httpRes = HTTPResponse(status: .ok) -// Encode the greeting to the response -try JSONEncoder().encode(greeting, to: &httpRes, on: ...) -``` - -## API Docs - -Check out the [API docs](https://api.vapor.codes/http/latest/HTTP/index.html) for more in-depth information about all of the methods. - diff --git a/3.0/docs/http/server.md b/3.0/docs/http/server.md deleted file mode 100644 index f0ea7d6c..00000000 --- a/3.0/docs/http/server.md +++ /dev/null @@ -1,58 +0,0 @@ -# Using HTTPServer - -HTTP servers respond to incoming [`HTTPRequests`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPRequest.html) with [`HTTPResponses`](https://api.vapor.codes/http/latest/HTTP/Structs/HTTPResponse.html). The [`HTTPServer`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPServer.html) type is what powers Vapor's higher-level server. This short guide will show you how to set up your own HTTP server manually. - -!!! tip - If you are using Vapor, you probably don't need to use HTTP's APIs directly. Refer to [Vapor → Getting Started](../vapor/getting-started.md) for the more convenient APIs. - -## Responder - -Creating an HTTP server is easy, and only takes a few lines of code. The first step is to create an [`HTTPServerResponder`](https://api.vapor.codes/http/latest/HTTP/Protocols/HTTPServerResponder.html). This will be directly responsible for generating responses to incoming requests. - -Let's create a simple responder that will echo the request's content. - -```swift -/// Echoes the request as a response. -struct EchoResponder: HTTPServerResponder { - /// See `HTTPServerResponder`. - func respond(to req: HTTPRequest, on worker: Worker) -> Future { - // Create an HTTPResponse with the same body as the HTTPRequest - let res = HTTPResponse(body: req.body) - // We don't need to do any async work here, we can just - // se the Worker's event-loop to create a succeeded future. - return worker.eventLoop.newSucceededFuture(result: res) - } -} -``` - -## Start - -Now that we have a responder, we can create our [`HTTPServer`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPServer.html). We just need to choose a hostname and port for the server to bind to. In this example, we will bind to `http://localhost:8123`. - -```swift -// Create an EventLoopGroup with an appropriate number -// of threads for the system we are running on. -let group = MultiThreadedEventLoopGroup(numThreads: System.coreCount) -// Make sure to shutdown the group when the application exits. -defer { try! group.syncShutdownGracefully() } - -// Start an HTTPServer using our EchoResponder -// We are fine to use `wait()` here since we are on the main thread. -let server = try HTTPServer.start( - hostname: "localhost", - port: 8123, - responder: EchoResponder(), - on: group -).wait() - -// Wait for the server to close (indefinitely). -try server.onClose.wait() -``` - -The static [`start(...)`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPServer.html#/s:4HTTP10HTTPServerC5startXeXeFZ) method creates and returns a new [`HTTPServer`](https://api.vapor.codes/http/latest/HTTP/Classes/HTTPServer.html) asynchronously. The future will be completed when the server has finished booting succesfully, or it will contain an error if something went wrong. - -Once the start future is complete, our server is running. By waiting for the server's `onClose` future to complete, we can keep our application alive until the server closes. Normally the server will not close itself--it will just run indefinitely. However if `server.close()` is ever called, the application can exit gracefully. - -## API Docs - -That's it! Congratulations on making your first HTTP server and responder. Check out the [API docs](https://api.vapor.codes/http/latest/HTTP/index.html) for more in-depth information about all of the available parameters and methods. diff --git a/3.0/docs/images/droplet-color.svg b/3.0/docs/images/droplet-color.svg deleted file mode 100644 index 11c540fb..00000000 --- a/3.0/docs/images/droplet-color.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - Vapor - The Vapor droplet logo in pink and blue. - - - - - - - - - - \ No newline at end of file diff --git a/3.0/docs/images/droplet-white.svg b/3.0/docs/images/droplet-white.svg deleted file mode 100644 index 9f645196..00000000 --- a/3.0/docs/images/droplet-white.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - Vapor - The Vapor droplet logo in white. - - - - \ No newline at end of file diff --git a/3.0/docs/index.md b/3.0/docs/index.md deleted file mode 100644 index ba8cab7e..00000000 --- a/3.0/docs/index.md +++ /dev/null @@ -1,38 +0,0 @@ -# Vapor Documentation - -This is the documentation for Vapor, a Web Framework for Swift that works on macOS and Ubuntu, and all of the packages that Vapor offers. - -Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API. - -## Getting Started - -If this is your first time using Vapor, head to the [Install → macOS](install/macos.md) section to install Swift and Vapor. - -Once you have Vapor installed, check out [Getting Started → Hello, world](getting-started/hello-world.md) to create your first Vapor app! - -## Like Vapor? - -Our small team works hard to make Vapor awesome (and free). Support the framework by [starring Vapor on GitHub](https://github.com/vapor/vapor) -or [donating $1 monthly](https://opencollective.com/vapor)—it helps us a lot. Thanks! - -## Other Sources - -Here are some other great places to find information about Vapor. - -| name | description | link | -|----------------|--------------------------------------------------|-----------------------------------------------------------------| -| Vapor Discord | Chat with thousands of Vapor developers. | [visit →](http://vapor.team) | -| API docs | Auto-generated documentation from code comments. | [visit →](http://api.vapor.codes) | -| Stack Overflow | Ask and answer questions with the `vapor` tag. | [visit →](http://stackoverflow.com/questions/tagged/vapor) | -| Swift Forums | Post in Vapor's section of the Swift.org forums. | [visit →](https://forums.swift.org/c/related-projects/vapor) | -| Source Code | Learn how Vapor works under the hood. | [visit →](https://github.com/vapor/vapor) | -| GitHub Issues | Report bugs or request features on GitHub. | [visit →](https://github.com/vapor/vapor/issues) | - -## Service Providers - -Vapor providers are a convenient way to add functionality to your Vapor projects. -For a full list of providers, check out the [`vapor-service`](https://github.com/search?utf8=✓&q=topic%3Avapor-service&type=Repositories) tag on GitHub. - -## Authors - -[Tanner Nelson](mailto:tanner@vapor.codes), [Logan Wright](mailto:logan@vapor.codes), and the hundreds of members of Vapor. diff --git a/3.0/docs/install/macos.md b/3.0/docs/install/macos.md deleted file mode 100644 index 8b3d31a7..00000000 --- a/3.0/docs/install/macos.md +++ /dev/null @@ -1,57 +0,0 @@ -# Install on macOS - -To use Vapor on macOS, you just need to have Xcode 9.3 or greater installed. - -## Install Xcode - -Install [Xcode 9.3 or greater](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) from the Mac App Store. - -Xcode 9.1 - -!!! warning - After Xcode has been downloaded, you must open it to finish the installation. This may take a while. - -### Verify Installation - -Double check the installation was successful by opening Terminal and running: - -```sh -swift --version -``` - -You should see output similar to: - -```sh -Apple Swift version 4.1.0 (swiftlang-900.0.69.2 clang-900.0.38) -Target: x86_64-apple-macosx10.9 -``` - -Vapor requires Swift 4.1 or greater. - -## Install Vapor - -Now that you have Swift 4.1, let's install the [Vapor Toolbox](../getting-started/toolbox.md). - -The toolbox includes all of Vapor's dependencies as well as a handy CLI tool for creating new projects. - -```sh -brew tap vapor/tap -brew install vapor/tap/vapor -``` - -!!! tip - If you don't already have Homebrew installed, install it at brew.sh → - -### Verify Installation - -Double check the installation was successful by opening Terminal and running: - -```sh -vapor --help -``` - -You should see a long list of available commands. - -## Done - -Now that you have installed Vapor, create your first app in [Getting Started → Hello, world](../getting-started/hello-world.md). diff --git a/3.0/docs/install/ubuntu.md b/3.0/docs/install/ubuntu.md deleted file mode 100644 index d2376051..00000000 --- a/3.0/docs/install/ubuntu.md +++ /dev/null @@ -1,69 +0,0 @@ -# Install on Ubuntu - -Installing Vapor on Ubuntu only takes a couple of minutes. - -## Supported - -Vapor supports the same versions of Ubuntu that Swift supports. - -| Version | Codename | -|---------|-------------------| -| 18.10 | Cosmic Cuttlefish | -| 18.04 | Bionic Beaver | -| 16.10 | Yakkety Yak | -| 16.04 | Xenial Xerus | -| 14.04 | Trusty Tahr | - -## Installation - -Visit Swift.org's [Using Downloads](https://swift.org/download/#using-downloads) guide for instructions on how to install Swift on Linux. - -Double check the Swift installation was successful by printing the version. - -```sh -swift --version -``` - -You should see output similar to: - -```sh -Apple Swift version 4.1.0 (swiftlang-900.0.69.2 clang-900.0.38) -Target: x86_64-apple-macosx10.9 -``` - -## Docker - -You can also use Swift's official Docker images which come with the compiler preinstalled. Learn more at [Swift's Docker Hub](https://hub.docker.com/_/swift). - -## Install Toolbox - -Now that you have Swift installed, let's install the [Vapor Toolbox](https://github.com/vapor/toolbox). This CLI tool is not required to use Vapor, but it includes helpful utilities. - -On Linux, you will need to build the toolbox from source. View the toolbox's releases on GitHub to find the latest version. - -!!! warning - Vapor 3 compatible versions of the toolbox are semver major 3. - -```sh -git clone https://github.com/vapor/toolbox.git -cd toolbox -git checkout -swift build -c release --disable-sandbox -mv .build/release/vapor /usr/local/bin -``` - -Double check the installation was successful by printing help. - -```sh -vapor --help -``` - -You should see a list of available commands. - -## Done - -Now that you have installed Vapor, create your first app in [Getting Started → Hello, world](../getting-started/hello-world.md). - -## Swift.org - -Check out [Swift.org](https://swift.org)'s guide to [using downloads](https://swift.org/download/#using-downloads) if you need more detailed instructions for installing Swift 4.1. diff --git a/3.0/docs/jobs/dispatching-jobs.md b/3.0/docs/jobs/dispatching-jobs.md deleted file mode 100644 index bbdd181e..00000000 --- a/3.0/docs/jobs/dispatching-jobs.md +++ /dev/null @@ -1,64 +0,0 @@ -# Dispatching Jobs - -To dispatch a job, you need access to an instance of `QueueService`. For example: - -```swift -final class EmailController: RouteCollection { - let queue: QueueService - - init(queue: QueueService) { - self.queue = queue - } - - func boot(router: Router) throws { - router.get("/sendemail", use: sendEmail) - } - - func sendEmail(req: Request) throws -> Future { - let job = EmailJobContext(to: "to@to.com", from: "from@from.com", message: "message") - return queue.dispatch(job: job).transform(to: .ok) - } -} -``` - -!!! tip - `QueueService` is thread safe so you can pass it directly to a controller. - -### Setting `maxRetryCount` - -Jobs will automatically retry themselves upon error if you specify a `maxRetryCount`. For example: - -```swift -queue.dispatch(job: job, maxRetryCount: 10) -``` - -### Specify a priority - -Jobs can be sorted into different queue types/priorities depending on your needs. For example, you may want to open an `email` queue and a `background-processing` queue to sort jobs. - -Start by extending `QueueType`: - -```swift -extension QueueType { - static let emails = QueueType(name: "emails") -} -``` - -Then, specify the queue type when you call `dispatch`: - -```swift -queue.dispatch(job: job, queue: .emails) -``` - -If you do not specify a queue the job will be run on the `default` queue. Make sure to follow the instructions in [Getting Started](/jobs/getting-started.md#running-workers) to start workers for each queue type. - -### Specifying a delay - -Jobs can also be set to only run after a certain `Date` has passed. To specify a delay, pass a `Date` into the `delayUntil` parameter in `dispatch`: - -```swift -let oneMinuteDelay = Date(timeIntervalSinceNow: 60) -queue.dispatch(job: job, delayUntil: oneMinuteDelay) -``` - -If a job is dequeued before its delay parameter, the job will be re-queued by the driver. \ No newline at end of file diff --git a/3.0/docs/jobs/getting-started.md b/3.0/docs/jobs/getting-started.md deleted file mode 100644 index 166b7a0b..00000000 --- a/3.0/docs/jobs/getting-started.md +++ /dev/null @@ -1,124 +0,0 @@ -# Jobs - -Jobs ([vapor-community/jobs](https://github.com/vapor/jobs)) is a pure Swift queuing system that allows you to offload task responsibility to a side worker. - -Some of the tasks this package works well for: - -- Sending emails outside of the main request thread -- Performing complex or long-running database operations -- Ensuring job integrity and resilience -- Speeding up response time by delaying non-critical processing -- Scheduling jobs to occur at a specific time - -This package is similar to [Ruby Sidekiq](https://github.com/mperham/sidekiq). It provides the following features: - -- Safe handling of `SIGTERM` and `SIGINT` signals sent by hosting providers to indicate a shutdown, restart, or new deploy. -- Different queue priorities. For example, you can specify a job to be run on the email queue and another job to be run on the data-processing queue. -- Implements the reliable queue process to help with unexpected failures. -- Includes a maxRetryCount feature that will repeat the job until it succeeds up until a specified count. -- Uses NIO to utilize all available cores and EventLoops for jobs. -- Allows users to schedule repeating tasks - -Jobs currently has support for the following drivers which interface with the main protocol: - -- [JobsRedisDriver](https://github.com/vapor/jobs-redis-driver) -- [JobsPostgresqlDriver](https://github.com/vapor-community/jobs-postgresql-driver) - -!!! tip - You should not install this package directly unless you are building a new driver. Install one of the driver packages instead. - -## Getting Started - -Let's take a look at how you can get started using Jobs. - -### Package - -The first step to using Jobs is adding one of the drivers as a dependency to your project in your SPM package manifest file. In this example, we'll use the Redis driver. - -```swift -// swift-tools-version:5.1 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - .package(url: "https://github.com/vapor-community/jobs-redis-driver.git", from: "1.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["JobsRedisDriver", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -```sh -open Package.swift -``` - -Or: - -```sh -vapor xcode -``` - -### Config - -The next step is to configure the Jobs in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import Jobs - -/// Register Jobs providers -try services.register(JobsProvider()) -``` - -You can also specify a custom `refreshInterval` or `persistenceKey` key, if you'd like: - -```swift -import Jobs - -/// Register Jobs providers -try services.register(JobsProvider(refreshInterval: .seconds(10), persistenceKey: "custom_key", commandKey: "queues")) -``` - -### Registering a `Job` - -After modeling a job you must add it to your configuration section like this: - -```swift -//Register jobs -services.register { container -> JobsConfig in - var jobsConfig = JobsConfig() - jobsConfig.add(try EmailJob(emailService: container.make())) - return jobsConfig -} -``` - -### Persistence Layer Config - -To register a persistence driver, see the driver's specific instructions. - -### Running Workers as Processes - -To start a new queue worker, run `vapor run jobs`. You can also specify a specific type of worker to run: `vapor run jobs --queue emails`. - -!!! tip - Workers should stay running in production. Consult your hosting provider to find out how to keep long-running processes alive. Heroku, for example, allows you to specify "worker" dynos like this in your Procfile: `worker: Run run jobs` - -### Running Workers in-process - -To run a worker in the same process as your application (as opposed to starting a whole separate server to handle it), call the `JobsCommand` like this in your `boot.swift` file: - -```swift -JobsCommand(application: app).run() -``` - -To run scheduled jobs in process, pass the `scheduled` flag: - -```swift -JobsCommand(application: app, scheduled: true).run() -``` \ No newline at end of file diff --git a/3.0/docs/jobs/jobs.md b/3.0/docs/jobs/jobs.md deleted file mode 100644 index 34319d1b..00000000 --- a/3.0/docs/jobs/jobs.md +++ /dev/null @@ -1,44 +0,0 @@ -# The `Job` Protocol - -Jobs are defined by the `Job` and `JobData` protocols. - -### Modeling a `JobData` object -```swift -import Foundation -import Jobs -import Vapor - -struct EmailJobContext: JobData { - let to: String - let from: String - let message: String -} -``` - -### Modeling a `Job` object: -```swift -import Foundation -import Jobs -import Vapor - -struct EmailJob: Job { - let emailService: EmailService - - func dequeue(_ context: JobContext, _ data: EmailJobContext) -> EventLoopFuture { - return emailService.sendEmail(to: data.to, subject: data.subject, content: data.message) - } - - func error(_ context: JobContext, _ error: Error, _ data: EmailJobContext) -> EventLoopFuture { - // If you don't want to handle errors you can simply return a future. You can also omit this function entirely. - return context.eventLoop.future() - } -} -``` - -!!! tip - Don't forget to follow the instructions in [Getting Started](/jobs/getting-started.md#registering-a-job) to add this job to your configuration file. - -!!! warning - Each job's `Context` must have a unique name. Do not nest your context objects without uniquely naming them. - -`dequeue` and `error` are not throwing methods - in order to return an error from the function you must use `worker.future(error: myErrorHere)`. \ No newline at end of file diff --git a/3.0/docs/jobs/redis-driver.md b/3.0/docs/jobs/redis-driver.md deleted file mode 100644 index eb43ac1c..00000000 --- a/3.0/docs/jobs/redis-driver.md +++ /dev/null @@ -1,37 +0,0 @@ -# Jobs Redis Driver - -In order to setup the Redis jobs driver, add the following to your SPM manifest: - -```swift -// swift-tools-version:5.1 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - .package(url: "https://github.com/vapor-community/jobs-redis-driver.git", from: "0.2.0"), - ], - targets: [ - .target(name: "App", dependencies: ["JobsRedisDriver", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -### Configuration - -In your `configure.swift` file, add the following: - -```swift -guard let url = URL(string: "redis://127.0.0.1:6379") else { throw Abort(.internalServerError) } -guard let redisConfiguration = RedisConfiguration(url: url) else { throw Abort(.internalServerError) } - -services.register(JobsPersistenceLayer.self) { container -> JobsRedisDriver in - let redisClient = RedisConnectionSource(config: redisConfiguration, eventLoop: container.next()) - return JobsRedisDriver(client: redisClient, eventLoop: container.next()) -} -``` \ No newline at end of file diff --git a/3.0/docs/jobs/scheduling-jobs.md b/3.0/docs/jobs/scheduling-jobs.md deleted file mode 100644 index 24d5b5a8..00000000 --- a/3.0/docs/jobs/scheduling-jobs.md +++ /dev/null @@ -1,90 +0,0 @@ -# Scheduling Jobs - -The Jobs package also allows you to schedule jobs to occur at certain points in time. - -## Starting the scheduler worker -The scheduler requires a separate worker process to be running, similar to the queue worker. You can start the worker by running this command: - -```sh -vapor run jobs --scheduled -``` - -!!! tip - Workers should stay running in production. Consult your hosting provider to find out how to keep long-running processes alive. Heroku, for example, allows you to specify "worker" dynos like this in your Procfile: `worker: Run run jobs --scheduled` - -## Creating a `ScheduledJob` -To being, start by creating a new `ScheduledJob`: - -```swift -import Vapor -import Jobs - -struct CleanupJob: ScheduledJob { - // Add extra services here via dependency injection, if you need them. - - func run(context: JobContext) -> EventLoopFuture { - // Do some work here, perhaps queue up another job. - return context.eventLoop.makeSucceededFuture(()) - } -} -``` - -Then, in your configure code, register the scheduled job: - -```swift -services.register { container -> JobsConfig in - var jobsConfig = JobsConfig() - - jobsConfig.schedule(CleanupJob()) - .yearly() - .in(.may) - .on(23) - .at(.noon) - - return jobsConfig -} -``` - -The job in the example above will be run every year on May 23rd at 12:00 PM. - -!!! tip - The Scheduler takes the timezone of your server. - -## Available builder methods -There are five main methods that can be called on a scheduler, each of which creates its respective builder object that contains more helper methods. You should continue building out a scheduler object until the compiler does not give you a warning about an unused result. See below for all available methods: - -| Helper Function | Available Modifiers | Description | -|-----------------|---------------------------------------|--------------------------------------------------------------------------------| -| `yearly()` | `in(_ month: Month) -> Monthly` | The month to run the job in. Returns a `Monthly` object for further building. | -| `monthly()` | `on(_ day: Day) -> Daily` | The day to run the job in. Returns a `Daily` object for further building. | -| `weekly()` | `on(_ dayOfWeek: DayOfWeek) -> Daily` | The day of the week to run the job on. Returns a `Daily` object. | -| `daily()` | `at(_ time: Time)` | The time to run the job on. Final method in the chain. | -| | `at(_ hour: Hour24, _ minute: Minute)`| The hour and minute to run the job on. Final method in the chain. | -| | `at(_ hour: Hour12, _ minute: Minute, _ period: HourPeriod)` | The hour, minute, and period to run the job on. Final method of the chain | -| `hourly()` | `at(_ minute: Minute)` | The minute to run the job at. Final method of the chain. | - -## Available helpers -Jobs ships with some helpers enums to make scheduling a bit easier: - -| Helper Function | Available Helper Enum | -|-----------------|---------------------------------------| -| `yearly()` | `.january`, `.february`, `.march`, ...| -| `monthly()` | `.first`, `.last`, `.exact(1)` | -| `weekly()` | `.sunday`, `.monday`, `.tuesday`, ... | -| `daily()` | `.midnight`, `.noon` | - -To use the helper enum, call in to the appropriate modifier on the helper function and pass the value. For example: - -```swift -// Every year in January -.yearly().in(.january) - -// Every month on the first day -.monthly().on(.first) - -// Every week on Sunday -.weekly().on(.sunday) - -// Every day at midnight -.daily().at(.midnight) -``` \ No newline at end of file diff --git a/3.0/docs/jwt/getting-started.md b/3.0/docs/jwt/getting-started.md deleted file mode 100644 index c5597c7f..00000000 --- a/3.0/docs/jwt/getting-started.md +++ /dev/null @@ -1,38 +0,0 @@ -# Getting Started with JWT - -JWT ([vapor/jwt](https://github.com/vapor/jwt)) is a package for parsing and serializing **J**SON **W**eb **T**okens supporting both HMAC and RSA signing. JWTs are often used for implementing _decentralized_ authentication and authorization. - -Since all of the authenticated user's information can be embedded _within_ a JWT, there is no need to query a central authentication server with each request to your service. Unlike standard bearer tokens that must be looked up in a centralized database, JWTs contain cryptographic signatures that can be used to independently verify their authenticity. - -If implemented correctly, JWTs can be a powerful tool for making your application [horizontally scalable](https://stackoverflow.com/questions/11707879/difference-between-scaling-horizontally-and-vertically-for-databases). Learn more about JWT at [jwt.io](https://jwt.io). - -!!! tip - If your goal is not horizontal scalability, a standard bearer token will likely be a better solution. JWTs have some downsides worth considering such as the inability to revoke a token once it has been issued (until it expires normally). - -Let's take a look at how you can get started using JWT. - -## Package - -The first step to using JWT is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // 🔏 JSON Web Token signing and verification (HMAC, RSA). - .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["JWT", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -That's it for basic setup. The next section will give you an overview of the package's APIs. As always, feel free to visit the [API Docs](https://api.vapor.codes/jwt/latest/JWT/index.html) for more specific information. diff --git a/3.0/docs/jwt/overview.md b/3.0/docs/jwt/overview.md deleted file mode 100644 index 07a10a93..00000000 --- a/3.0/docs/jwt/overview.md +++ /dev/null @@ -1,186 +0,0 @@ -# Using JWT - -JSON Web Tokens are a great tool for implementing _decentralized_ authentication and authorization. Once you are finished configuring your app to use the JWT package (see [JWT → Getting Started](getting-started.md)), you are ready to begin using JWTs in your app. - -## Structure - -Like other forms of token-based auth, JWTs are sent using the bearer authorization header. - -```http -GET /hello HTTP/1.1 -Authorization: Bearer -... -``` - -In the example HTTP request above, `` would be replaced by the serialized JWT. [jwt.io](https://jwt.io) hosts an online tool for parsing and serializing JWTs. We will use that tool to create a token for testing. - -![JWT.io](https://user-images.githubusercontent.com/1342803/44101613-ce328e04-9fb5-11e8-9aed-2d9900d0c40c.png) - -### Header - -The header is mainly used to specify which algorithm was used to generate the token's signature. This is used by the accepting app to verify the token's authenticity. - -Here is the raw JSON data for our header: - -```json -{ - "alg": "HS256", - "typ": "JWT" -} -``` - -This specifies the HMAC SHA-256 signing algorithm and that our token is indeed a JWT. - -### Payload - -The payload is where you store information to identify the authenticated user. You can store any data you want here, but be careful not to store too much as some web browsers limit HTTP header sizes. - -The payload is also where you store _claims_. Claims are standardized key / value pairs that many JWT implementations can recognize and act on automatically. A commonly used claim is _Expiration Time_ which stores the token's expiration date as a unix timestamp at key `"exp"`. See a full list of supported claims in [RFC 7519 § 4.1](https://tools.ietf.org/html/rfc7519#section-4.1). - -To keep things simple, we will just include our user's identifier and name in the payload: - -```json -{ - "id": 42, - "name": "Vapor Developer" -} -``` - -### Secret - -Last but not least is the secret key used to sign and verify the JWT. For this example, we are using the `HS256` algorithm (specified in the JWT header). HMAC algorithms use a single secret key for both signing and verifying. - -To keep things simple, we will use the following string as our key: - -``` -secret -``` - -Other algorithms, like RSA, use asymmetric (public and private) keys. With these types of algorithms, only the _private_ key is able to create (sign) JWTs. Both the _public_ and _private_ keys can verify JWTs. This allows for an added layer of security as you can distribute the public key to services that should only be able to verify tokens, not create them. - -### Serialized - -Finally, here is our fully serialized token. This will be sent via the bearer authorization header. - -``` -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM -``` - -Each segment is separated by a `.`. The overall structure of the token is the following: - -``` -
    .. -``` - -Note that the header and payload segments are simply base64-url encoded JSON. It is important to remember that all information your store in a normal JWT is publically readable. - - -## Parse - -Let's take a look at how to parse and verify incoming JWTs. - -### Payload - -First, we need to create a `Codable` type that represents our payload. This should also conform to [`JWTPayload`](https://api.vapor.codes/jwt/latest/JWT/Protocols/JWTPayload.html). - -```swift -struct User: JWTPayload { - var id: Int - var name: String - - func verify(using signer: JWTSigner) throws { - // nothing to verify - } -} -``` - -Since our simple payload does not include any claims, we can leave the `verify(using:)` method empty for now. - -### Route - -Now that our payload type is ready, we can parse and verify an incoming JWT. - -```swift -import JWT -import Vapor - -router.get("hello") { req -> String in - // fetches the token from `Authorization: Bearer ` header - guard let bearer = req.http.headers.bearerAuthorization else { - throw Abort(.unauthorized) - } - - // parse JWT from token string, using HS-256 signer - let jwt = try JWT(from: bearer.token, verifiedUsing: .hs256(key: "secret")) - return "Hello, \(jwt.payload.name)!" -} -``` - -This snippet creates a new route at `GET /hello`. The first part of the route handler fetches the `` value from the bearer authorization header. The second part uses the [`JWT`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html) struct to parse the token using an `HS256` signer. - -Once the JWT is parsed, we access the [`payload`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html#/s:3JWTAAV7payloadxvp) property which contains an instance of our `User` type. We then access the `name` property to say hello! - -Run the following request and check the output: - -```http -GET /hello HTTP/1.1 -Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM -Content-Length: 0 -``` - -You should see the following response: - -```http -HTTP/1.1 200 OK -Content-Length: 23 -Hello, Vapor Developer! -``` - -## Serialize - -Let's take a look at how to create and sign a JWT. - -### Payload - -First, we need to create a `Codable` type that represents our payload. This should also conform to [`JWTPayload`](https://api.vapor.codes/jwt/latest/JWT/Protocols/JWTPayload.html). - -```swift -struct User: JWTPayload { - var id: Int - var name: String - - func verify(using signer: JWTSigner) throws { - // nothing to verify - } -} -``` - -Since our simple payload does not include any claims, we can leave the `verify(using:)` method empty for now. - -### Route - -Now that our payload type is ready, we can generate a JWT. - -```swift -router.post("login") { req -> String in - // create payload - let user = User(id: 42, name: "Vapor Developer") - - // create JWT and sign - let data = try JWT(payload: user).sign(using: .hs256(key: "secret")) - return String(data: data, encoding: .utf8) ?? "" -} -``` - -This snippet creates a new route at `POST /login`. The first part of the route handler creates an instance of our `User` payload type. The second part creates an instance of `JWT` using our payload, and calls the [`sign(using:)`](https://api.vapor.codes/jwt/latest/JWT/Structs/JWT.html#/s:3JWTAAV4sign10Foundation4DataVAA9JWTSignerC5using_tKF) method. This method returns `Data`, which we convert to a `String`. - -If you visit this route, you should get the following output: - -``` -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NDIsIm5hbWUiOiJWYXBvciBEZXZlbG9wZXIifQ.__Dm_tr1Ky2VYhZNoN6XpEkaRHjtRgaM6HdgDFcc9PM -``` - -If you plug that JWT into [jwt.io](https://jwt.io) and enter the secret (`secret`), you should see the encoded data and a message "Signature Verified". - - - diff --git a/3.0/docs/leaf/custom-tags.md b/3.0/docs/leaf/custom-tags.md deleted file mode 100644 index 1056b865..00000000 --- a/3.0/docs/leaf/custom-tags.md +++ /dev/null @@ -1,56 +0,0 @@ -# Custom Tags - -You can create custom Leaf tags using the [`TagRenderer`](https://api.vapor.codes/template-kit/latest/TemplateKit/Protocols/TagRenderer.html) protocol. - -To demonstrate this, let's take a look at creating a custom tag `#now` that prints the current timestamp. The tag will also support a single, optional parameter for specifying the date format. - -## Tag Renderer - -First create a class called `NowTag` and conform it to `TagRenderer`. - -```swift -final class NowTag: TagRenderer { - init() { } - - func render(tag: TagContext) throws -> EventLoopFuture { - ... - } -} -``` - -Now let's implement the `render(tag:)` method. The `TagContext` context passed to this method has everything we should need. - -```swift -let formatter = DateFormatter() -switch tag.parameters.count { -case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" -case 1: - guard let string = tag.parameters[0].string else { - throw ... - } - formatter.dateFormat = string -default: - throw ... -} - -let string = formatter.string(from: .init()) -return tag.container.future(.string(string)) -``` - -## Configure Tag - -Now that we've implemented `NowTag`, we just need to configure it. You can configure any `TagRenderer` like this--even if they come from a separate package. - -```swift -services.register { container -> LeafTagConfig in - var config = LeafTagConfig.default() - config.use(NowTag(), as: "now") - return config -} -``` - -And that's it! We can now use our custom tag in Leaf. - -```leaf -The time is #now() -``` diff --git a/3.0/docs/leaf/getting-started.md b/3.0/docs/leaf/getting-started.md deleted file mode 100644 index 960c1f38..00000000 --- a/3.0/docs/leaf/getting-started.md +++ /dev/null @@ -1,108 +0,0 @@ -# Leaf - -Leaf is a powerful templating language with Swift-inspired syntax. You can use it to generate dynamic HTML pages for a front-end website or generate rich emails to send from an API. - -## Package - -The first step to using Leaf is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["Leaf", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -## Configure - -Once you have added the package to your project, you can configure Vapor to use it. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import Leaf - -try services.register(LeafProvider()) -``` - -If your application supports multiple view renderers, you may need to specify that you would like to use Leaf. - -```swift -config.prefer(LeafRenderer.self, for: ViewRenderer.self) -``` - -## Folder Structure - -Once you have configured Leaf, you will need to ensure you have a `Views` folder to store your `.leaf` files in. By default, Leaf expects the views folder to be a `./Resources/Views` relative to your project's root. - -You will also likely want to enable Vapor's [`FileMiddleware`](https://api.vapor.codes/vapor/latest/Vapor/Classes/FileMiddleware.html) to serve files from your `/Public` folder. - -``` -VaporApp -├── Package.swift -├── Resources -│   ├── Views -│   │   └── hello.leaf -├── Public -│   ├── images (images resources) -│   ├── styles (css resources) -└── Sources -    └── ... -``` - -## Syntax Highlighting - -You may also wish to install one of these third-party packages that provide support for syntax highlighting in Leaf templates. - -### Sublime - -Install the package [Leaf](https://packagecontrol.io/packages/Leaf) from package control. - -### Atom - -[language-leaf](https://atom.io/packages/language-leaf) by ButkiewiczP - -### Xcode - -It is currently not possible to implement Leaf Syntax Highlighting in Xcode, however, using Xcode's HTML Syntax Coloring can help a bit. Select one or more Leaf files and then choose Editor > Syntax Coloring > HTML. Your selected Leaf files will now use Xcode's HTML Syntax Coloring. Unfortunately the usefulness of this is limited because this association will be removed when `vapor xcode` is run. - -There appears to be a way to [make Xcode file associations persist](http://stackoverflow.com/questions/9050035/how-to-make-xcode-recognize-a-custom-file-extension-as-objective-c-for-syntax-hi) but that requires a bit more kung-fu. - -Also, there is a unsupported solution that associates `.leaf`-files in Xcode with HTML syntax coloring automatically: [xcode-leaf-color-schemer](https://github.com/ashokgelal/xcode-leaf-color-schemer) by ashokgelal. - -### VS Code - -[html-leaf](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf) by FranciscoAmado - -### CLion & AppCode - -Some preliminary work has been done to implement a Leaf Plugin for CLion & AppCode but lack of skill and interest in Java has slowed progress! If you have IntelliJ SDK experience and want to help with this, message Tom Holland on [Vapor Slack](http://vapor.team) - -## Rendering a View - -Now that Leaf is configured, let's render your first template. Inside of the `Resources/Views` folder, create a new file called `hello.leaf` with the following contents: - -```leaf -Hello, #(name)! -``` - -Then, register a route (usually done in `routes.swift` or a controller) to render the view. - -```swift -import Leaf - -router.get("hello") { req -> Future in - return try req.view().render("hello", ["name": "Leaf"]) -} -``` - -Open your browser and visit `/hello`. You should see `Hello, Leaf!`. Congratulations on rendering your first Leaf view! diff --git a/3.0/docs/leaf/overview.md b/3.0/docs/leaf/overview.md deleted file mode 100644 index 098509df..00000000 --- a/3.0/docs/leaf/overview.md +++ /dev/null @@ -1,289 +0,0 @@ -# Leaf Overview - -Leaf is a powerful templating language with Swift-inspired syntax. You can use it to generate dynamic HTML pages for a front-end website or generate rich emails to send from an API. - -This guide will give you an overview of Leaf's syntax and the available tags. - -## Template syntax - -Here is an example of a basic Leaf tag usage. - -```leaf -There are #count(users) users. -``` - -Leaf tags are made up of four elements: - -- Token `#`: This signals the leaf parser to begin looking for a tag. -- Name `count`: that identifies the tag. -- Parameter List `(users)`: May accept zero or more arguments. -- Body: An optional body can be supplied to some tags. This is similar to Swift's trailing-closure syntax. - -There can be many different usages of these four elements depending on the tag's implementation. Let's look at a few examples of how Leaf's built-in tags might be used: - -```leaf -#(variable) -#embed("template") -#set("title") { Welcome to Vapor } -#count(friends) -#for(friend in friends) {
  • #(friend.name)
  • } -``` - -Leaf also supports many expressions you are familiar with in Swift. - -- `+` -- `>` -- `==` -- `||` -- etc. - -```leaf -#if(1 + 1 == 2) { - Hello! -} -``` - -## Context - -In the example from [Getting Started](./getting-started.md), we used a `[String: String]` dictionary to pass data to Leaf. However, you can pass anything that conforms to `Encodable`. It's actually preferred to use `Encodable` structs since `[String: Any]` is not supported. This means you *can not* pass in an array, and should instead wrap it in a struct: - -```swift -struct WelcomeContext: Encodable { - var title: String - var numbers: [Int] -} -return try req.view().make("home", WelcomeContext(title: "Hello!", numbers: [42, 9001])) -``` - -That will expose `title` and `numbers` to our Leaf template, which can then be used inside tags. For example: - -```leaf -

    #(title)

    -#for(number in numbers){ -

    #(number)

    -} -``` - -## Usage - -Here are some common Leaf usage examples. - -### Conditions - -Leaf is able to evaluate a range of conditions using its `#if` tag. For example, if you provide a variable it will check that variable exists in its context: - -```leaf -#if(title) { - The title is #(title) -} else { - No title was provided. -} -``` - -You can also write comparisons, for example: - -```leaf -#if(title == "Welcome") { - This is a friendly web page. -} else { - No strangers allowed! -} -``` - -If you want to use another tag as part of your condition, you should omit the `#` for the inner tag. For example: - -```leaf -#if(lowercase(title) == "welcome") { - This is a friendly web page. -} else { - No strangers allowed! -} -``` - -Just like in Swift, you can also use `else if` statements: - -```leaf -#if(title == "Welcome") { - This is a friendly web page. -} else if (1 == 2) { - What? -} else { - No strangers allowed! -} -``` - -### Loops - -If you provide an array of items, Leaf can loop over them and let you manipulate each item individually using its `#for` tag. - -For example, we could update our Swift code to provide a list of planets: - -```swift -struct SolarSystem: Codable { - let planets = ["Venus", "Earth", "Mars"] -} - -return try req.view().render(..., SolarSystem()) -``` - -We could then loop over them in Leaf like this: - -```leaf -Planets: -
      -#for(planet in planets) { -
    • #(planet)
    • -} -
    -``` - -This would render a view that looks like: - -``` -Planets: -- Venus -- Earth -- Mars -``` - -Leaf provides some extra variables inside a `#for` loop to give you more information about the loop's progress: - -- The `isFirst` variable is true when the current iteration is the first one. -- The `isLast` variable is true when it's the last iteration. -- The `index` variable will be set to the number of the current iteration, counting from 0. - -Here's how we could use a loop variable to print just the first name in our array: - -```leaf -#for(planet in planets) { - #if(isFirst) { #(planet) is first! } -} -``` - -### Embedding templates - -Leaf’s `#embed` tag allows you to copy the contents of one template into another. When use this, you should always omit the template file's .leaf extension. - -Embedding is useful for copying in a standard piece of content, for example a page footer or advert code: - -```leaf -#embed("footer") -``` - -This tag is also useful for building one template on top of another. For example, you might have a master.leaf file that includes all the code required to lay out your website – HTML structure, CSS and JavaScript – with some gaps in place that represent where page content varies. - -Using this approach, you would construct a child template that fills in its unique content, then embeds the parent template that places the content appropriately. To do this, you can use the `#set` and `#get` tags to store and later retrieve content from the context. - -For example, you might create a child.leaf template like this: - -```leaf -#set("body") { -

    Welcome to Vapor!

    -} - -#embed("master") -``` - -This stores some HTML in the context as `body` using `#set`. We then embed master.leaf which will render `body` along with any other context variables passed in from Swift. For example, master.leaf might look like this: - -```leaf - - - #(title) - - #get(body) - -``` - -Here we are using `#get` to fetch the content previously stored in the context. When passed `["title": "Hi there!"]` from Swift, child.leaf will render as follows: - -```html - - - Hi there! - -

    Welcome to Vapor!

    - -``` - -### Comments - -You can write single or multiline comments with Leaf. They will be discarded when rendering the view. - -```leaf -#// Say hello to the user -Hello, #(name)! -``` - -Multi-line comments are opened with `#/*` and closed with `*/`. - -```leaf -#/* - Say hello to the user -*/ -Hello, #(name)! -``` - -### Other tags - -#### `#date` - -The `#date` tag formats dates into a readable string. - -```swift -render(..., ["now": Date()]) -``` - -```leaf -The time is #date(now) -``` - -You can pass a custom date formatter string as the second argument. See Swift's [`DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) for more information. - -```leaf -The date is #date(now, "yyyy-MM-dd") -``` - -#### `#capitalize` - -The `#capitalize` tag uppercases the first letter of any string. - -```leaf -#capitalize(name) -``` - -#### `#contains` - -The `#contains` tag accepts an array and a value as its two parameters, and returns true if the array in parameter one contains the value in parameter two. - -```leaf -#if(contains(planets, "Earth")) { - Earth is here! -} else { - Earth is not in this array. -} -``` - -#### `#count` - -The `#count` tag returns the number of items in an array. For example: - -```leaf -Your search matched #count(matches) pages. -``` - -#### `#lowercase` - -The `#lowercase` tag lowercases all letters in a string. - -```leaf -#lowercase(name) -``` - -#### `#uppercase` - -The `#uppercase` tag uppercases all letters in a string. - -```leaf -#uppercase(name) -``` diff --git a/3.0/docs/logging/getting-started.md b/3.0/docs/logging/getting-started.md deleted file mode 100644 index 2ecb2b29..00000000 --- a/3.0/docs/logging/getting-started.md +++ /dev/null @@ -1,43 +0,0 @@ -# Getting Started with Logging - -The Logging module is provided as a part of Vapor's Console package ([vapor/console](https://github.com/vapor/console)). This module provides convenience APIs for creating log - -!!! tip - For an in-depth look at all of Logging's APIs, check out the [Logging API docs](https://api.vapor.codes/console/latest/Logging/index.html). - -## Usage - -This package is included with Vapor and exported by default. You will have access to all `Logging` APIs when you import `Vapor`. - -```swift -import Vapor // implies import Logging -``` - -### Standalone - -The Logging module, part of the larger Vapor Console package, can also be used on its own with any Swift project. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - /// 💻 APIs for creating interactive CLI tools. - .package(url: "https://github.com/vapor/console.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Logging", ... ]) - ] -) -``` - -Use `import Logging` to access the APIs. - -## Overview - -Continue to [Logging → Overview](overview.md) for an overview of Logging's features. \ No newline at end of file diff --git a/3.0/docs/logging/overview.md b/3.0/docs/logging/overview.md deleted file mode 100644 index df17ae9b..00000000 --- a/3.0/docs/logging/overview.md +++ /dev/null @@ -1,17 +0,0 @@ -# Logging Overview - -The logging package provides convenience APIs for logging information while your app is running. The [`Logger`](https://api.vapor.codes/console/latest/Logging/Protocols/Logger.html) protocol declares a common interface for logging information. A default [`PrintLogger`](https://api.vapor.codes/console/latest/Logging/Classes/PrintLogger.html) is available, but you can implement custom loggers to suit your specific needs. - -## Log - -First, you will want to use a `Container` to create an instance of `Logger`. Then you can use the convenience methods to log information. - -```swift -let logger = try req.make(Logger.self) -logger.info("Logger created!") -``` - -See [`Logger`](https://api.vapor.codes/console/latest/Logging/Protocols/Logger.html) in the API docs for a list of all available methods. - -Check out [Service → Services](../service/services.md#instance) for more information on how to register a custom logger. - diff --git a/3.0/docs/multipart/getting-started.md b/3.0/docs/multipart/getting-started.md deleted file mode 100644 index 86f2654e..00000000 --- a/3.0/docs/multipart/getting-started.md +++ /dev/null @@ -1,42 +0,0 @@ -# Getting Started with Multipart - -Multipart ([vapor/multipart](https://github.com/vapor/multipart)) is a small package that helps you parse and serialize `multipart` encoded data. Multipart is a widely-supported encoding on the web. It's most often used for serializing web forms, especially ones that contain rich media like images. - -The Multipart package makes it easy to use this encoding by integrating directly with `Codable`. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `Multipart` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The Multipart package is lightweight, pure-Swift, and has very few dependencies. This means it can be used to work with multipart-encoded data for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/multipart.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Multipart", ... ]) - ] -) -``` - -Use `import Multipart` to access the APIs. - -!!! warning - Some of this guide may contain Vapor-specific APIs, however most of it should be applicable to the Multipart package in general. - Visit the [API Docs](https://api.vapor.codes/multipart/latest/Multipart/index.html) for Multipart-specific API info. - diff --git a/3.0/docs/multipart/overview.md b/3.0/docs/multipart/overview.md deleted file mode 100644 index f02c25a6..00000000 --- a/3.0/docs/multipart/overview.md +++ /dev/null @@ -1,112 +0,0 @@ -# Using Multipart - -Multipart is a widely-supported encoding on the web. It's most often used for serializing web forms, especially ones that contain rich media like images. It allows for arbitrary data to be encoded in each part thanks to a unique delimiter _boundary_ that is defined separately. This boundary is guaranteed by the client to not appear anywhere in the data. - -Multipart is a powerful encoding, however it is rarely used in its base format. Most commonly, `multipart/form-data` is used. This encoding adds a `"name"` property to each part of the multipart data. This is required for serializing web forms. For the rest of this guide, assume we are talking about `multipart/form-data` unless otherwise specified. - -!!! tip - Multipart integrates with [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html) like all other encoding methods in Vapor. See [Vapor → Content](../vapor/content.md) for more information about the [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html) protocol. - -Let's take a look at how to decode a `multipart/form-data`-encoded request. - -## Decode - -Most often, you will be decoding `multipart/form-data`-encoded requests from a web form. Let's take a look at what one of these requests might look like. After that, we will take a look at what the HTML form for that request would look like. - -### Request - -Here is an example `multipart/form-data`-encoded request for creating a new user. - -```http -POST /users HTTP/1.1 -Content-Type: multipart/form-data; boundary=123 - ---123 -Content-Disposition: form-data; name="name" - -Vapor ---123 -Content-Disposition: form-data; name="age" - -3 ---123 -Content-Disposition: form-data; name="image"; filename="droplet.png" - - ---123 -Content-Disposition: form-data; name="isAdmin" - -false ---123-- -``` - -You can see the multipart data uses a _boundary_ (in this case it is `"123"`) to separate the data. This will usually be a longer string. The client sending a multipart-encoded request must ensure that the boundary it supplies does not appear anywhere in the content it is sending you. That's what allows this encoding to be used to send things like files. - -### Form - -There are many ways to create a multipart-encoded request, but the most common is an HTML web form. Here is what the HTML form for this request might have looked like. - -```html -
    - - - -
    -``` - -Take note of the `enctype` attribute on the `
    ` as well as the `file` type input. This is what allows us to send files via the web form. - -### Content - -Now let's take a look at how we would handle this request in Vapor. The first step (as always with [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html)) is to create a `Codable` struct that represents the data structure. - -```swift -import Vapor - -struct User: Content { - var name: String - var age: Int - var image: Data - var isAdmin: Bool -} -``` - -!!! tip - You can use [`File`](https://api.vapor.codes/core/latest/Core/Structs/File.html) instead of `Data` if you would also like to access the filename. - -Now that we have our `User` struct, let's decode that request! We can use the [`ContentContainer`](https://api.vapor.codes/vapor/latest/Vapor/Structs/ContentContainer.html) to do this easily. - -```swift -router.post("users") { req -> Future in - return try req.content.decode(User.self).map(to: HTTPStatus.self) { user in - print(user.name) // "Vapor" - print(user.age) // 3 - print(user.image) // Raw image data - print(user.isAdmin) - return .ok - } -} -``` - -Now when you post the form to `/users`, you should see the information printed in the console. Nice work! - -## Encode - -APIs encode multipart data much less often than they decode it. However, encoding is just as easy with Vapor. Using our same `User` struct from the previous example, here is how we can encode a multipart-encoded response. - -```swift -router.get("multipart") { req -> User in - let res = req.makeResponse() - let user = User(name: "Vapor", age: 3, image: Data(...), isAdmin: false) - res.content.encode(user, as: .formData) - return user -} -``` - -!!! tip - If you set a default `MediaType` on your `Content` types, then you can return them directly in the route closure. - -## Parsing & Serializing - -The Multipart package also offers APIs for parsing and serializing `multipart/form-data` data without using `Codable`. Check out the [API Docs](https://api.vapor.codes/multipart/latest/Multipart/index.html) for more information on using those APIs. - diff --git a/3.0/docs/mysql/getting-started.md b/3.0/docs/mysql/getting-started.md deleted file mode 100644 index 63d45077..00000000 --- a/3.0/docs/mysql/getting-started.md +++ /dev/null @@ -1,128 +0,0 @@ -# MySQL - -MySQL ([vapor/mysql](https://github.com/vapor/mysql)) is a pure Swift MySQL (and MariaDB) client built on top of [SwiftNIO](https://github.com/apple/swift-nio.git). - -The higher-level, Fluent ORM guide is located at [Fluent → Getting Started](../fluent/getting-started.md). Using just the MySQL package directly for your project may be a good idea if any of the following are true: - -- You have an existing DB with non-standard structure. -- You rely heavily on custom or complex SQL queries. -- You just plain don't like ORMs. - -MySQL core extends [DatabaseKit](../database-kit/getting-started.md) which provides some conveniences like connection pooling and integrations with Vapor's [Services](../getting-started/services.md) architecture. - -!!! tip - Even if you do choose to use [Fluent MySQL](../fluent/getting-started.md), all of the features of MySQL core will be available to you. - -## Getting Started - -Let's take a look at how you can get started using MySQL core. - -### Package - -The first step to using MySQL core is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // 🐬 Pure Swift MySQL client built on non-blocking, event-driven sockets. - .package(url: "https://github.com/vapor/mysql.git", from: "3.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["MySQL", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -```sh -vapor xcode -``` - -### Config - -The next step is to configure the database in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import MySQL - -/// Register providers first -try services.register(MySQLProvider()) -``` - -Registering the provider will add all of the services required for MySQL to work properly. It also includes a default database config struct that uses standard credentials. - -#### Customizing Config - -You can of course override the default configuration provided by `MySQLProvider` if you'd like. - -To configure your database manually, register a [`DatabasesConfig`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabasesConfig.html) struct to your services. - -```swift -// Configure a MySQL database -let mysql = try MySQLDatabase(config: MySQLDatabaseConfig(...)) - -/// Register the configured MySQL database to the database config. -var databases = DatabasesConfig() -databases.add(database: mysql, as: .mysql) -services.register(databases) -``` - -See [`MySQLDatabase`](https://api.vapor.codes/mysql/latest/MySQL/Classes/MySQLDatabase.html) and [`MySQLDatabaseConfig`](https://api.vapor.codes/mysql/latest/MySQL/Structs/MySQLDatabaseConfig.html) for more information. - -MySQL's default database identifier is `.mysql`. You can create a custom identifier if you want by extending [`DatabaseIdentifier`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseIdentifier.html). - -### Query - -Now that the database is configured, you can make your first query. - -```swift -struct MySQLVersion: Codable { - let version: String -} - -router.get("sql") { req in - return req.withPooledConnection(to: .mysql) { conn in - return conn.raw("SELECT @@version as version") - .all(decoding: MySQLVersion.self) - }.map { rows in - return rows[0].version - } -} -``` - -Visiting this route should display your MySQL version. - -Here we are making use database connection pooling. You can learn more about creating connections in [DatabaseKit → Getting Started](../database-kit/getting-started.md). - -Learn more about building queries in [SQL → Getting Started](../sql/getting-started.md). - -Visit MySQL's [API docs](https://api.vapor.codes/mysql/latest/MySQL/index.html) for detailed information about all available types and methods. - -### Cache - -You can configure Vapor to use MySQL as a cache if you are using Fluent. This enables things such as session persistence across server reboots. - -```swift -typealias MySQLCache = DatabaseKeyedCache> - -/// Register MySQL as a cache provider. -services.register(KeyedCache.self) { container -> MySQLCache in - let pool = try container.connectionPool(to: .mysql) - return .init(pool: pool) -} -config.prefer(MySQLCache.self, for: KeyedCache.self) - -/// Sets up the migrations so that Fluent can create the necessary tables. -var migrations = MigrationConfig() -migrations.prepareCache(for: .mysql) -services.register(migrations) -``` diff --git a/3.0/docs/postgresql/getting-started.md b/3.0/docs/postgresql/getting-started.md deleted file mode 100644 index ef549db9..00000000 --- a/3.0/docs/postgresql/getting-started.md +++ /dev/null @@ -1,108 +0,0 @@ -# PostgreSQL - -PostgreSQL ([vapor/postgresql](https://github.com/vapor/postgresql)) is a pure Swift PostgreSQL client built on top of [SwiftNIO](https://github.com/apple/swift-nio.git). - -The higher-level, Fluent ORM guide is located at [Fluent → Getting Started](../fluent/getting-started.md). Using just the PostgreSQL package directly for your project may be a good idea if any of the following are true: - -- You have an existing DB with non-standard structure. -- You rely heavily on custom or complex SQL queries. -- You just plain don't like ORMs. - -PostgreSQL core extends [DatabaseKit](../database-kit/getting-started.md) which provides some conveniences like connection pooling and integrations with Vapor's [Services](../getting-started/services.md) architecture. - -!!! tip - Even if you do choose to use [Fluent PostgreSQL](../fluent/getting-started.md), all of the features of PostgreSQL core will be available to you. - -## Getting Started - -Let's take a look at how you can get started using PostgreSQL core. - -### Package - -The first step to using PostgreSQL core is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // 🐘 Non-blocking, event-driven Swift client for PostgreSQL. - .package(url: "https://github.com/vapor/postgresql.git", from: "1.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["PostgreSQL", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -```sh -vapor xcode -``` - -### Config - -The next step is to configure the database in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import PostgreSQL - -/// Register providers first -try services.register(PostgreSQLProvider()) -``` - -Registering the provider will add all of the services required for PostgreSQL to work properly. It also includes a default database config struct that uses standard credentials. - -#### Customizing Config - -You can of course override the default configuration provided by `PostgreSQLProvider` if you'd like. - -To configure your database manually, register a [`DatabasesConfig`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabasesConfig.html) struct to your services. - -```swift -// Configure a PostgreSQL database -let postgresql = try PostgreSQLDatabase(config: PostgreSQLDatabaseConfig(...)) - -/// Register the configured PostgreSQL database to the database config. -var databases = DatabasesConfig() -databases.add(database: postgresql, as: .psql) -services.register(databases) -``` - -See [`PostgreSQLDatabase`](https://api.vapor.codes/postgresql/latest/PostgreSQL/Classes/PostgreSQLDatabase.html) and [`PostgreSQLDatabaseConfig`](https://api.vapor.codes/postgresql/latest/PostgreSQL/Structs/PostgreSQLDatabaseConfig.html) for more information. - -PostgreSQL's default database identifier is `.psql`. You can create a custom identifier if you want by extending [`DatabaseIdentifier`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseIdentifier.html). - -### Query - -Now that the database is configured, you can make your first query. - -```swift -struct PostgreSQLVersion: Codable { - let version: String -} - -router.get("sql") { req in - return req.withPooledConnection(to: .psql) { conn in - return conn.raw("SELECT version()") - .all(decoding: PostgreSQLVersion.self) - }.map { rows in - return rows[0].version - } -} -``` - -Visiting this route should display your PostgreSQL version. - -Here we are making use database connection pooling. You can learn more about creating connections in [DatabaseKit → Getting Started](../database-kit/getting-started.md). - -Learn more about building queries in [SQL → Getting Started](../sql/getting-started.md). - -Visit PostgreSQL's [API docs](https://api.vapor.codes/postgresql/latest/PostgreSQL/index.html) for detailed information about all available types and methods. diff --git a/3.0/docs/redis/getting-started.md b/3.0/docs/redis/getting-started.md deleted file mode 100644 index 479052d6..00000000 --- a/3.0/docs/redis/getting-started.md +++ /dev/null @@ -1,68 +0,0 @@ -# Getting Started with Redis - -Redis ([vapor/redis](https://github.com/vapor/redis)) is a pure-Swift, event-driven, non-blocking Redis client built on top of SwiftNIO. - -You can use this package to interactively send Redis commands to your server directly, or as a cache through Vapor's `KeyedCache` interface. - -Let's take a look at how you can get started using Redis. - -## Package - -The first step to using Redis is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // ⚡️Non-blocking, event-driven Redis client. - .package(url: "https://github.com/vapor/redis.git", from: "3.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["Redis", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -## Provider - -Once you have succesfully added the Auth package to your project, the next step is to configure it in your application. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import Redis - -// register Redis provider -try services.register(RedisProvider()) -``` - -That's it for basic setup. The next step is to create a Redis connection and send a command. - -## Command - -First, create a new connection to your Redis database. This package is built on top of DatabaseKit, so you can use any of its convenience methods for creating a new connection. See [DatabaseKit → Overview](../database-kit/overview.md) for more information. - -```swift -router.get("redis") { req -> Future in - return req.withNewConnection(to: .redis) { redis in - // use redis connection - } -} -``` - -Once you have a connection, you can use it to send a command. Let's send the `"INFO"` command which should return information about our Redis server. - -```swift -// send INFO command to redis -return redis.command("INFO") - // map the resulting RedisData to a String - .map { $0.string ?? "" } -``` - -Run your app and query `GET /redis`. You should see information about your Redis server printed as output. Congratulations! - diff --git a/3.0/docs/redis/overview.md b/3.0/docs/redis/overview.md deleted file mode 100644 index 8156405f..00000000 --- a/3.0/docs/redis/overview.md +++ /dev/null @@ -1,92 +0,0 @@ -# Using Redis - -Redis ([vapor/redis](https://github.com/vapor/redis)) is a pure-Swift, event-driven, non-blocking Redis client built on top of SwiftNIO. - -You can use this package to interact send Redis commands to your server directly, or as a cache through Vapor's `KeyedCache` interface. - -## Redis Commands - -Let's take a look at how to send and recieve data using Redis commands. - -### Connection - -The first thing you will need to send a Redis command is a connection. This package is built on top of DatabaseKit, so you can use any of its convenience methods for creating a new connection. - -For this example, we will use the `withNewConnection(to:)` method to create a new connection to Redis. - -```swift -router.get("redis") { req -> Future in - return req.withNewConnection(to: .redis) { redis in - // use redis connection - } -} -``` - -See [DatabaseKit → Overview](../database-kit/overview.md) for more information. - -### Available Commands - -See [`RedisClient`](https://api.vapor.codes/redis/latest/Redis/Classes/RedisClient.html) for a list of all available commands. Here we'll take a look at some common commands. - -#### Get / Set - -Redis's `GET` and `SET` commands allow you to store and later retrieve data from the server. You can pass any `Codable` type as the value to this command. - -```swift -router.get("set") { req -> Future in - // create a new redis connection - return req.withNewConnection(to: .redis) { redis in - // save a new key/value pair to the cache - return redis.set("hello", to: "world") - // convert void future to HTTPStatus.ok - .transform(to: .ok) - } -} - -router.get("get") { req -> Future in - // create a new redis connection - return req.withNewConnection(to: .redis) { redis in - // fetch the key/value pair from the cache, decoding a String - return redis.get("hello", as: String.self) - // handle nil case - .map { $0 ?? "" } - } -} -``` - -#### Delete - -Redis's `DELETE` command allows you to clear a previously stored key/value pair. - -```swift -router.get("del") { req -> Future in - // create a new redis connection - return req.withNewConnection(to: .redis) { redis in - // fetch the key/value pair from the cache, decoding a String - return redis.delete("hello") - // convert void future to HTTPStatus.ok - .transform(to: .ok) - } -} -``` - -See [`RedisClient`](https://api.vapor.codes/redis/latest/Redis/Classes/RedisClient.html) for a list of all available commands. - -## Keyed Cache - -You can also use Redis as the backend to Vapor's [`KeyedCache`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Protocols/KeyedCache.html) protocol. - -```swift -router.get("set") { req -> Future in - let string = try req.query.get(String.self, at: "string") - return try req.keyedCache(for: .redis).set("string", to: string) - .transform(to: .ok) -} - -router.get("get") { req -> Future in - return try req.keyedCache(for: .redis).get("string", as: String.self) - .unwrap(or: Abort(.badRequest, reason: "No string set yet.")) -} -``` - -See [DatabaseKit → Overview](../database-kit/overview.md#keyed-cache) for more information. \ No newline at end of file diff --git a/3.0/docs/routing/getting-started.md b/3.0/docs/routing/getting-started.md deleted file mode 100644 index c432b0f0..00000000 --- a/3.0/docs/routing/getting-started.md +++ /dev/null @@ -1,49 +0,0 @@ -# Routing - -Routing ([vapor/routing](https://github.com/vapor/routing)) is a small framework for routing things like HTTP requests. It lets you register and lookup routes in a router using nested, dynamic path components. - -For example, the routing package can help you route a request like the following and collect the values of the dynamic components. - -``` -/users/:user_id/comments/:comment_id -``` - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `Routing` APIs when you import `Vapor`. - -!!! tip - If you use Vapor, most of Routing's APIs will be wrapped by more convenient methods. See [Getting Started → Routing](../getting-started/routing.md) for more information. - -```swift -import Vapor -``` - -## Standalone - -The Routing package is lightweight, pure-Swift, and has very few dependencies. This means it can be used as a routing framework for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/routing.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Routing", ... ]) - ] -) -``` - -Use `import Routing` to access the APIs. - -!!! warning - Some of this guide may contain Vapor-specific APIs, however most of it should be applicable to the Routing package in general. - Visit the [API Docs](https://api.vapor.codes/routing/latest/Routing/index.html) for Routing-specific API info. - diff --git a/3.0/docs/routing/overview.md b/3.0/docs/routing/overview.md deleted file mode 100644 index a7bc5521..00000000 --- a/3.0/docs/routing/overview.md +++ /dev/null @@ -1,65 +0,0 @@ -# Routing Overview - -Routing ([vapor/routing](https://github.com/vapor/routing)) is a small framework for routing things like HTTP requests. It lets you register and lookup routes in a router using nested, dynamic path components. - -!!! tip - If you use Vapor, most of Routing's APIs will be wrapped by more convenient methods. See [Vapor → Routing] for more information. - -This guide will show you how to register a static route and a dynamic route and how to use [`Parameter`s](https://api.vapor.codes/routing/latest/Routing/Protocols/Parameter.html). - -## Register - -The first step to routing is to register some routes. Let's take a look at how to do that with a simple router—a `TrieRouter` which holds numbers. Usually you would store something like HTTP responders, but we'll keep things simple for this example. - -```swift -// Create a router that stores Doubles -let router = TrieRouter(Double.self) - -// Register some routes and values to the router -router.register(route: Route(path: ["funny", "meaning_of_universe"], output: 42)) -router.register(route: Route(path: ["funny", "leet"], output: 1337)) -router.register(route: Route(path: ["math", "pi"], output: 3.14)) - -// Create empty Parameters to hold dynamic params (none yet) -var params = Parameters() - -// Test fetching some routes -print(router.route(path: ["fun", "meaning_of_universe"], parameters: ¶ms)) // 42 -print(router.route(path: ["foo"], parameters: ¶ms)) // nil -``` - -Here we are using [`register(...)`](https://api.vapor.codes/routing/latest/Routing/Classes/TrieRouter.html#/s:7Routing10TrieRouterC8registeryAA5RouteCyxG5route_tF) to register routes to our router, then later [`route(...)`](https://api.vapor.codes/routing/latest/Routing/Classes/TrieRouter.html#/s:7Routing10TrieRouterC5routexSgSayqd__G4path_AA10ParametersVz10parameterstAA17RoutableComponentRd__lF) to fetch them. The [`TrieRouter`](https://api.vapor.codes/routing/latest/Routing/Classes/TrieRouter.html) uses a trie (digital tree) internally to make finding value in the router fast. - -## Parameter - -Let's take a look at registering some dynamic path components. These are parts of the path that are variable and whose value should be collected for later use. You will often see this used for situations like show a webpage for a user: - -``` -/users/:user_id -``` - -Here is how you would implement that with `TrieRouter`. For this example, we will ignore the route output. - -```swift -// Create a route for /users/:user_id -let user = Route(path: [.constant("users"), .parameter("user_id")], output: ...) - -// Create a router and register our route -let router = TrieRouter(...) -router.register(route: user) - -// Create empty Parameters to hold dynamic values -var params = Parameters() - -// Route the path /users/42 -_ = router.route(path: ["users", "42"], parameters: ¶ms) - -// The params contains our dynamic value! -print(params) // ["user_id": "42"] -``` - -Note that the String used for `.parameter(...)` will be the key to fetch the value from `Parameters`. - -## API Docs - -Check out the [API docs](https://api.vapor.codes/routing/latest/Routing/index.html) for more in-depth information about all of the available parameters and methods. diff --git a/3.0/docs/service/getting-started.md b/3.0/docs/service/getting-started.md deleted file mode 100644 index 1ccaf8aa..00000000 --- a/3.0/docs/service/getting-started.md +++ /dev/null @@ -1,51 +0,0 @@ -# Getting Started with Service - -Service ([vapor/service](https://github.com/vapor/service)) is a dependency injection (inversion of control) framework. It allows you to register, configure, and create your application's dependencies in a maintainable way. - -```swift -/// register a service during boot -services.register(PrintLogger.self, as: Logger.self) - -/// you can then create that service later -let logger = try someContainer.make(Logger.self) -print(logger is PrintLogger) // true -``` - -You can read more about [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) on Wikipedia. Also be sure to check out the [Getting Started → Services](../getting-started/services.md) guide. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `Service` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The Service package is lightweight, pure-Swift, and has very few dependencies. This means it can be used as a dependency injection framework for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/service.git", from: "1.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Service", ... ]) - ] -) -``` - -Use `import Service` to access the APIs. - -!!! warning - Some of this guide may contain Vapor-specific APIs, however most of it should be applicable to the Services package in general. - Visit the [API Docs](https://api.vapor.codes/service/latest/Service/index.html) for Service-specific API info. - diff --git a/3.0/docs/service/provider.md b/3.0/docs/service/provider.md deleted file mode 100644 index 6add8ea4..00000000 --- a/3.0/docs/service/provider.md +++ /dev/null @@ -1,51 +0,0 @@ -# Using Providers - -The [`Provider`](https://api.vapor.codes/service/latest/Service/Protocols/Provider.html) protocol make it easy to integrate external services into your application. All of Vapor's official packages, like [Fluent](../fluent/getting-started.md), use the provider system to expose their services. - -Providers can: - -- Register services to your [`Services`](https://api.vapor.codes/service/latest/Service/Structs/Services.html) struct. -- Hook into your [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html)'s lifecycle. - -## Register - -Once you have added a Service-exposing [SPM dependency](../getting-started/spm.md#dependencies) to your project, adding the provider is easy. - -```swift -import Foo - -try services.register(FooProvider()) -``` - -This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -!!! note - You can search GitHub for the [`vapor-service`](https://github.com/topics/vapor-service) tag for a list of packages that expose services using this method. - - -## Create - -Creating a custom provider can be a great way to organize your code. You will also want to create a provider if you are working on a third-party package for Vapor. - -Here is what a simple provider would look like for the `Logger` examples from the [Services](services.md) section. - -```swift -public final class LoggerProvider: Provider { - /// See `Provider`. - public func register(_ services: inout Services) throws { - services.register(PrintLogger.self) - services.register(FileLogger.self) - } - - /// See `Provider`. - public func didBoot(_ container: Container) throws -> Future { - let logger = try container.make(Logger.self) - logger.log("Hello from LoggerProvider!") - return .done(on: container) - } -} -``` - -Now when someone registers the `LoggerProvider` to their `Services` struct, it will automatically register the print and file loggers. When the container boots, the success message will be printed to verify the provider was added. - -See the [`Provider`](https://api.vapor.codes/service/latest/Service/Protocols/Provider.html) protocol's API docs for more information. diff --git a/3.0/docs/service/services.md b/3.0/docs/service/services.md deleted file mode 100644 index 7456d7bf..00000000 --- a/3.0/docs/service/services.md +++ /dev/null @@ -1,104 +0,0 @@ -# Using Services - -This guide will show you how to register, configure, and create your own service. In this example we will be assuming two different `Logger` implementations. - -- `PrintLogger`: Prints logs. -- `FileLogger`: Saves logs to a file. Already conforms to `ServiceType`. - -## Register - -Let's take a look at how we can register our `PrintLogger`. First you must conform your type to `Service`. The easiest way to do this is simply adding the conformance in an extension. - -```swift -extension PrintLogger: Service { } -``` - -It's an empty protocol so there should be no missing requirements. - -### Factory - -Now the service can be registered to the [`Services`](https://api.vapor.codes/service/latest/Service/Structs/Services.html) struct. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -services.register(Logger.self) { container in - return PrintLogger() -} -``` - -By registering the `PrintLogger` using a factory (closure) method, we allow the [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html) to dynamically create the service once it is needed. Any [`SubContainer`](https://api.vapor.codes/service/latest/Service/Protocols/SubContainer.html)s created later can call this method again to create their own `PrintLogger`s. - - -### Service Type - -To make registering a service easier, you can conform it to [`ServiceType`](https://api.vapor.codes/service/latest/Service/Protocols/ServiceType.html). - -```swift -extension PrintLogger: ServiceType { - /// See `ServiceType`. - static var serviceSupports: [Any.Type] { - return [Logger.self] - } - - /// See `ServiceType`. - static func makeService(for worker: Container) throws -> PrintLogger { - return PrintLogger() - } -} -``` - -Services conforming to [`ServiceType`](https://api.vapor.codes/service/latest/Service/Protocols/ServiceType.html) can be registered using just the type name. This will automatically conform to `Service` as well. - -```swift -services.register(PrintLogger.self) -``` -### Instance - -You can also register pre-initialized instances to `Services`. - -```swift -services.register(PrintLogger(), as: Logger.self) -``` - -!!! warning - If using reference types (`class`) this method will share the _same_ object between all [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html)s and [`SubContainer`](https://api.vapor.codes/service/latest/Service/Protocols/SubContainer.html)s. - Be careful to protect against race conditions. - -## Configure - -If more than one service is registered for a given interface, we will need to choose which service is used. - -```swift -services.register(PrintLogger.self) -services.register(FileLogger.self) -``` - -Assuming the above services are registered, we can use service [`Config`](https://api.vapor.codes/service/latest/Service/Structs/Config.html) to pick which one we want. - -```swift -switch env { -case .production: config.prefer(FileLogger.self, for: Logger.self) -default: config.prefer(PrintLogger.self, for: Logger.self) -} -``` - -Here we are using the [`Environment`](https://api.vapor.codes/service/latest/Service/Structs/Environment.html) to dynamically prefer a service. This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -!!! note - You can also dynamically _register_ services based on environment instead of using service config. - However, service config is required for choosing services that come from the framework or a provider. - -## Create - -After you have registered your services, you can use a [`Container`](https://api.vapor.codes/service/latest/Service/Protocols/Container.html) to create them. - -```swift -let logger = try someContainer.make(Logger.self) -logger.log("Hello, world!") - -// PrintLogger or FileLogger depending on the container's environment -print(type(of: logger)) -``` - -!!! tip - Usually the framework will create any required containers for you. You can use [`BasicContainer`](https://api.vapor.codes/service/latest/Service/Classes/BasicContainer.html) if you want to create one for testing. - diff --git a/3.0/docs/sql/getting-started.md b/3.0/docs/sql/getting-started.md deleted file mode 100644 index e0cb8889..00000000 --- a/3.0/docs/sql/getting-started.md +++ /dev/null @@ -1,18 +0,0 @@ -# Getting Started with SQL - -SQL ([vapor/sql](https://github.com/vapor/sql)) is a library for building and serializing SQL queries in Swift. It has an extensible, protocol-based design and supports DQL, DML, and DDL. - -!!! tip - If you use Fluent, you will usually not need to build SQL queries manually. - -## Choosing a Driver - -Vapor's SQL database packages are built on top of this library. - -|database|repo|version|dbid|notes| -|-|-|-|-|-| -|[PostgreSQL](../postgresql/getting-started.md)|[postgresql](https://github.com/vapor/postgresql.git)|1.0.0|`psql`|**Recommended**. Open source, standards compliant SQL database. Available on most cloud hosting providers.| -|[MySQL](../mysql/getting-started.md)|[mysql](https://github.com/vapor/mysql)|3.0.0|`mysql`|Popular open source SQL database. Available on most cloud hosting providers. This driver also supports MariaDB.| -|[SQLite](../sqlite/getting-started.md)|[sqlite](https://github.com/vapor/sqlite)|3.0.0|`sqlite`|Open source, embedded SQL database. Its simplistic nature makes it a great candiate for prototyping and testing.| - -Once you have selected a driver and added it to your `Package.swift` file, you can continue following this guide. diff --git a/3.0/docs/sql/overview.md b/3.0/docs/sql/overview.md deleted file mode 100644 index 34f92683..00000000 --- a/3.0/docs/sql/overview.md +++ /dev/null @@ -1,69 +0,0 @@ -# Using SQL - -The SQL library helps you build and serialize SQL queries in Swift. It has an extensible, protocol-based design that supports many standard SQL queries like: - -- `SELECT`, `INSERT`, `UPDATE`, `DELETE` -- `CREATE TABLE`, `ALTER TABLE`, `DROP TABLE` -- `CREATE INDEX`, `DROP INDEX` - -This package also integrates deeply with `Codable` and parameter binding to make working with your database fast and secure. - -This guide assumes you have already chosen and configured a driver in [SQL → Getting Started](getting-started.md). In some cases, these SQL dialects will have different syntaxes or supported features. Be sure to check their API docs for additional functionality. - -## Connection - -The first step to building a SQL query is getting access to a connection. Most often, you will use `withPooledConnection(to:)` followed by your database's `dbid`. - -!!! note - Refer to the table in [SQL → Getting Started](getting-started.md) for your database's default `dbid`. - The `dbid` allows you to use multiple databases per application. - -```swift -router.get("sql") { req in - return req.withPooledConnection(to: .<#dbid#>) { conn in - return // use conn to perform a query - } -} -``` - -Check out [Database Kit → Overview → Connections](../database-kit/overview.md#connections) for more information. The rest of this guide will assume you have access to a SQL database connection. - -## Select - -Use the [`select()`](https://api.vapor.codes/sql/latest/SQL/Protocols/SQLConnection.html#/s:3SQL13SQLConnectionPAAE6selectAA16SQLSelectBuilderCyxGyF) method on a connection to create a [`SQLSelectBuilder`](https://api.vapor.codes/sql/latest/SQL/Classes/SQLSelectBuilder.html). This builder helps you create `SELECT` statements and supports: - -- `*`, columns, and expressions like functions -- `FROM` -- `JOIN` -- `GROUP BY` -- `ORDER BY` - -The select builder conforms to [`SQLPredicateBuilder`](https://api.vapor.codes/sql/latest/SQL/Protocols/SQLPredicateBuilder.html) for building `WHERE` predicates. It also conforms to [`SQLQueryFetcher`](https://api.vapor.codes/sql/latest/SQL/Protocols/SQLQueryFetcher.html) for decoding `Codable` models from the result set. - -Let's take a look at an example `SELECT` query. Replace the Xcode placeholder with the name of the database you are using, i.e., `SQLite`. - -```swift -struct User: SQLTable, Codable { - static let sqlTableIdentifierString = "users" - let id: Int? - let name: String -} - -let users = conn.select() - .all().from(User.self) - .where(\User.name == "Vapor") - .all(decoding: User.self) -print(users) // Future<[User]> -``` - -The resulting SQL will look something like this: - -```sql -SELECT * FROM "users" WHERE "users"."name" = ? -``` - -As you can see, the Swift code reads similarly to actual SQL. Be sure to visit the API docs for the various builder protocols to see all available methods. - -## API Docs - -Check out the [API docs](https://api.vapor.codes/sql/latest/SQL/index.html) for more in-depth information about SQL's APIs. diff --git a/3.0/docs/sqlite/getting-started.md b/3.0/docs/sqlite/getting-started.md deleted file mode 100644 index 36ff7fa0..00000000 --- a/3.0/docs/sqlite/getting-started.md +++ /dev/null @@ -1,109 +0,0 @@ -# SQLite - -SQLite ([vapor/sqlite](https://github.com/vapor/sqlite)) is a wrapper around the `libsqlite` C-library. - -The higher-level, Fluent ORM guide is located at [Fluent → Getting Started](../fluent/getting-started.md). Using just the SQLite package directly for your project may be a good idea if any of the following are true: - -- You have an existing DB with non-standard structure. -- You rely heavily on custom or complex SQL queries. -- You just plain don't like ORMs. - -SQLite core is built on top of [DatabaseKit](../database-kit/getting-started.md) which provides some conveniences like connection pooling and integrations with Vapor's [Services](../getting-started/services.md) architecture. - -!!! tip - Even if you do choose to use [Fluent SQLite](../fluent/getting-started.md), all of the features of SQLite core will be available to you. - -## Getting Started - -Let's take a look at how you can get started using SQLite core. - -### Package - -The first step to using SQLite core is adding it as a dependency to your project in your SPM package manifest file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "MyApp", - dependencies: [ - /// Any other dependencies ... - - // 🔵 SQLite 3 wrapper for Swift. - .package(url: "https://github.com/vapor/sqlite.git", from: "3.0.0"), - ], - targets: [ - .target(name: "App", dependencies: ["SQLite", ...]), - .target(name: "Run", dependencies: ["App"]), - .testTarget(name: "AppTests", dependencies: ["App"]), - ] -) -``` - -Don't forget to add the module as a dependency in the `targets` array. Once you have added the dependency, regenerate your Xcode project with the following command: - -```sh -vapor xcode -``` - -### Config - -The next step is to configure the database in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -import SQLite - -/// Register providers first -try services.register(SQLiteProvider()) -``` - -Registering the provider will add all of the services required for SQLite to work properly. It also includes a default database config struct that uses an in-memory DB. - -#### Customizing Config - -You can of course override the default configuration provided by `SQLiteProvider` if you'd like. - -SQLite supports in-memory and file-based persistance. To configure your database manually, register a [`DatabasesConfig`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabasesConfig.html) struct to your services. - -```swift -// Configure a SQLite database -let sqlite = try SQLiteDatabase(storage: .file(path: "db.sqlite")) - -/// Register the configured SQLite database to the database config. -var databases = DatabasesConfig() -databases.add(database: sqlite, as: .sqlite) -services.register(databases) -``` - -See [`SQLiteDatabase`](https://api.vapor.codes/sqlite/latest/SQLite/Classes/SQLiteDatabase.html) and [`SQLiteStorage`](https://api.vapor.codes/sqlite/latest/SQLite/Enums/SQLiteStorage.html) for more information. - -SQLite's default database identifier is `.sqlite`. You can create a custom identifier if you want by extending [`DatabaseIdentifier`](https://api.vapor.codes/database-kit/latest/DatabaseKit/Structs/DatabaseIdentifier.html). - -### Query - -Now that the database is configured, you can make your first query. - -```swift -struct SQLiteVersion: Codable { - let version: String -} - -router.get("sql") { req in - return req.withPooledConnection(to: .sqlite) { conn in - return conn.select() - .column(function: "sqlite_version", as: "version") - .all(decoding: SQLiteVersion.self) - }.map { rows in - return rows[0].version - } -} -``` - -Visiting this route should display your SQLite version. - -Here we are making use database connection pooling. You can learn more about creating connections in [DatabaseKit → Getting Started](../database-kit/getting-started.md). - -Once we have a connection, we can use [`select()`](https://api.vapor.codes/sql/latest/SQL/Protocols/SQLConnection.html#/s:3SQL13SQLConnectionPAAE6selectAA16SQLSelectBuilderCyxGyF) to create a `SELECT` query builder. Learn more about building queries in [SQL → Getting Started](../sql/getting-started.md). - -Visit SQLite's [API docs](https://api.vapor.codes/sqlite/latest/SQLite/index.html) for detailed information about all available types and methods. diff --git a/3.0/docs/template-kit/getting-started.md b/3.0/docs/template-kit/getting-started.md deleted file mode 100644 index c56399f3..00000000 --- a/3.0/docs/template-kit/getting-started.md +++ /dev/null @@ -1,121 +0,0 @@ -# Getting Started with Template Kit - -Template Kit ([vapor/template-kit](https://github.com/vapor/template-kit)) is a framework for implementing templating languages in Swift. It is currently used to power Leaf ([vapor/leaf](https://github.com/vapor/leaf)) and hopefully more languages in the future. - -Template Kit is designed to make implementing a templating language easy by defining a common template structure and handling the entire serialization step. - -!!! warning - These docs are for developers interested in implementing a templating language using Template Kit. See [Leaf → Getting Started](../leaf/getting-started.md) for information about using Leaf. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `TemplateKit` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The Template Kit package is lightweight, pure-Swift, and has very few dependencies. This means it can be used as a templating framework for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/template-kit.git", from: "1.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["TemplateKit", ... ]) - ] -) -``` - -Use `import TemplateKit` to access the APIs. - - -## Overview - -Let's take a look at how Leaf uses Template Kit to render views. - -Assume we have a template `greeting.leaf` with the following contents: - -```leaf -Hello, #capitalize(name)! -``` - -This first step in rendering this view is to parse the syntax into an abstract syntax tree (AST). This is the part of view rendering that Leaf is responsible for, since Leaf has a unique syntax. - -Leaf does this by creating a `LeafParser` that conforms to [`TemplateParser`](https://api.vapor.codes/template-kit/latest/TemplateKit/Protocols/TemplateParser.html). - - -``` -greeting.leaf -> LeafParser -> AST -``` - -In code, this looks like: - -```swift -func parse(scanner: TemplateByteScanner) throws -> [TemplateSyntax] -``` - -The AST for our example `greeting.leaf` file would look something like this: - -```swift -[ - .raw(data: "Hello. "), - .tag( - name: "capitalize", - parameters: [.identifier("name")] - ), - .raw(data: "!"), -] -``` - -Now that Leaf has created an AST, it's job is done! Template Kit will handle converting this AST into a rendered view. All it needs is a `TemplateData` to use for filling in any variables. - -```swift -let data = TemplateData.dictionary(["name": "vapor"]) -``` - -The above data will be combined with the AST and used by the [`TemplateSerializer`](https://api.vapor.codes/template-kit/latest/TemplateKit/Classes/TemplateSerializer.html) to create a rendered view. - -``` -AST + Data -> TemplateSerializer -> View -``` - -Our rendered view will look something like: - -```html -Hello, Vapor! -``` - -All of these steps are handled by `LeafRenderer` which conforms to [`TemplateRenderer`](https://api.vapor.codes/template-kit/latest/TemplateKit/Protocols/TemplateRenderer.html). A template renderer is simply an object that contains both a parser and a serializer. When you implement one, you will get several helpful extensions from Template Kit for free that help load files and cache parsed ASTs. It's what the end user will use to render views. - -The entire pipeline looks like this: - -``` - LeafRenderer - | -|----------------------------------------------------------------| - greeting.leaf -> LeafParser -> AST -> TemplateSerializer -> View - ^ - / - TemplateData -``` - -In code, the [method](https://api.vapor.codes/template-kit/latest/TemplateKit/Protocols/TemplateRenderer.html#/s:11TemplateKit0A8RendererPAAE6renderXeXeF) looks like this: - -```swift -public func render(_ path: String, _ context: TemplateData) -> Future -``` - -Check out Template Kit's [API docs](https://api.vapor.codes/template-kit/latest/TemplateKit/index.html) for detailed information about all of the protocols, structs, and classes Template Kit offers. - - diff --git a/3.0/docs/testing/getting-started.md b/3.0/docs/testing/getting-started.md deleted file mode 100644 index 813e1984..00000000 --- a/3.0/docs/testing/getting-started.md +++ /dev/null @@ -1,3 +0,0 @@ -# Getting Started with Testing - -Coming soon. diff --git a/3.0/docs/url-encoded-form/getting-started.md b/3.0/docs/url-encoded-form/getting-started.md deleted file mode 100644 index 9c98833b..00000000 --- a/3.0/docs/url-encoded-form/getting-started.md +++ /dev/null @@ -1,42 +0,0 @@ -# Getting Started with URL-Encoded Form - -URL-Encoded Form ([vapor/url-encoded-form](https://github.com/vapor/url-encoded-form)) is a small package that helps you parse and serialize `application/x-www-form-urlencoded` data. URL-encoded forms are a widely-supported encoding on the web. It's most often used for serializing web forms sent via POST requests. - -The URL-Encoded Form package makes it easy to use this encoding by integrating directly with `Codable`. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `URLEncodedForm` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The URL-Encoded Form package is lightweight, pure-Swift, and has very few dependencies. This means it can be used to work with `form-urlencoded` data for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/url-encoded-form.git", from: "1.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["URLEncodedForm", ... ]) - ] -) -``` - -Use `import URLEncodedForm` to access the APIs. - -!!! warning - Some of this guide may contain Vapor-specific APIs, however most of it should be applicable to the URL-Encoded Form package in general. - Visit the [API Docs](https://api.vapor.codes/url-encoded-form/latest/URLEncodedForm/index.html) for specific API info. - diff --git a/3.0/docs/url-encoded-form/overview.md b/3.0/docs/url-encoded-form/overview.md deleted file mode 100644 index e2ad8bfc..00000000 --- a/3.0/docs/url-encoded-form/overview.md +++ /dev/null @@ -1,126 +0,0 @@ -# Using URL-Encoded Form - -URL-Encoded Form is a widely-supported encoding on the web. It's most often used for serializing web forms sent via POST requests. This encoding is also used to send structured data in URL query strings. - -It is a relatively efficient encoding for sending small amounts of data. However, all data must be percent-encoded making this encoding suboptimal for large amounts of data. See the [Multipart](../multipart/getting-started.md) encoding if you need to upload things like files. - -!!! tip - URL-Encoded Form integrates with [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html) like all other encoding methods in Vapor. See [Vapor → Content](../vapor/content.md) for more information about the [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html) protocol. - -Let's take a look at how to decode a `application/x-www-form-urlencoded` request. - -## Decode Body - -Most often, you will be decoding `form-urlencoded`-encoded requests from a web form. Let's take a look at what one of these requests might look like. After that, we will take a look at what the HTML form for that request would look like. - -### Request - -Here is an example `form-urlencoded`-encoded request for creating a new user. - -```http -POST /users HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -name=Vapor&age=3&luckyNumbers[]=5&luckyNumbers[]=7 -``` - -You can see the `[]` notation is used to encode arrays. Your web form will need to use this notation as well. - -### Form - -There are many ways to create a `form-urlencoded`-encoded request, but the most common is an HTML web form. Here is what the HTML form for this request might have looked like. - -```html - - - - - - -``` - -Since we are not specifying a special `enctype` attribute on the `
    `, the browser will URL-encode the form by default. We are also providing two fields with the same name, `luckyNumbers[]`. This will let us send an array of values. - -### Content - -Now let's take a look at how we would handle this request in Vapor. The first step (as always with [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html)) is to create a `Codable` struct that represents the data structure. - -```swift -import Vapor - -struct User: Content { - var name: String - var age: Int - var luckyNumbers: [Int] -} -``` - -Now that we have our `User` struct, let's decode that request! We can use the [`ContentContainer`](https://api.vapor.codes/vapor/latest/Vapor/Structs/ContentContainer.html) to do this easily. - -```swift -router.post("users") { req -> Future in - return try req.content.decode(User.self).map(to: HTTPStatus.self) { user in - print(user.name) // "Vapor" - print(user.age) // 3 - print(user.luckyNumbers) // [5, 7] - return .ok - } -} -``` - -Now when you post the form to `/users`, you should see the information printed in the console. Nice work! - -## Encode Body - -APIs encode `form-urlencoded` data much less often than they decode it. However, encoding is just as easy with Vapor. Using our same `User` struct from the previous example, here is how we can encode a `form-urlencoded`-encoded response. - -```swift -router.get("multipart") { req -> User in - let res = req.makeResponse() - let user = User(name: "Vapor", age: 3, luckyNumbers: [5, 7]) - res.content.encode(user, as: .urlEncodedForm) - return user -} -``` - -!!! tip - If you set a default `MediaType` on your `Content` types, then you can return them directly in the route closure. - -## URL Query - -URL-Encoded Forms are also useful for sending structured data in the URL query string. This is widely used for sending data via GET requests where HTTP bodies are not allowed. - -Let's take a look at how we can decode some search parameters from the query string. - -```http -GET /users?name=Vapor&age=3 HTTP/1.1 -``` -The first step (as always with [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html)) is to create a `Codable` struct that represents the data structure. - -```swift -import Vapor - -struct UsersFilters: Content { - var name: String? - var age: Int? -} -``` - -Here we are making both `name` and `age` optional since the route can be called without any flags to return all users. - -Now that we have a `Codable` struct, we can decode the URL query string. The process is almost identical to decoding content, expect we use `req.query` instead of `req.content`. - -```swift -router.get("users") { req -> Future<[User]> in - let filters = try req.query.decode(UsersFilters.self) - print(filters.name) // Vapor - print(filters.age) // 3 - return // fetch users with filters -} -``` - -!!! tip - Decoding the URL query string is not asynchronous because, unlike HTTP bodies, Vapor can be sure it is available when calling the route closure. - - - diff --git a/3.0/docs/validation/getting-started.md b/3.0/docs/validation/getting-started.md deleted file mode 100644 index 8eae19d4..00000000 --- a/3.0/docs/validation/getting-started.md +++ /dev/null @@ -1,42 +0,0 @@ -# Getting Started with Validation - -Validation ([vapor/validation](https://github.com/vapor/validation)) is a framework for validating data sent to your application. It can help validate things like names, emails and more. It is also extensible, allowing you to easily create custom validators. - -The rest of this guide will show you how to add and import the `Validation` module. For more information on using this package, check out [Validation → Overview](overview.md). - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `Validation` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The Service package is lightweight, pure-Swift, and has very few dependencies. This means it can be used as a validation framework for any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/validation.git", from: "2.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Validation", ... ]) - ] -) -``` - -Use `import Validation` to access the APIs. - -!!! warning - Some of this guide may contain Vapor-specific APIs, however most of it should be applicable to the Validation package in general. - Visit the [API Docs](https://api.vapor.codes/validation/latest/Validation/index.html) for Validation-specific API info. - diff --git a/3.0/docs/validation/overview.md b/3.0/docs/validation/overview.md deleted file mode 100644 index f9674264..00000000 --- a/3.0/docs/validation/overview.md +++ /dev/null @@ -1,132 +0,0 @@ -# Validation Overview - -Validation is a framework for validating data sent to your application. It can help validate things like names, emails and more. It is also extensible, allowing you to easily create custom validators. - -## Swift & Codable - -Swift's strong type system and `Codable` take care of most of the basic validation that web apps need to do. - -```swift -struct User: Codable { - var id: UUID? - var name: String - var age: Int - var email: String? - var profilePictureURL: String? -} -``` - -For example, when you decode the above `User` model, Swift will automatically ensure the following: - - - `id` is a valid `UUID` or is `nil`. - - `name` is a valid `String` and is _not_ `nil`. - - `age` is a valid `Int` and is _not_ `nil`. - - `email` is a valid `String` or is `nil`. - - `profilePictureURL` is a valid `String` or is `nil`. - -This is a great first step, but there is still room for improvement here. Here are some examples of things Swift and `Codable` would not mind, but are not ideal: - - - `name` is empty string `""` - - `name` contains non-alphanumeric characters - - `age` is a negative number `-42` - - `email` is not correctly formatted `test@@vapor.codes` - - `profilePictureURL` is not a `URL` without a scheme - -Luckily the Validation package can help. - -## Validatable - -Let's take a look at how the Validation package can help you validate incoming data. We'll start by conforming our `User` model from the previous section to the [`Validatable`](https://api.vapor.codes/validation/latest/Validation/Protocols/Validatable.html) protocol. - -!!! note - This assumes `User` already conforms to `Reflectable` (added by default when using one of Fluent's `Model` protocols). If not, you will need to add conformance to `Reflectable` manually. - -```swift -extension User: Validatable { - /// See `Validatable`. - static func validations() -> Validations { - // define validations - } -} - -let user = User(...) -// since User conforms to Validatable, we get a new method validate() -// that throws an error if any validations fail -try user.validate() -``` - -This is the basic structure of [`Validatable`](https://api.vapor.codes/validation/latest/Validation/Protocols/Validatable.html) conformance. Let's take a look at how we can implement the static `validations()` method. - -## Validations - -First let's start by verifying that the name is at least 3 characters long. - -```swift -extension User: Validatable { - /// See `Validatable`. - static func validations() throws -> Validations { - var validations = Validations(User.self) - try validations.add(\.name, .count(3...)) - return validations - } -} -``` - -The `count(...)` validation accepts Swift [`Range`](https://developer.apple.com/documentation/swift/range) notation and will validate that a collection's count is within that range. By only placing a value on the left side of `...`, we only set a minimum range. - -Take a look at all of the available validators [here](https://api.vapor.codes/validation/latest/Validation/Structs/Validator.html). - -### Operators - -Validating that the name is three or more characters is great, but we also want to make sure that the name is alphanumeric characters only. We can do this by combining multiple validators using `&&`. - -```swift -try validations.add(\.name, .count(3...) && .alphanumeric) -``` - -Now our name will only be considered valid if it is three or more characters _and_ alphanumeric. Take a look at all of the available operators [here](https://api.vapor.codes/validation/latest/Validation/Functions.html). - -### Nil - -You may want to run validations on optionals only if a value is present. The `&&` and `||` operators have special overloads that help you do this. - -```swift -try validations.add(\.email, .email || .nil) -``` - -The [`nil`](https://api.vapor.codes/validation/latest/Validation/Structs/Validator.html#/s:10Validation9ValidatorV3nilXevZ) validator checks if a `T?` optional value is `nil`. - -The [`email`](https://api.vapor.codes/validation/latest/Validation/Structs/Validator.html#/s:10Validation9ValidatorVAASSRszlE5emailACySSGvZ) validator checks if a `String` is a valid email address. However, the property on our `User` is a `String?`. This means the email validator cannot be used directly with the property. - -We can combine these two operators using `||` to express the validation we want: validate the email is correctly formatted if it is not nil. - -## Validate - -Let's finish up the rest of our validations using our new knowledge. - -```swift -extension User: Validatable { - /// See `Validatable`. - static func validations() throws -> Validations { - var validations = Validations(User.self) - try validations.add(\.name, .alphanumeric && .count(3...)) - try validations.add(\.age, .range(18...)) - try validations.add(\.email, .email || .nil) - try validations.add(\.profilePictureURL, .url || .nil) - return validations - } -} -``` - -Now let's try out validating our model. - -```swift -router.post(User.self, at: "users") { req, user -> User in - try user.validate() - return user -} -``` - -When you query that route, you should see that errors are thrown if the data does not meet your validations. If the data is correct, your user model is returned successfully. - -Congratulations on setting up your first `Validatable` model! Check out the [API docs](https://api.vapor.codes/validation/latest/Validation/index.html) for more information and code samples. diff --git a/3.0/docs/vapor/client.md b/3.0/docs/vapor/client.md deleted file mode 100644 index b7c97d2b..00000000 --- a/3.0/docs/vapor/client.md +++ /dev/null @@ -1,45 +0,0 @@ -# Using Client - -[`Client`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html) is a convenience wrapper around the lower level [HTTP → Client](../http/client.md). It automatically parses things like hostname and port from URIs and helps you encode and decode [Content](content.md). - -```swift -let res = try req.client().get("http://vapor.codes") -print(res) // Future -``` - -## Container - -The first thing you will need is a service [Container](../getting-started/services.md#container) to create your client. - -If you are making this external API request as the result of an incoming request to your server, you should use the `Request` container to create a client. This is most often the case. - -If you need a client during boot, use the `Application` or if you are in a `Command` use the command context's container. - -Once you have a `Container`, use the [`client()`](https://api.vapor.codes/vapor/latest/Vapor/Extensions/Container.html#/s:5Vapor6clientXeXeF) method to create a `Client`. - -```swift -// Creates a generic Client -let client = try container.client() -``` - -## Send - -Once you have a `Client`, you can use the [`send(...)`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html#/s:5Vapor6ClientP4sendXeXeF) method to send a `Request`. Note that the request URI must include a scheme and hostname. - -```swift -let req: Request ... -let res = try client.send(req) -print(res) // Future -``` - -You can also use the convenience methods like [`get(...)`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html#/s:5Vapor6ClientPAAE3getXeXeF), [`post(...)`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html#/s:5Vapor6ClientPAAE4postXeXeF), etc. - -```swift -let user: User ... -let res = try client.post("http://api.vapor.codes/users") { post in - try post.content.encode(user) -} -print(res) // Future -``` - -See [Content](./content.md) for more information on encoding and decoding content to messages. diff --git a/3.0/docs/vapor/content.md b/3.0/docs/vapor/content.md deleted file mode 100644 index 4a59aa6c..00000000 --- a/3.0/docs/vapor/content.md +++ /dev/null @@ -1,383 +0,0 @@ -# Using Content - -In Vapor 3, all content types (JSON, protobuf, [URLEncodedForm](../url-encoded-form/getting-started.md), [Multipart](../multipart/getting-started.md), etc) are treated the same. All you need to parse and serialize content is a `Codable` class or struct. - -For this introduction, we will use mostly JSON as an example. But keep in mind the API is the same for any supported content type. - -## Server - -This first section will go over decoding and encoding messages sent between your server and connected clients. See the [client](#client) section for encoding and decoding content in messages sent to external APIs. - -### Request - -Let's take a look at how you would parse the following HTTP request sent to your server. - -```http -POST /login HTTP/1.1 -Content-Type: application/json - -{ - "email": "user@vapor.codes", - "password": "don't look!" -} -``` - -First, create a struct or class that represents the data you expect. - -```swift -import Vapor - -struct LoginRequest: Content { - var email: String - var password: String -} -``` - -Notice the key names exactly match the keys in the request data. The expected data types also match. Next conform this struct or class to `Content`. - -#### Decode - -Now we are ready to decode that HTTP request. Every [`Request`](https://api.vapor.codes/vapor/latest/Vapor/Classes/Request.html) has a [`ContentContainer`](https://api.vapor.codes/vapor/latest/Vapor/Structs/ContentContainer.html) that we can use to decode content from the message's body. - -```swift -router.post("login") { req -> Future in - return req.content.decode(LoginRequest.self).map { loginRequest in - print(loginRequest.email) // user@vapor.codes - print(loginRequest.password) // don't look! - return HTTPStatus.ok - } -} -``` - -We use `.map(to:)` here since `decode(...)` returns a [future](../async/getting-started.md). - -!!! note - Decoding content from requests is asynchronous because HTTP allows bodies to be split into multiple parts using chunked transfer encoding. - -#### Router - -To help make decoding content from incoming requests easier, Vapor offers a few extensions on [`Router`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Router.html) to do this automatically. - -```swift -router.post(LoginRequest.self, at: "login") { req, loginRequest in - print(loginRequest.email) // user@vapor.codes - print(loginRequest.password) // don't look! - return HTTPStatus.ok -} -``` - -#### Detect Type - -Since the HTTP request in this example declared JSON as its content type, Vapor knows to use a JSON decoder automatically. This same method would work just as well for the following request. - -```http -POST /login HTTP/1.1 -Content-Type: application/x-www-form-urlencoded - -email=user@vapor.codes&don't+look! -``` - -All HTTP requests must include a content type to be valid. Because of this, Vapor will automatically choose an appropriate decoder or error if it encounters an unknown media type. - -!!! tip - You can [configure](#configure) the default encoders and decoders Vapor uses. - -#### Custom - -You can always override Vapor's default decoder and pass in a custom one if you want. - -```swift -let user = try req.content.decode(User.self, using: JSONDecoder()) -print(user) // Future -``` - -### Response - -Let's take a look at how you would create the following HTTP response from your server. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "name": "Vapor User", - "email": "user@vapor.codes" -} -``` - -Just like decoding, first create a struct or class that represents the data that you are expecting. - -```swift -import Vapor - -struct User: Content { - var name: String - var email: String -} -``` - -Then just conform this struct or class to `Content`. - -#### Encode - -Now we are ready to encode that HTTP response. - -```swift -router.get("user") { req -> User in - return User(name: "Vapor User", email: "user@vapor.codes") -} -``` - -This will create a default `Response` with `200 OK` status code and minimal headers. You can customize the response using a convenience `encode(...)` method. - -```swift -router.get("user") { req -> Future in - return User(name: "Vapor User", email: "user@vapor.codes") - .encode(status: .created) -} -``` - -#### Override Type - -Content will automatically encode as JSON by default. You can always override which content type is used -using the `as:` parameter. - -```swift -try res.content.encode(user, as: .urlEncodedForm) -``` - -You can also change the default media type for any class or struct. - -```swift -struct User: Content { - /// See `Content`. - static let defaultContentType: MediaType = .urlEncodedForm - - ... -} -``` - -## Client - -Encoding content to HTTP requests sent by [`Client`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html)s is similar to encoding HTTP responses returned by your server. - -### Request - -Let's take a look at how we can encode the following request. - -```http -POST /login HTTP/1.1 -Host: api.vapor.codes -Content-Type: application/json - -{ - "email": "user@vapor.codes", - "password": "don't look!" -} -``` - -#### Encode - -First, create a struct or class that represents the data you expect. - -```swift -import Vapor - -struct LoginRequest: Content { - var email: String - var password: String -} -``` - -Now we are ready to make our request. Let's assume we are making this request inside of a route closure, so we will use the _incoming_ request as our container. - -```swift -let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!") -let res = try req.client().post("https://api.vapor.codes/login") { loginReq in - // encode the loginRequest before sending - try loginReq.content.encode(loginRequest) -} -print(res) // Future -``` - -### Response - -Continuing from our example in the encode section, let's see how we would decode content from the client's response. - -```http -HTTP/1.1 200 OK -Content-Type: application/json - -{ - "name": "Vapor User", - "email": "user@vapor.codes" -} -``` - -First of course we must create a struct or class to represent the data. - -```swift -import Vapor - -struct User: Content { - var name: String - var email: String -} -``` - -#### Decode - -Now we are ready to decode the client response. - -```swift -let res: Future // from the Client - -let user = res.flatMap { try $0.content.decode(User.self) } -print(user) // Future -``` - -### Example - -Let's now take a look at our complete [`Client`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html) request that both encodes and decodes content. - -```swift -// Create the LoginRequest data -let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!") -// POST /login -let user = try req.client().post("https://api.vapor.codes/login") { loginReq in - // Encode Content before Request is sent - return try loginReq.content.encode(loginRequest) -}.flatMap { loginRes in - // Decode Content after Response is received - return try loginRes.content.decode(User.self) -} -print(user) // Future -``` - -## Query String - -URL-Encoded Form data can be encoded and decoded from an HTTP request's URI query string just like content. All you need is a class or struct that conforms to [`Content`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Content.html). In these examples, we will be using the following struct. - -```swift -struct Flags: Content { - var search: String? - var isAdmin: Bool? -} -``` - -### Decode - -All [`Request`](https://api.vapor.codes/vapor/latest/Vapor/Classes/Request.html)s have a [`QueryContainer`](https://api.vapor.codes/vapor/latest/Vapor/Structs/QueryContainer.html) that you can use to decode the query string. - -```swift -let flags = try req.query.decode(Flags.self) -print(flags) // Flags -``` - -### Encode - -You can also encode content. This is useful for encoding query strings when using [`Client`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html). - -```swift -let flags: Flags ... -try req.query.encode(flags) -``` - -## Dynamic Properties - -One of the most frequently asked questions regarding `Content` is: - -> How do I add a property to just this response? - -The way Vapor 3 handles `Content` is based entirely on `Codable`. At no point (that is publically accessible) is your data in an arbitrary data structure like `[String: Any]` that you can mutate at will. Because of this, all data structures that your app accepts and returns _must_ be statically defined. - -Let's take a look at a common scenario to better understand this. Very often when you are creating a user, there are a couple different data formats required: - -- create: password should be supplied twice to check values match -- internal: you should store a hash not the plaintext password -- public: when listing users, the password hash should not be included - -To do this, you should create three types. - -```swift -// Data required to create a user -struct UserCreate: Content { - var email: String - var password: String - var passwordCheck: String -} - -// Our internal User representation -struct User: Model { - var id: Int? - var email: String - var passwordHash: Data -} - -// Public user representation -struct PublicUser: Content { - var id: Int - var email: String -} - -// Create a router for POST /users -router.post(UserCreate.self, at: "users") { req, userCreate -> PublicUser in - guard userCreate.password == passwordCheck else { /* some error */ } - let hasher = try req.make(/* some hasher */) - let user = try User( - email: userCreate.email, - passwordHash: hasher.hash(userCreate.password) - ) - // save user - return try PublicUser(id: user.requireID(), email: user.email) -} -``` - -For other methods such as `PATCH` and `PUT`, you may want to create additional types to supports the unique semantics. - -### Benefits - -This method may seem a bit verbose at first when compared to dynamic solutions, but it has a number of key advantages: - -- **Statically Typed**: Very little validation is needed on top of what Swift and Codable do automatically. -- **Readability**: No need for Strings and optional chaining when working with Swift types. -- **Maintainable**: Large projects will appreciate having this information separated and clearly stated. -- **Shareable**: Types defining what content your routes accept and return can be used to conform to specifications like OpenAPI or even be shared directly with clients written in Swift. -- **Performance**: Working with native Swift types is much more performant than mutating `[String: Any]` dictionaries. - -## JSON - -JSON is a very popular encoding format for APIs and the way in which dates, data, floats, etc are encoded is non-standard. Because of this, Vapor makes it easy to use custom [`JSONDecoder`](https://api.vapor.codes/vapor/latest/Vapor/Extensions/JSONDecoder.html#/s:5Vapor6customXeXeFZ)s when you interact with other APIs. - -```swift -// Conforms to Encodable -let user: User ... -// Encode JSON using custom date encoding strategy -try req.content.encode(json: user, using: .custom(dates: .millisecondsSince1970)) -``` - -You can also use this method for decoding. - -```swift -// Decode JSON using custom date encoding strategy -let user = try req.content.decode(json: User.self, using: .custom(dates: .millisecondsSince1970)) -``` - -If you would like to set a custom JSON encoder or decoder globally, you can do so using [configuration](#configure). - -## Configure - -Use [`ContentConfig`](https://api.vapor.codes/vapor/latest/Vapor/Structs/ContentConfig.html) to register custom encoder/decoders for your application. These custom coders will be used anywhere you do `content.encode`/`content.decode`. - -```swift -/// Create default content config -var contentConfig = ContentConfig.default() - -/// Create custom JSON encoder -var jsonEncoder = JSONEncoder() -jsonEncoder.dateEncodingStrategy = .millisecondsSince1970 - -/// Register JSON encoder and content config -contentConfig.use(encoder: jsonEncoder, for: .json) -services.register(contentConfig) -``` \ No newline at end of file diff --git a/3.0/docs/vapor/getting-started.md b/3.0/docs/vapor/getting-started.md deleted file mode 100644 index 11842d29..00000000 --- a/3.0/docs/vapor/getting-started.md +++ /dev/null @@ -1,31 +0,0 @@ -# Getting Started with Vapor - -Check out the main [Getting Started](../getting-started/hello-world.md) guide which covers Vapor specifically. This page is here mostly for consistency with the rest of the packages. - -More in-depth information on the APIs included in Vapor, see the sub-sections to the left. - -## Package - -If you don't want to use one of Vapor's templates to get started, you can always include the framework manually. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["Vapor", ... ]) - ] -) -``` - -Use `import Vapor` to access the APIs. - -## API Docs - -The rest of this guide will give you an overview of what is available in the Vapor package. As always, feel free to visit the [API docs](http://api.vapor.codes/vapor/latest/Vapor/index.html) for more in-depth information. \ No newline at end of file diff --git a/3.0/docs/vapor/middleware.md b/3.0/docs/vapor/middleware.md deleted file mode 100644 index d3a80a95..00000000 --- a/3.0/docs/vapor/middleware.md +++ /dev/null @@ -1,58 +0,0 @@ -# Middleware - -Middleware is a logic chain between the client and a Vapor route handler. It allows you to make operations on incoming requests before they get to the route handler, and on outgoing responses before they go to the client. - -## Configuration, and ErrorMiddleware - -Middleware is registered in your `configure.swift` file. `ErrorMiddleware` is a very common example; it will take a thrown error in your software and convert it to a legible HTTP response code. - -```swift -var middlewares = MiddlewareConfig() -middlewares.use(ErrorMiddleware.self) -middlewares.use(FileMiddleware.self) -// etc. -services.register(middlewares) -``` - -You will often run several middlewares in a single project. These middlewares are stacked up, and then registered together. The order in which middleware are listed can sometimes matter (see `CORSMiddleware` below). - -## FileMiddleware - -`FileMiddleware` enables the serving of assets from the Public folder of your project to the client. You might include static files like stylesheets or bitmap images here. - -```swift -var middlewares = MiddlewareConfig() -middlewares.use(FileMiddleware.self) -services.register(middlewares) -``` - -Now that the `FileMiddleware` is registered, a file like “Public/images/logo.png” can be linked from a Leaf template as ``. - -## CORSMiddleware - -Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served. REST APIs built in Vapor will require a CORS policy in order to safely return requests to modern web browsers. - -An example configuration could look something like this: - -```swift -var middlewares = MiddlewareConfig() -let corsConfiguration = CORSMiddleware.Configuration( - allowedOrigin: .all, - allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH], - allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent, .accessControlAllowOrigin] -) -let corsMiddleware = CORSMiddleware(configuration: corsConfiguration) -middlewares.use(corsMiddleware) -middlewares.use(ErrorMiddleware.self) -services.register(middlewares) -``` - -Given that thrown errors are immediately returned to the client, the `CORSMiddleware` must be listed _before_ the `ErrorMiddleware`; otherwise the HTTP error response will be returned without CORS headers, and cannot be read by the browser. - -## Authentication and Sessions Middleware - -The Vapor Auth package has middlewares that can do basic user validation, token validation, and manage sessions. See the [Auth documentation](https://docs.vapor.codes/3.0/auth/getting-started/) for an outline of the `AuthMiddleware`. - -## Middleware API - -Information on how middleware works and authoring custom middleware can be found in the [Vapor API Documentation](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Middleware.html). diff --git a/3.0/docs/vapor/sessions.md b/3.0/docs/vapor/sessions.md deleted file mode 100644 index 7e5e9f14..00000000 --- a/3.0/docs/vapor/sessions.md +++ /dev/null @@ -1,79 +0,0 @@ -# Using Sessions - -This guide will show you how to use sessions in Vapor to maintain state for a connected client. - -Sessions work by creating unique identifiers for each new client and asking the client to supply this identifier with each request. When the next request is received, Vapor uses this unique identifier to restore the session data. This identifier could be transmitted in any format, but it is almost always done with cookies and that is how Vapor's sessions work. - -When a new client connects and session data is set, Vapor will return a `Set-Cookie` header. The client is then expected to re-supply the value with each request in a `Cookie` header. All browsers do this automatically. If your ever decide to invalidate the session, Vapor will delete any related data and notify the client that their cookie is no longer valid. - -## Middleware - -The first step to using sessions is enabling [`SessionsMiddleware`](https://api.vapor.codes/vapor/latest/Vapor/Classes/SessionsMiddleware.html). This can be done globally for the entire application or on a per-route basis. - -### Globally - -To globally enable sessions, add the middleware to your [`MiddlewareConfig`](https://api.vapor.codes/vapor/latest/Vapor/Structs/MiddlewareConfig.html). - - -```swift -var middlewares = MiddlewareConfig.default() -middlewares.use(SessionsMiddleware.self) -services.register(middlewares) -``` - -This is usually done in [`configure.swift`](../getting-started/structure.md#configureswift). - -### Per Route - -To enable sessions for a group of routes, use the [`grouped(...)`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Router.html) methods on `Router`. - -```swift -// create a grouped router at /sessions w/ sessions enabled -let sessions = router.grouped("sessions").grouped(SessionsMiddleware.self) - -// create a route at GET /sessions/foo -sessions.get("foo") { req in - // use sessions -} -``` - -## Sessions - -When `SessionsMiddleware` boots it will attempt to make a [`Sessions`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Sessions.html) and a [`SessionsConfig`](https://api.vapor.codes/vapor/latest/Vapor/Structs/SessionsConfig.html). Vapor will use an in-memory session by default. You can override both of these services by registering them in `configure.swift`. - -You can use Fluent databases (like MySQL, PostgreSQL, etc) or caches like Redis to store your sessions. See the respective guides for more information. - -## Session - -Once you have `SessionsMiddleware` enabled, you can use [`req.session()`](https://api.vapor.codes/vapor/latest/Vapor/Classes/Request.html#/s:5Vapor7RequestC7sessionAA7SessionCyKF) to access the session. Here is a simple example that does simple CRUD operations on a `"name"` value in the session. - -```swift -// create a route at GET /sessions/get -sessions.get("get") { req -> String in - // access "name" from session or return n/a - return try req.session()["name"] ?? "n/a" -} - -// create a route at GET /sessions/set/:name -sessions.get("set", String.parameter) { req -> String in - // get router param - let name = try req.parameters.next(String.self) - - // set name to session at key "name" - try req.session()["name"] = name - - // return the newly set name - return name -} - -// create a route at GET /sessions/del -sessions.get("del") { req -> String in - // destroy the session - try req.destroySession() - - // signal success - return "done" -} -``` - -That's it, congratulations on getting sessions working! diff --git a/3.0/docs/vapor/websocket.md b/3.0/docs/vapor/websocket.md deleted file mode 100644 index a9e88d4a..00000000 --- a/3.0/docs/vapor/websocket.md +++ /dev/null @@ -1,88 +0,0 @@ -# Using WebSockets - -Vapor includes convenience methods for working with the lower level WebSocket [client](../websocket/overview.md#client) and [server](../websocket/overview.md#server). - -## Server - -Vapor's WebSocket server includes the ability to route incoming requests just like its HTTP server. - -When Vapor's main HTTP [`Server`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Server.html) boots it will attempt to create a [`WebSocketServer`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/WebSocketServer.html). If one is registered, it will be added as an HTTP upgrade handler to the server. - -So to create a WebSocket server, all you need to do is register one in [`configure.swift`](../getting-started/structure.md#configureswift). - -```swift -// Create a new NIO websocket server -let wss = NIOWebSocketServer.default() - -// Add WebSocket upgrade support to GET /echo -wss.get("echo") { ws, req in - // Add a new on text callback - ws.onText { ws, text in - // Simply echo any received text - ws.send(text) - } -} - -// Register our server -services.register(wss, as: WebSocketServer.self) -``` - -That's it. Next time you boot your server, you will be able to perform a WebSocket upgrade at `GET /echo`. You can test this using a simple command line tool called [`wsta`](https://github.com/esphen/wsta) available for macOS and Linux. - -```sh -$ wsta ws://localhost:8080/echo -Connected to ws://localhost:8080/echo -hello, world! -hello, world! -``` - -### Parameters - -Like Vapor's HTTP router, you can also use routing parameters with your WebSocket server. - -```swift -// Add WebSocket upgrade support to GET /chat/:name -wss.get("chat", String.parameter) { ws, req in - let name = try req.parameters.next(String.self) - ws.send("Welcome, \(name)!") - - // ... -} -``` - -Now let's test this new route: - -```sh -$ wsta ws://localhost:8080/chat/Vapor -Connected to ws://localhost:8080/chat/Vapor -Welcome, Vapor! -``` - -## Client - -Vapor also supports connecting to WebSocket servers as a client. The easiest way to connect to a WebSocket server is through the [`webSocket(...)`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html#/s:5Vapor6ClientPAAE9webSocketXeXeF) method on [`Client`](https://api.vapor.codes/vapor/latest/Vapor/Protocols/Client.html). - -For this example, we will assume our application connects to a WebSocket server in [`boot.swift`](../getting-started/structure.md#bootswift) - -```swift -// connect to echo.websocket.org -let done = try app.client().webSocket("ws://echo.websocket.org").flatMap { ws -> Future in - // setup an on text callback that will print the echo - ws.onText { ws, text in - print("rec: \(text)") - // close the websocket connection after we recv the echo - ws.close() - } - - // when the websocket first connects, send message - ws.send("hello, world!") - - // return a future that will complete when the websocket closes - return ws.onClose -} - -print(done) // Future - -// wait for the websocket to close -try done.wait() -``` \ No newline at end of file diff --git a/3.0/docs/version/1_5.md b/3.0/docs/version/1_5.md deleted file mode 100644 index b296eea3..00000000 --- a/3.0/docs/version/1_5.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/3.0/docs/version/2_0.md b/3.0/docs/version/2_0.md deleted file mode 100644 index c5451988..00000000 --- a/3.0/docs/version/2_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/3.0/docs/version/3_0.md b/3.0/docs/version/3_0.md deleted file mode 100644 index a22a17f2..00000000 --- a/3.0/docs/version/3_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/3.0/docs/version/4_0.md b/3.0/docs/version/4_0.md deleted file mode 100644 index 1ec89cc1..00000000 --- a/3.0/docs/version/4_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/3.0/docs/version/support.md b/3.0/docs/version/support.md deleted file mode 100644 index 017390d8..00000000 --- a/3.0/docs/version/support.md +++ /dev/null @@ -1,34 +0,0 @@ -# Version Support - -![Support Matrix](https://user-images.githubusercontent.com/1342803/39658502-3a7f7c8e-4fe3-11e8-8551-d2c30d44ee05.png) - -Vapor 3.0 is currently active. - -Vapor 2.4 is being maintained until November 2018. - -Vapor 1.5 is no longer maintained (ended November 2017). - -## Core - -All packages in the [Vapor GitHub](https://github.com/vapor) are maintained according to the following rules. - -### Active - -While a version is active, reported security issues and bugs are fixed. - -Additionally, new features and optimizations may be added. If new API is added, the minor version number will be incremented. At no point can existing API be removed or broken during an active version. Semver is strictly followed and tested. - -### Maintenance - -When a new version of Vapor is released, the previous version will enter a maintenance phase which lasts for six months. During this phase, critical security issues and bugs will be fixed. No new features will be added. - -!!! note - Only the latest minor version will be maintained. - -### Unstable - -The master branch is the latest development version of Vapor and is constantly changing. Before a new version of Vapor is released, there may be unstable alpha and beta phases in which you are welcome to test and provide feedback on the changes. - -## Community - -All packages in the [Vapor Community GitHub](https://github.com/vapor-community) are maintained in strict accordance of semver. Violations of semver should be reported as issues to the offending package's GitHub page. diff --git a/3.0/docs/version/upgrading.md b/3.0/docs/version/upgrading.md deleted file mode 100644 index 1d7961aa..00000000 --- a/3.0/docs/version/upgrading.md +++ /dev/null @@ -1,106 +0,0 @@ -# Upgrading Versions - -This document provides information about changes between version and tips for migrating your projects. - -## 2.4 to 3.0 - -Vapor 3 has been rewritten from the ground up to be async and event-driven. This release contains the most changes of any previous release (and most likely any future release). - -Because of this, it is recommended that to migrate your projects you start by creating a new, empty template and migrate by copy / pasting code over to the new project. - -We recommend reading the [Getting Started → Hello, world!](../getting-started/hello-world.md) section for Vapor 3 to familiarize yourself with the new APIs. - -### Async - -The biggest change in Vapor 3 is that the framework is now completely asynchronous. When you call methods that need to perform slow work like network requests or disk access instead of blocking they will now return a `Future`. - -Futures are values that may not exist yet, so you cannot interact with them directly. Instead, you must use `map`/`flatMap` to access the values. - -```swift -// vapor 2 -let res = try drop.client.get("http://vapor.codes") -print(res.status) // HTTPStatus -return res.status - -// vapor 3 -let f = try req.client().get("http://vapor.codes").map { res in - print(res.http.status) // HTTPStatus - return res.http.status -} -print(f) // Future -``` - -See [Async → Getting Started](../async/getting-started.md) to learn more. - -### Application & Services - -`Droplet` has been renamed to `Application` and is now a service-container. In Vapor 2, the `Droplet` had stored properties for things you would need during development (like views, hashers, etc). In Vapor 3, this is all done via services. - -While the `Application` _is_ a service-container, you should not use it from your route closures. This is to prevent race conditions since Vapor runs on multiple threads (event loops). Instead, use the `Request` that is supplied to your route closure. This has a _copy_ of all of the application's services for you to use. - -```swift -// vapor 2 -return try drop.view.make("myView") - -// vapor 3 -return try req.make(ViewRenderer.self).render("myView") -// shorthand -return try req.view().render("myView") -``` - -See [Service → Getting Started](../service/getting-started.md) to learn more. - -### Database Connections - -In Vapor 3, database connections are no longer statically accessible. This makes doing things like transactions and connection pooling much more predictable and performant. - -In order to create a `QueryBuilder` in Fluent 3, you will need access to something `DatabaseConnectable`. Most often you can just use the incoming `Request`, but you can also create connections manually if you need. - -```swift -// vapor 2 -User.makeQuery().all() - -// vapor 3 -User.query(on: req).all() -``` - -See [DatabaseKit → Getting Started](../database-kit/getting-started.md) to learn more. - -### Migrating SQL Database - -When migrating from Fluent 2 to 3 you may need to update your `fluent` table to support the new format. In Fluent 3, the migration log table has the following changes: - -- `id` is now a `UUID`. -- `createdAt` and `updatedAt` must now be `camelCase`. - -Depending on how your Fluent database was configured, your tables may already be in the correct format. If not, you can run the following queries to transfer the table data. - -Use this query if your column names were already set to `camelCase`. - -```sql -ALTER TABLE fluent RENAME TO fluent_old; -CREATE TABLE fluent - AS (SELECT UUID() as id, name, batch, createdAt, updatedAt from fluent_old); -``` - -Use this query if your column names were `snake_case`. - -```sql -ALTER TABLE fluent RENAME TO fluent_old; -CREATE TABLE fluent - AS (SELECT UUID() as id, name, batch, created_at as createdAt, updated_at as updatedAt from fluent_old); -``` - -After you have verified the table was transferred properly, you can drop the old fluent table. - -```sql -DROP TABLE fluent_old; -``` - -### Work in progress - -This migration guide is a work in progress. Please feel free to add any migration tips here by submitting a PR. - -Join the [#upgrading-to-3](https://discordapp.com/invite/BnXmVGA) in Vapor's team chat to ask questions and get help in real time. - -Also check out [Getting started with Vapor 3](https://engineering.nodesagency.com/articles/Vapor/Getting-started-with-Vapor-3/), an in-depth article about the differences between Vapor 2 and 3. This article was written by two developers from an app development company using Vapor. diff --git a/3.0/docs/websocket/getting-started.md b/3.0/docs/websocket/getting-started.md deleted file mode 100644 index a8d1e6fa..00000000 --- a/3.0/docs/websocket/getting-started.md +++ /dev/null @@ -1,40 +0,0 @@ -# Getting Started with WebSocket - -WebSocket ([vapor/websocket-kit](https://github.com/vapor/websocket-kit)) is a non-blocking, event-driven WebSocket library built on SwiftNIO. It makes working with SwiftNIO's WebSocket handlers easy and provides integration with [HTTP](../http/getting-started.md) clients and servers. Creating a WebSocket echo server takes just a few lines of code. - -!!! tip - If you use Vapor, most of WebSocket's APIs will be wrapped by more convenient methods. - -## Vapor - -This package is included with Vapor and exported by default. You will have access to all `WebSocket` APIs when you import `Vapor`. - -```swift -import Vapor -``` - -## Standalone - -The WebSocket package is lightweight, pure Swift, and only depends on SwiftNIO. This means it can be used as a WebSocket framework any Swift project—even one not using Vapor. - -To include it in your package, add the following to your `Package.swift` file. - -```swift -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Project", - dependencies: [ - ... - .package(url: "https://github.com/vapor/websocket-kit.git", from: "2.0.0"), - ], - targets: [ - .target(name: "Project", dependencies: ["WebSocket", ... ]) - ] -) -``` - -Use `import WebSocketKit` to access the APIs. - -The rest of this guide will give you an overview of what is available in the WebSocket package. As always, feel free to visit the [API docs](http://api.vapor.codes/websocket/latest/WebSocket/index.html) for more in-depth information. diff --git a/3.0/docs/websocket/overview.md b/3.0/docs/websocket/overview.md deleted file mode 100644 index a3ac8145..00000000 --- a/3.0/docs/websocket/overview.md +++ /dev/null @@ -1,94 +0,0 @@ -# Using WebSockets - -Unlike HTTP, WebSockets allow you to communicate between client and server in an open, interactive way. You can send messages (called frames) in either text or binary format. Both the client and the server can send as many messages as they want at a time, without having to wait for responses. - -Although WebSocket is its own protocol, it still uses HTTP to get setup. Every WebSocket connection will start with an HTTP request with special headers followed by an HTTP response with status `101 Switching Protocols`. After this initial handshake, the connection is a WebSocket connection. - -## WebSocket - -The [`WebSocket`](https://api.vapor.codes/websocket/latest/WebSocket/Classes/WebSocket.html) class represents a connected WebSocket client. You can use this to set callbacks for receiving data and to send data. - -```swift -let ws: WebSocket = ... -// Send an initial message to this WebSocket -ws.send("Hello!") - -// Set a new callback for receiving text formatted data -ws.onText { ws, string in - // Echo the text back, reversed. - ws.send(string.reversed()) -} -``` - -!!! tip - All callbacks will receive a reference to the `WebSocket`. Use these if you need to send data to avoid creating a reference cycle. - -The `WebSocket` has an [`onClose`](https://api.vapor.codes/websocket/latest/WebSocket/Classes/WebSocket.html#/s:9WebSocketAAC7onCloseXev) future that will be completed when the connection closes. You can use [`close()`](https://api.vapor.codes/websocket/latest/WebSocket/Classes/WebSocket.html#/s:9WebSocketAAC5closeyyF) to close the connection yourself. - -## Server - -WebSocket servers connect to one or more WebSocket clients at a time. As mentioned previously, WebSocket connections must start via an HTTP request and response handshake. Because of this, WebSocket servers are built on top of [HTTP servers](../http/server.md) using the HTTP upgrade mechanism. - -```swift -// First, create an HTTPProtocolUpgrader -let ws = HTTPServer.webSocketUpgrader(shouldUpgrade: { req in - // Returning nil in this closure will reject upgrade - if req.url.path == "/deny" { return nil } - // Return any additional headers you like, or just empty - return [:] -}, onUpgrade: { ws, req in - // This closure will be called with each new WebSocket client - ws.send("Connected") - ws.onText { ws, string in - ws.send(string.reversed()) - } -}) - -// Next, create your server, adding the WebSocket upgrader -let server = try HTTPServer.start( - ... - upgraders: [ws], - ... -).wait() -// Run the server. -try server.onClose.wait() -``` - -!!! seealso - Visit [HTTP → Server](../http/server.md) for more information on setting up an HTTP server. - -The WebSocket protocol upgrader consists of two callbacks. - -The first callback `shouldUpgrade` receives the incoming HTTP request that is requesting upgrade. This callback decides whether or not to complete the upgrade based on the contents of the request. If `nil` is returned in this closure, the upgrade will be rejected. - -The second callback `onUpgrade` is called each time a new WebSocket client connects. This is where you configure your callbacks and send any initial data. - -!!! warning - The upgrade closures may be called on any event loop. Be careful to avoid race conditions if you must access external variables. - -## Client - -You can also use the WebSocket package to connect _to_ a WebSocket server. Just like the WebSocket server used an HTTP server, the WebSocket client uses HTTP client. - -```swift -// Create a new WebSocket connected to echo.websocket.org -let ws = try HTTPClient.webSocket(hostname: "echo.websocket.org", on: ...).wait() - -// Set a new callback for receiving text formatted data. -ws.onText { ws, text in - print("Server echo: \(text)") -} - -// Send a message. -ws.send("Hello, world!") - -// Wait for the Websocket to close. -try ws.onClose.wait() -``` - -!!! seealso - Visit [HTTP → Client](../http/client.md) for more information on setting up an HTTP client. - -## API Docs - -Check out the [API docs](https://api.vapor.codes/websocket/latest/WebSocket/index.html) for more in-depth information about all of the methods. diff --git a/3.0/mkdocs.yml b/3.0/mkdocs.yml deleted file mode 100644 index 59ac9efc..00000000 --- a/3.0/mkdocs.yml +++ /dev/null @@ -1,155 +0,0 @@ -site_name: Vapor Docs -copyright: Copyright © 2018 Qutheory, LLC -site_url: https://docs.vapor.codes/3.0/ -nav: -- 'Overview': 'index.md' -- 'Install': - - 'macOS': 'install/macos.md' - - 'Ubuntu': 'install/ubuntu.md' -- 'Getting Started': - - 'Hello, world': 'getting-started/hello-world.md' - - 'Toolbox': 'getting-started/toolbox.md' - - 'SPM': 'getting-started/spm.md' - - 'Xcode': 'getting-started/xcode.md' - - 'Folder Structure': 'getting-started/structure.md' - - 'Application': 'getting-started/application.md' - - 'Controllers': 'getting-started/controllers.md' - - 'Routing': 'getting-started/routing.md' - - 'Content': 'getting-started/content.md' - - 'Async': 'getting-started/async.md' - - 'Services': 'getting-started/services.md' - - 'Deployment': 'getting-started/cloud.md' -- 'Async': - - 'Getting Started': 'async/getting-started.md' - - 'Overview': 'async/overview.md' -- 'Auth': - - 'Getting Started': 'auth/getting-started.md' - - 'Stateless (API)': 'auth/api.md' - - 'Sessions (Web)': 'auth/web.md' -- 'Console': - - 'Getting Started': 'console/getting-started.md' - - 'Overview': 'console/overview.md' -- 'Command': - - 'Getting Started': 'command/getting-started.md' - - 'Overview': 'command/overview.md' -- 'Crypto': - - 'Getting Started': 'crypto/getting-started.md' - - 'Digests': 'crypto/digests.md' - - 'Ciphers': 'crypto/ciphers.md' - - 'Asymmetric': 'crypto/asymmetric.md' - - 'Random': 'crypto/random.md' - - 'TOTP & HOTP': 'crypto/otp.md' -- 'Database Kit': - - 'Getting Started': 'database-kit/getting-started.md' - - 'Overview': 'database-kit/overview.md' -- 'Fluent': - - 'Getting Started': 'fluent/getting-started.md' - - 'Models': 'fluent/models.md' - - 'Querying': 'fluent/querying.md' - - 'Migrations': 'fluent/migrations.md' - - 'Relations': 'fluent/relations.md' - - 'Transaction': 'fluent/transaction.md' -- 'HTTP': - - 'Getting Started': 'http/getting-started.md' - - 'Client': 'http/client.md' - - 'Server': 'http/server.md' - - 'Message': 'http/message.md' -- 'Jobs': - - 'Getting Started': 'jobs/getting-started.md' - - 'Redis Driver': 'jobs/redis-driver.md' - - 'Modeling Jobs': 'jobs/jobs.md' - - 'Dispatching Jobs': 'jobs/dispatching-jobs.md' - - 'Scheduling Jobs': 'jobs/scheduling-jobs.md' -- 'JWT': - - 'Getting Started': 'jwt/getting-started.md' - - 'Overview': 'jwt/overview.md' -- 'Leaf': - - 'Getting Started': 'leaf/getting-started.md' - - 'Overview': 'leaf/overview.md' - - 'Custom tags': 'leaf/custom-tags.md' -- 'Logging': - - 'Getting Started': 'logging/getting-started.md' - - 'Overview': 'logging/overview.md' -- 'Multipart': - - 'Getting Started': 'multipart/getting-started.md' - - 'Overview': 'multipart/overview.md' -- 'MySQL': - - 'Getting Started': 'mysql/getting-started.md' -- 'PostgreSQL': - - 'Getting Started': 'postgresql/getting-started.md' -- 'Redis': - - 'Getting Started': 'redis/getting-started.md' - - 'Overview': 'redis/overview.md' -- 'Routing': - - 'Getting Started': 'routing/getting-started.md' - - 'Overview': 'routing/overview.md' -- 'Service': - - 'Getting Started': 'service/getting-started.md' - - 'Services': 'service/services.md' - - 'Provider': 'service/provider.md' -- 'SQL': - - 'Getting Started': 'sql/getting-started.md' - - 'Overview': 'sql/overview.md' -- 'SQLite': - - 'Getting Started': 'sqlite/getting-started.md' -- 'Template Kit': - - 'Getting Started': 'template-kit/getting-started.md' -- 'Testing': - - 'Getting Started': 'testing/getting-started.md' -- 'URL-Encoded Form': - - 'Getting Started': 'url-encoded-form/getting-started.md' - - 'Overview': 'url-encoded-form/overview.md' -- 'Validation': - - 'Getting Started': 'validation/getting-started.md' - - 'Overview': 'validation/overview.md' -- 'Vapor': - - 'Getting Started': 'vapor/getting-started.md' - - 'Client': 'vapor/client.md' - - 'Content': 'vapor/content.md' - - 'Sessions': 'vapor/sessions.md' - - 'WebSocket': 'vapor/websocket.md' - - 'Middleware': 'vapor/middleware.md' -- 'WebSocket': - - 'Getting Started': 'websocket/getting-started.md' - - 'Overview': 'websocket/overview.md' -- 'Deploy': - - 'Heroku': 'deploy/heroku.md' -- 'Extras': - - 'Style Guide': 'extras/style-guide.md' - - 'Yeoman': 'extras/yeoman.md' -- 'Version (3.0)': - - '1.5': 'version/1_5.md' - - '2.0': 'version/2_0.md' - - '3.0': 'version/3_0.md' - - '4.0': 'version/4_0.md' - - 'Upgrading': 'version/upgrading.md' - - 'Support': 'version/support.md' -markdown_extensions: - - admonition - - codehilite: - guess_lang: false - - footnotes - - meta - - toc: - permalink: true -theme: - name: "material" - custom_dir: "theme/" - palette: - primary: "blue" - accent: "purple" - logo: 'images/droplet-white.svg' -repo_url: http://github.com/vapor/vapor -edit_uri: https://github.com/vapor/documentation/edit/master/3.0/docs/ -extra: - social: - - icon: fontawesome/brands/twitter - link: 'https://twitter.com/@codevapor' - - icon: fontawesome/brands/discord - link: 'http://vapor.team/' - - icon: fontawesome/brands/github - link: 'https://github.com/vapor' - font: - text: 'Roboto Slab' - code: 'Source Code Pro' - disqus: 'vapor-docs' diff --git a/3.0/theme/main.html b/3.0/theme/main.html deleted file mode 100644 index e01f20b2..00000000 --- a/3.0/theme/main.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base.html" %} - -{% block styles %} - {{ super() }} - -{% endblock %} - -{% block libs %} - {{ super() }} - -{% endblock %} \ No newline at end of file diff --git a/4.0/docs/version/1_5.md b/4.0/docs/version/1_5.md deleted file mode 100644 index b296eea3..00000000 --- a/4.0/docs/version/1_5.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/4.0/docs/version/2_0.md b/4.0/docs/version/2_0.md deleted file mode 100644 index c5451988..00000000 --- a/4.0/docs/version/2_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/4.0/docs/version/3_0.md b/4.0/docs/version/3_0.md deleted file mode 100644 index a22a17f2..00000000 --- a/4.0/docs/version/3_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/4.0/docs/version/4_0.md b/4.0/docs/version/4_0.md deleted file mode 100644 index 1ec89cc1..00000000 --- a/4.0/docs/version/4_0.md +++ /dev/null @@ -1,3 +0,0 @@ -# Redirecting... - - diff --git a/4.0/theme/scripts/carbon.js b/4.0/theme/scripts/carbon.js deleted file mode 100644 index c09d4c99..00000000 --- a/4.0/theme/scripts/carbon.js +++ /dev/null @@ -1,9 +0,0 @@ -// data-md-component="toc" -document.addEventListener("DOMContentLoaded", function(event) { - var toc_inner = document.querySelectorAll('[data-md-component=toc] .md-sidebar__inner')[0]; - var script = document.createElement("script"); - script.src = '//cdn.carbonads.com/carbon.js?serve=CK7DT2QW&placement=vaporcodes'; - script.type = 'text/javascript'; - script.id = '_carbonads_js'; - toc_inner.appendChild(script); -}); \ No newline at end of file diff --git a/4.0/theme/styles/carbon.css b/4.0/theme/styles/carbon.css deleted file mode 100644 index e1af32c4..00000000 --- a/4.0/theme/styles/carbon.css +++ /dev/null @@ -1,61 +0,0 @@ -#carbonads { - margin-left: 10px; - margin-top: 12px; - display: block; - overflow: hidden; - max-width: 160px; - border: solid 1px hsla(0, 0%, 0%, .1); - border-radius: 4px; - background-color: hsl(0, 0%, 98%); - text-align: center; - font-size: 12px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, - Cantarell, "Helvetica Neue", Helvetica, Arial, sans-serif; - line-height: 1.5; -} - -#carbonads a { - color: inherit; - text-decoration: none; -} - -#carbonads a:hover { - color: inherit; -} - -#carbonads span { - position: relative; - display: block; - overflow: hidden; -} - -.carbon-img { - display: block; - margin-bottom: 8px; - max-width: 160px; - line-height: 1; -} - -.carbon-img img { - display: block; - margin: 0 auto; - max-width: 160px !important; - width: 160px; - height: auto; -} - -.carbon-text { - display: block; - padding: 0 1em 8px; -} - -.carbon-poweredby { - display: block; - padding: 10px 12px; - background: repeating-linear-gradient(-45deg, transparent, transparent 5px, hsla(0, 0%, 0%, .025) 5px, hsla(0, 0%, 0%, .025) 10px) hsla(203, 11%, 95%, .4); - text-transform: uppercase; - letter-spacing: .5px; - font-weight: 600; - font-size: 9px; - line-height: 0; -} diff --git a/build.sh b/build.sh deleted file mode 100755 index c700d65d..00000000 --- a/build.sh +++ /dev/null @@ -1,26 +0,0 @@ -cd 2.0; -mkdocs build; -cd ..; - -cd 3.0; -mkdocs build; -cd ..; - -cd 4.0; -mkdocs build; -cd ..; - -cd 1.5; -couscous generate; -cd ..; - -rm -rf site -mkdir -p site; - -mv 2.0/site site/2.0; -mv 3.0/site site/3.0; -mv 4.0/site site/4.0; -mv 1.5/.couscous/generated site/1.5; - -echo "" > site/index.html; -cp googlefc012e5d94cfa05f.html site/googlefc012e5d94cfa05f.html; diff --git a/4.0/docs/apns.md b/docs/apns.md similarity index 100% rename from 4.0/docs/apns.md rename to docs/apns.md diff --git a/4.0/docs/assets/favicon.png b/docs/assets/favicon.png similarity index 100% rename from 4.0/docs/assets/favicon.png rename to docs/assets/favicon.png diff --git a/4.0/docs/assets/fonts/Roboto-Bold.ttf b/docs/assets/fonts/Roboto-Bold.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-Bold.ttf rename to docs/assets/fonts/Roboto-Bold.ttf diff --git a/4.0/docs/assets/fonts/Roboto-BoldItalic.ttf b/docs/assets/fonts/Roboto-BoldItalic.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-BoldItalic.ttf rename to docs/assets/fonts/Roboto-BoldItalic.ttf diff --git a/4.0/docs/assets/fonts/Roboto-Italic.ttf b/docs/assets/fonts/Roboto-Italic.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-Italic.ttf rename to docs/assets/fonts/Roboto-Italic.ttf diff --git a/4.0/docs/assets/fonts/Roboto-Light.ttf b/docs/assets/fonts/Roboto-Light.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-Light.ttf rename to docs/assets/fonts/Roboto-Light.ttf diff --git a/4.0/docs/assets/fonts/Roboto-LightItalic.ttf b/docs/assets/fonts/Roboto-LightItalic.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-LightItalic.ttf rename to docs/assets/fonts/Roboto-LightItalic.ttf diff --git a/4.0/docs/assets/fonts/Roboto-Regular.ttf b/docs/assets/fonts/Roboto-Regular.ttf similarity index 100% rename from 4.0/docs/assets/fonts/Roboto-Regular.ttf rename to docs/assets/fonts/Roboto-Regular.ttf diff --git a/4.0/docs/assets/fonts/RobotoMono-Bold.ttf b/docs/assets/fonts/RobotoMono-Bold.ttf similarity index 100% rename from 4.0/docs/assets/fonts/RobotoMono-Bold.ttf rename to docs/assets/fonts/RobotoMono-Bold.ttf diff --git a/4.0/docs/assets/fonts/RobotoMono-BoldItalic.ttf b/docs/assets/fonts/RobotoMono-BoldItalic.ttf similarity index 100% rename from 4.0/docs/assets/fonts/RobotoMono-BoldItalic.ttf rename to docs/assets/fonts/RobotoMono-BoldItalic.ttf diff --git a/4.0/docs/assets/fonts/RobotoMono-Italic.ttf b/docs/assets/fonts/RobotoMono-Italic.ttf similarity index 100% rename from 4.0/docs/assets/fonts/RobotoMono-Italic.ttf rename to docs/assets/fonts/RobotoMono-Italic.ttf diff --git a/4.0/docs/assets/fonts/RobotoMono-Regular.ttf b/docs/assets/fonts/RobotoMono-Regular.ttf similarity index 100% rename from 4.0/docs/assets/fonts/RobotoMono-Regular.ttf rename to docs/assets/fonts/RobotoMono-Regular.ttf diff --git a/4.0/docs/assets/logo.png b/docs/assets/logo.png similarity index 100% rename from 4.0/docs/assets/logo.png rename to docs/assets/logo.png diff --git a/4.0/docs/async.md b/docs/async.md similarity index 100% rename from 4.0/docs/async.md rename to docs/async.md diff --git a/4.0/docs/authentication.md b/docs/authentication.md similarity index 100% rename from 4.0/docs/authentication.md rename to docs/authentication.md diff --git a/4.0/docs/client.md b/docs/client.md similarity index 100% rename from 4.0/docs/client.md rename to docs/client.md diff --git a/4.0/docs/commands.md b/docs/commands.md similarity index 100% rename from 4.0/docs/commands.md rename to docs/commands.md diff --git a/4.0/docs/content.md b/docs/content.md similarity index 100% rename from 4.0/docs/content.md rename to docs/content.md diff --git a/4.0/docs/controllers.md b/docs/controllers.md similarity index 100% rename from 4.0/docs/controllers.md rename to docs/controllers.md diff --git a/4.0/docs/crypto.md b/docs/crypto.md similarity index 100% rename from 4.0/docs/crypto.md rename to docs/crypto.md diff --git a/4.0/docs/deploy/digital-ocean.md b/docs/deploy/digital-ocean.md similarity index 100% rename from 4.0/docs/deploy/digital-ocean.md rename to docs/deploy/digital-ocean.md diff --git a/4.0/docs/deploy/docker.md b/docs/deploy/docker.md similarity index 100% rename from 4.0/docs/deploy/docker.md rename to docs/deploy/docker.md diff --git a/4.0/docs/deploy/heroku.md b/docs/deploy/heroku.md similarity index 100% rename from 4.0/docs/deploy/heroku.md rename to docs/deploy/heroku.md diff --git a/4.0/docs/deploy/nginx.md b/docs/deploy/nginx.md similarity index 100% rename from 4.0/docs/deploy/nginx.md rename to docs/deploy/nginx.md diff --git a/4.0/docs/deploy/supervisor.md b/docs/deploy/supervisor.md similarity index 100% rename from 4.0/docs/deploy/supervisor.md rename to docs/deploy/supervisor.md diff --git a/4.0/docs/environment.md b/docs/environment.md similarity index 100% rename from 4.0/docs/environment.md rename to docs/environment.md diff --git a/4.0/docs/errors.md b/docs/errors.md similarity index 100% rename from 4.0/docs/errors.md rename to docs/errors.md diff --git a/4.0/docs/files.md b/docs/files.md similarity index 100% rename from 4.0/docs/files.md rename to docs/files.md diff --git a/4.0/docs/fluent/advanced.md b/docs/fluent/advanced.md similarity index 100% rename from 4.0/docs/fluent/advanced.md rename to docs/fluent/advanced.md diff --git a/4.0/docs/fluent/migration.md b/docs/fluent/migration.md similarity index 100% rename from 4.0/docs/fluent/migration.md rename to docs/fluent/migration.md diff --git a/4.0/docs/fluent/model.md b/docs/fluent/model.md similarity index 100% rename from 4.0/docs/fluent/model.md rename to docs/fluent/model.md diff --git a/4.0/docs/fluent/overview.md b/docs/fluent/overview.md similarity index 99% rename from 4.0/docs/fluent/overview.md rename to docs/fluent/overview.md index b87805b1..e2df634a 100644 --- a/4.0/docs/fluent/overview.md +++ b/docs/fluent/overview.md @@ -118,7 +118,7 @@ MySQL is a popular open source SQL database. It is available on many cloud hosti To use MySQL, add the following dependencies to your package. ```swift -.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0-beta") +.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0") ``` ```swift diff --git a/4.0/docs/fluent/query.md b/docs/fluent/query.md similarity index 100% rename from 4.0/docs/fluent/query.md rename to docs/fluent/query.md diff --git a/4.0/docs/fluent/relations.md b/docs/fluent/relations.md similarity index 100% rename from 4.0/docs/fluent/relations.md rename to docs/fluent/relations.md diff --git a/4.0/docs/fluent/schema.md b/docs/fluent/schema.md similarity index 100% rename from 4.0/docs/fluent/schema.md rename to docs/fluent/schema.md diff --git a/4.0/docs/fluent/transaction.md b/docs/fluent/transaction.md similarity index 100% rename from 4.0/docs/fluent/transaction.md rename to docs/fluent/transaction.md diff --git a/4.0/docs/folder-structure.md b/docs/folder-structure.md similarity index 100% rename from 4.0/docs/folder-structure.md rename to docs/folder-structure.md diff --git a/4.0/docs/hello-world.md b/docs/hello-world.md similarity index 100% rename from 4.0/docs/hello-world.md rename to docs/hello-world.md diff --git a/4.0/docs/images/digital-ocean-create-droplet.png b/docs/images/digital-ocean-create-droplet.png similarity index 100% rename from 4.0/docs/images/digital-ocean-create-droplet.png rename to docs/images/digital-ocean-create-droplet.png diff --git a/4.0/docs/images/digital-ocean-distributions-ubuntu-18.png b/docs/images/digital-ocean-distributions-ubuntu-18.png similarity index 100% rename from 4.0/docs/images/digital-ocean-distributions-ubuntu-18.png rename to docs/images/digital-ocean-distributions-ubuntu-18.png diff --git a/4.0/docs/images/digital-ocean-droplet-list.png b/docs/images/digital-ocean-droplet-list.png similarity index 100% rename from 4.0/docs/images/digital-ocean-droplet-list.png rename to docs/images/digital-ocean-droplet-list.png diff --git a/4.0/docs/images/swift-download-ubuntu-18-copy-link.png b/docs/images/swift-download-ubuntu-18-copy-link.png similarity index 100% rename from 4.0/docs/images/swift-download-ubuntu-18-copy-link.png rename to docs/images/swift-download-ubuntu-18-copy-link.png diff --git a/4.0/docs/images/xcode-mac-app-store.png b/docs/images/xcode-mac-app-store.png similarity index 100% rename from 4.0/docs/images/xcode-mac-app-store.png rename to docs/images/xcode-mac-app-store.png diff --git a/4.0/docs/images/xcode-scheme-area.png b/docs/images/xcode-scheme-area.png similarity index 100% rename from 4.0/docs/images/xcode-scheme-area.png rename to docs/images/xcode-scheme-area.png diff --git a/4.0/docs/images/xcode-scheme-menu.png b/docs/images/xcode-scheme-menu.png similarity index 100% rename from 4.0/docs/images/xcode-scheme-menu.png rename to docs/images/xcode-scheme-menu.png diff --git a/4.0/docs/images/xcode-scheme-options.png b/docs/images/xcode-scheme-options.png similarity index 100% rename from 4.0/docs/images/xcode-scheme-options.png rename to docs/images/xcode-scheme-options.png diff --git a/4.0/docs/index.md b/docs/index.md similarity index 62% rename from 4.0/docs/index.md rename to docs/index.md index 8b13b95b..68361509 100644 --- a/4.0/docs/index.md +++ b/docs/index.md @@ -1,11 +1,4 @@ -This is the documentation for Vapor, a web framework for Swift. - -Vapor is the most used web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website or API. - -## Languages - -- English [(docs.vapor.codes)](https://docs.vapor.codes) -- 简体中文 [(cn.docs.vapor.codes)](https://cn.docs.vapor.codes) +Welcome to the Vapor Documentation! Vapor is a web framework for Swift, allowing you to write backends, web apps APIs and HTTP servers in Swift. Vapor is written in Swift, which is a modern, powerful and safe language providing a number of benefits over the more traditional server languages. ## Getting Started @@ -17,16 +10,19 @@ Once you have Vapor installed, check out [Getting Started → Hello, world](hell Here are some other great places to find information about Vapor. -| name | description | link | -|----------------|--------------------------------------------------|-----------------------------------------------------------------| -| Vapor Discord | Chat with thousands of Vapor developers. | [visit →](https://vapor.team) | -| API docs | Auto-generated documentation from code comments. | [visit →](https://api.vapor.codes) | -| Stack Overflow | Ask and answer questions with the `vapor` tag. | [visit →](https://stackoverflow.com/questions/tagged/vapor) | -| Swift Forums | Post in Vapor's section of the Swift.org forums. | [visit →](https://forums.swift.org/c/related-projects/vapor) | -| Source Code | Learn how Vapor works under the hood. | [visit →](https://github.com/vapor/vapor) | -| GitHub Issues | Report bugs or request features on GitHub. | [visit →](https://github.com/vapor/vapor/issues) | +| name | description | link | +|----------------|--------------------------------------------------|-------------------------------------------------------------------| +| Vapor Discord | Chat with thousands of Vapor developers. | [visit →](https://vapor.team) | +| API docs | Auto-generated documentation from code comments. | [visit →](https://api.vapor.codes) | +| Stack Overflow | Ask and answer questions with the `vapor` tag. | [visit →](https://stackoverflow.com/questions/tagged/vapor) | +| Swift Forums | Post in Vapor's section of the Swift.org forums. | [visit →](https://forums.swift.org/c/related-projects/vapor) | +| Source Code | Learn how Vapor works under the hood. | [visit →](https://github.com/vapor/vapor) | +| GitHub Issues | Report bugs or request features on GitHub. | [visit →](https://github.com/vapor/vapor/issues) | +## Old Documentation + +Documentation for deprecated versions of Vapor that are now end-of-life can be found at [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/). ## Authors -[Tanner Nelson](mailto:tanner@vapor.codes), [Logan Wright](mailto:logan@vapor.codes), and the hundreds of members of the Vapor community. +The Vapor Core Team, and the hundreds of members of the Vapor community. diff --git a/4.0/docs/index.nl.md b/docs/index.nl.md similarity index 50% rename from 4.0/docs/index.nl.md rename to docs/index.nl.md index 5ea9822e..49e54c79 100644 --- a/4.0/docs/index.nl.md +++ b/docs/index.nl.md @@ -1,11 +1,4 @@ -Dit is de documentatie voor Vapor, een web framework voor Swift. - -Vapor is het meest gebruikte web framework voor Swift. Het biedt een prachtig expressieve en gebruiksvriendelijke basis voor uw volgende website of API. - -## Talen - -- English [(docs.vapor.codes)](https://docs.vapor.codes) -- 简体中文 [(cn.docs.vapor.codes)](https://cn.docs.vapor.codes) +Welkom bij de Vapor Documentatie! Vapor is een web framework voor Swift, dat u toelaat om back-ends, web apps APIs en HTTP servers te schrijven in Swift. Vapor is geschreven in Swift, wat een moderne, krachtige en veilige taal die verscheidene voordelen bied tegenover de meer traditionele server talen. ## Eerste stappen @@ -19,14 +12,18 @@ Hier zijn een paar andere geweldige plaatsen om meer informatie te vinden over V | naam | beschrijving | link | |----------------|--------------------------------------------------|-----------------------------------------------------------------| -| Vapor Discord | Praat met duizenden Vapor developers. | [visit →](https://vapor.team) | -| API docs | Auto-generated documentation from code comments. | [visit →](https://api.vapor.codes) | -| Stack Overflow | Vraag en beantwoord vragen met de `vapor` tag. | [visit →](https://stackoverflow.com/questions/tagged/vapor) | -| Swift Forums | Post in de Vapor sectie van de Swift.org forums. | [visit →](https://forums.swift.org/c/related-projects/vapor) | -| Source Code | Leer hoe Vapor werkt onder de motorkap. | [visit →](https://github.com/vapor/vapor) | -| GitHub Issues | Meld fouten of verzoek functies aan op GitHub. | [visit →](https://github.com/vapor/vapor/issues) | +| Vapor Discord | Praat met duizenden Vapor developers. | [visit →](https://vapor.team) | +| API docs | Auto-generated documentation from code comments. | [visit →](https://api.vapor.codes) | +| Stack Overflow | Vraag en beantwoord vragen met de `vapor` tag. | [visit →](https://stackoverflow.com/questions/tagged/vapor) | +| Swift Forums | Post in de Vapor sectie van de Swift.org forums. | [visit →](https://forums.swift.org/c/related-projects/vapor) | +| Source Code | Leer hoe Vapor werkt onder de motorkap. | [visit →](https://github.com/vapor/vapor) | +| GitHub Issues | Meld fouten of verzoek functies aan op GitHub. | [visit →](https://github.com/vapor/vapor/issues) | +## Oude documentatie + +Documentatie voor verouderde versies van Vapor die nu op hun levenseinde zitten kunnen gevonden worden op [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/). + ## Auteurs -[Tanner Nelson](mailto:tanner@vapor.codes), [Logan Wright](mailto:logan@vapor.codes), en de honderden leden van de Vapor gemeenschap. +Het Vapor Core Team, en de honderden leden van de Vapor gemeenschap. diff --git a/4.0/docs/install/linux.md b/docs/install/linux.md similarity index 100% rename from 4.0/docs/install/linux.md rename to docs/install/linux.md diff --git a/4.0/docs/install/macos.md b/docs/install/macos.md similarity index 100% rename from 4.0/docs/install/macos.md rename to docs/install/macos.md diff --git a/4.0/docs/javascripts/syntax.js b/docs/javascripts/syntax.js similarity index 100% rename from 4.0/docs/javascripts/syntax.js rename to docs/javascripts/syntax.js diff --git a/4.0/docs/jwt.md b/docs/jwt.md similarity index 100% rename from 4.0/docs/jwt.md rename to docs/jwt.md diff --git a/4.0/docs/leaf/custom-tags.md b/docs/leaf/custom-tags.md similarity index 100% rename from 4.0/docs/leaf/custom-tags.md rename to docs/leaf/custom-tags.md diff --git a/4.0/docs/leaf/getting-started.md b/docs/leaf/getting-started.md similarity index 95% rename from 4.0/docs/leaf/getting-started.md rename to docs/leaf/getting-started.md index 7c069801..d71503b6 100644 --- a/4.0/docs/leaf/getting-started.md +++ b/docs/leaf/getting-started.md @@ -45,7 +45,7 @@ This tells Vapor to use the `LeafRenderer` when you call `req.view` in your code Leaf has an internal cache for rendering pages. When the `Application`'s environment is set to `.development`, this cache is disabled, so that changes to templates take effect immediately. In `.production` and all other environments, the cache is enabled by default; any changes made to templates will not take effect until the application is restarted. !!! warning - For Leaf to be able to find the templates when running from Xcode, you must set the [custom working directory](https://docs.vapor.codes/4.0/xcode/#custom-working-directory) for you Xcode workspace. + For Leaf to be able to find the templates when running from Xcode, you must set the [custom working directory](/xcode/#custom-working-directory) for you Xcode workspace. ## Folder Structure diff --git a/4.0/docs/leaf/overview.md b/docs/leaf/overview.md similarity index 100% rename from 4.0/docs/leaf/overview.md rename to docs/leaf/overview.md diff --git a/4.0/docs/logging.md b/docs/logging.md similarity index 100% rename from 4.0/docs/logging.md rename to docs/logging.md diff --git a/4.0/docs/middleware.md b/docs/middleware.md similarity index 100% rename from 4.0/docs/middleware.md rename to docs/middleware.md diff --git a/4.0/docs/passwords.md b/docs/passwords.md similarity index 100% rename from 4.0/docs/passwords.md rename to docs/passwords.md diff --git a/4.0/docs/queues.md b/docs/queues.md similarity index 100% rename from 4.0/docs/queues.md rename to docs/queues.md diff --git a/4.0/docs/redis/overview.md b/docs/redis/overview.md similarity index 100% rename from 4.0/docs/redis/overview.md rename to docs/redis/overview.md diff --git a/4.0/docs/redis/sessions.md b/docs/redis/sessions.md similarity index 100% rename from 4.0/docs/redis/sessions.md rename to docs/redis/sessions.md diff --git a/4.0/docs/routing.md b/docs/routing.md similarity index 99% rename from 4.0/docs/routing.md rename to docs/routing.md index f4bf864c..70daf189 100644 --- a/4.0/docs/routing.md +++ b/docs/routing.md @@ -212,7 +212,7 @@ app.get("hello", ":name") { req -> String in We can be sure that `req.parameters.get` will never return `nil` here since our route path includes `:name`. However, if you are accessing route parameters in middleware or in code triggered by multiple routes, you will want to handle the possibility of `nil`. !!! tip - If you want to retrieve URL query params, e.g. `/hello/?name=foo` you need to use Vapor's Content APIs to handle URL encoded data in the URL's query string. See [`Content` reference](https://docs.vapor.codes/4.0/content/) for more details. + If you want to retrieve URL query params, e.g. `/hello/?name=foo` you need to use Vapor's Content APIs to handle URL encoded data in the URL's query string. See [`Content` reference](/content/) for more details. `req.parameters.get` also supports casting the parameter to `LosslessStringConvertible` types automatically. diff --git a/4.0/docs/server.md b/docs/server.md similarity index 100% rename from 4.0/docs/server.md rename to docs/server.md diff --git a/4.0/docs/services.md b/docs/services.md similarity index 100% rename from 4.0/docs/services.md rename to docs/services.md diff --git a/4.0/docs/sessions.md b/docs/sessions.md similarity index 100% rename from 4.0/docs/sessions.md rename to docs/sessions.md diff --git a/4.0/docs/spm.md b/docs/spm.md similarity index 100% rename from 4.0/docs/spm.md rename to docs/spm.md diff --git a/4.0/docs/stylesheets/fonts.css b/docs/stylesheets/fonts.css similarity index 59% rename from 4.0/docs/stylesheets/fonts.css rename to docs/stylesheets/fonts.css index ac8ffda8..b97ee786 100644 --- a/4.0/docs/stylesheets/fonts.css +++ b/docs/stylesheets/fonts.css @@ -5,40 +5,40 @@ /* ----- Roboto ----- */ @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-Regular.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-Regular.ttf') format("truetype"); } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-Light.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-Light.ttf') format("truetype"); font-weight: 300; font-style: normal; } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-LightItalic.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-LightItalic.ttf') format("truetype"); font-weight: 300; font-style: italic; } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-Italic.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-Italic.ttf') format("truetype"); font-weight: 400; font-style: italic; } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-Bold.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-Bold.ttf') format("truetype"); font-weight: 700; font-style: normal; } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/Roboto-BoldItalic.ttf') format("truetype"); + src: url('/assets/fonts/Roboto-BoldItalic.ttf') format("truetype"); font-weight: 700; font-style: italic; } @@ -46,26 +46,26 @@ /* ----- Roboto Mono ----- */ @font-face { font-family: "Roboto Mono"; - src: url('/4.0/assets/fonts/RobotoMono-Regular.ttf') format("truetype"); + src: url('/assets/fonts/RobotoMono-Regular.ttf') format("truetype"); } @font-face { font-family: "Roboto Mono"; - src: url('/4.0/assets/fonts/RobotoMono-Italic.ttf') format("truetype"); + src: url('/assets/fonts/RobotoMono-Italic.ttf') format("truetype"); font-weight: 400; font-style: italic; } @font-face { font-family: "Roboto Mono"; - src: url('/4.0/assets/fonts/RobotoMono-Bold.ttf') format("truetype"); + src: url('/assets/fonts/RobotoMono-Bold.ttf') format("truetype"); font-weight: 700; font-style: normal; } @font-face { font-family: "Roboto"; - src: url('/4.0/assets/fonts/RobotoMono-BoldItalic.ttf') format("truetype"); + src: url('/assets/fonts/RobotoMono-BoldItalic.ttf') format("truetype"); font-weight: 700; font-style: italic; } diff --git a/4.0/docs/stylesheets/syntax.css b/docs/stylesheets/syntax.css similarity index 100% rename from 4.0/docs/stylesheets/syntax.css rename to docs/stylesheets/syntax.css diff --git a/4.0/docs/testing.md b/docs/testing.md similarity index 100% rename from 4.0/docs/testing.md rename to docs/testing.md diff --git a/4.0/docs/upgrading.md b/docs/upgrading.md similarity index 100% rename from 4.0/docs/upgrading.md rename to docs/upgrading.md diff --git a/4.0/docs/validation.md b/docs/validation.md similarity index 100% rename from 4.0/docs/validation.md rename to docs/validation.md diff --git a/docs/version/legacy-docs.md b/docs/version/legacy-docs.md new file mode 100644 index 00000000..8c9e661c --- /dev/null +++ b/docs/version/legacy-docs.md @@ -0,0 +1,3 @@ +# Redirecting... + + diff --git a/4.0/docs/websockets.md b/docs/websockets.md similarity index 100% rename from 4.0/docs/websockets.md rename to docs/websockets.md diff --git a/4.0/docs/xcode.md b/docs/xcode.md similarity index 100% rename from 4.0/docs/xcode.md rename to docs/xcode.md diff --git a/fixSearchIndex.swift b/fixSearchIndex.swift index de5e9077..fe1b0bea 100755 --- a/fixSearchIndex.swift +++ b/fixSearchIndex.swift @@ -29,7 +29,7 @@ struct SearchIndexDocs: Codable { let title: String } -let searchIndexPath = "site/4.0/search/search_index.json" +let searchIndexPath = "site/search/search_index.json" let fileURL = URL(fileURLWithPath: searchIndexPath) let indexData = try Data(contentsOf: fileURL) diff --git a/4.0/mkdocs.yml b/mkdocs.yml similarity index 93% rename from 4.0/mkdocs.yml rename to mkdocs.yml index 9fadbbb9..f9e37c0d 100644 --- a/4.0/mkdocs.yml +++ b/mkdocs.yml @@ -1,13 +1,13 @@ # Project information site_name: Vapor Docs -site_url: https://docs.vapor.codes/4.0/ +site_url: https://docs.vapor.codes/ site_author: Vapor Community site_description: Vapor 4's documentation (web framework for Swift). # Repository repo_name: Vapor GitHub repo_url: http://github.com/vapor/vapor -edit_uri: https://github.com/vapor/documentation/edit/main/4.0/docs/ +edit_uri: https://github.com/vapor/documentation/edit/main/docs/ # Copyright copyright: "Copyright © Vapor Community" @@ -18,6 +18,7 @@ theme: custom_dir: theme/ language: en locale: en + font: false palette: primary: black @@ -33,7 +34,9 @@ theme: # Customization extra: # Font - font: false + font: + text: Roboto Slab + code: Source Code Pro # Social social: @@ -151,8 +154,5 @@ nav: - Nginx: "deploy/nginx.md" - Docker: "deploy/docker.md" - Version (4.0): - - v1.5: "version/1_5.md" - - v2.0: "version/2_0.md" - - v3.0: "version/3_0.md" - - v4.0: "version/4_0.md" + - Legacy Docs: "version/legacy-docs.md" - Upgrading: "upgrading.md" diff --git a/stack.yml b/stack.yml index 6fe39275..f3b4c902 100644 --- a/stack.yml +++ b/stack.yml @@ -21,14 +21,13 @@ Resources: WebsiteConfiguration: IndexDocument: index.html # Redirect - ErrorDocument: /4.0/404.html + ErrorDocument: /404.html RoutingRules: - RoutingRuleCondition: HttpErrorCodeReturnedEquals: '404' KeyPrefixEquals: / RedirectRule: HostName: !Ref 'DomainName' - ReplaceKeyPrefixWith: 4.0/ WebsiteCloudfront: Type: AWS::CloudFront::Distribution Properties: @@ -46,7 +45,7 @@ Resources: CustomErrorResponses: - ErrorCode: 403 ResponseCode: 404 - ResponsePagePath: /4.0/404.html + ResponsePagePath: /404.html DefaultCacheBehavior: AllowedMethods: - GET diff --git a/4.0/theme/404.html b/theme/404.html similarity index 85% rename from 4.0/theme/404.html rename to theme/404.html index 9654df09..4c422f6b 100644 --- a/4.0/theme/404.html +++ b/theme/404.html @@ -12,7 +12,7 @@ - + @@ -22,10 +22,10 @@ - + - + @@ -33,11 +33,11 @@ - + - + @@ -49,7 +49,7 @@ - + @@ -82,9 +82,9 @@