mirror of https://github.com/vapor/docs.git
Merge branch 'main' into feature/japanese-basic
This commit is contained in:
commit
1be790883e
|
|
@ -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:
|
||||
- "*"
|
||||
|
|
@ -11,7 +11,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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,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
|
||||
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
swift setUpRedirects.swift
|
||||
- name: Configure AWS credentials
|
||||
id: cred
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.DOCS_DEPLOYER_AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.DOCS_DEPLOYER_AWS_SECRET_ACCESS_KEY }}
|
||||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
- name: Deploy to AWS Cloudformation
|
||||
id: clouddeploy
|
||||
if: steps.cred.outcome == 'success'
|
||||
uses: aws-actions/aws-cloudformation-github-deploy@v1.0.3
|
||||
uses: aws-actions/aws-cloudformation-github-deploy@v1.2.0
|
||||
with:
|
||||
name: vapor-docs-stack
|
||||
template: stack.yml
|
||||
|
|
|
|||
|
|
@ -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@main
|
||||
secrets: inherit
|
||||
|
|
@ -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.
|
||||
|
|
@ -71,5 +71,9 @@ You can check it out by running `mkdocs serve` in the terminal. Once you are sat
|
|||
|
||||
Finally, you should add the new language to the [issue template](https://github.com/vapor/docs/blob/main/.github/translation_needed.description.leaf) to ensure that any future changes are applied to the new translation and to the [search index script](https://github.com/vapor/docs/blob/main/fixSearchIndex.swift) to ensure search works correctly.
|
||||
|
||||
## Licensing
|
||||
|
||||
|
||||
<p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/">
|
||||
Except where otherwise noted, <a property="dct:title" rel="cc:attributionURL" href="https://github.com/vapor/docs">Vapor Documentation</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://vapor.codes">Vapor</a> is licensed under <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/" rel="license noopener noreferrer">CC BY-NC-SA 4.0 <img style="height: 16px;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg"> <img style="height: 16px" src="https://mirrors.creativecommons.org/presskit/icons/by.svg"> <img style="height: 16px" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg"> <img style="height: 16px;" src="https://mirrors.creativecommons.org/presskit/icons/sa.svg"></a>
|
||||
</p>
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
||||
|
|
@ -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())
|
||||
```
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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** 请求来重定向。
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
### 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).
|
||||
|
|
@ -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 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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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 l’une 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
|
||||
```
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 → macOS](../install/macos.md)
|
||||
- [Installazione → 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! 🎉
|
||||
|
|
|
|||
|
|
@ -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 → Swift Packages → Update to Latest Package Versions.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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 →](https://github.com/vapor/vapor) |
|
||||
| Issues di GitHub | Segnala bug o richiedi funzionalità su GitHub. | [visita →](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/).
|
||||
|
|
|
|||
|
|
@ -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 → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
Dopo aver installato Vapor, puoi iniziare a creare il tuo primo progetto usando [Inizio → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
|
|
|
|||
|
|
@ -1,26 +1,38 @@
|
|||
# Linux にインストール
|
||||
|
||||
Vapor を使用するには、Swift 5.6 以上が必要です。これは、[Swift.org](https://swift.org/download/)で利用可能なツールチェーンを使用してインストールできます。
|
||||
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/)を参照してください。
|
||||
|
||||
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 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
|
||||
|
|
@ -31,7 +43,7 @@ Fedora ユーザーは、以下のコマンドを使用して Swift を簡単に
|
|||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
Fedora 30 を使用している場合、Swift 5.6 またはそれ以降のバージョンを取得するには、EPEL8 を追加する必要があります。
|
||||
Fedora 35 を使用している場合、Swift 5.6 またはそれ以降のバージョンを取得するには、EPEL8 を追加する必要があります。
|
||||
|
||||
## Docker
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||

|
||||
|
||||
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 → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
Dopo aver installato Vapor, puoi iniziare a creare il tuo primo progetto usando [Inizio → Ciao, mondo](../getting-started/hello-world.it.md).
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
```
|
||||
|
|
@ -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"])
|
||||
```
|
||||
|
|
@ -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!
|
||||
|
|
@ -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!
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
```
|
||||
|
|
@ -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)")
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Redis y Sesiones
|
||||
|
||||
Redis puede actuar como un proveedor de almacenamiento para el caché de [datos de sesión](../advanced/sessions.md#session-data) como las credenciales del usuario.
|
||||
|
||||
Si no se proporciona un [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate) personalizado, se usará uno predeterminado.
|
||||
|
||||
## Comportamiento Predeterminado
|
||||
|
||||
### Creación de SessionID
|
||||
|
||||
A menos que implementes el método [`makeNewID()`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makenewid()-3hyne) en [tu propio RedisSessionsDelegate](#redissessionsdelegate), todos los valores de [`SessionID`](https://api.vapor.codes/vapor/documentation/vapor/sessionid) se crearán haciendo lo siguiente:
|
||||
|
||||
1. Generar 32 bytes de caracteres aleatorios.
|
||||
2. Codificar el valor en base64.
|
||||
|
||||
Por ejemplo: `Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
|
||||
|
||||
### Almacenamiento de SessionData
|
||||
|
||||
La implementación predeterminada de `RedisSessionsDelegate` almacenará [`SessionData`](https://api.vapor.codes/vapor/documentation/vapor/sessiondata) como un simple valor de cadena JSON usando `Codable`.
|
||||
|
||||
A menos que implementes el método [`makeRedisKey(for:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makerediskey(for:)-5nfge) en tu propio `RedisSessionsDelegate`, `SessionData` se almacenará en Redis con una clave que antepone el `SessionID` con `vrs-` (**V**apor **R**edis **S**essions).
|
||||
|
||||
Por ejemplo: `vrs-Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
|
||||
|
||||
## Registrando un Delegado Personalizado
|
||||
|
||||
Para personalizar cómo se leen y se escriben los datos en Redis, registra tu propio objeto `RedisSessionsDelegate` de la siguiente manera:
|
||||
|
||||
```swift
|
||||
import Redis
|
||||
|
||||
struct CustomRedisSessionsDelegate: RedisSessionsDelegate {
|
||||
// implementación
|
||||
}
|
||||
|
||||
app.sessions.use(.redis(delegate: CustomRedisSessionsDelegate()))
|
||||
```
|
||||
|
||||
## RedisSessionsDelegate
|
||||
|
||||
> Documentación de la API: [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate)
|
||||
|
||||
Un objeto que cumple con este protocolo puede usarse para cambiar cómo `SessionData` se almacena en Redis.
|
||||
|
||||
Solo dos métodos requieren de implementación en un tipo que cumpla con el protocolo: [`redis(_:store:with:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:store:with:)) y [`redis(_:fetchDataFor:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:fetchdatafor:)).
|
||||
|
||||
Ambos son obligatorios, ya que la forma en que personalizas la escritura de los datos de sesión en Redis está intrínsecamente vinculada a cómo se lee de Redis.
|
||||
|
||||
### Ejemplo de RedisSessionsDelegate Hash
|
||||
|
||||
Por ejemplo, si quisieras almacenar los datos de sesión como un [**Hash** en Redis](https://redis.io/topics/data-types-intro#redis-hashes), podrías implementar algo como lo siguiente:
|
||||
|
||||
```swift
|
||||
func redis<Client: RedisClient>(
|
||||
_ client: Client,
|
||||
store data: SessionData,
|
||||
with key: RedisKey
|
||||
) -> EventLoopFuture<Void> {
|
||||
// almacena cada campo de datos como un campo hash separado
|
||||
return client.hmset(data.snapshot, in: key)
|
||||
}
|
||||
func redis<Client: RedisClient>(
|
||||
_ client: Client,
|
||||
fetchDataFor key: RedisKey
|
||||
) -> EventLoopFuture<SessionData?> {
|
||||
return client
|
||||
.hgetall(from: key)
|
||||
.map { hash in
|
||||
// hash es [String: RESPValue], por lo que necesitamos intentar desempaquetar el
|
||||
// valor como una cadena y almacenar cada valor en el contenedor de datos
|
||||
return hash.reduce(into: SessionData()) { result, next in
|
||||
guard let value = next.value.string else { return }
|
||||
result[next.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Redis & Sessioni
|
||||
|
||||
Redis può fungere da provider di archiviazione per il caching dei [dati di sessione](../advanced/sessions.md#session-data) come le credenziali degli utenti.
|
||||
|
||||
Se non viene fornito un [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate) personalizzato, sarà utilizzato quello di default.
|
||||
|
||||
## Comportamento di Default
|
||||
|
||||
### Creazione di SessionID
|
||||
|
||||
A meno che non implementi il metodo [`makeNewID()`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makenewid()-3hyne) nel [tuo `RedisSessionsDelegate` personale](#redissessionsdelegate), tutti i valori [`SessionID`](https://api.vapor.codes/vapor/documentation/vapor/sessionid) saranno creati facendo quanto segue:
|
||||
|
||||
1. Generare 32 byte di caratteri casuali
|
||||
1. Codificare il valore in base64
|
||||
|
||||
Per esempio: `Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
|
||||
|
||||
### Archiviazione di SessionData
|
||||
|
||||
L'implementazione di default di `RedisSessionsDelegate` salverà [`SessionData`](https://api.vapor.codes/vapor/documentation/vapor/sessiondata) come una semplice stringa JSON usando `Codable`.
|
||||
|
||||
A meno che non implementi il metodo [`makeRedisKey(for:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/makerediskey(for:)-5nfge) nel tuo `RedisSessionsDelegate` personale, `SessionData` sarà salvato in Redis con una chiave che precede il `SessionID` con `vrs-` (**V**apor **R**edis **S**essions)
|
||||
|
||||
Per esempio: `vrs-Hbxozx8rTj+XXGWAzOhh1npZFXaGLpTWpWCaXuo44xQ=`
|
||||
|
||||
## Registrare un Delegato Modificato
|
||||
|
||||
Per modificare il modo in cui i dati vengono letti e scritti su Redis, registra il tuo oggetto `RedisSessionsDelegate` come segue:
|
||||
|
||||
```swift
|
||||
import Redis
|
||||
|
||||
struct CustomRedisSessionsDelegate: RedisSessionsDelegate {
|
||||
// implementazione
|
||||
}
|
||||
|
||||
app.sessions.use(.redis(delegate: CustomRedisSessionsDelegate()))
|
||||
```
|
||||
|
||||
## RedisSessionsDelegate
|
||||
|
||||
> Documentazione dell'API: [`RedisSessionsDelegate`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate)
|
||||
|
||||
Un oggetto che è conforme a questo protocollo può essere usato per cambiare come `SessionData` è salvato in Redis.
|
||||
|
||||
Viene richiesto di implementare solo due metodi a un tipo conforme al protocollo: [`redis(_:store:with:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:store:with:)) e [`redis(_:fetchDataFor:)`](https://api.vapor.codes/redis/documentation/redis/redissessionsdelegate/redis(_:fetchdatafor:)).
|
||||
|
||||
Entrambi sono necessari, in quanto il modo in cui tu personalizzi la scrittura dei dati di sessione su Redis è intrinsecamente legato a come deve essere letto da Redis.
|
||||
|
||||
### Esempio di Hash di RedisSessionsDelegate
|
||||
|
||||
Per esempio, se vuoi salvare i dati di sessione come un [**Hash** in Redis](https://redis.io/topics/data-types-intro#redis-hashes), dovresti implementare qualcosa simile a quanto segue:
|
||||
|
||||
```swift
|
||||
func redis<Client: RedisClient>(
|
||||
_ client: Client,
|
||||
store data: SessionData,
|
||||
with key: RedisKey
|
||||
) -> EventLoopFuture<Void> {
|
||||
// salva ogni campo dei dati come un campo hash separato
|
||||
return client.hmset(data.snapshot, in: key)
|
||||
}
|
||||
func redis<Client: RedisClient>(
|
||||
_ client: Client,
|
||||
fetchDataFor key: RedisKey
|
||||
) -> EventLoopFuture<SessionData?> {
|
||||
return client
|
||||
.hgetall(from: key)
|
||||
.map { hash in
|
||||
// hash è [String: RESPValue] quindi dobbiamo provare e spacchettare il
|
||||
// valore come una stringa e salvare ogni valore nel container dei dati
|
||||
return hash.reduce(into: SessionData()) { result, next in
|
||||
guard let value = next.value.string else { return }
|
||||
result[next.key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# Note sulla Versione di Vapor
|
||||
|
||||
Siccome è difficile, se non impossibile, tenere la documentazione aggiornata costantemente, qui troverai le note sulla versione (release notes) di vari pacchetti differenti legati all'ecosistema Vapor.
|
||||
|
||||
## vapor
|
||||
|
||||
## fluent
|
||||
|
||||
## fluent-kit
|
||||
|
||||
## leaf
|
||||
|
||||
## leaf-kit
|
||||
|
||||
## fluent-postgres-driver
|
||||
|
||||
## fluent-mysql-driver
|
||||
|
||||
## fluent-sqlite-driver
|
||||
|
||||
## fluent-mongo-driver
|
||||
|
||||
## postgres-nio
|
||||
|
||||
## mysql-nio
|
||||
|
||||
## sqlite-nio
|
||||
|
||||
## postgres-kit
|
||||
|
||||
## mysql-kit
|
||||
|
||||
## sqlite-kit
|
||||
|
||||
## sql-kit
|
||||
|
||||
## apns
|
||||
|
||||
## queues
|
||||
|
||||
## queues-redis-driver
|
||||
|
||||
## redis
|
||||
|
||||
## jwt
|
||||
|
||||
## jwt-kit
|
||||
|
||||
## websocket-kit
|
||||
|
||||
## routing-kit
|
||||
|
||||
## console-kit
|
||||
|
||||
## async-kit
|
||||
|
||||
## multipart-kit
|
||||
|
||||
## toolbox
|
||||
|
||||
## core
|
||||
|
||||
## swift-codecov-action
|
||||
|
||||
## api-docs
|
||||
|
|
@ -0,0 +1,896 @@
|
|||
# Autenticazione
|
||||
|
||||
L'autenticazione è la verifica dell'identità di un utente. Ciò può avvenire attraverso la verifica di credenziali come un nome utente e una password o un tramite un token. L'autenticazione (talvolta chiamata auth/c) si distingue dall'autorizzazione (auth/z), che è l'atto di verificare i permessi di un utente precedentemente autenticato per permettergli di eseguire determinate operazioni.
|
||||
|
||||
## Introduzione
|
||||
|
||||
Le API di autenticazione di Vapor supportano l'autenticazione di un utente tramite l'intestazione `Authorization` della richiesta, utilizzando le autorizzazioni [Basic](https://tools.ietf.org/html/rfc7617) e [Bearer](https://tools.ietf.org/html/rfc6750). È supportata anche l'autenticazione di un utente tramite la decodifica dei dati dall'API [Content](../basics/content.md).
|
||||
|
||||
L'autenticazione viene implementata creando un `Authenticator` che contiene la logica di verifica. Un autenticatore può essere utilizzato per proteggere singoli gruppi di route o un'intera applicazione. Vapor fornisce i seguenti autenticatori:
|
||||
|
||||
|Protocollo|Descrizione|
|
||||
|-|-|
|
||||
|`RequestAuthenticator`/`AsyncRequestAuthenticator`|Autenticatore di base in grado di creare middleware.|
|
||||
|[`BasicAuthenticator`/`AsyncBasicAuthenticator`](#basic)|Autenticatore che verifica l'header Basic.|
|
||||
|[`BearerAuthenticator`/`AsyncBearerAuthenticator`](#bearer)|Autenticatore che verifica l'header Bearer.|
|
||||
|[`CredentialsAuthenticator`/`AsyncCredentialsAuthenticator`](#credentials)|Autenticatore che verifica le credenziali decodificate dal contenuto della richiesta.|
|
||||
|
||||
Se l'autenticazione ha successo, l'autenticatore aggiunge l'utente verificato a `req.auth`. Si può quindi accedere a questo utente usando `req.auth.get(_:)` nelle route protette dall'autenticatore. Se l'autenticazione fallisce, l'utente non viene aggiunto a `req.auth` e qualsiasi tentativo di accesso fallirà.
|
||||
|
||||
## Authenticatable
|
||||
|
||||
Per utilizzare l'API di autenticazione, ti occorre innanzitutto un tipo di utente conforme ad `Authenticatable`. Questo può essere una `struct`, una `class` o anche un `Model` Fluent. Gli esempi seguenti assumono una semplice struttura `User` che ha una sola proprietà: `name`.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct User: Authenticatable {
|
||||
var name: String
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Ogni esempio che segue utilizzerà un'istanza di un autenticatore che abbiamo creato. In questi esempi, lo abbiamo chiamato `UserAuthenticator`.
|
||||
|
||||
### Route
|
||||
|
||||
Gli autenticatori sono middleware e possono essere utilizzati per proteggere le route.
|
||||
|
||||
```swift
|
||||
let protected = app.grouped(UserAuthenticator())
|
||||
protected.get("me") { req -> String in
|
||||
try req.auth.require(User.self).name
|
||||
}
|
||||
```
|
||||
|
||||
Per recuperare l'utente autenticato, viene usato il metodo `req.auth.require`. Se l'autenticazione fallisce, questo metodo lancia un errore, proteggendo la route.
|
||||
|
||||
### Middleware di Guardia
|
||||
|
||||
Puoi anche usare `GuardMiddleware` nel gruppo di route, per assicurarsi che un utente sia stato autenticato prima di raggiungere il gestore di route.
|
||||
|
||||
```swift
|
||||
let protected = app.grouped(UserAuthenticator())
|
||||
.grouped(User.guardMiddleware())
|
||||
```
|
||||
|
||||
La richiesta di autenticazione non viene effettuata dal middleware dell'autenticatore per consentire la composizione degli autenticatori. Per saperne di più sulla [composizione](#composition) leggi più sotto.
|
||||
|
||||
## Basic
|
||||
|
||||
L'autenticazione di base invia un nome utente e una password nell'intestazione `Authorization`. Il nome utente e la password sono concatenati con i due punti (ad esempio, `test:secret`), codificati in base 64 e preceduti da `"Basic"`. La seguente richiesta di esempio codifica il nome utente `test` con la password `secret`.
|
||||
|
||||
```http
|
||||
GET /me HTTP/1.1
|
||||
Authorization: Basic dGVzdDpzZWNyZXQ=
|
||||
```
|
||||
|
||||
In genere, l'autenticazione di base viene utilizzata una sola volta per registrare un utente e generare un token. Questo riduce al minimo la frequenza di invio della password sensibile dell'utente. Non si dovrebbe mai inviare l'autorizzazione di base in chiaro o su una connessione TLS non verificata.
|
||||
|
||||
Per implementare l'autenticazione di base nella tua applicazione, puoi creare un nuovo autenticatore conforme a `BasicAuthenticator`. Di seguito è riportato un esempio di autenticatore codificato per verificare la richiesta di cui sopra.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct UserAuthenticator: BasicAuthenticator {
|
||||
typealias User = App.User
|
||||
|
||||
func authenticate(
|
||||
basic: BasicAuthorization,
|
||||
for request: Request
|
||||
) -> EventLoopFuture<Void> {
|
||||
if basic.username == "test" && basic.password == "secret" {
|
||||
request.auth.login(User(name: "Vapor"))
|
||||
}
|
||||
return request.eventLoop.makeSucceededFuture(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se stai usando `async`/`await`, puoi usare `AsyncBasicAuthenticator`:
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct UserAuthenticator: AsyncBasicAuthenticator {
|
||||
typealias User = App.User
|
||||
|
||||
func authenticate(
|
||||
basic: BasicAuthorization,
|
||||
for request: Request
|
||||
) async throws {
|
||||
if basic.username == "test" && basic.password == "secret" {
|
||||
request.auth.login(User(name: "Vapor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questo protocollo richiede che implementi il metodo `authenticate(basic:for:)`, che sarà richiamato quando una richiesta in arrivo contiene l'intestazione `Authorization: Basic ...`. Al metodo viene passata una struct `BasicAuthorization` contenente il nome utente e la password.
|
||||
|
||||
In questo autenticatore di prova, il nome utente e la password vengono verificati rispetto ai valori codificati. In un autenticatore reale, potresti voler effettuare un controllo su un database o su un'API esterna, per questo motivo il metodo `authenticate` consente di restituire una future.
|
||||
|
||||
!!! tip
|
||||
Le password non devono mai essere memorizzate in un database in chiaro. Utilizzate sempre gli hash delle password per il confronto.
|
||||
|
||||
Se i parametri di autenticazione sono corretti, in questo caso corrispondono ai valori codificati, viene effettuato l'accesso a uno `User` di nome Vapor. Se i parametri di autenticazione non corrispondono, non viene registrato alcun utente, il che significa che l'autenticazione è fallita.
|
||||
|
||||
Se aggiungi questo autenticatore alla tua applicazione e testi la route definita sopra, dovresti vedere il nome `"Vapor"` restituito per un login riuscito. Se le credenziali non sono corrette, dovresti vedere un errore `401 Unauthorized`.
|
||||
|
||||
## Bearer
|
||||
|
||||
L'autenticazione Bearer invia un token nell'intestazione `Authorization`. Il token è preceduto dalla stringa `"Bearer"`. La seguente richiesta di esempio invia un token di accesso `secret`.
|
||||
|
||||
```http
|
||||
GET /me HTTP/1.1
|
||||
Authorization: Bearer foo
|
||||
```
|
||||
|
||||
L'autenticazione Bearer è comunemente usata per l'autenticazione degli endpoint API. L'utente in genere richiede un token Bearer inviando credenziali come nome utente e password a un endpoint di login. Questo token può durare minuti o giorni, a seconda delle esigenze dell'applicazione.
|
||||
|
||||
Finché il token è valido, l'utente può usarlo al posto delle proprie credenziali per autenticarsi con l'API. Se il token non è valido, è possibile generarne uno nuovo utilizzando l'endpoint di login.
|
||||
|
||||
Per implementare l'autenticazione Bearer nella tua applicazione, puoi creare un nuovo autenticatore conforme a `BearerAuthenticator`. Di seguito è riportato un esempio di autenticatore codificato per verificare la richiesta di cui sopra.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct UserAuthenticator: BearerAuthenticator {
|
||||
typealias User = App.User
|
||||
|
||||
func authenticate(
|
||||
bearer: BearerAuthorization,
|
||||
for request: Request
|
||||
) -> EventLoopFuture<Void> {
|
||||
if bearer.token == "foo" {
|
||||
request.auth.login(User(name: "Vapor"))
|
||||
}
|
||||
return request.eventLoop.makeSucceededFuture(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se stai usando `async`/`await`, puoi usare `AsyncBearerAuthenticator`:
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct UserAuthenticator: AsyncBearerAuthenticator {
|
||||
typealias User = App.User
|
||||
|
||||
func authenticate(
|
||||
bearer: BearerAuthorization,
|
||||
for request: Request
|
||||
) async throws {
|
||||
if bearer.token == "foo" {
|
||||
request.auth.login(User(name: "Vapor"))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questo protocollo richiede l'implementazione di `authenticate(bearer:for:)` che verrà richiamata quando una richiesta in arrivo contiene l'intestazione `Authorization: Bearer ...`. Al metodo viene passata una struct `BearerAuthorization` contenente il token.
|
||||
|
||||
In questo autenticatore di prova, il token viene testato rispetto a un valore codificato. In un vero autenticatore, potresti voler verificare il token confrontandolo con un database o usando misure crittografiche, come si fa con JWT. Ecco perché il metodo `authenticate` consente di restituire una future.
|
||||
|
||||
!!! tip
|
||||
Quando si implementa la verifica dei token, è importante considerare la scalabilità orizzontale. Se l'applicazione deve gestire molti utenti contemporaneamente, l'autenticazione può essere un potenziale collo di bottiglia. Considera il modo in cui il tuo progetto scalerà su più istanze dell'applicazione in esecuzione contemporaneamente.
|
||||
|
||||
Se i parametri di autenticazione sono corretti, e in questo caso corrispondono al valore codificato, viene effettuato l'accesso a un `Utente` di nome Vapor. Se i parametri di autenticazione non corrispondono, non viene registrato alcun utente, il che significa che l'autenticazione è fallita.
|
||||
|
||||
Se aggiungi questo autenticatore alla tua applicazione e testi la route definita sopra, dovresti vedere il nome `"Vapor"` restituito per un login riuscito. Se le credenziali non sono corrette, dovresti vedere un errore `401 Unauthorized`.
|
||||
|
||||
## Composizione
|
||||
|
||||
Puoi comporre (combinare insieme) più autenticatori per creare un'autenticazione dell'endpoint più complessa. Poiché un middleware autenticatore non rifiuta la richiesta se l'autenticazione fallisce, puoi concatenare più di un middleware. Puoi concatenare più autenticatori in due modi diversi.
|
||||
|
||||
### Composizione dei Metodi
|
||||
|
||||
Il primo metodo di composizione dell'autenticazione consiste nel concatenare più autenticatori per lo stesso tipo di utente. Prendi l'esempio seguente:
|
||||
|
||||
```swift
|
||||
app.grouped(UserPasswordAuthenticator())
|
||||
.grouped(UserTokenAuthenticator())
|
||||
.grouped(User.guardMiddleware())
|
||||
.post("login")
|
||||
{ req in
|
||||
let user = try req.auth.require(User.self)
|
||||
// Fai qualcosa con l'utente.
|
||||
}
|
||||
```
|
||||
|
||||
Questo esempio presuppone due autenticatori `UserPasswordAuthenticator` e `UserTokenAuthenticator` che autenticano entrambi `User`. Entrambi gli autenticatori sono aggiunti al gruppo di route. Infine, `GuardMiddleware` viene aggiunto dopo gli autenticatori per richiedere che `User` sia stato autenticato con successo.
|
||||
|
||||
Questa composizione di autenticatori dà come risultato una route a cui si può accedere sia tramite password che tramite token. Una route di questo tipo potrebbe consentire a un utente di effettuare il login e generare un token, per poi continuare a usare quel token per generare nuovi token.
|
||||
|
||||
### Composizione di Utenti
|
||||
|
||||
Il secondo metodo di composizione dell'autenticazione consiste nel concatenare gli autenticatori per diversi tipi di utenti. Prendiamo il seguente esempio:
|
||||
|
||||
```swift
|
||||
app.grouped(AdminAuthenticator())
|
||||
.grouped(UserAuthenticator())
|
||||
.get("secure")
|
||||
{ req in
|
||||
guard req.auth.has(Admin.self) || req.auth.has(User.self) else {
|
||||
throw Abort(.unauthorized)
|
||||
}
|
||||
// Fai qualcosa.
|
||||
}
|
||||
```
|
||||
|
||||
Questo esempio presuppone due autenticatori `AdminAuthenticator` e `UserAuthenticator` che autenticano rispettivamente `Admin` e `User`. Entrambi gli autenticatori sono aggiunti al gruppo di route. Invece di usare `GuardMiddleware`, viene aggiunto un controllo nel gestore di route per vedere se `Admin` o `User` sono stati autenticati. In caso contrario, viene lanciato un errore.
|
||||
|
||||
Questa composizione di autenticatori dà luogo a un percorso a cui possono accedere due tipi diversi di utenti con metodi di autenticazione potenzialmente diversi. Un percorso di questo tipo potrebbe consentire l'autenticazione di un utente normale, pur consentendo l'accesso a un super-utente.
|
||||
|
||||
## Manualmente
|
||||
|
||||
Puoi anche gestire l'autenticazione manualmente, utilizzando `req.auth`. Questo è particolarmente utile per i test.
|
||||
|
||||
Per accedere manualmente a un utente, puoi utilizzare `req.auth.login(_:)`. A questo metodo può essere passato qualsiasi utente `Authenticatable`.
|
||||
|
||||
```swift
|
||||
req.auth.login(User(name: "Vapor"))
|
||||
```
|
||||
|
||||
Per ottenere l'utente autenticato puoi usare `req.auth.require(_:)`:
|
||||
|
||||
```swift
|
||||
let user: User = try req.auth.require(User.self)
|
||||
print(user.name) // String
|
||||
```
|
||||
|
||||
Puoi anche usare `req.auth.get(_:)` se non vuoi lanciare automaticamente un errore quando l'autenticazione fallisce.
|
||||
|
||||
```swift
|
||||
let user = req.auth.get(User.self)
|
||||
print(user?.name) // String?
|
||||
```
|
||||
|
||||
Per effettuare il logout di un utente, puoi usare `req.auth.logout(_:)`:
|
||||
|
||||
```swift
|
||||
req.auth.logout(User.self)
|
||||
```
|
||||
|
||||
## Fluent
|
||||
|
||||
[Fluent](../fluent/overview.md) definisce due protocolli `ModelAuthenticatable` e `ModelTokenAuthenticatable` che possono essere aggiunti ai modelli esistenti. Conformare i modelli a questi protocolli consente di creare autenticatori per proteggere gli endpoint.
|
||||
|
||||
`ModelTokenAuthenticatable` si autentica con un token Bearer. È quello che puoi usare per proteggere la maggior parte degli endpoint. `ModelAuthenticatable` si autentica con nome utente e password ed è usato da un singolo endpoint per generare token.
|
||||
|
||||
Questa guida presuppone che tu abbia già familiarità con Fluent e che abbia configurato con successo la tua applicazione per utilizzare un database. Se non conosci Fluent, inizia dalla [panoramica](../fluent/overview.md).
|
||||
|
||||
### User
|
||||
|
||||
Per iniziare, è necessario un modello che rappresenti l'utente da autenticare. Per questa guida, useremo il modello seguente, ma puoi usare un qualsiasi modello esistente.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
final class User: Model, Content {
|
||||
static let schema = "users"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
|
||||
@Field(key: "email")
|
||||
var email: String
|
||||
|
||||
@Field(key: "password_hash")
|
||||
var passwordHash: String
|
||||
|
||||
init() { }
|
||||
|
||||
init(id: UUID? = nil, name: String, email: String, passwordHash: String) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.email = email
|
||||
self.passwordHash = passwordHash
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Il modello deve essere in grado di memorizzare un nome utente, in questo caso un'e-mail, e un hash di password. Abbiamo anche impostato `email` come campo unico, per evitare utenti duplicati. La migrazione corrispondente per questo modello di esempio è qui:
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
extension User {
|
||||
struct Migration: AsyncMigration {
|
||||
var name: String { "CreateUser" }
|
||||
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("users")
|
||||
.id()
|
||||
.field("name", .string, .required)
|
||||
.field("email", .string, .required)
|
||||
.field("password_hash", .string, .required)
|
||||
.unique(on: "email")
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("users").delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Non dimenticare di aggiungere la migrazione a `app.migrations`.
|
||||
|
||||
```swift
|
||||
app.migrations.add(User.Migration())
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Poiché gli indirizzi email non sono sensibili alle maiuscole e alle minuscole, puoi aggiungere un [`Middleware`](../fluent/model.md#lifecycle) che coercizzi l'indirizzo email in minuscolo prima di salvarlo nella base dati. Tieni presente, però, che `ModelAuthenticatable` usa un confronto sensibile alle maiuscole e alle minuscole, quindi se fai questo devi assicurarti che l'input dell'utente sia tutto minuscolo, o con la coercizione delle maiuscole nel client o con un autenticatore personalizzato.
|
||||
|
||||
La prima cosa di cui hai bisogno è un endpoint per creare nuovi utenti. Useremo `POST /users`. Crea una struttura [Content](../basics/content.md) che rappresenti i dati che questo endpoint si aspetta.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
extension User {
|
||||
struct Create: Content {
|
||||
var name: String
|
||||
var email: String
|
||||
var password: String
|
||||
var confirmPassword: String
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se vuoi, puoi conformare questa struttura a [Validatable](../basics/validation.md) per aggiungere requisiti di validazione.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
extension User.Create: Validatable {
|
||||
static func validations(_ validations: inout Validations) {
|
||||
validations.add("name", as: String.self, is: !.empty)
|
||||
validations.add("email", as: String.self, is: .email)
|
||||
validations.add("password", as: String.self, is: .count(8...))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Ora puoi creare l'endpoint `POST /users`.
|
||||
|
||||
```swift
|
||||
app.post("users") { req async throws -> User in
|
||||
try User.Create.validate(content: req)
|
||||
let create = try req.content.decode(User.Create.self)
|
||||
guard create.password == create.confirmPassword else {
|
||||
throw Abort(.badRequest, reason: "Passwords did not match")
|
||||
}
|
||||
let user = try User(
|
||||
name: create.name,
|
||||
email: create.email,
|
||||
passwordHash: Bcrypt.hash(create.password)
|
||||
)
|
||||
try await user.save(on: req.db)
|
||||
return user
|
||||
}
|
||||
```
|
||||
|
||||
Questo endpoint convalida la richiesta in arrivo, decodifica la struttura `User.Create` e controlla che le password corrispondano. Utilizza quindi i dati decodificati per creare un nuovo `User` e lo salva nel database. La password in chiaro viene sottoposta a hash con `Bcrypt` prima di essere salvata nel database.
|
||||
|
||||
Compila ed esegui il progetto, assicurandoti di eseguire prima le migrazioni sul database, quindi utilizza la seguente richiesta per creare un nuovo utente.
|
||||
|
||||
```http
|
||||
POST /users HTTP/1.1
|
||||
Content-Length: 97
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Vapor",
|
||||
"email": "test@vapor.codes",
|
||||
"password": "secret42",
|
||||
"confirmPassword": "secret42"
|
||||
}
|
||||
```
|
||||
|
||||
#### Modello Authenticatable
|
||||
|
||||
Ora che hai un modello utente e un endpoint per creare nuovi utenti, conforma il modello a `ModelAuthenticatable`. Questo ti permetterà di autenticare il modello usando nome utente e password.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
extension User: ModelAuthenticatable {
|
||||
static let usernameKey = \User.$email
|
||||
static let passwordHashKey = \User.$passwordHash
|
||||
|
||||
func verify(password: String) throws -> Bool {
|
||||
try Bcrypt.verify(password, created: self.passwordHash)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questa estensione aggiunge la conformità `ModelAuthenticatable` a `User`. Le prime due proprietà specificano quali campi devono essere utilizzati per memorizzare rispettivamente il nome utente e l'hash della password. La notazione `\` crea un percorso chiave per i campi che Fluent può usare per accedervi.
|
||||
|
||||
L'ultimo requisito è un metodo per verificare le password in chiaro inviate nell'intestazione di autenticazione Basic. Poiché usiamo Bcrypt per l'hash della password durante la registrazione, useremo Bcrypt per verificare che la password fornita corrisponda all'hash della password memorizzata.
|
||||
|
||||
Ora che l'utente `User` è conforme a `ModelAuthenticatable`, puoi creare un autenticatore per proteggere la route di login.
|
||||
|
||||
```swift
|
||||
let passwordProtected = app.grouped(User.authenticator())
|
||||
passwordProtected.post("login") { req -> User in
|
||||
try req.auth.require(User.self)
|
||||
}
|
||||
```
|
||||
|
||||
`ModelAuthenticatable` aggiunge un metodo statico `authenticator` per creare un autenticatore.
|
||||
|
||||
Verifica che questo percorso funzioni inviando la seguente richiesta:
|
||||
|
||||
```http
|
||||
POST /login HTTP/1.1
|
||||
Authorization: Basic dGVzdEB2YXBvci5jb2RlczpzZWNyZXQ0Mg==
|
||||
```
|
||||
|
||||
Questa richiesta passa il nome utente `test@vapor.codes` e la password `secret42` tramite l'intestazione di autenticazione Basic. Dovrebbe essere restituito l'utente precedentemente creato.
|
||||
|
||||
Anche se in teoria si potrebbe usare l'autenticazione di base per proteggere tutti gli endpoint, è consigliato usare un token separato. In questo modo si riduce al minimo la frequenza di invio della password sensibile dell'utente su Internet. Inoltre, l'autenticazione è molto più veloce, poiché è sufficiente eseguire l'hashing della password durante l'accesso.
|
||||
|
||||
### Token Utente
|
||||
|
||||
Crea un nuovo modello per rappresentare i token degli utenti.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
import Vapor
|
||||
|
||||
final class UserToken: Model, Content {
|
||||
static let schema = "user_tokens"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "value")
|
||||
var value: String
|
||||
|
||||
@Parent(key: "user_id")
|
||||
var user: User
|
||||
|
||||
init() { }
|
||||
|
||||
init(id: UUID? = nil, value: String, userID: User.IDValue) {
|
||||
self.id = id
|
||||
self.value = value
|
||||
self.$user.id = userID
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questo modello deve avere un campo `value` per memorizzare la stringa unica del token. Deve anche avere una [relazione padre](../fluent/overview.md#parent) con il modello utente. Puoi aggiungere anche altre proprietà a questo token, come ad esempio una data di scadenza.
|
||||
|
||||
Quindi, crea una migrazione per questo modello.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
|
||||
extension UserToken {
|
||||
struct Migration: AsyncMigration {
|
||||
var name: String { "CreateUserToken" }
|
||||
|
||||
func prepare(on database: Database) async throws {
|
||||
try await database.schema("user_tokens")
|
||||
.id()
|
||||
.field("value", .string, .required)
|
||||
.field("user_id", .uuid, .required, .references("users", "id"))
|
||||
.unique(on: "value")
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: Database) async throws {
|
||||
try await database.schema("user_tokens").delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nota che questa migrazione rende unico il campo `value`. Inoltre, crea un riferimento a chiave esterna tra il campo `user_id` e la tabella utenti.
|
||||
|
||||
Non dimenticare di aggiungere la migrazione a `app.migrations`.
|
||||
|
||||
```swift
|
||||
app.migrations.add(UserToken.Migration())
|
||||
```
|
||||
|
||||
Infine, aggiungi un metodo su `User` per generare un nuovo token. Questo metodo sarà utilizzato durante il login.
|
||||
|
||||
```swift
|
||||
extension User {
|
||||
func generateToken() throws -> UserToken {
|
||||
try .init(
|
||||
value: [UInt8].random(count: 16).base64,
|
||||
userID: self.requireID()
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Qui usiamo `[UInt8].random(count:)` per generare un valore casuale di token. Per questo esempio, vengono utilizzati 16 byte, o 128 bit, di dati casuali. Puoi modificare questo numero come ritieni opportuno. I dati casuali vengono poi codificati in base-64 per facilitarne la trasmissione nelle intestazioni HTTP.
|
||||
|
||||
Ora che puoi generare i token utente, aggiorna la route `POST /login` per creare e restituire un token.
|
||||
|
||||
```swift
|
||||
let passwordProtected = app.grouped(User.authenticator())
|
||||
passwordProtected.post("login") { req async throws -> UserToken in
|
||||
let user = try req.auth.require(User.self)
|
||||
let token = try user.generateToken()
|
||||
try await token.save(on: req.db)
|
||||
return token
|
||||
}
|
||||
```
|
||||
|
||||
Verifica che questa route funzioni utilizzando la stessa richiesta di login di cui sopra. Ora dovresti ottenere un token al momento dell'accesso che assomigli a qualcosa di simile:
|
||||
|
||||
```
|
||||
8gtg300Jwdhc/Ffw784EXA==
|
||||
```
|
||||
|
||||
Conserva il token ottenuto: lo utilizzeremo a breve.
|
||||
|
||||
#### Modello Token Authenticatable
|
||||
|
||||
Conforma `UserToken` a `ModelTokenAuthenticatable`. Questo permetterà ai token di autenticare il modello `User`.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
import Fluent
|
||||
|
||||
extension UserToken: ModelTokenAuthenticatable {
|
||||
static let valueKey = \UserToken.$value
|
||||
static let userKey = \UserToken.$user
|
||||
|
||||
var isValid: Bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Il primo requisito del protocollo specifica quale campo memorizza il valore univoco del token. Questo è il valore che sarà inviato nell'intestazione di autenticazione Bearer. Il secondo requisito specifica la parentela con il modello `User`. Questo è il modo in cui Fluent cercherà l'utente autenticato.
|
||||
|
||||
Il requisito finale è un booleano `isValid`. Se è `false`, il token sarà cancellato dal database e l'utente non sarà autenticato. Per semplicità, renderemo i token eterni, codificando in modo rigido questo valore a `true`.
|
||||
|
||||
Ora che il token è conforme a `ModelTokenAuthenticatable`, si può creare un autenticatore per proteggere le route.
|
||||
|
||||
Crea un nuovo endpoint `GET /me` per ottenere l'utente attualmente autenticato.
|
||||
|
||||
```swift
|
||||
let tokenProtected = app.grouped(UserToken.authenticator())
|
||||
tokenProtected.get("me") { req -> User in
|
||||
try req.auth.require(User.self)
|
||||
}
|
||||
```
|
||||
|
||||
Simile a `User`, `UserToken` ha ora un metodo statico `authenticator()` che può generare un autenticatore. L'autenticatore cercherà di trovare un `UserToken` corrispondente, utilizzando il valore fornito nell'intestazione di autenticazione del portatore. Se trova una corrispondenza, recupera il relativo `User` e lo autentica.
|
||||
|
||||
Verifica che questa route funzioni inviando la seguente richiesta HTTP, dove il token è il valore salvato dalla richiesta `POST /login`.
|
||||
|
||||
```http
|
||||
GET /me HTTP/1.1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
Dovresti vedere l'utente attualmente autenticato.
|
||||
|
||||
## Sessioni
|
||||
|
||||
L'[API delle Sessioni](../advanced/sessions.md) di Vapor può essere utilizzata per persistere automaticamente l'autenticazione dell'utente tra le richieste. Questo funziona memorizzando un identificatore univoco per l'utente nei dati di sessione della richiesta, dopo il successo del login. Nelle richieste successive, l'identificatore dell'utente viene recuperato dalla sessione e usato per autenticare l'utente prima di chiamare il gestore della route.
|
||||
|
||||
Le sessioni sono ottime per le applicazioni web front-end costruite in Vapor che servono HTML direttamente ai browser web. Per le API, si consiglia di utilizzare un'autenticazione stateless basata su token per conservare i dati dell'utente tra una richiesta e l'altra.
|
||||
|
||||
### Session Authenticatable
|
||||
|
||||
Per utilizzare l'autenticazione basata sulla sessione, occorre un tipo conforme a `SessionAuthenticatable`. Per questo esempio, useremo una semplice struct.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct User {
|
||||
var email: String
|
||||
}
|
||||
```
|
||||
|
||||
Per essere conformi a `SessionAuthenticatable`, è necessario specificare un `sessionID`. Questo è il valore che verrà memorizzato nei dati di sessione e deve identificare in modo univoco l'utente.
|
||||
|
||||
```swift
|
||||
extension User: SessionAuthenticatable {
|
||||
var sessionID: String {
|
||||
self.email
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Per il nostro tipo `User`, useremo l'indirizzo e-mail come identificatore unico di sessione.
|
||||
|
||||
### Autenticatore di Sessione
|
||||
|
||||
Poi, avrai bisogno di un `SessionAuthenticator` per gestire la risoluzione delle istanze dell'utente dall'identificatore di sessione persistito.
|
||||
|
||||
```swift
|
||||
struct UserSessionAuthenticator: SessionAuthenticator {
|
||||
typealias User = App.User
|
||||
func authenticate(sessionID: String, for request: Request) -> EventLoopFuture<Void> {
|
||||
let user = User(email: sessionID)
|
||||
request.auth.login(user)
|
||||
return request.eventLoop.makeSucceededFuture(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Se stai usando `async`/`await`, puoi usare `AsyncSessionAuthenticator`:
|
||||
|
||||
```swift
|
||||
struct UserSessionAuthenticator: AsyncSessionAuthenticator {
|
||||
typealias User = App.User
|
||||
func authenticate(sessionID: String, for request: Request) async throws {
|
||||
let user = User(email: sessionID)
|
||||
request.auth.login(user)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Poiché tutte le informazioni necessarie per inizializzare il nostro `User` di esempio sono contenute nell'identificatore di sessione, possiamo creare e accedere all'utente in modo sincrono. In un'applicazione reale, è probabile che venga utilizzato l'identificatore di sessione per eseguire una ricerca nel database o una richiesta API per recuperare il resto dei dati dell'utente prima dell'autenticazione.
|
||||
|
||||
Quindi, crea un semplice autenticatore di portatori per eseguire l'autenticazione iniziale.
|
||||
|
||||
```swift
|
||||
struct UserBearerAuthenticator: AsyncBearerAuthenticator {
|
||||
func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
|
||||
if bearer.token == "test" {
|
||||
let user = User(email: "hello@vapor.codes")
|
||||
request.auth.login(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questo autenticatore autenticherà un utente con l'email `hello@vapor.codes` quando viene inviato il token portatore `test`.
|
||||
|
||||
Infine, combina tutti questi pezzi insieme nell'applicazione.
|
||||
|
||||
```swift
|
||||
// Crea un gruppo di route protette che richiedono autenticazione.
|
||||
let protected = app.routes.grouped([
|
||||
app.sessions.middleware,
|
||||
UserSessionAuthenticator(),
|
||||
UserBearerAuthenticator(),
|
||||
User.guardMiddleware(),
|
||||
])
|
||||
|
||||
// Aggiungi una route GET /me che restituisce l'email dell'utente.
|
||||
protected.get("me") { req -> String in
|
||||
try req.auth.require(User.self).email
|
||||
}
|
||||
```
|
||||
|
||||
Viene prima aggiunto `SessionsMiddleware`, per abilitare il supporto alle sessioni nell'applicazione. Puoi trovare maggiori informazioni sulla configurazione delle sessioni nella sezione [API di sessione](../advanced/sessions.md).
|
||||
|
||||
Successivamente, viene aggiunto il `SessionAuthenticator`. Questo gestisce l'autenticazione dell'utente se è attiva una sessione.
|
||||
|
||||
Se l'autenticazione non è ancora stata persistita nella sessione, la richiesta sarà inoltrata all'autenticatore successivo. L'autenticatore `UserBearerAuthenticator` controllerà il token del portatore e autenticherà l'utente se è uguale a `"test"`.
|
||||
|
||||
Infine, `User.guardMiddleware()` assicura che `User` sia stato autenticato da uno dei middleware precedenti. Se l'utente non è stato autenticato, verrà lanciato un errore.
|
||||
|
||||
Per testare questa route, invia prima la seguente richiesta:
|
||||
|
||||
```http
|
||||
GET /me HTTP/1.1
|
||||
authorization: Bearer test
|
||||
```
|
||||
|
||||
Questo farà sì che `UserBearerAuthenticator` autentichi l'utente. Una volta autenticato, `UserSessionAuthenticator` persisterà l'identificatore dell'utente nella memoria di sessione e genererà un cookie. Puoi poi utilizzare il cookie dalla risposta in una seconda richiesta alla route.
|
||||
|
||||
```http
|
||||
GET /me HTTP/1.1
|
||||
cookie: vapor_session=123
|
||||
```
|
||||
|
||||
Questa volta, `UserSessionAuthenticator` autenticherà l'utente e dovrebbe essere restituita l'e-mail dell'utente.
|
||||
|
||||
### Model Session Authenticatable
|
||||
|
||||
I modelli Fluent possono generare `SessionAuthenticator` conformandosi a `ModelSessionAuthenticatable`. Questo userà l'identificatore univoco del modello come identificatore di sessione ed eseguirà automaticamente una ricerca nel database per ripristinare il modello dalla sessione.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
|
||||
final class User: Model { ... }
|
||||
|
||||
// Consente di persistere il modello nelle sessioni.
|
||||
extension User: ModelSessionAuthenticatable { }
|
||||
```
|
||||
|
||||
Puoi aggiungere `ModelSessionAuthenticatable` a qualsiasi modello esistente come conformità vuota. Una volta aggiunto, sarà disponibile un nuovo metodo statico per creare un `SessionAuthenticator` per quel modello.
|
||||
|
||||
```swift
|
||||
User.sessionAuthenticator()
|
||||
```
|
||||
|
||||
Questo utilizzerà il database predefinito dell'applicazione per la risoluzione dell'utente. Per specificare un database, passa l'identificatore.
|
||||
|
||||
```swift
|
||||
User.sessionAuthenticator(.sqlite)
|
||||
```
|
||||
|
||||
## Autenticazione per Sito Web
|
||||
|
||||
I siti web sono un caso particolare per l'autenticazione, perché l'uso di un browser limita il modo in cui è possibile collegare le credenziali a un browser. Questo porta a due diversi scenari di autenticazione:
|
||||
|
||||
* l'accesso iniziale tramite un form
|
||||
* chiamate successive autenticate con un cookie di sessione
|
||||
|
||||
Vapor e Fluent forniscono diversi aiutanti per rendere tutto ciò semplice.
|
||||
|
||||
### Autenticazione di Sessione
|
||||
|
||||
L'autenticazione di sessione funziona come descritto sopra. Devi applicare il middleware di sessione e l'autenticatore di sessione a tutte le route a cui l'utente accederà. Queste includono tutte le route protette, le route che sono pubbliche, ma per le quali vuoi accedere all'utente se è loggato (ad esempio, per visualizzare un pulsante per l'account), **e** le route di login.
|
||||
|
||||
È possibile attivarlo globalmente nella propria applicazione in `configure.swift` in questo modo:
|
||||
|
||||
```swift
|
||||
app.middleware.use(app.sessions.middleware)
|
||||
app.middleware.use(User.sessionAuthenticator())
|
||||
```
|
||||
|
||||
Questi middleware svolgono le seguenti funzioni:
|
||||
|
||||
* Il middleware delle sessioni prende il cookie di sessione fornito nella richiesta e lo converte in una sessione.
|
||||
* l'autenticatore di sessione prende la sessione e verifica se esiste un utente autenticato per quella sessione. In caso affermativo, il middleware autentica la richiesta. Nella risposta, l'autenticatore di sessione vede se la richiesta ha un utente autenticato e lo salva nella sessione, in modo che sia autenticato nella richiesta successiva.
|
||||
|
||||
!!! note
|
||||
Di default, il cookie di sessione non è impostato su `secure` e/o `httpOnly`. Per ulteriori informazioni su come configurare i cookie, consultare le [API di sessione](../advanced/sessions.md#configuration) di Vapor.
|
||||
|
||||
### Protezione delle Route
|
||||
|
||||
Quando si proteggono le route per un'API, tradizionalmente restituisci una risposta HTTP con un codice di stato come **401 Unauthorized** se la richiesta non è autenticata. Tuttavia, questa non è una buona esperienza per l'utente che utilizza un browser. Vapor fornisce un `RedirectMiddleware` per qualsiasi tipo `Authenticatable` da utilizzare in questo scenario:
|
||||
|
||||
```swift
|
||||
let protectedRoutes = app.grouped(User.redirectMiddleware(path: "/login?loginRequired=true"))
|
||||
```
|
||||
|
||||
L'oggetto `RedirectMiddleware` supporta anche il passaggio di una chiusura che restituisce il percorso di reindirizzamento come `Stringa` durante la creazione, per una gestione avanzata degli url. Ad esempio, includendo il percorso di reindirizzamento come parametro di query alla destinazione del reindirizzamento per la gestione dello stato.
|
||||
|
||||
```swift
|
||||
let redirectMiddleware = User.redirectMiddleware { req -> String in
|
||||
return "/login?authRequired=true&next=\(req.url.path)"
|
||||
}
|
||||
```
|
||||
|
||||
Questo funziona in modo simile a `GuardMiddleware`. Qualsiasi richiesta alle route registrate su `protectedRoutes` che non sia autenticata sarà reindirizzata al percorso fornito. Questo permette di dire agli utenti di effettuare il login, invece di fornire semplicemente un **401 Unauthorized**.
|
||||
|
||||
Assicurati di includere un Autenticatore di sessione prima del `RedirectMiddleware` per garantire che l'utente autenticato sia caricato prima di passare attraverso il `RedirectMiddleware`.
|
||||
|
||||
```swift
|
||||
let protectedRoutes = app.grouped([User.sessionAuthenticator(), redirectMiddleware])
|
||||
```
|
||||
|
||||
### Form per il Login
|
||||
|
||||
Per autenticare un utente e le richieste future con una sessione, è necessario effettuare il login. Vapor fornisce un protocollo `ModelCredentialsAuthenticatable` a cui conformarsi. Questo gestisce l'accesso tramite un modulo. Per prima cosa, conforma il tuo `User` a questo protocollo:
|
||||
|
||||
```swift
|
||||
extension User: ModelCredentialsAuthenticatable {
|
||||
static let usernameKey = \User.$email
|
||||
static let passwordHashKey = \User.$password
|
||||
|
||||
func verify(password: String) throws -> Bool {
|
||||
try Bcrypt.verify(password, created: self.password)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Questo è identico a `ModelAuthenticatable` e se lo `User` è già conforme a questo, non è necessario fare altro. Quindi applica il middleware `ModelCredentialsAuthenticator` alla richiesta POST del modulo di login:
|
||||
|
||||
```swift
|
||||
let credentialsProtectedRoute = sessionRoutes.grouped(User.credentialsAuthenticator())
|
||||
credentialsProtectedRoute.post("login", use: loginPostHandler)
|
||||
```
|
||||
|
||||
Esso utilizza l'autenticatore di credenziali predefinito per proteggere il percorso di accesso. È necessario che invii `username` e `password` nella richiesta POST. Si può impostare il form in questo modo:
|
||||
|
||||
```html
|
||||
<form method="POST" action="/login">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" placeholder="Username" name="username" autocomplete="username" required autofocus>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" placeholder="Password" name="password" autocomplete="current-password" required>
|
||||
<input type="submit" value="Sign In">
|
||||
</form>
|
||||
```
|
||||
|
||||
Il `CredentialsAuthenticator` estrae `username` e `password` dal corpo della richiesta, trova l'utente dal nome utente e verifica la password. Se la password è valida, il middleware autentica la richiesta. Il `SessionAuthenticator` autentica quindi la sessione per le richieste successive.
|
||||
|
||||
## JWT
|
||||
|
||||
[JWT](jwt.md) fornisce un `JWTAuthenticator` che può essere usato per autenticare i token web JSON nelle richieste in arrivo. Se non conosci JWT, dai un'occhiata alla [panoramica](jwt.md).
|
||||
|
||||
Per prima cosa, crea un tipo che rappresenti un payload JWT.
|
||||
|
||||
```swift
|
||||
// Esempio di payload JWT.
|
||||
struct SessionToken: Content, Authenticatable, JWTPayload {
|
||||
|
||||
// Costanti
|
||||
let expirationTime: TimeInterval = 60 * 15
|
||||
|
||||
// Dati del payload
|
||||
var expiration: ExpirationClaim
|
||||
var userId: UUID
|
||||
|
||||
init(userId: UUID) {
|
||||
self.userId = userId
|
||||
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationTime))
|
||||
}
|
||||
|
||||
init(user: User) throws {
|
||||
self.userId = try user.requireID()
|
||||
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationTime))
|
||||
}
|
||||
|
||||
func verify(using signer: JWTSigner) throws {
|
||||
try expiration.verifyNotExpired()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Successivamente, possiamo definire una rappresentazione dei dati contenuti in una risposta di login andata a buon fine. Per ora la risposta avrà solo una proprietà, una stringa che rappresenta un JWT firmato.
|
||||
|
||||
```swift
|
||||
struct ClientTokenReponse: Content {
|
||||
var token: String
|
||||
}
|
||||
```
|
||||
|
||||
Utilizzando il nostro modello per il token JWT e la risposta, possiamo usare una route di login protetta da password che restituisce un `ClientTokenReponse` e include un `SessionToken` firmato.
|
||||
|
||||
```swift
|
||||
let passwordProtected = app.grouped(User.authenticator(), User.guardMiddleware())
|
||||
passwordProtected.post("login") { req -> ClientTokenReponse in
|
||||
let user = try req.auth.require(User.self)
|
||||
let payload = try SessionToken(with: user)
|
||||
return ClientTokenReponse(token: try req.jwt.sign(payload))
|
||||
}
|
||||
```
|
||||
|
||||
In alternativa, se non vuoi usare un autenticatore, puoi avere qualcosa di simile a questo:
|
||||
|
||||
```swift
|
||||
app.post("login") { req -> ClientTokenReponse in
|
||||
// Valida le credenziali dell'utente
|
||||
// Ottieni lo userId dell'utente
|
||||
let payload = try SessionToken(userId: userId)
|
||||
return ClientTokenReponse(token: try req.jwt.sign(payload))
|
||||
}
|
||||
```
|
||||
|
||||
Conformando il payload a `Authenticatable` e `JWTPayload`, puoi generare un autenticatore di route usando il metodo `authenticator()`. Aggiungilo a un gruppo di route per recuperare e verificare automaticamente il JWT prima che la route venga chiamata.
|
||||
|
||||
```swift
|
||||
// Crea un gruppo di route che richiede il SessionToken JWT.
|
||||
let secure = app.grouped(SessionToken.authenticator(), SessionToken.guardMiddleware())
|
||||
```
|
||||
|
||||
L'aggiunta dell'opzionale [middleware di guardia](#guard-middleware) richiede che l'autorizzazione sia riuscita.
|
||||
|
||||
All'interno delle route protette, si può accedere al payload JWT autenticato usando `req.auth`.
|
||||
|
||||
```swift
|
||||
// Restituisce una risposta ok se il token fornito dall'utente è valido.
|
||||
secure.post("validateLoggedInUser") { req -> HTTPStatus in
|
||||
let sessionToken = try req.auth.require(SessionToken.self)
|
||||
print(sessionToken.userId)
|
||||
return .ok
|
||||
}
|
||||
```
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
# Aggiornamento a 4.0
|
||||
|
||||
Questa guida vi mostrerà come aggiornare un progetto Vapor 3.x a Vapor 4.x. La guida cercherà di coprire tutti i cambiamenti riguardanti i pacchetti Vapor e anche alcuni dei più comuni pacchetti di terze parti. Se notate qualcosa di mancante, non esitate a chiedere aiuto nella [chat del team Vapor](https://discord.gg/vapor). Anche issues e pull requests su GitHub sono ben accette.
|
||||
Questa guida ti mostrerà come aggiornare un progetto Vapor 3.x a Vapor 4.x. La guida cercherà di coprire tutti i cambiamenti riguardanti i pacchetti Vapor e anche alcuni dei più comuni pacchetti di terze parti. Se noti che manca qualcosa, non esitare a chiedere aiuto nella [chat del team Vapor](https://discord.gg/vapor). Anche issues e pull requests su GitHub sono ben accette.
|
||||
|
||||
## Dipendenze
|
||||
|
||||
Per usare Vapor 4, avrete bisogno di almeno Xcode 11.4 e macOS 10.15.
|
||||
Per usare Vapor 4, avrai bisogno di almeno Xcode 11.4 e macOS 10.15.
|
||||
|
||||
La sezione Installazione della documentazione contiene le istruzioni per installare le dipendenze.
|
||||
|
||||
## Package.swift
|
||||
|
||||
Il primo passo per aggiornare a Vapor 4 è aggiornare il file delle dipendenze del pacchetto. Qui è riportato un esempio di un `Package.swift` aggiornato. Potete anche visitare il [template Package.swift aggiornato](https://github.com/vapor/template/blob/main/Package.swift).
|
||||
Il primo passo per aggiornare a Vapor 4 è aggiornare il file delle dipendenze del pacchetto. Qui è riportato un esempio di un `Package.swift` aggiornato. Puoi anche visitare il [template Package.swift aggiornato](https://github.com/vapor/template/blob/main/Package.swift).
|
||||
|
||||
```diff
|
||||
-// swift-tools-version:4.0
|
||||
|
|
@ -97,14 +97,14 @@ Vapor potrebbe eventualmente aggiungere il supporto per piattaforme aggiuntive i
|
|||
|
||||
Vapor 4 utilizza il supporto nativo di SPM di Xcode 11. Ciò significa che non ci sarà più bisogno di generare il file `.xcodeproj`. Per aprire un progetto Vapor 4 in Xcode, basterà aprire il file `Package.swift` tramite `vapor xcode` o `open Package.swift`, Xcode poi procederà a scaricare le dipendenze.
|
||||
|
||||
Una volta aggiornato il Package.swift, potreste dover chiudere Xcode e rimuovere i seguenti file dalla directory del progetto:
|
||||
Una volta aggiornato il Package.swift, potresti dover chiudere Xcode e rimuovere i seguenti file dalla directory del progetto:
|
||||
|
||||
- `Package.resolved`
|
||||
- `.build`
|
||||
- `.swiftpm`
|
||||
- `*.xcodeproj`
|
||||
|
||||
Una volta che le nuove dipendenze sono state scaricate, noterete errori di compilazione, probabilmente più di qualcuno. Non vi preoccupate! Vi mostreremo come risolverli.
|
||||
Una volta che le nuove dipendenze sono state scaricate, noterai errori di compilazione, probabilmente un bel po'. Non ti preoccupare! Ti mostreremo come risolverli.
|
||||
|
||||
## Run
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ Il file `main.swift` andrà a sostituire il file `app.swift`, quindi potete rimu
|
|||
|
||||
## App
|
||||
|
||||
Diamo un'occhiata a come aggiornare la strutura di base di App.
|
||||
Diamo un'occhiata a come aggiornare la struttura di base di App.
|
||||
|
||||
### configure.swift
|
||||
|
||||
|
|
@ -158,9 +158,9 @@ public func configure(_ app: Application) throws {
|
|||
}
|
||||
```
|
||||
|
||||
Cambiamenti di sintassi per cose come routing, middleware, fluent ecc. sono menzionati nelle sezioni seguenti.
|
||||
Cambiamenti di sintassi per cose come routing, middleware, fluent, ecc. sono menzionati nelle sezioni seguenti.
|
||||
|
||||
### routes.swift
|
||||
### boot.swift
|
||||
|
||||
Il contenuto di `boot` può essere inserito nel metodo `configure` dal momento che ora accetta un'istanza di `Application`.
|
||||
|
||||
|
|
@ -212,13 +212,13 @@ Invece che registrare un `MiddlewareConfig` ai servizi, i middleware possono ess
|
|||
+ req.client.get("https://vapor.codes")
|
||||
```
|
||||
|
||||
Come Application, anche Request espone servizi come proprietà e metodi. È fortemente consigliato l'uso di servizi specifici alla Request da dentro le chiusure dei route handler.
|
||||
Come Application, anche Request espone servizi come proprietà e metodi. È fortemente consigliato l'uso di servizi specifici alla Request da dentro le closure dei route handler.
|
||||
|
||||
Questo nuovo pattern va a sostituire il vecchio pattern di `Container` e `Service` e `Config` che era usato in Vapor 3.
|
||||
|
||||
### Providers
|
||||
### Provider
|
||||
|
||||
I providers sono stati rimossi in Vapor 4. I providers erano usati per registrare servizi e configurazioni ai servizi. Ora i pacchetti possono estendere direttamente `Application` e `Request` per registrare servizi e configurazioni.
|
||||
I provider sono stati rimossi in Vapor 4. I provider erano usati per registrare servizi e configurazioni ai servizi. Ora i pacchetti possono estendere direttamente `Application` e `Request` per registrare servizi e configurazioni.
|
||||
|
||||
Diamo un'occhiata a come è configurato Leaf in Vapor 4.
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ Per usare Leaf, basta usare `app.leaf`.
|
|||
|
||||
### Ambiente
|
||||
|
||||
Si può accedere all'ambiente attuale (produzione, sviluppo ecc) tramite la proprietà `app.environment`.
|
||||
Si può accedere all'ambiente attuale (produzione, sviluppo, ecc.) tramite la proprietà `app.environment`.
|
||||
|
||||
### Servizi Personalizzati
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ app.storage[MyNumber.self] = 5
|
|||
print(app.storage[MyNumber.self]) // 5
|
||||
```
|
||||
|
||||
L'accsso a `Application.storage` può essere avvolto in una proprietà computata per rendere il codice più leggibile:
|
||||
L'accesso a `Application.storage` può essere avvolto in una proprietà computata per rendere il codice più leggibile:
|
||||
|
||||
```swift
|
||||
extension Application {
|
||||
|
|
@ -316,7 +316,7 @@ Vapor 4 utilizza le API asincrone di SwiftNIO direttamente senza fare l'overload
|
|||
|
||||
Il cambiamento più ovvio è che il typealias `Future` di `EventLoopFuture` è stato rimosso. Si può risolvere questo problema semplicemente usando "trova e sostituisci".
|
||||
|
||||
In più NIO non supporta il label `to:` che veniva usato da Vapor 3, che comunque dato il nuovo sistema di inferenza dei tipi di Swift 5.2, non è più necessario.
|
||||
In più NIO non supporta il label `to:` che veniva usato da Vapor 3, che comunque dato il nuovo sistema di inferenza dei tipi di Swift 5.2 non è più necessario.
|
||||
|
||||
```diff
|
||||
- futureA.map(to: String.self) { ... }
|
||||
|
|
@ -338,7 +338,7 @@ Il `flatMap` globale di Vapor 3 per combinare diversi futuri non è più disponi
|
|||
```diff
|
||||
- flatMap(futureA, futureB) { a, b in
|
||||
+ futureA.and(futureB).flatMap { (a, b) in
|
||||
// Do something with a and b.
|
||||
// Fai qualcosa con a e b.
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -346,7 +346,7 @@ Il `flatMap` globale di Vapor 3 per combinare diversi futuri non è più disponi
|
|||
|
||||
Molti metodi e proprietà che utilizzavano `Data` ora usano `ByteBuffer`, un tipo di storage di byte più potente e performante. Potete leggere di più su `ByteBuffer` nella [documentazione di SwiftNIO](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer).
|
||||
|
||||
Per convertire un `ByteBuffer` a `Data` si può usare:
|
||||
Per convertire un `ByteBuffer` in `Data` si può usare:
|
||||
|
||||
```swift
|
||||
Data(buffer.readableBytesView)
|
||||
|
|
@ -415,7 +415,7 @@ futureA.flatMapThrowing { a in
|
|||
|
||||
## Routing
|
||||
|
||||
Ora le routes sono registrate direttamente su Application.
|
||||
Ora le route sono registrate direttamente su Application.
|
||||
|
||||
```swift
|
||||
app.get("hello") { req in
|
||||
|
|
@ -423,9 +423,9 @@ app.get("hello") { req in
|
|||
}
|
||||
```
|
||||
|
||||
Ciò significa che non c'è più bisogno di registrare un router ai servizi. Basta passare l'istanza di `Application` al metodo `routes` e si può cominciare ad aggiungere endpoints. Tutti i metodi disponibili sul `RoutesBuilder` sono disponibili su `Application`.
|
||||
Ciò significa che non c'è più bisogno di registrare un router ai servizi. Basta passare l'istanza di `Application` al metodo `routes` e si può cominciare ad aggiungere endpoint. Tutti i metodi disponibili sul `RoutesBuilder` sono disponibili su `Application`.
|
||||
|
||||
### Content Sincrono
|
||||
### Contenuto Sincrono
|
||||
|
||||
La decodifica delle richieste è ora sincrona.
|
||||
|
||||
|
|
@ -458,7 +458,7 @@ In Vapor 4 gli URL sono divisi da virgole e non devono contenere `/`.
|
|||
|
||||
### Parametri di una route
|
||||
|
||||
Il protocollo `Parameter` è stato rimosso per promuovere l'uso di parametri chiamati esplicitamente. In questo modo si evitano problemi di parametri duplicati e il fetching non ordinato dei parametri nei middleware e nei gestori delle routes.
|
||||
Il protocollo `Parameter` è stato rimosso per promuovere l'uso di parametri chiamati esplicitamente. In questo modo si evitano problemi di parametri duplicati e il fetching non ordinato dei parametri nei middleware e nei gestori delle route.
|
||||
|
||||
```diff
|
||||
- router.get("planets", String.parameter) { req in
|
||||
|
|
@ -505,7 +505,7 @@ Ora l'API di Fluent è indipendente dal database su cui viene utilizzata. Basta
|
|||
+ import Fluent
|
||||
```
|
||||
|
||||
### Models
|
||||
### Modelli
|
||||
|
||||
I modelli utilizzano il protocollo `Model` e devono essere delle classi:
|
||||
|
||||
|
|
@ -604,7 +604,7 @@ Le tabelle pivot sono modelli normali che conformano a `Model` con due propriet
|
|||
|
||||
### Query
|
||||
|
||||
Si può accedere al contesto del database utilizzando `req.db` nei route handlers.
|
||||
Si può accedere al contesto del database utilizzando `req.db` nei route handler.
|
||||
|
||||
```diff
|
||||
- Planet.query(on: req)
|
||||
|
|
@ -620,7 +620,7 @@ Ora i key path ai campi hanno il prefisso `$` per specificare che si tratta del
|
|||
+ filter(\.$foo == ...)
|
||||
```
|
||||
|
||||
### Migrations
|
||||
### Migrazioni
|
||||
|
||||
Le migrazioni devono essere scritte manualmente e non si basano più sul concetto di reflection:
|
||||
|
||||
|
|
@ -645,19 +645,19 @@ I metodi `prepare` e `revert` non sono più statici.
|
|||
+ func prepare(on database: Database) -> EventLoopFuture<Void>
|
||||
```
|
||||
|
||||
La creazione di uno schema builder è fatta tramite un metodo su `Database`.
|
||||
La creazione di un costruttore di schema è fatta tramite un metodo su `Database`.
|
||||
|
||||
```diff
|
||||
- <#Database#>Database.create(Galaxy.self, on: conn) { builder in
|
||||
- // Use builder.
|
||||
- // Usa builder.
|
||||
- }
|
||||
+ var builder = database.schema("Galaxy")
|
||||
+ // Use builder.
|
||||
+ // Usa builder.
|
||||
```
|
||||
|
||||
I metodi `create`, `update` e `delete` sono metodi dello schema builder e assomigliano al funzionamento di un query builder.
|
||||
I metodi `create`, `update` e `delete` sono metodi del costruttore di schema e assomigliano al funzionamento di un costruttore di query.
|
||||
|
||||
La definizione dei campi è tipata tramite stringhe e segue il seguente pattern:
|
||||
La definizione dei campi è tipata tramite stringhe e usa il seguente pattern:
|
||||
|
||||
```swift
|
||||
field(<name>, <type>, <constraints>)
|
||||
|
|
@ -668,7 +668,7 @@ field(<name>, <type>, <constraints>)
|
|||
+ builder.field("name", .string, .required)
|
||||
```
|
||||
|
||||
La costruzione degli schemi può essere concatenata come un query builder:
|
||||
La costruzione degli schemi può essere concatenata come un costruttore di query:
|
||||
|
||||
```swift
|
||||
database.schema("Galaxy")
|
||||
|
|
@ -765,7 +765,7 @@ app.users.use { req in
|
|||
}
|
||||
```
|
||||
|
||||
Si può ora accedere alla repository nei route handlers con `req.users.all()` e si può facilmente sostituire la repository con una mock per i test. Basta creare un nuovo file `TestUserRepository`:
|
||||
Si può ora accedere alla repository nei route handler con `req.users.all()` e si può facilmente sostituire la repository con una simulata per i test. Basta creare un nuovo file `TestUserRepository`:
|
||||
```swift
|
||||
final class TestUserRepository: UserRepository {
|
||||
var users: [User]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
# Reindirizzando...
|
||||
|
||||
<meta http-equiv="refresh" content="0; url=https://legacy.docs.vapor.codes/">
|
||||
|
|
@ -1,5 +1,29 @@
|
|||
#!/usr/bin/swift
|
||||
|
||||
/*
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Copyright (c) 2023 Vapor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct SearchIndex: Codable {
|
||||
|
|
|
|||
23
mkdocs.yml
23
mkdocs.yml
|
|
@ -10,7 +10,7 @@ repo_url: http://github.com/vapor/vapor
|
|||
edit_uri: https://github.com/vapor/documentation/edit/main/docs/
|
||||
|
||||
# Copyright
|
||||
copyright: "Copyright © Vapor Community"
|
||||
copyright: "Vapor Documentation © 2023 by Vapor is licensed under CC BY-NC-SA 4.0"
|
||||
|
||||
# Configuration
|
||||
theme:
|
||||
|
|
@ -60,6 +60,8 @@ extra:
|
|||
link: https://discord.gg/vapor
|
||||
- icon: fontawesome/brands/github
|
||||
link: https://github.com/vapor
|
||||
- icon: fontawesome/brands/mastodon
|
||||
link: https://hachyderm.io/@codevapor
|
||||
|
||||
extra_css:
|
||||
- stylesheets/syntax.css
|
||||
|
|
@ -193,17 +195,19 @@ plugins:
|
|||
nav_translations:
|
||||
Advanced: Avancé
|
||||
Async: Asynchrone
|
||||
Authentication: Authentication
|
||||
Basis: Bases
|
||||
Authentication: Authentification
|
||||
Basics: Bases
|
||||
Commands: Commandes
|
||||
Content: Contenu
|
||||
Contributing: Contribuer
|
||||
Contributing Guide: Guide de la contribution
|
||||
Contributing Guide: Guide de contribution
|
||||
Controllers: Contrôleurs
|
||||
Custom Tags: Tags customisés
|
||||
Errors: Erreurs
|
||||
Deploy: Deployer
|
||||
Environment: Environement
|
||||
Files: Fichiers
|
||||
Folder Structure: Strucutre du Dossier
|
||||
Folder Structure: Structure du Dossier
|
||||
Getting Started: Commencer
|
||||
Hello, world: Bonjour, monde
|
||||
Install: Installer
|
||||
|
|
@ -214,9 +218,12 @@ plugins:
|
|||
Query: Requête
|
||||
Queues: Files d'attente
|
||||
Relataions: Relations
|
||||
Release Notes: Notes de Version
|
||||
Request: Requête
|
||||
Routing: Routage
|
||||
Schema: Schema
|
||||
Security: Securité
|
||||
Services: Sercvies
|
||||
Services: Services
|
||||
Sessions: Sessions
|
||||
Testing: Test
|
||||
Transactions: Transactions
|
||||
|
|
@ -274,6 +281,7 @@ plugins:
|
|||
Content: Contenuto
|
||||
Contributing: Contribuire
|
||||
Contributing Guide: Guida alla Contribuzione
|
||||
Controllers: Controller
|
||||
Crypto: Crittografia
|
||||
Custom Tags: Tag Personalizzati
|
||||
Deploy: Deploy
|
||||
|
|
@ -287,7 +295,7 @@ plugins:
|
|||
Install: Installazione
|
||||
JWT: JWT
|
||||
Leaf: Leaf
|
||||
Legacy Docs: Documentazione Legacy
|
||||
Legacy Docs: Documentazione Obsoleta
|
||||
Logging: Logging
|
||||
Middleware: Middleware
|
||||
Migrations: Migrazioni
|
||||
|
|
@ -298,6 +306,7 @@ plugins:
|
|||
Queues: Code
|
||||
Redis: Redis
|
||||
Relations: Relazioni
|
||||
Release Notes: Note sulla Versione
|
||||
Routing: Routing
|
||||
Schema: Schema
|
||||
Security: Sicurezza
|
||||
|
|
|
|||
|
|
@ -1,5 +1,29 @@
|
|||
#!/usr/bin/swift
|
||||
|
||||
/*
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Copyright (c) 2023 Vapor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
let basicDirectories = [
|
||||
|
|
@ -61,4 +85,4 @@ func createRedirect(directory: String, newDirectory: String) throws {
|
|||
let fileURL = URL(fileURLWithPath: "site/\(directory)/index.html")
|
||||
try FileManager.default.createDirectory(atPath: "site/\(directory)", withIntermediateDirectories: true, attributes: nil)
|
||||
try redirectString.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue