Merge branch 'main' into translation/add-authentication-german-translation

This commit is contained in:
Gwynne Raskind 2023-11-11 11:43:31 -06:00
commit b8c9190626
No known key found for this signature in database
GPG Key ID: 2BD7A0EA7D137CCA
113 changed files with 8896 additions and 824 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

@ -0,0 +1,15 @@
The docs have been updated in PR ##(number). The translations should be updated if required.
Languages:
- [ ] English
- [ ] Chinese
- [ ] German
- [ ] Dutch
- [ ] Italian
- [ ] Spanish
- [ ] Polish
- [ ] Korean
- [ ] Japanese
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,16 +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
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

@ -69,7 +69,11 @@ You can check it out by running `mkdocs serve` in the terminal. Once you are sat
> NOTE: If a file isn't translated, it will just default to the default language file. So you don't have to translate everything all at once.
Finally, you should add the new language to the [issue template](https://github.com/vapor/docs/blob/main/.github/workflows/translation-issue-template.md) to ensure that any future changes are applied to the new translation.
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

@ -28,7 +28,7 @@ app.middleware.use(MiddlewareA())
app.middleware.use(MiddlewareB())
app.group(MiddlewareC()) {
$0.get("hello") { req in
$0.get("hello") { req in
"Hello, middleware."
}
}
@ -124,6 +124,14 @@ app.middleware.use(file)
Zodra `FileMiddleware` is geregistreerd, kan een bestand als `Public/images/logo.png` worden gekoppeld vanuit een Leaf template als `<img src="/images/logo.png"/>`.
Als je server is opgenomen in een Xcode Project, zoals een iOS app, gebruik dan dit in de plaats:
```swift
let file = try FileMiddleware(bundle: .main, publicDirectory: "Public")
```
Zorg er ook voor dat je Folder References gebruikt in plaats van Groups in Xcode om de mappenstructuur in resources te behouden na het bouwen van de applicatie.
## CORS Middleware
Cross-origin resource sharing (CORS) is een mechanisme waarmee beperkte bronnen op een webpagina kunnen worden opgevraagd vanuit een ander domein buiten het domein van waaruit de eerste bron werd geserveerd. REST API's die in Vapor zijn gebouwd, hebben een CORS-beleid nodig om verzoeken veilig te kunnen terugsturen naar moderne webbrowsers.

View File

@ -309,6 +309,9 @@ Als u geen wachtrij opgeeft, wordt de taak uitgevoerd op de `standaard` wachtrij
Met het pakket Queues kunt u ook taken plannen die op bepaalde tijdstippen moeten worden uitgevoerd.
!!! warning "Waarschuwing"
Geplande taken werken alleen als ze zijn ingesteld voordat de applicatie opstart, zoals in `configure.swift`. Ze werken niet in route handlers.
### De planner starten
De scheduler vereist dat een afzonderlijk workerproces draait, gelijkaardig aan de queue worker. U kunt de worker starten door dit commando uit te voeren:

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)。

View File

@ -82,6 +82,16 @@ struct Hello: LifecycleHandler {
func willBoot(_ app: Application) throws {
app.logger.info("Hello!")
}
// Called after application boots.
func didBoot(_ app: Application) throws {
app.logger.info("Server is running")
}
// Called before application shutdown.
func shutdown(_ app: Application) {
app.logger.info("Goodbye!")
}
}
// Add lifecycle handler.

View File

@ -81,6 +81,16 @@ struct Hello: LifecycleHandler {
func willBoot(_ app: Application) throws {
app.logger.info("Hello!")
}
// Wordt aangeroepen nadat de applicatie is opgestart.
func didBoot(_ app: Application) throws {
app.logger.info("Server is running")
}
// Wordt aangeroepen voordat de applicatie wordt afgesloten.
func shutdown(_ app: Application) {
app.logger.info("Goodbye!")
}
}
// Voeg levenscyclus handler toe.

View File

@ -81,6 +81,16 @@ struct Hello: LifecycleHandler {
func willBoot(_ app: Application) throws {
app.logger.info("Hello!")
}
// 应用程序启动后调用。
func didBoot(_ app: Application) throws {
app.logger.info("Server is running")
}
// 在应用程序关闭前调用。
func shutdown(_ app: Application) {
app.logger.info("Goodbye!")
}
}
// 添加生命周期处理程序。

View File

@ -34,7 +34,7 @@ final class MyTests: XCTestCase {
}
```
Elke functie die begint met `test` zal automatisch worden uitgevoerd wanneer uw app wordt getest.
Elke functie die begint met `test` zal automatisch worden uitgevoerd wanneer uw app wordt getest.
### Tests Uitvoeren
@ -42,7 +42,8 @@ Gebruik `cmd+u` met het `-Package` schema geselecteerd om tests in Xcode uit te
## Testbare Applicatie
Initialiseer een instantie van `Application` met behulp van de `.testing` omgeving. Je moet `app.shutdown()` aanroepen voordat deze applicatie de-initialiseert.
Initialiseer een instantie van `Application` met behulp van de `.testing` omgeving. Je moet `app.shutdown()` aanroepen voordat deze applicatie de-initialiseert.
De shutdown is nodig om de resources die de app heeft geclaimd vrij te geven. In het bijzonder is het belangrijk om de threads vrij te geven die de applicatie aanvraagt bij het opstarten. Als je `shutdown()` niet aanroept op de app na elke unit test, kan je testsuite crashen met een precondition failure bij het toewijzen van threads voor een nieuwe instantie van `Application`.
```swift
let app = Application(.testing)
@ -63,7 +64,7 @@ try app.test(.GET, "hello") { res in
}
```
De eerste twee parameters zijn de HTTP methode en URL om op te vragen. De afsluiter achteraan accepteert de HTTP respons die je kunt verifiëren met `XCTAssert` methoden.
De eerste twee parameters zijn de HTTP methode en URL om op te vragen. De afsluiter achteraan accepteert de HTTP respons die je kunt verifiëren met `XCTAssert` methoden.
Voor meer complexe verzoeken, kunt u een `beforeRequest` closure toevoegen om headers te wijzigen of inhoud te coderen. Vapor's [Content API](../basics/content.md) is beschikbaar op zowel het test request als het antwoord.
@ -89,7 +90,7 @@ app.testable(method: .inMemory).test(...)
app.testable(method: .running).test(...)
```
De `inMemory` optie wordt standaard gebruikt.
De `inMemory` optie wordt standaard gebruikt.
De `running` optie ondersteunt het doorgeven van een specifieke poort om te gebruiken. Standaard wordt `8080` gebruikt.

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

@ -0,0 +1,70 @@
# Controladores
Los controladores son una gran manera de organizar tu código. Son colecciones de métodos que aceptan una petición (request) y devuleven una respuesta (response).
Un buen sitio donde ubicar tus controladores sería en la carpeta [Controllers](../getting-started/folder-structure.es.md#controllers).
## Descripción
Veamos un controlador de ejemplo.
```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
}
}
```
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`:
```swift
try app.register(collection: TodosController())
```

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

197
docs/basics/errors.es.md Normal file
View File

@ -0,0 +1,197 @@
# Errores
Vapor está basado en el protocolo `Error` de Swift para el manejo de errores. Los manejadores de rutas pueden lanzar (`throw`) un error o devolver un `EventLoopFuture` fallido. Lanzar o devolver un `Error` de Swift resultará en una respuesta de estado (status response) `500` y el error será registrado. `AbortError` y `DebuggableError` pueden usarse para cambiar la respuesta resultante y el registro respectivamente. El manejo de errores lo lleva a cabo `ErrorMiddleware`. Este middleware es añadido por defecto a la aplicación y puede reemplazarse por lógica personalizada si se desea.
## Abort
Vapor proporciona un struct de error por defecto llamado `Abort`. Este struct se conforma con ambos `AbortError` y `DebuggableError`. Puedes inicializarlo con un estado (status) HTTP y un motivo (reason) de fallo opcional.
```swift
// Error 404, motivo (reason) por defecto "Not Found" usado.
throw Abort(.notFound)
// Error 401,motivo (reason) personalizada usado.
throw Abort(.unauthorized, reason: "Invalid Credentials")
```
En situaciones asíncronas antiguas que no soportan lanzamiento de errores y en las que debes devolver un `EventLoopFuture`, como en un closure `flatMap`, puedes devolver un futuro fallido.
```swift
guard let user = user else {
req.eventLoop.makeFailedFuture(Abort(.notFound))
}
return user.save()
```
Vapor incluye una extensión de ayuda para hacer unwrapping de futuro con valores opcionales: `unwrap(or:)`.
```swift
User.find(id, on: db)
.unwrap(or: Abort(.notFound))
.flatMap
{ user in
// User no opcional proporcionado al closure.
}
```
Si `User.find` devuelve `nil`, el futuro fallará con el error suministrado. Sino, se suministrará el `flatMap` con un valor no opcional. Si usas `async`/`await` puedes manejar el opcional de la manera habitual:
```swift
guard let user = try await User.find(id, on: db) {
throw Abort(.notFound)
}
```
## Abort Error
Por defecto, cualquier `Error` de Swift lanzado o devuelto por un closure de ruta resultará en una respuesta `500 Internal Server Error`. Cuando se compile en modo de depuración (debug mode), `ErrorMiddleware` incluirá una descripción del error. Esto se elimina cuando el proyecto es compilado en modo de despliegue (release mode) por razones de seguridad.
Para configurar el estado (status) o motivo (reason) de la petición HTTP resultante para un error en específico, debes conformarlo a `AbortError`.
```swift
import Vapor
enum MyError {
case userNotLoggedIn
case invalidEmail(String)
}
extension MyError: AbortError {
var reason: String {
switch self {
case .userNotLoggedIn:
return "User is not logged in."
case .invalidEmail(let email):
return "Email address is not valid: \(email)."
}
}
var status: HTTPStatus {
switch self {
case .userNotLoggedIn:
return .unauthorized
case .invalidEmail:
return .badRequest
}
}
}
```
## Error Depurable
`ErrorMiddleware` usa el método `Logger.report(error:)` para registrar errores lanzados por tus rutas. Este método comprobará la conformación a protocolos como `CustomStringConvertible` y `LocalizedError` para registrar mensajes legibles.
Para personalizar el registro de errores, puedes conformar tus errores con `DebuggableError`. Este protocolo incluye una variedad de útiles propiedades como un identificador único, localización de fuentes (source location) y traza de la pila (stack trace). La mayoría de estas propiedades son opcionales, lo que facilita adoptar la conformancia.
Para conformarse de mejor forma a `DebuggableError`, tu error debe ser un struct que permita guardar información sobre las trazas de fuente y pila en caso de ser necesario. Debajo hay un ejemplo del enum `MyError` mencionado anteriormente, actualizado para usar un `struct` y capturar información sobre la fuente de error.
```swift
import Vapor
struct MyError: DebuggableError {
enum Value {
case userNotLoggedIn
case invalidEmail(String)
}
var identifier: String {
switch self.value {
case .userNotLoggedIn:
return "userNotLoggedIn"
case .invalidEmail:
return "invalidEmail"
}
}
var reason: String {
switch self.value {
case .userNotLoggedIn:
return "User is not logged in."
case .invalidEmail(let email):
return "Email address is not valid: \(email)."
}
}
var value: Value
var source: ErrorSource?
init(
_ value: Value,
file: String = #file,
function: String = #function,
line: UInt = #line,
column: UInt = #column
) {
self.value = value
self.source = .init(
file: file,
function: function,
line: line,
column: column
)
}
}
```
`DebuggableError` tiene otras propiedades como `possibleCauses` y `suggestedFixes` que puedes usar para mejorar la depuración de tus errores. Echa un vistazo al protocolo para más información.
## Stack Traces (Trazas de Pila)
Vapor incluye soporte para visualizar stack traces para errores normales y crashes de Swift.
### Swift Backtrace
Vapor usa la librería de [SwiftBacktrace](https://github.com/swift-server/swift-backtrace) para proporcionar stack traces después de un error crítico (fatal error) o comprobaciones (assertion) en Linux. Para que esto funcione, tu app debe incluir símbolos de depuración (debug symbols) durante la compilación.
```sh
swift build -c release -Xswiftc -g
```
### Trazas de Error
Por defecto, `Abort` capturará el stack trace actual al inicializarse. Tus tipos de errores personalizados pueden conseguir esto conformándose con `DebuggableError` y guardando `StackTrace.capture()`.
```swift
import Vapor
struct MyError: DebuggableError {
var identifier: String
var reason: String
var stackTrace: StackTrace?
init(
identifier: String,
reason: String,
stackTrace: StackTrace? = .capture()
) {
self.identifier = identifier
self.reason = reason
self.stackTrace = stackTrace
}
}
```
Cuando el [nivel de registro](logging.es.md#nivel) de tu app se establece a `.debug` o inferior, stack traces de errores se incluirán en los registros.
Los stack traces no serán capturados cuando el nivel de registro sea mayor que `.debug`. Para sobrescribir este comportamiento, establece `StackTrace.isCaptureEnabled` manualmente en `configure`.
```swift
// Siempre captura stack traces, sin importar el nivel de registro.
StackTrace.isCaptureEnabled = true
```
## Middleware de Error
`ErrorMiddleware` es el único middleware añadido a tu aplicación por defecto. Este middleware transforma errores de Swift que hayan sido lanzados o devueltos por tus controladores de rutas en respuestas HTTP. Sin este middleware, los errores lanzados darían lugar al cierre de la conexión sin una respuesta.
Para personalizar el manejo de errores más allá de lo que `AbortError` y `DebuggableError` ofrecen, puedes reemplazar `ErrorMiddleware` con tu propia lógica de manejo de errores. Para hacerlo, elimina primero el middleware por defecto estableciendo una configuración vacía en `app.middleware`. Luego, añade tu propio middleware de manejo de errores como el primer middleware de tu aplicación.
```swift
// Elimina todos los middleware existentes.
app.middleware = .init()
// Añade middleware de manejo de errores personalizado primero.
app.middleware.use(MyErrorMiddleware())
```
Muy pocos middleware deberían ir _antes_ del middleware de manejo de errores. Una excepción a tener en cuenta de esta regla es `CORSMiddleware`.

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

View File

@ -13,7 +13,7 @@ Zorg ervoor dat je de heroku cli tool hebt geïnstalleerd.
### HomeBrew
```bash
brew install heroku/brew/heroku
brew tap heroku/brew && brew install heroku
```
### Andere Installatiemogelijkheden
@ -52,7 +52,7 @@ git init
#### Master/Main
Standaard deponeert Heroku de **master/main** branch. Zorg ervoor dat alle wijzigingen in deze branch zijn gecontroleerd voordat u gaat pushen.
Je kunt het beste één branch behouden voor deployments naar Heroku, zoals de **main** of **master** branch. Zorg ervoor dat alle wijzigingen in deze branch zijn gecontroleerd voordat je gaat pushen.
Controleer uw huidige branch met
@ -63,7 +63,7 @@ git branch
De asterisk geeft de huidige branch aan.
```bash
* master
* main
commander
other-branches
```
@ -71,10 +71,10 @@ De asterisk geeft de huidige branch aan.
!!! note "Opmerking"
Als je geen uitvoer ziet en je hebt net `git init` uitgevoerd. Je moet eerst je code committen, daarna krijg je uitvoer te zien van het `git branch` commando.
Als u momenteel _niet_ op **master/main** bent, schakel daar dan over door in te voeren:
Als u momenteel _niet_ op de juiste branch bent, schakel daar dan naar over door in te voeren in de terminal (voor branch **main**):
```bash
git checkout master
git checkout main
```
#### Veranderingen Vastleggen
@ -110,10 +110,10 @@ heroku buildpacks:set vapor/vapor
### Swift versie bestand
Het buildpack dat we hebben toegevoegd zoekt naar een **.swift-version** bestand om te weten welke versie van swift gebruikt moet worden. (vervang 5.2.1 door de versie die uw project nodig heeft).
Het buildpack dat we hebben toegevoegd zoekt naar een **.swift-version** bestand om te weten welke versie van swift gebruikt moet worden. (vervang 5.8.1 door de versie die uw project nodig heeft).
```bash
echo "5.2.1" > .swift-version
echo "5.8.1" > .swift-version
```
Dit creëert **.swift-version** met `5.2.1` als inhoud.
@ -150,12 +150,12 @@ git commit -m "adding heroku build files"
Je bent klaar om uit te rollen, voer dit uit vanaf de terminal. Het kan een tijdje duren om te bouwen, dit is normaal.
```none
git push heroku master
git push heroku main
```
### Scale Up
Als je eenmaal succesvol hebt gebouwd, moet je ten minste één server toevoegen, één web is gratis en je kunt het krijgen met het volgende:
Als je eenmaal succesvol hebt gebouwd, moet je ten minste één server toevoegen. Prijzen starten vanaf $5/maand voor het Eco plan (zie [prijzen](https://www.heroku.com/pricing#containers)). Zorg ervoor dat jouw betaalgegevens geconfigureerd zijn op Heroku. Dan, voor een enkele web worker:
```bash
heroku ps:scale web=1
@ -163,7 +163,7 @@ heroku ps:scale web=1
### Continued Deployment
Elke keer dat je wil updaten, zet je gewoon de laatste veranderingen in master en push je naar heroku en het zal opnieuw deployen
Elke keer dat je wil updaten, zet je gewoon de laatste veranderingen in main en push je naar heroku en het zal opnieuw deployen
## Postgres
@ -171,9 +171,9 @@ Elke keer dat je wil updaten, zet je gewoon de laatste veranderingen in master e
Bezoek uw applicatie op dashboard.heroku.com en ga naar de **Add-ons** sectie.
Voer hier `postgress` in en u zult een optie zien voor `Heroku Postgres`. Selecteer deze.
Voer hier `postgres` in en u zult een optie zien voor `Heroku Postgres`. Selecteer deze.
Kies het hobby dev free plan, en provision. Heroku zal de rest doen.
Kies het Eco plan voor $5/maand (zie [prijzen](https://www.heroku.com/pricing#data-services)), en provision. Heroku zal de rest doen.
Zodra je klaar bent, zie je de database verschijnen onder de **Resources** tab.

View File

@ -13,7 +13,7 @@ Heroku 是一个一站式程序托管平台,你可以通过[heroku.com](https:
### HomeBrew
```bash
brew install heroku/brew/heroku
brew tap heroku/brew && brew install heroku
```
### 其他安装方式
@ -52,7 +52,7 @@ git init
#### Master
默认情况下Heroku 部署 **master** 分支。 确保在推送之前将所有更改都加入此分支。
你应该选择一个分支,并坚持将其用于部署到 Heroku比如 **main****master** 分支。确保在推送之前将所有更改都加入此分支。
通过以下命令检查你当前的分支:
@ -63,7 +63,7 @@ git branch
星号表示当前分支。
```bash
* master
* main
commander
other-branches
```
@ -71,10 +71,10 @@ git branch
> **提示**:如果你没有看到任何输出并且你刚刚执行了 `git init`。 你需要先提交commit你的代码然后你会看到 `git branch` 命令的输出。
如果你当前 _不在_ **master** 上,请输入以下命令来切换
如果你当前 _不在_ 正确的分支上,请输入以下命令来切换(针对 **main** 分支来说)
```bash
git checkout master
git checkout main
```
#### 提交更改
@ -110,13 +110,13 @@ heroku buildpacks:set vapor/vapor
### Swift 版本文件
我们添加的运行包会查找 **.swift-version** 文件以了解要使用的 swift 版本。 (将 5.2.1 替换为你的项目需要的任何版本。)
我们添加的运行包会查找 **.swift-version** 文件以了解要使用的 swift 版本。 (将 5.8.1 替换为你的项目需要的任何版本。)
```bash
echo "5.2.1" > .swift-version
echo "5.8.1" > .swift-version
```
这将创建 **.swift-version** ,内容为 `5.2.1`。
这将创建 **.swift-version** ,内容为 `5.8.1`。
### Procfile
@ -150,12 +150,12 @@ git commit -m "adding heroku build files"
你已准备好开始部署,从终端运行以下命令。 构建过程可能会需要一些时间,不必担心。
```none
git push heroku master
git push heroku main
```
### 扩展
成功构建后,你需要添加至少一台服务器,一个网站服务是免费的,你可以通过以下方式获得它
成功构建后,你需要添加至少一台服务器,Eco 计划的价格从每月$5起参见[定价](https://www.heroku.com/pricing#containers)),请确保在 Heroku 上配置了付款方式。然后,针对单个 web worker 执行下面命令
```bash
heroku ps:scale web=1
@ -163,7 +163,7 @@ heroku ps:scale web=1
### 继续部署
当你想更新时只需将最新的更改推入 master 分支并推送到 heroku它就会重新部署。
当你想更新时只需将最新的更改推入 main 分支并推送到 heroku它就会重新部署。
## Postgres
@ -171,9 +171,9 @@ heroku ps:scale web=1
在 dashboard.heroku.com 上访问你的应用程序,然后转到 **Add-ons** 部分。
从这里输入`postgress`,你会看到`Heroku Postgres`的选项。 选择它。
从这里输入`postgres`,你会看到`Heroku Postgres`的选项。 选择它。
选择爱好开发免费计划hobby dev free plan。 Heroku 将自动完成剩下的工作
选择每月$5的 Eco 计划(参见[定价](https://www.heroku.com/pricing#data-services)),并进行预配。剩下的交给 Heroku 处理
完成后,你会看到数据库出现在 **Resources** 选项卡下。

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

@ -135,6 +135,8 @@ De `@Siblings` eigenschap creëert een veel-op-veel relatie tussen twee modellen
Laten we eens kijken naar een voorbeeld van een veel-op-veel relatie tussen een `Planet` en een `Tag`.
```swift
enum PlanetTagStatus: String, Codable { case accepted, pending }
// Voorbeeld van een pivot model.
final class PlanetTag: Model {
static let schema = "planet+tag"
@ -148,17 +150,25 @@ final class PlanetTag: Model {
@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) throws {
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
}
}
```
Pivots zijn normale modellen die twee `@Parent` relaties bevatten. Één voor elk van de modellen die gerelateerd moeten worden. Extra eigenschappen kunnen worden opgeslagen op de pivot indien gewenst.
Elk model dat tenminste twee `@Parent` relaties bevat, één voor elk model dat gerelateerd moet worden, kan gebruikt worden als pivot. Het model kan aanvullende eigenschappen bevatten, zoals zijn ID, en kan zelfs andere `@Parent` relaties bevatten.
Het toevoegen van een [unieke](schema.md#unique) constraint aan het pivot model kan helpen om overbodige entries te voorkomen. Zie [schema](schema.md) voor meer informatie.
@ -197,13 +207,24 @@ final class Tag: Model {
De `@Siblings` eigenschap heeft methoden voor het toevoegen en verwijderen van modellen uit de relatie.
Gebruik de `attach` methode om een model aan de relatie toe te voegen. Hierdoor wordt het pivot model automatisch aangemaakt en opgeslagen.
Gebruik de `attach()` methode om een enkel model of een array van modellen toe te voegen aan de relatie. Pivot modellen worden indien nodig automatisch aangemaakt en opgeslagen. Er kan een callback closure worden gespecificeerd om aanvullende eigenschappen van elke gecreëerde pivot in te vullen:
```swift
let earth: Planet = ...
let inhabited: Tag = ...
// Voegt het model toe aan de relatie.
try await earth.$tags.attach(inhabited, on: database)
// Vul de pivot attributen in bij het maken van de relatie.
try await earth.$tags.attach(inhabited, on: database) { pivot in
pivot.comments = "This is a life-bearing planet."
pivot.status = .accepted
}
// Voeg meerdere modellen met attributen toe aan de relatie.
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
}
```
Bij het koppelen van een enkel model, kunt u de `method` parameter gebruiken om te kiezen of de relatie wel of niet gecontroleerd moet worden voor het opslaan.

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

@ -0,0 +1,83 @@
# 폴더 구조
이제 첫 번째 Vapor 앱을 만들고 빌드하고 실행했으니, Vapor의 폴더 구조에 익숙해지는 시간을 가져보겠습니다. 이 구조는 SPM의 폴더 구조를 기반으로 하기 때문에, 이전에 [SPM](spm.ko.md)을 사용한 적이 있다면 익숙할 것입니다.
```
.
├── 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` 파일의 존재 여부를 확인하고 해당 파일을 반환합니다.
해당 [https://vapor.codes/favicon.ico](https://vapor.codes/favicon.ico)에 접속하면 Vapor로고 이미지를 확인할 수 있습니다.
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.ko.md) 매니페스트에서 선언된 패키지 모듈을 반영합니다.
### App
이 폴더는 애플리케이션 로직이 모두 들어가는 곳입니다.
#### Controllers
컨트롤러는 애플리케이션 로직을 그룹화하는 좋은 방법입니다. 대부분의 컨트롤러에는 요청을 받아들이고 어떤 형태의 응답을 반환하는 많은 함수가 있습니다.
#### Migrations
마이그레이션 폴더는 Fluent를 사용하는 경우 데이터베이스 마이그레이션을 위한 곳입니다.
#### Models
모델 폴더는 `Content` 구조체나 Fluent `Model`을 저장하기 좋은 장소입니다.
#### configure.swift
이 파일에는 `configure(_:)` 함수가 포함되어 있습니다. 이 메서드는 `entrypoint.swift`에 의해 호출되어 새로 생성된 `Application`을 구성합니다. 여기에서 라우트, 데이터베이스, 프로바이더 등과 같은 서비스를 등록해야 합니다.
#### entrypoint.swift
이 파일에는 Vapor 애플리케이션을 설정, 구성 및 실행하는 `@main` 진입점이 포함되어 있습니다.
#### routes.swift
이 파일에는 `routes(_:)` 함수가 포함되어 있습니다. 이 메서드는 `configure(_:)`의 마지막 부분에서 `Application`에 라우트를 등록하는 데 사용됩니다.
## Tests
`Sources` 폴더의 각 비실행(non-executable) 모듈은 `Tests`에 해당하는 폴더를 가질 수 있습니다. 이 폴더에는 패키지를 테스트하기 위해 `XCTest` 모듈을 기반으로 작성된 코드가 포함됩니다. 테스트는 명령줄에서 `swift test`를 사용하거나 Xcode에서 ⌘+U를 눌러 실행할 수 있습니다.
### AppTests
이 폴더에는 `App` 모듈의 코드를 위한 단위 테스트가 포함되어 있습니다.
## Package.swift
마지막으로 [SPM](spm.ko.md)의 패키지 매니페스트가 있습니다.

View File

@ -0,0 +1,82 @@
# Struktura folderów
Udało Ci się stworzyć, zbudować oraz uruchomić swoją pierwszą aplikacje Vapor, użyjmy tego momentu aby zapoznać Cię z strukturą folderów w projekcie. Struktura jest bazowana na strukturze [SPM](spm.md)a, więc jeśli wcześniej pracowałeś/aś z jego pomocą powinna być dla Ciebie znana.
```
.
├── Public
├── Sources
│ ├── App
│ │ ├── Controllers
│ │ ├── Migrations
│ │ ├── Models
│ │ ├── configure.swift
│ │ ├── entrypoint.swift
│ │ └── routes.swift
├── Tests
│ └── AppTests
└── Package.swift
```
Sekcja poniżej wyjaśnia każdą część struktury folderów w detalach.
## Public
Then folder zawiera wszystkie publiczne pliki, które będą serwowane przez aplikacje jeśli `FileMiddleware` jest włączony. To zazwyczaj są obrazy, arkusze stylów i skrypty przeglądarki. Dla przykładu, zapytanie do `localhost:8080/favicon.ico` będzie sprawdzać czy `Public/favicon.ico` istnieje i zwracać je.
Musisz aktywować `FileMiddleware` w pliku `configure.swift` twojego projektu, zanim Vapor będzie potrafił serwować pliki publiczne.
```swift
// Serwuje pliki folderu `Public/`
let fileMiddleware = FileMiddleware(
publicDirectory: app.directory.publicDirectory
)
app.middleware.use(fileMiddleware)
```
## Sources
Ten folder zawiera wszystkie pliki źródłowe twojego projektu.
Folder o na górze zagnieżdżenia, `App`, odzwierciedla moduł pakietu,
zadeklarowany w manifeście [SwiftPM](spm.md).
### App
To miejsce na cała logikę twojej aplikacji.
#### Controllers
Kontrolery to świetny sposób na grupowania razem logiki aplikacji. Większość kontrolerów posiada wiele funkcji które przyjmują jakąś formę zapytania i zwracają dla niej odpowiedź.
#### Migrations
W tym folderze znajdują się wszystkie migracje bazy danych jeśli używać Fluenta.
#### Models
To świetne miejsce do trzymania `Content` struct lub `Model` z Fluenta.
#### configure.swift
Ten plik zawiera funkcję `configure(_:)`. Metoda ta jest wywoływana przez `entrypoint.swift` w celu skonfigurowania nowo utworzonej `Aplikacji`. W tym miejscu należy zarejestrować usługi, takie jak trasy, bazy danych, dostawców i inne.
#### entrypoint.swift
Ten plik zawiera punkt wejścia `@main` dla aplikacji, która ustawia, konfiguruje i uruchamia aplikację Vapor.
#### routes.swift
Ten plik zawiera funkcję `routes(_:)`. Metoda ta jest wywoływana pod koniec `configure(_:)` w celu zarejestrowania ścieżek czy inaczej końcówek w `Application`.
## Tests
Każdy niewykonywalny moduł w folderze `Sources` może mieć odpowiadający mu folder w `Tests`. Zawiera on kod zbudowany na module `XCTest` do testowania aplikacji. Testy można uruchomić za pomocą `swift test` w wierszu poleceń lub naciskając ⌘+U w Xcode.
### AppTests
Ten folder zawiera testy jednostkowe dla kodu w module `App`.
## Package.swift
Na końcu znajduje się manifest pakietu [SPM](spm.md).

View File

@ -38,7 +38,7 @@ app.middleware.use(fileMiddleware)
## Sources
此文件夹包含项目的所有 Swift 源文件。 顶级文件夹“App”反映了您的包的模块,如 [SwiftPM](spm.md) 清单中声明的那样。
此文件夹包含项目的所有 Swift 源文件。 顶级文件夹 `App` 反映了你的包的模块,如 [SwiftPM](spm.md) 清单中声明的那样。
### App
@ -62,11 +62,11 @@ models 文件夹常用于存放 `Content` 和 Fluent `Model` 的类或结构体
#### entrypoint.swift
该文件包含用于设置、配置和运行 Vapor 应用程序的应用程序的“@main”入口点
该文件包含应用程序的 `@main` 入口点,用于设置、配置和运行 Vapor 应用程序。
#### routes.swift
这个文件包含 `routes(_:)` 方法,它会在 `configure(_:)` 结尾处被调用,用以将路由注册到你的`Application`。
这个文件包含 `routes(_:)` 方法,它会在 `configure(_:)` 结尾处被调用,用以将路由注册到你的 `Application`
## Tests

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

@ -0,0 +1,80 @@
# Hello, world
해당 가이드문서에서는 새로운 Vapor 프로젝트를 만들고, 빌드하고, 서버를 실행하는 단계별 절차를 안내합니다.
아직 Swift 또는 Vapor Toolbox를 설치하지 않았다면 하단에 운영체제에 맞게 설치하기 문서를 확인하세요.
- [Install &rarr; macOS](../install/macos.ko.md)
- [Install &rarr; Linux](../install/linux.ko.md)
## 새 프로젝트 생성하기
첫 번째 단계는 컴퓨터에 새로운 Vapor 프로젝트를 만드는 것입니다. 터미널을 열고 Toolbox의 새 프로젝트 명령을 사용하세요. 이렇게 하면 현재 디렉토리에 새 폴더가 생성되며 프로젝트가 포함됩니다.
```sh
vapor new hello -n
```
!!! 팁
`-n` 플래그를 사용하면 모든 질문에 자동으로 "no"로 대답하여 기본 템플릿을 얻을 수 있습니다.
!!! 팁
Vapor Toolbox 없이도 [템플릿 저장소](https://github.com/vapor/template-bare)를 클론하여 GitHub에서 최신 템플릿을 사용할 수 있습니다.
!!! 팁
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 및 다른 운영 체제(또는 macOS에서 Xcode를 사용하지 않을 경우)에서는 Vim이나 VSCode와 같은 즐겨찾는 편집기에서 프로젝트를 편집할 수 있습니다. 다른 IDE를 설정하는 방법에 대한 최신 정보는 [Swift Server Guides](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

@ -18,6 +18,9 @@ vapor new hello -n
!!! tip
De `-n` vlag geeft je een kaal sjabloon door automatisch nee te antwoorden op alle vragen.
!!! tip
Je kan ook de laatste template van Github halen zonder de Vapor Toolbox te installeren door de [template repository](https://github.com/vapor/template-bare) te clonen.
!!! tip
Vapor en het sjabloon gebruiken nu standaard `async`/`await`. Als je niet kunt updaten naar macOS 12 en/of `EventLoopFuture` wilt blijven gebruiken, gebruik dan de vlag `--branch macos10-15`.
@ -25,7 +28,7 @@ Eens het commando voltooid is, navigeer naar de nieuw aangemaakt map:
```sh
cd hello
```
```
## Bouwen en uitvoeren

View File

@ -0,0 +1,81 @@
# Witaj, świecie
Then poradnik przeprowadzi cię krok po kroku przez tworzenie nowego projektu z użyciem Vapor, budowania go oraz uruchomienie serwera.
Jeśli jeszcze nie masz zainstalowanego Swifta czy Vapor Toolbox, to sprawdź sekcje poniżej.
- [Instalacja &rarr; macOS](../install/macos.md)
- [Instalacja &rarr; Linux](../install/linux.md)
## Nowy projekt
Pierwszym krokiem jest utworzenie nowego projektu Vapor na komputerze. Otwórz terminal i użyj polecenia nowego projektu w Toolbox. Spowoduje to utworzenie nowego folderu w bieżącym katalogu zawierającego projekt.
```sh
vapor new hello -n
```
!!! tip
Flaga `-n` tworzy projekt z użyciem minimalistycznego szablonu, po przez odpowiadanie na wszystkie pytania nie.
!!! tip
Można również pobrać najnowszy szablon z GitHub bez Vapor Toolbox, klonując [repozytorium z szablonami](https://github.com/vapor/template-bare).
!!! tip
Vapor i szablon używają teraz domyślnie `async`/`await`.
Jeśli nie możesz zaktualizować systemu do macOS 12 i/lub chcesz nadal używać `EventLoopFuture`,
użyj flagi `--branch macos10-15`.
Po tym jak działanie komendy zakończy się, wejdź do nowo stworzonego folderu przy użyciu:
```sh
cd hello
```
## Zbuduj i uruchom
### Xcode
Najpierw, otwórz projekt w XCode.
```sh
open Package.swift
```
Automatycznie rozpocznie pobieranie zależności Menedżera pakietów Swift. Może to zająć trochę czasu przy pierwszym otwarciu projektu. Po zakończeniu rozpoznawania zależności Xcode wypełni dostępne schematy.
W górnej części okna, po prawej stronie przycisków Play i Stop, kliknij nazwę projektu, aby wybrać schemat projektu i wybierz odpowiedni cel uruchamiania - najprawdopodobniej "My Mac". Kliknij przycisk odtwarzania, aby utworzyć i uruchomić projekt.
W oknie terminala Xcode powinna pojawić się konsola.
```sh
[ INFO ] Server starting on http://127.0.0.1:8080
```
### Linux
W systemie Linux i innych systemach operacyjnych (a nawet w systemie macOS, jeśli nie chcesz używać Xcode) możesz edytować projekt w swoim ulubionym edytorze, takim jak Vim lub VSCode. Aktualne informacje na temat konfiguracji innych IDE można znaleźć w [Swift Server Guides](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md).
Aby zbudować i uruchomić projekt, w Terminalu uruchom:
```sh
swift run
```
Spowoduje to zbudowanie i uruchomienie projektu. Przy pierwszym uruchomieniu pobieranie i rozwiązywanie zależności zajmie trochę czasu. Po uruchomieniu powinieneś zobaczyć następujące informacje w konsoli:
```sh
[ INFO ] Server starting on http://127.0.0.1:8080
```
## Odwiedź localhost
Otwórz swoją przeglądarkę, a następnie adres: <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> lub <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a>
Powinieneś widzieć następująca stronę.
```html
Hello, world!
```
Gratulujemy stworzenia, zbudowania i uruchomienia twojej pierwszej aplikacji z użyciem Vapora! 🎉🎉

View File

@ -17,7 +17,7 @@ vapor new hello -n
```
!!! tip "建议"
使用 `-n` 为所有的问题自动选择 no 来为提供一个基本的模板。
使用 `-n` 为所有的问题自动选择 no 来为提供一个基本的模板。
!!! tip "建议"
你也可以不使用 Vapor Toolbox直接从 GitHub 克隆[模板库](https://github.com/vapor/template-bare)来获取最新的模板。

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

@ -0,0 +1,95 @@
# Swift Package Manager
[Swift Package Manager](https://swift.org/package-manager/) (SPM)은 프로젝트의 소스 코드 및 종속성을 빌드하는 데 사용됩니다. Vapor는 SPM을 강력하게 활용하므로 SPM의 기본 원리를 이해하는 것이 좋습니다.
SPM은 Cocoapods, Ruby Gems 및 NPM과 유사합니다. `swift build``swift test`와 같은 명령어로 command line(커맨드라인)에서 SPM을 사용할 수 있으며, 호환되는 IDE에서도 사용할 수 있습니다. 그러나 다른 일부 패키지 관리자와는 달리 SPM은 중앙 패키지 인덱스가 없습니다. 대신 SPM은 Git 저장소의 URL을 활용하며, [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging)를 사용하여 버전 의존성을 관리합니다.
## Package Manifest
SPM이 프로젝트에서 먼저 찾는 곳은 Package Manifest입니다. 이는 프로젝트의 루트 디렉토리에 있어야 하며 `Package.swift`로 이름이 지정되어야 합니다.
다음은 Package Manifest의 예입니다.
```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"),
])
]
)
```
각 부분에 대한 Package Manifest의 설명은 다음 섹션에서 설명됩니다.
### Tools Version
패키지 매니페스트의 맨 첫 줄은 필요한 Swift tool 버전을 나타냅니다. 이는 패키지가 지원하는 Swift의 최소 버전을 지정합니다. 패키지 설명 API도 Swift 버전에 따라 변경될 수 있으므로, 이 줄은 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 앱은 두 개의 타겟을 가지지만, 코드를 구성하기 위해 필요에 따라 원하는 만큼 추가할 수 있습니다. 각 타겟은 어떤 모듈에 의존하는지를 선언해야 합니다. 코드에서 모듈을 가져오려면 여기에 모듈 이름을 추가해야 합니다. 타겟은 프로젝트 내의 다른 타겟이나 추가한 패키지에서 노출된 모듈에 의존할 수 있습니다.
타겟은 패키지에 포함된 모든 모듈, 실행 파일 및 테스트입니다. 대부분의 Vapor 앱은 두 개의 타겟을 가지지만, 코드를 구성하기 위해 필요에 따라 원하는 만큼 추가할 수 있습니다. 각 타겟은 어떤 모듈에 의존하는지를 선언해야 합니다. 모듈을 코드에서 가져오려면 여기에 모듈 이름을 추가해야 합니다. 타겟은 프로젝트 내의 다른 타겟이나 [main dependencies](#dependencies)배열에 추가한 패키지의 모듈에 의존할 수 있습니다.
## Folder Structure
아래는 SPM 패키지의 전형적인 폴더 구조입니다.
```
.
├── Sources
│ └── App
│ └── (Source code)
├── Tests
│ └── AppTests
└── Package.swift
```
`.target` 또는 `.executableTarget``Sources` 폴더의 하위 폴더와 대응합니다.
`.testTarget``Tests` 폴더의 하위 폴더와 대응합니다.
## Package.resolved
프로젝트를 처음 빌드할 때 SPM은 각 종속성의 버전을 저장하는 `Package.resolved` 파일을 생성합니다. 프로젝트를 다음으로 빌드할 때에도 새로운 버전이 있더라도 동일한 버전이 사용됩니다.
종속성을 업데이트하려면 `swift package update` 명령을 실행하세요.
## Xcode
Xcode 11 이상을 사용하는 경우 `Package.swift` 파일이 수정될 때마다 종속성, 타겟, products 등의 변경이 자동으로 반영됩니다.
최신 종속성으로 업데이트하려면 File &rarr; Swift Packages &rarr; Update To Latest Swift Package Versions을 사용하세요.
또한 `.swiftpm` 파일을 `.gitignore`에 추가하는 것이 좋습니다. 이곳에는 Xcode가 Xcode 프로젝트 구성을 저장합니다.

View File

@ -0,0 +1,95 @@
# Swift Package Manager
[Swift Package Manager](https://swift.org/package-manager/) (SPM) jest używany do budowania kodu źródłowego twojego projektu i zależności. Vapor bardzo mocno polega na SPM, więc dobrym pomysłem jest zrozumieć podstawy tego jak działa.
SPM jest podobny do Cocoapods, Ruby gems albo NPM. Można również używać SPM z poziomu wiersza poleceń na przykład `swift build` lub `swift test` lub z kompatybilnymi IDE. Natomiast, coś co wyróżnia go od innych managerów zależności, nie ma on centralnego indexu pakietów (central package index). Zamiast tego SPM wykorzystuje adresy URL do repozytoriów Git i zależności wersji za pomocą [Git tags](https://git-scm.com/book/en/v2/Git-Basics-Tagging).
## Manifest pakietu
Pierwszym miejscem, do którego SPM zagląda w projekcie jest manifest pakietu. Powinien on zawsze znajdować się w katalogu głównym projektu i nosić nazwę `Package.swift`.
Spójrz na ten przykładowy manifest pakietu.
```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"),
])
]
)
```
Każda część manifestu jest wyjaśniona w poniższych sekcjach.
### Tools Version
Pierwszy wiersz manifestu pakietu wskazuje wymaganą wersję narzędzi Swift. Określa ona minimalną wersję języka Swift obsługiwaną przez pakiet. Interfejs API opisu pakietu może również zmieniać się między wersjami Swift, więc ten wiersz zapewnia, że Swift będzie wiedział, jak przeanalizować manifest.
### Package Name
Pierwszym argumentem do `Package` jest nazwa pakietu. Jeśli pakiet jest publiczny, jako nazwy należy użyć ostatniego segmentu adresu URL repozytorium Git.
### Platforms
Tablica `platforms` określa, które platformy obsługuje ten pakiet. Określając `.macOS(.v12)` pakiet wymaga systemu macOS 12 lub nowszego. Gdy Xcode załaduje ten projekt, automatycznie ustawi minimalną wersję wdrożenia na macOS 12, aby można było korzystać ze wszystkich dostępnych interfejsów API.
### Dependencies
Zależności (z ang. dependencies) to inne pakiety SPM, na których opiera się pakiet. Wszystkie aplikacje Vapor opierają się na pakiecie Vapor, ale można dodać dowolną liczbę innych zależności.
W powyższym przykładzie widać, że pakiet [vapor/vapor](https://github.com/vapor/vapor) w wersji 4.76.0 lub nowszej jest zależny od tego pakietu. Po dodaniu zależności do pakietu, musisz następnie zasygnalizować, które [targets](#targets) zależą od
nowo dostępnych modułów.
### Targets
Cele (z ang. targets) to wszystkie moduły, pliki wykonywalne i testy, które zawiera pakiet. Większość aplikacji Vapor będzie miała dwa obiekty docelowe, chociaż możesz dodać tyle, ile chcesz, aby uporządkować swój kod. Każdy cel deklaruje, od których modułów zależy. Musisz dodać nazwy modułów w tym miejscu, aby zaimportować je w swoim kodzie. Cel może zależeć od innych celów w projekcie lub dowolnych modułów udostępnionych przez pakiety dodane do
tablicy [głównych zależności](#dependencies).
## Folder Structure
Poniżej znajduje się typowa struktura folderów dla pakietu SPM.
```fish
.
├── Sources
│ └── App
│ └── (Kod źródłowy)
├── Tests
│ └── AppTests
└── Package.swift
```
Każdy `.target` lub `.executableTarget` odpowiada folderowi w folderze `Sources`.
Każdy `.testTarget` odpowiada folderowi w folderze `Tests`.
## Package.resolved
Przy pierwszej kompilacji projektu, SPM utworzy plik `Package.resolved`, który przechowuje wersję każdej zależności. Przy następnej kompilacji projektu te same wersje zostaną użyte, nawet jeśli dostępne są nowsze wersje.
Aby zaktualizować zależności, uruchom `swift package update`.
## Xcode
Jeśli korzystasz z Xcode 11 lub nowszego, zmiany w zależnościach, celach, produktach itp. będą wprowadzane automatycznie za każdym razem, gdy plik `Package.swift` zostanie zmodyfikowany.
Jeśli chcesz zaktualizować do najnowszych zależności, użyj File &rarr; Swift Packages &rarr; Update To Latest Swift Package Versions.
Możesz również dodać plik `.swiftpm` do pliku `.gitignore`. Jest to miejsce, w którym Xcode będzie przechowywać konfigurację projektu Xcode.

View File

@ -49,13 +49,13 @@ let package = Package(
### Platforms
`platforms` 数组指定此程序包支持的平台和版本。通过指定 `.macOS.v12`,说明此软件包需要 macOS 12 或更高版本。 Xcode 加载该项目时,它将最低部署版本设置为 macOS 12以便你可以使用所有可用的 API。
`platforms` 数组指定此程序包支持的平台和版本。通过指定 `.macOS.v12)`,说明此软件包需要 macOS 12 或更高版本。 Xcode 加载该项目时,它将最低部署版本设置为 macOS 12以便你可以使用所有可用的 API。
### Dependencies
dependencies 字段代表项目需要依赖的 package。所有 Vapor 应用都依赖 Vapor package ,但是你也可以添加其它想要的依赖库。
如上面这个示例,[vapor/vapor](https://github.com/vapor/vapor) 4.0 或以上版本是这个 package 的 dependency。当在 package 中添加了 dependency 后,接下来你必须设置是哪个 targets 依赖了新的可用模块。
如上面这个示例,[vapor/vapor](https://github.com/vapor/vapor) 4.76.0 或以上版本是这个 package 的 dependency。当在 package 中添加了 dependency 后,接下来你必须设置是哪个 targets 依赖了新的可用模块。
### Targets

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

@ -0,0 +1,44 @@
# Xcode
이 페이지에서는 Xcode를 사용하는 데 도움이 되는 팁에 대해 알려드립니다. 다른 개발 환경을 사용하는 경우 이 부분을 건너뛰어도 됩니다.
## Custom Working Directory
기본적으로 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 scheme에서 custom working directory를 설정하세요.
먼저, 재생 및 중지 버튼 옆에 있는 scheme selector를 클릭하여 프로젝트의 scheme을 편집하세요.
![Xcode Scheme Area](../images/xcode-scheme-area.png)
Edit Scheme을 선택합니다.
![Xcode Scheme Menu](../images/xcode-scheme-menu.png)
scheme editor에서 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

@ -0,0 +1,44 @@
# Xcode
Ta strona zawiera wskazówki i porady dotyczące korzystania z Xcode. Jeśli korzystasz z innego środowiska programistycznego, możesz ją pominąć.
## Niestandardowy katalog roboczy
Domyślnie Xcode uruchamia projekt z folderu _DerivedData_. Folder ten nie jest taki sam jak folder główny projektu (gdzie znajduje się plik _Package.swift_). Oznacza to, że Vapor nie będzie w stanie znaleźć plików i folderów takich jak _.env_ lub _Public_.
Można to stwierdzić, jeśli podczas uruchamiania aplikacji pojawi się następujące ostrzeżenie.
```fish
[ WARNING ] No custom working directory set for this scheme, using /path/to/DerivedData/project-abcdef/Build/
```
Aby to naprawić, ustaw niestandardowy katalog roboczy w schemacie Xcode dla swojego projektu.
Najpierw edytuj schemat projektu, klikając selektor schematu przy przyciskach odtwarzania i zatrzymania.
![Xcode Scheme Area](../images/xcode-scheme-area.png)
Wybierz _Edit Scheme..._ z listy rozwijanej.
![Xcode Scheme Menu](../images/xcode-scheme-menu.png)
W edytorze schematów wybierz akcję _App_ i zakładkę _Options_. Zaznacz opcję _Use custom working directory_ i wprowadź ścieżkę do folderu głównego projektu.
![Xcode Scheme Options](../images/xcode-scheme-options.png)
Możesz uzyskać pełną ścieżkę do katalogu głównego projektu, uruchamiając `pwd` z otwartego tam okna terminala.
```sh
# Sprawdź, czy jesteśmy w folderze projektu vapor
vapor --version
# Uzyskaj ścieżkę do tego folderu
pwd
```
Powinieneś zobaczyć dane wyjściowe podobne do poniższych.
```
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 コミュニティの数百人のメンバー。

28
docs/index.ko.md Normal file
View File

@ -0,0 +1,28 @@
Vapor 문서에 오신 걸 환영합니다! Vapor는 Swift를 사용하여 백엔드, 웹 앱 API 및 HTTP 서버를 작성할 수 있는 웹 프레임워크입니다. Vapor은 현대적이고 강력하며 안전한 언어인 Swift로 작성되어 기존의 서버 언어에 비해 여러 가지 이점을 제공합니다.
## 시작하기
Vapor를 처음 사용하시는 경우, [설치 → macOS](install/macos.ko.md) 로 이동하여 Swift와 Vapor를 설치하세요.
Vapor를 설치한 후에는, [시작하기 → Hello, world](getting-started/hello-world.ko.md)를 확인하여 첫 번째 Vapor 앱을 생성해보세요!
## 추가 정보
다른 Vapor에 대한 정보를 찾을 수 있는 좋은 자료들이 몇 군데 있습니다. 다음은 몇 가지 예시입니다.
| 이름 | 설명 | 링크 |
|----------------|--------------------------------------------------|-------------------------------------------------------------------|
| Vapor 디스코드 | 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에서 버그를 보고하거나 기능을 요청하세요. | [visit &rarr;](https://github.com/vapor/vapor/issues) |
## 이전 버전 문서
더 이상 지원되지 않는 Vapor의 이전 버전에 대한 문서는 [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/)에서 확인할 수 있습니다.
## 작성자
Vapor Core 팀 및 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) で初めてのアプリを作成してください。

62
docs/install/linux.ko.md Normal file
View File

@ -0,0 +1,62 @@
# 리눅스에 Vapor 설치하기
Vapor를 사용하려면 Swift 5.6 이상이 필요합니다. 리눅스에 Swift를 설치하려면, [Swift.org](https://swift.org/download/)에서 제공하는 툴체인을 사용하여 설치할 수 있습니다.
## 지원되는 배포판 및 버전
Vapor는 Swift 5.6 이상을 지원하는 Linux 배포판의 동일한 버전을 지원합니다.
!!! 노트
아래에 나열된 지원되는 버전은 언제든지 최신 정보가 아닐 수 있습니다. 공식적으로 지원되는 운영 체제는 [Swift Releases](https://swift.org/download/#releases) 페이지에서 확인할 수 있습니다.
|배포판|버전|Swift 버전|
|-|-|-|
|Ubuntu|20.04|>= 5.6|
|Fedora|>= 30|>= 5.6|
|CentOS|8|>= 5.6|
|Amazon Linux|2|>= 5.6|
공식적으로 지원되지 않는 Linux 배포판은 소스 코드를 컴파일하여 Swift를 실행할 수 있지만, Vapor는 안정성을 보장할 수 없습니다. Swift의 컴파일 방법에 대해서는 [Swift 저장소](https://github.com/apple/swift#getting-started)에서 자세히 알아보세요.
## Swift 설치하기
Swift를 Linux에 설치하는 방법은 Swift.org의 [다운로드 및 사용하기](https://swift.org/download/#using-downloads) 가이드를 참조하세요.
### Fedora
Fedora 사용자는 다음 명령어를 사용하여 Swift를 설치할 수 있습니다.
```sh
sudo dnf install swift-lang
```
Fedora 30을 사용하는 경우, Swift 5.6 이상 버전을 얻기 위해 EPEL 8을 추가해야 합니다.
## Docker
Swift의 공식 Docker 이미지를 사용하여 미리 컴파일된 컴파일러를 사용할 수도 있습니다. [Swift's Docker Hub](https://hub.docker.com/_/swift)에서 더 자세한 내용을 알아보세요.
## Toolbox 설치하기
이제 Swift가 설치되었으므로 [Vapor Toolbox](https://github.com/vapor/toolbox)를 설치해봅시다. 이 CLI 도구는 Vapor를 사용하는 데 필수적이지는 않지만, 유용한 유틸리티를 제공합니다.
Linux에서는 Toolbox를 소스 코드로부터 빌드해야 합니다. GitHub에서 toolbox의 <a href="https://github.com/vapor/toolbox/releases" target="_blank">releases</a>를 확인하여 최신 버전을 찾아보세요.
```sh
git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install
```
Toolbox 설치가 성공적으로 이루어졌는지 확인하기 위해 도움말을 출력해보세요.
```sh
vapor --help
```
사용 가능한 명령어 목록이 표시되어야 합니다.
## 다음 단계
Swift를 설치한 후 [시작하기 &rarr; Hello, world](../getting-started/hello-world.ko.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

62
docs/install/linux.pl.md Normal file
View File

@ -0,0 +1,62 @@
# Zainstaluj na Linux
Aby używać Vapor, będziesz potrzebować Swifta w wersji 5.6 lub wyższej. Możesz go zainstalować używając jednego z plików instalacyjnych na [Swift.org](https://swift.org/download/).
## Wspierane dystrybucje i wersje
Vapor wspiera te same wersje dystrybucji Linuxa jak wersja 5.6 lub nowsza Swifta.
!!! note
Wspierane wersje wypisane poniżej mogą być przeterminowane w momencie gdy to czytasz. Możesz zobaczyć które systemy operacyjne czy dystrybucje są wpierane na stronie [Swift Releases](https://swift.org/download/#releases).
|Dystrybucja|Wersja|Wersja Swift|
|-|-|-|
|Ubuntu|20.04|>= 5.6|
|Fedora|>= 30|>= 5.6|
|CentOS|8|>= 5.6|
|Amazon Linux|2|>= 5.6|
Dystrybucje Linuxa które nie są oficjalnie wspierane mogą również użyć Swifta po przez kompilacje kodu źródłowego, lecz Vapor nie daje gwarancji stabilności. Dowiedz się więcej o kompilacji Swifta z oficjalnego repozytorium [Swift repo](https://github.com/apple/swift#getting-started).
## Instalacja Swifta
Wejdź na Swift.org i użyj instrukcji pod adresem [Using Downloads](https://swift.org/download/#using-downloads) aby zainstalować Swifta na Linux.
### Fedora
Użytkownicy Fedory mogę po prostu użyć następującej komendy aby zainstalować Swifta:
```sh
sudo dnf install swift-lang
```
Jeśli używasz Fedora 30, będziesz musiał dodać EPEL 8, aby używać Swifta 5.6 lub nowszego.
## Docker
Możesz również użyć oficjalnego obrazu Docker Swifta, który ma już preinstalowany kompilator. Dowiedz się więcej na [Swift's Docker Hub](https://hub.docker.com/_/swift).
## Zainstaluj Toolbox
Teraz gdy masz już zainstalowanego Swifta, zainstalujmy [Vapor Toolbox](https://github.com/vapor/toolbox). Jest to narzędzie CLI (z ang. Command Line Interface), które nie jest potrzebne by używać Vapora, natomiast jest wyposażone w przydatne usprawnienia takie jak kreator nowego projektu.
Na Linux, musisz zbudować toolbox z źródła. Odwiedź [wydania](https://github.com/vapor/toolbox/releases) toolboxu na Github aby znaleźć najnowsza wersję.
```sh
git clone https://github.com/vapor/toolbox.git
cd toolbox
git checkout <desired version>
make install
```
Sprawdź dwa razy czy instalacja przeszła poprawnie po przez wyświetlenie pomocy.
```sh
vapor --help
```
Powinna być widoczna lista dostępnych komend.
## Następnie
Kiedy już udało Ci się zainstalować Swifta, stwórz swoja pierwszą aplikacje w sekcji [Pierwsze kroki &rarr; Witaj, świecie](../getting-started/hello-world.md).

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) で初めてのアプリを作成してください。

48
docs/install/macos.ko.md Normal file
View File

@ -0,0 +1,48 @@
# macOS에 Vapor 설치하기
macOS에서 Vapor를 사용하려면 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가 다운로드되면 설치를 완료하기 위해 Xcode를 열어야 합니다. 이 작업은 시간이 걸릴 수 있습니다.
터미널을 열고 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
```
Toolbox 설치가 성공적으로 이루어졌는지 확인하기 위해 도움말을 출력해보세요.
```sh
vapor --help
```
사용 가능한 명령어 목록이 표시되어야 합니다.
## 다음 단계
이제 Swift와 Vapor Toolbox를 설치했으므로, [시작하기 &rarr; Hello, world](../getting-started/hello-world.ko.md)에서 첫 번째 앱을 생성해보세요.

48
docs/install/macos.pl.md Normal file
View File

@ -0,0 +1,48 @@
# Zainstaluj na macOS
Aby używać Vapor na macOS, potrzebujesz Swift w wersji 5.6 lub wyższej. Swift oraz wszystkie jego zależności są częścią instalacji Xcode.
## Zainstaluj Xcode
Zainstaluj [Xcode](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) z Mac App Store.
![Xcode w Mac App Store](../images/xcode-mac-app-store.png)
Po tym jak Xcode będzie już pobrany, musi go otworzyć aby skończyć instalacje. To może chwilę zająć.
Dwa razy sprawdź aby upewnić się że instalacja była sukcesem otwierając Terminal i wyświetlając wersję Swifta.
```sh
swift --version
```
Powinna wyświetlić się wersja Swifta.
```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 wymaga wersji Swifta 5.6 lub wyższej.
## Zainstaluj Toolbox
Teraz gdy masz już zainstalowanego Swifta, zainstalujmy [Vapor Toolbox](https://github.com/vapor/toolbox). Jest to narzędzie CLI (z ang. Command Line Interface), które nie jest potrzebne by używać Vapora, natomiast jest wyposażone w przydatne usprawnienia takie jak kreator nowego projektu.
Toolbox jest wydawany przy pomocy Homebrew. Jeśli jeszcze nie masz Homebrew, to zajrzyj na [brew.sh](https://brew.sh) po instrukcje jak zainstalować.
```sh
brew install vapor
```
Sprawdź dwa razy czy instalacja przeszła poprawnie po przez wyświetlenie pomocy.
```sh
vapor --help
```
Powinna być widoczna lista dostępnych komend.
## Następnie
Kiedy już udało Ci się zainstalować Swifta, stwórz swoja pierwszą aplikacje w sekcji [Pierwsze kroki &rarr; Witaj, świecie](../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!

View File

@ -73,6 +73,9 @@ Now that Leaf is configured, let's render your first template. Inside of the `Re
Hello, #(name)!
```
!!! tip
If you're using VSCode as your code editor, we recommend installing the Leaf extension to enable syntax highlighting: [Leaf HTML](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf).
Then, register a route (usually done in `routes.swift` or a controller) to render the view.
```swift

View File

@ -74,6 +74,9 @@ Nu Leaf is geconfigureerd, laten we je eerste template renderen. Maak in de map
Hello, #(name)!
```
!!! tip
Als je VSCode als code editor gebruikt, raden we aan de Leaf extensie te installeren om syntax highlighting mogelijk te maken: [Leaf HTML](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf).
Registreer dan een route (meestal gedaan in `routes.swift` of een controller) om de view te renderen.
```swift

View File

@ -74,6 +74,9 @@ VaporApp
Hello, #(name)!
```
!!! tip "建议"
如果你正在使用 VSCode 作为代码编辑器,我们推荐安装 [Leaf HTML](https://marketplace.visualstudio.com/items?itemName=Francisco.html-leaf) 扩展,以启用语法高亮功能。
然后,注册一个路由(通常在 `routes.swift` 中或一个控制器中完成注册)来渲染视图。
```swift

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

View File

@ -17,7 +17,7 @@ Leaf tags bestaan uit vier elementen::
- Token `#`: Dit geeft de leaf parser het signaal om te beginnen zoeken naar een tag.
- Naam `count`: die de tag identificeert.
- Parameter Lijst `(users)`: Kan nul of meer argumenten aanvaarden.
- Body: Een optionele body kan aan sommige tags worden toegevoegd met behulp van een puntkomma en een afsluitende tag
- Body: Een optionele body kan aan sommige tags worden toegevoegd met behulp van een dubbelpunt en een afsluitende tag
Er kunnen veel verschillende toepassingen zijn voor deze vier elementen, afhankelijk van de implementatie van de tag. Laten we eens kijken naar een paar voorbeelden van hoe de ingebouwde tags van Leaf gebruikt kunnen worden:

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` 来处理它们各自的生命周期事件。

Some files were not shown because too many files have changed in this diff Show More