diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 065f7e14a7a..42fe2fca9f3 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,46 +3,46 @@ name: Deploy Website on: push: branches: - - main - - '*.*' + - main + - '*.*' jobs: deploy: runs-on: ubuntu-latest steps: - - - name: Checkout the website - uses: actions/checkout@v2 - with: - repository: api-platform/website - ref: main - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v2 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: Install deps - run: yarn install - - - name: Retrieve docs - run: bin/retrieve-documentation - - - name: Build website - env: - GITHUB_KEY: ${{ secrets.CONTRIBUTORS_GITHUB_TOKEN }} - run: yarn gatsby build - - - name: Deploy - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./public - cname: api-platform.com + - name: Checkout the website + uses: actions/checkout@v3 + with: + repository: api-platform/website + ref: main + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> "$GITHUB_OUTPUT" + + - uses: actions/cache@v3 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Install deps + run: yarn install + + - name: Retrieve docs + run: bin/retrieve-documentation + + - name: Build website + env: + GITHUB_KEY: ${{ secrets.CONTRIBUTORS_GITHUB_TOKEN }} + NODE_OPTIONS: --openssl-legacy-provider + run: yarn gatsby build + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./public + cname: api-platform.com diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bbf07cd1fa..a7901d1e280 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 0 @@ -24,7 +24,7 @@ jobs: DEFAULT_BRANCH: main GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} diff --git a/core/controllers.md b/core/controllers.md index 33145bcceec..10538c4576c 100644 --- a/core/controllers.md +++ b/core/controllers.md @@ -44,12 +44,9 @@ use Symfony\Component\HttpKernel\Attribute\AsController; #[AsController] class CreateBookPublication extends AbstractController { - private $bookPublishingHandler; - - public function __construct(BookPublishingHandler $bookPublishingHandler) - { - $this->bookPublishingHandler = $bookPublishingHandler; - } + public function __construct( + private BookPublishingHandler $bookPublishingHandler + ) {} public function __invoke(Book $book): Book { @@ -117,14 +114,15 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - post_publication: - class: ApiPlatform\Metadata\Post - method: POST - uriTemplate: /books/{id}/publication - controller: App\Controller\CreateBookPublication +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + post_publication: + class: ApiPlatform\Metadata\Post + method: POST + uriTemplate: /books/{id}/publication + controller: App\Controller\CreateBookPublication ``` ```xml @@ -188,14 +186,15 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - post_publication: - class: ApiPlatform\Metadata\Post - method: POST - uriTemplate: /books/{id}/publication - controller: ApiPlatform\Action\PlaceholderAction +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + post_publication: + class: ApiPlatform\Metadata\Post + method: POST + uriTemplate: /books/{id}/publication + controller: ApiPlatform\Action\PlaceholderAction ``` ```xml @@ -258,15 +257,16 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - post_publication: - class: ApiPlatform\Metadata\Get - uriTemplate: /books/{id}/publication - controller: App\Controller\CreateBookPublication - normalizationContext: - groups: ['publication'] +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + post_publication: + class: ApiPlatform\Metadata\Get + uriTemplate: /books/{id}/publication + controller: App\Controller\CreateBookPublication + normalizationContext: + groups: ['publication'] ``` ```xml @@ -329,14 +329,15 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - post_publication: - class: ApiPlatform\Metadata\Post - uriTemplate: /books/{id}/publication - controller: App\Controller\CreateBookPublication - read: false +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + post_publication: + class: ApiPlatform\Metadata\Post + uriTemplate: /books/{id}/publication + controller: App\Controller\CreateBookPublication + read: false ``` ```xml @@ -401,14 +402,15 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - post_publication: - class: ApiPlatform\Metadata\Post - routeName: book_post_publication - book_post_discontinuation: - class: ApiPlatform\Metadata\Post +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + post_publication: + class: ApiPlatform\Metadata\Post + routeName: book_post_publication + book_post_discontinuation: + class: ApiPlatform\Metadata\Post ``` ```xml diff --git a/core/dto.md b/core/dto.md index 17d7adaf8ed..f27168f3449 100644 --- a/core/dto.md +++ b/core/dto.md @@ -140,11 +140,12 @@ class Book {} ``` ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Post: - output: App\Dto\AnotherRepresentation - processor: App\State\BookRepresentationProcessor +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Post: + output: App\Dto\AnotherRepresentation + processor: App\State\BookRepresentationProcessor ``` ```xml diff --git a/core/extending-jsonld-context.md b/core/extending-jsonld-context.md index 64e1140b04c..bea0e172bfb 100644 --- a/core/extending-jsonld-context.md +++ b/core/extending-jsonld-context.md @@ -88,10 +88,11 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: - hydraContext: { foo: 'bar' } +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: + hydraContext: { foo: 'bar' } ``` ```xml diff --git a/core/filters.md b/core/filters.md index dca54bc8afd..81719e62739 100644 --- a/core/filters.md +++ b/core/filters.md @@ -71,11 +71,12 @@ to a Resource in two ways: ```yaml # api/config/api_platform/resources.yaml - App\Entity\Offer: - operations: - ApiPlatform\Metadata\GetCollection: - filters: ['offer.date_filter'] - # ... + resources: + App\Entity\Offer: + operations: + ApiPlatform\Metadata\GetCollection: + filters: ['offer.date_filter'] + # ... ``` ```xml @@ -1461,7 +1462,7 @@ Start by creating a custom attribute to mark restricted entities: ```php decorated)($context); - $schemas = $openApi->getComponents()->getSchemas(); - - $schemas['Token'] = new \ArrayObject([ - 'type' => 'object', - 'properties' => [ - 'token' => [ - 'type' => 'string', - 'readOnly' => true, - ], - ], - ]); - $schemas['Credentials'] = new \ArrayObject([ - 'type' => 'object', - 'properties' => [ - 'email' => [ - 'type' => 'string', - 'example' => 'johndoe@example.com', - ], - 'password' => [ - 'type' => 'string', - 'example' => 'apassword', - ], - ], - ]); - - $schemas = $openApi->getComponents()->getSecuritySchemes() ?? []; - $schemas['JWT'] = new \ArrayObject([ - 'type' => 'http', - 'scheme' => 'bearer', - 'bearerFormat' => 'JWT', - ]); - - $pathItem = new Model\PathItem( - ref: 'JWT Token', - post: new Model\Operation( - operationId: 'postCredentialsItem', - tags: ['Token'], - responses: [ - '200' => [ - 'description' => 'Get JWT token', - 'content' => [ - 'application/json' => [ - 'schema' => [ - '$ref' => '#/components/schemas/Token', - ], - ], - ], - ], - ], - summary: 'Get JWT token to login.', - requestBody: new Model\RequestBody( - description: 'Generate new JWT Token', - content: new \ArrayObject([ - 'application/json' => [ - 'schema' => [ - '$ref' => '#/components/schemas/Credentials', - ], - ], - ]), - ), - security: [], - ), - ); - $openApi->getPaths()->addPath('/auth', $pathItem); - - return $openApi; - } -} -``` - -And register this service in `config/services.yaml`: +If you need to modify the default configuration, you can do it in the dedicated configuration file: ```yaml -# api/config/services.yaml -services: +# config/packages/lexik_jwt_authentication.yaml +lexik_jwt_authentication: # ... - - App\OpenApi\JwtDecorator: - decorates: 'api_platform.openapi.factory' - arguments: ['@.inner'] + api_platform: + check_path: /auth + username_path: email + password_path: password ``` +You will see something like this in Swagger UI: + +![API Endpoint to retrieve JWT Token from SwaggerUI](images/jwt-token-swagger-ui.png) + ## Testing To test your authentication with `ApiTestCase`, you can write a method as below: diff --git a/core/messenger.md b/core/messenger.md index d44341fadfc..b70c8180362 100644 --- a/core/messenger.md +++ b/core/messenger.md @@ -51,16 +51,16 @@ final class Person ```yaml # api/config/api_platform/resources.yaml resources: - App\Entity\Person: - operations: - ApiPlatform\Metadata\Post: - status: 202 - messenger: true - output: false - ApiPlatform\Metadata\Get: - status: 404 - controller: ApiPlatform\Action\NotFoundAction - read: false + App\Entity\Person: + operations: + ApiPlatform\Metadata\Post: + status: 202 + messenger: true + output: false + ApiPlatform\Metadata\Get: + status: 404 + controller: ApiPlatform\Action\NotFoundAction + read: false ``` [/codeSelector] diff --git a/core/openapi.md b/core/openapi.md index 068e43c6df0..faac21c8878 100644 --- a/core/openapi.md +++ b/core/openapi.md @@ -161,25 +161,26 @@ class Product // The class name will be used to name exposed resources ``` ```yaml -# api/config/api_platform/resources.yaml -resources: +# api/config/api_platform/properties.yaml +properties: App\Entity\Product: - properties: name: - attributes: - openapi_context: - type: string - enum: ['one', 'two'] - example: one + attributes: + openapiContext: + type: string + enum: ['one', 'two'] + example: one timestamp: - attributes: - openapi_context: - type: string - format: date-time + attributes: + openapiContext: + type: string + format: date-time ``` ```xml + + - - - Create a rabbit picture - # Pop a great rabbit picture by color!! + - - - - - - object - - - - - string - - - - - string - - - - - - - - - - - + ![A great rabbit](https://rabbit.org/graphics/fun/netbunnies/jellybean1-brennan1.jpg)"> + + + + + + + + + object + + + + + string + + + + + string + + + + + + + + + + + + diff --git a/core/operations.md b/core/operations.md index ed420e64604..2c4f37f7b28 100644 --- a/core/operations.md +++ b/core/operations.md @@ -90,10 +90,11 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\GetCollection: ~ # nothing more to add if we want to keep the default controller - ApiPlatform\Metadata\Get: ~ +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\GetCollection: ~ # nothing more to add if we want to keep the default controller + ApiPlatform\Metadata\Get: ~ ``` ```xml @@ -143,12 +144,13 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\GetCollection: - method: GET - ApiPlatform\Metadata\Get: - method: GET +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\GetCollection: + method: GET + ApiPlatform\Metadata\Get: + method: GET ``` ```xml @@ -203,13 +205,14 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\GetCollection: ~ - ApiPlatform\Metadata\Get: - controller: ApiPlatform\Action\NotFoundAction - read: false - output: false +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\GetCollection: ~ + ApiPlatform\Metadata\Get: + controller: ApiPlatform\Action\NotFoundAction + read: false + output: false ``` ```xml @@ -272,21 +275,22 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Post: - uriTemplate: '/grimoire' - status: 301 - ApiPlatform\Metadata\Get: - uriTemplate: '/grimoire/{id}' - requirements: - id: '\d+' - defaults: - color: 'brown' - host: '{subdomain}.api-platform.com' - schemes: ['https'] - options: - my_option: 'my_option_value' +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Post: + uriTemplate: '/grimoire' + status: 301 + ApiPlatform\Metadata\Get: + uriTemplate: '/grimoire/{id}' + requirements: + id: '\d+' + defaults: + color: 'brown' + host: '{subdomain}.api-platform.com' + schemes: ['https'] + options: + my_option: 'my_option_value' ``` ```xml @@ -349,8 +353,9 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - routePrefix: /library +resources: + App\Entity\Book: + routePrefix: /library ``` ```xml @@ -367,81 +372,6 @@ App\Entity\Book: [/codeSelector] -API Platform will automatically map this `post_publication` operation to the route `book_post_publication`. Let's create a custom action -and its related route using annotations: - -```php - Book::class, - '_api_operation_name' => '_api_/books/{id}/publication_post', - ], - methods: ['POST'], - )] - public function __invoke(Book $book): Book - { - $this->bookPublishingHandler->handle($book); - - return $book; - } -} -``` - -It is mandatory to set `_api_resource_class` and `_api_operation_name`in the parameters of the route (`defaults` key). It allows API Platform to work with the Symfony routing system. - -Alternatively, you can also use a traditional Symfony controller and YAML or XML route declarations. The following example does -the exact same thing as the previous example: - -```php -handle($book); - } -} -``` - -```yaml -# api/config/routes.yaml -book_post_publication: - path: /books/{id}/publication - methods: ['POST'] - defaults: - _controller: App\Controller\BookController::createPublication - _api_resource_class: App\Entity\Book - _api_operation_name: post_publication -``` - ## Defining Which Operation to Use to Generate the IRI Using multiple operations on your resource, you may want to specify which operation to use to generate the IRI, instead diff --git a/core/pagination.md b/core/pagination.md index 20ca3cfdd2c..97314d95aaa 100644 --- a/core/pagination.md +++ b/core/pagination.md @@ -90,8 +90,63 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - paginationEnabled: false +resources: + App\Entity\Book: + paginationEnabled: false +``` +[/codeSelector] + +### Disabling the Pagination For a Specific Operation + +You can also disable an operation for a specific operation: + +[codeSelector] + +```php + + + + + + + + + + ``` [/codeSelector] diff --git a/core/security.md b/core/security.md index 34aa89253a6..d9ff89a0d4b 100644 --- a/core/security.md +++ b/core/security.md @@ -48,15 +48,16 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - security: 'is_granted("ROLE_USER")' - operations: - ApiPlatform\Metadata\GetCollection: ~ - ApiPlatform\Metadata\Post: - security: 'is_granted("ROLE_ADMIN")' - ApiPlatform\Metadata\Get: ~ - ApiPlatform\Metadata\Put: - security: 'is_granted("ROLE_ADMIN") or object.owner == user' +resources: + App\Entity\Book: + security: 'is_granted("ROLE_USER")' + operations: + ApiPlatform\Metadata\GetCollection: ~ + ApiPlatform\Metadata\Post: + security: 'is_granted("ROLE_ADMIN")' + ApiPlatform\Metadata\Get: ~ + ApiPlatform\Metadata\Put: + security: 'is_granted("ROLE_ADMIN") or object.owner == user' ``` [/codeSelector] @@ -139,12 +140,13 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - operations: - ApiPlatform\Metadata\Get: ~ - ApiPlatform\Metadata\GetCollectionPut: - securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)" - # ... +resources: + App\Entity\Book: + operations: + ApiPlatform\Metadata\Get: ~ + ApiPlatform\Metadata\GetCollectionPut: + securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)" + # ... ``` [/codeSelector] @@ -315,19 +317,20 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - security: 'is_granted("ROLE_USER")' - operations: - ApiPlatform\Metadata\Post: - security: 'is_granted("ROLE_ADMIN")' - securityMessage: 'Only admins can add books.' - ApiPlatform\Metadata\Get: - security: 'is_granted("ROLE_USER") and object.owner == user' - securityMessage: 'Sorry, but you are not the book owner.' - ApiPlatform\Metadata\Put: - securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)" - securityPostDenormalizeMessage: 'Sorry, but you are not the actual book owner.' - # ... +resources: + App\Entity\Book: + security: 'is_granted("ROLE_USER")' + operations: + ApiPlatform\Metadata\Post: + security: 'is_granted("ROLE_ADMIN")' + securityMessage: 'Only admins can add books.' + ApiPlatform\Metadata\Get: + security: 'is_granted("ROLE_USER") and object.owner == user' + securityMessage: 'Sorry, but you are not the book owner.' + ApiPlatform\Metadata\Put: + securityPostDenormalize: "is_granted('ROLE_ADMIN') or (object.owner == user and previous_object.owner == user)" + securityPostDenormalizeMessage: 'Sorry, but you are not the actual book owner.' + # ... ``` [/codeSelector] diff --git a/core/serialization.md b/core/serialization.md index c8d05b326d9..2ee8948197e 100644 --- a/core/serialization.md +++ b/core/serialization.md @@ -1074,6 +1074,8 @@ For ORM, it also supports [composite identifiers](https://www.doctrine-project.o If you are not using the Doctrine ORM or MongoDB ODM Provider, you must explicitly mark the identifier using the `identifier` attribute of the `ApiPlatform\Metadata\ApiProperty` annotation. For example: +[codeSelector] + ```php + + + + + +``` + +[/codeSelector] + In some cases, you will want to set the identifier of a resource from the client (e.g. a client-side generated UUID, or a slug). In such cases, you must make the identifier property a writable class property. Specifically, to use client-generated IDs, you must do the following: diff --git a/core/subresources.md b/core/subresources.md index 8fe8fa94516..ed0a332d67c 100644 --- a/core/subresources.md +++ b/core/subresources.md @@ -249,7 +249,7 @@ Now let's add the Company class: ```php token; } - $response = static::createClient()->request('POST', '/login', ['body' => $body ?: [ + $response = static::createClient()->request('POST', '/login', ['json' => $body ?: [ 'username' => 'admin@example.com', 'password' => '$3cr3t', ]]); $this->assertResponseIsSuccessful(); - $data = json_decode($response->getContent()); - $this->token = $data->access_token; + $data = $response->toArray(); + $this->token = $data['token']; - return $data->access_token; + return $data['token']; } } ``` diff --git a/core/url-generation-strategy.md b/core/url-generation-strategy.md index 8c4bb8a9f46..73c6ae404e6 100644 --- a/core/url-generation-strategy.md +++ b/core/url-generation-strategy.md @@ -51,8 +51,9 @@ class Book ```yaml # api/config/api_platform/resources.yaml -App\Entity\Book: - urlGenerationStrategy: !php/const ApiPlatform\Api\UrlGeneratorInterface::ABS_URL +resources: + App\Entity\Book: + urlGenerationStrategy: !php/const ApiPlatform\Api\UrlGeneratorInterface::ABS_URL ``` ```xml diff --git a/core/user.md b/core/user.md index 0861e06c6ac..f65ed7825d9 100644 --- a/core/user.md +++ b/core/user.md @@ -33,7 +33,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ new GetCollection(), - new Post(processor: UserPasswordHasher::class), + new Post(processor: UserPasswordHasher::class, validationContext: ['groups' => ['Default', 'user:create']]), new Get(), new Put(processor: UserPasswordHasher::class), new Patch(processor: UserPasswordHasher::class), @@ -106,9 +106,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this->plainPassword; } - public function setPlainPassword(?string $painPassword): self + public function setPlainPassword(?string $plainPassword): self { - $this->plainPassword = $painPassword; + $this->plainPassword = $plainPassword; return $this; } diff --git a/create-client/images/nextjs/create-client-nextjs-list.png b/create-client/images/nextjs/create-client-nextjs-list.png index 467e4d4576f..23179dfd32b 100644 Binary files a/create-client/images/nextjs/create-client-nextjs-list.png and b/create-client/images/nextjs/create-client-nextjs-list.png differ diff --git a/create-client/images/nextjs/create-client-nextjs-show.png b/create-client/images/nextjs/create-client-nextjs-show.png index 9983f70dfbe..f52dd54007c 100644 Binary files a/create-client/images/nextjs/create-client-nextjs-show.png and b/create-client/images/nextjs/create-client-nextjs-show.png differ diff --git a/create-client/images/nuxt/create-client-nuxt-edit.png b/create-client/images/nuxt/create-client-nuxt-edit.png new file mode 100644 index 00000000000..3b86f90eb57 Binary files /dev/null and b/create-client/images/nuxt/create-client-nuxt-edit.png differ diff --git a/create-client/images/nuxt/create-client-nuxt-list.png b/create-client/images/nuxt/create-client-nuxt-list.png new file mode 100644 index 00000000000..4fe5e926bac Binary files /dev/null and b/create-client/images/nuxt/create-client-nuxt-list.png differ diff --git a/create-client/images/nuxtjs/create-client-nuxtjs-edit.png b/create-client/images/nuxtjs/create-client-nuxtjs-edit.png deleted file mode 100644 index a6c4d96e443..00000000000 Binary files a/create-client/images/nuxtjs/create-client-nuxtjs-edit.png and /dev/null differ diff --git a/create-client/images/nuxtjs/create-client-nuxtjs-list.png b/create-client/images/nuxtjs/create-client-nuxtjs-list.png deleted file mode 100644 index da095d5c974..00000000000 Binary files a/create-client/images/nuxtjs/create-client-nuxtjs-list.png and /dev/null differ diff --git a/create-client/index.md b/create-client/index.md index 19b6019f21b..fac74db7971 100644 --- a/create-client/index.md +++ b/create-client/index.md @@ -10,7 +10,7 @@ and native mobile apps from APIs supporting the [Hydra](http://www.hydra-cg.com/ It is able to generate apps using the following frontend stacks: - [Next.js](nextjs.md) -- [Nuxt.js](nuxtjs.md) +- [Nuxt](nuxt.md) - [Quasar](quasar.md) - [Vuetify](vuetify.md) - [React](react.md) diff --git a/create-client/nuxt.md b/create-client/nuxt.md new file mode 100644 index 00000000000..4f80abdaee3 --- /dev/null +++ b/create-client/nuxt.md @@ -0,0 +1,102 @@ +# Nuxt Generator + +Bootstrap a [Nuxt 3](https://nuxt.com/) application: + +```console +npx nuxi init my-app +cd my-app +``` + +Install the required dependencies: + +```console +yarn add dayjs @pinia/nuxt qs @types/qs +``` + +To generate the code you need for a given resource, run the following command: + +```console +yarn create @api-platform/client https://demo.api-platform.com . --generator nuxt --resource foo +``` + +Replace the URL with the entrypoint of your Hydra-enabled API. You can also use an OpenAPI documentation with `https://demo.api-platform.com/docs.json` and `-f openapi3`. + +Omit the resource flag to generate files for all resource types exposed by the API. + +Add Pinia module in `nuxt.config.ts`: + +```typescript +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + // ... + modules: ["@pinia/nuxt"], + // ... +}); +``` + +Delete `app.vue` as it will prevent Nuxt router to work correctly. + +Optionally, install Tailwind to get an app that looks good: + +```console +yarn add -D tailwindcss postcss autoprefixer +yarn tailwindcss init -p +``` + +Add this code in `nuxt.config.ts`: + +```typescript +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + // ... + css: ['~/assets/css/main.css'], + postcss: { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + }, + // ... +}); +``` + +And this code in `tailwind.config.js`: + +```javascript +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./components/**/*.{js,vue,ts}", + "./layouts/**/*.vue", + "./pages/**/*.vue", + "./plugins/**/*.{js,ts}", + "./nuxt.config.{js,ts}", + "./app.vue", + ], + theme: { + extend: {}, + }, + plugins: [], +} +``` + +Create the file `assets/css/main.css` and add this code in it: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +You can launch the server with: + +```console +yarn dev -o +```` + +Go to `https://localhost:3000/books/` to start using your app. + +## Screenshots + +![List](images/nuxt/create-client-nuxt-list.png) +![Edit](images/nuxt/create-client-nuxt-edit.png) diff --git a/create-client/nuxtjs.md b/create-client/nuxtjs.md deleted file mode 100644 index 816e58ddb6a..00000000000 --- a/create-client/nuxtjs.md +++ /dev/null @@ -1,152 +0,0 @@ -# Nuxt.js Generator - -The Nuxt.js generator scaffolds components for server-side rendered (SSR) applications using [Nuxt.js](https://nuxtjs.org/) and [Vuetify](https://vuetifyjs.com/). - -## Install - -### Nuxt - -Create a [Nuxt.js application](https://nuxtjs.org/guides/get-started/installation#using-create-nuxt-app). - -```console -npm init nuxt-app your-app-name -``` - -It will ask you some questions, you can use these answers: - -```console -Project name: your-app-name -Programming language: JavaScript -Package manager: NPM -UI framework: Vuetify.js -Nuxt.js modules: None -Linting tools: Prettier, Lint staged files -Testing framework: None -Rendering mode: Single Page App -Deployment target: Static (Static/JAMStack hosting) -``` - -### Installing the Generator Dependencies - -Install required dependencies: - -```console -npm install moment lodash vue-i18n vuelidate vuex-map-fields nuxt-i18n -npm install --dev @nuxtjs/vuetify @nuxtjs/fontawesome -``` - -## Updating Nuxt Config - -Update your `nuxt.config.js` with following: - -```javascript - buildModules: [ - // ... - '@nuxtjs/vuetify', - '@nuxtjs/fontawesome', - 'nuxt-i18n' - ], - // ... - // to avoid name conflicts in generators - components: false, -``` - -## Generating Routes - -```console -npm init @api-platform/client https://demo.api-platform.com . -- --generator nuxt -``` - -Replace the URL by the entrypoint of your Hydra-enabled API. -You can also use an OpenAPI documentation with `-f openapi3`. - -**Note:** Omit the resource flag to generate files for all resource types exposed by the API. - -## Updating Default Layout - -Update your `layouts/default.vue` with following: - -```vue - - - -``` - -## Starting the Project - -You can launch the server with: - -```console -npm run dev -```` - -Go to `https://localhost:3000/books/` to start using your app. - -## Screenshots - -![List](images/nuxtjs/create-client-nuxtjs-list.png) -![Edit](images/nuxtjs/create-client-nuxtjs-edit.png) diff --git a/create-client/vuetify.md b/create-client/vuetify.md index dacbba0af41..a54fb760869 100644 --- a/create-client/vuetify.md +++ b/create-client/vuetify.md @@ -1,269 +1,64 @@ # Vuetify Generator -## Install With Docker - -If you use the API Platform distribution with Docker, first you have to add the [Vue CLI](https://cli.vuejs.org/guide/) to the Docker image. - -In the `dev` stage of `pwa/Dockerfile`, add this line: - -```dockerfile -RUN pnpm install -g @vue/cli @vue/cli-service-global -``` - -Then, rebuild your containers. - -Delete the content of the `pwa\` directory (the distribution comes with a prebuilt Next.js app). - -Create a new Vue App and install vuetify and other vue packages: - -```console -docker compose exec pwa \ - vue create -d . -docker compose exec pwa \ - vue add vuetify -docker compose exec pwa \ - pnpm install router lodash moment vue-i18n vue-router vuelidate vuex vuex-map-fields -``` - -Update the entrypoint: - -```javascript -// client/src/config/entrypoint.js -export const ENTRYPOINT = 'https://localhost:8443'; -``` - -Update the scripts part of the new `package.json`: - -```json - "scripts": { - "build": "vue-cli-service build", - "lint": "vue-cli-service lint", - "start": "vue-cli-service serve --port 3000 --https" - }, -``` - -Rebuild the docker containers again to install the Vue App and start the vue server. - -Generate the vuetify components with the following command: - -```console -docker compose exec pwa \ - pnpm create @api-platform/client --generator vuetify --resource book -``` - -Omit the resource flag to generate files for all resource types exposed by the API. - -Continue by [generating the VueJS Web App](#generating-the-vuejs-web-app). - -## Install Without Docker - -Create a Vuetify application using -[Vue CLI 3](https://cli.vuejs.org/guide/): +Bootstrap a Vuetify 3 application using `create-vuetify`: ```console -vue create -d vuetify-app -cd vuetify-app -vue add vuetify +npm init vuetify -- --typescript --preset essentials +cd my-app ``` Install the required dependencies: ```console -npm install router lodash moment vue-i18n vue-router vuelidate vuex vuex-map-fields +npm install dayjs qs @types/qs vue-i18n ``` -In the app directory, generate the files for the resource you want: +To generate all the code you need for a given resource run the following command: ```console -npm init @api-platform/client https://demo.api-platform.com src/ -- --generator vuetify +npm init @api-platform/client https://demo.api-platform.com src/ -- --generator vuetify --resource book ``` Replace the URL with the entrypoint of your Hydra-enabled API. -You can also use an OpenAPI documentation with `-f openapi3`. +You can also use an OpenAPI documentation with `https://demo.api-platform.com/docs.json` and `-f openapi3`. Omit the resource flag to generate files for all resource types exposed by the API. -## Generating the VueJS Web App - -The code is ready to be executed! Register the generated routes: +**Note:** Make sure to follow the result indications of the command to register the routes and the translations. -```javascript -// src/router/index.js -import Vue from 'vue'; -import VueRouter from 'vue-router'; -import bookRoutes from './book'; -import reviewRoutes from './review'; +Then add this import in `src/plugins/vuetify.ts`: -Vue.use(VueRouter); - -export default new VueRouter({ - mode: 'history', - routes: [bookRoutes, reviewRoutes] -}); +```typescript +// src/plugins/vuetify.ts +import { VDataTableServer } from "vuetify/labs/VDataTable" ``` -Add the modules to the store: - -```javascript -// src/store/index.js -import Vue from 'vue'; -import Vuex from 'vuex'; -import makeCrudModule from './modules/crud'; -import notifications from './modules/notifications'; -import bookService from '../services/book'; -import reviewService from '../services/review'; - -Vue.use(Vuex); +In the same file replace the export with: -const store = new Vuex.Store({ - modules: { - notifications, - book: makeCrudModule({ - service: bookService - }), - review: makeCrudModule({ - service: reviewService - }) +```typescript +// src/plugins/vuetify.ts +export default createVuetify({ + components: { + VDataTableServer, }, - strict: process.env.NODE_ENV !== 'production' }); - -export default store; ``` -Update the `src/plugins/vuetify.js` file with the following: - -```javascript -// src/plugins/vuetify.js -import Vue from 'vue'; -import Vuetify from 'vuetify/lib'; +In `src/plugins/index.ts` add this import: -Vue.use(Vuetify); - -const opts = { - icons: { - iconfont: 'mdi' - } -}; - -export default new Vuetify(opts); +```typescript +// src/plugins/index.ts +import i18n from "@/plugins/i18n" ``` -The generator comes with an i18n feature to allow quick translations of some labels in the generated code, to make it -work, you need to create this file: - -```javascript -// src/i18n.js -import Vue from 'vue'; -import VueI18n from 'vue-i18n'; -import messages from './locales/en'; - -Vue.use(VueI18n); - -export default new VueI18n({ - locale: process.env.VUE_APP_I18N_LOCALE || 'en', - fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', - messages: { - en: messages - } -}); -``` +In the same file add `.use(i18n)` chained with the other `use()` functions. -Update your `App.vue`: +You can launch the server with: -```vue - - - - +```console +npm run dev ``` -To finish, update your `main.js`: - -```javascript -// main.js -import Vue from 'vue'; -import Vuelidate from 'vuelidate'; -import App from './App.vue'; -import vuetify from './plugins/vuetify'; - -import store from './store'; -import router from './router'; -import i18n from './i18n'; - -Vue.config.productionTip = false; - -Vue.use(Vuelidate); - -new Vue({ - i18n, - router, - store, - vuetify, - render: h => h(App) -}).$mount('#app'); -``` +Go to `http://localhost:3000/books/` to start using your app. -Go to `https://localhost:8000/books/` to start using your app. +**Note:** In order to Mercure to work with the demo, you have to use the port 3000. diff --git a/deployment/kubernetes.md b/deployment/kubernetes.md index 34ad2b79aef..e18bcb88dde 100644 --- a/deployment/kubernetes.md +++ b/deployment/kubernetes.md @@ -36,9 +36,9 @@ If you do not have gcloud yet, install it with these command. Versioning: The 0.1.0 is the version. This value should be the same as the attribute `appVersion` in `Chart.yaml`. Infos for [Google Container pulling and pushing](https://cloud.google.com/container-registry/docs/pushing-and-pulling) - docker build -t gcr.io/test-api-platform/php:0.1.0 -t gcr.io/test-api-platform/php:latest api --target api_platform_php - docker build -t gcr.io/test-api-platform/caddy:0.1.0 -t gcr.io/test-api-platform/caddy:latest api --target api_platform_caddy - docker build -t gcr.io/test-api-platform/pwa:0.1.0 -t gcr.io/test-api-platform/pwa:latest pwa --target api_platform_pwa_prod + docker build -t gcr.io/test-api-platform/php:0.1.0 -t gcr.io/test-api-platform/php:latest api --target app_php + docker build -t gcr.io/test-api-platform/caddy:0.1.0 -t gcr.io/test-api-platform/caddy:latest api --target app_caddy + docker build -t gcr.io/test-api-platform/pwa:0.1.0 -t gcr.io/test-api-platform/pwa:latest pwa --target prod #### 2. Push your images to your Docker registry diff --git a/distribution/index.md b/distribution/index.md index 39c90111ad1..93e628a5ad2 100644 --- a/distribution/index.md +++ b/distribution/index.md @@ -20,7 +20,7 @@ The easiest and most powerful way to get started is [to download the API Platfor * the API skeleton, including [the Core library](../core/index.md), [the Symfony framework](https://symfony.com/) ([optional](../core/bootstrap.md)) and [the Doctrine ORM](https://www.doctrine-project.org/projects/orm.html) ([optional](../core/extending.md)) * [the client scaffolding tool](../create-client/) to generate [Next.js](../create-client/ -) web applications from the API documentation ([Nuxt.js](https://nuxtjs.org/), [Vue](https://vuejs.org/), [Create React App](https://reactjs.org), [React Native](https://facebook.github.io/react-native/), [Quasar](https://quasar.dev/) and [Vuetify](https://vuetifyjs.com/) are also supported) +) web applications from the API documentation ([Nuxt](https://nuxt.com/), [Vue](https://vuejs.org/), [Create React App](https://reactjs.org), [React Native](https://facebook.github.io/react-native/), [Quasar](https://quasar.dev/) and [Vuetify](https://vuetifyjs.com/) are also supported) * [a beautiful admin interface](../admin/), built on top of React Admin, dynamically created by parsing the API documentation * all you need to [create real-time and async APIs using the Mercure protocol](../core/mercure.md) * a [Docker](../deployment/docker-compose.md) definition to start a working development environment in a single command, providing containers for the API and the Next.js web application diff --git a/extra/releases.md b/extra/releases.md index 065cdc1e1ea..1c78a45d218 100644 --- a/extra/releases.md +++ b/extra/releases.md @@ -6,7 +6,7 @@ A new minor version is released every six months, and a new major version is rel For example: - version 3.0 has been released on 15 September, 2022; -- version 3.1 will be released on March, 2023; +- version 3.1 has been released on 23 January, 2023; - version 3.2 will be released on September, 2023; - version 3.3 will be released on March, 2024; - versions 3.4 and 4.0 will be released on September, 2024. @@ -15,8 +15,8 @@ For example: 3 versions are maintained at the same time: -* **stable** (currently the **3.0** branch): regular bugfixes are integrated in this version -* **old-stable** (currently **2.7** branch): [security fixes](security.md) are integrated in this version, regular bugfixes are **not** backported in it +* **stable** (currently the **3.1** branch): regular bugfixes are integrated in this version +* **old-stable** (currently **3.0** and **2.7** branches): [security fixes](security.md) are integrated in this version, regular bugfixes are **not** backported in it * **development** (**main** branch): new features target this branch Older versions (1.x, 2.6...) **are not maintained**. If you still use them, you must upgrade as soon as possible. diff --git a/outline.yaml b/outline.yaml index b4854a5dafd..58621103d90 100644 --- a/outline.yaml +++ b/outline.yaml @@ -82,7 +82,7 @@ chapters: items: - index - nextjs - - nuxtjs + - nuxt - vuetify - quasar - react