Merge branch 'main' into translation/add-basic-polish-translation

This commit is contained in:
Gwynne Raskind 2023-11-11 11:53:16 -06:00
commit 17891b5705
No known key found for this signature in database
GPG Key ID: 2BD7A0EA7D137CCA
81 changed files with 7584 additions and 837 deletions

2
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,2 @@
* @0xTim @gwynne
docs/**/*.??.md @vapor/Translators

18
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,18 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
groups:
dependencies:
patterns:
- "*"
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"
groups:
dependencies:
patterns:
- "*"

View File

@ -9,6 +9,7 @@ Languages:
- [ ] Spanish
- [ ] Polish
- [ ] Korean
- [ ] Japanese
Assigned to @vapor/translators - please submit a PR with the relevant updates and check the box once merged.
Assigned to @vapor/translators - please submit a PR with the relevant updates and check the box once merged.
Please ensure you tag your PR with the `translation-update` so it doesn't create a new issue!

View File

@ -1,5 +1,7 @@
name: Build docs and check cloudformation and dead links
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
branches:
@ -11,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
run: pip install -r requirements.txt
- name: Build documentation

View File

@ -1,60 +1,49 @@
name: Build and deploy the Vapor documentation
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- main
jobs:
deploy:
name: Build and deploy
runs-on: ubuntu-latest
permissions: { id-token: write, contents: read }
env: { AWS_PAGER: '' }
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Build the website
run: |
mkdocs build
swift fixSearchIndex.swift
cp googlefc012e5d94cfa05f.html site/googlefc012e5d94cfa05f.html;
swift setUpRedirects.swift
- name: Configure AWS credentials
id: cred
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.DOCS_DEPLOYER_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.DOCS_DEPLOYER_AWS_SECRET_ACCESS_KEY }}
aws-region: 'eu-west-2'
- name: Deploy to AWS Cloudformation
id: clouddeploy
if: steps.cred.outcome == 'success'
uses: aws-actions/aws-cloudformation-github-deploy@v1.0.3
with:
name: vapor-docs-stack
template: stack.yml
no-fail-on-empty-changeset: "1"
parameter-overrides: >-
DomainName=docs.vapor.codes,
S3BucketName=vapor-docs-site,
AcmCertificateArn=${{ secrets.CERTIFICATE_ARN }}
- name: Deploy to S3
id: s3deploy
if: steps.clouddeploy.outcome == 'success'
uses: jakejarvis/s3-sync-action@master
with:
args: --acl public-read --follow-symlinks --delete
env:
AWS_S3_BUCKET: 'vapor-docs-site'
AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOYER_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOYER_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-west-2'
SOURCE_DIR: 'site'
- name: Invalidate CloudFront
uses: awact/cloudfront-action@master
env:
SOURCE_PATH: '/*'
AWS_REGION: 'eu-west-2'
AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOYER_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOYER_AWS_SECRET_ACCESS_KEY }}
DISTRIBUTION_ID: ${{ secrets.DOCS_DISTRIBUTION_ID }}
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dependencies
run: pip install -r requirements.txt
- name: Build the website
run: |
mkdocs build
swift fixSearchIndex.swift
cp googlefc012e5d94cfa05f.html site/googlefc012e5d94cfa05f.html
swift setUpRedirects.swift
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.OIDC_ROLE_ARN }}
aws-region: ${{ vars.OIDC_ROLE_REGION }}
- name: Deploy CloudFormation stack
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: vapor-docs-stack
template: stack.yml
no-fail-on-empty-changeset: '1'
parameter-overrides: >-
DomainName=docs.vapor.codes,
S3BucketName=${{ secrets.DOCS_S3_BUCKET_NAME }},
AcmCertificateArn=${{ secrets.CERTIFICATE_ARN }}
- name: Upload data to S3
run: |
aws s3 sync ./site 's3://${{ secrets.DOCS_S3_BUCKET_NAME }}' --no-progress --acl public-read
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation --distribution-id '${{ secrets.DOCS_DISTRIBUTION_ID }}' --paths '/*'

View File

@ -1,11 +0,0 @@
name: issue-to-project-board-workflow
on:
# Trigger when an issue gets labeled or deleted
issues:
types: [reopened, closed, labeled, unlabeled, assigned, unassigned]
jobs:
update_project_boards:
name: Update project boards
uses: vapor/ci/.github/workflows/update-project-boards-for-issue.yml@reusable-workflows
secrets: inherit

View File

@ -1,17 +0,0 @@
---
title: Translation needed for {{'#'}}{{env.PR_NUMBER}}
---
The docs have been updated in PR {{'#'}}{{env.PR_NUMBER}}. The translations should be updated if required.
Languages:
- [ ] English
- [ ] Chinese
- [ ] German
- [ ] Dutch
- [ ] Italian
- [ ] Spanish
- [ ] Polish
- [ ] Korean
Assigned to @vapor/translators - please submit a PR with the relevant updates and check the box once merged. Please ensure you tag your PR with the `translation-update` so it doesn't create a new issue!

View File

@ -1,26 +0,0 @@
name: Check PR and create issue for translation
on:
push:
branches:
- main
jobs:
create:
name: Create issue
runs-on: ubuntu-latest
steps:
- name: Find PR
uses: 8BitJonny/gh-get-current-pr@2.1.0
id: find-pr
with:
state: all
- uses: actions/checkout@v2
- name: Create issue
if: steps.find-pr.outputs.number && !contains(steps.find-pr.outputs.pr_labels, 'translation-update') && !contains(steps.find-pr.outputs.pr_labels, 'no-translation-needed')
uses: JasonEtco/create-an-issue@v2
env:
PR_NUMBER: ${{ steps.find-pr.outputs.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
filename: .github/workflows/translation-issue-template.md

437
LICENSE Normal file
View File

@ -0,0 +1,437 @@
Attribution-NonCommercial-ShareAlike 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License
("Public License"). To the extent this Public License may be
interpreted as a contract, You are granted the Licensed Rights in
consideration of Your acceptance of these terms and conditions, and the
Licensor grants You such rights in consideration of benefits the
Licensor receives from making the Licensed Material available under
these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License.
d. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
e. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
f. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike.
h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
i. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
k. NonCommercial means not primarily intended for or directed towards
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and
b. produce, reproduce, and Share Adapted Material for
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. Additional offer from the Licensor -- Adapted Material.
Every recipient of Adapted Material from You
automatically receives an offer from the Licensor to
exercise the Licensed Rights in the Adapted Material
under the conditions of the Adapter's License You apply.
c. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
b. ShareAlike.
In addition to the conditions in Section 3(a), if You Share
Adapted Material You produce, the following conditions also apply.
1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition
in any reasonable manner based on the medium, means, and
context in which You Share Adapted Material.
3. You may not offer or impose any additional or different terms
or conditions on, or apply any Effective Technological
Measures to, Adapted Material that restrict exercise of the
rights granted under the Adapter's License You apply.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes
only;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material,
including for purposes of Section 3(b); and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.

View File

@ -71,5 +71,9 @@ You can check it out by running `mkdocs serve` in the terminal. Once you are sat
Finally, you should add the new language to the [issue template](https://github.com/vapor/docs/blob/main/.github/translation_needed.description.leaf) to ensure that any future changes are applied to the new translation and to the [search index script](https://github.com/vapor/docs/blob/main/fixSearchIndex.swift) to ensure search works correctly.
## Licensing
<p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/">
Except where otherwise noted, <a property="dct:title" rel="cc:attributionURL" href="https://github.com/vapor/docs">Vapor Documentation</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://vapor.codes">Vapor</a> is licensed under <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="license noopener noreferrer">CC BY-NC-SA 4.0 <img style="height: 16px;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg"> <img style="height: 16px" src="https://mirrors.creativecommons.org/presskit/icons/by.svg"> <img style="height: 16px" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg"> <img style="height: 16px;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg"></a>
</p>

147
docs/advanced/apns.it.md Normal file
View File

@ -0,0 +1,147 @@
# APNS
Il servizio di notifiche push di Apple (APNS) consente di inviare notifiche push a dispositivi iOS, macOS, tvOS e watchOS. Questo pacchetto fornisce un client per inviare notifiche push ad APNS da Vapor. È basato su [APNSwift](https://github.com/swift-server-community/APNSwift).
## Inizio
Vediamo come iniziare ad usare APNS.
### Pacchetto
Il primo passo per usare APNS è aggiungere il pacchetto alle dipendenze.
```swift
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "my-app",
dependencies: [
// Altre dipendenze...
.package(url: "https://github.com/vapor/apns.git", from: "5.0.0"),
],
targets: [
.target(name: "App", dependencies: [
// Altre dipendenze...
.product(name: "APNS", package: "apns")
]),
// Altri target...
]
)
```
Se il manifesto viene modificato direttamente in Xcode, esso rileverà automaticamente le modifiche e scaricherà la nuova dipendenza quando il file viene salvato. Altrimenti, da Terminale, basta eseguire `swift package resolve` per scaricare la nuova dipendenza.
### Configurazione
Il modulo APNS aggiunge una nuova proprietà `apns` ad `Application`. Per inviare notifiche push, è necessario impostare le proprie credenziali sulla proprietà `configuration`.
```swift
import APNS
// Configurazione di APNS utilizzando l'autenticazione tramite JWT.
let apnsConfig = APNSClientConfiguration(
authenticationMethod: .jwt(
privateKey: try .loadFrom(filePath: "<#path to .p8#>")!,
keyIdentifier: "<#key identifier#>",
teamIdentifier: "<#team identifier#>"
),
environment: .sandbox
)
app.apns.containers.use(
apnsConfig,
eventLoopGroupProvider: .shared(app.eventLoopGroup),
responseDecoder: JSONDecoder(),
requestEncoder: JSONEncoder(),
backgroundActivityLogger: app.logger,
as: .default
)
```
I segnaposto dell'esempio devono essere sostituiti con le credenziali.
Questo esempio utilizza [l'autenticazione JWT](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/) con la chiave `.p8`, ottenibile dal portale per sviluppatori di Apple. Per l'autenticazione [TLS](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/), si può utilizzare il metodo di autenticazione `.tls`:
```swift
authenticationMethod: .tls(
privateKeyPath: <#path to private key#>,
pemPath: <#path to pem file#>,
pemPassword: <#optional pem password#>
)
```
### Invio
Una volta configurato APNS, è possibile inviare notifiche push tramite il metodo `apns.send` presente su `Application` e `Request`.
```swift
// Payload Codable personalizzato
struct Payload: Codable {
let acme1: String
let acme2: String
}
// Creazione di un Alert per la notifica push
let dt = "70075697aa918ebddd64efb165f5b9cb92ce095f1c4c76d995b384c623a258bb"
let payload = Payload(acme1: "hey", acme2: 2)
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
// Send the notification
try! await req.apns.client.sendAlertNotification(
alert,
deviceToken: dt,
deadline: .distantFuture
)
```
Dall'interno di un route handler si può utilizzare `req.apns`.
```swift
// Invia una notifica push.
app.get("test-push") { req async throws -> HTTPStatus in
try await req.apns.send(..., to: ...)
return .ok
}
```
Il primo parametro contiene la notifica push mentre il secondo parametro contiene il token del dispositivo a cui inviare la notifica push.
## Alert
`APNSAlertNotification` rappresenta i metadati della notifica push da inviare. [Qui](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) ci sono informazioni più specifiche su ogni proprietà; i nomi delle proprietà seguono lo schema di denominazione uno-a-uno elencato nella documentazione di Apple.
```swift
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
```
Questo tipo può essere passato come parametro al metodo `send`.
### Dati di notifica personalizzati
Apple consente di inviare dati personalizzati in ogni notifica. Per renderlo semplice in APNS basta che i dati da inviare conformino a `Codable`:
```swift
struct Payload: Codable {
let acme1: String
let acme2: String
}
```
## Altre informazioni
Per ulteriori informazioni su come utilizzare APNS, vedere il [README di APNSwift](https://github.com/swift-server-community/APNSwift).

View File

@ -1,6 +1,6 @@
# APNS
Vapor's Apple Push Notification Service (APNS) API makes it easy to authenticate and send push notifications to Apple devices. It's built on top of [APNSwift](https://github.com/kylebrowning/APNSwift).
Vapor's Apple Push Notification Service (APNS) API makes it easy to authenticate and send push notifications to Apple devices. It's built on top of [APNSwift](https://github.com/swift-server-community/APNSwift).
## Getting Started
@ -11,14 +11,14 @@ Let's take a look at how you can get started using APNS.
The first step to using APNS is adding the package to your dependencies.
```swift
// swift-tools-version:5.2
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "my-app",
dependencies: [
// Other dependencies...
.package(url: "https://github.com/vapor/apns.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/apns.git", from: "5.0.0"),
],
targets: [
.target(name: "App", dependencies: [
@ -40,15 +40,22 @@ The APNS module adds a new property `apns` to `Application`. To send push notifi
import APNS
// Configure APNS using JWT authentication.
app.apns.configuration = try .init(
let apnsConfig = APNSClientConfiguration(
authenticationMethod: .jwt(
key: .private(filePath: <#path to .p8#>),
privateKey: try .loadFrom(filePath: "<#path to .p8#>")!,
keyIdentifier: "<#key identifier#>",
teamIdentifier: "<#team identifier#>"
),
topic: "<#topic#>",
environment: .sandbox
)
app.apns.containers.use(
apnsConfig,
eventLoopGroupProvider: .shared(app.eventLoopGroup),
responseDecoder: JSONDecoder(),
requestEncoder: JSONEncoder(),
backgroundActivityLogger: app.logger,
as: .default
)
```
Fill in the placeholders with your credentials. The above example shows [JWT-based auth](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns) using the `.p8` key you get from Apple's developer portal. For [TLS-based auth](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns) with a certificate, use the `.tls` authentication method:
@ -66,16 +73,29 @@ authenticationMethod: .tls(
Once APNS is configured, you can send push notifications using `apns.send` method on `Application` or `Request`.
```swift
// Send a push notification.
try app.apns.send(
.init(title: "Hello", subtitle: "This is a test from vapor/apns"),
to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D"
).wait()
// Or
try await app.apns.send(
.init(title: "Hello", subtitle: "This is a test from vapor/apns"),
to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D"
// Custom Codable Payload
struct Payload: Codable {
let acme1: String
let acme2: Int
}
// Create push notification Alert
let dt = "70075697aa918ebddd64efb165f5b9cb92ce095f1c4c76d995b384c623a258bb"
let payload = Payload(acme1: "hey", acme2: 2)
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
// Send the notification
try! await req.apns.client.sendAlertNotification(
alert,
deviceToken: dt,
deadline: .distantFuture
)
```
@ -83,12 +103,6 @@ Use `req.apns` whenever you are inside of a route handler.
```swift
// Sends a push notification.
app.get("test-push") { req -> EventLoopFuture<HTTPStatus> in
req.apns.send(..., to: ...)
.map { .ok }
}
// Or
app.get("test-push") { req async throws -> HTTPStatus in
try await req.apns.send(..., to: ...)
return .ok
@ -99,50 +113,36 @@ The first parameter accepts the push notification alert and the second parameter
## Alert
`APNSwiftAlert` is the actual metadata of the push notification alert to send. More details on the specifics of each property are provided [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). They follow a one-to-one naming scheme listed in Apple's documentation
`APNSAlertNotification` is the actual metadata of the push notification alert to send. More details on the specifics of each property are provided [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). They follow a one-to-one naming scheme listed in Apple's documentation
```swift
let alert = APNSwiftAlert(
title: "Hey There",
subtitle: "Full moon sighting",
body: "There was a full moon last night did you see it"
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
```
This type can be passed directly to the `send` method and it will be wrapped in an `APNSwiftPayload` automatically.
This type can be passed directly to the `send` method.
### Payload
`APNSwiftPayload` is the metadata of the push notification. Things like the alert, badge count. More details on the specifics of each property are provided [here](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html). They follow a one-to-one naming scheme listed in Apple's documentation
```swift
let alert = ...
let aps = APNSwiftPayload(alert: alert, badge: 1, sound: .normal("cow.wav"))
```
This can be passed to the `send` method.
### Custom Notification Data
Apple provides engineers with the ability to add custom payload data to each notification. In order to facilitate this we have the `APNSwiftNotification`.
Apple provides engineers with the ability to add custom payload data to each notification. In order to facilitate this we accept `Codable` conformance to the payload parameter on all `send` apis.
```swift
struct AcmeNotification: APNSwiftNotification {
let acme2: [String]
let aps: APNSwiftPayload
init(acme2: [String], aps: APNSwiftPayload) {
self.acme2 = acme2
self.aps = aps
}
// Custom Codable Payload
struct Payload: Codable {
let acme1: String
let acme2: Int
}
let aps: APNSwiftPayload = ...
let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps)
```
This custom notification type can be passed to the `send` method.
## More Information
For more information on available methods, see [APNSwift's README](https://github.com/kylebrowning/APNSwift).
For more information on available methods, see [APNSwift's README](https://github.com/swift-server-community/APNSwift).

View File

@ -1,6 +1,6 @@
# APNS
在 Vapor 中使用基于 [APNSwift](https://github.com/kylebrowning/APNSwift) 构建的 API可以轻松实现 Apple 推送通知服务(APNS) 的身份验证并将推送通知发送到 Apple 设备。
在 Vapor 中使用基于 [APNSwift](https://github.com/swift-server-community/APNSwift) 构建的 API可以轻松实现 Apple 推送通知服务(APNS) 的身份验证并将推送通知发送到 Apple 设备。
## 入门
@ -11,14 +11,14 @@
使用 APNS 的第一步是将此依赖项添加到你的 Package.swift 文件中。
```swift
// swift-tools-version:5.2
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "my-app",
dependencies: [
// Other dependencies...
.package(url: "https://github.com/vapor/apns.git", from: "3.0.0"),
.package(url: "https://github.com/vapor/apns.git", from: "5.0.0"),
],
targets: [
.target(name: "App", dependencies: [
@ -40,18 +40,29 @@ APNS 模块为 `Application` 添加了一个 `apns` 新属性。要发送推送
import APNS
// 使用 JWT 认证 配置 APNS。
app.apns.configuration = try .init(
let apnsConfig = APNSClientConfiguration(
authenticationMethod: .jwt(
key: .private(filePath: <#path to .p8#>),
privateKey: try .loadFrom(filePath: "<#path to .p8#>")!,
keyIdentifier: "<#key identifier#>",
teamIdentifier: "<#team identifier#>"
),
topic: "<#topic#>",
environment: .sandbox
)
app.apns.containers.use(
apnsConfig,
eventLoopGroupProvider: .shared(app.eventLoopGroup),
responseDecoder: JSONDecoder(),
requestEncoder: JSONEncoder(),
backgroundActivityLogger: app.logger,
as: .default
)
```
在占位符中填写你的凭据。上面的示例显示了[基于 JWT 的认证](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns)方式,使用从 Apple 开发者官网获得的 `.p8` 密钥。对于[基于 TLS 的认证](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns),请使用 `.tls` 身份验证方法:
在占位符中填写你的凭证。
上面的示例显示了[基于 JWT 的认证](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token-based_connection_to_apns)方式,使用从 Apple 开发者官网获得的 `.p8` 密钥。
对于[基于 TLS 的认证](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_certificate-based_connection_to_apns),请使用 `.tls` 身份验证方法:
```swift
authenticationMethod: .tls(
@ -61,88 +72,80 @@ authenticationMethod: .tls(
)
```
### 发送
### 发送通知
配置 APNS 后,你可以使用 `apns.send` 方法在 `Application``Request` 中发送推送通知。
配置 APNS 后,你可以使用 `apns.client.sendAlertNotification` 方法在 `Application``Request` 中发送通知。
```swift
// 发送一条推送。
try app.apns.send(
.init(title: "Hello", subtitle: "This is a test from vapor/apns"),
to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D"
).wait()
// 或者
try await app.apns.send(
.init(title: "Hello", subtitle: "This is a test from vapor/apns"),
to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D"
// 自定义遵循 `Codable` 协议的 Payload
struct Payload: Codable {
let acme1: String
let acme2: Int
}
// 创建推送
let dt = "70075697aa918ebddd64efb165f5b9cb92ce095f1c4c76d995b384c623a258bb"
let payload = Payload(acme1: "hey", acme2: 2)
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
// 发送通知
try! await req.apns.client.sendAlertNotification(
alert,
deviceToken: dt,
deadline: .distantFuture
)
```
每当你在路由内部处理时,使用 `req.apns` 发送推送通知。
当你在路由内部处理时,使用 `req.apns` 发送推送通知。
```swift
// 发送推送通知
app.get("test-push") { req -> EventLoopFuture<HTTPStatus> in
req.apns.send(..., to: ...)
.map { .ok }
}
// 或者
// 发送通知
app.get("test-push") { req async throws -> HTTPStatus in
try await req.apns.send(..., to: ...)
return .ok
}
```
第一个参数接受推送通知的数据,第二个参数是目标设备令牌
第一个参数接受推送通知的数据,第二个参数是目标设备的 token
## Alert
`APNSwiftAlert` 是要发送的推送通知的实际元数据。[此处](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html)提供了每个属性的详细信息。它们遵循 Apple 文档中列出的一对一命名方案。
`APNSAlertNotification` 是要发送的推送通知的实际元数据。[此处](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html)提供了每个属性的详细信息。它们遵循 Apple 文档中列出的一对一命名方案。
```swift
let alert = APNSwiftAlert(
title: "Hey There",
subtitle: "Full moon sighting",
body: "There was a full moon last night did you see it"
let alert = APNSAlertNotification(
alert: .init(
title: .raw("Hello"),
subtitle: .raw("This is a test from vapor/apns")
),
expiration: .immediately,
priority: .immediately,
topic: "<#my topic#>",
payload: payload
)
```
此类型可以直接传递给 `send` 方法,它将自动包装在 `APNSwiftPayload` 中。
### Payload
`APNSwiftPayload` 是推送通知的元数据。诸如推送弹窗,徽章数之类的东西。[此处](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html)提供了每个属性的详细信息。它们遵循 Apple 文档中列出的一对一命名方案。
```swift
let alert = ...
let aps = APNSwiftPayload(alert: alert, badge: 1, sound: .normal("cow.wav"))
```
这可以传递给 `send` 方法。
此类型可以直接传递给 `send` 方法。
### 自定义通知数据
Apple 为工程师提供了为每个通知添加定制有效载荷数据的能力。为了方便操作,我们有了 `APNSwiftNotification`
Apple 为工程师提供了向每个通知添加自定义 Payload 的能力,为了方便使用,对于 `send` api我们接受所有遵循 `Codable` 协议的 Payload。
```swift
struct AcmeNotification: APNSwiftNotification {
let acme2: [String]
let aps: APNSwiftPayload
init(acme2: [String], aps: APNSwiftPayload) {
self.acme2 = acme2
self.aps = aps
}
}
let aps: APNSwiftPayload = ...
let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps)
// 自定义遵循 `Codable` 协议的 Payload
struct Payload: Codable {
let acme1: String
let acme2: Int
```
可将此自定义通知类型传递给该 `send` 方法。
## 更多信息
了解更多可用方法的信息,请参阅 [APNSwift](https://github.com/kylebrowning/APNSwift)。
了解更多可用方法的信息,请参阅 [APNSwift](https://github.com/swift-server-community/APNSwift)。

View File

@ -0,0 +1,65 @@
# Request
L'oggetto [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) è passato come parametro ad ogni [route handler](../basics/routing.md).
```swift
app.get("hello", ":name") { req -> String in
let name = req.parameters.get("name")!
return "Hello, \(name)!"
}
```
È la finestra principale per il resto delle funzionalità di Vapor. Contiene API per il [corpo della richiesta](../basics/content.md), i [parametri della query](../basics/content.md#query), il [logger](../basics/logging.md), il [client HTTP](../basics/client.md), l'[Authenticator](../security/authentication.md) e altro ancora. Accedere a questa funzionalità tramite la richiesta mantiene la computazione sul corretto event loop e consente di simulare il comportamento per i test. È anche possibile aggiungere i propri [servizi](../advanced/services.md) alla `Request` con le estensioni.
La documentazione API completa per `Request` può essere trovata [qui](https://api.vapor.codes/vapor/documentation/vapor/request).
## Application
La proprietà `Request.application` contiene un riferimento all'oggetto [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application). Questo oggetto contiene tutta la configurazione e il funzionamento principale dell'applicazione. La maggior parte di essa dovrebbe essere impostata in `configure.swift` prima che l'applicazione parta completamente, e molte delle API a basso livello non saranno necessarie nella maggior parte delle applicazioni. Una delle proprietà più utili è `Application.eventLoopGroup`, che può essere utilizzata per ottenere un `EventLoop` per i processi che ne hanno bisogno tramite il metodo `any()`. Contiene anche l'[Environment](../basics/environment.md).
## Body
Se si desidera accedere direttamente al corpo della richiesta come `ByteBuffer`, è possibile utilizzare `Request.body.data`. Esso può essere utilizzato per lo streaming dei dati dal corpo della richiesta a un file (anche se è meglio utilizzare la proprietà [`fileio`](../advanced/files.md) della richiesta) o a un altro client HTTP.
## Cookies
Anche se l'utilizzo più utile dei cookie è tramite le [sessioni](../advanced/sessions.md#configuration) integrate, è anche possibile accedere ai cookie direttamente tramite `Request.cookies`.
```swift
app.get("my-cookie") { req -> String in
guard let cookie = req.cookies["my-cookie"] else {
throw Abort(.badRequest)
}
if let expiration = cookie.expires, expiration < Date() {
throw Abort(.badRequest)
}
return cookie.string
}
```
## Headers
Tramite `Request.headers` si può accedere ad un oggetto `HTTPHeaders`: esso contiene tutti gli header che sono state inviate inviati con la richiesta. Può, per esempio, essere utilizzato per accedere all'intestazione `Content-Type`.
```swift
app.get("json") { req -> String in
guard let contentType = req.headers.contentType, contentType == .json else {
throw Abort(.badRequest)
}
return "JSON"
}
```
Si può vedere la documentazione completa per `HTTPHeaders` [qui](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niohttp1/httpheaders). Vapor implementa anche diverse estensioni a `HTTPHeaders` per semplificare il lavoro con gli header più comunemente utilizzati; un elenco è disponibile [qui](https://api.vapor.codes/vapor/documentation/vapor/niohttp1/httpheaders#instance-properties).
## Indirizzo IP
Si può accedere al `SocketAddress` che rappresenta il client tramite `Request.remoteAddress`, che può essere utile per il logging o il rate limiting utilizzando la rappresentazione stringa `Request.remoteAddress.ipAddress`. Potrebbe non rappresentare accuratamente l'indirizzo IP del client se l'applicazione è dietro un proxy inverso.
```swift
app.get("ip") { req -> String in
return req.remoteAddress.ipAddress
}
```
Si può vedere la documentazione completa per `SocketAddress` [qui](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niocore/socketaddress).

66
docs/advanced/request.md Normal file
View File

@ -0,0 +1,66 @@
# Request
The [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) object is passed into every [route handler](../basics/routing.md).
```swift
app.get("hello", ":name") { req -> String in
let name = req.parameters.get("name")!
return "Hello, \(name)!"
}
```
It is the main window into the rest of Vapor's functionality. It contains APIs for the [request body](../basics/content.md), [query parameters](../basics/content.md#query), [logger](../basics/logging.md), [HTTP client](../basics/client.md), [Authenticator](../security/authentication.md), and more. Accessing this functionality through the request keeps computation on the correct event loop and allows it to be mocked for testing. You can even add your own [services](../advanced/services.md) to the `Request` with extensions.
The full API documentation for `Request` can be found [here](https://api.vapor.codes/vapor/documentation/vapor/request).
## Application
The `Request.application` property holds a reference to the [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application). This object contains all of the configuration and core functionality for the application. Most of it should only be set in `configure.swift`, before the application fully starts, and many of the lower level APIs won't be needed in most applications. One of the most useful properties is `Application.eventLoopGroup`, which can be used to get an `EventLoop` for processes that need a new one via the `any()` method. It also contains the [`Environment`](../basics/environment.md).
## Body
If you want direct access to the request body as a `ByteBuffer`, you can use `Request.body.data`. This can be used for streaming data from the request body to a file (though you should use the [`fileio`](../advanced/files.md) property on the request for this instead) or to another HTTP client.
## Cookies
While the most useful application of cookies is via built-in [sessions](../advanced/sessions.md#configuration), you can also access cookies directly via `Request.cookies`.
```swift
app.get("my-cookie") { req -> String in
guard let cookie = req.cookies["my-cookie"] else {
throw Abort(.badRequest)
}
if let expiration = cookie.expires, expiration < Date() {
throw Abort(.badRequest)
}
return cookie.string
}
```
## Headers
An `HTTPHeaders` object can be accessed at `Request.headers`. This contains all of the headers sent with the request. It can be used to access the `Content-Type` header, for example.
```swift
app.get("json") { req -> String in
guard let contentType = req.headers.contentType, contentType == .json else {
throw Abort(.badRequest)
}
return "JSON"
}
```
See further documentation for `HTTPHeaders` [here](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niohttp1/httpheaders). Vapor also adds several extensions to `HTTPHeaders` to make working with the most commonly-used headers easier; a list is available [here](https://api.vapor.codes/vapor/documentation/vapor/niohttp1/httpheaders#instance-properties)
## IP Address
The `SocketAddress` representing the client can be accessed via `Request.remoteAddress`, which may be useful for logging or rate limiting using the string representation `Request.remoteAddress.ipAddress`. It may not accurately represent the client's IP address if the application is behind a reverse proxy.
```swift
app.get("ip") { req -> String in
return req.remoteAddress.ipAddress
}
```
See further documentation for `SocketAddress` [here](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niocore/socketaddress).

View File

@ -0,0 +1,69 @@
# Request
[`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 对象被传递到每一个[路由处理程序](../basics/routing.md)中.
```swift
app.get("hello", ":name") { req -> String in
let name = req.parameters.get("name")!
return "Hello, \(name)!"
}
```
它是进入 Vapor 的主要窗口。包含了用于 [请求体](../basics/content.md)[查询参数](../basics/content.md#query)[日志记录器](../basics/logging.md)[HTTP 客户端](../basics/client.md)[认证器](../security/authentication.md), 等的 API。 通过请求访问这些功能可以将计算保持在正确的事件循环中,并允许在测试中进行模拟。你甚至可以通过扩展将自己的[服务](../advanced/services.md)添加到 `Request` 中。
完整的 `Request` API 文档可以在[这里](https://api.vapor.codes/vapor/documentation/vapor/request)找到。
## Application
`Request.application` 属性持有对 [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 的引用。 这个对象包含了应用程序的所有配置和核心功能。大部分配置应该只在 `configure.swift` 中设置,在应用程序完全启动之前进行,许多较低级别的 API 在大多数应用程序中都不会被使用。其中最有用的属性之一 `Application.eventLoopGroup`,它可以通过 `any()` 方法用于需要新的 `EventLoop` 的进程中获取。它还包含了 [`Environment`](../basics/environment.md)。
## Body
如果你想以 `ByteBuffer` 的形式直接访问请求体,可以使用 `Request.body.data`。这可以用于从请求体流式传输数据到文件(尽管在这种情况下应该使用请求的 [fileio](../advanced/files.md) 属性),或者传输到另一个 HTTP 客户端。
## Cookies
虽然最常见的 cookie 应用是通过内置的[会话](../advanced/sessions.md#configuration)进行的,但你也可以通过 `Request.cookies` 直接访问 cookie。
```swift
app.get("my-cookie") { req -> String in
guard let cookie = req.cookies["my-cookie"] else {
throw Abort(.badRequest)
}
if let expiration = cookie.expires, expiration < Date() {
throw Abort(.badRequest)
}
return cookie.string
}
```
## Headers
通过 `Request.headers` 访问一个 `HTTPHeaders` 对象,其中包含了与请求一起发送的所有标头。例如,可以使用它来访问 `Content-Type` 标头。
```swift
app.get("json") { req -> String in
guard let contentType = req.headers.contentType, contentType == .json else {
throw Abort(.badRequest)
}
return "JSON"
}
```
进一步了解 `HTTPHeaders` 的文档,请参阅[此处](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niohttp1/httpheaders)。Vapor 还为 `HTTPHeaders` 添加了几个扩展,以便更轻松地处理最常用的标头;你可以在[此处](https://api.vapor.codes/vapor/documentation/vapor/niohttp1/httpheaders#instance-properties)找到扩展列表。
## IP Address
代表客户端的 `SocketAddress` 可以通过 `Request.remoteAddress` 访问,这可能对于日志记录或使用字符串表示的 `Request.remoteAddress.ipAddress` 进行速率限制很有用。如果应用程序在反向代理后面,则可能无法准确表示客户端的 IP 地址。
```swift
app.get("ip") { req -> String in
return req.remoteAddress.ipAddress
}
```
了解更多 `SocketAddress` 文档,请参阅[此处](https://swiftpackageindex.com/apple/swift-nio/2.56.0/documentation/niocore/socketaddress)。

428
docs/basics/async.es.md Normal file
View File

@ -0,0 +1,428 @@
# Asincronía
## Async Await
Swift 5.5 introdujo la concurrencia en el lenguaje en forma de `async`/`await`. Esto proporciona una forma de primer nivel de manejar código asincrónico en aplicaciones Swift y Vapor.
Vapor está construido sobre [SwiftNIO](https://github.com/apple/swift-nio.git), que proporciona tipos primitivos para programación asincrónica de bajo nivel. Estos se usaron (y todavía se usan) dentro de Vapor antes de que llegara `async`/`await`. Sin embargo, la mayoría del código ahora se puede escribir usando `async`/`await` en lugar de usar `EventLoopFuture`s. Esto simplificará nuestro código y hará que sea mucho más fácil pensar el proyecto.
La mayoría de las API de Vapor ahora ofrecen versiones `EventLoopFuture` y `async`/`await` para que elijas cuál es mejor. En general, solo debes usar un modelo de programación por controlador de ruta y no mezclar ni combinar en el código. Para aplicaciones que necesitan control explícito sobre bucles de eventos, o aplicaciones de muy alto rendimiento, debes continuar usando `EventLoopFuture`s hasta que se implementen ejecutores personalizados. Para todos los demás, debes usar `async`/`await` ya que los beneficios de legibilidad y mantenibilidad superan con creces cualquier pequeña penalización de rendimiento.
### Migrar a async/await
Hay algunos pasos necesarios para migrar a async/await. Para empezar, si usas macOS, debe tener macOS 12 Monterey o superior y Xcode 13.1 o superior. Para otras plataformas, debes ejecutar Swift 5.5 o superior. A continuación, asegúrate de haber actualizado todas tus dependencias.
En tu Package.swift, configura la versión de las herramientas en 5.5 en la parte superior del archivo:
```swift
// swift-tools-version:5.5
import PackageDescription
// ...
```
A continuación, configura la versión de la plataforma en macOS 12:
```swift
platforms: [
.macOS(.v12)
],
```
Finalmente actualiza el target `Run` para marcarlo como un destino ejecutable:
```swift
.executableTarget(name: "Run", dependencies: [.target(name: "App")]),
```
Nota: si estás implementando en Linux, asegúrate de actualizar la versión de Swift allí también, ej. en Heroku o en su Dockerfile. Por ejemplo, su Dockerfile cambiaría a:
```diff
-FROM swift:5.2-focal as build
+FROM swift:5.5-focal as build
...
-FROM swift:5.2-focal-slim
+FROM swift:5.5-focal-slim
```
Ahora puedes migrar el código existente. Generalmente las funciones que devuelven `EventLoopFuture`s ahora son `async`. Por ejemplo:
```swift
routes.get("firstUser") { req -> EventLoopFuture<String> in
User.query(on: req.db).first().unwrap(or: Abort(.notFound)).flatMap { user in
user.lastAccessed = Date()
return user.update(on: req.db).map {
return user.name
}
}
}
```
Ahora se convierte en:
```swift
routes.get("firstUser") { req async throws -> String in
guard let user = try await User.query(on: req.db).first() else {
throw Abort(.notFound)
}
user.lastAccessed = Date()
try await user.update(on: req.db)
return user.name
}
```
### APIs antiguas y nuevas
Si encuentras APIs que aún no ofrecen una versión `async`/`await`, puedes llamar a `.get()` en una función que devuelve un `EventLoopFuture` para convertirla.
Ejemplo:
```swift
return someMethodCallThatReturnsAFuture().flatMap { futureResult in
// usar futureResult
}
```
Puede convertirse en
```swift
let futureResult = try await someMethodThatReturnsAFuture().get()
```
Si necesitas ir al revés, puedes convertir
```swift
let myString = try await someAsyncFunctionThatGetsAString()
```
en
```swift
let promise = request.eventLoop.makePromise(of: String.self)
promise.completeWithTask {
try await someAsyncFunctionThatGetsAString()
}
let futureString: EventLoopFuture<String> = promise.futureResult
```
## `EventLoopFuture`s
Es posible que hayas notado que algunas API en Vapor esperan o devuelven un tipo genérico de `EventLoopFuture`. Si es la primera vez que oyes hablar de futuros, al principio puede parecerte un poco confuso. Pero no te preocupes, esta guía te mostrará cómo aprovechar sus potentes APIs.
Las promesas y los futuros son tipos relacionados, pero distintos. Las promesas se utilizan para _crear_ futuros. La mayor parte del tiempo, trabajarás con futuros devueltos por las APIs de Vapor y no tendrás que preocuparte por crear promesas.
|tipo|descripción|mutabilidad|
|-|-|-|
|`EventLoopFuture`|Referencia a un valor que puede no estar disponible todavía.|read-only|
|`EventLoopPromise`|Una promesa de proporcionar algún valor de forma asincrónica.|read/write|
Los futuros son una alternativa a las APIs asincrónicas basadas en callbacks. Los futuros pueden encadenarse y transformarse de maneras que los simples closures no pueden lograr.
## Transformando
Al igual que los opcionales y arrays en Swift, los futuros se pueden utilizar con map y flat-map. Estas son las operaciones más comunes que realizarás con futuros.
|método|argumento|descripción|
|-|-|-|
|[`map`](#map)|`(T) -> U`|Asigna un valor futuro a un valor diferente.|
|[`flatMapThrowing`](#flatmapthrowing)|`(T) throws -> U`|Asigna un valor futuro a un valor diferente o a un error.|
|[`flatMap`](#flatmap)|`(T) -> EventLoopFuture<U>`|Asigna un valor futuro a un valor _futuro_ diferente.|
|[`transform`](#transform)|`U`|Asigna un futuro a un valor ya disponible.|
Si observas las firmas de los métodos para `map` y `flatMap` en `Optional<T>` y `Array<T>`, verás que son muy similares a los métodos disponibles en `EventLoopFuture<T>`.
### map
El método `map` te permite transformar el valor del futuro en otro valor. Debido a que es posible que el valor futuro aún no esté disponible (puede ser el resultado de una tarea asincrónica), debemos proporcionar un closure para aceptar el valor.
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
/// Asigna la cadena futura a un número entero
let futureInt = futureString.map { string in
print(string) // La cadena de futuro
return Int(string) ?? 0
}
/// Ahora tenemos un futuro entero
print(futureInt) // EventLoopFuture<Int>
```
### flatMapThrowing
El método `flatMapThrowing` te permite transformar el valor del futuro a otro valor _o_ generar un error.
!!! info "Información"
Debido a que para generar un error debes crear un nuevo futuro internamente, este método tiene el prefijo `flatMap` aunque el closure no acepte un retorno futuro.
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
/// Asigna la cadena futura a un número entero
let futureInt = futureString.flatMapThrowing { string in
print(string) // La cadena de futuro
// Convierta la cadena a un número entero o arroje un error
guard let int = Int(string) else {
throw Abort(...)
}
return int
}
/// Ahora tenemos un futuro entero
print(futureInt) // EventLoopFuture<Int>
```
### flatMap
El método `flatMap` te permite transformar el valor futuro en otro valor futuro. Recibe el nombre de mapa "flat" porque es lo que le permite evitar la creación de futuros anidados (por ejemplo, `EventLoopFuture<EventLoopFuture<T>>`). En otras palabras, te ayuda a mantener tus genéricos planos (flat).
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
/// Supongamos que hemos creado un cliente HTTP
let client: Client = ...
/// Transformamos la cadena de futuro con flatMap a una respuesta de futuro
let futureResponse = futureString.flatMap { string in
client.get(string) // EventLoopFuture<ClientResponse>
}
/// Ahora tenemos una respuesta de futuro
print(futureResponse) // EventLoopFuture<ClientResponse>
```
!!! info "Información"
Si usáramos `map` en el ejemplo anterior, habríamos terminado con: `EventLoopFuture<EventLoopFuture<ClientResponse>>`.
Para llamar a un método de throws dentro de un `flatMap`, usa las palabras clave `do` / `catch` de Swift y crea un [futuro completo](#makefuture).
```swift
/// Supongamos cadena y cliente futuros del ejemplo anterior.
let futureResponse = futureString.flatMap { string in
let url: URL
do {
// Algún método sincrónico con throws.
url = try convertToURL(string)
} catch {
// Utiliza el bucle de eventos para crear un futuro precompletado.
return eventLoop.makeFailedFuture(error)
}
return client.get(url) // EventLoopFuture<ClientResponse>
}
```
### transform
El método `transform` te permite modificar el valor de un futuro, ignorando el valor existente. Esto es especialmente útil para transformar los resultados de `EventLoopFuture<Void>` donde el valor real del futuro no es importante.
!!! tip "Consejo"
`EventLoopFuture<Void>`, a veces llamado señal o signal, es un futuro cuyo único propósito es notificarle sobre la finalización o falla de alguna operación asíncrona.
```swift
/// Supongamos que recuperamos un futuro vacío de alguna API
let userDidSave: EventLoopFuture<Void> = ...
/// Transforma el futuro vacío a un estado HTTP
let futureStatus = userDidSave.transform(to: HTTPStatus.ok)
print(futureStatus) // EventLoopFuture<HTTPStatus>
```
Aunque hayamos proporcionado un valor ya disponible para `transform`, esto sigue siendo una _transformación_. El futuro no se completará hasta que todos los futuros anteriores se hayan completado (o hayan fallado).
### Encadenar
Lo bueno de las transformaciones de futuros es que pueden encadenarse. Esto te permite expresar muchas conversiones y subtareas fácilmente.
Modifiquemos los ejemplos anteriores para ver cómo podemos aprovechar el encadenamiento.
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
/// Supongamos que hemos creado un cliente HTTP
let client: Client = ...
/// Transforma la cadena en una URL y luego en una respuesta
let futureResponse = futureString.flatMapThrowing { string in
guard let url = URL(string: string) else {
throw Abort(.badRequest, reason: "Invalid URL string: \(string)")
}
return url
}.flatMap { url in
client.get(url)
}
print(futureResponse) // EventLoopFuture<ClientResponse>
```
Después de la llamada inicial a map, se crea un `EventLoopFuture<URL>` temporal. Este futuro se asigna inmediatamente a un `EventLoopFuture<Response>`
## Futuro
Echemos un vistazo a algunos otros métodos para usar `EventLoopFuture<T>`.
### makeFuture
Puedes utilizar un bucle de eventos para crear un futuro precompletado con el valor o un error.
```swift
// Crear un futuro pre-éxito.
let futureString: EventLoopFuture<String> = eventLoop.makeSucceededFuture("hello")
// Crea un futuro pre-fallido.
let futureString: EventLoopFuture<String> = eventLoop.makeFailedFuture(error)
```
### whenComplete
Puedes usar `whenComplete` para agregar una respuesta de llamada que se ejecutará cuando el futuro tenga éxito o falle.
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
futureString.whenComplete { result in
switch result {
case .success(let string):
print(string) // La cadena del futuro
case .failure(let error):
print(error) // Un Error de Swift
}
}
```
!!! note "Nota"
Puedes agregar tantos callbacks a un futuro como desees.
### Esperar
Puedes utilizar `.wait()` para esperar sincrónicamente a que se complete el futuro. Dado que un futuro puede fracasar, esta función puede lanzar errores (throwing).
```swift
/// Supongamos que recuperamos una cadena futura de alguna API
let futureString: EventLoopFuture<String> = ...
/// Bloquear hasta que la cadena esté lista
let string = try futureString.wait()
print(string) /// String
```
`wait()` solo se puede usar en un hilo en segundo plano o en el hilo principal, por ejemplo, en `configure.swift`. _No_ se puede utilizar en un subproceso de bucle de eventos, es decir, en closures de rutas.
!!! warning "Advertencia"
Intentar llamar a `wait()` en un hilo de bucle de eventos provocará un error de aserción.
## Promesa
La mayoría de las veces, transformarás los futuros devueltos desde llamadas a APIs de Vapor. Sin embargo, en algún momento es posible que necesites crear una promesa propia.
Para crear una promesa, necesitarás acceso a un `EventLoop`. Puedes obtener acceso a un bucle de eventos desde `Application` o `Request` según el contexto.
```swift
let eventLoop: EventLoop
// Crea una nueva promesa para alguna cadena.
let promiseString = eventLoop.makePromise(of: String.self)
print(promiseString) // EventLoopPromise<String>
print(promiseString.futureResult) // EventLoopFuture<String>
// Completa el futuro asociado.
promiseString.succeed("Hello")
// Falla el futuro asociado.
promiseString.fail(...)
```
!!! info "Información"
Una promesa sólo puede cumplirse una vez. Cualquier finalización posterior será ignorada.
Las promesas se pueden completar (`succeed` / `fail`) desde cualquier hilo. Es por eso que las promesas requieren que se inicialice un bucle de eventos. Las promesas garantizan que la acción de finalización regrese a su bucle de eventos para su ejecución.
## Event Loop
Cuando su aplicación arranca, normalmente creará un bucle de eventos (event loop) para cada núcleo de la CPU en la que se está ejecutando. Cada bucle de eventos tiene exactamente un hilo. Si está familiarizado con los bucles de eventos de Node.js, los de Vapor son similares. La principal diferencia es que Vapor puede ejecutar múltiples bucles de eventos en un proceso, ya que Swift admite subprocesos múltiples.
Cada vez que un cliente se conecta a su servidor, será asignado a uno de los bucles de eventos. A partir de ese momento, toda la comunicación entre el servidor y ese cliente ocurrirá en ese mismo bucle de eventos (y por asociación, el hilo de ese bucle de eventos).
El bucle de eventos es responsable de realizar un seguimiento del estado de cada cliente conectado. Si hay una solicitud del cliente esperando ser leída, el bucle de eventos activa una notificación de lectura, lo que provoca que se lean los datos. Una vez que se lea la solicitud, se completarán todos los futuros que estén esperando los datos de esa solicitud.
En los closures de ruta, puedes acceder al bucle de eventos actual mediante `Request`.
```swift
req.eventLoop.makePromise(of: ...)
```
!!! warning "Advertencia"
Vapor espera que los closures de rutas permanezcan en `req.eventLoop`. Si saltas subprocesos, debes garantizar que el acceso a `Request` y la respuesta final futura ocurran en el bucle de eventos de la solicitud.
Fuera de los closures de rutas, puedes obtener uno de los bucles de eventos disponibles a través de `Application`.
```swift
app.eventLoopGroup.next().makePromise(of: ...)
```
### hop
Puedes cambiar el bucle de eventos de un futuro usando `hop`.
```swift
futureString.hop(to: otherEventLoop)
```
## Bloqueos
Llamar a un código de bloqueo en un subproceso de bucle de eventos puede impedir que su aplicación responda a las solicitudes entrantes de manera oportuna. Un ejemplo de una llamada de bloqueo sería algo como `libc.sleep(_:)`.
```swift
app.get("hello") { req in
/// Pone en suspensión el hilo del bucle de eventos.
sleep(5)
/// Devuelve una cadena simple una vez que el hilo se reactiva.
return "Hello, world!"
}
```
`sleep(_:)` es un comando que bloquea el hilo actual durante la cantidad de segundos proporcionados. Si realiza un trabajo de bloqueo como este directamente en un bucle de eventos, el bucle de eventos no podrá responder a ningún otro cliente asignado a él mientras dure el trabajo de bloqueo. En otras palabras, si realiza `sleep(5)` en un bucle de eventos, todos los demás clientes conectados a ese bucle de eventos (posiblemente cientos o miles) se retrasarán durante al menos 5 segundos.
Asegúrate de ejecutar cualquier trabajo de bloqueo en segundo plano. Utiliza promesas para notificar al bucle de eventos cuando este trabajo se realice sin bloqueo.
```swift
app.get("hello") { req -> EventLoopFuture<String> in
/// Enviar algún trabajo para que se realice en un hilo en segundo plano
return req.application.threadPool.runIfActive(eventLoop: req.eventLoop) {
/// Pone el hilo de fondo en suspensión
/// Esto no afectará ninguno de los bucles de eventos
sleep(5)
/// Cuando se haya completado el "trabajo de bloqueo",
/// se devuelve el resultado.
return "Hello world!"
}
}
```
No todas las llamadas de bloqueo serán tan obvias como `sleep(_:)`. Si sospechas que una llamada que estás utilizando puede estar bloqueando, investiga el método o pregúntale a alguien. Las secciones siguientes analizan con más detalle cómo se pueden bloquear los métodos.
### I/O Bound
El bloqueo I/O bound significa esperar en un recurso lento, como una red o un disco duro, que pueden ser órdenes de magnitud más lento que la CPU. Bloquear la CPU mientras espera estos recursos resulta en una pérdida de tiempo.
!!! danger "Peligro"
Nunca realices llamadas I/O bound de bloqueo directamente en un bucle de eventos.
Todos los paquetes de Vapor están construidos en SwiftNIO y utilizan entrada o salida (I/O) sin bloqueo. Sin embargo, existen muchos paquetes de Swift y bibliotecas C que bloquean la entrada o salida. Lo más probable es que si una función realiza entrada o salida de disco o de red y utiliza una API síncrona (sin devoluciones de llamada ni futuros), esté bloqueando.
### CPU Bound
La mayor parte del tiempo durante una solicitud, se dedica a esperar a que se carguen recursos externos, como consultas de bases de datos y solicitudes de red. Debido a que Vapor y SwiftNIO no son bloqueantes, este tiempo de inactividad se puede utilizar para cumplir con otras solicitudes entrantes. Sin embargo, es posible que algunas rutas de su aplicación deban realizar un trabajo pesado vinculado a la CPU como resultado de una solicitud.
Mientras un bucle de eventos procesa el trabajo vinculado a la CPU, no podrás responder a otras solicitudes entrantes. Esto normalmente está bien, ya que las CPU son rápidas y la mayoría del trabajo de la CPU que realizan las aplicaciones web es liviano. Pero esto puede convertirse en un problema si las rutas con un trabajo prolongado de la CPU impiden que se responda rápidamente a las solicitudes de rutas más rápidas.
Identificar el trabajo de CPU de larga duración en su aplicación y moverlo a subprocesos en segundo plano, puede ayudar a mejorar la confiabilidad y la capacidad de respuesta de su servicio. El trabajo vinculado a la CPU es más un área gris que el trabajo vinculado a entrada o salida y, en última instancia, depende de ti determinar dónde deseas trazar la línea.
Un ejemplo común de trabajo pesado vinculado a la CPU es el encriptación (hashing) con Bcrypt durante el registro y el inicio de sesión del usuario. Bcrypt es deliberadamente muy lento y consume mucha CPU por razones de seguridad. Este puede ser el trabajo con mayor uso de CPU que realmente realiza una aplicación web simple. Mover el hash a un subproceso en segundo plano puede permitir que la CPU intercale el trabajo del bucle de eventos mientras calcula los hashes, lo que da como resultado una mayor concurrencia.

76
docs/basics/client.it.md Normal file
View File

@ -0,0 +1,76 @@
# Client
L'API client di Vapor ti permette di fare chiamate HTTP a risorse esterne. Si basa su [async-http-client](https://github.com/swift-server/async-http-client) e si integra con l'API [content](content.md).
## Panoramica
Puoi accedere al client di default attraverso `Application` o in un handler di route attraverso `Request`.
```swift
app.client // Client
app.get("test") { req in
req.client // Client
}
```
Il client dell'applicazione è utile per fare richieste HTTP durante la configurazione. Se fai richieste HTTP in un handler di route, usa sempre il client della richiesta.
### Metodi
Per fare una richiesta `GET`, passa l'URL desiderato al comodo metodo `get`.
```swift
let response = try await req.client.get("https://httpbin.org/status/200")
```
Ci sono metodi per ogni verbo HTTP come `get`, `post`, e `delete`. La risposta del client viene ritornata come future e contiene lo stato, l'header, e il corpo HTTP.
### Contenuto
L'API [content](content.md) di Vapor è disponibile per gestire i dati nelle richieste e nelle risposte del client. Per codificare il contenuto, parametri della query o aggiungere header alla richiesta, usa la closure `beforeSend`.
```swift
let response = try await req.client.post("https://httpbin.org/status/200") { req in
// Codifica la stringa di query all'URL della richiesta.
try req.query.encode(["q": "test"])
// Codifica il JSON per il corpo della richiesta.
try req.content.encode(["hello": "world"])
// Aggiungi l'header di autenticazione alla richiesta
let auth = BasicAuthorization(username: "something", password: "somethingelse")
req.headers.basicAuthorization = auth
}
// Gestisci la risposta.
```
Puoi anche decodificare il corpo della risposta usando `Content` in modo simile:
```swift
let response = try await req.client.get("https://httpbin.org/json")
let json = try response.content.decode(MyJSONResponse.self)
```
Se usi i future puoi usare `flatMapThrowing`:
```swift
return req.client.get("https://httpbin.org/json").flatMapThrowing { res in
try res.content.decode(MyJSONResponse.self)
}.flatMap { json in
// Usa il JSON qui
}
```
## Configurazione
Puoi configurare il client HTTP sottostante tramite l'applicazione.
```swift
// Disabilita il redirect automatico seguente.
app.http.client.configuration.redirectConfiguration = .disallow
```
Nota che devi configurare il client di default _prima_ di usarlo per la prima volta.

View File

@ -76,7 +76,7 @@ struct Profile: Content {
不是所有的媒体类型都支持所有的 `Codable` 协议。例如JSON 不支持顶层片段Plaintext 不支持嵌套数据。
## 查询
## 查询(Query)
Vapor 的 Content API 支持处理 URL 查询字符串中的 URL 编码数据。

View File

@ -1,37 +1,13 @@
# Controller
Mit Controller kannst du deinen Code sinnvoll aufteilen und in deinem Projekt für Ordnung sorgen. Ein Controller kann beispielsweise ein oder mehrere Methoden beinhalten, die Serveranfragen entgegennehmen und ein enstprechendes Ergebnis zurückliefern. Das folgende Beispiel zeigt einen möglichen Aufbau eines solchen Controllers:
Mit Controller können wir unseren Code sinnvoll aufteilen und in unserem Projekt für Ordnung sorgen. Ein Controller kann eine oder mehrere Methoden beinhalten, die Serveranfragen entgegennehmen, verarbeiten und ein entsprechendes Ergebnis zurückliefern. Das folgende Beispiel zeigt einen möglichen Aufbau eines solchen Controllers:
```swift
/// [TodoController.swift]
// [TodosController.swift]
import Vapor
struct TodosController: RouteCollection {
func index(req: Request) async throws -> String {
// ...
}
func create(req: Request) throws -> EventLoopFuture<String> {
// ...
}
func show(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
}
// ...
}
func update(req: Request) throws -> String {
// ...
}
func delete(req: Request) throws -> String {
// ...
}
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("todos")
todos.get(use: index)
@ -43,15 +19,50 @@ struct TodosController: RouteCollection {
todo.delete(use: delete)
}
}
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
return todo
}
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
try await todo.save(on: req.db)
return todo
}
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) {
throw Abort(.notFound)
}
try await todo.delete(on: req.db)
return .ok
}
}
```
Die Methoden sollten immer ein Object vom Typ _Request_ annehmen und ein Wert von Typ _ResponseEncodable_ zurückgegeben. Dabei kann die Methode sowohl asynchron mittels _async/await_ oder _EventLoopFuture_, als auch synchron ausgeführt werden.
Die erwähnten Methoden sollten immer ein Objekt vom Typ _Request_ annehmen und ein Wert von Typ _ResponseEncodable_ zurückgegeben. Dabei kann die Methode sowohl asynchron, als auch synchron ausgeführt werden.
Zum Schluss muss der Controller der Anwendung bekannt gemacht werden. Hierzu wird der Controller mit Hilfe der Methode _register(:_)_ an das Object _app_ übergeben.
Zum Schluss müssen wir der Anwendung unseren Controller bekannt machen. Hierzu wird der Controller mit Hilfe der Methode _register(:_)_ an das Objekt _app_ übergeben.
```swift
/// [routes.swift]
// [routes.swift]
try app.register(collection: TodosController())
```

View File

@ -24,41 +24,44 @@ struct TodosController: RouteCollection {
}
}
func index(req: Request) async throws -> String {
// ...
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
func create(req: Request) throws -> EventLoopFuture<String> {
// ...
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
func show(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
return todo
}
func update(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
try await todo.save(on: req.db)
return todo
}
func delete(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) {
throw Abort(.notFound)
}
// ...
try await todo.delete(on: req.db)
return .ok
}
}
```
Los métodos de un controlador deben aceptar siempre una `Request` (petición) y devolver algo `ResponseEncodable`. Este método puede ser síncrono o asíncrono (o devolver un `EventLoopFuture`).
!!! note "Nota"
Un [EventLoopFuture](async.md) cuya expectativa es `ResponseEncodable` (por ejemplo, `EventLoopFuture<String>`) es también `ResponseEncodable`.
Los métodos de un controlador deben aceptar siempre una `Request` (petición) y devolver algo `ResponseEncodable`. Este método puede ser síncrono o asíncrono.
Finalmente necesitas registrar el controlador en `routes.swift`:

View File

@ -0,0 +1,70 @@
# Controller
I controller sono un ottimo modo per organizzare il codice. Sono collezioni di metodi che prendono una richiesta e ritornano una risposta, e sono a tutti gli effetti gli endpoint dell'applicazione.
Il luogo migliore in cui mettere i controller è nella loro [cartella](../getting-started/folder-structure.it.md#controllers).
## Panoramica
Diamo un'occhiata ad un esempio di controller.
```swift
import Vapor
struct TodosController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("todos")
todos.get(use: index)
todos.post(use: create)
todos.group(":id") { todo in
todo.get(use: show)
todo.put(use: update)
todo.delete(use: delete)
}
}
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
return todo
}
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
try await todo.save(on: req.db)
return todo
}
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) {
throw Abort(.notFound)
}
try await todo.delete(on: req.db)
return .ok
}
}
```
I metodi dei controller prendono sempre in input una `Request` e ritornano un qualsiasi `ResponseEncodable`. Tali metodi possono essere asincroni o sincroni.
Infine, il controller viene registrato nel `routes.swift`:
```swift
try app.register(collection: TodosController())
```

View File

@ -24,41 +24,45 @@ struct TodosController: RouteCollection {
}
}
func index(req: Request) async throws -> String {
// ...
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
func create(req: Request) throws -> EventLoopFuture<String> {
// ...
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
func show(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
return todo
}
func update(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
try await todo.save(on: req.db)
return todo
}
func delete(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) {
throw Abort(.notFound)
}
// ...
try await todo.delete(on: req.db)
return .ok
}
}
```
Controller methods should always accept a `Request` and return something `ResponseEncodable`. This method can be asynchronous or synchronous (or return an `EventLoopFuture`)
Controller methods should always accept a `Request` and return something `ResponseEncodable`. This method can be asynchronous or synchronous.
!!! note
[EventLoopFuture](async.md) whose expectation is `ResponseEncodable` (i.e, `EventLoopFuture<String>`) is also `ResponseEncodable`.
Finally you need to register the controller in `routes.swift`:

View File

@ -25,41 +25,44 @@ struct TodosController: RouteCollection {
}
}
func index(req: Request) async throws -> String {
// ...
func index(req: Request) async throws -> [Todo] {
try await Todo.query(on: req.db).all()
}
func create(req: Request) throws -> EventLoopFuture<String> {
// ...
func create(req: Request) async throws -> Todo {
let todo = try req.content.decode(Todo.self)
try await todo.save(on: req.db)
return todo
}
func show(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func show(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
return todo
}
func update(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func update(req: Request) async throws -> Todo {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// ...
let updatedTodo = try req.content.decode(Todo.self)
todo.title = updatedTodo.title
try await todo.save(on: req.db)
return todo
}
func delete(req: Request) throws -> String {
guard let id = req.parameters.get("id") else {
throw Abort(.internalServerError)
func delete(req: Request) async throws -> HTTPStatus {
guard let todo = try await Todo.find(req.parameters.get("id"), on: req.db) {
throw Abort(.notFound)
}
// ...
try await todo.delete(on: req.db)
return .ok
}
}
```
`Controller` 的方法接受 `Request` 参数,并返回 `ResponseEncodable` 对象。该方法可以是异步或者同步(或者返回一个 `EventLoopFuture`)
!!! note "注意"
[EventLoopFuture](async.md) 期望返回值为 `ResponseEncodable` (i.e, `EventLoopFuture<String>`) 或 `ResponseEncodable`.
`Controller` 的方法接受 `Request` 参数,并返回 `ResponseEncodable` 对象。该方法可以是异步或者同步。
最后,你需要在 `routes.swift` 中注册 Controller

View File

@ -383,7 +383,7 @@ Für eine Weiterleitung kann es verschiedenste Gründe geben. Mit der Methode *r
req.redirect(to: "/some/new/path")
/// redirect a page permanently
req.redirect(to: "/some/new/path", type: .permanent)
req.redirect(to: "/some/new/path", redirectType: .permanent)
```
Es gibt verschiedene Arten von Weiterleitungen:

View File

@ -424,10 +424,10 @@ req.redirect(to: "/some/new/path")
También puedes especificar el tipo de redirección, por ejemplo para redirigir una página de forma permanente (para que su SEO se actualice correctamente) usa:
```swift
req.redirect(to: "/some/new/path", type: .permanent)
req.redirect(to: "/some/new/path", redirectType: .permanent)
```
Los diferentes `RedirectType` son:
Los diferentes `Redirect` son:
* `.permanent` - devuelve una redirección **301 Moved Permanently**
* `.normal` - devuelve una redirección **303 See Other**. Este es el valor por defecto de Vapor y le dice al cliente que siga la redirección con una petición **GET**.

View File

@ -424,10 +424,10 @@ req.redirect(to: "/some/new/path")
You can also specify the type of redirect, for example to redirect a page permanently (so that your SEO is updated correctly) use:
```swift
req.redirect(to: "/some/new/path", type: .permanent)
req.redirect(to: "/some/new/path", redirectType: .permanent)
```
The different `RedirectType`s are:
The different `Redirect`s are:
* `.permanent` - returns a **301 Permanent** redirect
* `.normal` - returns a **303 see other** redirect. This is the default by Vapor and tells the client to follow the redirect with a **GET** request.

View File

@ -423,10 +423,10 @@ req.redirect(to: "/some/new/path")
U kunt ook het type omleiding specificeren, bijvoorbeeld om een pagina permanent om te leiden (zodat uw SEO correct wordt bijgewerkt) gebruiken we:
```swift
req.redirect(to: "/some/new/path", type: .permanent)
req.redirect(to: "/some/new/path", redirectType: .permanent)
```
De verschillende `RedirectType`s zijn:
De verschillende `Redirect`s zijn:
* `.permanent` - geeft een **301 Permanent** omleiding.
* `.normal` - retourneert een **303 see other** redirect. Dit is de standaard door Vapor en vertelt de client om de omleiding te volgen met een **GET** verzoek.

View File

@ -432,10 +432,10 @@ req.redirect(to: "/some/new/path")
你可以设置重定向的类型,比如说永久的重定向一个页面(来使你的 SEO 正确的更新),请使用:
```swift
req.redirect(to: "/some/new/path", type: .permanent)
req.redirect(to: "/some/new/path", redirectType: .permanent)
```
不同的 `RedirectType` 有:
不同的 `Redirect` 有:
* `.permanent` - 返回一个 **301 Permanent** 重定向。
* `.normal` - 返回一个 **303 see other** 重定向。这是 Vapor 的默认行为,来告诉客户端去使用一个 **GET** 请求来重定向。

View File

@ -227,3 +227,66 @@ Validators can also be combined to build complex validations using operators.
|`!`|prefix|Inverts a validator, requiring the opposite.|
|`&&`|infix|Combines two validators, requires both.|
|`||`|infix|Combines two validators, requires one.|
## Custom Validators
Creating a custom validator for zip codes allows you to extend the functionality of the validation framework. In this section, we'll walk you through the steps to create a custom validator for validating zip codes.
First create a new type to represent the `ZipCode` validation results. This struct will be responsible for reporting whether a given string is a valid zip code.
```swift
extension ValidatorResults {
/// Represents the result of a validator that checks if a string is a valid zip code.
public struct ZipCode {
/// Indicates whether the input is a valid zip code.
public let isValidZipCode: Bool
}
}
```
Next, conform the new type to `ValidatorResult`, which defines the behavior expected from a custom validator.
```swift
extension ValidatorResults.ZipCode: ValidatorResult {
public var isFailure: Bool {
!self.isValidZipCode
}
public var successDescription: String? {
"is a valid zip code"
}
public var failureDescription: String? {
"is not a valid zip code"
}
}
```
Finally, implement the validation logic for zip codes. Use a regular expression to check whether the input string matches the format of a USA zip code.
```swift
private let zipCodeRegex: String = "^\\d{5}(?:[-\\s]\\d{4})?$"
extension Validator where T == String {
/// Validates whether a `String` is a valid zip code.
public static var zipCode: Validator<T> {
.init { input in
guard let range = input.range(of: zipCodeRegex, options: [.regularExpression]),
range.lowerBound == input.startIndex && range.upperBound == input.endIndex
else {
return ValidatorResults.ZipCode(isValidZipCode: false)
}
return ValidatorResults.ZipCode(isValidZipCode: true)
}
}
}
```
Now that you've defined the custom `zipCode` validator, you can use it to validate zip codes in your application. Simply add the following line to your validation code:
```swift
validations.add("zipCode", as: String.self, is: .zipCode)
```

View File

@ -0,0 +1,57 @@
# Colaborar con Vapor
Vapor es un proyecto impulsado por la comunidad y las contribuciones de los miembros constituyen una parte importante de su desarrollo. Esta guía te ayudará a comprender el proceso de colaboración y a realizar tus primeros commits en Vapor.
¡Cualquier aporte que hagas es útil! Incluso cosas pequeñas como corregir errores tipográficos marcan una gran diferencia para las personas que usan Vapor.
## Código de Conducta
Vapor ha adoptado el Código de Conducta de Swift, que se puede encontrar en [https://www.swift.org/code-of-conduct/](https://www.swift.org/code-of-conduct/). Se espera que todos los colaboradores sigan este código de conducta.
## ¿En que trabajar?
Decidir en qué trabajar puede ser un gran obstáculo cuando se trata de comenzar con el código abierto. Por lo general, las mejores cosas en las que trabajar son los problemas que encuentres o las funciones que desees. Sin embargo, Vapor tiene algunas cosas útiles que te ayudarán a colaborar.
### Problemas de Seguridad
Si descubres un problema de seguridad y deseas informarlo o ayudar a solucionarlo, **no** plantees un issue ni crees un pull request. Contamos con un proceso separado para problemas de seguridad para garantizar que no expongamos vulnerabilidades hasta que haya una solución disponible. Envía un correo electrónico a security@vapor.codes o [consulta aquí](https://github.com/vapor/.github/blob/main/SECURITY.md) para obtener más detalles.
### Pequeños problemas
Si encuentras un pequeño problema, error o error tipográfico, no dudes en seguir adelante y crear una pull request para solucionarlo. Si soluciona un issue abierto en cualquiera de los repositorios, puedes vincularlo en la pull request en la barra lateral para que se cierre automáticamente cuando se hace el merge.
![GitHub Link Issue](../images/github-link-issue.png)
### Nuevas funcionalidades
Si deseas proponer cambios más importantes, como nuevas funcionalidades o correcciones de errores que cambian cantidades significativas de código, primero abre un issue o publícalo en el canal `#development` en Discord. Esto nos permite discutir el cambio contigo, ponerlo en contexto y ofrecerte sugerencias. ¡No queremos que pierdas el tiempo si una función no encaja con nuestros planes!
### Tableros de Vapor
Si solo quieres contribuir pero no tienes idea de en qué trabajar, ¡eso es fantástico! Vapor tiene un par de tableros que pueden ayudar. Vapor tiene alrededor de 40 repositorios que se desarrollan activamente y revisarlos todos para encontrar algo en lo que trabajar no es práctico, por lo que usamos tableros para organizar dichas tareas.
El primer tablero es el [tablero de good first issue](https://github.com/orgs/vapor/projects/10). Cualquier problema en la organización GitHub de Vapor que esté etiquetado como `good first issue` se agregará al tablero para que lo encuentres. Estos son temas que creemos que serán buenos para que trabajen las personas relativamente nuevas en Vapor, ya que no requieren mucha experiencia con el código.
El segundo tablero es el [tablero de help wanted](https://github.com/orgs/vapor/projects/11). Esto genera issues etiquetados como `help wanted`. Estos son problemas que podrían solucionarse, pero el equipo central actualmente tiene otras prioridades. Estos problemas generalmente requieren un poco más de conocimiento si no están marcados con `good first issue`, ¡pero podrían ser proyectos divertidos en los que trabajar!
### Traducciones
El último ámbito donde las contribuciones son extremadamente valiosas es la documentación. La documentación tiene traducciones para varios idiomas, pero no todas las páginas están traducidas y hay muchos más idiomas que nos gustaría disponer. Si estás interesado en contribuir con nuevos idiomas o actualizaciones, consulta el [README de los docs](https://github.com/vapor/docs#translating) o comunícate en el canal `#documentation` en Discord.
## Proceso de Colaboración
Si nunca has trabajado en un proyecto de código abierto, los pasos para colaborar pueden resultar confusos, pero son bastante simples.
Primero, haz un fork de Vapor o cualquier repositorio en el que quieras trabajar. Puedes hacer esto en la UI de GitHub y GitHub tiene [algunos documentos excelentes](https://docs.github.com/es/get-started/quickstart/fork-a-repo) sobre cómo hacer esto.
Luego puede realizar cambios en tu fork con el proceso habitual de commit y push. Una vez que estés listo para enviar tu solución, puedes crear una PR en el repositorio de Vapor. Nuevamente, GitHub tiene [documentos excelentes](https://docs.github.com/es/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) sobre cómo hacer esto.
## Enviar una Pull Request
Al enviar una pull request, hay varias cosas que debes verificar:
* Todos los tests pasan
* Se agregaron nuevos tests para cualquier comportamiento nuevo o errores corregidos
* Se documentan nuevas APIs públicas. Usamos DocC para documentar nuestra API.
Vapor utiliza la automatización para reducir la cantidad de trabajo necesario para muchas tareas. Para las pull requests, utilizamos [Vapor Bot](https://github.com/VaporBot) para generar actualizaciones cuando se fusiona una pull request. El cuerpo y el título de la pull request se utilizan para generar las notas de la nueva versión, así que asegúrate de que tengan sentido y cubran lo que esperarías ver en las notas de la nueva versión. Tenemos más detalles sobre las [directrices de colaboración en Vapor](https://github.com/vapor/vapor/blob/main/.github/contributing.md#release-title).

View File

@ -0,0 +1,57 @@
# Contribuire a Vapor
Vapor è un progetto gestito dalla community e le contribuzioni dai membri della community formano una parte significante dello sviluppo di Vapor. Questa guida ti aiuterà a capire il processo di contribuzione e aiutarti a fare i tuoi primi commit in Vapor!
Qualsiasi contributo darai sarà utile! Anche le piccole cose come aggiustare errori di battitura fanno la differenza per le persone che usano Vapor.
## Codice di Condotta
Vapor ha adottato il Codice di Condotta di Swift che puoi trovare su [https://www.swift.org/code-of-conduct/](https://www.swift.org/code-of-conduct/). Tutti i contributori devono seguire il codice di condotta.
## Su cosa lavorare
Capire su cosa lavorare può essere un grande ostacolo quando sei agli inizi nell'open source! Di solito le cose migliori su cui lavorare sono problemi che incontri o funzionalità che vuoi. Tuttavia, Vapor ha delle cose utili per aiutarti a contribuire.
### Problemi di sicurezza
Se scopri un problema di sicurezza e vuoi riferirlo o aiutare a risolverlo perfavore **non** creare una issue o una pull request. Abbiamo un processo separato per i problemi di sicurezza per assicurare che non esponiamo vulnerabilità finché una soluzione non è disponibile. Manda una email a security@vapor.codes o [guarda qui](https://github.com/vapor/.github/blob/main/SECURITY.md) per più dettagli.
### Piccoli problemi
Se trovi un piccolo problema, bug o errore di battitura, sentiti libero di procedere e creare una pull request per risolverlo. Se risolve una issue aperta su una qualsiasi delle repo puoi linkarla nella pull request nella barra laterale in modo che la issue venga automaticamente chiusa quando la pull request viene unita.
![GitHub Link Issue](../images/github-link-issue.png)
### Nuove funzionalità
Se vuoi proporre cambiamenti più grandi, come nuove funzionalità o bug fix che cambiano quantità significative di codice, allora per favore apri una issue prima o fai un post nel canale `#development` di Discord. Questo ci permetterà di discutere il cambiamento con te in quanto potrebbe esserci del contesto da aggiungere o delle indicazioni da darti. Non vogliamo farti perdere tempo se una funzionalità non rientra nei nostri piani!
### Bacheca di Vapor
Se vuoi solo contribuire ma non hai idea su cosa lavorare, fantastico! Vapor ha un paio di bacheche che possono aiutarti. Vapor ha circa 40 repository che sono attivamente sviluppate e cercare fra tutte queste per trovare qualcosa su cui lavorare non è fattibile, quindi usiamo le bacheche per aggregarle.
La prima bachecha è la [bacheca delle buone prime issue](https://github.com/orgs/vapor/projects/10). Ogni issue nell'organizzazione GitHub di Vapor che è taggata come `good first issue` sarà aggiunta alla bacheca. Queste sono issue che pensiamo possano essere buone per persone relativamente nuove a Vapor per lavorarci su, in quanto non richiedono molta esperienza sul codice.
La seconda bacheca è la [bacheca del "Cercasi aiuto"](https://github.com/orgs/vapor/projects/11). Questa contiene issue taggate `help wanted`. Queste sono issue che potrebbero essere aggiustate, ma la squadra principale al momento ha altre priorità. Queste issue di solito richiedono un po' più di conoscenza se non sono anche taggate `good first issue`, ma potrebbero essere un progetto divertente su cui lavorare!
### Traduzioni
L'area finale in cui le contribuzioni sono estremamente importanti è la documentazione. La documentazione ha traduzioni per diverse lingue, ma non tutte le pagine sono tradotte e ci sono molte altre lingue che ci piacerebbe supportare! Se sei interessato a contribuire con nuove lingue o aggiornamenti guarda il [README della documentazione](https://github.com/vapor/docs#translating) o fatti sentire nel canale `#documentation` su Discord.
## Processo di Contribuzione
Se non hai mai lavorato ad un progetto open source, i passi per contribuire effettivamente potrebbero sembrare confusionari, ma sono molto semplici.
Primo, forka Vapor o qualsiasi repo sulla quale vuoi lavorare. Puoi farlo nell'interfaccia GitHub e GitHub ha [un'ottima documentazione](https://docs.github.com/en/get-started/quickstart/fork-a-repo) su come farlo.
A questo punto puoi fare cambiamenti nella tua fork con il solito processo di commit e push. Quando sei pronto a presentare i tuoi cambiamenti, puoi creare una PR sulla repo di Vapor. Di nuovo, GitHub ha [un'ottima documentazione](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) su come farlo.
## Presentare una Pull Request
Quando presenti una pull request ci sono delle cose che dovresti controllare:
* Che tutti i test passino
* Che ci siano nuovi test aggiunti per ogni nuovo comportamento o bug fixato
* Che le nuove API pubbliche siano documentate. Usiamo DocC per la documentazione delle nostre API.
Vapor usa automazioni per ridurre il carico di lavoro richiesto per molti compiti. Per le pull requests, usiamo il [Bot Vapor](https://github.com/VaporBot) per generare release quando una pull request è unita. Il corpo e il titolo della pull request sono usati per generare le note sulla versione, quindi assicurati che abbiano senso e mettici quello che ti aspetti di vedere in una nota sulla versione. Ci sono più dettagli sulle [linee guida alla contribuzione di Vapor](https://github.com/vapor/vapor/blob/main/.github/contributing.md#release-title).

View File

@ -71,52 +71,25 @@ ssh vapor@your_server_ip
Now that you've created a new Ubuntu server and logged in as a non-root user you can install Swift.
### Swift Dependencies
### Automated installation using Swiftly CLI tool (recommended)
Install Swift's required dependencies.
Visit the [Swiflty website](https://swift-server.github.io/swiftly/) for instructions on how to install Swiftly and Swift on Linux. After that, install Swift with the following command:
#### Basic usage
```sh
sudo apt-get update
sudo apt-get install binutils git gnupg2 libc6-dev libcurl4-openssl-dev
\ libedit2 libgcc-9-dev libpython3.8 libsqlite3-0 libstdc++-9-dev
\ libxml2-dev libz3-dev pkg-config tzdata unzip zlib1g-dev
```
$ swiftly install latest
### Download Swift Toolchain
Fetching the latest stable Swift release...
Installing Swift 5.9.1
Downloaded 488.5 MiB of 488.5 MiB
Extracting toolchain...
Swift 5.9.1 installed successfully!
This guide will install Swift 5.7.3. Visit the [Swift Releases](https://swift.org/download/#releases) page for a link to latest release. Copy the download link for Ubuntu 22.04.
$ swift --version
![Download Swift](../images/swift-download-ubuntu-copy-link.png)
Download and decompress the Swift toolchain.
```sh
wget https://download.swift.org/swift-5.7.3-release/ubuntu2204/swift-5.7.3-RELEASE/swift-5.7.3-RELEASE-ubuntu22.04.tar.gz
tar xzf swift-5.7.3-RELEASE-ubuntu22.04.tar.gz
```
!!! note
Swift's [Using Downloads](https://swift.org/download/#using-downloads) guide includes information on how to verify downloads using PGP signatures.
### Install Swift Toolchain
Move Swift somewhere easy to acess. This guide will use `/swift` with each compiler version in a subfolder.
```sh
sudo mkdir /swift
sudo mv swift-5.7.3-RELEASE-ubuntu22.04 /swift/5.7.3
```
Add Swift to `/usr/bin` so it can be executed by `vapor` and `root`.
```sh
sudo ln -s /swift/5.7.3/usr/bin/swift /usr/bin/swift
```
Verify that Swift was installed correctly.
```sh
swift --version
Swift version 5.9.1 (swift-5.9.1-RELEASE)
Target: x86_64-unknown-linux-gnu
```
## Install Vapor Using the Vapor Toolbox

194
docs/fluent/advanced.es.md Normal file
View File

@ -0,0 +1,194 @@
# Avanzado
Fluent se esfuerza por crear una API general e independiente de cualquier base de datos que te permita trabajar con tus datos. Esto facilita el proceso de aprendizaje de Fluent, sin importar el conector de base de datos que uses. Crear APIs generalizadas también puede hacer que trabajar con tu base de datos en Swift se sienta más cómodo.
Sin embargo, puede que necesites usar una característica de tu base de datos subyacente que todavía no tenga soporte en Fluent. Esta guía cubre APIs y patrones avanzados en Fluent que solo funcionan con ciertas bases de datos.
## SQL
Todos los conectores SQL de Fluent están construidos con [SQLKit](https://github.com/vapor/sql-kit). Esta implementación general de SQL se incluye con Fluent en el módulo `FluentSQL`.
### Base de datos SQL
Cualquier `Database` de Fluent puede convertirse (cast) a una `SQLDatabase`. Esto incluye `req.db`, `app.db`, la `database` pasada a `Migration`, etc.
```swift
import FluentSQL
if let sql = req.db as? SQLDatabase {
// El conector subyacente de base de datos es SQL.
let planets = try await sql.raw("SELECT * FROM planets").all(decoding: Planet.self)
} else {
// La base de datos subyacente _no_ es SQL.
}
```
Esta conversión solo funcionará si el conector de la base de datos subyacente es una base de datos SQL. Aprende más acerca de los métodos de `SQLDatabase` en el [README de SQLKit](https://github.com/vapor/sql-kit).
### Bases de datos SQL específicas
También puedes convertir a bases de datos SQL específicas importando el conector (driver).
```swift
import FluentPostgresDriver
if let postgres = req.db as? PostgresDatabase {
// El conector subyacente de base de datos es PostgreSQL.
postgres.simpleQuery("SELECT * FROM planets").all()
} else {
// La base de datos subyacente _no_ es PostgreSQL.
}
```
En el momento de esta redacción, están soportados los siguientes conectores SQL:
|Base de Datos|Conector|Librería|
|-|-|-|
|`PostgresDatabase`|[vapor/fluent-postgres-driver](https://github.com/vapor/fluent-postgres-driver)|[vapor/postgres-nio](https://github.com/vapor/postgres-nio)|
|`MySQLDatabase`|[vapor/fluent-mysql-driver](https://github.com/vapor/fluent-mysql-driver)|[vapor/mysql-nio](https://github.com/vapor/mysql-nio)|
|`SQLiteDatabase`|[vapor/fluent-sqlite-driver](https://github.com/vapor/fluent-sqlite-driver)|[vapor/sqlite-nio](https://github.com/vapor/sqlite-nio)|
Visita los README de la librerías para más información sobre las APIs específicas de cada base de datos.
### SQL Personalizado
Casi todos los tipos de consultas y esquemas de Fluent soportan la opción `.custom`. Esto te permite utilizar características de bases de datos que Fluent todavía no soporta.
```swift
import FluentPostgresDriver
let query = Planet.query(on: req.db)
if req.db is PostgresDatabase {
// ILIKE soportado.
query.filter(\.$name, .custom("ILIKE"), "earth")
} else {
// ILIKE no soportado.
query.group(.or) { or in
or.filter(\.$name == "earth").filter(\.$name == "Earth")
}
}
query.all()
```
Las bases de datos SQL soportan tanto `String` como `SQLExpression` en todos los casos `.custom`. El módulo `FluentSQL` ofrece métodos convenientes para casos de uso comunes.
```swift
import FluentSQL
let query = Planet.query(on: req.db)
if req.db is SQLDatabase {
// El conector subyacente de base de datos es SQL.
query.filter(.sql(raw: "LOWER(name) = 'earth'"))
} else {
// La base de datos subyacente _no_ es SQL.
}
```
A continuación tienes un ejemplo de `.custom` usando el método `.sql(raw:)` con el constructor del esquema.
```swift
import FluentSQL
let builder = database.schema("planets").id()
if database is MySQLDatabase {
// El conector subyacente de base de datos es MySQL.
builder.field("name", .sql(raw: "VARCHAR(64)"), .required)
} else {
// La base de datos subyacente _no_ es MySQL.
builder.field("name", .string, .required)
}
builder.create()
```
## MongoDB
Fluent MongoDB es una integración entre [Fluent](../fluent/overview.md) y el conector [MongoKitten](https://github.com/OpenKitten/MongoKitten/). Aprovecha el sistema de tipado fuerte de Swift y la interfaz no anclada a bases de datos de Fluent, usando MongoDB.
El identificador más común en MongoDB es ObjectId. Puedes usarlo para tus proyectos usando `@ID(custom: .id)`.
Si necesitas usar los mismos modelos con SQL, no uses `ObjectId`. Usa `UUID` en su lugar.
```swift
final class User: Model {
// Nombre de la tabla o colección.
static let schema = "users"
// Identificador único para este User.
// En este caso, se usa ObjectId
// Fluent recomienda usar UUID por defecto, sin embargo ObjectId también está soportado
@ID(custom: .id)
var id: ObjectId?
// La dirección email del User
@Field(key: "email")
var email: String
// La contraseña de User se guarda como un hash de BCrypt
@Field(key: "password")
var passwordHash: String
// Crea una nueva instancia de User vacía para el uso de Fluent
init() { }
// Crea un nuevo User con todas las propiedades establecidas.
init(id: ObjectId? = nil, email: String, passwordHash: String, profile: Profile) {
self.id = id
self.email = email
self.passwordHash = passwordHash
self.profile = profile
}
}
```
### Modelado de Datos
En MongoDB, los Modelos se definen de la misma manera que en cualquier otro entorno de Fluent. La principal diferencia entre bases de datos SQL y MongoDB reside en las relaciones y la arquitectura.
En entornos SQL, es muy común crear tablas de unión para relaciones entre dos entidades. En MongoDB, sin embargo, una colección (array) puede usarse para guardar identificadores relacionados. Debido al diseño de MongoDB, es más práctico y eficiente diseñar tus modelos con estructuras de datos anidadas.
### Datos Flexibles
Puedes añadir datos flexibles en MongoDB, pero este código no funcionará en entornos SQL.
Para crear un almacenamiento de datos arbitrarios agrupados puedes usar `Document`.
```swift
@Field(key: "document")
var document: Document
```
Fluent no puede soportar consultas de tipos estrictos en estos valores. Puedes usar un key path con notación de punto (dot notated key path) en tus consultas.
Esto está aceptado en MongoDB para acceder a valores anidados.
```swift
Something.query(on: db).filter("document.key", .equal, 5).first()
```
### Uso de expresiones regulares
Puedes consultar a MongoDB usando el caso `.custom()`, y pasando una expresión regular. [MongoDB](https://www.mongodb.com/docs/manual/reference/operator/query/regex/) acepta expresiones regulares compatibles con Perl.
Por ejemplo, puedes hacer una consulta que obvie mayúsculas y minúsculas sobre el campo `name`:
```swift
import FluentMongoDriver
var queryDocument = Document()
queryDocument["name"]["$regex"] = "e"
queryDocument["name"]["$options"] = "i"
let planets = try Planet.query(on: req.db).filter(.custom(queryDocument)).all()
```
Esto devolverá planetas que contengan 'e' y 'E'. También puedes crear otras RegEx complejas aceptadas por MongoDB.
### Raw Access
Para acceder a la instancia `MongoDatabase` en bruto, convierte (cast) la instancia de base de datos a `MongoDatabaseRepresentable` de la siguiente manera:
```swift
guard let db = req.db as? MongoDatabaseRepresentable else {
throw Abort(.internalServerError)
}
let mongodb = db.raw
```
A partir de aquí puedes usar todas las APIs de MongoKitten.

View File

@ -0,0 +1,96 @@
# Migraciones
Las migraciones son una especie de control de versiones para tu base de datos. Cada migración define un cambio en la base de datos y cómo deshacerlo. Modificando tu base de datos mediante migraciones creas una manera consistente, testable y fácil de compartir, de evolucionar tu base de datos a lo largo del tiempo.
```swift
// Una migración de ejemplo.
struct MyMigration: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
// Haz un cambio en la base de datos.
}
func revert(on database: Database) -> EventLoopFuture<Void> {
// Deshaz el cambio hecho en `prepare`, si es posible.
}
}
```
Si estás usando `async`/`await` deberías implementar el protocolo `AsyncMigration`:
```swift
struct MyMigration: AsyncMigration {
func prepare(on database: Database) async throws {
// Haz un cambio en la base de datos.
}
func revert(on database: Database) async throws {
// Deshaz el cambio hecho en `prepare`, si es posible.
}
}
```
En el método `prepare` haces cambios en la `Database` proporcionada. Pueden ser cambios en el esquema de la base de datos, como añadir o quitar una tabla, colección, campo o restricción. También pueden modificar el contenido de la base de datos, por ejemplo creando una nueva instancia de un modelo, actualizando valores de campos o haciendo una limpieza general.
En el método `revert` deshaces estos cambios, siempre y cuando sea posible. Tener la capacidad de deshacer migraciones puede facilitar el prototipado y el testing. También te ofrece un plan de recuperación si un despliegue en producción no marcha según lo planeado.
## Registro
Las migraciones son registradas en tu aplicación usando `app.migrations`.
```swift
import Fluent
import Vapor
app.migrations.add(MyMigration())
```
Puedes añadir una migración a una base de datos específica mediante el parámetro `to`, sino se usará la base de datos por defecto.
```swift
app.migrations.add(MyMigration(), to: .myDatabase)
```
Las migraciones deberían estar listadas según el orden de dependencia. Por ejemplo, si `MigrationB` depende de `MigrationA`, debería añadirse a `app.migrations` la segunda.
## Migrar
Para migrar tu base de datos, ejecuta el comando `migrate`.
```sh
swift run App migrate
```
También puedes ejecutar este [comando desde Xcode](../advanced/commands.md#xcode). El comando de migración comprobará la base de datos para ver si se han registrado nuevas migraciones desde la última ejecución. Si hay nuevas migraciones pedirá confirmación antes de ejecutarlas.
### Revertir
Para deshacer una migración en tu base de datos, ejecuta `migrate` con la marca `--revert`.
```sh
swift run App migrate --revert
```
El comando comprobará la base de datos para ver que conjunto de migraciones fue ejecutado la vez anterior y pedirá confirmación antes de revertirlas.
### Migración Automática
Si quieres que tus migraciones se ejecuten automáticamente antes de ejecutar otros comandos, añade la marca `--auto-migrate`.
```sh
swift run App serve --auto-migrate
```
También puedes hacerlo de manera programática.
```swift
try app.autoMigrate().wait()
// o
try await app.autoMigrate()
```
Ambas opciones también exiten para revertir: `--auto-revert` y `app.autoRevert()`.
## Próximos Pasos
Echa un vistazo a las guías de [schema builder](schema.md) y [query builder](query.md) para más información acerca de qué poner dentro de tus migraciones.

596
docs/fluent/model.es.md Normal file
View File

@ -0,0 +1,596 @@
# Modelos
Los modelos representan datos guardados en tablas o colecciones en tu base de datos. Los modelos tienen uno o más campos que guardan valores codificables. Todos los modelos tienen un identificador único. Los empaquetadores de propiedades (property wrappers) se usan para denotar identificadores, campos y relaciones.
A conntinuación hay un ejemplo de un modelo simple con un campo. Cabe destacar que los modelos no describen el esquema completo (restricciones, índices, claves foráneas...) de la base de datos. Los esquemas se definen en [migraciones](migration.md). Los modelos se centran en representar los datos guardados en los esquemas de tu base de datos.
```swift
final class Planet: Model {
// Nombre de la tabla o colección.
static let schema = "planets"
// Identificador único para este Planet.
@ID(key: .id)
var id: UUID?
// El nombre del Planet.
@Field(key: "name")
var name: String
// Crea un nuevo Planet vacío.
init() { }
// Crea un nuevo Planet con todas sus propiedades establecidas.
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
```
## Esquema
Todos los modelos requieren una propiedad estática de sólo lectura (get-only) llamada `schema`. Esta cadena hace referencia al nombre de la tabla o colección que el modelo representa.
```swift
final class Planet: Model {
// Nombre de la tabla o colección.
static let schema = "planets"
}
```
Al consultar este modelo, los datos serán recuperados de y guardados en el esquema llamado `"planets"`.
!!! tip "Consejo"
El nombre del esquema típicamente es el nombre de la clase en plural y en minúsculas.
## Identificador
Todos los modelos deben tener una propiedad `id` definida usando el property wrapper `@ID`. Este campo identifica instancias de tu modelo unívocamente.
```swift
final class Planet: Model {
// Identificador único para este Planet.
@ID(key: .id)
var id: UUID?
}
```
Por defecto la propiedad `@ID` debería usar la clave especial `.id`, que se resuelve en una clave apropiada para el conector (driver) de la base de datos subyacente. Para SQL es `"id"` y para NoSQL es `"_id"`.
El `@ID` también debería ser de tipo `UUID`. Este es el único valor de identificación soportado por todos los conectores de bases de datos. Fluent generará nuevos identificadores UUID automáticamente en la creación de modelos.
`@ID` tiene un valor opcional dado que los modelos no guardados pueden no tener un identificador. Para obtener el identificador o lanzar un error, usa `requireID`.
```swift
let id = try planet.requireID()
```
### Exists
`@ID` tiene una propiedad `exists` que representa si el modelo existe o no en la base de datos. Cuando inicializas un modelo, el valor es `false`. Después de guardar un modelo o recuperarlo de la base de datos, the value is `true`. Esta propiedad es mutable.
```swift
if planet.$id.exists {
// Este modelo existe en la base de datos.
}
```
### Identificador Personalizado
Fluent soporta claves y tipos de identificadores personalizados mediante la sobrecarga `@ID(custom:)`.
```swift
final class Planet: Model {
// Identificador único para este Planet.
@ID(custom: "foo")
var id: Int?
}
```
El ejemplo anterior usa un `@ID` con la clave personalizada `"foo"` y el tipo identificador `Int`. Esto es compatible con bases de datos SQL que usen claves primarias de incremento automático, pero no es compatible con NoSQL.
Los `@ID`s personalizados permiten al usuario especificar cómo debería generarse el identificador usando el parámetro `generatedBy`.
```swift
@ID(custom: "foo", generatedBy: .user)
```
El parámetro `generatedBy` soporta estos casos:
|Generated By|Descripción|
|-|-|
|`.user`|Se espera que la propiedad `@ID` sea establecida antes de guardar un nuevo modelo.|
|`.random`|El tipo del valor `@ID` debe conformarse a `RandomGeneratable`.|
|`.database`|Se espera que la base de datos genere el valor durante el guardado.|
Si el parámetro `generatedBy` se omite, Fluent intentará inferir un caso apropiado basado en el tipo de valor del `@ID`. Por ejemplo, `Int` usará por defecto la generación `.database` si no se especifica lo contrario.
## Inicializador
Los modelos deben tener un método inicializador (init) vacío.
```swift
final class Planet: Model {
// Crea un nuevo Planet vacío.
init() { }
}
```
Fluent necesita este método internamente para inicializar modelos devueltos por consultas. También es usado para el espejado (reflection).
Puedes querer añadir a tu modelo un inicializador de conveniencia que acepte todas las propiedades.
```swift
final class Planet: Model {
// Crea un nuevo Planet con todas las propiedades establecidas.
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
```
El uso de inicializadores de conveniencia facilita añadir nuevas propiedades al modelo en un futuro.
## Campo
Los modelos pueden tener o no propiedades `@Field` para guardar datos.
```swift
final class Planet: Model {
// El nombre del Planet.
@Field(key: "name")
var name: String
}
```
Los campos requieren que la clave de la base de datos sea definida explícitamente. No es necesario que sea igual que el nombre de la propiedad.
!!! tip "Consejo"
Fluent recomienda usar `snake_case` para claves de bases de datos y `camelCase` para nombres de propiedades.
Los valores de los campos pueden ser de cualquier tipo conformado a `Codable`. Guardar estructuras y colecciones (arrays) anidados en `@Field` está soportado, pero las operaciones de filtrado son limitadas. Ve a [`@Group`](#group) para una alternativa.
Para campos que contengan un valor opcional, usa `@OptionalField`.
```swift
@OptionalField(key: "tag")
var tag: String?
```
!!! warning "Advertencia"
Un campo no opcional que tenga una propiedad observadora `willSet` que referencie su valor actual o una propiedad observadora `didSet` que referencie a `oldValue` dará un error fatal.
## Relaciones
Los modelos pueden tener ninguna o más propiedades relacionales referenciando otros modelos, `@Parent`, `@Children` y `@Siblings`. Aprende más sobre las relaciones en la sección de [relaciones](relations.md).
## Timestamp
`@Timestamp` es un tipo especial de `@Field` que guarda un `Foundation.Date`. Las timestamps (marcas de tiempo) son establecidas automáticamente por Fluent dependiendo del disparador (trigger) seleccionado.
```swift
final class Planet: Model {
// Cuando este Planet fue creado.
@Timestamp(key: "created_at", on: .create)
var createdAt: Date?
// Cuando este Planet fue actualizado por última vez.
@Timestamp(key: "updated_at", on: .update)
var updatedAt: Date?
}
```
`@Timestamp` soporta los siguiente triggers.
|Trigger|Descripción|
|-|-|
|`.create`|Establecido cuando una nueva instancia de modelo es guardada en la base de datos.|
|`.update`|Establecido cuando una instancia de modelo ya existente es guardada en la base de datos.|
|`.delete`|Establecido cuando un modelo es eliminado de la base de datos. Ver [soft delete](#soft-delete).|
El valor de fecha de los `@Timestamp`s es opcional y debería establecer a `nil` al inicializar un nuevo modelo.
### Formato de Timestamp
Por defecto `@Timestamp` usará una codificación eficiente para `datetime` basada en el controlador de tu base de datos. Puedes personalizar cómo la marca de tiempo se guarda en la base de datos usando el parámetro `format`.
```swift
// Guarda un timestamp formateado según ISO 8601 representando
// cuando este modelo fue actualizado por última vez.
@Timestamp(key: "updated_at", on: .update, format: .iso8601)
var updatedAt: Date?
```
Cabe destacar que la migración asociada al ejemplo con `.iso8601` necesitaría ser guardado en formato `.string`.
```swift
.field("updated_at", .string)
```
A continuación hay un listado de los formatos disponibles para timestamp.
|Formato|Descripción|Tipo|
|-|-|-|
|`.default`|Usa codificación eficiente de `datetime` específica para la base de datos.|Date|
|`.iso8601`|Cadena [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). Soporta parámetro `withMilliseconds`.|String|
|`.unix`|Segundos desde época Unix incluyendo fracción.|Double|
Puedes acceder el valor puro (raw value) del timestamp directamente usando la propiedad `timestamp`.
```swift
// Establece manualmente el timestamp en este
// @Timestamp formateado con ISO 8601.
model.$updatedAt.timestamp = "2020-06-03T16:20:14+00:00"
```
### Soft Delete
Añadir un `@Timestamp` que use el disparador `.delete`a tu modelo habilitará el borrado no permanente (soft-deletion).
```swift
final class Planet: Model {
// Cuando este Planet fue borrado.
@Timestamp(key: "deleted_at", on: .delete)
var deletedAt: Date?
}
```
Los modelos eliminados de manera no permanente siguen existiendo en la base de datos después de su borrado, pero no serán devueltos en las consultas.
!!! tip "Consejo"
Puedes establecer manualmente un timestamp de borrado con una fecha en el futuro. Puede usarse como una fecha de caducidad.
Para forzar el borrado de un modelo de eliminación no permanente en la base de datos, usa el parámetro `force` en `delete`.
```swift
// Elimina el modelo de la base de datos
// inclusive si es soft-deletable
model.delete(force: true, on: database)
```
Para restaurar un modelo eliminado no permanentemente, usa el método `restore`.
```swift
// Limpia en timestamp de borrado del modelo
// para que pueda devolverse en consultas.
model.restore(on: database)
```
Para incluir modelos eliminados no permanentemente en una consulta, usa `withDeleted`.
```swift
// Recupera todos los planetas, incluidos los eliminados no permanentemente.
Planet.query(on: database).withDeleted().all()
```
## Enum
`@Enum` es un tipo especial de `@Field` para guardar tipos representables mediante cadenas como enumeraciones nativas de bases de datos. Las enumeraciones nativas de bases de datos proporcionan una capa añadida de seguridad de tipos a tu base de datos y pueden ser más óptimas que las enumeraciones en bruto (raw enums).
```swift
// Enum Codable y representable mediante String para tipos de animales.
enum Animal: String, Codable {
case dog, cat
}
final class Pet: Model {
// Guarda el tipo de animal como un enum nativo de base de datos.
@Enum(key: "type")
var type: Animal
}
```
Solo los tipos conformados a `RawRepresentable` donde el `RawValue` es `String` son compatibles con `@Enum`. Los enums soportados por `String` cumplen este requisito por defecto.
Para guardar una enumeración opcional, usa `@OptionalEnum`.
La base de datos debe prepararse para manejar enumeraciones via una migración. Ver [enum](schema.md#enum) para más información.
### Raw Enums
Cualquier enumeración respaldada por una tipo de dato `Codable`, como `String` o `Int`, puede guardarse en `@Field`. Se guardará en la base de datos como el dato en bruto.
## Grupo
`@Group` te permite guardar un grupo anidado de campos en tu modelo como una única propiedad. Al contrario que las structs conformadas con Codable guardadas en un `@Field`, los campos en un `@Group` son consultables. Fluent consigue esto guardando `@Group` como una estructura plana en la base de datos.
Para usar un `@Group`, primero define la estructura anidada que quieres guardar usando el protocolo `Fields`. Es muy similar a `Model`, pero no requiere identificador ni nombre de esquema. Aquí puedes guardar muchas de las propiedades soportadas por `Model`, como `@Field`, `@Enum`, o inclusive otro `@Group`.
```swift
// Una mascota (pet) con nombre y tipo de animal.
final class Pet: Fields {
// El nombre de la mascota.
@Field(key: "name")
var name: String
// El tipo de la mascota.
@Field(key: "type")
var type: String
// Crea un nuevo Pet vacío.
init() { }
}
```
Después de crear la definición de la estructura anidada (fields), puedes usarla como valor de una propiedad `@Group`.
```swift
final class User: Model {
// La mascota anidada al usuario.
@Group(key: "pet")
var pet: Pet
}
```
Los campos de un `@Group` son accesibles mediante la sintaxis de punto (dot-syntax).
```swift
let user: User = ...
print(user.pet.name) // String
```
Puedes consultar campos anidados de la manera habitual usando sintaxis de punto en los empquetadores de propiedad (property wrappers).
```swift
User.query(on: database).filter(\.$pet.$name == "Zizek").all()
```
En la base de datos, `@Group` se guarda como una estructura plana con claves unidas mediante `_`. A continuación hay un ejemplo de cómo se vería `User` en la base de datos.
|id|name|pet_name|pet_type|
|-|-|-|-|
|1|Tanner|Zizek|Cat|
|2|Logan|Runa|Dog|
## Codable
Los modelos se conforman a `Codable` por defecto. Esto te permite usar tus modelos con la [API de contenido](../basics/content.es.md) de Vapor añadiendo la conformidad al protocolo `Content`.
```swift
extension Planet: Content { }
app.get("planets") { req async throws in
// Devuelve un array de todos los planetas.
try await Planet.query(on: req.db).all()
}
```
Al serializar desde/hacia `Codable`, las propiedades del modelo usarán sus nombres de variable en lugar de las claves. Las relaciones serán serializadas como estructuras anidadas y cualquier carga previa (eager loading) de datos será incluida.
### Data Transfer Object
La conformidad por defecto del modelo a `Codable` puede facilitar el prototipado y usos simples. Sin embargo, no se ajusta a todos los casos de uso. Para ciertas situaciones necesitarás usar un data transfer object (DTO).
!!! tip "Consejo"
Un DTO es un tipo `Codable` aparte que representa la estructura de datos que quieres codificar/descodificar.
Toma como base el siguiente modelo de `User` para los ejemplos a continuación.
```swift
// Modelo de usuario reducido como referencia.
final class User: Model {
@ID(key: .id)
var id: UUID?
@Field(key: "first_name")
var firstName: String
@Field(key: "last_name")
var lastName: String
}
```
Un caso de uso común de DTOs es la implementación de peticiones (request) `PATCH`. Estas peticiones solo incluyen valores para los campos que deberían actualizarse. La decodificación de un `Model` directamente de esa petición fallaría si cualquiera de los campos no opcionales faltase. En el siguiente ejemplo puedes ver cómo se usa un DTO para decodificar datos de la petición y actualizar un modelo.
```swift
// Structure of PATCH /users/:id request.
struct PatchUser: Decodable {
var firstName: String?
var lastName: String?
}
app.patch("users", ":id") { req async throws -> User in
// Decodificar los datos de la petición.
let patch = try req.content.decode(PatchUser.self)
// Recuperar el usuario deseado de la base de datos.
guard let user = try await User.find(req.parameters.get("id"), on: req.db) else {
throw Abort(.notFound)
}
// Si se diera un nombre, se actualiza.
if let firstName = patch.firstName {
user.firstName = firstName
}
// Si se diera un apellido, se actualiza.
if let lastName = patch.lastName {
user.lastName = lastName
}
// Guarda el usuario y lo devuelve.
try await user.save(on: req.db)
return user
}
```
Otro uso común de DTO es personalizar el formato de las respuestas de tu API. El ejemplo a continuación muestra cómo puede usarse un DTO para añadir un campo computado a una respuesta.
```swift
// Estructura de la respuesta GET /users.
struct GetUser: Content {
var id: UUID
var name: String
}
app.get("users") { req async throws -> [GetUser] in
// Recuperar todos los usuarios de la base de datos.
let users = try await User.query(on: req.db).all()
return try users.map { user in
// Convertir cada usuario al tipo de devolución para GET.
try GetUser(
id: user.requireID(),
name: "\(user.firstName) \(user.lastName)"
)
}
}
```
Aunque la estructura del DTO sea idéntica a la del modelo conformado con `Codable`, tenerlo como tipo aparte puede ayudar a mantener los proyectos grandes ordenados. Si necesitaras hacer un cambio en las propiedades de tu modelo, no tienes que preocuparte de si rompes la API pública de tu app. Puedes considerar agrupar tus DTO en un package aparte que puedas compartir con los consumidores de tu API.
Por estas razones, recomendamos encarecidamente usar DTO siempre que sea posible, especialmente en proyectos grandes.
## Alias
El protocolo `ModelAlias` te permite identificar de manera unívoca un modelo unido varias veces en una consulta. Para más información, consulta [uniones](query.md#join).
## Guardar
Para guardar un modelo en la base de datos, usa el método `save(on:)`.
```swift
planet.save(on: database)
```
Este método llamará internamente a `create` o `update` dependiendo de si el modelo ya existe o no en la base de datos.
### Crear
Puedes llamar al método `create` para guardar un modelo nuevo en la base de datos.
```swift
let planet = Planet(name: "Earth")
planet.create(on: database)
```
`create` también está disponible en una colección (array) de modelos. Con esto puedes guardar en la base de datos todos los modelos en una única remesa (batch) / consulta (query).
```swift
// Ejemplo de batch create.
[earth, mars].create(on: database)
```
!!! warning "Advertencia"
Los modelos que usen [`@ID(custom:)`](#custom-identifier) con el generador `.database` (normalmente `Int`s de incremento automático) no tendrán sus identificadores recién creados después del batch create. Para situaciones en las que necesites acceder a los identificadores llama a `create` en cada modelo.
Para crear una colección de modelos individualmente usa `map` + `flatten`.
```swift
[earth, mars].map { $0.create(on: database) }
.flatten(on: database.eventLoop)
```
Si estás usando `async`/`await` puedes usar:
```swift
await withThrowingTaskGroup(of: Void.self) { taskGroup in
[earth, mars].forEach { model in
taskGroup.addTask { try await model.create(on: database) }
}
}
```
### Actualizar
Puedes llamar al método `update` para guardar un modelo recuperado de la base de datos.
```swift
guard let planet = try await Planet.find(..., on: database) else {
throw Abort(.notFound)
}
planet.name = "Earth"
try await planet.update(on: database)
```
Para actualizar una colección de modelos usa `map` + `flatten`.
```swift
[earth, mars].map { $0.update(on: database) }
.flatten(on: database.eventLoop)
// TOOD
```
## Consultar
Los modelos exponen un método estático llamado `query(on:)` que devuelve un constructor de consultas (query builder).
```swift
Planet.query(on: database).all()
```
Aprende más sobre las consultas en la sección de [consulta](query.md).
## Encontrar
Los modelos tienen un método estático llamado `find(_:on:)` para encontrar una instancia del modelo por su identificador.
```swift
Planet.find(req.parameters.get("id"), on: database)
```
Este método devuelve `nil` si no se ha encontrado ningún modelo con ese identificador.
## Ciclo de vida
Los middleware de modelo te permiten engancharte a los eventos del ciclo de vida de tu modelo. Están soportados los siguientes eventos de ciclo de vida (lifecycle):
|Método|Descripción|
|-|-|
|`create`|Se ejecuta antes de crear un modelo.|
|`update`|Se ejecuta antes de actualizar un modelo.|
|`delete(force:)`|Se ejecuta antes de eliminar un modelo.|
|`softDelete`|Se ejecuta antes de borrar de manera no permanente (soft-delete) un modelo.|
|`restore`|Se ejecuta antes de restaurar un modelo (opuesto de soft delete).|
Los middleware de modelo se declaran usando los protocolos `ModelMiddleware` o `AsyncModelMiddleware`. Todos los eventos de ciclo de vida tienen una implementación por defecto, así que solo necesitas implementar aquellos que necesites. Cada método acepta el modelo en cuestión, una referencia a la base de datos y la siguiente acción en la cadena. El middleware puede elegir devolver pronto, devolver un futuro fallido o llamar a la siguiente acción para continuar de manera normal.
Si usas estos métodos, puedes llevar a cabo acciones tanto antes como después de que un evento en específico se complete. Puedes llevar a cabo acciones después de que el evento se complete si asignas el futuro devuelto por el siguiente respondedor.
```swift
// Middleware de ejemplo que capitaliza nombres.
struct PlanetMiddleware: ModelMiddleware {
func create(model: Planet, on db: Database, next: AnyModelResponder) -> EventLoopFuture<Void> {
// El modelo puede alterarse aquí antes de ser creado.
model.name = model.name.capitalized()
return next.create(model, on: db).map {
// Una vez se haya creado el planeta
// el código que haya aquí se ejecutará
print ("Planet \(model.name) was created")
}
}
}
```
o si usas `async`/`await`:
```swift
struct PlanetMiddleware: AsyncModelMiddleware {
func create(model: Planet, on db: Database, next: AnyAsyncModelResponder) async throws {
// El modelo puede alterarse aquí antes de ser creado.
model.name = model.name.capitalized()
try await next.create(model, on: db)
// Una vez se haya creado el planeta
// el código que haya aquí se ejecutará
print ("Planet \(model.name) was created")
}
}
```
Una vez hayas creado tu middleware, puedes activarlo usando `app.databases.middleware`.
```swift
// Ejemplo de middleware de configuración de modelo.
app.databases.middleware.use(PlanetMiddleware(), on: .psql)
```
## Espacio de BBDD
Fluent soporta establecer un espacio para un `Model`, lo que permite la partición de modelos individuales de Fluent entre esquemas de PostgreSQL, bases de datos MySQL, y varias bases de datos SQLite adjuntas. MongoDB no soporta los espacios en el momento de la redacción. Para poner un modelo en un espacio distinto al usado por defecto, añade una nueva propiedad estática al modelo:
```swift
public static let schema = "planets"
public static let space: String? = "mirror_universe"
// ...
```
Fluent usará esto para la construcción de todas las consultas a la base de datos.

View File

@ -527,7 +527,7 @@ Planet.find(req.parameters.get("id"), on: database)
如果没有找到具有该标识符的模型,则返回 `nil`
## 生命周期
## 生命周期(Lifecycle)
模型中间件允许你监听模型的生命周期事件。支持以下生命周期事件。

364
docs/fluent/relations.es.md Normal file
View File

@ -0,0 +1,364 @@
# Relaciones
La [API de modelo](model.md) de Fluent te ayuda a crear y mantener referencias entre tus modelos mediante relaciones. Estos son los tres tipos de relaciones soportados:
- [Parent](#parent) / [Child](#optional-child) (Uno a uno)
- [Parent](#parent) / [Children](#children) (Uno a muchos)
- [Siblings](#siblings) (Muchos a muchos)
## Parent
La relación `@Parent` guarda una referencia a la propiedad `@ID` de otro modelo.
```swift
final class Planet: Model {
// Ejemplo de relación parent.
@Parent(key: "star_id")
var star: Star
}
```
`@Parent` contiene un `@Field` llamado `id` usado para establecer y actualizar la relación.
```swift
// Establece el id de la relación parent
earth.$star.id = sun.id
```
Por ejemplo, el inicializador de `Planet` se vería de la siguiente manera:
```swift
init(name: String, starID: Star.IDValue) {
self.name = name
// ...
self.$star.id = starID
}
```
El parámetro `key` define la clave del campo en el que se guarda el identificador del parent. Asumiendo que `Star` tiene un identificador `UUID`, esta relación `@Parent` es compatible con la siguiente [definición de campo](schema.md#field).
```swift
.field("star_id", .uuid, .required, .references("star", "id"))
```
Cabe destacar que la restricción (constraint) [`.references`](schema.md#field-constraint) es opcional. Ver [schema](schema.md) para más información.
### Optional Parent
La relación `@OptionalParent` guarda una referencia opcional a la propiedad `@ID` de otro modelo. Funciona de manera similar a `@Parent` pero permite que la relación sea `nil`.
```swift
final class Planet: Model {
// Ejemplo de una relación parent opcional.
@OptionalParent(key: "star_id")
var star: Star?
}
```
La definición del campo es similar a la de `@Parent`, excepto la constraint `.required`, que debe ser omitida.
```swift
.field("star_id", .uuid, .references("star", "id"))
```
## Optional Child
La propiedad `@OptionalChild` crea una relación uno a uno entre dos modelos. No guarda ningún valor en el modelo raíz.
```swift
final class Planet: Model {
// Ejemplo de una relación optional child.
@OptionalChild(for: \.$planet)
var governor: Governor?
}
```
El parámetro `for` acepta un key path hacia una relación `@Parent` o `@OptionalParent` referenciando el modelo raíz.
Puede añadirse un nuevo modelo a esta relación usando el método `create`.
```swift
// Ejemplo de añadir un nuevo modelo a una relación.
let jane = Governor(name: "Jane Doe")
try await mars.$governor.create(jane, on: database)
```
Esto establecerá de manera automática el identificador (id) del parent en el modelo child.
Dado que esta relación no guarda valores, no se necesita especificar un esquema de base de datos para el modelo raíz.
La naturaleza "uno a uno" de la relación debe hacerse patente en el esquema del modelo child usando una constraint `.unique` en la columna que haga referencia al modelo parent.
```swift
try await database.schema(Governor.schema)
.id()
.field("name", .string, .required)
.field("planet_id", .uuid, .required, .references("planets", "id"))
// Ejemplo de una restricción única
.unique(on: "planet_id")
.create()
```
!!! warning "Advertencia"
Omitir la restricción de unicidad en el campo del identificador del parent en el esquema del cliente puede ocasionar resultados impredecibles.
Si no hay una restricción de unicidad, la tabla child puede llegar a contener más de una fila child para un solo parent; en este caso, una propiedad `@OptionalChild` seguirá pudiendo acceder a un único child, sin manera de controlar cuál de ellos carga. Si necesitaras guardas varias filas child para un único parent, usa `@Children`.
## Children
La propiedad `@Children` crea una relación uno a muchos entre dos modelos. No guarda ningún valor en el modelo raíz.
```swift
final class Star: Model {
// Ejemplo de una relación children.
@Children(for: \.$star)
var planets: [Planet]
}
```
El parámetro `for` acepta un key path a una relación `@Parent` o `@OptionalParent` referenciando el modelo raíz. En este caso, estamos referenciando la relación `@Parent` del anterior [ejemplo](#parent).
Puede añadirse un nuevo modelo a esta relación usando el método `create`.
```swift
// Ejemplo de añadir un nuevo modelo a una relación.
let earth = Planet(name: "Earth")
try await sun.$planets.create(earth, on: database)
```
Esto establecerá de manera automática el identificador (id) del parent en el modelo child.
Dado que esta relación no guarda valores, no se necesita especificar un esquema de base de datos.
## Siblings
La propiedad `@Siblings` crea una relación muchos a muchos entre dos modelos. Lo hace mediante un modelo terciario llamado pivote.
Echemos un vistazo a un ejemplo de relación muchos a muchos entre un `Planet` y una `Tag`.
```swift
enum PlanetTagStatus: String, Codable { case accepted, pending }
// Ejemplo de modelo pivote.
final class PlanetTag: Model {
static let schema = "planet+tag"
@ID(key: .id)
var id: UUID?
@Parent(key: "planet_id")
var planet: Planet
@Parent(key: "tag_id")
var tag: Tag
@OptionalField(key: "comments")
var comments: String?
@OptionalEnum(key: "status")
var status: PlanetTagStatus?
init() { }
init(id: UUID? = nil, planet: Planet, tag: Tag, comments: String?, status: PlanetTagStatus?) throws {
self.id = id
self.$planet.id = try planet.requireID()
self.$tag.id = try tag.requireID()
self.comments = comments
self.status = status
}
}
```
Cualquier modelo que incluya al menos dos relaciones `@Parent` referenciando a sus respectivos modelos puede usarse como pivote. El modelo puede contener propiedades adicionales como su propio ID, e inclusive otras relaciones `@Parent`.
Añadir una restricción de [unicidad](schema.md#unique) al modelo pivote puede ayudar a prevenir entradas redundantes. Ver [schema](schema.md) para más información.
```swift
// No permite relaciones duplicadas.
.unique(on: "planet_id", "tag_id")
```
Una vez el pivote está creado, usa la propiedad `@Siblings` para crear la relación.
```swift
final class Planet: Model {
// Ejemplo de relación sibling.
@Siblings(through: PlanetTag.self, from: \.$planet, to: \.$tag)
public var tags: [Tag]
}
```
La propiedad `@Siblings` requiere de tres parámetros:
- `through`: El tipo del modelo pivote.
- `from`: El key path del pivote a la relación parent referenciando el modelo raíz.
- `to`: El key path del pivote a la relación parent referenciando el modelo conectado.
La propiedad `@Siblings` inversa en el modelo conectado completa la relación.
```swift
final class Tag: Model {
// Ejemplo de una relación sibling.
@Siblings(through: PlanetTag.self, from: \.$tag, to: \.$planet)
public var planets: [Planet]
}
```
### Añadir a Siblings
La propiedad `@Siblings` tiene métodos para añadir o quitar modelos de la relación.
Usa el método `attach()` para añadir un modelo o una colección (array) de modelos a la relación. Los modelos pivote son creados y guardados de manera automática según sea necesario. Un closure de callback puede especificarse para poblar propiedades adicionales de cada pivote creado:
```swift
let earth: Planet = ...
let inhabited: Tag = ...
// Añadir el modelo a la relación.
try await earth.$tags.attach(inhabited, on: database)
// Poblar los atributos del pivote al establecer la relación.
try await earth.$tags.attach(inhabited, on: database) { pivot in
pivot.comments = "This is a life-bearing planet."
pivot.status = .accepted
}
// Añadir varios modelos con atributos a la relación.
let volcanic: Tag = ..., oceanic: Tag = ...
try await earth.$tags.attach([volcanic, oceanic], on: database) { pivot in
pivot.comments = "This planet has a tag named \(pivot.$tag.name)."
pivot.status = .pending
}
```
Si estás añadiendo un solo modelo, puedes usar el parámetro `method` para indicar si la relación debería ser revisada o no antes del guardado.
```swift
// Solo añade si la relación no es ya existente.
try await earth.$tags.attach(inhabited, method: .ifNotExists, on: database)
```
Usa el método `detach` para eliminar un modelo de la relación. Esto borra el modelo pivote correspondiente.
```swift
// Elimina el modelo de la relación.
try await earth.$tags.detach(inhabited, on: database)
```
Puedes comprobar que un modelo esté conectado o no usando el método `isAttached`.
```swift
// Comprueba que los modelos estén conectados.
earth.$tags.isAttached(to: inhabited)
```
## Get
Usa el método `get(on:)` para recuperar el valor de una relación.
```swift
// Recupera todos los planetas del sol.
sun.$planets.get(on: database).map { planets in
print(planets)
}
// O
let planets = try await sun.$planets.get(on: database)
print(planets)
```
Usa el parámetro `reload` para indicar si la relación debería ser recuperada de nuevo o no de la base de datos si ya ha sido cargada.
```swift
try await sun.$planets.get(reload: true, on: database)
```
## Query
Usa el método `query(on:)` en una relación para crear un constructor de consulta para los modelos conectados.
```swift
// Recupera todos los planetas del sol cuyo nombre empiece con M.
try await sun.$planets.query(on: database).filter(\.$name =~ "M").all()
```
Ver [query](query.md) para más información.
## Eager Loading
El constructor de consultas (query builder) de Fluent te permite precargar las relaciones de un modelo cuando es recuperado de la base de datos. Esto se conoce como "eager loading" y te permite acceder a las relaciones de manera sincrónica sin la necesidad de llamar previamente a [`load`](#lazy-eager-loading) o [`get`](#get) .
Para hacer un "eager load" de una relación, pasa un key path a la relación con el método `with` en el constructor de consultas.
```swift
// Ejemplo de eager loading.
Planet.query(on: database).with(\.$star).all().map { planets in
for planet in planets {
// `star` es accesible de manera sincrónica aquí
// dado que ha sido precargada.
print(planet.star.name)
}
}
// O
let planets = try await Planet.query(on: database).with(\.$star).all()
for planet in planets {
// `star` es accesible de manera sincrónica aquí
// dado que ha sido precargada.
print(planet.star.name)
}
```
En el ejemplo anterior, se le ha pasado un key path a la relación [`@Parent`](#parent) llamada `star` con `with`. Esto provoca que el constructor de consultas haga una consulta adicional después de cargar todos los planetas para recuperar todas las estrellas conectadas a éstos. Las estrellas son accesibles de manera sincrónica mediante la propiedad `@Parent`.
Cada relación precargada (eager loaded) necesita una única consulta adicional, sin importar cuántos modelos se hayan devuelto. La precarga (eager loading) sólo es posible con los métodos de constructor de consultas `all` y `first`.
### Nested Eager Load
El método de constructor de consultas `with` te permite precargar relaciones en el modelo que está siendo consultado. Sin embargo, también puedes precargar relaciones en los modelos conectados.
```swift
let planets = try await Planet.query(on: database).with(\.$star) { star in
star.with(\.$galaxy)
}.all()
for planet in planets {
// `star.galaxy` es accesible de manera sincrónica aquí
// dado que ha sido precargada.
print(planet.star.galaxy.name)
}
```
El método `with` acepta un closure opcional como segundo parámetro. Este closure acepta un constructor de precarga para la relación elegida. No hay límite de profundidad en el anidado de precargas.
## Lazy Eager Loading
En caso de que ya hayas recuperado el modelo del parent y quieres cargar una de sus relaciones, puedes usar el método `load(on:)` para hacerlo. Esto recuperará el modelo conectado de la base de datos y permitirá acceder a él como una propiedad local.
```swift
planet.$star.load(on: database).map {
print(planet.star.name)
}
// O
try await planet.$star.load(on: database)
print(planet.star.name)
```
Para comprobar si una relación se ha cargado, usa la propiedad `value`.
```swift
if planet.$star.value != nil {
// La relación se ha cargado.
print(planet.star.name)
} else {
// La relación no se ha cargado.
// Intentar acceder a planet.star fallará.
}
```
Si ya tienes el modelo conectado en una variable, puedes establecer la relación manualmente usando la propiedad `value` mencionada anteriormente.
```swift
planet.$star.value = star
```
Esto unirá el modelo conectado al modelo parent como si hubiera sido cargado como "eager loaded" o como "lazy loaded", sin necesitar una consulta extra a la base de datos.

View File

@ -218,6 +218,21 @@ Below is an example using foreign key actions.
Foreign key actions happen solely in the database, bypassing Fluent.
This means things like model middleware and soft-delete may not work correctly.
## SQL
The `.sql` parameter allows you to add arbitrary SQL to your schema. This is useful for adding specific constraints or data types.
A common use case is defining a default value for a field:
```swift
.field("active", .bool, .required, .sql(.default(true)))
```
or even a default value for a timestamp:
```swift
.field("created_at", .datetime, .required, .sql(.default(SQLFunction("now"))))
```
## Dictionary
The dictionary data type is capable of storing nested dictionary values. This includes structs that conform to `Codable` and Swift dictionaries with a `Codable` value.
@ -389,4 +404,4 @@ try await db.schema("planets", space: "mirror_universe")
.id()
// ...
.create()
```
```

View File

@ -218,6 +218,21 @@ database.schema("planets").delete()
!!! warning "警告"
外键操作仅发生在数据库中,绕过 Fluent。这意味着模型中间件和软删除之类的东西可能无法正常工作。
## SQL
`.sql` 参数允许你向 schema 中添加任意的 SQL。这对于添加特定的约束或数据类型非常有用。
一个常见的用例是为字段定义默认值:
```swift
.field("active", .bool, .required, .sql(.default(true)))
```
甚至可以为时间戳字段定义默认值:
```swift
.field("created_at", .datetime, .required, .sql(.default(SQLFunction("now"))))
```
## 字典(Dictionary)
字典数据类型能够存储嵌套的字典值。这包括遵循 `Codable` 协议的结构和具有 `Codable` 值的 Swift 字典。

View File

@ -0,0 +1,50 @@
# Transacciones
Las transacciones te permiten asegurar que múltiples operaciones se completen con éxito antes de un guardado en tu base de datos.
Una vez una transacción ha empezado, puedes ejecutar consultas de Fluent de manera normal. Sin embargo, ningún dato será guardado en la base de datos hasta que la transacción se complete.
Si se lanza un error en algún momento durante la transacción (por ti o por la base de datos), no se efectuará ningún cambio.
Para llevar a cabo una transacción, necesitas acceso a algo que pueda conectar con la base de datos. Normalmente, esto es una petición HTTP entrante. Para esto, usa `req.db.transaction(_ :)`:
```swift
req.db.transaction { database in
// usar la base de datos
}
```
Una vez dentro del closure de la transacción, debes usar la base de datos proporcionada en el parámetro del closure (llamado `database` en el ejemplo) para hacer consultas.
Cuando este closure devuelva de manera exitosa, se hará la transacción.
```swift
var sun: Star = ...
var sirius: Star = ...
return req.db.transaction { database in
return sun.save(on: database).flatMap { _ in
return sirius.save(on: database)
}
}
```
El ejemplo anterior guardará `sun` y *después* `sirius` antes de completar la transacción. Si falla el guardado de cualquiera de las estrellas, ninguna se guardará.
Una vez la transacción se haya completado, el resultado puede transformarse en un futuro diferente, como una respuesta HTTP que indique la finalización, de manera similar al siguiente ejemplo:
```swift
return req.db.transaction { database in
// usa la base de datos y ejecuta la transacción
}.transform(to: HTTPStatus.ok)
```
## `async`/`await`
Si usas `async`/`await` puedes refactorizar el código de la siguiente manera:
```swift
try await req.db.transaction { transaction in
try await sun.save(on: transaction)
try await sirius.save(on: transaction)
}
return .ok
```

View File

@ -0,0 +1,45 @@
# Transactions
Les transactions vous permettent de garantir que plusieurs opérations se terminent avec succès avant d'enregistrer les données dans votre base de données.
Une fois une transaction démarrée, vous pouvez exécuter des requêtes Fluent normalement. Cependant, aucune donnée ne sera enregistrée dans la base de données tant que la transaction n'est pas terminée.
Si une erreur est générée à tout moment au cours de la transaction (par vous ou par la base de données), aucune des modifications ne prendra effet.
Pour effectuer une transaction, vous devez accéder à quelque chose qui peut se connecter à la base de données. Il s'agit généralement d'une requête HTTP entrante. Pour cela, utilisez `req.db.transaction(_ :)` :
```swift
req.db.transaction { database in
// utiliser la base de données
}
```
Une fois à l'intérieur de la closure de la transaction, vous devez utiliser la base de données fournie dans le paramètre de closure (nommée `database` dans l'exemple) pour effectuer des requêtes.
Une fois cette fermeture réussie, la transaction sera validée.
```swift
var sun: Star = ...
var sirius: Star = ...
return req.db.transaction { database in
return sun.save(on: database).flatMap { _ in
return sirius.save(on: database)
}
}
```
L'exemple ci-dessus enregistrera `sun` et *puis* `sirius` avant de terminer la transaction. Si lune des étoiles ne parvient pas à sauvegarder, aucune des deux ne le fera.
Une fois la transaction terminée, le résultat peut être transformé dans un futur différent, par exemple en un statut HTTP pour indiquer la fin, comme indiqué ci-dessous :
```swift
return req.db.transaction { database in
// utiliser la base de données et effectue une transaction
}.transform(to: HTTPStatus.ok)
```
## `async`/`await`
Si vous utilisez `async`/`await`, vous pouvez refactoriser le code comme suit :
```swift
try await req.db.transaction { transaction in
try await sun.save(on: transaction)
try await sirius.save(on: transaction)
}
return .ok
```

View File

@ -1,6 +1,6 @@
# Struttura della Cartella
Dopo aver creato, compilato ed eseguito la vostra prima applicazione, è il momento di dare un'occhiata a come Vapor struttura la cartella del progetto. La struttura si basa su [SwiftPM](spm.md), quindi se avete già familiarità con SwiftPM vi sentirete a casa.
Dopo aver creato, compilato ed eseguito la tua prima applicazione, è il momento di dare un'occhiata a come Vapor struttura le cartelle del progetto. La struttura si basa su [SwiftPM](spm.md), quindi se hai già familiarità con SwiftPM ti sentirai a casa.
```
.
@ -23,9 +23,9 @@ Le seguenti sezioni spiegano in maggior dettaglio la struttura della cartella.
## Public
Questa cartella contiene tutti i file pubblici che saranno messi a disposizione dall'applicazione se `FileMiddleware` è abilitato. In genere si tratta di immagini, fogli di stile e script del browser. Ad esempio, una richiesta a `localhost:8080/favicon.ico` controlla se `Public/favicon.ico` esiste e lo restituisce.
Questa cartella contiene tutti i file pubblici che saranno messi a disposizione dall'applicazione se `FileMiddleware` è abilitato. In genere si tratta di immagini, style sheet e script del browser. Ad esempio, una richiesta a `localhost:8080/favicon.ico` controlla se `Public/favicon.ico` esiste e lo restituisce.
Perché Vapor possa servire i file pubblici, bisognerà abilitare `FileMiddleware` nel file `configure.swift`.
Affinché Vapor possa servire i file pubblici, bisognerà abilitare `FileMiddleware` nel file `configure.swift`.
```swift
// Fornisce i file dalla cartella `Public/`
@ -38,7 +38,7 @@ app.middleware.use(fileMiddleware)
## Sources
Questa cartella contiene tutti i file sorgente Swift che verranno utilizzati dal progetto.
La cartella di primo livello, `App`, riflette il modulo del vostro pacchetto, come dichiarato nel manifesto [SwiftPM](spm.md).
La cartella di primo livello, `App`, riflette il modulo del tuo pacchetto, come dichiarato nel manifesto [SwiftPM](spm.md).
### App
@ -66,15 +66,15 @@ Questo file contiene la funzione `main(_:)`. Questo metodo viene chiamato dal si
#### routes.swift
Questo file contiene la funzione `routes(_:)`. Questo metodo viene chiamato da `configure(_:)` per registrare gli endpoints dell'applicazione.
Questo file contiene la funzione `routes(_:)`. Questo metodo viene chiamato da `configure(_:)` per registrare gli endpoint dell'applicazione.
## Tests
Per ogni mdulo non eseguibile nella cartella `Sources` si può avere una cartella corrispondente in `Tests`. Essa conterrà i test per quel modulo scritti sulla base del modulo di testing `XCTest`. I test possono essere eseguiti utilizzando `swift test` da riga di comando o premendo ⌘+U in Xcode.
Per ogni modulo non eseguibile nella cartella `Sources` si può avere una cartella corrispondente in `Tests`. Essa conterrà i test per quel modulo scritti sulla base del modulo di testing `XCTest`. I test possono essere eseguiti utilizzando `swift test` da riga di comando o premendo ⌘+U in Xcode.
### AppTests
Questa cartella contiene gli unit tests per il codice del modulo `App`.
Questa cartella contiene gli unit test per il codice del modulo `App`.
## Package.swift

View File

@ -0,0 +1,81 @@
# フォルダ構造
あなたの初めての Vapor アプリを作成し、ビルドし、実行したので、Vapor のフォルダ構造に慣れるための時間を取りましょう。この構造は、[SPM](spm.ja.md) のフォルダ構造に基づいていますので、以前に SPM を使ったことがあれば、見慣れているはずです。
```
.
├── Public
├── Sources
│ ├── App
│ │ ├── Controllers
│ │ ├── Migrations
│ │ ├── Models
│ │ ├── configure.swift
│ │ ├── entrypoint.swift
│ │ └── routes.swift
├── Tests
│ └── AppTests
└── Package.swift
```
以下のセクションでは、フォルダ構造の各部分について詳しく説明します。
## Public
このフォルダには、`FileMiddleware` が有効になっている場合にアプリによって提供される公開ファイルが含まれます。これは通常、画像やスタイルシート、ブラウザスクリプトです。例えば、`localhost:8080/favicon.ico` へのリクエストは、`Public/favicon.ico` が存在するかどうかを確認し、それを返します。
Vapor が公開ファイルを提供できるようにする前に、`configure.swift` ファイルで `FileMiddleware` を有効にする必要があります。
```swift
// Serves files from `Public/` directory
let fileMiddleware = FileMiddleware(
publicDirectory: app.directory.publicDirectory
)
app.middleware.use(fileMiddleware)
```
## Sources
このフォルダには、プロジェクトのすべての Swift ソースファイルが含まれています。
トップレベルのフォルダ、`App` は、[SwiftPM](spm.ja.md) のマニフェストで宣言されたパッケージのモジュール反映をしています。
### App
ここには、アプリケーションの全てのロジックが入ります。
#### Controllers
Controllers は、アプリケーションのロジックをまとめるのに適しています。ほとんどのコントローラには、リクエストを受け取り、何らかの形でレスポンスを返す多くの関数があります。
#### Migrations
Migrations フォルダは、Fluent を使用している場合、データベースの移行を格納する場所です。
#### Models
Models フォルダは、`Content` 構造体や Fluent の `Model` を保存するのに適しています。
#### configure.swift
このファイルには、`configure(_:)` 関数が含まれています。このメソッドは、新しく作成された `Application` を設定するために `entrypoint.swift` から呼び出されます。ここで、ルート、データベース、プロバイダなどのサービスの登録をする必要があります。
#### entrypoint.swift
このファイルには、Vapor アプリケーションの設定と実行を行うアプリケーションの `@main` エントリーポイントが含まれています。
#### routes.swift
このファイルには、`routes(_:)` 関数が含まれています。このメソッドは、`Application` へのルートを登録するために、`configure(_:)` の終わり近くで呼び出されます。
## Tests
`Sources` フォルダ内の各非実行可能モジュールには、`Tests` の対応するフォルダがあります。これには、パッケージのテストのために `XCTest` モジュールに基づいてビルドされたコードが含まれています。テストは、コマンドラインで `swift test` を使用するか、Xcode で ⌘+U を押すと実行できます。
### AppTests
このフォルダには、`App` モジュールのコードの単体テストが含まれています。
## Package.swift
最後に、[SPM](spm.ja.md) のパッケージマニフェストがあります。

View File

@ -1,15 +1,15 @@
# Ciao, mondo
Questa guida vi mostrerà, passo dopo passo, come creare, compilare ed eseguire il vostro primo progetto con Vapor.
Questa guida ti mostrerà, passo dopo passo, come creare, compilare ed eseguire il tuo primo progetto con Vapor.
Se non avete ancora installato Swift o la Toolbox Vapor, seguite la guida di installazione prima di continuare.
Se non hai ancora installato Swift o la Toolbox Vapor, segui la guida di installazione prima di continuare.
- [Installazione &rarr; macOS](../install/macos.md)
- [Installazione &rarr; Linux](../install/linux.md)
## Nuovo Progetto
Il primo passo è creare un nuovo progetto Vapor sul vostro computer. Aprite il terminale e utilizzate il comando `new` della Toolbox. Questo creerà una nuova cartella nella directory corrente contenente il progetto.
Il primo passo è creare un nuovo progetto Vapor sul tuo computer. Apri il terminale e utilizza il comando `new` della Toolbox. Questo creerà una nuova cartella nella directory corrente contenente il progetto.
```sh
vapor new hello -n
@ -20,10 +20,10 @@ vapor new hello -n
!!! tip
Vapor e il template ora utilizzano `async`/`await` di default.
Se non potete aggiornare a macOS 12 e/o avete bisogno di continuare ad utilizzare gli `EventLoopFuture`s,
utilizzate l'opzione `--branch macos10-15`.
Se non puoi aggiornare a macOS 12 e/o hai bisogno di continuare ad utilizzare gli `EventLoopFuture`,
utilizza l'opzione `--branch macos10-15`.
Una volta terminato il comando, entrate nella cartella appena creata:
Una volta terminato il comando, entra nella cartella appena creata:
```sh
cd hello
@ -33,17 +33,17 @@ cd hello
### Xcode
Per prima cosa, aprite il progetto in Xcode:
Per prima cosa, apri il progetto in Xcode:
```sh
open Package.swift
```
Xcode inizierà automaticamente a scaricare le dipendenze di Swift Package Manager. La prima volta che aprite un progetto ci vorrà un po' di tempo. Quando la risoluzione delle dipendenze sarà completata, Xcode popolerà gli schemi disponibili.
Xcode inizierà automaticamente a scaricare le dipendenze di Swift Package Manager. La prima volta che apri un progetto ci vorrà un po' di tempo. Quando la risoluzione delle dipendenze sarà completata, Xcode popolerà gli schemi disponibili.
Nella parte superiore della finestra, alla destra dei pulsanti Play e Stop, cliccate sul nome del progetto per selezionare lo schema del progetto e selezionate un target di esecuzione appropriato, spesso "My Mac". Cliccate sul pulsante play per compilare ed eseguire il progetto.
Nella parte superiore della finestra, alla destra dei pulsanti Play e Stop, clicca sul nome del progetto per selezionare lo schema del progetto e seleziona un target di esecuzione appropriato, spesso "My Mac". Clicca sul pulsante play per compilare ed eseguire il progetto.
Dovreste ora veder apparire la Console nella parte inferiore della finestra di Xcode.
Dovresti ora veder apparire la Console nella parte inferiore della finestra di Xcode.
```sh
[ INFO ] Server starting on http://
@ -51,15 +51,15 @@ Dovreste ora veder apparire la Console nella parte inferiore della finestra di X
### Linux
Su Linux e altri sistemi operativi (e anche su macOS se non volete utilizzare Xcode) potete modificare il progetto nel vostro editor preferito, come Vim o VSCode. Per maggiori dettagli su come configurare altri IDE, consultate le [Guide di Swift sul Server](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md)
Su Linux e altri sistemi operativi (e anche su macOS se non volete utilizzare Xcode) puoi modificare il progetto nel tuo editor preferito, come Vim o VSCode. Per maggiori dettagli su come configurare altri IDE, consulta le [Guide di Swift sul Server](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md)
Per compilare ed eseguire il progetto, nel Terminale eseguite:
Per compilare ed eseguire il progetto, nel Terminale esegui:
```sh
swift run
```
Questo comando compilerà ed eseguirà il progetto. La prima volta che lo eseguite ci vorrà un po' di tempo per scaricare e indicizzare le dipendenze. Una volta avviato, dovrebbe apparire il seguente codice nel terminale:
Questo comando compilerà ed eseguirà il progetto. La prima volta che lo esegui ci vorrà un po' di tempo per scaricare e indicizzare le dipendenze. Una volta avviato, dovrebbe apparire il seguente codice nel terminale:
```sh
[ INFO ] Server starting on http://127.0.0.1:8080
@ -67,7 +67,7 @@ Questo comando compilerà ed eseguirà il progetto. La prima volta che lo esegui
## Visitare Localhost
Ora che il progetto è in esecuzione, aprite il vostro browser e visitate <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> oppure <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a>.
Ora che il progetto è in esecuzione, apri il tuo browser e visita <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> oppure <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a>.
Dovrebbe apparire la seguente pagina:
@ -75,4 +75,4 @@ Dovrebbe apparire la seguente pagina:
Hello, world!
```
Congratulazioni per aver creato, compilato ed eseguito il vostro primo progetto Vapor! 🎉
Congratulazioni per aver creato, compilato ed eseguito il tuo primo progetto Vapor! 🎉

View File

@ -0,0 +1,84 @@
# Hello, world
このガイドは、新しい Vapor プロジェクトを作成、ビルド、サーバーを実行する手順を説明します。
まだ、Swift や Vapor ツールボックスをインストールしていない場合は、インストールセクションを参照してください。
- [Install &rarr; macOS](../install/macos.ja.md)
- [Install &rarr; Linux](../install/linux.ja.md)
## 新規プロジェクト
最初のステップは、コンピュータに新しい Vapor プロジェクトを作成することです。ターミナルを開き、ツールボックスの新規プロジェクトコマンドを使用してください。これにより、現在のディレクトリにプロジェクトを含む新しいフォルダが作成されます。
```sh
vapor new hello -n
```
!!! tip
`-n` フラグは、すべての質問に自動的に「いいえ」と答えることで、ベアボーンのテンプレートを提供します。
!!! tip
Vapor ツールボックスを使用せずに GitHub [テンプレートリポジトリ](https://github.com/vapor/template-bare)をクローンして最新のテンプレートを取得することもできます。
!!! tip
Vapor and the template now uses `async`/`await` by default.
Vapor テンプレートは、デフォルトで `async`/`await` を使用します。
macOS 12 にアップデートできない、または `EventLoopFuture` を継続して使用する必要がある場合は、
`--branch macos10-15` フラグを使います。
コマンドが完了したら、新しく作成されたフォルダに移動します。
```sh
cd hello
```
## ビルド & 実行
### Xcode
まず、Xcode でプロジェクトを開きます:
```sh
open Package.swift
```
自動的に Swift Package Manager の依存関係をダウンロードし始めます。プロジェクトを初めて開くとき、時間がかかることがあります。依存関係の解決が完了すると、Xcode は利用可能なスキームを表示します。
ウィンドウの上部に、再生ボタンと停止ボタンの右側にあるプロジェクト名をクリックして、プロジェクトのスキームを選択します。適切な実行ターゲットを選択してください。おそらく、"My Mac" が適しているでしょう。プレイボタンをクリックして、プロジェクトをビルドして実行します。
Xcode のウィンドウの下部に、コンソールが表示されるはずです。
```sh
[ INFO ] Server starting on http://127.0.0.1:8080
```
### Linux
Linux やその他 OS (または Xcode を使用したくない場合の macOS も含む)では、 Vim や VScode のようなお好きなエディタでプロジェクトを編集できます。他の IDE の設定に関する最新の詳細は、[Swift Server ガイド](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md)を参照してください。
プロジェクトをビルドして実行するには、ターミナルで以下のコマンドを実行します:
```sh
swift run
```
これにより、プロジェクトがビルドされて実行されます。初めてこれを実行すると、依存関係を取得および解決するのに時間がかかります。実行が開始されると、コンソールに以下の内容が表示されるはずです:
```sh
[ INFO ] Server starting on http://127.0.0.1:8080
```
## Localhost へのアクセス
ウェブブラウザを開き、<a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> または <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a> にアクセスしてください。
次のページが表示されるはずです。
```html
Hello, world!
```
おめでとうございます! Vapor アプリの作成、ビルド、実行することに成功しました! 🎉

View File

@ -1,12 +1,12 @@
# Swift Package Manager
Il [Swift Package Manager](https://swift.org/package-manager/) (SPM) è utilizzato per la compilazione del codice sorgente e delle dipendenze del vostro progetto. Poiché Vapor si basa molto su SPM, è una buona idea capire i suoi funzionamenti di base.
Il [Swift Package Manager](https://swift.org/package-manager/) (SPM) è utilizzato per la compilazione del codice sorgente e delle dipendenze del tuo progetto. Poiché Vapor si basa molto su SPM, è una buona idea capire i suoi funzionamenti di base.
SPM è simile a Cocoapods, Ruby gems e NPM. Si può utilizzare SPM dalla riga di comando con comandi come `swift build` e `swift test` o con IDE compatibili. Tuttavia, a differenza di alcuni altri package manager, non esiste un indice centrale dei pacchetti SPM. Esso sfrutta invece gli URL delle repository Git e le dipendenze delle versioni utilizzando i [tag Git](https://git-scm.com/book/en/v2/Git-Basics-Tagging).
## Manifesto del Pacchetto
Il primo posto in cui SPM cerca nel vostro progetto è il manifesto del pacchetto. Questo dovrebbe sempre essere situato nella directory principale del vostro progetto e chiamarsi `Package.swift`.
Il primo posto che SPM cerca nel tuo progetto è il manifesto del pacchetto. Questo dovrebbe sempre essere situato nella directory principale del tuo progetto e chiamarsi `Package.swift`.
Diamo un'occhiata a questo esempio di manifesto del pacchetto.
@ -60,18 +60,18 @@ Nell'esempio precedente, si può notare che il pacchetto dipende da [vapor/vapor
### Target
I target sono i moduli che compongono il vostro pacchetto. I target possono essere eseguibili, librerie o test. Solitamente un progetto Vapor ha due target, tuttavia se ne possono aggiungere in modo da organizzare il codice.
I target sono i moduli che compongono il tuo pacchetto. I target possono essere eseguibili, librerie o test. Solitamente un progetto Vapor ha due target, tuttavia se ne possono aggiungere in modo da organizzare il codice.
Ogni target dichiara i moduli da cui dipende. Per poter importare ed usare i vari moduli nel codice bisogna dichiarare qui i loro nomi. Un target può dipendere da altri target nello stesso pacchetto o da qualsiasi modulo presente nei pacchetti aggiunto all'array delle [dipendenze principali](#dependencies).
## Struttura della Cartella
Questa è la tipica struttura di una cartella di un pacchetto SPM:
Questa è la tipica struttura della cartella di un pacchetto SPM:
```
.
├── Sources
│ └── App
│ └── (Source code)
│ └── (Codice sorgente)
├── Tests
│ └── AppTests
└── Package.swift
@ -88,7 +88,7 @@ Per aggiornare le dipendenze basta eseguire `swift package update` e SPM aggiorn
# Xcode
Usando Xcode qualsiasi cambiamento a dipendenze, target, prodotti ecc. sarà automatico non appena si salva il file `Package.swift`.
Usando Xcode qualsiasi cambiamento a dipendenze, target, prodotti, ecc. sarà automatico non appena si salva il file `Package.swift`.
Per aggiornare le dipendenze, basta andare su File &rarr; Swift Packages &rarr; Update to Latest Package Versions.

View File

@ -0,0 +1,95 @@
# Swift Package Manager
[Swift Package Manager](https://swift.org/package-manager/) (SPM) は、プロジェクトのソースコードと依存関係のビルドに使用されます。Vapor は SPM に大きく依存しているため、SPM の基本的な動作を理解することがおすすめです。
SPM は Cocoapods 、Ruby gems 、 NPM に似ています。SPM は、`swift build` や `swift test` などのコマンドでコマンドラインから使用することも、互換性のある IDE から使用することもできます。しかし、他のパッケージマネージャとは異なり、SPM パッケージのための中央のパッケージインデックスは存在しません。SPM は代わりに、Git リポジトリへの URL を利用し、[Git タグ](https://git-scm.com/book/en/v2/Git-Basics-Tagging)を使用して依存関係のバージョンを管理します。
## パッケージマニフェスト
SPM がプロジェクトで最初に探す場所は、パッケージマニフェストです。これは常にプロジェクトのルールディレクトリに配置され、`Package.swift` という名前でなければなりません。
以下は、パッケージマニフェストの例です。
```swift
// swift-tools-version:5.8
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.macOS(.v12)
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.76.0"),
],
targets: [
.executableTarget(
name: "App",
dependencies: [
.product(name: "Vapor", package: "vapor")
]
),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
```
以下のセクションでは、マニフェストの各部分を説明していきます。
### Tools Version
パッケージマニフェストの最初の行は、必要な Swift ツールのバージョンを示しています。これは、パッケージがサポートする Swift のバージョン間で Package description API が変更される場合があるため、この行は Swift がマニフェストをどのように解析するかを知らせるために必要ようです。
### Package Name
`Package` への最初の引数は、パッケージの名前です。パッケージが公開されている場合、Git リポジトリの URL の最後のセグメントを名前として使用する必要があります。
### Platforms
`platforms` 配列は、このパッケージがサポートするプラットフォームを指定します。
`.macOS(.v12)` を指定することで、このパッケージは macOS 12 以降が必要です。Xcode でこのプロジェクトをロードすると、利用可能な API を全て使用できるように、最小のデプロイメントバージョンが自動的に macOS 12 に設定されます。
### Dependencies
依存関係は、パッケージが依存している他の SPM パッケージです。全ての Vapor アプリケーションは Vapor パッケージに依存していますが、必要に応じて他の依存関係を追加することができます。
上の例では、[vapor/vapor](https://github.com/vapor/vapor)のバージョン 4.76.0 以降がこのパッケージの依存関係であることがわかります。パッケージに依存関係を追加すると、次にどの[ターゲット](#targets)が新しく利用可能になったモジュールに依存しているかを示す必要があります。
### Targets
ターゲットは、パッケージが含むすべてのモジュール、実行ファイル、テストです。ほとんどの Vapor アプリは 2 つのターゲットを持っていますが、コードを整理するために必要なだけ多くのターゲットを追加することができます。各ターゲットは、それが依存するモジュールを宣言します。コード内でそれらをインポートするためには、ここでモジュールの名前を追加する必要があります。ターゲットは、プロジェクト内の他のターゲットや、[主な依存関係](#dependencies)配列に追加したパッケージで公開されている任意のモジュールに依存することができます。
## フォルダ構造
以下は、SPM パッケージの典型的なフォルダ構造です。
```
.
├── Sources
│ └── App
│ └── (Source code)
├── Tests
│ └── AppTests
└── Package.swift
```
`.target``.executableTarget` は、`Sources` 内のフォルダに対応しています。
各`.testTarget` は、`Tests` 内のフォルダに対応しています。
## Package.resolved
プロジェクトを初めてビルドすると、SPM は各依存関係のバージョンを保存する `Package.resolved` ファイルを作成します。次にプロジェクトをビルドするとき、新しいバージョンが利用可能であっても、これらの同じバージョンが使用されます。
依存関係を更新するには、`swift package update` を実行します。
## Xcode
もし、Xocde 11 以降を使用している場合、`Package.swift` ファイルが変更されるたびに、依存関係、ターゲット、プロダクトなどの変更が自動的に行われます。
最新の依存関係に更新するには、File &rarr; Swift Packages &rarr; Update To Latest Swift Package Versions を使用します。
また、`.swiftpm` ファイルを `.gitignore` に追加することもおすすめします。これは、Xcode がXcode project の設定を保存する場所です。

View File

@ -1,12 +1,12 @@
# Xcode
Questa pagina contiene alcuni consigli e trucchi per l'utilizzo di Xcode. Non è necessaria se si preferisce usare un ambiente di sviluppo diverso.
Questa pagina contiene alcuni consigli e trucchi per l'utilizzo di Xcode. Puoi saltarla se preferisci usare un ambiente di sviluppo diverso.
## Directory di lavoro personalizzata
Di default Xcode eseguirà il progetto dalla cartella _DerivedData_. Questa cartella non è la stessa della cartella principale del progetto (dove si trova il file _Package.swift_). Questo significa che Vapor non sarà in grado di trovare file e cartelle come _.env_ o _Public_.
Si può notare che questo sta accadendo se si vede il seguente avviso quando si esegue il progetto.
Si capisce se questo sta accadendo se si vede il seguente avviso quando si esegue il progetto.
```fish
[ WARNING ] No custom working directory set for this scheme, using /path/to/DerivedData/project-abcdef/Build/
@ -40,5 +40,5 @@ Si dovrebbe vedere un output simile al seguente.
```
framework: 4.x.x
toolbox: 18.x.x
/path/to/project
/percorso/al/progetto
```

View File

@ -0,0 +1,44 @@
# Xcode
このページでは、Xcode の仕様に関するいくつかのヒントとテクニックを紹介します。異なる開発環境を使用している場合、このセクションはスキップしてもよいです。
## カスタムワーキングディレクトリ
デフォルトでは、Xcode はあなたのプロジェクトを _DerivedData_ フォルダから実行します。このフォルダは、プロジェクトのルートフォルダ ( _Package.swift_ ファイルがある場所) とは異なります。これは、 Vapor が _.env__Public_ のようなファイルやフォルダを見つけることができないことを意味します。
アプリを実行するときに以下の警告が表示される場合、この問題が発生していることがわかります。
```fish
[ WARNING ] No custom working directory set for this scheme, using /path/to/DerivedData/project-abcdef/Build/
```
これを修正するには、プロジェクトの Xcode スキームでカスタムワーキングディレクトリを設定します。
まず、プレイボタンとストップボタンの隣にあるスキームセレクタをクリックして、プロジェクトのスキームを編集します。
![Xcode Scheme Area](../images/xcode-scheme-area.png)
ドロップダウンから _Edit Scheme..._ を選択します。
![Xcode Scheme Menu](../images/xcode-scheme-menu.png)
スキームエディタで、_App_ アクションと _Options_ タブを選択します。_Use custom working directory_ をチェックし、プロジェクトのルートフォルダへのパスを入力します。
![Xcode Scheme Options](../images/xcode-scheme-options.png)
プロジェクトのルートへのフルパスは、その場所で開いたターミナルウィンドウから `pwd` を実行することで取得できます。
```fish
# verify we are in vapor project folder
vapor --version
# get path to this folder
pwd
```
以下のような出力が表示されるはずです。
```
framework: 4.x.x
toolbox: 18.x.x
/path/to/project
```

View File

@ -1,14 +1,14 @@
Benvenuti alla documentazione di Vapor! Vapor è un framework web per Swift. Esso permette di creare backend, applicazioni web, API e server HTTP in Swift. Vapor è scritto in Swift, un linguaggio moderno, potente e sicuro che fornisce diversi benefici rispetto a linguaggi lato server tradizionali.
Benvenuto nella documentazione di Vapor! Vapor è un framework web per Swift. Permette di creare backend, applicazioni web, API e server HTTP in Swift. Vapor è scritto in Swift, un linguaggio moderno, potente e sicuro che fornisce diversi benefici rispetto a linguaggi lato server tradizionali.
## Inizio
Se è la prima volta che utilizzate Vapor, dirigetevi a [Installazione → macOS](install/macos.md) per installare Swift e Vapor.
Se è la prima volta che utilizzi Vapor, vai su [Installazione → macOS](install/macos.md) per installare Swift e Vapor.
Una volta installato Vapor, date un'occhiata a [Inizio → Ciao, mondo](getting-started/hello-world.md) per creare la vostra prima applicazione Vapor!
Una volta installato Vapor, date un'occhiata a [Inizio → Ciao, mondo](getting-started/hello-world.md) per creare la tua prima applicazione Vapor!
## Altre Fonti
Elenchiamo alcuni altri ottimi posti dove trovare informazioni su Vapor.
Ecco alcuni altri ottimi posti dove trovare informazioni su Vapor.
| nome | descrizione | link |
|----------------|--------------------------------------------------|-------------------------------------------------------------------|
@ -19,6 +19,6 @@ Elenchiamo alcuni altri ottimi posti dove trovare informazioni su Vapor.
| Codice Sorgente | Scopri come funziona Vapor sotto il cofano. | [visita &rarr;](https://github.com/vapor/vapor) |
| Issues di GitHub | Segnala bug o richiedi funzionalità su GitHub. | [visita &rarr;](https://github.com/vapor/vapor/issues) |
## Documentazione Vecchia
## Documentazione Obsoleta
La documentazione per le versioni deprecate di Vapor che non sono più manutenute sono su [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/).
La documentazione per le versioni deprecate di Vapor che non sono più manutenute è su [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/).

29
docs/index.ja.md Normal file
View File

@ -0,0 +1,29 @@
Vapor ドキュメントへようこそ! Vapor は Swift のWeb フレームワークで、 Swift でバックエンド、ウェブアプリの API 、 HTTP サーバーを書くことができます。 Vapor は Swift で書いており、それは伝統的なサーバー言語よりも多くの利点を提供するモダンで強力で安全な言語です。
## はじめに
もし、Vapor を使うのが初めてならば、Swift と Vapor をインストールするために[インストール → macOS](install/macos.md)に進んでください。
Vapor をインストールしたら、初めての Vapor アプリを作成するために[はじめに → Hello, world](getting-started/hello-world.md)をチェックしてください!
## その他の情報源
Vapor に関する情報を見つけるには、他にも素晴らしい場所があります。
| 名前 | 説明 | リンク |
|----------------|--------------------------------------------------|-------------------------------------------------------------------|
| Vapor Discord | 数千人の Vapor 開発者とチャットします | [visit &rarr;](https://vapor.team) |
| API ドキュメント | コードコメントから自動生成されたドキュメント | [visit &rarr;](https://api.vapor.codes) |
| Stack Overflow | `vapor` タグで質問し回答します | [visit &rarr;](https://stackoverflow.com/questions/tagged/vapor) |
| Swift フォーラム | Swift.org フォーラムの Vapor セクションに投稿します | [visit &rarr;](https://forums.swift.org/c/related-projects/vapor) |
| ソースコード | Vapor の内部での動作を学びます | [visit &rarr;](https://github.com/vapor/vapor) |
| GitHub Issues | GitHub でバグを報告したり、機能をリクエストします | [visit &rarr;](https://github.com/vapor/vapor/issues) |
## 旧ドキュメント
廃止されたバージョンの Vapor ドキュメントは、[https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/)で見ることができます。
## 著者
Vapor コアチーム、および Vapor コミュニティの数百人のメンバー。

View File

@ -1,13 +1,13 @@
# Installazione su Linux
Per usare Vapor, avrete bisogno di Swift 5.2 o superiore. Potete installarlo usando le toolchains disponibili su [Swift.org](https://swift.org/download/)
Per usare Vapor, avrai bisogno di Swift 5.2 o superiore. Puoi installarlo usando le toolchain disponibili su [Swift.org](https://swift.org/download/)
## Distribuzioni e Versioni supportate
Vapor supporta le stesse versioni delle distribuzioni Linux che supportano Swift 5.2 o versioni più recenti.
!!! note
Le versioni supportate elencate di seguito potrebbero essere obsolete in qualsiasi momento. Potete controllare quali sistemi operativi sono ufficialmente supportati sulla pagina [Swift Releases](https://swift.org/download/#releases).
Le versioni supportate elencate di seguito potrebbero diventare obsolete in qualsiasi momento. Puoi controllare quali sistemi operativi sono ufficialmente supportati sulla pagina [Swift Releases](https://swift.org/download/#releases).
|Distribuzione|Versione|Versione di Swift|
|-|-|-|
@ -17,11 +17,11 @@ Vapor supporta le stesse versioni delle distribuzioni Linux che supportano Swift
|CentOS|8|>= 5.2.4|
|Amazon Linux|2|>= 5.2.4|
Le distribuzioni Linux non ufficialmente supportate possono comunque eseguire Swift compilando il codice sorgente, ma Vapor non può garantirne la stabilità. Potete saperne di più sulla compilazione di Swift dal [repo di Swift](https://github.com/apple/swift#getting-started).
Le distribuzioni Linux non ufficialmente supportate possono comunque eseguire Swift compilando il codice sorgente, ma Vapor non può garantirne la stabilità. Puoi saperne di più sulla compilazione di Swift dal [repo di Swift](https://github.com/apple/swift#getting-started).
## Installare Swift
Visitate la guida [Using Downloads](https://swift.org/download/#using-downloads) di Swift.org per le istruzioni su come installare Swift su Linux.
Visita la guida [Using Downloads](https://swift.org/download/#using-downloads) di Swift.org per le istruzioni su come installare Swift su Linux.
### Fedora
@ -31,17 +31,17 @@ Gli utenti Fedora possono semplicemente usare il seguente comando per installare
sudo dnf install swift-lang
```
Se state usando Fedora 30, dovrete aggiungere EPEL 8 per ottenere Swift 5.2 o versioni più recenti.
Se stai usando Fedora 30, dovrai aggiungere EPEL 8 per ottenere Swift 5.2 o versioni più recenti.
## Docker
Potete anche usare le immagini Docker ufficiali di Swift che includono il compilatore preinstallato. Potete saperne di più sul [Docker Hub di Swift](https://hub.docker.com/_/swift).
Puoi anche usare le immagini Docker ufficiali di Swift che includono il compilatore preinstallato. Puoi saperne di più sul [Docker Hub di Swift](https://hub.docker.com/_/swift).
## Installare la Toolbox
Ora che avete installato Swift, potete installare la [Toolbox di Vapor](https://github.com/vapor/toolbox). Questo strumento CLI non è necessario per usare Vapor, ma include degli strumenti utili.
Ora che hai installato Swift, puoi installare la [Toolbox di Vapor](https://github.com/vapor/toolbox). Questo strumento CLI non è necessario per usare Vapor, ma include degli strumenti utili.
Su Linux, dovrete compilare la toolbox dal codice sorgente. Guardate le <a href="https://github.com/vapor/toolbox/releases" target="_blank"> release </a> della toolbox su GitHub per trovare l'ultima versione.
Su Linux, dovrai compilare la toolbox dal codice sorgente. Guarda le <a href="https://github.com/vapor/toolbox/releases" target="_blank"> release </a> della toolbox su GitHub per trovare l'ultima versione.
```sh
git clone https://github.com/vapor/toolbox.git
@ -50,14 +50,14 @@ git checkout <desired version>
make install
```
Controllate che l'installazione sia andata a buon fine stampando l'aiuto.
Controlla che l'installazione sia andata a buon fine stampando l'aiuto.
```sh
vapor --help
```
Dovreste vedere una lista di comandi disponibili.
Dovresti vedere una lista di comandi disponibili.
## Come continuare
Dopo aver installato Vapor, potete iniziare a creare il vostro primo progetto usando [Inizio &rarr; Ciao, mondo](../getting-started/hello-world.it.md).
Dopo aver installato Vapor, puoi iniziare a creare il tuo primo progetto usando [Inizio &rarr; Ciao, mondo](../getting-started/hello-world.it.md).

75
docs/install/linux.ja.md Normal file
View File

@ -0,0 +1,75 @@
# Linux にインストール
Vapor を使うには、Swift 5.7 以上が必要です。これは Swift Server Workgroup が提供する CLI ツール [Swiftly](https://swift-server.github.io/swiftly/) を使ってインストールできます(推奨)。または、[Swift.org](https://swift.org/download/) で利用可能なツールチェーンを使用してインストールできます。
## サポートされているディストリビューションとバージョン
Vapor は、Swift 5.7 またはそれ以上の新しいバージョンがサポートする Linux ディストリビューションと同じバージョンをサポートしています。公式にサポートされているオペレーティングシステムの最新情報については、[公式サポートページ](https://www.swift.org/platform-support/)を参照してください。
公式にはサポートされていない Linux ディストリビューションでも、ソースコードをコンパイルすることで、Swift を実行できるかもしれませんが、Vapor は安定性を保証できません。[Swift repo](https://github.com/apple/swift#getting-started) から Swift のコンパイル方法について詳しく学ぶことができます。
## Swift のインストール
### Swiftly CLI ツールを使用した自動インストール (推奨)
Linux で Swiftly と Swift をインストールする手順については、[Swifty のウェブサイト](https://swift-server.github.io/swiftly/)をご覧ください。その手順に従った後、次のコマンドで Swift をインストールします。
#### 基本的な使い方
```sh
$ swiftly install latest
Fetching the latest stable Swift release...
Installing Swift 5.9.1
Downloaded 488.5 MiB of 488.5 MiB
Extracting toolchain...
Swift 5.9.1 installed successfully!
$ swift --version
Swift version 5.9.1 (swift-5.9.1-RELEASE)
Target: x86_64-unknown-linux-gnu
```
### ツールチェーンを使用した手動インストール
Linux 上で Swift をインストールする方法については、Swift.org の[ダウンロードの使用](https://swift.org/download/#using-downloads)を参照してください。
### Fedora
Fedora ユーザーは、以下のコマンドを使用して Swift を簡単にインストールできます:
```sh
sudo dnf install swift-lang
```
Fedora 35 を使用している場合、Swift 5.6 またはそれ以降のバージョンを取得するには、EPEL8 を追加する必要があります。
## Docker
Swift の公式Docker イメージも使用できます。これにはコンパイラが事前にインストールされています。[Swift の Docker Hub](https://hub.docker.com/_/swift) で詳しく学ぶことができます。
## ツールボックスのインストール
Swift をインストールしたら、[Vapor Toolbox](https://github.com/vapor/toolbox) をインストールしましょう。この CLI ツールは、Vapor を使用するために必須ではありませんが、役立つユーティリティが含まれています。
Linux 上では、ソースからツールボックスをビルドする必要があります。GitHub のツールボックスの<a href="https://github.com/vapor/toolbox/releases" target="_blank">リリース</a>で最新バージョンを見つけてください。
```sh
git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install
```
インストールが成功したかどうかを確認するためにヘルプを表示します。
```sh
vapor --help
```
利用可能なコマンドのリストが表示されるはずです。
## 次へ
Swift をインストールしたら、[はじめに &rarr; hello, world](../getting-started/hello-world.md) で初めてのアプリを作成してください。

View File

@ -1,25 +1,38 @@
# Install on Linux
To use Vapor, you will need Swift 5.6 or greater. This can be installed using the toolchains available on [Swift.org](https://swift.org/download/)
To use Vapor, you will need Swift 5.7 or greater. This can be installed using the CLI tool [Swiftly](https://swift-server.github.io/swiftly/) provided by the Swift Server Workgroup (recommended), or the toolchains available on [Swift.org](https://swift.org/download/).
## Supported Distributions and Versions
Vapor supports the same versions of Linux distributions that Swift 5.6 or newer versions supports.
!!! note
The supported versions listed below may be outdated at any time. You can check which operating systems are officially supported on the [Swift Releases](https://swift.org/download/#releases) page.
|Distribution|Version|Swift Version|
|-|-|-|
|Ubuntu|20.04|>= 5.6|
|Fedora|>= 30|>= 5.6|
|CentOS|8|>= 5.6|
|Amazon Linux|2|>= 5.6|
Vapor supports the same versions of Linux distributions that Swift 5.7 or newer versions supports. Please refer to the [official support page](https://www.swift.org/platform-support/) in order to find updated information about which operating systems are officially supported.
Linux distributions not officially supported may also run Swift by compiling the source code, but Vapor cannot prove stability. Learn more about compiling Swift from the [Swift repo](https://github.com/apple/swift#getting-started).
## Install Swift
### Automated installation using Swiftly CLI tool (recommended)
Visit the [Swiflty website](https://swift-server.github.io/swiftly/) for instructions on how to install Swiftly and Swift on Linux. After that, install Swift with the following command:
#### Basic usage
```sh
$ swiftly install latest
Fetching the latest stable Swift release...
Installing Swift 5.9.1
Downloaded 488.5 MiB of 488.5 MiB
Extracting toolchain...
Swift 5.9.1 installed successfully!
$ swift --version
Swift version 5.9.1 (swift-5.9.1-RELEASE)
Target: x86_64-unknown-linux-gnu
```
### Manual installation with the toolchain
Visit Swift.org's [Using Downloads](https://swift.org/download/#using-downloads) guide for instructions on how to install Swift on Linux.
### Fedora
@ -30,7 +43,7 @@ Fedora users can simply use the following command to install Swift:
sudo dnf install swift-lang
```
If you're using Fedora 30, you'll need to add EPEL 8 to get Swift 5.6 or newer versions.
If you're using Fedora 35, you'll need to add EPEL 8 to get Swift 5.7 or newer versions.
## Docker

View File

@ -1,22 +1,22 @@
# Installazione su macOS
Per usare Vapor su macOS, avrete bisogno di Swift 5.6 o superiore. Swift e tutte le sue dipendenze vengono installati automaticamente quando si installa Xcode.
Per usare Vapor su macOS, avrai bisogno di Swift 5.6 o superiore. Swift e tutte le sue dipendenze vengono installati automaticamente quando si installa Xcode.
## Installare Xcode
Potete installare Xcode dal [Mac App Store](https://apps.apple.com/us/app/xcode/id497799835?mt=12).
Puoi installare Xcode dal [Mac App Store](https://apps.apple.com/us/app/xcode/id497799835?mt=12).
![Xcode nel Mac App Store](../images/xcode-mac-app-store.png)
Dopo aver scaricato Xcode, dovete aprirlo per completare l'installazione. Questo potrebbe richiedere un po' di tempo.
Dopo aver scaricato Xcode, dovrai aprirlo per completare l'installazione. Questo potrebbe richiedere un po' di tempo.
Controllate che l'installazione sia andata a buon fine aprendo il Terminale e stampando la versione di Swift.
Controlla che l'installazione sia andata a buon fine aprendo il Terminale e stampando la versione di Swift.
```sh
swift --version
```
Dovreste vedere stampate le informazioni della versione di Swift:
Dovresti vedere stampate le informazioni della versione di Swift:
```sh
swift-driver version: 1.75.2 Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
@ -27,22 +27,22 @@ Vapor 4 richiede Swift 5.6 o superiore.
## Installare la Toolbox
Ora che avete installato Swift, potete installare la [Vapor Toolbox](https://github.com/vapor/toolbox). Questo strumento da linea di comando non è necessario per usare Vapor, ma include strumenti utili come il creatore di nuovi progetti.
Ora che hai installato Swift, puoi installare la [Vapor Toolbox](https://github.com/vapor/toolbox). Questo strumento da linea di comando non è necessario per usare Vapor, ma include strumenti utili come il creatore di nuovi progetti.
La toolbox è distribuita tramite Homebrew. Se non avete ancora Homebrew, visitate <a href="https://brew.sh" target="_blank">brew.sh</a> per le istruzioni di installazione.
La toolbox è distribuita tramite Homebrew. Se non hai ancora Homebrew, visita <a href="https://brew.sh" target="_blank">brew.sh</a> per le istruzioni di installazione.
```sh
brew install vapor
```
Controllate che l'installazione sia andata a buon fine stampando l'aiuto.
Controlla che l'installazione sia andata a buon fine stampando l'aiuto.
```sh
vapor --help
```
Dovreste vedere una lista di comandi disponibili.
Dovresti vedere una lista di comandi disponibili.
## Come continuare
Dopo aver installato Vapor, potete iniziare a creare il vostro primo progetto usando [Inizio &rarr; Ciao, mondo](../getting-started/hello-world.it.md).
Dopo aver installato Vapor, puoi iniziare a creare il tuo primo progetto usando [Inizio &rarr; Ciao, mondo](../getting-started/hello-world.it.md).

49
docs/install/macos.ja.md Normal file
View File

@ -0,0 +1,49 @@
# macOS へのインストール
Vapor を macOS で使用するには、Swift 5.6 以上が必要です。Swift とそれに関連するすべての依存関係は、Xcode にバンドルされています。
## Xcode のインストール
Mac App Store から[Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) をインストールします。
![Xcode in Mac App Store](../images/xcode-mac-app-store.png)
Xcode のダウンロードが完了したら、インストールを完了するために開く必要があります。これには時間がかかる場合があります。
インストールが成功したことを確認するために、Terminal を開いて Swift のバージョンを表示してください。
```sh
swift --version
```
Swift のバージョン情報が表示されるはずです。
```sh
swift-driver version: 1.75.2 Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)
Target: arm64-apple-macosx13.0
```
Vapor 4 は、Swift 5.6 以上が必要です。
## Toolbox のインストール
Swift をインストールしたので、次に [Vapor Toolbox](https://github.com/vapor/toolbox) をインストールしましょう。このCLIツールはVapor を使用するためには必須ではありませんが、新しいプロジェクトクリエイーターのような便利なユーティリティが含まれています。
Toolbox は Homebrew 経由で配布されています。まだ Homebrew をインストールしていない場合は、<a href="https://brew.sh" target="_blank">brew.sh</a> を参照してインストール手順を確認してください。
```sh
brew install vapor
```
インストールが成功したかどうかを確認するために、ヘルプを表示してください。
```sh
vapor --help
```
利用可能なコマンドのリストが表示されるはずです。
## 次へ
Swift と Vapor Toolbox をインストールしたので、 [はじめに &rarr; Hello, world](../getting-started/hello-world.md) で初めてのアプリを作成してください。

128
docs/leaf/custom-tags.es.md Normal file
View File

@ -0,0 +1,128 @@
# Etiquetas Personalizadas
Puedes crear etiquetas personalizadas de Leaf utilizando el protocolo [`LeafTag`](https://api.vapor.codes/leafkit/documentation/leafkit/leaftag).
Para demostrarlo, vamos a crear una etiqueta personalizada `#now` que muestra la marca de tiempo actual. La etiqueta también soportará un único parámetro opcional para especificar el formato de fecha.
!!! tip "Consejo"
Si tu etiqueta personalizada muestra HTML, deberías hacer que tu etiqueta personalizada cumpla con `UnsafeUnescapedLeafTag` para que el HTML no se escape. Recuerda verificar o sanear cualquier entrada del usuario.
## `LeafTag`
Primero, crea una clase llamada `NowTag` y hazla cumplir con `LeafTag`.
```swift
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
...
}
}
```
Ahora implementemos el método `render(_:)`. El contexto `LeafContext` pasado a este método tiene todo lo que deberíamos necesitar.
```swift
enum NowTagError: Error {
case invalidFormatParameter
case tooManyParameters
}
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
let formatter = DateFormatter()
switch ctx.parameters.count {
case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
case 1:
guard let string = ctx.parameters[0].string else {
throw NowTagError.invalidFormatParameter
}
formatter.dateFormat = string
default:
throw NowTagError.tooManyParameters
}
let dateAsString = formatter.string(from: Date())
return LeafData.string(dateAsString)
}
}
```
## Configurar la Etiqueta
Ahora que hemos implementado `NowTag`, sólo necesitamos informar a Leaf sobre ella. Puedes añadir cualquier etiqueta de esta manera - incluso si provienen de un paquete separado. Típicamente haces esto en `configure.swift`:
```swift
app.leaf.tags["now"] = NowTag()
```
¡Y eso es todo! Ahora podemos usar nuestra etiqueta personalizada en Leaf.
```leaf
The time is #now()
```
## Propiedades de Contexto
El `LeafContext` contiene dos propiedades importantes. `parameters` y `data` que tienen todo lo que deberíamos necesitar.
- `parameters`: Un array que contiene los parámetros de la etiqueta.
- `data`: Un diccionario que contiene los datos de la vista pasados a `render(_:_:)` como contexto.
### Ejemplo de Etiqueta Hello
Para ver cómo usar esto, implementemos una simple etiqueta hello usando ambas propiedades.
#### Usando Parámetros
Podemos acceder al primer parámetro que contendría `name`.
```swift
enum HelloTagError: Error {
case missingNameParameter
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.parameters[0].string else {
throw HelloTagError.missingNameParameter
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
```
```leaf
#hello("John")
```
#### Usando Data
Podemos acceder al valor `name` usando la clave "name" dentro de la propiedad data.
```swift
enum HelloTagError: Error {
case nameNotFound
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.data["name"]?.string else {
throw HelloTagError.nameNotFound
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
```
```leaf
#hello()
```
_Controlador_:
```swift
return try await req.view.render("home", ["name": "John"])
```

128
docs/leaf/custom-tags.it.md Normal file
View File

@ -0,0 +1,128 @@
# Tag Personalizzati
Puoi creare tag Leaf personalizzati usando il protocollo [`LeafTag`](https://api.vapor.codes/leafkit/documentation/leafkit/leaftag).
Per mostrare come funziona, diamo un'occhiata alla creazione di un tag personalizzato `#now` che stampa l'attuale marca temporale. Il tag supporterà anche un singolo parametro opzionale per specificare il formato della data.
!!! tip
Se il tuo tag personalizzato renderizza HTML dovresti conformare il tuo tag personalizzato a `UnsafeUnescapedLeafTag` così che l'HTML non sia "escaped". Ricorda di controllare o ripulire ogni input dell'utente.
## `LeafTag`
Prima creiamo una classe chiamata `NowTag` e conformiamola a `LeafTag`.
```swift
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
...
}
}
```
Adesso implementiamo il metodo `render(_:)`. Il contesto `LeafContext` passato a questo metodo ha tutto quello che ci dovrebbe servire.
```swift
enum NowTagError: Error {
case invalidFormatParameter
case tooManyParameters
}
struct NowTag: LeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
let formatter = DateFormatter()
switch ctx.parameters.count {
case 0: formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
case 1:
guard let string = ctx.parameters[0].string else {
throw NowTagError.invalidFormatParameter
}
formatter.dateFormat = string
default:
throw NowTagError.tooManyParameters
}
let dateAsString = formatter.string(from: Date())
return LeafData.string(dateAsString)
}
}
```
## Configura il Tag
Adesso che abbiamo implementato `NowTag`, dobbiamo solo dirlo a Leaf. Puoi aggiungere qualsiasi tag così - anche se vengono da un pacchetto separato. Di solito si fa in `configure.swift`:
```swift
app.leaf.tags["now"] = NowTag()
```
Fatto! Ora possiamo usare la nostra tag personalizzata in Leaf.
```leaf
The time is #now()
```
## Proprietà del Contesto
Il `LeafContext` contiene due proprietà importanti: `parameters` e `data`, che hanno tutto quello che ci dovrebbe servire.
- `parameters`: Un array che contiene i parametri del tag;
- `data`: Un dizionario che contiene i dati della view passata a `render(_:_:)` come contesto.
### Tag Hello di Esempio
Per vedere come usarlo, implementiamo un semplice tag di saluto usando entrambe le proprietà.
#### Usando i Parametri
Possiamo accedere al primo parametro che dovrebbe contenere il nome.
```swift
enum HelloTagError: Error {
case missingNameParameter
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.parameters[0].string else {
throw HelloTagError.missingNameParameter
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
```
```leaf
#hello("John")
```
#### Usando i Dati
Possiamo accedere al valore del nome usando la chiave "name" dentro la proprietà dei dati.
```swift
enum HelloTagError: Error {
case nameNotFound
}
struct HelloTag: UnsafeUnescapedLeafTag {
func render(_ ctx: LeafContext) throws -> LeafData {
guard let name = ctx.data["name"]?.string else {
throw HelloTagError.nameNotFound
}
return LeafData.string("<p>Hello \(name)</p>")
}
}
```
```leaf
#hello()
```
_Controller_:
```swift
return try await req.view.render("home", ["name": "John"])
```

View File

@ -0,0 +1,96 @@
# Leaf
Leaf es un potente lenguaje de plantillas con una sintaxis inspirada en Swift. Puedes usarlo para generar páginas HTML dinámicas para el front-end de un sitio web o generar correos electrónicos enriquecidos para enviar desde una API.
## Paquete
El primer paso para usar Leaf es agregarlo como una dependencia en tu proyecto en tu archivo de manifiesto del paquete SPM.
```swift
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.macOS(.v10_15)
],
dependencies: [
/// Cualquier otra dependencia ...
.package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"),
],
targets: [
.target(name: "App", dependencies: [
.product(name: "Leaf", package: "leaf"),
// Cualquier otra dependencia
]),
// Otros targets
]
)
```
## Configuración
Una vez agregado el paquete a tu proyecto, puedes configurar Vapor para usarlo. Esto generalmente se hace en [`configure.swift`](../getting-started/folder-structure.md#configureswift).
```swift
import Leaf
app.views.use(.leaf)
```
Esto le indica a Vapor que use el `LeafRenderer` cuando llames a `req.view` en tu código.
!!! note "Nota"
Leaf tiene un caché interno para renderizar páginas. Cuando el entorno de `Application` está configurado en `.development`, este caché está deshabilitado, de modo que los cambios en las plantillas surten efecto de inmediato. En `.production` y en todos los demás entornos, el caché está habilitado de forma predeterminada; cualquier cambio realizado en las plantillas no surtirá efecto hasta que se reinicie la aplicación.
!!! warning "Advertencia"
Para que Leaf pueda encontrar las plantillas al ejecutar desde Xcode, debes establecer [el directorio de trabajo personalizado](../getting-started/xcode.md#custom-working-directory) para tu espacio de trabajo en Xcode.
## Estructura de Carpetas
Una vez que hayas configurado Leaf, deberás asegurarte de tener una carpeta `Views` para almacenar tus archivos `.leaf`. Por defecto, Leaf espera que la carpeta de vistas esté en `./Resources/Views` relativo a la raíz de tu proyecto.
También es probable que quieras habilitar el [`FileMiddleware`](https://api.vapor.codes/vapor/documentation/vapor/filemiddleware) de Vapor para servir archivos desde tu carpeta `/Public` si planeas servir archivos Javascript y CSS, por ejemplo.
```
VaporApp
├── Package.swift
├── Resources
│   ├── Views
│   │   └── hello.leaf
├── Public
│   ├── images (recursos de imágenes)
│   ├── styles (recursos css)
└── Sources
   └── ...
```
## Renderizando una Vista
Ahora que Leaf está configurado, vamos a renderizar tu primera plantilla. Dentro de la carpeta `Resources/Views`, crea un nuevo archivo llamado `hello.leaf` con el siguiente contenido:
```leaf
Hello, #(name)!
```
!!! tip "Consejo"
Si estás usando VSCode como tu editor de código, te recomendamos instalar la extensión de Leaf para habilitar el resaltado de sintaxis: [Leaf HTML](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf).
Luego, registra una ruta (generalmente en `routes.swift` o en un controlador) para renderizar la vista.
```swift
app.get("hello") { req -> EventLoopFuture<View> in
return req.view.render("hello", ["name": "Leaf"])
}
// o
app.get("hello") { req async throws -> View in
return try await req.view.render("hello", ["name": "Leaf"])
}
```
Esto utiliza la propiedad genérica `view` en `Request` en lugar de llamar directamente a Leaf, permitiéndote cambiar a otro renderizador en tus pruebas.
Abre tu navegador y visita `/hello`. Deberías ver `Hello, Leaf!`. ¡Felicidades por renderizar tu primera vista Leaf!

View File

@ -0,0 +1,96 @@
# Leaf
Leaf è un potente linguaggio di templating con la sintassi ispirata a Swift. Puoi usarlo per generare pagine HTML dinamiche per un sito front-end o per generare email abbellite da inviare con una API.
## Pacchetto
Il primo passo per usare Leaf è aggiungerlo come una dipendenza al tuo progetto nel tuo file di manifesto del pacchetto SPM.
```swift
// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.macOS(.v10_15)
],
dependencies: [
/// Qualsiasi altra dipendenza ...
.package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"),
],
targets: [
.target(name: "App", dependencies: [
.product(name: "Leaf", package: "leaf"),
// Qualsiasi altra dipendenza
]),
// Altri target
]
)
```
## Configura
Non appena hai aggiunto il pacchetto al tuo progetto, puoi configurare Vapor per usarlo. Di solito si fa in [`configure.swift`](../getting-started/folder-structure.md#configureswift).
```swift
import Leaf
app.views.use(.leaf)
```
Questo dice a Vapor di usare `LeafRenderer` quando chiami `req.view` nel tuo codice.
!!! note
Leaf ha una cache interna per renderizzare le pagine. Quando l'ambiente di `Application` è impostato su `.development` questa cache è disabilitata, così che i cambiamenti ai template abbiano effetto immediatamente. In `.production` e tutti gli altri ambienti la cache è abilitata di default; qualsiasi cambiamento fatto ai template non avrà effetto finché l'applicazione non viene riavviata.
!!! warning
Per fare in modo che Leaf trovi i template quando gira su Xcode, devi impostare la [directory di lavoro personalizzata](../getting-started/xcode.md#custom-working-directory) per il tuo ambiente di lavoro Xcode.
## Struttura della Cartella
Non appena hai configurato Leaf, devi assicurarti di avere una cartella `Views` dove salvare i tuoi file `.leaf`. Di default, Leaf si aspetta che la cartella delle view sia `./Resources/Views`, relativamente alla radice del tuo progetto.
Probabilmente vorrai abilitare anche il [`FileMiddleware`](https://api.vapor.codes/vapor/documentation/vapor/filemiddleware) di Vapor per servire file dalla tua cartella `/Public` se hai in mente di servire file Javascript e CSS per esempio.
```
VaporApp
├── Package.swift
├── Resources
│   ├── Views
│   │   └── hello.leaf
├── Public
│   ├── images (immagini)
│   ├── styles (risorse css)
└── Sources
   └── ...
```
## Renderizzare una View
Adesso che Leaf è configurato, renderizziamo il tuo primo template. Dentro la cartella `Resources/Views`, crea un nuovo file chiamato `hello.leaf` con il seguente contenuto:
```leaf
Hello, #(name)!
```
!!! tip
Se usi VSCode come editor di testo, raccomandiamo di installare l'estensione Leaf per abilitare l'evidenziazione della sintassi: [Leaf HTML](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf).
Quindi, registra una route (di solito fatto in `routes.swift` o un controller) per renderizzare la view.
```swift
app.get("hello") { req -> EventLoopFuture<View> in
return req.view.render("hello", ["name": "Leaf"])
}
// oppure
app.get("hello") { req async throws -> View in
return try await req.view.render("hello", ["name": "Leaf"])
}
```
Questo usa la generica proprietà `view` su `Request` invece di chiamare Leaf direttamente. Questo ti permette di passare a un renderer diverso nei tuoi test.
Apri il tuo browser e visita `/hello`. Dovresti vedere `Hello, Leaf!`. Congratulazioni per aver renderizzato la tua prima view Leaf!

285
docs/leaf/overview.es.md Normal file
View File

@ -0,0 +1,285 @@
# Presentación
Leaf es un potente lenguaje de plantillas con una sintaxis inspirada en Swift. Puedes usarlo para generar páginas HTML dinámicas destinadas a un portal web o generar correos electrónicos enriquecidos para enviar desde una API.
Esta guía te proporcionará una visión general de la sintaxis de Leaf y las etiquetas disponibles.
## Sintaxis de la plantilla
Aquí tienes un ejemplo de cómo se usa una etiqueta básica de Leaf.
```leaf
There are #count(users) users.
```
Las etiquetas de Leaf constan de cuatro elementos:
- Token `#`: Esto indica al analizador de Leaf que comience a buscar una etiqueta.
- Nombre `count`: identifica a la etiqueta.
- Parámetro `(users)`: Puede aceptar cero o más argumentos.
- Cuerpo: Algunas etiquetas pueden tener un cuerpo opcional, que se suministra usando dos puntos y una etiqueta de cierre.
Dependiendo de la implementación de la etiqueta, puede haber muchos usos diferentes de estos cuatro elementos. Veamos algunos ejemplos de cómo se podrían usar las etiquetas incorporadas de Leaf:
```leaf
#(variable)
#extend("template"): I'm added to a base template! #endextend
#export("title"): Welcome to Vapor #endexport
#import("body")
#count(friends)
#for(friend in friends): <li>#(friend.name)</li> #endfor
```
Leaf también admite muchas expresiones con las que estás familiarizado en Swift.
- `+`
- `%`
- `>`
- `==`
- `||`
- etc.
```leaf
#if(1 + 1 == 2):
Hello!
#endif
#if(index % 2 == 0):
This is even index.
#else:
This is odd index.
#endif
```
## Contexto
En el ejemplo de [Comenzando](getting-started.md), usamos un diccionario `[String: String]` para pasar datos a Leaf. Sin embargo, puedes pasar cualquier cosa conformada con `Encodable`. Es preferible usar estructuras `Encodable` ya que `[String: Any]` no está soportado. Esto significa que *no puedes* pasar un array directamente, y en su lugar deberías envolverlo en una estructura:
```swift
struct WelcomeContext: Encodable {
var title: String
var numbers: [Int]
}
return req.view.render("home", WelcomeContext(title: "Hello!", numbers: [42, 9001]))
```
Esto expondrá `title` y `numbers` a nuestra plantilla de Leaf, que luego se pueden usar dentro de las etiquetas. Por ejemplo:
```leaf
<h1>#(title)</h1>
#for(number in numbers):
<p>#(number)</p>
#endfor
```
## Uso
Aquí hay algunos ejemplos comunes del uso de Leaf.
### Condiciones
Leaf puede evaluar una serie de condiciones usando su etiqueta `#if`. Por ejemplo, si proporcionas una variable, comprobará si esa variable existe en su contexto:
```leaf
#if(title):
The title is #(title)
#else:
No title was provided.
#endif
```
También puedes escribir comparaciones, por ejemplo:
```leaf
#if(title == "Welcome"):
This is a friendly web page.
#else:
No strangers allowed!
#endif
```
Si deseas usar otra etiqueta como parte de tu condición, debes omitir el `#` para la etiqueta interna. Por ejemplo:
```leaf
#if(count(users) > 0):
You have users!
#else:
There are no users yet :(
#endif
```
También puedes usar declaraciones `#elseif`:
```leaf
#if(title == "Welcome"):
Hello new user!
#elseif(title == "Welcome back!"):
Hello old user
#else:
Unexpected page!
#endif
```
### Bucles
Si proporcionas un array de elementos, Leaf puede recorrerlos y te permite manipular cada elemento individualmente usando su etiqueta `#for`.
Por ejemplo, podríamos actualizar nuestro código Swift para proporcionar una lista de planetas:
```swift
struct SolarSystem: Codable {
let planets = ["Venus", "Earth", "Mars"]
}
return req.view.render("solarSystem", SolarSystem())
```
Luego podríamos recorrerlos en Leaf de la siguiente manera:
```leaf
Planets:
<ul>
#for(planet in planets):
<li>#(planet)</li>
#endfor
</ul>
```
Esto renderizaría una vista que se vería así:
```
Planets:
- Venus
- Earth
- Mars
```
### Extendiendo plantillas
La etiqueta `#extend` de Leaf te permite copiar el contenido de una plantilla en otra. Al usar esto, siempre debes omitir la extensión .leaf del archivo de la plantilla.
Extender es útil para copiar un fragmento estándar de contenido, por ejemplo, un pie de página, un código publicitario o una tabla que se comparte en varias páginas:
```leaf
#extend("footer")
```
Esta etiqueta también es útil para construir una plantilla sobre otra. Por ejemplo, podrías tener un archivo layout.leaf que incluya todo el código necesario para estructurar tu sitio web - estructura HTML, CSS y JavaScript - con algunos espacios en su lugar que representan dónde varía el contenido de la página.
Usando este enfoque, construirías una plantilla hija que completa con su contenido único, y luego extiende la plantilla padre que coloca el contenido de manera adecuada. Para hacer esto, puedes usar las etiquetas `#export` e `#import` para almacenar y luego recuperar contenido del contexto.
Por ejemplo, podrías crear una plantilla `child.leaf` de la siguiente manera:
```leaf
#extend("master"):
#export("body"):
<p>Welcome to Vapor!</p>
#endexport
#endextend
```
Llamamos `#export` para almacenar algo de HTML y hacerlo disponible para la plantilla que estamos extendiendo actualmente. Luego renderizamos `master.leaf` y usamos los datos exportados cuando sea necesario, junto con cualquier otra variable de contexto pasada desde Swift. Por ejemplo, `master.leaf` podría verse así:
```leaf
<html>
<head>
<title>#(title)</title>
</head>
<body>#import("body")</body>
</html>
```
Aquí estamos usando `#import` para obtener el contenido pasado a la etiqueta `#extend`. Cuando se pasa `["title": "Hi there!"]` desde Swift, `child.leaf` se renderizará de la siguiente manera:
```html
<html>
<head>
<title>Hi there!</title>
</head>
<body><p>Welcome to Vapor!</p></body>
</html>
```
### Otras etiquetas
#### `#count`
La etiqueta `#count` devuelve el número de elementos en un array. Por ejemplo:
```leaf
Your search matched #count(matches) pages.
```
#### `#lowercased`
La etiqueta `#lowercased` convierte todas las letras de una cadena a minúsculas.
```leaf
#lowercased(name)
```
#### `#uppercased`
La etiqueta `#uppercased` convierte todas las letras de una cadena a mayúsculas.
```leaf
#uppercased(name)
```
#### `#capitalized`
La etiqueta `#capitalized` convierte a mayúsculas la primera letra de cada palabra de una cadena y el resto a minúsculas. Puedes ver [`String.capitalized`](https://developer.apple.com/documentation/foundation/nsstring/1416784-capitalized) para más información.
```leaf
#capitalized(name)
```
#### `#contains`
La etiqueta `#contains` acepta un array y un valor como sus dos parámetros y devuelve verdadero si el array en el primer parámetro contiene el valor en el segundo parámetro.
```leaf
#if(contains(planets, "Earth")):
Earth is here!
#else:
Earth is not in this array.
#endif
```
#### `#date`
La etiqueta `#date` formatea las fechas a una cadena legible. Por defecto utiliza el formato ISO8601.
```swift
render(..., ["now": Date()])
```
```leaf
The time is #date(now)
```
Puede pasar una cadena de formateado de fecha personalizada como segundo argumento. Ve a [`DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) de Swift para más información.
```leaf
The date is #date(now, "yyyy-MM-dd")
```
#### `#unsafeHTML`
La etiqueta `#unsafeHTML` actúa como una etiqueta variable - p. ej. `#(variable)`. Sin embargo, no escapa ningún HTML que `variable` pueda contener:
```leaf
The time is #unsafeHTML(styledTitle)
```
!!! note "Nota"
Debes tener cuidado al usar esta etiqueta para asegurarte de que la variable proporcionada no exponga a sus usuarios a un ataque XSS.
#### `#dumpContext`
La etiqueta `#dumpContext` renderiza todo el contexto a una cadena legible por humanos. Usa esta etiqueta para depurar lo que se está proporcionando como contexto para el renderizado actual.
```leaf
Hello, world!
#dumpContext
```

285
docs/leaf/overview.it.md Normal file
View File

@ -0,0 +1,285 @@
# Panoramica di Leaf
Leaf è un potente linguaggio di templating con la sintassi ispirata a Swift. Puoi usarlo per generare pagine HTML dinamiche per un sito front-end o per generare email abbellite da inviare con una API.
Questa guida ti darà una panoramica della sintassi di Leaf e dei tag disponibili.
## Sintassi del template
Questo è un esempio dell'utilizzo di un tag Leaf base.
```leaf
There are #count(users) users.
```
I tag Leaf sono composti da quattro elementi:
- Token `#`: Questo indica al parser di Leaf di iniziare a cercare un tag.
- Nome `count`: identifica il tag.
- Lista dei Parametri `(users)`: Può accettare zero o più argomenti.
- Corpo: A certi tag può essere fornito un corpo opzionale usando due punti e un tag di chiusura
Possono esserci molti utilizzi diversi di questi quattro elementi in base all'implementazione del tag. Diamo un'occhiata a qualche esempio di come i tag predefiniti di Leaf possono essere usati:
```leaf
#(variable)
#extend("template"): I'm added to a base template! #endextend
#export("title"): Welcome to Vapor #endexport
#import("body")
#count(friends)
#for(friend in friends): <li>#(friend.name)</li> #endfor
```
Leaf supporta anche molte espressioni che conosci in Swift.
- `+`
- `%`
- `>`
- `==`
- `||`
- ecc.
```leaf
#if(1 + 1 == 2):
Hello!
#endif
#if(index % 2 == 0):
This is even index.
#else:
This is odd index.
#endif
```
## Contesto
Nell'esempio in [Inizio](getting-started.md), abbiamo usato un dizionario `[String: String]` per passare dati a Leaf. In ogni caso, puoi passargli qualsiasi cosa conforme a `Encodable`. In realtà è preferibile usare strutture `Encodable` in quanto `[String: Any]` non è supportato. Questo significa che *non puoi* passargli un array, e dovresti invece impacchettarlo in una struct:
```swift
struct WelcomeContext: Encodable {
var title: String
var numbers: [Int]
}
return req.view.render("home", WelcomeContext(title: "Hello!", numbers: [42, 9001]))
```
Questo mostrerà `title` e `numbers` al nostro template Leaf, che potrà poi essere usato dentro i tag. Per esempio:
```leaf
<h1>#(title)</h1>
#for(number in numbers):
<p>#(number)</p>
#endfor
```
## Utilizzo
Ecco alcuni esempi di utilizzo comune di Leaf.
### Condizioni
Leaf è capace di valutare una serie di condizioni usando il suo tag `#if`. Per esempio, se gli fornisci una variabile controllerà che la variabile esista nel suo contesto:
```leaf
#if(title):
The title is #(title)
#else:
No title was provided.
#endif
```
Puoi anche utilizzare confronti, per esempio:
```leaf
#if(title == "Welcome"):
This is a friendly web page.
#else:
No strangers allowed!
#endif
```
Se vuoi usare un altro tag come parte della tua condizione, dovresti omettere il `#` per il tag interno. Per esempio:
```leaf
#if(count(users) > 0):
You have users!
#else:
There are no users yet :(
#endif
```
Puoi usare anche dichiarazioni `#elseif`:
```leaf
#if(title == "Welcome"):
Hello new user!
#elseif(title == "Welcome back!"):
Hello old user
#else:
Unexpected page!
#endif
```
### Cicli
Se fornisci un array di oggetti, Leaf può iterare su di essi e permetterti di manipolare ciascun oggetto individualmente usando il suo tag `#for`.
Per esempio, possiamo aggiornare il nostro codice Swift per fornire una lista di pianeti:
```swift
struct SolarSystem: Codable {
let planets = ["Venus", "Earth", "Mars"]
}
return req.view.render("solarSystem", SolarSystem())
```
In Leaf, possiamo iterare su di essi così:
```leaf
Planets:
<ul>
#for(planet in planets):
<li>#(planet)</li>
#endfor
</ul>
```
Questo creerebbe una view con questo aspetto:
```
Planets:
- Venus
- Earth
- Mars
```
### Estendere i template
Il tag di Leaf `#extend` ti permette di copiare il contenuto di un template in un altro. Quando lo usi, dovresti sempre omettere l'estensione del file di template .leaf.
Estendere è utile per copiare un pezzo di contenuto standard, per esempio un piè di pagina, codice per la pubblicità o una tabella condivisi su più pagine:
```leaf
#extend("footer")
```
Questo tag è utile anche per costruire un template sulla base di un altro. Per esempio, potresti avere un file layout.leaf che include tutto il codice richiesto per disporre il tuo sito  struttura HTML, CSS e JavaScript  con dei vuoti nei posti in cui il contenuto della pagina cambia.
Usando questo approccio, potresti costruire un template figlio che compila il suo contenuto particolare, poi estende il template padre che posiziona il contenuto in modo appropriato. Per fare questo, puoi usare i tag `#export` e `#import` per salvare e dopo recuperare il contenuto dal contesto.
Per esempio, potresti creare un template `child.leaf` così:
```leaf
#extend("master"):
#export("body"):
<p>Welcome to Vapor!</p>
#endexport
#endextend
```
Chiamiamo `#export` per salvare dell'HTML e renderlo disponibile al template che stiamo estendendo al momento. Poi renderizziamo `master.leaf` e usiamo i dati esportati quando richiesto insieme a qualsiasi altra variabile di contesto passata da Swift. Per esempio, `master.leaf` potrebbe essere così:
```leaf
<html>
<head>
<title>#(title)</title>
</head>
<body>#import("body")</body>
</html>
```
Qui stiamo usando `#import` per recuperare il contenuto passato al tag `#extend`. Quando viene passato `["title": "Hi there!"]` da Swift, `child.leaf` verrà renderizzato così:
```html
<html>
<head>
<title>Hi there!</title>
</head>
<body><p>Welcome to Vapor!</p></body>
</html>
```
### Altri tag
#### `#count`
Il tag `#count` ritorna il numero di oggetti in un array. Per esempio:
```leaf
Your search matched #count(matches) pages.
```
#### `#lowercased`
Il tag `#lowercased` mette in minuscolo tutte le lettere in una stringa.
```leaf
#lowercased(name)
```
#### `#uppercased`
Il tag `#uppercased` mette in maiuscolo tutte le lettere in una stringa.
```leaf
#uppercased(name)
```
#### `#capitalized`
Il tag `#capitalized` mette in maiuscolo la prima lettera in ogni parola di una stringa e mette in minuscolo le altre. Guarda [`String.capitalized`](https://developer.apple.com/documentation/foundation/nsstring/1416784-capitalized) per più informazioni.
```leaf
#capitalized(name)
```
#### `#contains`
Il tag `#contains` accetta un array e un valore come parametri, e ritorna true se l'array nel primo parametro contiene il valore nel secondo parametro.
```leaf
#if(contains(planets, "Earth")):
Earth is here!
#else:
Earth is not in this array.
#endif
```
#### `#date`
Il tag `#date` formatta le date in una stringa leggibile. Di default usa il formato ISO8601.
```swift
render(..., ["now": Date()])
```
```leaf
The time is #date(now)
```
Puoi passare una stringa in un formato di data personalizzato come secondo argomento. Guarda il [`DateFormatter`](https://developer.apple.com/documentation/foundation/dateformatter) di Swift per più informazioni.
```leaf
The date is #date(now, "yyyy-MM-dd")
```
#### `#unsafeHTML`
Il tag `#unsafeHTML` agisce come un tag di variabile - p.es. `#(variable)`. Però non evade nessun HTML che `variable` potrebbe contenere:
```leaf
The time is #unsafeHTML(styledTitle)
```
!!! note
Dovresti fare attenzione quando usi questo tag per assicurarti che la variabile che gli fornisci non esponga i tuoi utenti a un attacco XSS.
#### `#dumpContext`
Il tag `#dumpContext` renderizza l'intero contesto in una stringa leggibile. Usa questo tag per debuggare cosa viene fornito come contesto al rendering corrente.
```leaf
Hello, world!
#dumpContext
```

View File

@ -14,7 +14,7 @@ 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.
- 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 using a colon and a closing tag

169
docs/redis/overview.es.md Normal file
View File

@ -0,0 +1,169 @@
# Redis
[Redis](https://redis.io/) es uno de los motores de almacenamiento de base de datos en memoria más populares, comúnmente utilizado como caché o intermediario de mensajes.
Esta biblioteca es una integración entre Vapor y [**RediStack**](https://github.com/swift-server/RediStack), que es el controlador subyacente que se comunica con Redis.
!!! note "Nota"
La mayoría de las capacidades de Redis son proporcionadas por **RediStack**.
Recomendamos encarecidamente familiarizarse con su documentación.
_Se proporcionan enlaces donde corresponda._
## Paquete
El primer paso para usar Redis es añadirlo como una dependencia a tu proyecto en tu manifiesto de paquete Swift.
> Este ejemplo es para un paquete existente. Para obtener ayuda sobre cómo iniciar un nuevo proyecto, consulta la guía [Comenzando](../getting-started/hello-world.md).
```swift
dependencies: [
// ...
.package(url: "https://github.com/vapor/redis.git", from: "4.0.0")
]
// ...
targets: [
.target(name: "App", dependencies: [
// ...
.product(name: "Redis", package: "redis")
])
]
```
## Configurar
Vapor emplea una estrategia de agrupación para instancias de [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection), y hay varias opciones para configurar las conexiones individuales así como los propios grupos.
El mínimo requerido para configurar Redis es proporcionar una URL para conectar:
```swift
let app = Application()
app.redis.configuration = try RedisConfiguration(hostname: "localhost")
```
### Configuración de Redis
> Documentación de la API: [`RedisConfiguration`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration)
#### serverAddresses
Si tienes varios puntos de conexión con Redis, como un grupo de instancias de Redis, querrás crear una colección de [`[SocketAddress]`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress) para pasar en el inicializador.
La forma más común de crear un `SocketAddress` es con el método estático [`makeAddressResolvingHost(_:port:)`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress/makeaddressresolvinghost(_:port:)).
```swift
let serverAddresses: [SocketAddress] = [
try .makeAddressResolvingHost("localhost", port: RedisConnection.Configuration.defaultPort)
]
```
Para un único punto de conexión con Redis, puede ser más fácil trabajar con los inicializadores de conveniencia, ya que manejará la creación del `SocketAddress` por ti:
- [`.init(url:pool)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(url:tlsconfiguration:pool:)-o9lf) (con `String` o [`Foundation.URL`](https://developer.apple.com/documentation/foundation/url))
- [`.init(hostname:port:password:database:pool:)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(hostname:port:password:tlsconfiguration:database:pool:))
#### password
Si tu instancia de Redis está asegurada con una contraseña, deberás pasarla como el argumento `password`.
Cada conexión, según se crea, será autenticada usando la contraseña.
#### database
Este es el índice de la base de datos que deseas seleccionar cuando se crea cada conexión.
Esto te ahorra tener que enviar el comando `SELECT` a Redis tú mismo.
!!! warning "Advertencia"
La selección de la base de datos no se mantiene. Ten cuidado al enviar el comando `SELECT` por tu cuenta.
### Opciones del Grupo de Conexiones
> Documentación de la API: [`RedisConfiguration.PoolOptions`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/pooloptions)
!!! note "Nota"
Aquí solo se destacan las opciones que se cambian con más frecuencia. Para todas las opciones, consulta la documentación de la API.
#### minimumConnectionCount
Este es el valor que establece cuántas conexiones deseas que cada grupo mantenga en todo momento.
Si el valor es `0`, entonces si las conexiones se pierden por cualquier motivo, el grupo no las recreará hasta que sea necesario.
Esto se conoce como una conexión de "inicio en frío" ("cold start"), y tiene cierta sobrecarga sobre el mantenimiento de un recuento mínimo de conexiones.
#### maximumConnectionCount
Esta opción determina el comportamiento de cómo se mantiene el recuento máximo de conexiones.
!!! seealso "Ver También"
Consulta la API `RedisConnectionPoolSize` para familiarizarte con las opciones disponibles.
## Enviando un Comando
Puedes enviar comandos usando la propiedad `.redis` en cualquier instancia de [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) o [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request), lo que te dará acceso a un [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient).
Cualquier `RedisClient` tiene varias extensiones para todos los diversos [comandos de Redis](https://redis.io/commands).
```swift
let value = try app.redis.get("my_key", as: String.self).wait()
print(value)
// Optional("my_value")
// o
let value = try await app.redis.get("my_key", as: String.self)
print(value)
// Optional("my_value")
```
### Comandos no soportados
Si **RediStack** no soporta un comando con un método de extensión, aún puedes enviarlo manualmente.
```swift
// cada valor después del comando es el argumento posicional que Redis espera
try app.redis.send(command: "PING", with: ["hello"])
.map {
print($0)
}
.wait()
// "hello"
// o
let res = try await app.redis.send(command: "PING", with: ["hello"])
print(res)
// "hello"
```
## Modo Pub/Sub
Redis admite la capacidad de entrar en un [modo "Pub/Sub"](https://redis.io/topics/pubsub) donde una conexión puede escuchar "canales" específicos y ejecutar closures (métodos) específicos cuando los canales suscritos publican un "mensaje" (algún valor de datos).
Hay un ciclo de vida definido para una suscripción:
1. **subscribe**: invocado una vez cuando la suscripción comienza por primera vez
2. **message**: invocado 0 o más veces a medida que se publican mensajes en los canales suscritos
3. **unsubscribe**: invocado una vez cuando la suscripción termina, ya sea por solicitud o por pérdida de conexión
Cuando creas una suscripción, debes proporcionar al menos un [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) para manejar todos los mensajes que son publicados por el canal suscrito.
Opcionalmente, puedes proporcionar un `RedisSubscriptionChangeHandler` para `onSubscribe` y `onUnsubscribe` que maneje sus respectivos eventos del ciclo de vida.
```swift
// crea 2 suscripciones, una para cada canal
app.redis.subscribe
to: "channel_1", "channel_2",
messageReceiver: { channel, message in
switch channel {
case "channel_1": // haz algo con el mensaje
default: break
}
},
onUnsubscribe: { channel, subscriptionCount in
print("unsubscribed from \(channel)")
print("subscriptions remaining: \(subscriptionCount)")
}
```

169
docs/redis/overview.it.md Normal file
View File

@ -0,0 +1,169 @@
# Redis
[Redis](https://redis.io/) è uno dei più popolari archivi di strutture dati residente in memoria comunemente usato come cache o broker di messaggi.
Questa libreria è un'integrazione tra Vapor e [**RediStack**](https://github.com/swift-server/RediStack), che è il driver sottostante che comunica con Redis.
!!! note
La maggior parte delle funzionalità di Redis sono fornite da **RediStack**.
Raccomandiamo fortemente di acquisire familiarità con la sua documentazione.
_I link saranno forniti quando appropriato._
## Pacchetto
Il primo passo per usare Redis è aggiungerlo come dipendenza al tuo progetto nel tuo manifesto del pacchetto Swift.
> Questo esempio è per un pacchetto esistente. Per avere aiuto a iniziare un nuovo progetto, guarda la guida principale su [Inizio](../getting-started/hello-world.md).
```swift
dependencies: [
// ...
.package(url: "https://github.com/vapor/redis.git", from: "4.0.0")
]
// ...
targets: [
.target(name: "App", dependencies: [
// ...
.product(name: "Redis", package: "redis")
])
]
```
## Configura
Vapor impiega una strategia di pooling per le istanze [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection), e ci sono varie opzioni per configurare connessioni singole come anche le pool stesse.
Il minimo indispensabile richiesto per configurare Redis è fornire un URL per connettersi:
```swift
let app = Application()
app.redis.configuration = try RedisConfiguration(hostname: "localhost")
```
### Configurazione di Redis
> Documentazione dell'API: [`RedisConfiguration`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration)
#### serverAddresses
Se hai più endpoint Redis, come un cluster di istanze Redis, vorrai invece creare una collezione di [`[SocketAddress]`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress) da passare all'inizializzatore.
Il modo più comune di creare un `SocketAddress` è con il metodo statico [`makeAddressResolvingHost(_:port:)`](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/socketaddress/makeaddressresolvinghost(_:port:)).
```swift
let serverAddresses: [SocketAddress] = [
try .makeAddressResolvingHost("localhost", port: RedisConnection.Configuration.defaultPort)
]
```
Per un singolo endpoint Redis, potrebbe essere più facile lavorare con gli inizializzatori pratici, in quanto si occuperanno di creare il `SocketAddress` per te:
- [`.init(url:pool)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(url:tlsconfiguration:pool:)-o9lf) (con `String` o [`Foundation.URL`](https://developer.apple.com/documentation/foundation/url))
- [`.init(hostname:port:password:database:pool:)`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/init(hostname:port:password:tlsconfiguration:database:pool:))
#### password
Se la tua istanza Redis è protetta da una password, dovrai passarla come argomento `password`.
Ogni connessione, quando viene creata, sarà autenticata usando la password.
#### database
Questo è l'indice del database che intendi selezionare quando ogni connessione viene creata.
Questo ti evita di dover mandare il comando `SELECT` a Redis da te.
!!! warning
La selezione del database non è mantenuta. Stai attento quando mandi il comando `SELECT` da te.
### Opzioni del Pool di Connessioni
> Documentazione dell'API: [`RedisConfiguration.PoolOptions`](https://api.vapor.codes/redis/documentation/redis/redisconfiguration/pooloptions)
!!! note
Solo le opzioni cambiate più comunemente sono evidenziate qui. Per tutte le altre opzioni, fai riferimento alla documentazione dell'API.
#### minimumConnectionCount
Questo è il valore che indica quante connessioni vuoi che ogni pool mantenga in ogni momento.
Se il tuo valore è `0` allora se le connessioni si perdono per qualsiasi motivo, la pool non le ricreerà fino a quando non sarà necessario.
Questa è conosciuta come connessione "cold start", e ha dell'overhead rispetto a mantenere un numero di connessioni minime.
#### maximumConnectionCount
Quest'opzione determina il comportamento di come il numero massimo di connessioni è mantenuto.
!!! seealso
Fai riferimento all'API `RedisConnectionPoolSize` per familiarizzare con le opzioni disponibili.
## Inviare un Comando
Puoi inviare comandi usando la proprietà `.redis` su ogni istanza di [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) o di [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request), che ti darà accesso a [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient).
Ogni `RedisClient` ha diverse estensioni per tutti i vari [comandi Redis](https://redis.io/commands).
```swift
let value = try app.redis.get("my_key", as: String.self).wait()
print(value)
// Optional("my_value")
// oppure
let value = try await app.redis.get("my_key", as: String.self)
print(value)
// Optional("my_value")
```
### Comandi Non Supportati
Se **RediStack** non dovesse supportare un comando con un metodo di estensione, puoi comunque mandarlo manualmente.
```swift
// ogni valore dopo il comando è l'argomento di posizione che Redis si aspetta
try app.redis.send(command: "PING", with: ["hello"])
.map {
print($0)
}
.wait()
// "hello"
// oppure
let res = try await app.redis.send(command: "PING", with: ["hello"])
print(res)
// "hello"
```
## Modalità Pub/Sub
Redis supporta la possibilità di entrare in una [modalità "Pub/Sub"](https://redis.io/topics/pubsub) dove una connessione può ascoltare specifici "canali" ed eseguire specifiche closure quando i canali abbonati pubblicano un "messaggio" (qualche valore dei dati).
Un abbonamento ha un ciclo di vita ben definito:
1. **subscribe**: invocato una volta quando l'abbonamento inizia
1. **message**: invocato da 0 a più volte man mano che i messaggi sono pubbicati ai canali abbonati
1. **unsubscribe**: invocato una volta quando l'abbonamento finisce, o su richiesta o quando la connessione viene persa
Quando crei un abbonamento, devi fornire almeno un [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) per gestire tutti i messaggi che sono pubblicati dai canali abbonati.
Puoi facoltativamente fornire un `RedisSubscriptionChangeHandler` per `onSubscribe` e `onUnsubscribe` per gestire i loro rispettivi eventi di ciclo di vita.
```swift
// crea 2 abbonamenti, uno per ogni canale fornito
app.redis.subscribe
to: "channel_1", "channel_2",
messageReceiver: { channel, message in
switch channel {
case "channel_1": // fai qualcosa col messaggio
default: break
}
},
onUnsubscribe: { channel, subscriptionCount in
print("unsubscribed from \(channel)")
print("subscriptions remaining: \(subscriptionCount)")
}
```

View File

@ -32,7 +32,7 @@ targets: [
## Configure
Vapor employs a pooling strategy for [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) instances, and there are several options to configure individual connections as well as the pools themselves.
Vapor employs a pooling strategy for [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection) instances, and there are several options to configure individual connections as well as the pools themselves.
The bare minimum required for configuring Redis is to provide a URL to connect to:
@ -102,7 +102,7 @@ This option determines the behavior of how the maximum connection count is maint
## Sending a Command
You can send commands using the `.redis` property on any [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) or [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instance, which will give you access to a [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient).
You can send commands using the `.redis` property on any [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) or [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instance, which will give you access to a [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient).
Any `RedisClient` has several extensions for all of the various [Redis commands](https://redis.io/commands).
@ -148,7 +148,7 @@ There is a defined lifecycle to a subscription:
1. **message**: invoked 0+ times as messages are published to the subscribed channels
1. **unsubscribe**: invoked once when the subscription ends, either by request or the connection being lost
When you create a subscription, you must provide at least a [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) to handle all messages that are published by the subscribed channel.
When you create a subscription, you must provide at least a [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) to handle all messages that are published by the subscribed channel.
You can optionally provide a `RedisSubscriptionChangeHandler` for `onSubscribe` and `onUnsubscribe` to handle their respective lifecycle events.

View File

@ -32,7 +32,7 @@ targets: [
## Configuratie
Vapor gebruikt een pooling strategie voor [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) instanties, en er zijn verschillende opties om zowel individuele verbindingen als de pools zelf te configureren.
Vapor gebruikt een pooling strategie voor [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection) instanties, en er zijn verschillende opties om zowel individuele verbindingen als de pools zelf te configureren.
Het absolute minimum dat nodig is voor het configureren van Redis is het opgeven van een URL om verbinding mee te maken:
@ -102,7 +102,7 @@ Deze optie bepaalt het gedrag van hoe het maximum aantal verbindingen wordt bijg
## Een commando versturen
Je kunt commando's sturen met de `.redis` eigenschap op elke [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) of [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instantie, die je toegang geeft tot een [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient).
Je kunt commando's sturen met de `.redis` eigenschap op elke [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) of [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) instantie, die je toegang geeft tot een [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient).
Elke `RedisClient` heeft verschillende extensies voor alle verschillende [Redis commando's](https://redis.io/commands).
@ -148,7 +148,7 @@ Er is een bepaalde levenscyclus voor een abonnement:
1. **message**: 0+ keer aangeroepen als berichten worden gepubliceerd in de geabonneerde kanalen
1. **unsubscribe**: eenmaal aangeroepen wanneer het abonnement eindigt, hetzij door een verzoek, hetzij doordat de verbinding wordt verbroken
Wanneer je een abonnement aanmaakt, moet je minstens een [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) voorzien om alle berichten te behandelen die gepubliceerd worden door het geabonneerde kanaal.
Wanneer je een abonnement aanmaakt, moet je minstens een [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) voorzien om alle berichten te behandelen die gepubliceerd worden door het geabonneerde kanaal.
U kunt optioneel een `RedisSubscriptionChangeHandler` opgeven voor `onSubscribe` en `onUnsubscribe` om hun respectievelijke lifecycle events af te handelen.

View File

@ -32,7 +32,7 @@ targets: [
## 配置
Vapor 对 [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisconnection) 实例采用池化策略,并且有几个选项可以配置单个连接以及池本身。
Vapor 对 [`RedisConnection`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisconnection) 实例采用池化策略,并且有几个选项可以配置单个连接以及池本身。
配置 Redis 的最低要求是提供一个 URL 来连接:
@ -102,7 +102,7 @@ let serverAddresses: [SocketAddress] = [
## 发送命令
你可以使用 [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 或 [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 实例上的 `.redis` 属性发送命令,这使得你可以访问 [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redisclient)。
你可以使用 [`Application`](https://api.vapor.codes/vapor/documentation/vapor/application) 或 [`Request`](https://api.vapor.codes/vapor/documentation/vapor/request) 实例上的 `.redis` 属性发送命令,这使得你可以访问 [`RedisClient`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redisclient)。
对于各别的 [Redis 命令](https://redis.io/commands)`RedisClient` 都有其对应的扩展。
@ -148,7 +148,7 @@ Redis 支持进入[发布/订阅模式](https://redis.io/topics/pubsub),其中
1. **message**:在消息发布到订阅频道时调用 0+ 次
1. **unsubscribe**:订阅结束时调用一次,无论是通过请求还是连接丢失
创建订阅时,你必须至少提供一个 [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/1.4.1/documentation/redistack/redissubscriptionmessagereceiver) 来处理订阅频道发布的所有消息。
创建订阅时,你必须至少提供一个 [`messageReceiver`](https://swiftpackageindex.com/swift-server/RediStack/main/documentation/redistack/redissubscriptionmessagereceiver) 来处理订阅频道发布的所有消息。
你可以选择为 `onSubscribe``onUnsubscribe` 提供一个 `RedisSubscriptionChangeHandler` 来处理它们各自的生命周期事件。

78
docs/redis/sessions.es.md Normal file
View File

@ -0,0 +1,78 @@
# Redis y Sesiones
Redis puede actuar como un proveedor de almacenamiento para el caché de [datos de sesión](../advanced/sessions.md#session-data) como las credenciales del usuario.
Si no se proporciona un [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate) personalizado, se usará uno predeterminado.
## Comportamiento Predeterminado
### Creación de SessionID
A menos que implementes el método [`makeNewID()`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makenewid()-3hyne) en [tu propio RedisSessionsDelegate](#redissessionsdelegate), todos los valores de [`SessionID`](https://api.vapor.codes/vapor/documentation/vapor/sessionid) se crearán haciendo lo siguiente:
1. Generar 32 bytes de caracteres aleatorios.
2. Codificar el valor en base64.
Por ejemplo: `Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
### Almacenamiento de SessionData
La implementación predeterminada de `RedisSessionsDelegate` almacenará [`SessionData`](https://api.vapor.codes/vapor/documentation/vapor/sessiondata) como un simple valor de cadena JSON usando `Codable`.
A menos que implementes el método [`makeRedisKey(for:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makerediskey(for:)-5nfge) en tu propio `RedisSessionsDelegate`, `SessionData` se almacenará en Redis con una clave que antepone el `SessionID` con `vrs-` (**V**apor **R**edis **S**essions).
Por ejemplo: `vrs-Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
## Registrando un Delegado Personalizado
Para personalizar cómo se leen y se escriben los datos en Redis, registra tu propio objeto `RedisSessionsDelegate` de la siguiente manera:
```swift
import Redis
struct CustomRedisSessionsDelegate: RedisSessionsDelegate {
// implementación
}
app.sessions.use(.redis(delegate: CustomRedisSessionsDelegate()))
```
## RedisSessionsDelegate
> Documentación de la API: [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate)
Un objeto que cumple con este protocolo puede usarse para cambiar cómo `SessionData` se almacena en Redis.
Solo dos métodos requieren de implementación en un tipo que cumpla con el protocolo: [`redis(_:store:with:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:store:with:)) y [`redis(_:fetchDataFor:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:fetchdatafor:)).
Ambos son obligatorios, ya que la forma en que personalizas la escritura de los datos de sesión en Redis está intrínsecamente vinculada a cómo se lee de Redis.
### Ejemplo de RedisSessionsDelegate Hash
Por ejemplo, si quisieras almacenar los datos de sesión como un [**Hash** en Redis](https://redis.io/topics/data-types-intro#redis-hashes), podrías implementar algo como lo siguiente:
```swift
func redis<Client: RedisClient>(
_ client: Client,
store data: SessionData,
with key: RedisKey
) -> EventLoopFuture<Void> {
// almacena cada campo de datos como un campo hash separado
return client.hmset(data.snapshot, in: key)
}
func redis<Client: RedisClient>(
_ client: Client,
fetchDataFor key: RedisKey
) -> EventLoopFuture<SessionData?> {
return client
.hgetall(from: key)
.map { hash in
// hash es [String: RESPValue], por lo que necesitamos intentar desempaquetar el
// valor como una cadena y almacenar cada valor en el contenedor de datos
return hash.reduce(into: SessionData()) { result, next in
guard let value = next.value.string else { return }
result[next.key] = value
}
}
}
```

78
docs/redis/sessions.it.md Normal file
View File

@ -0,0 +1,78 @@
# Redis & Sessioni
Redis può fungere da provider di archiviazione per il caching dei [dati di sessione](../advanced/sessions.md#session-data) come le credenziali degli utenti.
Se non viene fornito un [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate) personalizzato, sarà utilizzato quello di default.
## Comportamento di Default
### Creazione di SessionID
A meno che non implementi il metodo [`makeNewID()`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makenewid()-3hyne) nel [tuo `RedisSessionsDelegate` personale](#redissessionsdelegate), tutti i valori [`SessionID`](https://api.vapor.codes/vapor/documentation/vapor/sessionid) saranno creati facendo quanto segue:
1. Generare 32 byte di caratteri casuali
1. Codificare il valore in base64
Per esempio: `Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
### Archiviazione di SessionData
L'implementazione di default di `RedisSessionsDelegate` salverà [`SessionData`](https://api.vapor.codes/vapor/documentation/vapor/sessiondata) come una semplice stringa JSON usando `Codable`.
A meno che non implementi il metodo [`makeRedisKey(for:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makerediskey(for:)-5nfge) nel tuo `RedisSessionsDelegate` personale, `SessionData` sarà salvato in Redis con una chiave che precede il `SessionID` con `vrs-` (**V**apor **R**edis **S**essions)
Per esempio: `vrs-Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
## Registrare un Delegato Modificato
Per modificare il modo in cui i dati vengono letti e scritti su Redis, registra il tuo oggetto `RedisSessionsDelegate` come segue:
```swift
import Redis
struct CustomRedisSessionsDelegate: RedisSessionsDelegate {
// implementazione
}
app.sessions.use(.redis(delegate: CustomRedisSessionsDelegate()))
```
## RedisSessionsDelegate
> Documentazione dell'API: [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate)
Un oggetto che è conforme a questo protocollo può essere usato per cambiare come `SessionData` è salvato in Redis.
Viene richiesto di implementare solo due metodi a un tipo conforme al protocollo: [`redis(_:store:with:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:store:with:)) e [`redis(_:fetchDataFor:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:fetchdatafor:)).
Entrambi sono necessari, in quanto il modo in cui tu personalizzi la scrittura dei dati di sessione su Redis è intrinsecamente legato a come deve essere letto da Redis.
### Esempio di Hash di RedisSessionsDelegate
Per esempio, se vuoi salvare i dati di sessione come un [**Hash** in Redis](https://redis.io/topics/data-types-intro#redis-hashes), dovresti implementare qualcosa simile a quanto segue:
```swift
func redis<Client: RedisClient>(
_ client: Client,
store data: SessionData,
with key: RedisKey
) -> EventLoopFuture<Void> {
// salva ogni campo dei dati come un campo hash separato
return client.hmset(data.snapshot, in: key)
}
func redis<Client: RedisClient>(
_ client: Client,
fetchDataFor key: RedisKey
) -> EventLoopFuture<SessionData?> {
return client
.hgetall(from: key)
.map { hash in
// hash è [String: RESPValue] quindi dobbiamo provare e spacchettare il
// valore come una stringa e salvare ogni valore nel container dei dati
return hash.reduce(into: SessionData()) { result, next in
guard let value = next.value.string else { return }
result[next.key] = value
}
}
}
```

65
docs/release-notes.it.md Normal file
View File

@ -0,0 +1,65 @@
# Note sulla Versione di Vapor
Siccome è difficile, se non impossibile, tenere la documentazione aggiornata costantemente, qui troverai le note sulla versione (release notes) di vari pacchetti differenti legati all'ecosistema Vapor.
## vapor
## fluent
## fluent-kit
## leaf
## leaf-kit
## fluent-postgres-driver
## fluent-mysql-driver
## fluent-sqlite-driver
## fluent-mongo-driver
## postgres-nio
## mysql-nio
## sqlite-nio
## postgres-kit
## mysql-kit
## sqlite-kit
## sql-kit
## apns
## queues
## queues-redis-driver
## redis
## jwt
## jwt-kit
## websocket-kit
## routing-kit
## console-kit
## async-kit
## multipart-kit
## toolbox
## core
## swift-codecov-action
## api-docs

View File

@ -0,0 +1,896 @@
# Autenticazione
L'autenticazione è la verifica dell'identità di un utente. Ciò può avvenire attraverso la verifica di credenziali come un nome utente e una password o un tramite un token. L'autenticazione (talvolta chiamata auth/c) si distingue dall'autorizzazione (auth/z), che è l'atto di verificare i permessi di un utente precedentemente autenticato per permettergli di eseguire determinate operazioni.
## Introduzione
Le API di autenticazione di Vapor supportano l'autenticazione di un utente tramite l'intestazione `Authorization` della richiesta, utilizzando le autorizzazioni [Basic](https://tools.ietf.org/html/rfc7617) e [Bearer](https://tools.ietf.org/html/rfc6750). È supportata anche l'autenticazione di un utente tramite la decodifica dei dati dall'API [Content](../basics/content.md).
L'autenticazione viene implementata creando un `Authenticator` che contiene la logica di verifica. Un autenticatore può essere utilizzato per proteggere singoli gruppi di route o un'intera applicazione. Vapor fornisce i seguenti autenticatori:
|Protocollo|Descrizione|
|-|-|
|`RequestAuthenticator`/`AsyncRequestAuthenticator`|Autenticatore di base in grado di creare middleware.|
|[`BasicAuthenticator`/`AsyncBasicAuthenticator`](#basic)|Autenticatore che verifica l'header Basic.|
|[`BearerAuthenticator`/`AsyncBearerAuthenticator`](#bearer)|Autenticatore che verifica l'header Bearer.|
|[`CredentialsAuthenticator`/`AsyncCredentialsAuthenticator`](#credentials)|Autenticatore che verifica le credenziali decodificate dal contenuto della richiesta.|
Se l'autenticazione ha successo, l'autenticatore aggiunge l'utente verificato a `req.auth`. Si può quindi accedere a questo utente usando `req.auth.get(_:)` nelle route protette dall'autenticatore. Se l'autenticazione fallisce, l'utente non viene aggiunto a `req.auth` e qualsiasi tentativo di accesso fallirà.
## Authenticatable
Per utilizzare l'API di autenticazione, ti occorre innanzitutto un tipo di utente conforme ad `Authenticatable`. Questo può essere una `struct`, una `class` o anche un `Model` Fluent. Gli esempi seguenti assumono una semplice struttura `User` che ha una sola proprietà: `name`.
```swift
import Vapor
struct User: Authenticatable {
var name: String
}
```
Ogni esempio che segue utilizzerà un'istanza di un autenticatore che abbiamo creato. In questi esempi, lo abbiamo chiamato `UserAuthenticator`.
### Route
Gli autenticatori sono middleware e possono essere utilizzati per proteggere le route.
```swift
let protected = app.grouped(UserAuthenticator())
protected.get("me") { req -> String in
try req.auth.require(User.self).name
}
```
Per recuperare l'utente autenticato, viene usato il metodo `req.auth.require`. Se l'autenticazione fallisce, questo metodo lancia un errore, proteggendo la route.
### Middleware di Guardia
Puoi anche usare `GuardMiddleware` nel gruppo di route, per assicurarsi che un utente sia stato autenticato prima di raggiungere il gestore di route.
```swift
let protected = app.grouped(UserAuthenticator())
.grouped(User.guardMiddleware())
```
La richiesta di autenticazione non viene effettuata dal middleware dell'autenticatore per consentire la composizione degli autenticatori. Per saperne di più sulla [composizione](#composition) leggi più sotto.
## Basic
L'autenticazione di base invia un nome utente e una password nell'intestazione `Authorization`. Il nome utente e la password sono concatenati con i due punti (ad esempio, `test:secret`), codificati in base 64 e preceduti da `"Basic"`. La seguente richiesta di esempio codifica il nome utente `test` con la password `secret`.
```http
GET /me HTTP/1.1
Authorization: Basic dGVzdDpzZWNyZXQ=
```
In genere, l'autenticazione di base viene utilizzata una sola volta per registrare un utente e generare un token. Questo riduce al minimo la frequenza di invio della password sensibile dell'utente. Non si dovrebbe mai inviare l'autorizzazione di base in chiaro o su una connessione TLS non verificata.
Per implementare l'autenticazione di base nella tua applicazione, puoi creare un nuovo autenticatore conforme a `BasicAuthenticator`. Di seguito è riportato un esempio di autenticatore codificato per verificare la richiesta di cui sopra.
```swift
import Vapor
struct UserAuthenticator: BasicAuthenticator {
typealias User = App.User
func authenticate(
basic: BasicAuthorization,
for request: Request
) -> EventLoopFuture<Void> {
if basic.username == "test" && basic.password == "secret" {
request.auth.login(User(name: "Vapor"))
}
return request.eventLoop.makeSucceededFuture(())
}
}
```
Se stai usando `async`/`await`, puoi usare `AsyncBasicAuthenticator`:
```swift
import Vapor
struct UserAuthenticator: AsyncBasicAuthenticator {
typealias User = App.User
func authenticate(
basic: BasicAuthorization,
for request: Request
) async throws {
if basic.username == "test" && basic.password == "secret" {
request.auth.login(User(name: "Vapor"))
}
}
}
```
Questo protocollo richiede che implementi il metodo `authenticate(basic:for:)`, che sarà richiamato quando una richiesta in arrivo contiene l'intestazione `Authorization: Basic ...`. Al metodo viene passata una struct `BasicAuthorization` contenente il nome utente e la password.
In questo autenticatore di prova, il nome utente e la password vengono verificati rispetto ai valori codificati. In un autenticatore reale, potresti voler effettuare un controllo su un database o su un'API esterna, per questo motivo il metodo `authenticate` consente di restituire una future.
!!! tip
Le password non devono mai essere memorizzate in un database in chiaro. Utilizzate sempre gli hash delle password per il confronto.
Se i parametri di autenticazione sono corretti, in questo caso corrispondono ai valori codificati, viene effettuato l'accesso a uno `User` di nome Vapor. Se i parametri di autenticazione non corrispondono, non viene registrato alcun utente, il che significa che l'autenticazione è fallita.
Se aggiungi questo autenticatore alla tua applicazione e testi la route definita sopra, dovresti vedere il nome `"Vapor"` restituito per un login riuscito. Se le credenziali non sono corrette, dovresti vedere un errore `401 Unauthorized`.
## Bearer
L'autenticazione Bearer invia un token nell'intestazione `Authorization`. Il token è preceduto dalla stringa `"Bearer"`. La seguente richiesta di esempio invia un token di accesso `secret`.
```http
GET /me HTTP/1.1
Authorization: Bearer foo
```
L'autenticazione Bearer è comunemente usata per l'autenticazione degli endpoint API. L'utente in genere richiede un token Bearer inviando credenziali come nome utente e password a un endpoint di login. Questo token può durare minuti o giorni, a seconda delle esigenze dell'applicazione.
Finché il token è valido, l'utente può usarlo al posto delle proprie credenziali per autenticarsi con l'API. Se il token non è valido, è possibile generarne uno nuovo utilizzando l'endpoint di login.
Per implementare l'autenticazione Bearer nella tua applicazione, puoi creare un nuovo autenticatore conforme a `BearerAuthenticator`. Di seguito è riportato un esempio di autenticatore codificato per verificare la richiesta di cui sopra.
```swift
import Vapor
struct UserAuthenticator: BearerAuthenticator {
typealias User = App.User
func authenticate(
bearer: BearerAuthorization,
for request: Request
) -> EventLoopFuture<Void> {
if bearer.token == "foo" {
request.auth.login(User(name: "Vapor"))
}
return request.eventLoop.makeSucceededFuture(())
}
}
```
Se stai usando `async`/`await`, puoi usare `AsyncBearerAuthenticator`:
```swift
import Vapor
struct UserAuthenticator: AsyncBearerAuthenticator {
typealias User = App.User
func authenticate(
bearer: BearerAuthorization,
for request: Request
) async throws {
if bearer.token == "foo" {
request.auth.login(User(name: "Vapor"))
}
}
}
```
Questo protocollo richiede l'implementazione di `authenticate(bearer:for:)` che verrà richiamata quando una richiesta in arrivo contiene l'intestazione `Authorization: Bearer ...`. Al metodo viene passata una struct `BearerAuthorization` contenente il token.
In questo autenticatore di prova, il token viene testato rispetto a un valore codificato. In un vero autenticatore, potresti voler verificare il token confrontandolo con un database o usando misure crittografiche, come si fa con JWT. Ecco perché il metodo `authenticate` consente di restituire una future.
!!! tip
Quando si implementa la verifica dei token, è importante considerare la scalabilità orizzontale. Se l'applicazione deve gestire molti utenti contemporaneamente, l'autenticazione può essere un potenziale collo di bottiglia. Considera il modo in cui il tuo progetto scalerà su più istanze dell'applicazione in esecuzione contemporaneamente.
Se i parametri di autenticazione sono corretti, e in questo caso corrispondono al valore codificato, viene effettuato l'accesso a un `Utente` di nome Vapor. Se i parametri di autenticazione non corrispondono, non viene registrato alcun utente, il che significa che l'autenticazione è fallita.
Se aggiungi questo autenticatore alla tua applicazione e testi la route definita sopra, dovresti vedere il nome `"Vapor"` restituito per un login riuscito. Se le credenziali non sono corrette, dovresti vedere un errore `401 Unauthorized`.
## Composizione
Puoi comporre (combinare insieme) più autenticatori per creare un'autenticazione dell'endpoint più complessa. Poiché un middleware autenticatore non rifiuta la richiesta se l'autenticazione fallisce, puoi concatenare più di un middleware. Puoi concatenare più autenticatori in due modi diversi.
### Composizione dei Metodi
Il primo metodo di composizione dell'autenticazione consiste nel concatenare più autenticatori per lo stesso tipo di utente. Prendi l'esempio seguente:
```swift
app.grouped(UserPasswordAuthenticator())
.grouped(UserTokenAuthenticator())
.grouped(User.guardMiddleware())
.post("login")
{ req in
let user = try req.auth.require(User.self)
// Fai qualcosa con l'utente.
}
```
Questo esempio presuppone due autenticatori `UserPasswordAuthenticator` e `UserTokenAuthenticator` che autenticano entrambi `User`. Entrambi gli autenticatori sono aggiunti al gruppo di route. Infine, `GuardMiddleware` viene aggiunto dopo gli autenticatori per richiedere che `User` sia stato autenticato con successo.
Questa composizione di autenticatori dà come risultato una route a cui si può accedere sia tramite password che tramite token. Una route di questo tipo potrebbe consentire a un utente di effettuare il login e generare un token, per poi continuare a usare quel token per generare nuovi token.
### Composizione di Utenti
Il secondo metodo di composizione dell'autenticazione consiste nel concatenare gli autenticatori per diversi tipi di utenti. Prendiamo il seguente esempio:
```swift
app.grouped(AdminAuthenticator())
.grouped(UserAuthenticator())
.get("secure")
{ req in
guard req.auth.has(Admin.self) || req.auth.has(User.self) else {
throw Abort(.unauthorized)
}
// Fai qualcosa.
}
```
Questo esempio presuppone due autenticatori `AdminAuthenticator` e `UserAuthenticator` che autenticano rispettivamente `Admin` e `User`. Entrambi gli autenticatori sono aggiunti al gruppo di route. Invece di usare `GuardMiddleware`, viene aggiunto un controllo nel gestore di route per vedere se `Admin` o `User` sono stati autenticati. In caso contrario, viene lanciato un errore.
Questa composizione di autenticatori dà luogo a un percorso a cui possono accedere due tipi diversi di utenti con metodi di autenticazione potenzialmente diversi. Un percorso di questo tipo potrebbe consentire l'autenticazione di un utente normale, pur consentendo l'accesso a un super-utente.
## Manualmente
Puoi anche gestire l'autenticazione manualmente, utilizzando `req.auth`. Questo è particolarmente utile per i test.
Per accedere manualmente a un utente, puoi utilizzare `req.auth.login(_:)`. A questo metodo può essere passato qualsiasi utente `Authenticatable`.
```swift
req.auth.login(User(name: "Vapor"))
```
Per ottenere l'utente autenticato puoi usare `req.auth.require(_:)`:
```swift
let user: User = try req.auth.require(User.self)
print(user.name) // String
```
Puoi anche usare `req.auth.get(_:)` se non vuoi lanciare automaticamente un errore quando l'autenticazione fallisce.
```swift
let user = req.auth.get(User.self)
print(user?.name) // String?
```
Per effettuare il logout di un utente, puoi usare `req.auth.logout(_:)`:
```swift
req.auth.logout(User.self)
```
## Fluent
[Fluent](../fluent/overview.md) definisce due protocolli `ModelAuthenticatable` e `ModelTokenAuthenticatable` che possono essere aggiunti ai modelli esistenti. Conformare i modelli a questi protocolli consente di creare autenticatori per proteggere gli endpoint.
`ModelTokenAuthenticatable` si autentica con un token Bearer. È quello che puoi usare per proteggere la maggior parte degli endpoint. `ModelAuthenticatable` si autentica con nome utente e password ed è usato da un singolo endpoint per generare token.
Questa guida presuppone che tu abbia già familiarità con Fluent e che abbia configurato con successo la tua applicazione per utilizzare un database. Se non conosci Fluent, inizia dalla [panoramica](../fluent/overview.md).
### User
Per iniziare, è necessario un modello che rappresenti l'utente da autenticare. Per questa guida, useremo il modello seguente, ma puoi usare un qualsiasi modello esistente.
```swift
import Fluent
import Vapor
final class User: Model, Content {
static let schema = "users"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Field(key: "email")
var email: String
@Field(key: "password_hash")
var passwordHash: String
init() { }
init(id: UUID? = nil, name: String, email: String, passwordHash: String) {
self.id = id
self.name = name
self.email = email
self.passwordHash = passwordHash
}
}
```
Il modello deve essere in grado di memorizzare un nome utente, in questo caso un'e-mail, e un hash di password. Abbiamo anche impostato `email` come campo unico, per evitare utenti duplicati. La migrazione corrispondente per questo modello di esempio è qui:
```swift
import Fluent
import Vapor
extension User {
struct Migration: AsyncMigration {
var name: String { "CreateUser" }
func prepare(on database: Database) async throws {
try await database.schema("users")
.id()
.field("name", .string, .required)
.field("email", .string, .required)
.field("password_hash", .string, .required)
.unique(on: "email")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("users").delete()
}
}
}
```
Non dimenticare di aggiungere la migrazione a `app.migrations`.
```swift
app.migrations.add(User.Migration())
```
!!! tip
Poiché gli indirizzi email non sono sensibili alle maiuscole e alle minuscole, puoi aggiungere un [`Middleware`](../fluent/model.md#lifecycle) che coercizzi l'indirizzo email in minuscolo prima di salvarlo nella base dati. Tieni presente, però, che `ModelAuthenticatable` usa un confronto sensibile alle maiuscole e alle minuscole, quindi se fai questo devi assicurarti che l'input dell'utente sia tutto minuscolo, o con la coercizione delle maiuscole nel client o con un autenticatore personalizzato.
La prima cosa di cui hai bisogno è un endpoint per creare nuovi utenti. Useremo `POST /users`. Crea una struttura [Content](../basics/content.md) che rappresenti i dati che questo endpoint si aspetta.
```swift
import Vapor
extension User {
struct Create: Content {
var name: String
var email: String
var password: String
var confirmPassword: String
}
}
```
Se vuoi, puoi conformare questa struttura a [Validatable](../basics/validation.md) per aggiungere requisiti di validazione.
```swift
import Vapor
extension User.Create: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("name", as: String.self, is: !.empty)
validations.add("email", as: String.self, is: .email)
validations.add("password", as: String.self, is: .count(8...))
}
}
```
Ora puoi creare l'endpoint `POST /users`.
```swift
app.post("users") { req async throws -> User in
try User.Create.validate(content: req)
let create = try req.content.decode(User.Create.self)
guard create.password == create.confirmPassword else {
throw Abort(.badRequest, reason: "Passwords did not match")
}
let user = try User(
name: create.name,
email: create.email,
passwordHash: Bcrypt.hash(create.password)
)
try await user.save(on: req.db)
return user
}
```
Questo endpoint convalida la richiesta in arrivo, decodifica la struttura `User.Create` e controlla che le password corrispondano. Utilizza quindi i dati decodificati per creare un nuovo `User` e lo salva nel database. La password in chiaro viene sottoposta a hash con `Bcrypt` prima di essere salvata nel database.
Compila ed esegui il progetto, assicurandoti di eseguire prima le migrazioni sul database, quindi utilizza la seguente richiesta per creare un nuovo utente.
```http
POST /users HTTP/1.1
Content-Length: 97
Content-Type: application/json
{
"name": "Vapor",
"email": "test@vapor.codes",
"password": "secret42",
"confirmPassword": "secret42"
}
```
#### Modello Authenticatable
Ora che hai un modello utente e un endpoint per creare nuovi utenti, conforma il modello a `ModelAuthenticatable`. Questo ti permetterà di autenticare il modello usando nome utente e password.
```swift
import Fluent
import Vapor
extension User: ModelAuthenticatable {
static let usernameKey = \User.$email
static let passwordHashKey = \User.$passwordHash
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.passwordHash)
}
}
```
Questa estensione aggiunge la conformità `ModelAuthenticatable` a `User`. Le prime due proprietà specificano quali campi devono essere utilizzati per memorizzare rispettivamente il nome utente e l'hash della password. La notazione `\` crea un percorso chiave per i campi che Fluent può usare per accedervi.
L'ultimo requisito è un metodo per verificare le password in chiaro inviate nell'intestazione di autenticazione Basic. Poiché usiamo Bcrypt per l'hash della password durante la registrazione, useremo Bcrypt per verificare che la password fornita corrisponda all'hash della password memorizzata.
Ora che l'utente `User` è conforme a `ModelAuthenticatable`, puoi creare un autenticatore per proteggere la route di login.
```swift
let passwordProtected = app.grouped(User.authenticator())
passwordProtected.post("login") { req -> User in
try req.auth.require(User.self)
}
```
`ModelAuthenticatable` aggiunge un metodo statico `authenticator` per creare un autenticatore.
Verifica che questo percorso funzioni inviando la seguente richiesta:
```http
POST /login HTTP/1.1
Authorization: Basic dGVzdEB2YXBvci5jb2RlczpzZWNyZXQ0Mg==
```
Questa richiesta passa il nome utente `test@vapor.codes` e la password `secret42` tramite l'intestazione di autenticazione Basic. Dovrebbe essere restituito l'utente precedentemente creato.
Anche se in teoria si potrebbe usare l'autenticazione di base per proteggere tutti gli endpoint, è consigliato usare un token separato. In questo modo si riduce al minimo la frequenza di invio della password sensibile dell'utente su Internet. Inoltre, l'autenticazione è molto più veloce, poiché è sufficiente eseguire l'hashing della password durante l'accesso.
### Token Utente
Crea un nuovo modello per rappresentare i token degli utenti.
```swift
import Fluent
import Vapor
final class UserToken: Model, Content {
static let schema = "user_tokens"
@ID(key: .id)
var id: UUID?
@Field(key: "value")
var value: String
@Parent(key: "user_id")
var user: User
init() { }
init(id: UUID? = nil, value: String, userID: User.IDValue) {
self.id = id
self.value = value
self.$user.id = userID
}
}
```
Questo modello deve avere un campo `value` per memorizzare la stringa unica del token. Deve anche avere una [relazione padre](../fluent/overview.md#parent) con il modello utente. Puoi aggiungere anche altre proprietà a questo token, come ad esempio una data di scadenza.
Quindi, crea una migrazione per questo modello.
```swift
import Fluent
extension UserToken {
struct Migration: AsyncMigration {
var name: String { "CreateUserToken" }
func prepare(on database: Database) async throws {
try await database.schema("user_tokens")
.id()
.field("value", .string, .required)
.field("user_id", .uuid, .required, .references("users", "id"))
.unique(on: "value")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("user_tokens").delete()
}
}
}
```
Nota che questa migrazione rende unico il campo `value`. Inoltre, crea un riferimento a chiave esterna tra il campo `user_id` e la tabella utenti.
Non dimenticare di aggiungere la migrazione a `app.migrations`.
```swift
app.migrations.add(UserToken.Migration())
```
Infine, aggiungi un metodo su `User` per generare un nuovo token. Questo metodo sarà utilizzato durante il login.
```swift
extension User {
func generateToken() throws -> UserToken {
try .init(
value: [UInt8].random(count: 16).base64,
userID: self.requireID()
)
}
}
```
Qui usiamo `[UInt8].random(count:)` per generare un valore casuale di token. Per questo esempio, vengono utilizzati 16 byte, o 128 bit, di dati casuali. Puoi modificare questo numero come ritieni opportuno. I dati casuali vengono poi codificati in base-64 per facilitarne la trasmissione nelle intestazioni HTTP.
Ora che puoi generare i token utente, aggiorna la route `POST /login` per creare e restituire un token.
```swift
let passwordProtected = app.grouped(User.authenticator())
passwordProtected.post("login") { req async throws -> UserToken in
let user = try req.auth.require(User.self)
let token = try user.generateToken()
try await token.save(on: req.db)
return token
}
```
Verifica che questa route funzioni utilizzando la stessa richiesta di login di cui sopra. Ora dovresti ottenere un token al momento dell'accesso che assomigli a qualcosa di simile:
```
8gtg300Jwdhc/Ffw784EXA==
```
Conserva il token ottenuto: lo utilizzeremo a breve.
#### Modello Token Authenticatable
Conforma `UserToken` a `ModelTokenAuthenticatable`. Questo permetterà ai token di autenticare il modello `User`.
```swift
import Vapor
import Fluent
extension UserToken: ModelTokenAuthenticatable {
static let valueKey = \UserToken.$value
static let userKey = \UserToken.$user
var isValid: Bool {
true
}
}
```
Il primo requisito del protocollo specifica quale campo memorizza il valore univoco del token. Questo è il valore che sarà inviato nell'intestazione di autenticazione Bearer. Il secondo requisito specifica la parentela con il modello `User`. Questo è il modo in cui Fluent cercherà l'utente autenticato.
Il requisito finale è un booleano `isValid`. Se è `false`, il token sarà cancellato dal database e l'utente non sarà autenticato. Per semplicità, renderemo i token eterni, codificando in modo rigido questo valore a `true`.
Ora che il token è conforme a `ModelTokenAuthenticatable`, si può creare un autenticatore per proteggere le route.
Crea un nuovo endpoint `GET /me` per ottenere l'utente attualmente autenticato.
```swift
let tokenProtected = app.grouped(UserToken.authenticator())
tokenProtected.get("me") { req -> User in
try req.auth.require(User.self)
}
```
Simile a `User`, `UserToken` ha ora un metodo statico `authenticator()` che può generare un autenticatore. L'autenticatore cercherà di trovare un `UserToken` corrispondente, utilizzando il valore fornito nell'intestazione di autenticazione del portatore. Se trova una corrispondenza, recupera il relativo `User` e lo autentica.
Verifica che questa route funzioni inviando la seguente richiesta HTTP, dove il token è il valore salvato dalla richiesta `POST /login`.
```http
GET /me HTTP/1.1
Authorization: Bearer <token>
```
Dovresti vedere l'utente attualmente autenticato.
## Sessioni
L'[API delle Sessioni](../advanced/sessions.md) di Vapor può essere utilizzata per persistere automaticamente l'autenticazione dell'utente tra le richieste. Questo funziona memorizzando un identificatore univoco per l'utente nei dati di sessione della richiesta, dopo il successo del login. Nelle richieste successive, l'identificatore dell'utente viene recuperato dalla sessione e usato per autenticare l'utente prima di chiamare il gestore della route.
Le sessioni sono ottime per le applicazioni web front-end costruite in Vapor che servono HTML direttamente ai browser web. Per le API, si consiglia di utilizzare un'autenticazione stateless basata su token per conservare i dati dell'utente tra una richiesta e l'altra.
### Session Authenticatable
Per utilizzare l'autenticazione basata sulla sessione, occorre un tipo conforme a `SessionAuthenticatable`. Per questo esempio, useremo una semplice struct.
```swift
import Vapor
struct User {
var email: String
}
```
Per essere conformi a `SessionAuthenticatable`, è necessario specificare un `sessionID`. Questo è il valore che verrà memorizzato nei dati di sessione e deve identificare in modo univoco l'utente.
```swift
extension User: SessionAuthenticatable {
var sessionID: String {
self.email
}
}
```
Per il nostro tipo `User`, useremo l'indirizzo e-mail come identificatore unico di sessione.
### Autenticatore di Sessione
Poi, avrai bisogno di un `SessionAuthenticator` per gestire la risoluzione delle istanze dell'utente dall'identificatore di sessione persistito.
```swift
struct UserSessionAuthenticator: SessionAuthenticator {
typealias User = App.User
func authenticate(sessionID: String, for request: Request) -> EventLoopFuture<Void> {
let user = User(email: sessionID)
request.auth.login(user)
return request.eventLoop.makeSucceededFuture(())
}
}
```
Se stai usando `async`/`await`, puoi usare `AsyncSessionAuthenticator`:
```swift
struct UserSessionAuthenticator: AsyncSessionAuthenticator {
typealias User = App.User
func authenticate(sessionID: String, for request: Request) async throws {
let user = User(email: sessionID)
request.auth.login(user)
}
}
```
Poiché tutte le informazioni necessarie per inizializzare il nostro `User` di esempio sono contenute nell'identificatore di sessione, possiamo creare e accedere all'utente in modo sincrono. In un'applicazione reale, è probabile che venga utilizzato l'identificatore di sessione per eseguire una ricerca nel database o una richiesta API per recuperare il resto dei dati dell'utente prima dell'autenticazione.
Quindi, crea un semplice autenticatore di portatori per eseguire l'autenticazione iniziale.
```swift
struct UserBearerAuthenticator: AsyncBearerAuthenticator {
func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
if bearer.token == "test" {
let user = User(email: "hello@vapor.codes")
request.auth.login(user)
}
}
}
```
Questo autenticatore autenticherà un utente con l'email `hello@vapor.codes` quando viene inviato il token portatore `test`.
Infine, combina tutti questi pezzi insieme nell'applicazione.
```swift
// Crea un gruppo di route protette che richiedono autenticazione.
let protected = app.routes.grouped([
app.sessions.middleware,
UserSessionAuthenticator(),
UserBearerAuthenticator(),
User.guardMiddleware(),
])
// Aggiungi una route GET /me che restituisce l'email dell'utente.
protected.get("me") { req -> String in
try req.auth.require(User.self).email
}
```
Viene prima aggiunto `SessionsMiddleware`, per abilitare il supporto alle sessioni nell'applicazione. Puoi trovare maggiori informazioni sulla configurazione delle sessioni nella sezione [API di sessione](../advanced/sessions.md).
Successivamente, viene aggiunto il `SessionAuthenticator`. Questo gestisce l'autenticazione dell'utente se è attiva una sessione.
Se l'autenticazione non è ancora stata persistita nella sessione, la richiesta sarà inoltrata all'autenticatore successivo. L'autenticatore `UserBearerAuthenticator` controllerà il token del portatore e autenticherà l'utente se è uguale a `"test"`.
Infine, `User.guardMiddleware()` assicura che `User` sia stato autenticato da uno dei middleware precedenti. Se l'utente non è stato autenticato, verrà lanciato un errore.
Per testare questa route, invia prima la seguente richiesta:
```http
GET /me HTTP/1.1
authorization: Bearer test
```
Questo farà sì che `UserBearerAuthenticator` autentichi l'utente. Una volta autenticato, `UserSessionAuthenticator` persisterà l'identificatore dell'utente nella memoria di sessione e genererà un cookie. Puoi poi utilizzare il cookie dalla risposta in una seconda richiesta alla route.
```http
GET /me HTTP/1.1
cookie: vapor_session=123
```
Questa volta, `UserSessionAuthenticator` autenticherà l'utente e dovrebbe essere restituita l'e-mail dell'utente.
### Model Session Authenticatable
I modelli Fluent possono generare `SessionAuthenticator` conformandosi a `ModelSessionAuthenticatable`. Questo userà l'identificatore univoco del modello come identificatore di sessione ed eseguirà automaticamente una ricerca nel database per ripristinare il modello dalla sessione.
```swift
import Fluent
final class User: Model { ... }
// Consente di persistere il modello nelle sessioni.
extension User: ModelSessionAuthenticatable { }
```
Puoi aggiungere `ModelSessionAuthenticatable` a qualsiasi modello esistente come conformità vuota. Una volta aggiunto, sarà disponibile un nuovo metodo statico per creare un `SessionAuthenticator` per quel modello.
```swift
User.sessionAuthenticator()
```
Questo utilizzerà il database predefinito dell'applicazione per la risoluzione dell'utente. Per specificare un database, passa l'identificatore.
```swift
User.sessionAuthenticator(.sqlite)
```
## Autenticazione per Sito Web
I siti web sono un caso particolare per l'autenticazione, perché l'uso di un browser limita il modo in cui è possibile collegare le credenziali a un browser. Questo porta a due diversi scenari di autenticazione:
* l'accesso iniziale tramite un form
* chiamate successive autenticate con un cookie di sessione
Vapor e Fluent forniscono diversi aiutanti per rendere tutto ciò semplice.
### Autenticazione di Sessione
L'autenticazione di sessione funziona come descritto sopra. Devi applicare il middleware di sessione e l'autenticatore di sessione a tutte le route a cui l'utente accederà. Queste includono tutte le route protette, le route che sono pubbliche, ma per le quali vuoi accedere all'utente se è loggato (ad esempio, per visualizzare un pulsante per l'account), **e** le route di login.
È possibile attivarlo globalmente nella propria applicazione in `configure.swift` in questo modo:
```swift
app.middleware.use(app.sessions.middleware)
app.middleware.use(User.sessionAuthenticator())
```
Questi middleware svolgono le seguenti funzioni:
* Il middleware delle sessioni prende il cookie di sessione fornito nella richiesta e lo converte in una sessione.
* l'autenticatore di sessione prende la sessione e verifica se esiste un utente autenticato per quella sessione. In caso affermativo, il middleware autentica la richiesta. Nella risposta, l'autenticatore di sessione vede se la richiesta ha un utente autenticato e lo salva nella sessione, in modo che sia autenticato nella richiesta successiva.
!!! note
Di default, il cookie di sessione non è impostato su `secure` e/o `httpOnly`. Per ulteriori informazioni su come configurare i cookie, consultare le [API di sessione](../advanced/sessions.md#configuration) di Vapor.
### Protezione delle Route
Quando si proteggono le route per un'API, tradizionalmente restituisci una risposta HTTP con un codice di stato come **401 Unauthorized** se la richiesta non è autenticata. Tuttavia, questa non è una buona esperienza per l'utente che utilizza un browser. Vapor fornisce un `RedirectMiddleware` per qualsiasi tipo `Authenticatable` da utilizzare in questo scenario:
```swift
let protectedRoutes = app.grouped(User.redirectMiddleware(path: "/login?loginRequired=true"))
```
L'oggetto `RedirectMiddleware` supporta anche il passaggio di una chiusura che restituisce il percorso di reindirizzamento come `Stringa` durante la creazione, per una gestione avanzata degli url. Ad esempio, includendo il percorso di reindirizzamento come parametro di query alla destinazione del reindirizzamento per la gestione dello stato.
```swift
let redirectMiddleware = User.redirectMiddleware { req -> String in
return "/login?authRequired=true&next=\(req.url.path)"
}
```
Questo funziona in modo simile a `GuardMiddleware`. Qualsiasi richiesta alle route registrate su `protectedRoutes` che non sia autenticata sarà reindirizzata al percorso fornito. Questo permette di dire agli utenti di effettuare il login, invece di fornire semplicemente un **401 Unauthorized**.
Assicurati di includere un Autenticatore di sessione prima del `RedirectMiddleware` per garantire che l'utente autenticato sia caricato prima di passare attraverso il `RedirectMiddleware`.
```swift
let protectedRoutes = app.grouped([User.sessionAuthenticator(), redirectMiddleware])
```
### Form per il Login
Per autenticare un utente e le richieste future con una sessione, è necessario effettuare il login. Vapor fornisce un protocollo `ModelCredentialsAuthenticatable` a cui conformarsi. Questo gestisce l'accesso tramite un modulo. Per prima cosa, conforma il tuo `User` a questo protocollo:
```swift
extension User: ModelCredentialsAuthenticatable {
static let usernameKey = \User.$email
static let passwordHashKey = \User.$password
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.password)
}
}
```
Questo è identico a `ModelAuthenticatable` e se lo `User` è già conforme a questo, non è necessario fare altro. Quindi applica il middleware `ModelCredentialsAuthenticator` alla richiesta POST del modulo di login:
```swift
let credentialsProtectedRoute = sessionRoutes.grouped(User.credentialsAuthenticator())
credentialsProtectedRoute.post("login", use: loginPostHandler)
```
Esso utilizza l'autenticatore di credenziali predefinito per proteggere il percorso di accesso. È necessario che invii `username` e `password` nella richiesta POST. Si può impostare il form in questo modo:
```html
<form method="POST" action="/login">
<label for="username">Username</label>
<input type="text" id="username" placeholder="Username" name="username" autocomplete="username" required autofocus>
<label for="password">Password</label>
<input type="password" id="password" placeholder="Password" name="password" autocomplete="current-password" required>
<input type="submit" value="Sign In">
</form>
```
Il `CredentialsAuthenticator` estrae `username` e `password` dal corpo della richiesta, trova l'utente dal nome utente e verifica la password. Se la password è valida, il middleware autentica la richiesta. Il `SessionAuthenticator` autentica quindi la sessione per le richieste successive.
## JWT
[JWT](jwt.md) fornisce un `JWTAuthenticator` che può essere usato per autenticare i token web JSON nelle richieste in arrivo. Se non conosci JWT, dai un'occhiata alla [panoramica](jwt.md).
Per prima cosa, crea un tipo che rappresenti un payload JWT.
```swift
// Esempio di payload JWT.
struct SessionToken: Content, Authenticatable, JWTPayload {
// Costanti
let expirationTime: TimeInterval = 60 * 15
// Dati del payload
var expiration: ExpirationClaim
var userId: UUID
init(userId: UUID) {
self.userId = userId
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationTime))
}
init(user: User) throws {
self.userId = try user.requireID()
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationTime))
}
func verify(using signer: JWTSigner) throws {
try expiration.verifyNotExpired()
}
}
```
Successivamente, possiamo definire una rappresentazione dei dati contenuti in una risposta di login andata a buon fine. Per ora la risposta avrà solo una proprietà, una stringa che rappresenta un JWT firmato.
```swift
struct ClientTokenReponse: Content {
var token: String
}
```
Utilizzando il nostro modello per il token JWT e la risposta, possiamo usare una route di login protetta da password che restituisce un `ClientTokenReponse` e include un `SessionToken` firmato.
```swift
let passwordProtected = app.grouped(User.authenticator(), User.guardMiddleware())
passwordProtected.post("login") { req -> ClientTokenReponse in
let user = try req.auth.require(User.self)
let payload = try SessionToken(with: user)
return ClientTokenReponse(token: try req.jwt.sign(payload))
}
```
In alternativa, se non vuoi usare un autenticatore, puoi avere qualcosa di simile a questo:
```swift
app.post("login") { req -> ClientTokenReponse in
// Valida le credenziali dell'utente
// Ottieni lo userId dell'utente
let payload = try SessionToken(userId: userId)
return ClientTokenReponse(token: try req.jwt.sign(payload))
}
```
Conformando il payload a `Authenticatable` e `JWTPayload`, puoi generare un autenticatore di route usando il metodo `authenticator()`. Aggiungilo a un gruppo di route per recuperare e verificare automaticamente il JWT prima che la route venga chiamata.
```swift
// Crea un gruppo di route che richiede il SessionToken JWT.
let secure = app.grouped(SessionToken.authenticator(), SessionToken.guardMiddleware())
```
L'aggiunta dell'opzionale [middleware di guardia](#guard-middleware) richiede che l'autorizzazione sia riuscita.
All'interno delle route protette, si può accedere al payload JWT autenticato usando `req.auth`.
```swift
// Restituisce una risposta ok se il token fornito dall'utente è valido.
secure.post("validateLoggedInUser") { req -> HTTPStatus in
let sessionToken = try req.auth.require(SessionToken.self)
print(sessionToken.userId)
return .ok
}
```

View File

@ -329,6 +329,9 @@ Don't forget to add the migration to `app.migrations`.
app.migrations.add(User.Migration())
```
!!! tip
Because email addresses are not case sensitive, you may want to add a [`Middleware`](../fluent/model.md#lifecycle) that coerces the email address to lowercase before saving it to the database. Be aware, though, that `ModelAuthenticatable` uses a case sensitive comparison, so if you do this you'll want to make sure the user's input is all lower case, either with case coercion in the client, or with a custom authenticator.
The first thing you will need is an endpoint to create new users. Let's use `POST /users`. Create a [Content](../basics/content.md) struct representing the data this endpoint expects.
```swift

View File

@ -326,6 +326,10 @@ extension User {
app.migrations.add(User.Migration())
```
!!! tip "建议"
由于电子邮件地址是不区分大小写的,你可能希望在将其保存到数据库之前添加一个[`中间件`](../fluent/model.md#lifecycle),将电子邮件地址强制转换为小写。但是要注意,`ModelAuthenticatable` 使用区分大小写的比较,如果你这样做的话,你需要确保用户的输入都是小写,要么在客户端使用大小写强制转换,要么使用自定义身份验证器。
首先需要一个端点来创建新用户。让我们使用 `POST /users`。创建一个 [Content](../basics/content.zh.md) 的结构体,表示这个端点期望的数据。
```swift

813
docs/upgrading.es.md Normal file
View File

@ -0,0 +1,813 @@
# Actualizar a 4.0
Esta guía muestra cómo actualizar un proyecto existente de Vapor 3.x a 4.x. Esta guía intenta cubrir todos los paquetes oficiales de Vapor, así como algunos providers de uso común. Si nota que falta algo, el [chat del equipo de Vapor](https://discord.gg/vapor) es un excelente lugar para pedir ayuda. También se agradecen issues y pull request.
## Dependencias
Para usar Vapor 4, necesitarás Xcode 11.4 y macOS 10.15 o superior.
La sección Instalación de los documentos analiza la instalación de dependencias.
## Package.swift
El primer paso para actualizar a Vapor 4 es actualizar las dependencias de su proyecto. A continuación se muestra un ejemplo de un archivo Package.swift actualizado. También puedes consultar la [plantilla Package.swift](https://github.com/vapor/template/blob/main/Package.swift) actualizada.
```diff
-// swift-tools-version:4.0
+// swift-tools-version:5.2
import PackageDescription
let package = Package(
name: "api",
+ platforms: [
+ .macOS(.v10_15),
+ ],
dependencies: [
- .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"),
+ .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
+ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
- .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"),
+ .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),
- .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
+ .package(url: "https://github.com/vapor/vapor.git", from: "4.3.0"),
],
targets: [
.target(name: "App", dependencies: [
- "FluentPostgreSQL",
+ .product(name: "Fluent", package: "fluent"),
+ .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
- "Vapor",
+ .product(name: "Vapor", package: "vapor"),
- "JWT",
+ .product(name: "JWT", package: "jwt"),
]),
- .target(name: "Run", dependencies: ["App"]),
- .testTarget(name: "AppTests", dependencies: ["App"])
+ .target(name: "Run", dependencies: [
+ .target(name: "App"),
+ ]),
+ .testTarget(name: "AppTests", dependencies: [
+ .target(name: "App"),
+ ])
]
)
```
Todos los paquetes que se hayan actualizado para Vapor 4 tendrán su número de versión principal incrementado en uno.
!!! warning "Advertencia"
El identificador de prelanzamiento `-rc` se utiliza ya que algunos paquetes de Vapor 4 aún no se han actualizado oficialmente.
### Paquetes Antiguos
Algunos paquetes de Vapor 3 han quedado obsoletos, como por ejemplo:
- `vapor/auth`: Ahora incluido en Vapor.
- `vapor/core`: Absorbido en varios módulos.
- `vapor/crypto`: Reemplazado por SwiftCrypto (Ahora incluido en Vapor).
- `vapor/multipart`: Ahora incluido en Vapor.
- `vapor/url-encoded-form`: Ahora incluido en Vapor.
- `vapor-community/vapor-ext`: Ahora incluido en Vapor.
- `vapor-community/pagination`: Ahora parte de Fluent.
- `IBM-Swift/LoggerAPI`: Reemplazado por SwiftLog.
### Dependencia Fluent
`vapor/fluent` ahora debe agregarse como una dependencia separada a su lista de dependencias y targets. Todos los paquetes específicos de bases de datos tienen el sufijo `-driver` para aclarar el requisito de `vapor/fluent`.
```diff
- .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"),
+ .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
+ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
```
### Plataformas
Los manifiestos del paquete de Vapor ahora son explícitamente compatibles con macOS 10.15 y superiores. Esto significa que tu paquete también deberá especificar la compatibilidad con la plataforma.
```diff
+ platforms: [
+ .macOS(.v10_15),
+ ],
```
Vapor puede agregar plataformas compatibles adicionales en el futuro. Tu paquete puede admitir cualquier subconjunto de estas plataformas siempre que el número de versión sea igual o mayor a los requisitos mínimos de versión de Vapor.
### Xcode
Vapor 4 utiliza SPM nativo de Xcode 11. Esto significa que ya no necesitarás generar archivos `.xcodeproj`. Al abrir la carpeta de tu proyecto en Xcode, se reconocerá automáticamente SPM y se incorporarán las dependencias.
Puedes abrir tu proyecto de forma nativa en Xcode usando `vapor xcode` o `open Package.swift`.
Una vez que hayas actualizado Package.swift, es posible que debas cerrar Xcode y borrar las siguientes carpetas del directorio raíz:
- `Package.resolved`
- `.build`
- `.swiftpm`
- `*.xcodeproj`
Una vez que tus paquetes actualizados se hayan resuelto exitosamente, deberías ver errores del compilador, probablemente bastantes. ¡No te preocupes! Te mostraremos cómo solucionarlos.
## Run
Lo primero que debemos hacer es actualizar el archivo `main.swift` de tu módulo Run al nuevo formato.
```swift
import App
import Vapor
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let app = Application(env)
defer { app.shutdown() }
try configure(app)
try app.run()
```
El contenido del archivo `main.swift` reemplaza al `app.swift` del módulo de aplicación, por lo que puedes eliminar ese archivo.
## Aplicación
Echemos un vistazo a cómo actualizar la estructura básica del módulo de la aplicación.
### configure.swift
El método `configure` debe cambiarse para aceptar una instancia de `Application`.
```diff
- public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws
+ public func configure(_ app: Application) throws
```
A continuación se muestra un ejemplo de un método de configuración actualizado.
```swift
import Fluent
import FluentSQLiteDriver
import Vapor
// Llamado antes de que se inicialice su aplicación.
public func configure(_ app: Application) throws {
// Sirve archivos del directorio `Public/`
// app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
// Configura la base de datos SQLite
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
// Configura migraciones
app.migrations.add(CreateTodo())
try routes(app)
}
```
A continuación se mencionan los cambios de sintaxis para configurar cosas como routing, middleware, fluent y más.
### boot.swift
El contenido de `boot` se puede colocar en el método `configure` ya que ahora acepta la instancia de la aplicación.
### routes.swift
El método `routes` debe cambiarse para aceptar una instancia de `Application`.
```diff
- public func routes(_ router: Router, _ container: Container) throws
+ public func routes(_ app: Application) throws
```
A continuación se menciona más información sobre los cambios en la sintaxis de routing.
## Servicios
Las APIs de servicios de Vapor 4 se han simplificado para que resulte más fácil descubrirlos y utilizarlos. Los servicios ahora están expuestos como métodos y propiedades en `Application` y `Request`, lo que permite al compilador ayudarte en su uso.
Para entender esto mejor, echemos un vistazo a algunos ejemplos.
```diff
// Cambiar el puerto predeterminado del servidor a 8281
- services.register { container -> NIOServerConfig in
- return .default(port: 8281)
- }
+ app.http.server.configuration.port = 8281
```
En lugar de registrar un `NIOServerConfig` en los servicios, la configuración del servidor ahora se expone como propiedades simples en Application que se pueden anular.
```diff
// Registrar middleware cors
let corsConfiguration = CORSMiddleware.Configuration(
allowedOrigin: .all,
allowedMethods: [.POST, .GET, .PATCH, .PUT, .DELETE, .OPTIONS]
)
let corsMiddleware = CORSMiddleware(configuration: corsConfiguration)
- var middlewares = MiddlewareConfig() // Create _empty_ middleware config
- middlewares.use(corsMiddleware)
- services.register(middlewares)
+ app.middleware.use(corsMiddleware)
```
En lugar de crear y registrar un `MiddlewareConfig` para los servicios, el middleware ahora se expone como una propiedad de Application a la que se puede agregar.
```diff
// Realizar una solicitud en un controlador de ruta.
- try req.make(Client.self).get("https://vapor.codes")
+ req.client.get("https://vapor.codes")
```
Al igual que Application, Request también expone los servicios como propiedades y métodos simples. Los servicios específicos de Request siempre deben usarse cuando se encuentre dentro de un closure de ruta.
Este nuevo patrón de servicio reemplaza los tipos `Container`, `Service` y `Config` de Vapor 3.
### Providers
Ya no se requiere que los providers configuren paquetes de terceros. En cambio, cada paquete amplía Application y Request con nuevas propiedades y métodos de configuración.
Echa un vistazo a cómo está configurado Leaf en Vapor 4.
```diff
// Utilizar Leaf para renderizar vistas.
- try services.register(LeafProvider())
- config.prefer(LeafRenderer.self, for: ViewRenderer.self)
+ app.views.use(.leaf)
```
Para configurar Leaf, usa la propiedad `app.leaf`.
```diff
// Deshabilitar el caché de vista de Leaf.
- services.register { container -> LeafConfig in
- return LeafConfig(tags: ..., viewsDir: ..., shouldCache: false)
- }
+ app.leaf.cache.isEnabled = false
```
### Entorno
Se puede acceder al entorno actual (producción, desarrollo, etc.) a través de `app.environment`.
### Servicios Personalizados
Los servicios personalizados que cumplen con el protocolo `Service` y están registrados en el contenedor en Vapor 3 ahora se pueden expresar como extensiones de Application o Request.
```diff
struct MyAPI {
let client: Client
func foo() { ... }
}
- extension MyAPI: Service { }
- services.register { container -> MyAPI in
- return try MyAPI(client: container.make())
- }
+ extension Request {
+ var myAPI: MyAPI {
+ .init(client: self.client)
+ }
+ }
```
Luego se puede acceder a este servicio utilizando la extensión en lugar de `make`.
```diff
- try req.make(MyAPI.self).foo()
+ req.myAPI.foo()
```
### Providers Personalizados
La mayoría de los servicios personalizados se pueden implementar mediante extensiones como se muestra en la sección anterior. Sin embargo, es posible que algunos providers avanzados necesiten conectarse al ciclo de vida de la aplicación o utilizar propiedades almacenadas.
El nuevo helper `Lifecycle` de la aplicación se puede utilizar para registrar controladores de ciclo de vida.
```swift
struct PrintHello: LifecycleHandler {
func willBoot(_ app: Application) throws {
print("Hello!")
}
}
app.lifecycle.use(PrintHello())
```
Para almacenar valores en Application, utilice el nuevo helper `Storage`.
```swift
struct MyNumber: StorageKey {
typealias Value = Int
}
app.storage[MyNumber.self] = 5
print(app.storage[MyNumber.self]) // 5
```
El acceso a `app.storage` se puede incluir en una propiedad calculada configurable para crear una API concisa.
```swift
extension Application {
var myNumber: Int? {
get { self.storage[MyNumber.self] }
set { self.storage[MyNumber.self] = newValue }
}
}
app.myNumber = 42
print(app.myNumber) // 42
```
## NIO
Vapor 4 ahora expone las APIs asíncronas de SwiftNIO directamente y no intenta sobrecargar métodos como `map` y` flatMap` o tipos de alias como `EventLoopFuture`. Vapor 3 proporcionó sobrecargas y alias para compatibilidad con versiones beta anteriores que se lanzaron antes de que existiera SwiftNIO. Estos se han eliminado para reducir la confusión con otros paquetes compatibles con SwiftNIO y seguir las recomendaciones de mejores prácticas de SwiftNIO.
### Cambios de nombres asíncronos
El cambio más obvio es la eliminación del alias de tipo `Future` para `EventLoopFuture`. Esto se puede solucionar con bastante facilidad buscando y reemplazando.
Además, NIO no admite las etiquetas `to:` que agregó Vapor 3. Dada la inferencia de tipos mejorada de Swift 5.2, `to:` ahora es menos necesario de todos modos.
```diff
- futureA.map(to: String.self) { ... }
+ futureA.map { ... }
```
Los métodos con el prefijo `new`, como `newPromise`, se han cambiado a `make` para adaptarse mejor al estilo de Swift.
```diff
- let promise = eventLoop.newPromise(String.self)
+ let promise = eventLoop.makePromise(of: String.self)
```
`catchMap` ya no está disponible, pero los métodos de NIO como `mapError` y` flatMapErrorThrowing` funcionarán en su lugar.
El método global `flatMap` de Vapor 3 para combinar múltiples futuros ya no está disponible. Esto se puede reemplazar utilizando el método `and` de NIO para combinar muchos futuros.
```diff
- flatMap(futureA, futureB) { a, b in
+ futureA.and(futureB).flatMap { (a, b) in
// Do something with a and b.
}
```
### ByteBuffer
Muchos métodos y propiedades que anteriormente usaban `Data` ahora usan `ByteBuffer` de NIO. Este tipo es un tipo de almacenamiento de bytes más potente y eficaz. Puedes leer más sobre su API en la [documentación de ByteBuffer de SwiftNIO](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer).
Para convertir un `ByteBuffer` nuevamente en `Data`, usa:
```swift
Data(buffer.readableBytesView)
```
### Throwing map / flatMap
El cambio más difícil es que `map` y `flatMap` ya no lanzan errores (throw). `map` tiene una versión throw llamada (de manera algo confusa) `flatMapThrowing`. Sin embargo, `flatMap` no tiene contrapartida throw. Esto puede requerir que tengas que reestructurar algo de código asincrónico.
Los maps que _no_ hacen throw deberían seguir funcionando bien.
```swift
// map que no devuelve throw.
futureA.map { a in
return b
}
```
Los maps que _si_ hacen throw deben cambiarse de nombre a `flatMapThrowing`.
```diff
- futureA.map { a in
+ futureA.flatMapThrowing { a in
if ... {
throw SomeError()
} else {
return b
}
}
```
Los flatMap que _no_ hacen throw deberían seguir funcionando bien.
```swift
// flatMap que no devuelve throw.
futureA.flatMap { a in
return futureB
}
```
En lugar de generar un error dentro de un flatMap, devuelva un error futuro. Si el error se origina en otro método throw, el error puede detectarse utilizando do / catch y devolverse como futuro.
```swift
// Devolver un error atrapado como futuro.
futureA.flatMap { a in
do {
try doSomething()
return futureB
} catch {
return eventLoop.makeFailedFuture(error)
}
}
```
Las llamadas a métodos que devuelven throw también se pueden refactorizar en un `flatMapThrowing` y encadenarlas usando tuplas.
```swift
// Método de throw refactorizado en flatMapThrowing con encadenamiento de tuplas.
futureA.flatMapThrowing { a in
try (a, doSomeThing())
}.flatMap { (a, result) in
// result es el valor de doShomething.
return futureB
}
```
## Routing
Las rutas ahora se registran directamente en Application.
```swift
app.get("hello") { req in
return "Hello, world"
}
```
Esto significa que ya no necesitas registrar un router en los servicios. Simplemente pasa tu aplicación a tu método `routes` y comienza a agregar rutas. Todos los métodos disponibles en `RoutesBuilder` están disponibles en `Application`.
### Contenido Sincrónico
El contenido de la solicitud de decodificación ahora es sincrónico.
```swift
let payload = try req.content.decode(MyPayload.self)
print(payload) // MyPayload
```
Este comportamiento puede ser anulado por rutas de registro utilizando la estrategia de recopilación de body `.stream`.
```swift
app.on(.POST, "streaming", body: .stream) { req in
// El body de la solicitud ahora es asíncrono.
req.body.collect().map { buffer in
HTTPStatus.ok
}
}
```
### Rutas separadas por comas
Las rutas ahora deben estar separadas por comas y no contener `/` para mantener la coherencia.
```diff
- router.get("v1/users/", "posts", "/comments") { req in
+ app.get("v1", "users", "posts", "comments") { req in
// Handle request.
}
```
### Parámetros de ruta
El protocolo `Parameter` se ha eliminado en favor de parámetros nombrados explícitamente. Esto evita problemas con parámetros duplicados y recuperación desordenada de parámetros en middleware y controladores de ruta.
```diff
- router.get("planets", String.parameter) { req in
- let id = req.parameters.next(String.self)
+ app.get("planets", ":id") { req in
+ let id = req.parameters.get("id")
return "Planet id: \(id)"
}
```
El uso de parámetros de ruta con modelos se menciona en la sección de Fluent.
## Middleware
`MiddlewareConfig` pasó a llamarse `MiddlewareConfiguration` y ahora es una propiedad de Application. Puedes agregar middleware a tu aplicación usando `app.middleware`.
```diff
let corsMiddleware = CORSMiddleware(configuration: ...)
- var middleware = MiddlewareConfig()
- middleware.use(corsMiddleware)
+ app.middleware.use(corsMiddleware)
- services.register(middlewares)
```
El middleware ya no se puede registrar por nombre de tipo. Primero debes inicializar el middleware antes de registrarlo.
```diff
- middleware.use(ErrorMiddleware.self)
+ app.middleware.use(ErrorMiddleware.default(environment: app.environment))
```
Para eliminar todo el middleware predeterminado, establece `app.middleware` en una configuración vacía usando:
```swift
app.middleware = .init()
```
## Fluent
La API de Fluent ahora es independiente de la base de datos. Puedes importar solo `Fluent`.
```diff
- import FluentMySQL
+ import Fluent
```
### Modelos
Todos los modelos ahora usan el protocolo `Model` y deben ser clases.
```diff
- struct Planet: MySQLModel {
+ final class Planet: Model {
```
Todos los campos se declaran utilizando los property wrappers `@Field` o `@OptionalField`.
```diff
+ @Field(key: "name")
var name: String
+ @OptionalField(key: "age")
var age: Int?
```
El ID de un modelo debe definirse utilizando el property wrapper `@ID`.
```diff
+ @ID(key: .id)
var id: UUID?
```
Los modelos que usan un identificador con una clave o tipo personalizado deben usar `@ID(custom:)`.
Todos los modelos deben tener su tabla o nombre de colección definido estáticamente.
```diff
final class Planet: Model {
+ static let schema = "Planet"
}
```
Todos los modelos ahora deben tener un inicializador vacío. Dado que todas las propiedades usan property wrappers, puede estar vacío.
```diff
final class Planet: Model {
+ init() { }
}
```
Los métodos `save`, `update`, y `create` del modelo ya no devuelven la instancia del modelo.
```diff
- model.save(on: ...)
+ model.save(on: ...).map { model }
```
Los modelos ya no se pueden utilizar como componentes de ruta. Utiliza `find` y `req.parameters.get` en su lugar.
```diff
- try req.parameters.next(ServerSize.self)
+ ServerSize.find(req.parameters.get("size"), on: req.db)
+ .unwrap(or: Abort(.notFound))
```
`Model.ID` ha sido renombrado a `Model.IDValue`.
Los timestamps del modelo ahora se declaran usando el property wrapper `@Timestamp`.
```diff
- static var createdAtKey: TimestampKey? = \.createdAt
+ @Timestamp(key: "createdAt", on: .create)
var createdAt: Date?
```
### Relaciones
Las relaciones ahora se definen mediante property wrappers.
Las relaciones padres utilizan el property wrapper `@Parent` y contienen la propiedad del campo internamente. La clave pasada a `@Parent` debe ser el nombre del campo que almacena el identificador en la base de datos.
```diff
- var serverID: Int
- var server: Parent<App, Server> {
- parent(\.serverID)
- }
+ @Parent(key: "serverID")
+ var server: Server
```
Las relaciones hijas utilizan el property wrapper `@Children` con una ruta clave al `@Parent` relacionado.
```diff
- var apps: Children<Server, App> {
- children(\.serverID)
- }
+ @Children(for: \.$server)
+ var apps: [App]
```
Las relaciones entre hermanos utilizan el property wrapper `@Siblings` con rutas clave al modelo de pivote.
```diff
- var users: Siblings<Company, User, Permission> {
- siblings()
- }
+ @Siblings(through: Permission.self, from: \.$user, to: \.$company)
+ var companies: [Company]
```
Los pivotes ahora son modelos normales que se ajustan a `Model` con dos relaciones `@Parent` y cero o más campos adicionales.
### Consultas
Ahora se accede al contexto de la base de datos a través de `req.db` en los controladores de ruta.
```diff
- Planet.query(on: req)
+ Planet.query(on: req.db)
```
Se ha cambiado el nombre de `DatabaseConnectable` a `Database`.
Las rutas clave a los campos ahora tienen el prefijo `$` para especificar el contenedor de propiedad en lugar del valor del campo.
```diff
- filter(\.foo == ...)
+ filter(\.$foo == ...)
```
### Migraciones
Los modelos ya no admiten migraciones automáticas basadas en reflection. Todas las migraciones deben escribirse manualmente.
```diff
- extension Planet: Migration { }
+ struct CreatePlanet: Migration {
+ ...
+}
```
Las migraciones ahora se escriben de forma estricta, están desacopladas de los modelos y utilizan el protocolo `Migration`.
```diff
- struct CreateGalaxy: <#Database#>Migration {
+ struct CreateGalaxy: Migration {
```
Los métodos `prepare` y `revert` ya no son estáticos.
```diff
- static func prepare(on conn: <#Database#>Connection) -> Future<Void> {
+ func prepare(on database: Database) -> EventLoopFuture<Void>
```
La creación de un schema builder se realiza mediante un método de instancia `Database`.
```diff
- <#Database#>Database.create(Galaxy.self, on: conn) { builder in
- // Use builder.
- }
+ var builder = database.schema("Galaxy")
+ // Use builder.
```
Los métodos `create`, `update`, y `delete` ahora se llaman en el schema builder de manera similar a como funciona el query builder.
Las definiciones de los campos ahora están escritas en formato de cadena y siguen el siguiente patrón:
```swift
field(<name>, <type>, <constraints>)
```
Mira el ejemplo a continuación.
```diff
- builder.field(for: \.name)
+ builder.field("name", .string, .required)
```
El schema building ahora se puede encadenar como un query builder.
```swift
database.schema("Galaxy")
.id()
.field("name", .string, .required)
.create()
```
### Configuración de Fluent
`DatabasesConfig` ha sido reemplazado por `app.databases`.
```swift
try app.databases.use(.postgres(url: "postgres://..."), as: .psql)
```
`MigrationsConfig` ha sido reemplazado por `app.migrations`.
```swift
app.migrations.use(CreatePlanet(), on: .psql)
```
### Repositorios
Como la forma en que funcionan los servicios en Vapor 4 ha cambiado, la manera de hacer repositorios de bases de datos también. Aún necesitas un protocolo como `UserRepository`, pero en lugar de hacer que una `final class` se ajuste a ese protocolo, debes crear una `struct`.
```diff
- final class DatabaseUserRepository: UserRepository {
+ struct DatabaseUserRepository: UserRepository {
let database: Database
func all() -> EventLoopFuture<[User]> {
return User.query(on: database).all()
}
}
```
También debes eliminar la conformidad de `ServiceType`, puesto que ya no existe en Vapor 4.
```diff
- extension DatabaseUserRepository {
- static let serviceSupports: [Any.Type] = [Athlete.self]
- static func makeService(for worker: Container) throws -> Self {
- return .init()
- }
- }
```
En su lugar, deberías crear un `UserRepositoryFactory`:
```swift
struct UserRepositoryFactory {
var make: ((Request) -> UserRepository)?
mutating func use(_ make: @escaping ((Request) -> UserRepository)) {
self.make = make
}
}
```
Esta factory es responsable de devolver un `UserRepository` para un `Request`.
El siguiente paso es agregar una extensión a `Application` para especificar su factory:
```swift
extension Application {
private struct UserRepositoryKey: StorageKey {
typealias Value = UserRepositoryFactory
}
var users: UserRepositoryFactory {
get {
self.storage[UserRepositoryKey.self] ?? .init()
}
set {
self.storage[UserRepositoryKey.self] = newValue
}
}
}
```
Para usar el repositorio creado dentro de una `Request`, agrega esta extensión a la `Request`:
```swift
extension Request {
var users: UserRepository {
self.application.users.make!(self)
}
}
```
El último paso es especificar el factory dentro de `configure.swift`
```swift
app.users.use { req in
DatabaseUserRepository(database: req.db)
}
```
Ahora puedes acceder a tu repositorio en tus controladores de ruta con: `req.users.all()` y reemplazar fácilmente los tests internos del factory.
Si deseas utilizar un repositorio simulado dentro de los tests, primero crea un `TestUserRepository`
```swift
final class TestUserRepository: UserRepository {
var users: [User]
let eventLoop: EventLoop
init(users: [User] = [], eventLoop: EventLoop) {
self.users = users
self.eventLoop = eventLoop
}
func all() -> EventLoopFuture<[User]> {
eventLoop.makeSuccededFuture(self.users)
}
}
```
Ahora puedes usar este repositorio simulado dentro de tus tests de la siguiente manera:
```swift
final class MyTests: XCTestCase {
func test() throws {
let users: [User] = []
app.users.use { TestUserRepository(users: users, eventLoop: $0.eventLoop) }
...
}
}
```

View File

@ -1,16 +1,16 @@
# Aggiornamento a 4.0
Questa guida vi mostrerà come aggiornare un progetto Vapor 3.x a Vapor 4.x. La guida cercherà di coprire tutti i cambiamenti riguardanti i pacchetti Vapor e anche alcuni dei più comuni pacchetti di terze parti. Se notate qualcosa di mancante, non esitate a chiedere aiuto nella [chat del team Vapor](https://discord.gg/vapor). Anche issues e pull requests su GitHub sono ben accette.
Questa guida ti mostrerà come aggiornare un progetto Vapor 3.x a Vapor 4.x. La guida cercherà di coprire tutti i cambiamenti riguardanti i pacchetti Vapor e anche alcuni dei più comuni pacchetti di terze parti. Se noti che manca qualcosa, non esitare a chiedere aiuto nella [chat del team Vapor](https://discord.gg/vapor). Anche issues e pull requests su GitHub sono ben accette.
## Dipendenze
Per usare Vapor 4, avrete bisogno di almeno Xcode 11.4 e macOS 10.15.
Per usare Vapor 4, avrai bisogno di almeno Xcode 11.4 e macOS 10.15.
La sezione Installazione della documentazione contiene le istruzioni per installare le dipendenze.
## Package.swift
Il primo passo per aggiornare a Vapor 4 è aggiornare il file delle dipendenze del pacchetto. Qui è riportato un esempio di un `Package.swift` aggiornato. Potete anche visitare il [template Package.swift aggiornato](https://github.com/vapor/template/blob/main/Package.swift).
Il primo passo per aggiornare a Vapor 4 è aggiornare il file delle dipendenze del pacchetto. Qui è riportato un esempio di un `Package.swift` aggiornato. Puoi anche visitare il [template Package.swift aggiornato](https://github.com/vapor/template/blob/main/Package.swift).
```diff
-// swift-tools-version:4.0
@ -97,14 +97,14 @@ Vapor potrebbe eventualmente aggiungere il supporto per piattaforme aggiuntive i
Vapor 4 utilizza il supporto nativo di SPM di Xcode 11. Ciò significa che non ci sarà più bisogno di generare il file `.xcodeproj`. Per aprire un progetto Vapor 4 in Xcode, basterà aprire il file `Package.swift` tramite `vapor xcode` o `open Package.swift`, Xcode poi procederà a scaricare le dipendenze.
Una volta aggiornato il Package.swift, potreste dover chiudere Xcode e rimuovere i seguenti file dalla directory del progetto:
Una volta aggiornato il Package.swift, potresti dover chiudere Xcode e rimuovere i seguenti file dalla directory del progetto:
- `Package.resolved`
- `.build`
- `.swiftpm`
- `*.xcodeproj`
Una volta che le nuove dipendenze sono state scaricate, noterete errori di compilazione, probabilmente più di qualcuno. Non vi preoccupate! Vi mostreremo come risolverli.
Una volta che le nuove dipendenze sono state scaricate, noterai errori di compilazione, probabilmente un bel po'. Non ti preoccupare! Ti mostreremo come risolverli.
## Run
@ -126,7 +126,7 @@ Il file `main.swift` andrà a sostituire il file `app.swift`, quindi potete rimu
## App
Diamo un'occhiata a come aggiornare la strutura di base di App.
Diamo un'occhiata a come aggiornare la struttura di base di App.
### configure.swift
@ -158,9 +158,9 @@ public func configure(_ app: Application) throws {
}
```
Cambiamenti di sintassi per cose come routing, middleware, fluent ecc. sono menzionati nelle sezioni seguenti.
Cambiamenti di sintassi per cose come routing, middleware, fluent, ecc. sono menzionati nelle sezioni seguenti.
### routes.swift
### boot.swift
Il contenuto di `boot` può essere inserito nel metodo `configure` dal momento che ora accetta un'istanza di `Application`.
@ -212,13 +212,13 @@ Invece che registrare un `MiddlewareConfig` ai servizi, i middleware possono ess
+ req.client.get("https://vapor.codes")
```
Come Application, anche Request espone servizi come proprietà e metodi. È fortemente consigliato l'uso di servizi specifici alla Request da dentro le chiusure dei route handler.
Come Application, anche Request espone servizi come proprietà e metodi. È fortemente consigliato l'uso di servizi specifici alla Request da dentro le closure dei route handler.
Questo nuovo pattern va a sostituire il vecchio pattern di `Container` e `Service` e `Config` che era usato in Vapor 3.
### Providers
### Provider
I providers sono stati rimossi in Vapor 4. I providers erano usati per registrare servizi e configurazioni ai servizi. Ora i pacchetti possono estendere direttamente `Application` e `Request` per registrare servizi e configurazioni.
I provider sono stati rimossi in Vapor 4. I provider erano usati per registrare servizi e configurazioni ai servizi. Ora i pacchetti possono estendere direttamente `Application` e `Request` per registrare servizi e configurazioni.
Diamo un'occhiata a come è configurato Leaf in Vapor 4.
@ -241,7 +241,7 @@ Per usare Leaf, basta usare `app.leaf`.
### Ambiente
Si può accedere all'ambiente attuale (produzione, sviluppo ecc) tramite la proprietà `app.environment`.
Si può accedere all'ambiente attuale (produzione, sviluppo, ecc.) tramite la proprietà `app.environment`.
### Servizi Personalizzati
@ -294,7 +294,7 @@ app.storage[MyNumber.self] = 5
print(app.storage[MyNumber.self]) // 5
```
L'accsso a `Application.storage` può essere avvolto in una proprietà computata per rendere il codice più leggibile:
L'accesso a `Application.storage` può essere avvolto in una proprietà computata per rendere il codice più leggibile:
```swift
extension Application {
@ -316,7 +316,7 @@ Vapor 4 utilizza le API asincrone di SwiftNIO direttamente senza fare l'overload
Il cambiamento più ovvio è che il typealias `Future` di `EventLoopFuture` è stato rimosso. Si può risolvere questo problema semplicemente usando "trova e sostituisci".
In più NIO non supporta il label `to:` che veniva usato da Vapor 3, che comunque dato il nuovo sistema di inferenza dei tipi di Swift 5.2, non è più necessario.
In più NIO non supporta il label `to:` che veniva usato da Vapor 3, che comunque dato il nuovo sistema di inferenza dei tipi di Swift 5.2 non è più necessario.
```diff
- futureA.map(to: String.self) { ... }
@ -338,7 +338,7 @@ Il `flatMap` globale di Vapor 3 per combinare diversi futuri non è più disponi
```diff
- flatMap(futureA, futureB) { a, b in
+ futureA.and(futureB).flatMap { (a, b) in
// Do something with a and b.
// Fai qualcosa con a e b.
}
```
@ -346,7 +346,7 @@ Il `flatMap` globale di Vapor 3 per combinare diversi futuri non è più disponi
Molti metodi e proprietà che utilizzavano `Data` ora usano `ByteBuffer`, un tipo di storage di byte più potente e performante. Potete leggere di più su `ByteBuffer` nella [documentazione di SwiftNIO](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer).
Per convertire un `ByteBuffer` a `Data` si può usare:
Per convertire un `ByteBuffer` in `Data` si può usare:
```swift
Data(buffer.readableBytesView)
@ -415,7 +415,7 @@ futureA.flatMapThrowing { a in
## Routing
Ora le routes sono registrate direttamente su Application.
Ora le route sono registrate direttamente su Application.
```swift
app.get("hello") { req in
@ -423,9 +423,9 @@ app.get("hello") { req in
}
```
Ciò significa che non c'è più bisogno di registrare un router ai servizi. Basta passare l'istanza di `Application` al metodo `routes` e si può cominciare ad aggiungere endpoints. Tutti i metodi disponibili sul `RoutesBuilder` sono disponibili su `Application`.
Ciò significa che non c'è più bisogno di registrare un router ai servizi. Basta passare l'istanza di `Application` al metodo `routes` e si può cominciare ad aggiungere endpoint. Tutti i metodi disponibili sul `RoutesBuilder` sono disponibili su `Application`.
### Content Sincrono
### Contenuto Sincrono
La decodifica delle richieste è ora sincrona.
@ -458,7 +458,7 @@ In Vapor 4 gli URL sono divisi da virgole e non devono contenere `/`.
### Parametri di una route
Il protocollo `Parameter` è stato rimosso per promuovere l'uso di parametri chiamati esplicitamente. In questo modo si evitano problemi di parametri duplicati e il fetching non ordinato dei parametri nei middleware e nei gestori delle routes.
Il protocollo `Parameter` è stato rimosso per promuovere l'uso di parametri chiamati esplicitamente. In questo modo si evitano problemi di parametri duplicati e il fetching non ordinato dei parametri nei middleware e nei gestori delle route.
```diff
- router.get("planets", String.parameter) { req in
@ -505,7 +505,7 @@ Ora l'API di Fluent è indipendente dal database su cui viene utilizzata. Basta
+ import Fluent
```
### Models
### Modelli
I modelli utilizzano il protocollo `Model` e devono essere delle classi:
@ -604,7 +604,7 @@ Le tabelle pivot sono modelli normali che conformano a `Model` con due propriet
### Query
Si può accedere al contesto del database utilizzando `req.db` nei route handlers.
Si può accedere al contesto del database utilizzando `req.db` nei route handler.
```diff
- Planet.query(on: req)
@ -620,7 +620,7 @@ Ora i key path ai campi hanno il prefisso `$` per specificare che si tratta del
+ filter(\.$foo == ...)
```
### Migrations
### Migrazioni
Le migrazioni devono essere scritte manualmente e non si basano più sul concetto di reflection:
@ -645,19 +645,19 @@ I metodi `prepare` e `revert` non sono più statici.
+ func prepare(on database: Database) -> EventLoopFuture<Void>
```
La creazione di uno schema builder è fatta tramite un metodo su `Database`.
La creazione di un costruttore di schema è fatta tramite un metodo su `Database`.
```diff
- <#Database#>Database.create(Galaxy.self, on: conn) { builder in
- // Use builder.
- // Usa builder.
- }
+ var builder = database.schema("Galaxy")
+ // Use builder.
+ // Usa builder.
```
I metodi `create`, `update` e `delete` sono metodi dello schema builder e assomigliano al funzionamento di un query builder.
I metodi `create`, `update` e `delete` sono metodi del costruttore di schema e assomigliano al funzionamento di un costruttore di query.
La definizione dei campi è tipata tramite stringhe e segue il seguente pattern:
La definizione dei campi è tipata tramite stringhe e usa il seguente pattern:
```swift
field(<name>, <type>, <constraints>)
@ -668,7 +668,7 @@ field(<name>, <type>, <constraints>)
+ builder.field("name", .string, .required)
```
La costruzione degli schemi può essere concatenata come un query builder:
La costruzione degli schemi può essere concatenata come un costruttore di query:
```swift
database.schema("Galaxy")
@ -765,7 +765,7 @@ app.users.use { req in
}
```
Si può ora accedere alla repository nei route handlers con `req.users.all()` e si può facilmente sostituire la repository con una mock per i test. Basta creare un nuovo file `TestUserRepository`:
Si può ora accedere alla repository nei route handler con `req.users.all()` e si può facilmente sostituire la repository con una simulata per i test. Basta creare un nuovo file `TestUserRepository`:
```swift
final class TestUserRepository: UserRepository {
var users: [User]

View File

@ -0,0 +1,3 @@
# Reindirizzando...
<meta http-equiv="refresh" content="0; url=https://legacy.docs.vapor.codes/">

View File

@ -1,5 +1,29 @@
#!/usr/bin/swift
/*
SPDX-License-Identifier: MIT
Copyright (c) 2023 Vapor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import Foundation
struct SearchIndex: Codable {
@ -43,6 +67,7 @@ let knownLanguages = [
"es",
"fr",
"it",
"ja",
"ko",
"nl",
"pl",

View File

@ -10,7 +10,7 @@ repo_url: http://github.com/vapor/vapor
edit_uri: https://github.com/vapor/documentation/edit/main/docs/
# Copyright
copyright: "Copyright &copy; Vapor Community"
copyright: "Vapor Documentation &copy; 2023 by Vapor is licensed under CC BY-NC-SA 4.0"
# Configuration
theme:
@ -60,6 +60,8 @@ extra:
link: https://discord.gg/vapor
- icon: fontawesome/brands/github
link: https://github.com/vapor
- icon: fontawesome/brands/mastodon
link: https://hachyderm.io/@codevapor
extra_css:
- stylesheets/syntax.css
@ -84,386 +86,446 @@ plugins:
# i18n plugin documentation
# https://github.com/ultrabug/mkdocs-static-i18n
- i18n:
default_language: 'en'
search_reconfigure: false
material_alternate: true
# Add the new languages here. DON'T CHANGE THE DEFAULT LANGUAGE
docs_structure: suffix
fallback_to_default: true
languages:
en:
- build: true
default: true
locale: en
name: English
build: true
zh:
- build: true
default: false
locale: zh
name: 简体中文
nav_translations:
APNS: 苹果推送服务
Advanced: 进阶
Async: 异步
Authentication: 认证
Basics: 入门
Client: 客户端
Commands: 命令
Content: 内容
Contributing: 贡献
Contributing Guide: 贡献指南
Crypto: 加密
Custom Tags: 自定义标签
Deploy: 部署
Environment: 环境
Errors: 错误
Files: 文件
Fluent: Fluent
Folder Structure: 项目结构
Getting Started: 开始
Hello, world: 你好世界
Install: 安装
JWT: JWT
Leaf: Leaf
Logging: 日志
Middleware: 中间件
Migrations: 迁移
Model: 模型
Overview: 概述
Passwords: 密码
Query: 查询
Queues: 队列
Redis: Redis
Relations: 关联
Routing: 路由
Schema: 模式
Security: 安全
Server: 服务器
Services: 服务
Sessions: 会话
SwiftPM: Swift 包管理器
Testing: 测试
Transactions: 事务
Validation: 验证
Version (4.0): 版本 (4.0)
WebSockets: 即时通讯
Welcome: 序言
Xcode: Xcode
site_name: Vapor 中文文档
build: true
nl:
- build: true
default: false
locale: nl
name: Nederlands
nav_translations:
Advanced: Geavanceerd
Async: Asynchroon
Authentication: Authenticatie
Basics: Basis
Commands: Commando's
Content: Inhoud
Contributing: Bijdragen
Contributing Guide: Gids Bijdragen
Crypto: Encryptie
Custom Tags: Zelfgemaakte Tags
Deploy: Opzetten
Environment: Omgeving
Files: Bestanden
Folder Structure: Folder Structuur
Getting Started: Aan De Slag
Hello, world: Hallo, wereld
Install: Installeren
Legacy Docs: Oude Documentatie
Logging: Loggen
Migrations: Migraties
Overview: Overzicht
Passwords: Wachtwoorden
Query: Opvragen
Queues: Wachtrijen
Relations: Relaties
Routing: Routering
Schema: Schema
Security: Veiligheid
Services: Diensten
Sessions: Sessies
Testing: Testen
Transactions: Transacties
Upgrading: Upgraden
Validation: Validatie
Version (4.0): Versie (4.0)
Welcome: Welkom
site_name: Vapor Documentatie
build: true
fr:
- build: true
default: false
locale: fr
name: Français
nav_translations:
Advanced: Avancé
Async: Asynchrone
Authentication: Authentification
Basics: Bases
Commands: Commandes
Content: Contenu
Contributing: Contribuer
Contributing Guide: Guide de contribution
Controllers: Contrôleurs
Custom Tags: Tags customisés
Errors: Erreurs
Deploy: Deployer
Environment: Environement
Files: Fichiers
Folder Structure: Structure du Dossier
Getting Started: Commencer
Hello, world: Bonjour, monde
Install: Installer
Legacy Docs: Documents hérité
Migrations: Migrations
Overview: Aperçu
Passwords: Mots de passe
Query: Requête
Queues: Files d'attente
Relataions: Relations
Release Notes: Notes de Version
Request: Requête
Routing: Routage
Schema: Schema
Security: Securité
Services: Services
Sessions: Sessions
Testing: Test
Transactions: Transactions
Upgrading: Mettre à jour
Validation: Validation
Version (4.0): Version (4.0)
Welcome: Bienvenue
site_name: Documentation Vapor
build: true
de:
- build: true
default: false
locale: de
name: German
nav_translations:
Advanced: Erweitert
Authentication: Authentifzierung
Basics: Grundlagen
Commands: Befehle
Content: Modelbindung
Contributing: Mitwirken
Contributing Guide: Leitfaden für Beiträge
Deploy: Bereitstellung
Environment: Umgebung
Errors: Fehlerbehandlung
Files: Dateien
Folder Structure: Verzeichnis
Getting Started: Einführung
Hello, world: Hallo Welt
Install: Installation
Logging: Protokollierung
Migrations: Migrationen
Model: Models
Overview: Übersicht
Query: Abfrage
Relations: Beziehungen
Security: Sicherheit
Services: Dienste
Sessions: Sitzungen
Testing: Testen
Transactions: Transaktionen
Validation: Validierung
Welcome: Begrüßung
site_name: Vapor Dokumentation
build: true
it:
- build: true
default: false
locale: it
name: Italiano
nav_translations:
APNS: APNS
Advanced: Avanzate
Async: Asincrono
Authentication: Autenticazione
Basics: Basi
Client: Client
Commands: Comandi
Content: Contenuto
Contributing: Contribuire
Contributing Guide: Guida alla Contribuzione
Controllers: Controller
Crypto: Crittografia
Custom Tags: Tag Personalizzati
Deploy: Deploy
Environment: Ambiente
Errors: Errori
Files: File
Fluent: Fluent
Folder Structure: Struttura della Cartella
Getting Started: Inizio
Hello, world: Ciao, mondo
Install: Installazione
JWT: JWT
Leaf: Leaf
Legacy Docs: Documentazione Obsoleta
Logging: Logging
Middleware: Middleware
Migrations: Migrazioni
Model: Modello
Overview: Panoramica
Passwords: Password
Query: Query
Queues: Code
Redis: Redis
Relations: Relazioni
Release Notes: Note sulla Versione
Routing: Routing
Schema: Schema
Security: Sicurezza
Server: Server
Services: Servizi
Sessions: Sessioni
SwiftPM: SwiftPM
Testing: Test
Transactions: Transazioni
Upgrading: Aggiornamento
Validation: Validazione
Version (4.0): Versione (4.0)
WebSockets: WebSockets
Welcome: Benvenuto
Xcode: Xcode
site_name: Documentazione di Vapor
build: true
es:
- build: true
default: false
locale: es
name: Español
nav_translations:
APNS: APNS
Advanced: Avanzado
Async: Asincronía
Authentication: Autenticación
Basics: Fundamentos
Client: Cliente
Commands: Comandos
Content: Content
Contributing: Colaborar
Contributing Guide: Guía para Colaborar
Controllers: Controladores
Crypto: Criptografía
Custom Tags: Etiquetas Personalizadas
Deploy: Desplegar
Environment: Entorno
Errors: Errores
Files: Ficheros
Fluent: Fluent
Folder Structure: Estructura de Carpetas
Getting Started: Comenzando
Hello, world: Hola, mundo
Install: Instalación
JWT: JWT
Leaf: Leaf
Legacy Docs: Documentación Legacy
Logging: Logging
Middleware: Middleware
Migrations: Migraciones
Model: Modelo
Overview: Presentación
Passwords: Contraseñas
Query: Consultas
Queues: Colas
Redis: Redis
Relations: Relaciones
Routing: Routing
Schema: Esquema
Security: Seguridad
Server: Servidor
Services: Servicios
Sessions: Sesiones
SwiftPM: SwiftPM
Testing: Testing
Transactions: Transacciones
Upgrading: Actualizar
Validation: Validación
Version (4.0): Versión (4.0)
WebSockets: WebSockets
Welcome: Bienvenido
Xcode: Xcode
site_name: Documentación de Vapor
build: true
pl:
- build: true
default: false
locale: pl
name: Polski
nav_translations:
APNS: APNS
Advanced: Zaawansowane
Async: Asynchroniczność
Authentication: Autentykacja
Basics: Podstawy
Client: Klient
Commands: Komendy
Content: Content
Contributing: Kontrybucja
Contributing Guide: Przewodnik do kontrybucji
Crypto: Kryptografia
Custom Tags: Własne tagi
Deploy: Wdrożenie
Environment: Środowisko
Errors: Błędy
Files: Pliki
Fluent: Fluent
Folder Structure: Struktura folderów
Getting Started: Jak zacząć
Hello, world: Witaj, świecie
Install: Instalacja
JWT: JWT
Leaf: Leaf
Legacy Docs: Przestażała dokumentacja
Logging: Logowanie
Middleware: Middleware
Migrations: Migracje
Model: Model
Overview: Prezentacja
Passwords: Hasła
Query: Zapytania
Queues: Kolejki
Redis: Redis
Relations: Relacje
Release Notes: Informacja o wersji
Routing: Kierowanie ruchem
Schema: Schematy
Security: Bezpieczeństwo
Server: Serwer
Services: Serwisy
Sessions: Sesje
SwiftPM: SwiftPM
Testing: Testowanie
Transactions: Transakcje
Upgrading: Aktualizacja
Validation: Walidacja
Version (4.0): Wersja (4.0)
WebSockets: WebSockety
Welcome: Witaj
Xcode: Xcode
site_name: Dokumentacja Vapor
ko:
- build: true
default: false
locale: ko
name: 한국어
nav_translations:
Advanced: 고급
Async: 비동기 처리
Authentication: 인증
Basics: 기본 사항
Client: 클라이언트
Commands: 명령어
Content: 컨텐츠
Contributing: 기여하기
Contributing Guide: 기여 가이드
Crypto: 암호화
Custom Tags: 사용자 정의 태그
Deploy: 배포
Environment: 환경 설정
Errors: 에러
Files: 파일
Folder Structure: 폴더 구조
Getting Started: 시작하기
Install: 설치
Legacy Docs: 이전 문서
Logging: 로깅
Migrations: 마이그레이션
Model: 모델
Overview: 개요
Passwords: 비밀번호
Query: 쿼리
Queues: 대기열
Relations: 관계
Routing: 라우팅
Schema: 스키마
Security: 보안
Services: 서비스
Sessions: 세션
Testing: 테스트
Transactions: 트랜잭션
Upgrading: 업그레이드
Validation: 유효성 검사
Version (4.0): 버전 (4.0)
WebSockets: 웹소켓
Welcome: 환영합니다
site_name: Vapor 문서
build: true
# Add navigation translations here
nav_translations:
nl:
Welcome: Welkom
Install: Installeren
Getting Started: Aan De Slag
Hello, world: Hallo, wereld
Folder Structure: Folder Structuur
Basics: Basis
Routing: Routering
Content: Inhoud
Validation: Validatie
Async: Asynchroon
Logging: Loggen
Environment: Omgeving
Overview: Overzicht
Relations: Relaties
Migrations: Migraties
Query: Opvragen
Transactions: Transacties
Schema: Schema
Advanced: Geavanceerd
Testing: Testen
Files: Bestanden
Commands: Commando's
Queues: Wachtrijen
Sessions: Sessies
Services: Diensten
Security: Veiligheid
Authentication: Authenticatie
Crypto: Encryptie
Passwords: Wachtwoorden
Deploy: Opzetten
Version (4.0): Versie (4.0)
Legacy Docs: Oude Documentatie
Upgrading: Upgraden
Custom Tags: Zelfgemaakte Tags
Contributing: Bijdragen
Contributing Guide: Gids Bijdragen
fr:
Welcome: Bienvenue
Install: Installer
Getting Started: Commencer
Hello, world: Bonjour, monde
Basis: Bases
Folder Structure: Strucutre du Dossier
Content: Contenu
Validation: Validation
Async: Asynchrone
Environment: Environement
Overview: Aperçu
Relataions: Relations
Migrations: Migrations
Query: Requête
Transactions: Transactions
Schema: Schema
Advanced: Avancé
Testing: Test
Files: Fichiers
Commands: Commandes
Queues: Files d'attente
Sessions: Sessions
Services: Sercvies
Security: Securité
Authentication: Authentication
Passwords: Mots de passe
Deploy: Deployer
Version (4.0): Version (4.0)
Legacy Docs: Documents hérité
Upgrading: Mettre à jour
Custom Tags: Tags customisés
Contributing: Contribuer
Contributing Guide: Guide de la contribution
de:
Welcome: Begrüßung
Install: Installation
Getting Started: Einführung
Basics: Grundlagen
Hello, world: Hallo Welt
Folder Structure: Verzeichnis
Validation: Validierung
Logging: Protokollierung
Environment: Umgebung
Errors: Fehlerbehandlung
Model: Models
Overview: Übersicht
Relations: Beziehungen
Migrations: Migrationen
Query: Abfrage
Transactions: Transaktionen
Sessions: Sitzungen
Advanced: Erweitert
Testing: Testen
Files: Dateien
Commands: Befehle
Services: Dienste
Authentication: Authentifzierung
Security: Sicherheit
Deploy: Bereitstellung
Contributing: Mitwirken
Contributing Guide: Leitfaden für Beiträge
Content: Modelbindung
zh:
Welcome: 序言
Install: 安装
Getting Started: 开始
Hello, world: 你好世界
Folder Structure: 项目结构
SwiftPM: Swift 包管理器
Xcode: Xcode
Basics: 入门
Routing: 路由
Content: 内容
Client: 客户端
Validation: 验证
Async: 异步
Logging: 日志
Environment: 环境
Errors: 错误
Fluent: Fluent
Overview: 概述
Model: 模型
Relations: 关联
Migrations: 迁移
Query: 查询
Transactions: 事务
Schema: 模式
Leaf: Leaf
Custom Tags: 自定义标签
Redis: Redis
Advanced: 进阶
Middleware: 中间件
Testing: 测试
Server: 服务器
Files: 文件
Commands: 命令
Queues: 队列
WebSockets: 即时通讯
Sessions: 会话
Services: 服务
Security: 安全
APNS: 苹果推送服务
Deploy: 部署
Authentication: 认证
Crypto: 加密
Passwords: 密码
JWT: JWT
Contributing: 贡献
Contributing Guide: 贡献指南
Version (4.0): 版本 (4.0)
it:
Welcome: Benvenuto
Install: Installazione
Getting Started: Inizio
Hello, world: Ciao, mondo
Folder Structure: Struttura della Cartella
SwiftPM: SwiftPM
Xcode: Xcode
Basics: Basi
Routing: Routing
Content: Contenuto
Client: Client
Validation: Validazione
Async: Asincrono
Logging: Logging
Environment: Ambiente
Errors: Errori
Fluent: Fluent
Overview: Panoramica
Model: Modello
Relations: Relazioni
Migrations: Migrazioni
Query: Query
Transactions: Transazioni
Schema: Schema
Leaf: Leaf
Custom Tags: Tag Personalizzati
Redis: Redis
Advanced: Avanzate
Middleware: Middleware
Testing: Test
Server: Server
Files: File
Commands: Comandi
Queues: Code
WebSockets: WebSockets
Sessions: Sessioni
Services: Servizi
Security: Sicurezza
APNS: APNS
Deploy: Deploy
Authentication: Autenticazione
Crypto: Crittografia
Passwords: Password
JWT: JWT
Contributing: Contribuire
Contributing Guide: Guida alla Contribuzione
Version (4.0): Versione (4.0)
Legacy Docs: Documentazione Legacy
Upgrading: Aggiornamento
es:
Welcome: Bienvenido
Install: Instalación
Getting Started: Comenzando
Hello, world: Hola, mundo
Folder Structure: Estructura de Carpetas
SwiftPM: SwiftPM
Xcode: Xcode
Basics: Fundamentos
Routing: Routing
Content: Content
Client: Cliente
Validation: Validación
Async: Async
Logging: Logging
Environment: Entorno
Errors: Errores
Fluent: Fluent
Overview: Presentación
Model: Modelo
Relations: Relaciones
Migrations: Migraciones
Query: Consultas
Transactions: Transacciones
Schema: Esquema
Leaf: Leaf
Custom Tags: Etiquetas Personalizadas
Redis: Redis
Advanced: Avanzado
Middleware: Middleware
Testing: Testing
Server: Servidor
Files: Ficheros
Commands: Comandos
Queues: Colas
WebSockets: WebSockets
Sessions: Sesiones
Services: Servicios
Security: Seguridad
APNS: APNS
Deploy: Desplegar
Authentication: Autenticación
Crypto: Criptografía
Passwords: Contraseñas
JWT: JWT
Contributing: Colaborar
Contributing Guide: Guía para Colaborar
Version (4.0): Versión (4.0)
Legacy Docs: Documentación Legacy
Upgrading: Actualizar
pl:
Welcome: Witaj
Install: Instalacja
Getting Started: Jak zacząć
Hello, world: Witaj, świecie
Folder Structure: Struktura folderów
SwiftPM: SwiftPM
Xcode: Xcode
Basics: Podstawy
Routing: Kierowanie ruchem
Content: Content
Client: Klient
Validation: Walidacja
Async: Asynchroniczność
Logging: Logowanie
Environment: Środowisko
Errors: Błędy
Fluent: Fluent
Overview: Prezentacja
Model: Model
Relations: Relacje
Migrations: Migracje
Query: Zapytania
Transactions: Transakcje
Schema: Schematy
Leaf: Leaf
Custom Tags: Własne tagi
Redis: Redis
Advanced: Zaawansowane
Middleware: Middleware
Testing: Testowanie
Server: Serwer
Files: Pliki
Commands: Komendy
Queues: Kolejki
WebSockets: WebSockety
Sessions: Sesje
Services: Serwisy
Security: Bezpieczeństwo
APNS: APNS
Deploy: Wdrożenie
Authentication: Autentykacja
Crypto: Kryptografia
Passwords: Hasła
JWT: JWT
Contributing: Kontrybucja
Contributing Guide: Przewodnik do kontrybucji
Version (4.0): Wersja (4.0)
Legacy Docs: Przestażała dokumentacja
Upgrading: Aktualizacja
Release Notes: Informacja o wersji
ko:
Welcome: 환영합니다
Install: 설치
Getting Started: 시작하기
Folder Structure: 폴더 구조
Basics: 기본 사항
Routing: 라우팅
Content: 컨텐츠
Client: 클라이언트
Validation: 유효성 검사
Async: 비동기 처리
Logging: 로깅
Environment: 환경 설정
Errors: 에러
Overview: 개요
Model: 모델
Relations: 관계
Migrations: 마이그레이션
Query: 쿼리
Transactions: 트랜잭션
Schema: 스키마
Advanced: 고급
Testing: 테스트
Files: 파일
Commands: 명령어
Queues: 대기열
WebSockets: 웹소켓
Sessions: 세션
Services: 서비스
Security: 보안
Authentication: 인증
Crypto: 암호화
Passwords: 비밀번호
Deploy: 배포
Version (4.0): 버전 (4.0)
Legacy Docs: 이전 문서
Upgrading: 업그레이드
Custom Tags: 사용자 정의 태그
Contributing: 기여하기
Contributing Guide: 기여 가이드
- build: true
default: false
locale: ja
name: 日本語
nav_translations:
Advanced: 上級
Async: 非同期
Authentication: 認証
Basics: 基礎
Client: クライアント
Commands: コマンド
Content: 内容
Contributing: 貢献
Contributing Guide: 貢献ガイド
Crypto: 暗号
Custom Tags: カスタムタグ
Deploy: デプロイ
Environment: 環境
Errors: エラー
Files: ファイル
Folder Structure: フォルダ構造
Getting Started: はじめに
Install: インストール
Legacy Docs: レガシードキュメント
Logging: ロギング
Migrations: マイグレーション
Model: モデル
Overview: 概要
Passwords: パスワード
Query: クエリ
Queues:
Relations: 関係
Release Notes: リリースノート
Routing: ルーティング
Schema: スキーマ
Security: セキュリティ
Services: サービス
Sessions: セッション
Testing: テスト
Transactions: トランザクション
Upgrading: アップグレード
Validation: バリデーション
Welcome: ようこそ
site_name: Vapor ドキュメント
reconfigure_material: true
reconfigure_search: false
nav:
- Welcome: "index.md"
- Install:
@ -478,7 +540,7 @@ nav:
- Routing: "basics/routing.md"
# TODO: Improve quality
# Mostly just a code sample with little explanation.
# - Controllers: "controllers.md"
- Controllers: "basics/controllers.md"
- Content: "basics/content.md"
- Client: "basics/client.md"
- Validation: "basics/validation.md"
@ -512,6 +574,7 @@ nav:
- WebSockets: "advanced/websockets.md"
- Sessions: "advanced/sessions.md"
- Services: "advanced/services.md"
- Request: "advanced/request.md"
- APNS: "advanced/apns.md"
- Security:
- Authentication: "security/authentication.md"

View File

@ -1,5 +1,29 @@
#!/usr/bin/swift
/*
SPDX-License-Identifier: MIT
Copyright (c) 2023 Vapor
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import Foundation
let basicDirectories = [
@ -61,4 +85,4 @@ func createRedirect(directory: String, newDirectory: String) throws {
let fileURL = URL(fileURLWithPath: "site/\(directory)/index.html")
try FileManager.default.createDirectory(atPath: "site/\(directory)", withIntermediateDirectories: true, attributes: nil)
try redirectString.write(to: fileURL, atomically: true, encoding: .utf8)
}
}