From 2ba6840ac3326d2722994a4f13971df78535651f Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:32:06 +0000 Subject: [PATCH 001/152] Allow path to be null and eager load relations --- src/Core/Routes/Actions/SearchForRoute.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index 6e2878a8f..ad883c92a 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -30,7 +30,7 @@ public function rules(): array { return [ 'slug' => 'required|string', - 'path' => 'string', + 'path' => 'nullable|string', ]; } @@ -41,7 +41,7 @@ public function rules(): array */ public function handle() { - $query = Route::whereSlug($this->slug); + $query = Route::whereSlug($this->slug)->with($this->resolveEagerRelations()); if ($this->path) { $query->wherePath($this->path); From 379f412298f82cc9f87dfe97e02562179b67e5d1 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:32:16 +0000 Subject: [PATCH 002/152] Allow null on basket id --- src/Http/Requests/Baskets/CreateRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Requests/Baskets/CreateRequest.php b/src/Http/Requests/Baskets/CreateRequest.php index 79b349017..1434df208 100644 --- a/src/Http/Requests/Baskets/CreateRequest.php +++ b/src/Http/Requests/Baskets/CreateRequest.php @@ -27,7 +27,7 @@ public function rules() { $rules = [ 'variants' => 'array', - 'basket_id' => 'hashid_is_valid:baskets', + 'basket_id' => 'nullable|hashid_is_valid:baskets', ]; $variants = GetCandy::productVariants()->getByHashedIds( From e3ac284bb8e7f4ced08a90e509edad8ea5659760 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:33:06 +0000 Subject: [PATCH 003/152] =?UTF-8?q?Don=E2=80=99t=20try=20and=20parse=20opt?= =?UTF-8?q?ion=20json=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/Products/Models/ProductVariant.php | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index c83f5cc73..d0ea5452a 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -96,18 +96,18 @@ public function getNameAttribute() public function getOptionsAttribute($val) { - $values = []; - $option_data = $this->product ? $this->product->option_data : []; - - foreach (json_decode($val, true) as $option => $value) { - if (! empty($data = $option_data[$option])) { - $values[$option] = $data['options'][$value]['values'] ?? [ - 'en' => null, - ]; - } - } - - return $values; + // $values = []; + // $option_data = $this->product ? $this->product->option_data : []; + + // foreach (json_decode($val, true) as $option => $value) { + // if (! empty($data = $option_data[$option])) { + // $values[$option] = $data['options'][$value]['values'] ?? [ + // 'en' => null, + // ]; + // } + // } + + return json_decode($val, true); } public function setOptionsAttribute($val) From b8ffa92736cf161406ecdb0eb10b2850afd6d902 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:33:17 +0000 Subject: [PATCH 004/152] Add create basket to spec --- openapi/baskets/paths/baskets.yaml | 36 +++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/openapi/baskets/paths/baskets.yaml b/openapi/baskets/paths/baskets.yaml index 7a5f1ce95..7a1ca206c 100644 --- a/openapi/baskets/paths/baskets.yaml +++ b/openapi/baskets/paths/baskets.yaml @@ -11,21 +11,21 @@ get: description: Get a paginated list of baskets tags: - Baskets -# post: -# summary: Create Basket -# operationId: post-baskets -# responses: -# '200': -# description: OK -# content: -# application/json: -# schema: -# $ref: '../responses/BasketResponse.yaml' -# requestBody: -# content: -# application/json: -# schema: -# $ref: '../requests/CreateBasketBody.yaml' -# tags: -# - Baskets -# description: '' +post: + summary: Create Basket + operationId: post-baskets + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/BasketResponse.yaml' + requestBody: + content: + application/json: + schema: + $ref: '../requests/CreateBasketBody.yaml' + tags: + - Baskets + description: '' From 39c06907790cc6d94ed2ebc0c22d793937ff526d Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:33:55 +0000 Subject: [PATCH 005/152] =?UTF-8?q?Don=E2=80=99t=20parse=20and/or=20sort?= =?UTF-8?q?=20product=20option=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/Resources/Products/ProductResource.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Http/Resources/Products/ProductResource.php b/src/Http/Resources/Products/ProductResource.php index d914cb987..7dfaa605f 100644 --- a/src/Http/Resources/Products/ProductResource.php +++ b/src/Http/Resources/Products/ProductResource.php @@ -23,7 +23,7 @@ public function payload() return [ 'id' => $this->encoded_id, 'drafted_at' => $this->drafted_at, - 'option_data' => $this->parseOptionData($this->option_data), + 'option_data' => $this->option_data, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; @@ -58,18 +58,7 @@ public function includes() 'customer_groups' => new CustomerGroupCollection($this->whenLoaded('customerGroups')), ]; } - - protected function parseOptionData($data) - { - $data = $this->sortOptions($data); - foreach ($data as $optionKey => $option) { - $sorted = $this->sortOptions($option['options']); - $data[$optionKey]['options'] = $sorted; - } - - return $data; - } - + protected function sortOptions($options) { $options = $options ?? []; From c09cc069bb95112a3bea9329256048c89f0f9d40 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 14 Dec 2020 08:34:26 +0000 Subject: [PATCH 006/152] Merge in master --- .github/workflows/laravel.yml | 10 +- ...30742_add_addressable_to_address_table.php | 2 +- openapi/.gitignore | 1 + openapi/account/paths/account.password.yaml | 4 +- openapi/openapi-full.yaml | 8766 ----------------- openapi/openapi.yaml | 245 +- openapi/users/requests/CreateUserBody.yaml | 8 +- .../Addresses/Actions/UpdateAddressAction.php | 3 +- src/Core/Addresses/Models/Address.php | 1 - .../Attributes/Actions/FetchAttribute.php | 1 - .../Actions/FetchFilterableAttributes.php | 1 - .../Categories/Services/CategoryService.php | 2 +- .../Services/CollectionService.php | 4 +- src/Core/Customers/Actions/CreateCustomer.php | 8 +- src/Core/Customers/Models/Customer.php | 2 +- .../Actions/FetchLanguagesAction.php | 1 + .../Services/ProductCategoryService.php | 2 +- src/Core/Search/Actions/FetchSearchedIds.php | 6 +- src/Core/Search/Actions/IndexDocuments.php | 1 + src/Core/Search/Actions/IndexObjects.php | 2 +- src/Core/Search/Actions/Search.php | 3 +- .../Commands/IndexCategoriesCommand.php | 10 +- .../Search/Commands/IndexProductsCommand.php | 12 +- .../Search/Commands/ScoreProductsCommand.php | 4 +- .../Search/Drivers/AbstractSearchDriver.php | 17 +- .../Actions/AbstractDocumentAction.php | 5 +- .../Elasticsearch/Actions/FetchIndex.php | 4 +- .../Actions/FetchProductMapping.php | 2 +- .../Elasticsearch/Actions/IndexCategories.php | 4 +- .../Elasticsearch/Actions/IndexProducts.php | 4 +- .../Actions/Searching/FetchAggregations.php | 6 +- .../Actions/Searching/FetchFilters.php | 8 +- .../Actions/Searching/FetchTerm.php | 3 +- .../Actions/Searching/Search.php | 29 +- .../Actions/Searching/SetSorting.php | 1 - .../Elasticsearch/Actions/SetIndexLive.php | 4 +- .../Drivers/Elasticsearch/Elasticsearch.php | 19 +- .../Events/IndexingCompleteEvent.php | 2 +- .../Filters/CustomerGroupFilter.php | 6 +- .../Elasticsearch/Filters/TextFilter.php | 2 +- .../Search/Drivers/Elasticsearch/Index.php | 3 +- .../Search/Indexables/AbstractIndexable.php | 2 + .../Search/Listeners/IndexObjectListener.php | 2 +- src/Core/Search/Providers/Elastic/Elastic.php | 3 +- src/Core/Search/Providers/Elastic/Indexer.php | 8 +- .../Providers/Elastic/Types/BaseType.php | 8 +- src/Core/Search/SearchManager.php | 2 +- src/Core/Traits/HasCandy.php | 1 - src/Core/Users/Actions/FetchUserFields.php | 4 +- src/Core/Users/Actions/UpdateUser.php | 2 +- .../Products/ProductController.php | 20 +- .../Products/ProductRouteController.php | 1 + .../Shipping/ShippingMethodController.php | 1 + .../ProductVariants/UpdateRequest.php | 4 +- src/Http/Resources/AbstractResource.php | 4 +- .../ActivityLog/ActivityResource.php | 2 +- src/Http/Resources/Baskets/BasketResource.php | 4 +- src/Http/Resources/Orders/OrderResource.php | 4 +- .../Products/ProductCustomerPriceResource.php | 2 +- .../Products/ProductTierResource.php | 2 +- .../Resources/Versioning/VersionResource.php | 4 +- src/Http/Validators/HashidValidator.php | 1 - src/Installer/Runners/PreflightRunner.php | 2 - src/Providers/SearchServiceProvider.php | 6 +- src/Providers/ShippingServiceProvider.php | 8 +- 65 files changed, 274 insertions(+), 9041 deletions(-) delete mode 100644 openapi/openapi-full.yaml diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml index 0b01e970f..bcf9a1feb 100644 --- a/.github/workflows/laravel.yml +++ b/.github/workflows/laravel.yml @@ -1,5 +1,4 @@ name: Test Runner - on: push: branches: @@ -15,14 +14,19 @@ on: - release/* jobs: laravel-tests: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - uses: nanasess/setup-php@master with: php-version: '7.3' + - uses: actions/setup-node@v2-beta + with: + node-version: '12' + - name: 'Install Speccy' + run: npm install -g speccy + - name: 'Build OpenAPI Spec' + run: speccy resolve openapi/openapi.yaml -o openapi/openapi-full.yaml - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - name: Execute tests (Unit and Feature tests) via PHPUnit diff --git a/database/migrations/2020_11_13_130742_add_addressable_to_address_table.php b/database/migrations/2020_11_13_130742_add_addressable_to_address_table.php index 378eeff66..a6194b69e 100644 --- a/database/migrations/2020_11_13_130742_add_addressable_to_address_table.php +++ b/database/migrations/2020_11_13_130742_add_addressable_to_address_table.php @@ -3,8 +3,8 @@ use GetCandy\Api\Core\Addresses\Models\Address; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\Schema; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Schema; class AddAddressableToAddressTable extends Migration { diff --git a/openapi/.gitignore b/openapi/.gitignore index e69de29bb..43137bf9e 100644 --- a/openapi/.gitignore +++ b/openapi/.gitignore @@ -0,0 +1 @@ +openapi-full.yaml \ No newline at end of file diff --git a/openapi/account/paths/account.password.yaml b/openapi/account/paths/account.password.yaml index ef2af4b46..c8053ba9c 100644 --- a/openapi/account/paths/account.password.yaml +++ b/openapi/account/paths/account.password.yaml @@ -1,7 +1,7 @@ post: - summary: Reset password + summary: Reset password tags: - - Users + - Account responses: '200': description: OK diff --git a/openapi/openapi-full.yaml b/openapi/openapi-full.yaml deleted file mode 100644 index 744e83fef..000000000 --- a/openapi/openapi-full.yaml +++ /dev/null @@ -1,8766 +0,0 @@ -openapi: 3.0.0 -x-samples-languages: - - curl - - node - - php - - javascript - - python -info: - title: GetCandy - contact: - name: GetCandy - url: https://getcandy.io - email: support@getcandy.io - version: 1.0.0 - license: - name: MIT - description: The GetCandy API -servers: - - url: http://localhost:8000/api/v1 -paths: - /addresses: - post: - summary: Create new address - operationId: post-addresses - responses: - "201": - description: Created - content: - application/json: - schema: - title: AddressResponse - type: object - properties: - data: - $ref: "#/paths/~1orders~1%7BorderId%7D~1shipping~1address/put/requestBody/content/application~1json/schema" - "401": - description: Unauthorized - "422": - description: Unprocessable Entity - tags: - - Addresses - description: This endpoint allows you to create a new address - requestBody: - content: - multipart/form-data: - schema: - title: CreateAddressBody - type: object - description: "" - properties: - salutation: - type: string - firstname: - type: string - lastname: - type: string - company_name: - type: string - email: - type: string - phone: - type: string - address: - type: string - address_two: - type: string - address_three: - type: string - city: - type: string - state: - type: string - postal_code: - type: string - country_id: - type: string - shipping: - type: boolean - user_id: - type: string - customer_id: - type: string - billing: - type: boolean - default: - type: boolean - last_used_at: - type: string - delivery_instructions: - type: string - meta: - type: array - items: {} - required: - - firstname - - lastname - - address - - city - - state - - postal_code - - country_id - x-tags: - - Addresses - description: "" - "/addresses/{addressId}": - put: - summary: Update existing address - operationId: put-addresses - responses: - "200": - description: Created - content: - application/json: - schema: - $ref: "#/paths/~1addresses/post/responses/201/content/application~1json/schema" - "401": - description: Unauthorized - "422": - description: Unprocessable Entity - tags: - - Addresses - description: This endpoint allows you to update an existing address - requestBody: - content: - application/javascript: - schema: - title: UpdateAddressBody - type: object - x-tags: - - Addresses - properties: - salutation: - type: string - firstname: - type: string - lastname: - type: string - company_name: - type: string - email: - type: string - phone: - type: string - address: - type: string - address_two: - type: string - address_three: - type: string - city: - type: string - state: - type: string - postal_code: - type: string - country_id: - type: string - shipping: - type: boolean - billing: - type: boolean - default: - type: boolean - last_used_at: - type: string - delivery_instructions: - type: string - meta: - type: array - items: {} - description: "" - delete: - summary: "" - operationId: delete-addresses-addressId - responses: - "204": - description: No Content - tags: - - Addresses - description: Delete an existing address - parameters: - - schema: - type: string - name: addressId - in: path - required: true - /channels: - get: - summary: Get all channels - responses: - "200": - description: OK - content: - application/json: - schema: - title: ChannelCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1channels/post/responses/201/content/application~1json/schema" - - type: object - properties: - meta: - title: Pagination - type: object - properties: - current_page: - type: integer - default: 1 - format: int32 - example: 1 - from: - type: integer - example: 24 - nullable: true - last_page: - type: integer - default: 15 - example: 15 - path: - type: string - per_page: - type: integer - example: 1 - to: - type: integer - nullable: true - total: - type: integer - description: "" - operationId: get-channels - parameters: - - schema: - type: string - in: query - name: includes - description: Comma separated includes for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page - description: Gets a paginated list of all channel - tags: - - Channels - post: - summary: Create a new channel - description: Create a new channel resource - responses: - "201": - description: OK - content: - application/json: - schema: - title: Channel - description: |- - ## Available includes - - - products - - categories - - collections - - discounts - - shippingMethods - x-examples: - webstore: - id: y3g6v91o - name: Webstore - handle: webstore - url: localhost:8080 - default: true - properties: - id: - type: string - example: y3g6v91o - name: - type: string - example: Webstore - handle: - type: string - example: webstore - url: - type: string - format: string - example: localhost:8080 - nullable: true - default: - type: boolean - default: false - example: true - x-tags: - - Channels - type: object - "422": - description: Unprocessable Entity - content: - application/json: - schema: - title: Unprocessable Entity - type: object - properties: - message: - type: string - example: The given data was invalid. - errors: - type: object - operationId: post-channels - requestBody: - content: - multipart/form-data: - schema: - title: CreateChannelBody - type: object - properties: - handle: - type: string - name: - type: string - url: - type: string - nullable: true - default: - type: boolean - nullable: true - examples: {} - description: "" - tags: - - Channels - "/channels/{channelId}": - parameters: - - schema: - type: string - name: channelId - in: path - required: true - get: - summary: Get the channel resource - responses: - "200": - description: OK - content: - application/json: - schema: - title: ChannelResponse - type: object - properties: - data: - $ref: "#/paths/~1channels/post/responses/201/content/application~1json/schema" - examples: - example-1: - value: - data: - id: y3g6v91o - name: Webstore - handle: webstore - url: http://webstore.test - default: true - published_at: null - meta: - lang: en - "404": - description: Not Found - content: - application/json: - schema: - title: ApiError - type: object - properties: - error: - type: object - properties: - http_code: - type: number - message: - type: string - operationId: get-channels-channelId - description: "" - parameters: - - schema: - type: string - in: query - name: includes - tags: - - Channels - put: - summary: Update the channel resource - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: put-channels-channelId - requestBody: - content: - multipart/form-data: - schema: - title: UpdateChannelBody - type: object - properties: - handle: - type: string - nullable: true - name: - type: string - nullable: true - url: - type: string - nullable: true - default: - type: boolean - nullable: true - description: "" - tags: - - Channels - delete: - summary: Delete the channel resource - responses: - "204": - description: No Content - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: delete-channels-channelId - tags: - - Channels - description: "" - /customers: - get: - summary: Get all customers - responses: - "200": - description: OK - content: - application/json: - schema: - title: CustomerCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1customers/post/responses/201/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-customers - parameters: - - schema: - type: string - in: query - name: include - description: Comma separated include for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page - - schema: - type: number - in: query - name: counts - description: Comma seperated count include to show relation counts - description: Gets a paginated list of all customers - tags: - - Customers - post: - summary: Create a new customer - description: Create a new customer - responses: - "201": - description: OK - content: - application/json: - schema: - title: Customer - x-examples: - webstore: - id: y3g6v91o - firstname: Joe - lastname: Bloggs - contact_number: 123456789 - alt_contact_number: 45553211244 - vat_no: GB1231 - company_name: ACME Enterprise - fields: - marketing: true - custom_key: custom_value - properties: - id: - type: string - example: y3g6v91o - nullable: true - firstname: - type: string - example: Joe - nullable: true - lastname: - type: string - example: Bloggs - nullable: true - contact_number: - anyOf: - - type: string - - type: number - example: 123456789 - nullable: true - alt_contact_number: - anyOf: - - type: string - - type: number - example: 45553211244 - nullable: true - company_name: - type: string - example: ACME Enterprise - nullable: true - vat_no: - anyOf: - - type: string - - type: number - example: GB12312 - nullable: true - fields: - type: object - nullable: true - x-tags: - - Customers - type: object - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: post-customers - requestBody: - content: - multipart/form-data: - schema: - title: CreateCustomerBody - type: object - properties: - firstname: - type: string - example: Joe - nullable: true - lastname: - type: string - example: Bloggs - nullable: true - contact_number: - type: number - example: 123456789 - nullable: true - alt_contact_number: - type: number - example: 45553211244 - nullable: true - company_name: - type: string - example: ACME Enterprise - nullable: true - vat_no: - type: string - example: GB12312 - nullable: true - fields: - type: object - nullable: true - examples: {} - description: "" - tags: - - Customers - /customers/fields: - get: - summary: Get custom customer fields - tags: - - Customers - responses: - "200": - description: OK - content: - application/json: - schema: - title: CustomerFieldsResponse - type: object - properties: - data: - title: CustomerFields - type: object - properties: - fields: - type: object - examples: - basic-example: - value: - data: - fields: - account_number: - label: Account Number - type: text - operationId: get-customer-fields - description: This endpoint returns any available customer fields which have been - defined in the getcandy config. - "/customers/{customerId}/users": - parameters: - - schema: - type: string - name: customerId - in: path - required: true - post: - summary: Attach a user to a customer record - responses: - "200": - description: OK - content: - application/json: - schema: - title: CustomerResponse - type: object - properties: - data: - $ref: "#/paths/~1customers/post/responses/201/content/application~1json/schema" - operationId: get-customers-customerId-users - description: "" - parameters: - - schema: - type: string - in: query - name: include - tags: - - Customers - "/customers/{customerId}/customer-groups": - parameters: - - schema: - type: string - name: customerId - in: path - required: true - put: - summary: Attach customer groups to a customer - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1customers~1%7BcustomerId%7D~1users/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-customers-customerId-customer-groups - description: "" - requestBody: - content: - application/json: - schema: - title: AttachCustomerToGroupBody - type: object - properties: - customer_group_ids: - type: array - items: - type: string - examples: {} - tags: - - Customers - "/customers/{customerId}": - parameters: - - schema: - type: string - name: customerId - in: path - required: true - get: - summary: Get the customer resource - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1customers~1%7BcustomerId%7D~1users/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-customers-customerId - description: "" - parameters: - - schema: - type: string - in: query - name: include - tags: - - Customers - put: - summary: Update the customer resource - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1customers~1%7BcustomerId%7D~1users/post/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: put-customers-customerId - requestBody: - content: - application/json: - schema: - title: UpdateCustomerBody - type: object - properties: - firstname: - type: string - example: Joe - nullable: true - lastname: - type: string - example: Bloggs - nullable: true - contact_number: - type: number - example: 123456789 - nullable: true - alt_contact_number: - type: number - example: 45553211244 - nullable: true - company_name: - type: string - example: ACME Enterprise - nullable: true - vat_no: - type: string - example: GB12312 - nullable: true - fields: - type: object - nullable: true - description: "" - tags: - - Customers - delete: - summary: Delete the customer resource - responses: - "204": - description: No Content - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: delete-customers-customerId - tags: - - Customers - description: "" - /countries: - get: - summary: Get all countries - responses: - "200": - description: OK - content: - application/json: - schema: - title: CountryCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Country - type: object - properties: - id: - type: string - name: - type: string - region: - type: string - iso_a_2: - type: string - iso_a_3: - type: string - iso_numeric: - type: string - preferred: - type: boolean - enabled: - type: boolean - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-countries - parameters: - - schema: - type: string - in: query - name: include - description: Comma separated includes for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page - description: Gets a paginated list of all channel - tags: - - Countries - "/countries/{countryId}": - parameters: - - schema: - type: string - name: countryId - in: path - required: true - get: - summary: Get the country resource - responses: - "200": - description: OK - content: - application/json: - schema: - title: CountryResponse - type: object - properties: - data: - $ref: "#/paths/~1countries/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - x-tags: - - Countries - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-countries-countryId - description: "" - parameters: - - schema: - type: string - in: query - name: include - tags: - - Countries - put: - summary: Update the country resource - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1countries~1%7BcountryId%7D/get/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: put-countries-countryId - requestBody: - content: - multipart/form-data: - schema: - title: UpdateCountryRequest - type: object - properties: - preferred: - type: boolean - nullable: true - enabled: - type: boolean - nullable: true - description: "" - tags: - - Countries - /customer-groups: - get: - summary: Get all customer groups - responses: - "200": - description: OK - content: - application/json: - schema: - title: CustomerGroupCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Customer Groups - description: "" - x-examples: - webstore: - id: y3g6v91o - name: Retail - handle: retail - default: true - system: true - properties: - id: - type: string - example: y3g6v91o - name: - type: string - example: Retail - handle: - type: string - example: retail - default: - type: boolean - default: false - example: true - system: - type: boolean - default: false - example: false - x-tags: - - Customer Groups - type: object - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-customer-groups - parameters: - - schema: - type: string - in: query - name: includes - description: Comma separated includes for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page - description: Gets a paginated list of all customer groups - tags: - - Customer Groups - post: - summary: Create a new customer group - description: Create a new customer group - responses: - "201": - description: OK - content: - application/json: - schema: - title: CustomerGroupResponse - type: object - properties: - data: - $ref: "#/paths/~1customer-groups/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: post-customer-groups - requestBody: - content: - multipart/form-data: - schema: - title: CreateCustomerGroupBody - type: object - properties: - handle: - type: string - name: - type: string - default: - type: boolean - nullable: true - system: - type: boolean - nullable: true - examples: {} - description: "" - tags: - - Customer Groups - "/customer-groups/{customerGroupId}": - parameters: - - schema: - type: string - name: customerGroupId - in: path - required: true - get: - summary: Get a single customer group - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1customer-groups/post/responses/201/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-customer-group-customerGroupId - description: "" - parameters: - - schema: - type: string - in: query - name: includes - tags: - - Customer Groups - put: - summary: Update the customer group resource - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1customer-groups/post/responses/201/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: put-customer-groups-customerGroupId - requestBody: - content: - multipart/form-data: - schema: - title: CreateCustomerGroupBody - type: object - properties: - handle: - type: string - nullable: true - name: - type: string - nullable: true - default: - type: boolean - nullable: true - system: - type: boolean - nullable: true - description: "" - tags: - - Customer Groups - delete: - summary: Delete the customer group resource - responses: - "204": - description: No Content - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: delete-customer-groups-customerGroupId - tags: - - Customer Groups - description: "" - /languages: - get: - summary: Get Languages - tags: - - Languages - responses: - "200": - description: OK - content: - application/json: - schema: - title: LanguageCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1languages~1%7BlanguageId%7D/put/requestBody/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-languages - description: Returns a paginated list of Languages - post: - summary: Create Language - tags: - - Languages - responses: - "201": - description: OK - content: - application/json: - schema: - title: LanguageResponse - type: object - properties: - data: - $ref: "#/paths/~1languages~1%7BlanguageId%7D/put/requestBody/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: post-languages - description: Create a new language - requestBody: - content: - multipart/form-data: - schema: - title: CreateLanguageBody - type: object - properties: - name: - type: string - lang: - type: string - iso: - type: string - description: Unique - enabled: - type: boolean - default: - type: boolean - current: - type: boolean - required: - - name - - lang - - iso - "/languages/{languageId}": - parameters: - - schema: - type: string - name: languageId - in: path - required: true - get: - summary: Get Language - tags: - - Languages - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1languages/post/responses/201/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-languages-languageId - description: Get a Language by ID - put: - summary: Update Language - tags: - - Languages - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1languages/post/responses/201/content/application~1json/schema" - examples: - language-example: - value: - data: - id: 6z8m9gmj - name: English - lang: en - iso: gb - default: true - enabled: true - current: false - operationId: put-languages-languageId - description: Update a Language using it's ID - requestBody: - content: - application/json: - schema: - title: Language - description: "" - properties: - id: - type: string - example: y3g6v91o - iso: - type: string - example: gb - lang: - type: string - example: en - name: - type: string - example: English - default: - type: boolean - default: false - example: true - enabled: - type: boolean - default: false - example: true - x-tags: - - Languages - type: object - delete: - summary: Delete Language - tags: - - Languages - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-languages-languageId - description: Delete a Language by its ID - /routes: - get: - summary: Get Routes - tags: - - Routes - responses: - "200": - description: OK - content: - application/json: - schema: - title: RouteCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1routes~1%7BrouteId%7D/put/requestBody/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-routes - description: Returns a paginated list of Routes - "/routes/{routeId}": - parameters: - - schema: - type: string - name: routeId - in: path - required: true - get: - summary: Get Route - tags: - - Routes - responses: - "200": - description: OK - content: - application/json: - schema: - title: RouteResponse - type: object - properties: - data: - $ref: "#/paths/~1routes~1%7BrouteId%7D/put/requestBody/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-routes-routeId - description: Get a Route by ID - put: - summary: Update Route - tags: - - Routes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1routes~1%7BrouteId%7D/get/responses/200/content/application~1json/schema" - operationId: put-routes-routeId - description: Update a Route using it's ID - requestBody: - content: - application/json: - schema: - title: Route - description: "" - properties: - id: - type: string - element_type: - type: string - element_id: - type: integer - default: - type: boolean - default: false - redirect: - type: boolean - default: false - slug: - type: string - path: - type: string - nullable: true - locale: - type: string - description: - type: string - nullable: true - drafted_at: - type: string - draft_parent_id: - type: integer - x-tags: - - Routes - type: object - delete: - summary: Delete Route - tags: - - Routes - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-routes-routeId - description: Delete a Router by its ID - /routes/search: - get: - summary: Search for Route - tags: - - Routes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1routes~1%7BrouteId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-routes-search - description: Get a Route by searching via slug or path - /product-families: - get: - summary: Get product families - tags: - - Product Families - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductFamilyCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: ProductFamily - type: object - properties: - id: - type: string - name: - type: string - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-product-families - description: Returns a paginated list of all product families. - parameters: - - schema: - type: string - in: query - name: include - description: Define included relationships - post: - summary: Create product family - tags: - - Product Families - responses: - "201": - description: OK - content: - application/json: - schema: - title: ProductFamilyResponse - type: object - properties: - data: - $ref: "#/paths/~1product-families/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: post-product-families - description: Create a new product family. - "/product-families/{productFamilyId}": - parameters: - - schema: - type: string - name: productFamilyId - in: path - required: true - get: - summary: Get a product family - tags: - - Product Families - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1product-families/post/responses/201/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-product-families-productFamilyId - description: Returns a single product family based on ID. - parameters: - - schema: - type: string - in: query - name: includes - put: - summary: Update product family - tags: - - Product Families - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1product-families/post/responses/201/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-product-families-productFamilyId - description: Update a product family - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - delete: - summary: Delete product family - tags: - - Product Families - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-product-families-productFamilyId - description: Sends a request to delete a product family - /baskets: - get: - summary: Get baskets - responses: - "200": - description: OK - content: - application/json: - schema: - title: BasketCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Basket - type: object - properties: - id: - type: string - total: - type: number - sub_total: - type: number - tax_total: - type: number - discount_total: - type: number - format: float - changed: - type: boolean - has_exclusions: - type: boolean - meta: - type: object - lines: - type: object - properties: - data: - type: array - items: - title: BasketLine - type: object - properties: - id: - type: string - quantity: - type: integer - line_total: - type: number - unit_price: - type: number - unit_tax: - type: number - line_discount: - type: number - tax: - type: number - order: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - operationId: get-baskets - description: Get a paginated list of baskets - tags: - - Baskets - "/baskets/{basketId}/meta": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - post: - summary: Add meta information - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - title: BasketResponse - type: object - properties: - data: - $ref: "#/paths/~1baskets/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-baskets-basketId-meta - description: Allows you to add custom meta information to a basket. - requestBody: - content: - application/json: - schema: - title: AddBasketMetaBody - type: object - properties: - value: - type: string - key: - type: string - required: - - key - - value - /baskets/resolve: - post: - summary: Resolve a basket - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - operationId: post-baskets-resolve - description: >- - This endpoint is for when you want to either merge a users basket with a - guest basket and then assign that new basket or associate a user to a - guest basket. - - - If you choose not to merge a basket, their current one will be overwritten with the guest basket. - requestBody: - content: - application/json: - schema: - title: SaveBasketBody - type: object - properties: - name: - type: string - required: - - name - /baskets/current: - get: - summary: Get the current basket for a user - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-baskets-current - description: This request will get the current active basket for a user - "/baskets/{basketId}/save": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - post: - summary: Save a basket for a user - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-baskets-basketId-save - requestBody: - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1resolve/post/requestBody/content/application~1json/schema" - description: Saves a basket to a users account. - "/baskets/{basketId}/claim": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - post: - summary: Allow a user to claim a basket - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-baskets-basketId-claim - description: 'A user is able to "claim" a guest basket. ' - "/baskets/{basketId}": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - delete: - summary: Delete a basket by ID - tags: - - Baskets - responses: - "204": - description: No Content - operationId: delete-baskets-basketId - description: Deletes a basket - get: - summary: Get basket - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-baskets-basketId - description: Get a basket by it's ID - put: - summary: Update a basket by ID - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-baskets-basketId - description: Updates a basket - /baskets/saved: - get: - summary: Get a users saved baskets - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - title: SavedBasketCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: SavedBasket - type: object - x-tags: - - Baskets - properties: - id: - type: string - name: - type: string - basket: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - "401": - description: Unauthorized - content: - application/json: - schema: - title: Unauthenticated - type: object - properties: - error: - type: string - x-examples: - example: - error: Unauthenticated. - x-tags: - - Statuses - examples: - example-1: - value: - error: Unauthenticated. - operationId: get-baskets-saved - description: Returns an authenticatd users saved baskets. - "/baskets/saved/{basketId}": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - put: - summary: Update a saved basket - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - title: SavedBasketResponse - type: object - properties: - data: - $ref: "#/paths/~1baskets~1saved/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-baskets-saved-basketId - requestBody: - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1resolve/post/requestBody/content/application~1json/schema" - description: Updates a saved basket on the API - "/baskets/{basketId}/discounts": - parameters: - - schema: - type: string - name: basketId - in: path - required: true - delete: - summary: Remove discount - tags: - - Baskets - responses: - "204": - description: No Content - operationId: delete-baskets-basketId-discounts - description: "Allows a user/guest to remove a basket from their basket. Useful - if you can only have one discount at a time and they wish to use a - different one. " - /basket-lines: - post: - summary: Create basket lines - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - operationId: post-basket-lines - description: Add lines to a basket - requestBody: - content: - application/json: - schema: - title: CreateBasketLinesBody - type: object - properties: - variants: - type: array - items: - type: object - properties: - id: - type: string - quantity: - type: integer - meta: - type: object - basket_id: - type: string - examples: - create-lines-example: - value: - basket_id: 15rf2395etf34t - variants: - - id: 534ed23ewdas - quantity: 1 - meta: - giftwrapped: true - delete: - summary: Delete basket lines - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - data: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - operationId: delete-basket-lines - description: Removes basket lines from a basket - "/basket-lines/{basketLineId}": - parameters: - - schema: - type: string - name: basketLineId - in: path - required: true - put: - summary: Update basket line - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-basket-lines-basketLineId - description: Update a basket line based on it's ID. - requestBody: - content: - application/json: - schema: - title: BasketLineUpdateBody - type: object - properties: - quantity: - type: integer - "/basket-lines/{basketLineId}/add": - parameters: - - schema: - type: string - name: basketLineId - in: path - required: true - put: - summary: Update basket line quantity - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - operationId: put-basket-lines-basketLineId-add - description: Update basket line quantity - requestBody: - content: - application/json: - schema: - $ref: "#/paths/~1basket-lines~1%7BbasketLineId%7D/put/requestBody/content/application~1json/schema" - "/basket-lines/{basketLineId}/remove": - parameters: - - schema: - type: string - name: basketLineId - in: path - required: true - put: - summary: Remove basket line quantity - tags: - - Baskets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - operationId: put-basket-lines-basketLineId-remove - description: Removes quantity from a basket line - requestBody: - content: - application/json: - schema: - $ref: "#/paths/~1basket-lines~1%7BbasketLineId%7D/put/requestBody/content/application~1json/schema" - /orders: - get: - summary: Get orders - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: OrderCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1orders~1%7BorderId%7D/put/responses/404/content/application~1json/schema/properties/data" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-orders - description: If you're an admin user you will be able to see all orders, - otherwise only the current users orders will be returned. - parameters: - - schema: - type: string - in: query - name: include - post: - summary: Create Order - operationId: post-orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: OrderResponse - type: object - properties: - data: - $ref: "#/paths/~1orders~1%7BorderId%7D/put/responses/404/content/application~1json/schema/properties/data" - "403": - description: Forbidden - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - forbidden-example: - value: - http_code: 403 - message: This basket already has an order that has been placed - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - unprocessable-example: - value: - basket_id: - - The basket id field is required. - description: Create an Order from a Basket instance - tags: - - Orders - requestBody: - content: - application/json: - schema: - title: CreateOrderBody - type: object - properties: - basket_id: - type: string - include: - type: string - required: - - basket_id - "/orders/{orderId}": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - get: - summary: Get Order - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-orders-orderId - description: >- - Get an Order by it's ID. - - - You must be an admin or owner to retrieve the order, otherwise you'll get a 404. - put: - summary: Update Order - operationId: put-orders-orderId - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - type: object - properties: - data: - title: Order - type: object - x-examples: - full-response: - id: 34fSEsef23ewds - display_id: "#ORD-1234567" - sub_total: 24150 - type: SagePay - delivery_total: 0 - discount_total: 0 - tax_total: 4830 - shipping_preference: null - shipping_method: Standard Delivery - order_total: 28980 - reference: CANDY-123456789 - customer_reference: null - invoice_reference: "#INV-CANDY-123456789" - vat_no: null - tracking_no: null - dispatched_at: null - currency: GBP - customer_name: Sweet Tooth - contact_details: - phone: "" - email: sweets@sweettooth.com - billing_details: - phone: "" - email: null - firstname: Sweet - lastname: Tooth - address: Sweet Shop Inc. - address_two: Pastry Lane - address_three: null - city: Chelmsford - county: Essex - state: null - country: United Kingdom - zip: CM2 5TH - shipping_details: - method: Standard Delivery - preference: null - phone: 12345-678910 - email: null - firstname: Sweet - lastname: Tooth - address: Sweet Shop Inc. - address_two: Pastry Lane - address_three: null - city: Chelmsford - county: Essex - state: null - country: United Kingdom - zip: CM2 5TH - status: dispatched - created_at: 2018-02-28T15:33:26.000000Z - updated_at: 2019-06-04T08:22:29.000000Z - placed_at: 2018-02-28T15:34:57.000000Z - notes: Please don't ring the doorbell and run away - meta: [] - properties: - id: - type: string - example: 123RFesfes356P - display_id: - type: string - example: "#ORD-123456" - sub_total: - type: integer - format: int32 - example: 12345 - type: - type: string - example: SagePay - delivery_total: - type: integer - format: int32 - example: 1307 - discount_total: - type: integer - tax_total: - type: integer - format: int32 - example: 1307 - shipping_preference: - type: string - shipping_method: - type: string - example: Standard Delivery - order_total: - type: integer - format: int32 - example: 7845 - reference: - type: string - example: CUSTOM-REFERENCE - customer_reference: - type: string - invoice_reference: - type: string - example: "#INV-1234567" - vat_no: - type: string - tracking_no: - type: string - dispatched_at: - type: string - currency: - type: string - example: GBP - customer_name: - type: string - example: Thanos - contact_details: - type: object - properties: - phone: - type: string - example: "12345678910" - email: - type: string - example: example@example.com - billing_details: - $ref: "#/paths/~1orders~1%7BorderId%7D~1shipping~1address/put/requestBody/content/application~1json/schema" - shipping_details: - $ref: "#/paths/~1orders~1%7BorderId%7D~1shipping~1address/put/requestBody/content/application~1json/schema" - status: - type: string - example: payment-received - created_at: - type: string - updated_at: - type: string - notes: - type: string - meta: - type: array - items: - type: object - basket: - $ref: "#/paths/~1baskets~1%7BbasketId%7D~1meta/post/responses/200/content/application~1json/schema" - discounts: - type: string - transactions: - title: TransactionCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1payments~1%7BtransactionId%7D~1refund/post/responses/200/content/application~1json/schema/properties/data" - - type: object - properties: - meta: - type: object - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - lines: - title: OrderLineCollection - type: object - properties: - data: - type: array - items: - title: OrderLine - type: object - properties: - id: - type: string - quantity: - type: integer - line_total: - type: integer - discount_total: - type: integer - delivery_total: - type: integer - unit_price: - type: integer - unit_qty: - type: integer - tax_total: - type: integer - tax_rate: - type: integer - description: - type: string - option: - type: string - sku: - type: string - is_shipping: - type: boolean - is_manual: - type: boolean - meta: - type: object - shipping: - title: OrderLineResponse - type: object - properties: - data: - $ref: "#/paths/~1orders~1%7BorderId%7D/put/responses/404/content/application~1json/schema/properties/data/properties/lines/properties/data/items" - logs: - title: ActivityLogCollection - type: object - properties: - data: - type: array - items: - title: ActivityLog - type: object - properties: - id: - type: string - type: - type: string - description: - type: string - properties: - type: string - created_at: - type: string - user: - $ref: "#/paths/~1users~1%7BuserId%7D/get/responses/200/content/application~1json/schema" - user: - $ref: "#/paths/~1users~1%7BuserId%7D/get/responses/200/content/application~1json/schema" - tags: - - Orders - description: Update an Order - requestBody: - content: - application/json: - schema: - type: object - properties: - tracking_no: - type: string - status: - type: string - description: Corresponds to status set in config - send_emails: - type: boolean - default: false - example: true - description: "" - parameters: - - schema: - type: string - in: query - name: include - /orders/types: - get: - summary: Get order types - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: OrderTypeCollection - type: object - properties: - data: - type: array - items: - title: OrderType - type: object - properties: - label: - type: string - examples: - order-types-example: - value: - data: - - label: Collection - - label: PayPal - - label: SagePay - operationId: get-orders-types - description: Returns all order types currently in the system - /orders/process: - post: - summary: Processes an order on the API - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "403": - description: Forbidden - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-orders-process - requestBody: - content: - application/json: - schema: - type: object - properties: - payment_type_id: - type: string - payment_type: - type: string - order_id: - type: string - payment_token: - type: string - customer_reference: - type: string - meta: - type: array - items: - type: object - notes: - type: string - company_name: - type: string - required: - - order_id - - payment_token - "/orders/email-preview/{status}": - parameters: - - schema: - type: string - name: status - in: path - required: true - get: - summary: Get order status preview email - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: EmailPreviewResponse - type: object - properties: - data: - title: EmailPreview - type: object - properties: - subject: - type: string - content: - type: string - "422": - description: |- - Unprocessable Entity - - This will happen if the passed order id cannot be found - content: - application/json: - schema: - type: object - properties: - id: - type: array - items: - type: string - operationId: get-orders-email-preview-status - description: >- - This endpoint will get a HTML email preview for an order status, this is - useful if you want to be able to see what email will be sent out for the - corresponding Order status. - - - Mailers for each order status should be stored in the getcandy config under `orders.mailers` - parameters: - - schema: - type: string - in: query - name: id - description: An order id to use for the template - required: true - /orders/bulk: - post: - summary: Bulk update orders - tags: - - Orders - responses: - "204": - description: No Content - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - bad-request-example: - value: - http_code: 400 - message: Unable to update field - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - orders: - type: array - items: - type: string - field: - type: array - items: - type: string - value: - type: array - items: - type: string - operationId: post-orders-bulk - description: |- - Allows you to bulk update a field across multiple Orders. - - You must have the correct priviledges to perform this action. - requestBody: - content: - application/json: - schema: - type: object - properties: - orders: - type: array - items: - type: string - field: - type: string - value: - type: string - send_emails: - type: boolean - description: Whether to send any mailers when changing status - required: - - orders - - field - examples: - bulk-update-example-body: - value: - orders: - - 1236ygsd3 - - 9etfxvj2q - - 75672fsfs - field: status - value: in-progress - send_emails: true - get: - summary: Get Order export - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: OrderExportResponse - type: object - properties: - data: - title: OrderExport - type: object - properties: - format: - type: string - content: - type: string - examples: - export-example: - value: - data: - format: csv - content: ZW1haWwsZG... - operationId: get-orders-bulk - description: Export orders into a base64 encoded string - parameters: - - schema: - type: string - enum: - - 1sfe534r4ref:934redfk - in: query - name: orders - description: Colon seperated order IDs - required: true - - schema: - type: string - in: query - description: The export format, must be present in config - name: format - required: true - "/orders/{orderId}/expire": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - post: - summary: Expire an Order - operationId: post-orders-orderId-expire - description: Sets an order to be expired. You must have the correct priviledges - to perform this action. Once an order is expired, it will no longer - appear in results unless performed by an admin or in the hub. - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - tags: - - Orders - "/orders/{orderId}/shipping/address": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - put: - summary: Update shipping address - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-orders-id-shipping-address - description: Update an orders shipping address - requestBody: - content: - application/json: - schema: - title: Address - type: object - properties: - phone: - type: string - email: - type: string - firstname: - type: string - lastname: - type: string - address: - type: string - address_two: - type: string - address_three: - type: string - city: - type: string - state: - type: string - country: - $ref: "#/paths/~1countries~1%7BcountryId%7D/get/responses/200/content/application~1json/schema" - postal_code: - type: string - salutation: - type: string - default: - type: boolean - shipping: - type: boolean - delivery: - type: boolean - delivery_instructions: - type: string - meta: - anyOf: - - type: object - - type: array - items: {} - last_used_at: - type: string - description: All fields are required when an `address_id` is not present. - "/orders/{orderId}/shipping/methods": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - get: - summary: Get Order Shipping Methods - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingPriceCollection - type: object - properties: - data: - type: array - items: - title: ShippingPrice - type: object - description: |- - ### Available Includes - - - method - - zone - - currency - - customer_groups - x-examples: - example-with-zone-method: - id: ujafe83dd - rate: 795 - tax: 159 - fixed: true - min_basket: 0 - min_basket_tax: 0 - min_weight: "0.00000" - weight_unit: kg - min_height: "0.00000" - height_unit: cm - min_width: "0.00000" - width_unit: cm - min_depth: "0.00000" - depth_unit: cm - min_volume: "0.00000" - volume_unit: l - method: - data: - id: v5ters33 - type: regional - name: Standard Delivery - description: Standard delivery - code: ND - zone: - data: - id: awd012es - name: Region 1 - properties: - id: - type: string - rate: - type: integer - format: int32 - tax: - type: integer - format: int32 - fixed: - type: boolean - min_basket: - type: integer - description: Minimum basket total to be eligible - min_basket_tax: - type: integer - min_weight: - type: string - weight_unit: - type: string - min_height: - type: string - height_unit: - type: string - min_width: - type: string - width_unit: - type: string - min_depth: - type: string - depth_unit: - type: string - min_volume: - type: string - volume_unit: - type: string - method: - $ref: "#/paths/~1shipping~1%7BshippingMethodId%7D/get/responses/200/content/application~1json/schema" - zone: - $ref: "#/paths/~1shipping~1zones/post/responses/200/content/application~1json/schema" - examples: - full-example: - value: - data: - - id: awd2qwda0d - rate: 795 - tax: 159 - fixed: true - min_basket: 0 - min_basket_tax: 0 - min_weight: "0.00000" - weight_unit: kg - min_height: "0.00000" - height_unit: cm - min_width: "0.00000" - width_unit: cm - min_depth: "0.00000" - depth_unit: cm - min_volume: "0.00000" - volume_unit: l - method: - data: - id: v8l4pl01 - type: regional - name: Standard Delivery - description: Method Description - code: ND - zone: - data: - id: p09prlrn - name: Region One - - id: 92owerfd3 - rate: 3000 - tax: 600 - fixed: true - min_basket: 0 - min_basket_tax: 0 - min_weight: "0.00000" - weight_unit: kg - min_height: "0.00000" - height_unit: cm - min_width: "0.00000" - width_unit: cm - min_depth: "0.00000" - depth_unit: cm - min_volume: "0.00000" - volume_unit: l - method: - data: - id: awd2e15gtfd - type: regional - name: Next Working Day - description: Next day delivery - excluding weekends - code: ND1030 - zone: - data: - id: 0o3wesde3 - name: Region One - - id: eqdas9932 - rate: 2000 - tax: 400 - fixed: true - min_basket: 0 - min_basket_tax: 0 - min_weight: "0.00000" - weight_unit: kg - min_height: "0.00000" - height_unit: cm - min_width: "0.00000" - width_unit: cm - min_depth: "0.00000" - depth_unit: cm - min_volume: "0.00000" - volume_unit: l - method: - data: - id: wz6d39dj - type: regional - name: Next Working Day - Before 1pm - description: Next working day before 1pm - code: ND1230 - zone: - data: - id: 12qewdfs4 - name: Region One - operationId: get-orders-id-shipping-methods - description: This will return a list of all ShippingMethod's that are available - for this order. - parameters: - - schema: - type: string - in: query - name: include - description: The available resources to include - "/orders/{orderId}/shipping/cost": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - put: - summary: Add shipping cost - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - price_id: - type: array - items: - type: string - examples: - unprocessable-example: - value: - price_id: - - Please choose a shipping option - operationId: put-orders-id-shipping-cost - description: Adds a shipping cost to an Order - requestBody: - content: - application/json: - schema: - type: object - properties: - price_id: - type: string - description: "" - parameters: - - schema: - type: string - in: query - name: include - description: Related resources to include in response - "/orders/{orderId}/contact": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - put: - summary: Add contact details - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - operationId: put-orders-id-contact - description: Add contact details to an order - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - type: string - phone: - type: string - description: "" - "/orders/{orderId}/billing/address": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - put: - summary: Update billing address - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-orders-orderId-billing-address - requestBody: - content: - application/json: - schema: - $ref: "#/paths/~1orders~1%7BorderId%7D~1shipping~1address/put/requestBody/content/application~1json/schema" - description: All fields are required when an `address_id` is not present. - description: Update an orders billing address - "/orders/{orderId}/lines": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - put: - summary: Add order line - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - quantity: - type: array - items: - type: string - line_total: - type: array - items: - type: string - unit_price: - type: array - items: - type: string - tax_rate: - type: array - items: - type: string - description: - type: array - items: - type: string - operationId: put-orders-id-lines - description: Adds an order line to an order - requestBody: - content: - application/json: - schema: - type: object - properties: - quantity: - type: integer - line_total: - type: integer - unit_price: - type: integer - tax_rate: - type: number - description: The tax rate as a percentage - format: double - example: 20 - description: - type: string - description: Shows publicly on the order line - is_manual: - type: boolean - description: Should this line be treated as a manual one - is_shipping: - type: boolean - default: false - option: - type: string - description: If this is a variant, list the option name here - sku: - type: string - discount_total: - type: integer - required: - - quantity - - line_total - - unit_price - - tax_rate - - description - - sku - "/orders/lines/{orderLineId}": - parameters: - - schema: - type: string - name: orderLineId - in: path - required: true - delete: - summary: Delete an order line - tags: - - Orders - responses: - "204": - description: No Content - operationId: delete-orders-lines-orderId - description: Deletes an order line from an order - "/orders/{orderId}/invoice": - parameters: - - schema: - type: string - name: orderId - in: path - required: true - get: - summary: Get order invoice - tags: - - Orders - responses: - "200": - description: OK - content: - application/json: - schema: - title: InvoiceResponse - type: object - properties: - data: - title: Invoice - type: object - properties: - encoding: - type: string - content: - type: string - "401": - description: Unauthorized - operationId: get-orders-id-invoice - description: Get an orders invoice - /products: - get: - summary: Get Products - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Product - type: object - properties: - id: - type: string - drafted_at: - type: string - format: date-time - description: If this product is a draft, this will be populated - option_data: - description: Displays any variant option data - type: object - created_at: - type: string - updated_at: - type: string - description: "" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-products - description: Gets a paginated list of products. - parameters: - - schema: - type: string - in: query - name: include - - schema: - type: boolean - default: true - in: query - name: paginated - description: "" - allowEmptyValue: true - - schema: - type: string - in: query - name: ids - description: Return only the selected IDs - - schema: - type: integer - in: query - name: limit - post: - summary: Create Product - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductResponse - type: object - properties: - data: - $ref: "#/paths/~1products/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - operationId: post-products - requestBody: - content: - multipart/form-data: - schema: - title: ProductCreateBody - type: object - properties: - name: - type: object - required: - - en - properties: - en: - type: string - url: - type: string - stock: - type: integer - family_id: - type: string - price: - type: number - format: float - sku: - type: string - required: - - name - - url - - stock - - family_id - - price - - sku - examples: {} - description: Creates a new product in the system. When creating a new product - will also create 1 variant for that product. - parameters: - - schema: - type: string - in: query - name: include - /products/recommended: - get: - summary: Get recommended products - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductRecommendationCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: ProductRecommendation - type: object - properties: - id: - type: string - product: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-products-recommended - parameters: - - schema: - type: string - in: query - name: basket_id - required: true - description: >- - This endpoint will return recommended products based on a basket. - - - Using product associations, the API will find products in the basket and display any relations that have been defined. - "/products/{productId}": - parameters: - - schema: - type: string - name: productId - in: path - required: true - get: - summary: Get Product - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - examples: {} - operationId: get-products-productId - parameters: - - schema: - type: string - in: query - name: include - - schema: - type: string - in: query - name: excl_tax - description: Prices shouldn't include tax - - schema: - type: string - in: query - name: full_response - description: Returns full `attribute_data` in response - - schema: - type: string - in: query - name: option_data - description: Include option data - - schema: - type: string - in: query - name: draft - description: Show draft if exists - description: Returns a product by it's given ID - put: - summary: Update Product - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1saved/get/responses/401/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-products-productId - requestBody: - content: - application/json: - schema: - title: ProductUpdateBody - type: object - properties: - attribute_data: - type: object - family_id: - type: string - description: "" - description: Updates a product by it's ID - delete: - summary: Delete Product - tags: - - Products - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-products-productId - description: >- - Deletes a product by it's ID - - - > This will only soft delete the product. It will then be available to restore at a later time. - /products/variants: - get: - summary: Get Product Variants - tags: - - Product Variants - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductVariantCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: ProductVariant - type: object - description: |- - ### Available includes - - - products - - basketLines - - image - - tax - - customerPricing - - tiers - properties: - id: - type: string - sku: - type: string - backorder: - type: string - requires_shipping: - type: boolean - price: - type: number - factor_tax: - type: number - unit_price: - type: number - total_tax: - type: number - unit_tax: - type: number - unit_qty: - type: integer - min_qty: - type: integer - max_qty: - type: integer - min_batch: - type: integer - inventory: - type: integer - incoming: - type: integer - group_pricing: - type: boolean - weight: - type: object - properties: - value: - type: string - unit: - type: string - height: - type: object - properties: - value: - type: string - unit: - type: string - width: - type: object - properties: - value: - type: string - unit: - type: string - depth: - type: object - properties: - value: - type: string - unit: - type: string - volume: - type: object - properties: - value: - type: string - unit: - type: string - product: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - image: - $ref: "#/paths/~1assets/put/responses/200/content/application~1json/schema" - customer_pricing: - type: object - properties: - data: - type: array - items: - title: CustomerPrice - type: object - properties: - id: - type: string - price: - type: number - tax: - $ref: "#/paths/~1taxes/post/responses/200/content/application~1json/schema" - group: - $ref: "#/paths/~1customer-groups/post/responses/201/content/application~1json/schema" - tax: - $ref: "#/paths/~1taxes/post/responses/200/content/application~1json/schema" - tiers: - title: ProductTierPriceCollection - type: object - properties: - data: - type: array - items: - title: ProductTierPrice - type: object - properties: - id: - type: string - lower_limit: - type: integer - price: - type: number - tax: - type: number - group: - $ref: "#/paths/~1customer-groups/post/responses/201/content/application~1json/schema" - options: - type: object - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-products-variants - parameters: - - schema: - type: string - in: query - name: include - description: Get a paginated list of all product variants in the system - "/products/variants/{productVariantId}": - parameters: - - schema: - type: string - name: productVariantId - in: path - required: true - get: - summary: Get Product Variant - tags: - - Product Variants - responses: - "200": - description: OK - content: - application/json: - schema: - title: ProductVariantResponse - type: object - properties: - data: - $ref: "#/paths/~1products~1variants/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "404": - description: Not Found - headers: {} - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-products-variants-productVariantId - description: Get a product variant by it's ID - "/products/variants/{productVariantId}/inventory": - parameters: - - schema: - type: string - name: productVariantId - in: path - required: true - put: - summary: Update ProductVariant Inventory - tags: - - Product Variants - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products~1variants~1%7BproductVariantId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: put-products-variants-productVariantId-inventory - description: Request to update a product variants inventory. - requestBody: - content: - application/json: - schema: - type: object - properties: - inventory: - type: integer - "/products/{productId}/duplicate": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Duplicate Product - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-products-productId-duplicate - description: |- - Duplicates a product, requires you to specify new slugs and SKU's. - - > Duplicated product will not immediately be active. - requestBody: - content: - application/json: - schema: - type: object - properties: - routes: - type: array - items: - type: object - properties: - old: - type: string - new: - type: string - skus: - type: array - items: - type: object - properties: - old: - type: string - new: - type: string - required: - - routes - - skus - description: We use old, new values on routes and skus for matching. - "/products/{productId}/urls": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Create Product route - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - operationId: post-product-urls - description: Creates and syncs a new product route. - requestBody: - content: - application/json: - schema: - type: object - properties: - slug: - type: string - "/products/{productId}/redirects": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Create Product redirect - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - operationId: post-product-redirects - description: Creates and syncs a new product route. - requestBody: - content: - application/json: - schema: - type: object - properties: - slug: - type: string - "/products/{productId}/attributes": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update Product attributes - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - attributes: - type: array - items: - type: string - operationId: post-products-product-attributes - description: Allows you to sync up the attributes which are directly associated - to this product. - requestBody: - content: - application/json: - schema: - type: object - properties: - attributes: - type: array - description: An array of attribute IDs - items: - type: string - required: - - attributes - "/products/{productId}/collections": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update Product collections - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - collections: - type: array - items: - type: string - operationId: post-products-productId-collections - description: Update a products collections. - requestBody: - content: - application/json: - schema: - type: object - properties: - collections: - type: array - description: An array of collection IDs to associate - items: - type: string - required: - - collections - description: "" - "/products/{productId}/categories": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update Product categories - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-products-product-categories - description: Sync product categories - requestBody: - content: - application/json: - schema: - type: object - properties: - categories: - type: array - description: Array of category ID's - items: - type: string - required: - - categories - "/products/{productId}/channels": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update Product channels - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-products-product-channels - description: Sync product Channels - requestBody: - content: - application/json: - schema: - type: object - properties: - channels: - type: array - description: Array of channel ID's - items: - type: string - required: - - channels - "/products/{productId}/categories/{categoryId}": - parameters: - - schema: - type: string - name: productId - in: path - required: true - - schema: - type: string - name: categoryId - in: path - required: true - delete: - summary: Detach category - tags: - - Products - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-products-product-categories-category - description: Detaches a category from a product. Does not delete the category. - "/products/{productId}/collections/{collectionId}": - parameters: - - schema: - type: string - name: productId - in: path - required: true - - schema: - type: string - name: collectionId - in: path - required: true - delete: - summary: Detach collection - tags: - - Products - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-products-product-collections-detach - description: Detaches a collection from a product. Does not delete the collection. - "/products/{productId}/associations": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update product associations - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - operationId: post-products-product-associations - description: Updates product associations - requestBody: - content: - application/json: - schema: - type: object - properties: - relations: - type: object - properties: - association_id: - type: string - type: - type: string - description: "" - delete: - summary: "" - operationId: delete-products-productId-associations - responses: - "204": - description: No Content - description: Removes product associations - tags: - - Products - "/products/{productId}/customer-groups": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Update customer groups - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - operationId: post-products-product-customer-groups - description: Update a products customer groups - requestBody: - content: - application/json: - schema: - type: object - properties: - groups: - type: array - items: - type: object - properties: - visible: - type: boolean - purchasable: - type: boolean - required: - - visible - - purchasable - required: - - groups - delete: - summary: Detach customer groups - operationId: delete-products-product-customer-groups - responses: - "204": - description: No Content - tags: - - Products - description: Detaches customer groups from a product - "/products/{productId}/drafts": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Create Draft - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-products-id-drafts - description: Create a draft product from an existing product. - get: - summary: Create draft - tags: - - Products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-products-productId-drafts - description: |- - Create a draft for a product. - - If a draft already exists, that current draft will be returned. - "/products/{productId}/publish": - parameters: - - schema: - type: string - name: productId - in: path - required: true - post: - summary: Publish Draft - tags: - - Products - responses: - "200": - content: - application/json: - schema: - $ref: "#/paths/~1products/post/responses/200/content/application~1json/schema" - description: "" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-products-id-publish - description: Publish a draft from a Product - /attributes: - get: - summary: Get Attributes - tags: - - Attributes - responses: - "200": - description: OK - content: - application/json: - schema: - title: AttributeCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Attribute - type: object - x-examples: - attribute-example: - id: dnj4zky5 - name: - en: Name - sv: Namn - handle: name - position: "1" - filterable: false - scopeable: true - translatable: true - variant: false - searchable: true - localised: true - type: text - required: true - lookups: null - system: false - description: |- - ### Available includes - - group - properties: - id: - type: string - example: dnj4zky5 - name: - type: object - handle: - type: string - position: - type: integer - filterable: - type: boolean - scopeable: - type: boolean - translatable: - type: boolean - variant: - type: boolean - searchable: - type: boolean - localised: - type: boolean - type: - type: string - required: - type: boolean - lookups: - type: array - items: - type: object - system: - type: boolean - group: - $ref: "#/paths/~1attribute-groups/post/responses/200/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - examples: - paginated-example: - value: - data: - - id: dnj4zky5 - name: - en: Name - sv: Namn - handle: name - position: "1" - filterable: false - scopeable: true - translatable: true - variant: false - searchable: true - localised: true - type: text - required: true - lookups: null - system: false - group: - data: - id: vokq5kmj - name: - en: General - handle: general - position: "1" - links: - first: http://storefront.test/api/v1/attributes?page=1 - last: http://storefront.test/api/v1/attributes?page=5 - prev: null - next: http://storefront.test/api/v1/attributes?page=2 - meta: - current_page: 1 - from: 1 - last_page: 5 - path: http://storefront.test/api/v1/attributes - per_page: 15 - to: 15 - total: 70 - operationId: get-attributes - parameters: [] - description: Return a paged array of attributes - post: - summary: Create Attribute - tags: - - Attributes - responses: - "201": - description: Created - content: - application/json: - schema: - title: AttributeResponse - type: object - properties: - data: - $ref: "#/paths/~1attributes/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - examples: - post-example: - value: - data: - id: w52log2p - name: - en: New attribute - handle: new-attribute - position: 2 - system: false - lookups: null - required: false - type: text - localised: false - searchable: false - variant: false - translatable: false - scopeable: false - filterable: false - meta: - lang: en - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - group_id: - type: array - items: - type: string - name: - type: array - items: - type: string - handle: - type: array - items: - type: string - examples: - full-unprocessable: - value: - group_id: - - The group id field is required. - name: - - The name field is required. - handle: - - The handle field is required. - operationId: post-attributes - description: Create a new attribute - requestBody: - content: - application/json: - schema: - type: object - properties: - group_id: - type: string - example: q1jo3jm4 - name: - type: array - items: - type: object - properties: - locale: - type: string - required: - - locale - handle: - type: string - position: - type: integer - format: int32 - example: 12 - filterable: - type: boolean - default: false - scopeable: - type: boolean - default: false - translatable: - type: boolean - default: false - variant: - type: boolean - default: false - searchable: - type: boolean - default: false - localised: - type: boolean - default: false - type: - type: string - default: text - required: - type: boolean - default: false - lookups: - type: array - items: - type: object - properties: - label: - type: string - value: - type: string - system: - type: boolean - default: false - required: - - group_id - - name - - handle - examples: - post-example: - value: - group_id: q1w24k3l - name: - en: New attribute - handle: new-attribute - lookups: - label: Lookup 1 - value: lookup-1 - parameters: [] - /attributes/order: - put: - summary: Update request to reorder attributes - tags: - - Attributes - responses: - "204": - description: No Content - operationId: put-attributes-order - description: Allows you to reorder a target category in relation to another. - requestBody: - content: - application/json: - schema: - title: AttributesReorderBody - type: object - properties: - groups: - type: array - items: - type: object - properties: - groupId: - type: integer - format: int32 - "/attributes/{attributeId}": - parameters: - - schema: - type: string - name: attributeId - in: path - required: true - get: - summary: Get an attribute - tags: - - Attributes - responses: - "200": - description: OK - headers: {} - content: - application/json: - schema: - $ref: "#/paths/~1attributes/post/responses/201/content/application~1json/schema" - examples: - full-example: - value: - data: - id: dnj4zky5 - name: - en: Name - sv: Namn - handle: name - position: "1" - filterable: false - scopeable: true - translatable: true - variant: false - searchable: true - localised: true - type: text - required: true - lookups: null - system: false - example-with-group: - value: - data: - id: dnj4zky5 - name: - en: Name - sv: Namn - handle: name - position: "1" - filterable: false - scopeable: true - translatable: true - variant: false - searchable: true - localised: true - type: text - required: true - lookups: null - system: false - group: - data: - id: vokq5kmj - name: - en: General - handle: general - position: "1" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-attributes-attributeId - parameters: - - schema: - type: string - in: query - name: include - description: "" - description: Returns an attribute from a given ID. - put: - summary: Update an attribute - operationId: put-attributes-attributeId - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1attributes/post/responses/201/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - not-found-example: - value: - error: - http_code: 404 - message: Resource not found - "422": - description: Unprocessable Entity (WebDAV) - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - examples: - validation-example: - value: - name: - - The name field is required. - description: Updates an attribute from a given ID. - parameters: [] - requestBody: - content: - application/json: - schema: - title: UpdateAttributesBody - type: object - properties: - attributes: - type: object - required: - - attributes - tags: - - Attributes - delete: - summary: Delete an attribute - operationId: delete-attributes-attributeId - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - not-found-example: - value: - error: - http_code: 404 - message: Resource not found - tags: - - Attributes - description: Delete an attribute. - parameters: [] - /attribute-groups: - get: - summary: Paginated list of Attribute Groups - tags: - - Attributes - responses: - "200": - description: OK - content: - application/json: - schema: - title: AttributeGroupCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1attribute-groups~1%7BattributeGroupId%7D/put/requestBody/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - examples: - without-pagination-example: - value: - data: - - id: vokq5kmj - name: - en: General - handle: general - position: "1" - - id: mqkj8wyj - name: - en: Meta Information - sv: SEO - handle: seo - position: "2" - - id: 96e7nk8r - name: - en: Default - handle: default - position: "4" - operationId: get-attribute-groups - parameters: - - schema: - type: boolean - default: false - in: query - name: all_records - description: Will skip pagination and return all records - - schema: - type: string - in: query - name: include - description: Returns a paginated list of available attribute groups - post: - summary: Create an Attribute Group - tags: - - Attributes - responses: - "200": - description: OK - content: - application/json: - schema: - title: AttributeGroupResponse - type: object - properties: - data: - $ref: "#/paths/~1attribute-groups~1%7BattributeGroupId%7D/put/requestBody/content/application~1json/schema" - examples: - created-example: - value: - data: - id: m1wrpejy - name: - en: New Group - handle: new-group - position: "13" - operationId: post-attribute-groups - requestBody: - content: - multipart/form-data: - schema: - title: CreateAttributeGroupBody - type: object - properties: - name: - type: object - required: - - en - properties: - en: - type: object - required: - - name - properties: - name: - type: string - examples: - create-example: - value: - name: - en: New Group - handle: new-group - /attribute-groups/reorder: - put: - summary: Reorder attribute groups - tags: - - Attributes - responses: - "204": - description: No Content - "422": - description: Unprocessable Entity - content: - application/json: - schema: - title: AttributeGroupOrderUnprocessableResponse - type: object - properties: - attributes: - type: array - items: - type: string - examples: - uprocessable-response: - value: - groups: - - The groups field is required. - operationId: put-attribute-groups-reorder - requestBody: - content: - application/json: - schema: - title: AttributeGroupReorderBody - type: object - properties: - groups: - type: array - items: - type: object - properties: - groupId: - type: integer - format: int32 - examples: - reorder-request-example: - value: - groups: - vokq5kmj: 1 - mqkj8wyj: 2 - description: Sends a request to reorder the attribute groups in the system - "/attribute-groups/{attributeGroupId}": - parameters: - - schema: - type: string - name: attributeGroupId - in: path - required: true - put: - summary: Update an attribute group - tags: - - Attributes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1attribute-groups/post/responses/200/content/application~1json/schema" - operationId: put-attribute-groups-attributeGroupId - description: Updates an attribute group. - requestBody: - content: - application/json: - schema: - title: AttributeGroup - type: object - x-examples: - full-example: - id: vokq5kmj - name: - en: General - handle: general - position: "1" - description: |- - ### Available includes - - attributes - properties: - id: - type: string - name: - type: object - handle: - type: string - position: - type: integer - format: int32 - attributes: - $ref: "#/paths/~1attributes/get/responses/200/content/application~1json/schema" - get: - summary: Get a single attribute group - tags: - - Attributes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1attribute-groups/post/responses/200/content/application~1json/schema" - examples: - full-example: - value: - data: - id: vokq5kmj - name: - en: General - handle: general - position: "1" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - not-found-example: - value: - error: - http_code: 404 - message: Resource not found - operationId: get-attribute-groups-attributeGroupId - parameters: - - schema: - type: integer - in: query - name: include - description: Gets a single attribute group - delete: - summary: Delete an attribute group - tags: - - Attributes - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-attribute-groups-attributeGroupId - description: Deletes an attribute group - /categories: - get: - summary: Get Categories - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - title: CategoryCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Category - type: object - x-examples: - Standard with mapped attributes: - id: v8l4pl01 - sort: min_price:asc - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - description: Category description - meta_keywords: null - name: Fasteners - page_title: Seo page title - short_description: Short Description - meta_description: Page meta description - layout: [] - primary_asset: [] - Full response: - id: v8l4pl01 - sort: min_price:asc - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - attribute_data: - description: - webstore: - en: Category description - meta_keywords: - webstore: - en: "" - name: - webstore: - en: Category name - page_title: - webstore: - en: Page title - short_description: - webstore: - en: Short description - meta_description: - webstore: - en: Meta description - layout: [] - primary_asset: [] - Full response with child: - id: v8l4pl01 - sort: min_price:asc - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - attribute_data: - description: - webstore: - en: Category description - meta_keywords: - webstore: - en: "" - name: - webstore: - en: Category name - page_title: - webstore: - en: Page title - short_description: - webstore: - en: Short description - meta_description: - webstore: - en: Meta description - children: - data: - - id: p09prlrn - sort: min_price:asc - left_pos: 2 - right_pos: 937 - attribute_data: - description: - webstore: - en: "" - meta_keywords: - webstore: - en: "" - name: - webstore: - en: Child Category - page_title: - webstore: - en: Child category SEO title - short_description: - webstore: - en: "" - meta_description: - webstore: - en: Child Category meta description - layout: [] - primary_asset: [] - layout: [] - primary_asset: [] - description: "" - properties: - id: - type: string - sort: - type: string - products_count: - type: integer - children_count: - type: integer - left_pos: - type: integer - right_pos: - type: integer - name: - type: string - attribute_data: - type: object - children: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema" - channels: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema" - ancestors: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema" - routes: - $ref: "#/paths/~1routes/get/responses/200/content/application~1json/schema" - layout: - title: LayoutResponse - type: object - properties: - data: - $ref: "#/paths/~1layouts/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - assets: - title: AssetCollection - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1assets/put/responses/200/content/application~1json/schema/properties/data" - primary_asset: - $ref: "#/paths/~1assets/put/responses/200/content/application~1json/schema" - attributes: - $ref: "#/paths/~1attributes/get/responses/200/content/application~1json/schema" - customer_groups: - $ref: "#/paths/~1customer-groups/get/responses/200/content/application~1json/schema" - products: - $ref: "#/paths/~1products/get/responses/200/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-categories - description: Returns a paginated resource of categories - "/categories/{categoryId}": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - description: "" - get: - summary: Return a single category - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - title: CategoryResponse - type: object - properties: - data: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - examples: {} - operationId: get-categories-categoryId - parameters: - - schema: - type: string - in: query - name: includes - description: Returns a single category from a given ID - put: - summary: Update a category - operationId: put-categories-categoryId - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - requestBody: - content: - application/json: - schema: - title: CreateCategoryBody - type: object - properties: - url: - type: string - path: - type: string - name: - type: object - required: - - en - properties: - en: - type: string - required: - - name - - path - - url - description: "" - tags: - - Categories - description: Update a category using a given ID. - "/categories/{categoryId}/layouts": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - post: - summary: Update a category layout - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - operationId: post-categories-category-layouts - requestBody: - content: - application/json: - schema: - title: CategoryAttachLayoutBody - type: object - properties: - layout_id: - type: string - description: Attaches layouts to a category resource - "/categories/{categoryId}/routes": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - post: - summary: Update a category's routes - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-categories-category-routes - description: Attaches routes to a category resource. - requestBody: - content: - application/json: - schema: - title: AttachCategoryRoutesBody - type: object - properties: - redirect: - type: boolean - description: - type: string - slug: - type: string - locale: - type: string - "/categories/parent/{parentId}": - parameters: - - schema: - type: string - name: parentId - in: path - description: If omitted will return top level catgories - required: true - get: - summary: Get categories by parent id - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema" - operationId: get-categories-parent-parentId? - description: Returns categories by a given parent ID. - parameters: - - schema: - type: string - in: query - name: include - /categories/reorder: - post: - summary: Reorder a category - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - title: Message - type: object - properties: - message: - type: string - examples: - success-response: - value: - status: success - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-categories-reorder - requestBody: - content: - application/json: - schema: - title: ReorderCategoryBody - type: object - properties: - action: - type: string - description: before, after, over - moved_node: - type: string - description: The ID of the category which moved - node: - type: string - description: The id of the category affected - required: - - node - - moved_node - "/categories/{categoryId}/products": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - put: - summary: Attach products - operationId: put-categories-categoryId-products - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - tags: - - Categories - requestBody: - content: - application/json: - schema: - title: AttachCategoryProductsBody - type: object - properties: - products: - type: array - items: - type: object - properties: - id: - type: string - position: - type: integer - format: int32 - example: 1 - sort_type: - type: string - description: custom, min_price:asc, min_price:desc, sku:asc, sku:desc - examples: - example-1: {} - description: When using "custom" sort type, sorting will be based on the position. - description: Attaches products to a category resource. - "/categories/{categoryId}/channels": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - post: - summary: Attach channels to a category - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-categories-categoryId-channels - requestBody: - content: - application/json: - schema: - title: AttachCategoryChannelsBody - type: object - properties: - channels: - type: array - items: - type: object - properties: - id: - type: string - published_at: - type: string - required: - - id - - published_at - required: - - channels - description: "" - description: Attaches channels to a catagory - "/categories/{categoryId}/drafts": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - post: - summary: Create or retrieve the current category draft - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-categories-categoryId-drafts - description: Create or return the current category draft resource. - parameters: - - schema: - type: string - in: query - name: include - "/categories/{categoryId}/customer-groups": - parameters: - - schema: - type: string - name: categoryId - in: path - required: true - post: - summary: Attach customer groups to a category - tags: - - Categories - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1%7BcategoryId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - type: object - properties: - "": - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-categories-categoryId-customer-groups - requestBody: - content: - application/json: - schema: - title: AttachCategoryCustomerGroupsBody - type: object - properties: - groups: - type: array - items: - type: object - properties: - id: - type: string - visible: - type: boolean - purchasable: - type: boolean - description: Attaches customer groups to a category resource. - /assets: - put: - summary: Update Assets - operationId: put-assets - responses: - "200": - description: OK - content: - application/json: - schema: - title: AssetResponse - type: object - properties: - data: - title: Asset - type: object - properties: - id: - type: string - title: - type: string - type: - type: string - caption: - type: string - kind: - type: string - external: - type: boolean - position: - type: integer - primary: - type: boolean - url: - type: string - sub_kind: - type: string - extension: - type: string - original_filename: - type: string - size: - type: string - width: - type: string - height: - type: string - transforms: - title: AssetTransformCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: AssetTransform - type: object - properties: - id: - type: string - handle: - type: string - url: - type: string - - type: object - properties: - meta: - type: object - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - tags: - $ref: "#/paths/~1tags/get/responses/200/content/application~1json/schema" - requestBody: - content: - application/json: - schema: - title: UpdateAssetBody - type: object - properties: - assets: - type: array - items: - type: object - properties: - id: - type: string - tags: - type: array - items: - type: string - required: - - id - description: "" - tags: - - Assets - description: Update all assets in the given array of ids. - post: - summary: Create Asset - tags: - - Assets - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1assets/put/responses/200/content/application~1json/schema" - operationId: post-assets - requestBody: - content: - multipart/form-data: - schema: - title: CreateAssetBody - type: object - properties: - mime_type: - type: string - description: Required when passing url - url: - type: string - description: External URL to file to upload, required without file - file: - type: object - description: "" - description: Upload an asset to a model - /assets/simple: - post: - summary: Simple asset upload - tags: - - Assets - responses: - "200": - description: OK - content: - application/json: - schema: - title: AssetSimple - type: object - properties: - thumbnail_url: - type: string - thumbnail: - type: string - url: - type: string - filename: - type: string - path: - type: string - examples: - upload-example: - value: - path: uploads/01/04/WGWVDkB2VXkDKx4UeeLnStBwnEJXGL2Vd6j5Le1P.png - filename: the-secret-recipe.png - url: http://storefront.test/storage/uploads/01/04/WGWVDkB2VXkDKx4UeeLnStBwnEJXGL2Vd6j5Le1P.png - thumbnail: uploads/01/04/thumbnails/WGWVDkB2VXkDKx4UeeLnStBwnEJXGL2Vd6j5Le1P.png - thumbnail_url: http://storefront.test/storage/uploads/01/04/thumbnails/WGWVDkB2VXkDKx4UeeLnStBwnEJXGL2Vd6j5Le1P.png - operationId: post-assets-simple - description: This endpoint allows you to upload an asset without having to - attach it to a model. This is good for one time uploads where you just - want to get back a URL - requestBody: - content: - multipart/form-data: - schema: - title: AssetSimpleUploadBody - type: object - properties: - file: - type: object - required: - - file - description: "" - "/assets/{assetId}/detach/{ownerId}": - parameters: - - schema: - type: string - name: assetId - in: path - required: true - description: The hashed asset id - - schema: - type: string - name: ownerId - in: path - required: true - description: The hashed owner id - post: - summary: Detach an asset from it's model - tags: - - Assets - responses: - "204": - description: No Content - operationId: post-assets-assetId-detach-ownerId - requestBody: - content: - application/json: - schema: - title: AssetDetachBody - type: object - properties: - attributes: - type: array - description: attributeId => position - items: - type: object - description: Detaches any assets from a given model. Useful if you want to - remove certain assets from a product (or another model) without deleting - the asset itself. - /taxes: - get: - summary: Get taxes - responses: - "200": - description: OK - content: - application/json: - schema: - title: TaxCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Tax - type: object - properties: - id: - type: string - name: - type: string - percentage: - type: number - default: - type: boolean - x-examples: - tax-example: - id: xndgx1jz - name: VAT - percentage: 20 - default: true - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1saved/get/responses/401/content/application~1json/schema" - operationId: get-taxes - description: Get a paginated list of taxes - tags: - - Taxes - post: - summary: Create tax - tags: - - Taxes - responses: - "200": - description: OK - content: - application/json: - schema: - title: TaxResponse - type: object - properties: - data: - $ref: "#/paths/~1taxes/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - percentage: - type: array - items: - type: string - operationId: post-taxes - description: Create a new tax resource. - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: Unique name - percentage: - type: number - required: - - name - - percentage - "/taxes/{taxId}": - parameters: - - schema: - type: string - name: taxId - in: path - required: true - get: - summary: Get tax record - tags: - - Taxes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1taxes/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-taxes-taxId - description: Get a tax record by it's ID - put: - summary: Update tax record - tags: - - Taxes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1taxes/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - percentage: - type: array - items: - type: string - operationId: put-taxes-taxId - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: Must be unique - percentage: - type: number - required: - - name - - percentage - description: Update a tax record by it's ID - delete: - summary: Delete tax record - tags: - - Taxes - responses: - "204": - description: No Content - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1baskets~1saved/get/responses/401/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-taxes-taxId - description: Delete a tax record by it's ID. - /associations/groups: - get: - summary: Paginated array of association groups - tags: - - Associations - responses: - "200": - description: OK - content: - application/json: - schema: - title: AssociationGroupCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: AssociationGroup - type: object - properties: - id: - type: string - name: - type: string - handle: - type: string - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - examples: - full-response: - value: - data: - - id: v8l4pl01 - name: Upsell - handle: upsell - - id: p09prlrn - name: Cross-sell - handle: cross-sell - - id: wz6d39dj - name: Alternate - handle: alternate - meta: - lang: en - pagination: - total: 3 - count: 3 - per_page: 50 - current_page: 1 - total_pages: 1 - links: [] - operationId: get-associations-groups - description: Returns a paginated response of association groups available in the - system - "/collections/{collectionId}/routes": - parameters: - - schema: - type: string - name: collectionId - in: path - required: true - post: - summary: Update a collection's routes - tags: - - Collections - responses: - "200": - description: OK - content: - application/json: - schema: - title: CollectionResponse - type: object - properties: - data: - title: Collection - type: object - description: |- - ### Available includes - - - routes - - layout - - channels - - assets - - attributes - - products - - customerGroups - properties: - id: - type: string - attribute_data: - type: object - routes: - $ref: "#/paths/~1routes/get/responses/200/content/application~1json/schema" - layout: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items/properties/layout" - channels: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema" - assets: - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items/properties/assets" - attributes: - $ref: "#/paths/~1attributes/get/responses/200/content/application~1json/schema" - products: - $ref: "#/paths/~1products/get/responses/200/content/application~1json/schema" - customer_groups: - $ref: "#/paths/~1customer-groups/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-collections-collection-routes - description: This request will allow you to attach routes to a collection - requestBody: - content: - application/json: - schema: - type: object - properties: - redirect: - type: boolean - description: - type: string - slug: - type: string - locale: - type: string - "/collections/{collectionId}/products": - parameters: - - schema: - type: string - name: collectionId - in: path - required: true - post: - summary: Update a collection's products - tags: - - Collections - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1collections~1%7BcollectionId%7D~1routes/post/responses/200/content/application~1json/schema" - operationId: post-collections-collectionId-products - requestBody: - content: - application/json: - schema: - type: object - properties: - products: - type: array - description: Pass all products you wish to be associated. - items: - type: string - examples: - example-with-products: - value: - products: - - 4r221sfef - - 534fw3r3s - - 58823sese - description: Syncs products with a collection. - /collections: - get: - summary: Get Collections - tags: - - Collections - responses: - "200": - description: OK - content: - application/json: - schema: - title: CollectionCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1collections~1%7BcollectionId%7D~1routes/post/responses/200/content/application~1json/schema/properties/data" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-collections - parameters: - - schema: - type: string - enum: - - routes - - layout - - channels - - assets - - attributes - - routes - - products - - customer_groups - in: query - name: include - description: "" - - schema: - type: string - default: "25" - in: query - name: per_page - - schema: - type: string - in: query - name: full_response - - schema: - type: string - in: query - name: sort - - schema: - type: string - in: query - name: page - description: Get a paginated response of collections. - post: - summary: Create Collection - operationId: post-collections - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1collections~1%7BcollectionId%7D~1routes/post/responses/200/content/application~1json/schema" - tags: - - Collections - description: Create a new collection. - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: object - properties: - en: - type: string - url: - type: string - examples: - new-example: - value: - name: - en: New Collection - url: new-collection - description: Creates a new collection. - "/collections/{collectionId}": - parameters: - - schema: - type: string - name: collectionId - in: path - required: true - get: - summary: Single Collection - tags: - - Collections - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1collections~1%7BcollectionId%7D~1routes/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-collections-collectionId - description: Get a single Collection by its ID - parameters: - - schema: - type: string - in: query - name: include - put: - summary: Update Collection - tags: - - Collections - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1collections~1%7BcollectionId%7D~1routes/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - operationId: put-collections-collectionId - description: Update a Collection by its ID. - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: object - properties: - en: - type: string - delete: - summary: Delete Collection - tags: - - Collections - responses: - "204": - description: No Content - operationId: delete-collections-collectionId - description: Delete a Collection by its ID - /discounts: - get: - summary: Get Discounts - responses: - "200": - description: OK - content: - application/json: - schema: - title: DiscountCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Discount - type: object - x-examples: - discount-simple-example: - id: 1xl0e6n4 - start_at: 2019-06-04 14:27:02 - end_at: null - priority: 0 - status: 1 - stop_rules: false - uses: 0 - name: Buy one get one free - discount-full-example: - id: 1xl0e6n4 - start_at: 2019-06-04 14:27:02 - end_at: null - priority: 0 - status: 1 - stop_rules: false - uses: 0 - attribute_data: - name: - webstore: - en: Buy one get one free - properties: - id: - type: string - start_at: - type: string - format: date-time - end_at: - type: string - format: date-time - priority: - type: boolean - status: - type: boolean - stop_rules: - type: boolean - uses: - type: integer - format: int32 - required: - - id - description: |- - ### Available includes - - sets - - rewards - - items - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-discounts - description: Returns a paginated list of Discounts - tags: - - Discounts - post: - summary: Create Discount - tags: - - Discounts - responses: - "200": - description: OK - content: - application/json: - schema: - title: DiscountResponse - type: object - properties: - data: - $ref: "#/paths/~1discounts/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - operationId: post-discounts - description: Create a new Discount. - requestBody: - content: - application/json: - schema: - type: object - properties: - start_at: - type: string - format: date-time - example: 2017-07-21T17:32:28Z - end_at: - type: string - format: date-time - example: 2017-07-21T17:32:28Z - name: - type: object - properties: - en: - type: string - example: Buy one get one free - uses: - type: integer - status: - type: boolean - channels: - type: array - items: - type: object - properties: - id: - type: string - published_at: - type: string - format: date-time - example: 2017-07-21T17:32:28Z - required: - - id - required: - - name - examples: - create-example: - value: - name: - en: Buy one get one free - start_at: 2019-06-04 14:27:02 - end_at: 2019-06-04 14:27:02 - channels: - - id: 1xl0e6n4 - published_at: 2019-06-04 14:27:02 - description: Create a base Discount for editing. - "/discounts/{discountId}": - parameters: - - schema: - type: string - name: discountId - in: path - required: true - get: - summary: Get a Discount - tags: - - Discounts - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1discounts/post/responses/200/content/application~1json/schema" - operationId: get-discounts-discountId - description: Returns a Discount by it's ID. - parameters: - - schema: - type: string - in: query - name: include - put: - summary: Update Discount - tags: - - Discounts - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1discounts/post/responses/200/content/application~1json/schema" - operationId: put-discounts-discountId - description: Updates a Discount - requestBody: - content: - application/json: - schema: - type: object - properties: - start_at: - type: string - format: date-time - end_at: - type: string - priority: - type: integer - stop_rules: - type: boolean - status: - type: boolean - channels: - type: object - properties: - data: - type: object - properties: - id: - type: string - published_at: - type: string - rewards: - type: object - properties: - data: - type: object - properties: - products: - type: array - items: - type: object - properties: - product_id: - type: string - quantity: - type: integer - sets: - type: object - properties: - data: - type: array - items: - type: object - properties: - scope: - type: string - outcome: - type: boolean - items: - type: object - properties: - data: - type: array - items: - type: object - properties: - eligibles: - type: array - items: - type: string - type: - type: string - required: - - start_at - delete: - summary: Delete Discount - tags: - - Discounts - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-discounts-discountId - description: Deletes a Discount - /layouts: - get: - summary: Get Layouts - tags: - - Layouts - responses: - "200": - description: OK - content: - application/json: - schema: - title: LayoutCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Layout - type: object - properties: - id: - type: string - name: - type: string - handle: - type: string - type: - type: string - required: - - id - - name - - handle - - type - x-examples: - layout-example: - id: jn63vl0d - name: Category listing - handle: category_listing - type: category - description: "" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-layouts - description: Get a paginated list of Layouts - /users: - get: - summary: Get all users - responses: - "200": - description: OK - content: - application/json: - schema: - title: UserCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1users/post/responses/201/content/application~1json/schema" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - description: "" - "401": - description: Unauthorized - content: - application/json: - schema: - title: Unauthorized - type: object - properties: - error: - type: string - x-examples: - unauthorized-example: - error: Unauthorized. - operationId: get-users - parameters: - - schema: - type: string - in: query - name: include - description: Comma separated includes for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page - description: Gets a paginated list of all users - tags: - - Users - post: - summary: Create a new user - description: Create a new user resource - responses: - "201": - description: OK - content: - application/json: - schema: - title: User - type: object - properties: - id: - type: string - example: y3g6v91o - name: - type: string - example: Name - email: - type: string - example: email@test.com - "403": - description: Forbidden - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - operationId: post-users - requestBody: - content: - application/json: - schema: - title: CreateUserBody - type: object - properties: - email: - type: string - firstname: - type: string - lastname: - type: string - password: - type: string - password_confirmation: - type: string - customer_id: - type: string - nullable: false - examples: {} - description: "" - tags: - - Users - "/users/{userId}": - parameters: - - schema: - type: string - name: userId - in: path - required: true - get: - summary: Get user - tags: - - Users - responses: - "200": - description: OK - content: - application/json: - schema: - title: UserResponse - type: object - properties: - data: - $ref: "#/paths/~1users/post/responses/201/content/application~1json/schema" - x-tags: - - Users - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-users-userId - description: Get a user by their given ID. - put: - summary: "" - operationId: put-users-userId - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1users~1%7BuserId%7D/get/responses/200/content/application~1json/schema" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1users/get/responses/401/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - $ref: "#/paths/~1channels/post/responses/422/content/application~1json/schema" - tags: - - Users - requestBody: - content: - application/json: - schema: - type: object - properties: - email: - type: string - password: - type: string - password_confirmation: - type: string - required: - - email - description: Updates a user record from their ID. - delete: - summary: "" - operationId: delete-users-userId - responses: - "204": - description: No Content - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1users/get/responses/401/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - tags: - - Users - description: Delete a user by their given ID. - /users/fields: - get: - summary: Get custom user fields - tags: - - Customers - responses: - "200": - description: OK - content: - application/json: - schema: - title: UserFieldsResponse - type: object - properties: - data: - title: UserFields - type: object - properties: - fields: - type: object - examples: - basic-example: - value: - data: - fields: - account_number: - label: Account Number - type: text - operationId: get-users-fields - description: This endpoint returns any available user fields which have been - defined in the getcandy config. - /users/current: - get: - summary: Get the current user - tags: - - Users - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1users~1%7BuserId%7D/get/responses/200/content/application~1json/schema" - operationId: get-users-current - description: Returns the user associated to the access token. - "/payments/{transactionId}/refund": - parameters: - - schema: - type: string - name: transactionId - in: path - required: true - post: - summary: Refund a payment - tags: - - Payments - responses: - "200": - description: OK - content: - application/json: - schema: - title: TransactionResponse - type: object - properties: - data: - title: Transaction - type: object - properties: - id: - type: string - transaction_id: - type: string - merchant: - type: string - amount: - type: integer - format: int32 - card_type: - type: string - last_four: - type: string - provider: - type: string - driver: - type: string - success: - type: boolean - refund: - type: boolean - address_matched: - type: boolean - cvc_matched: - type: boolean - threed_secure: - type: boolean - postcode_matched: - type: boolean - status: - type: string - notes: - type: string - created_at: - type: string - format: date - x-examples: - normal-example: - id: 2sef73d8ssefsf8 - transaction_id: 1234-1234-1234-1234 - merchant: mystorefront - amount: 7845 - card_type: Visa - last_four: "1234" - provider: SagePay - driver: sagepay - success: true - refund: false - address_matched: true - cvc_matched: true - threed_secure: true - postcode_matched: true - status: Ok - notes: null - created_at: 2020-04-30T11:44:34.000000Z - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - examples: - refund-issued-error: - value: - http_code: 400 - message: Refund already issued - invalid-balance-error: - value: - http_code: 400 - message: Amount exceeds remaining balance - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-payments-id-refund - requestBody: - content: - multipart/form-data: - schema: - type: object - properties: - amount: - type: integer - format: int32 - description: If left blank, the full amount will be refunded - notes: - type: string - description: "" - description: Refund a transaction - "/payments/{transactionId}/void": - parameters: - - schema: - type: string - name: transactionId - in: path - required: true - post: - summary: Void a payment - tags: - - Payments - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1payments~1%7BtransactionId%7D~1refund/post/responses/200/content/application~1json/schema" - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-payments-transactionId-void - description: Voids a payment in the system. - /payments/3d-secure: - post: - summary: Threed Secure Payment - tags: - - Payments - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1orders/post/responses/200/content/application~1json/schema" - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-payments-3d-secure - description: Validate a ThreeD secure request and process the transaaction, if - your payment provider supports it. - requestBody: - content: - application/json: - schema: - type: object - properties: - paRes: - type: string - transaction: - type: string - order_id: - type: string - required: - - paRes - - transaction - - order_id - /payments/provider: - get: - summary: Get Payment Provider - tags: - - Payments - operationId: get-payments-provider - description: Gets the default, configured payment provider. - responses: - "200": - description: OK - content: - application/json: - schema: - title: PaymentProviderResponse - type: object - properties: - data: - title: PaymentProvider - type: object - properties: - id: - type: string - name: - type: string - client_token: - type: string - description: | - If supported - exires_at: - type: string - description: If supported - x-examples: - provider-example: - name: SagePay - client_token: 123456-1234-1234-1234-123456789 - exires_at: 2020-04-03T13:24:23.211000Z - /payments/types: - get: - summary: Payment types - tags: - - Payments - responses: - "200": - description: OK - content: - application/json: - schema: - title: PaymentTypeCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: PaymentType - type: object - properties: - id: - type: string - name: - type: string - handle: - type: string - driver: - type: string - success_status: - type: string - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-payments-types - description: Returns a list of available Payment Types in the system - /reports/sales: - get: - summary: Get sales report - tags: - - Reports - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - labels: - type: array - items: - type: string - datasets: - type: array - items: - type: object - properties: - label: - type: string - backgroundColor: - type: string - yAxisId: - type: string - borderColor: - type: string - data: - type: array - items: - type: string - fill: - type: boolean - examples: - monthly-example: - value: - labels: - - January 2019 - - February 2019 - - March 2019 - - April 2019 - - May 2019 - - June 2019 - - July 2019 - - August 2019 - - September 2019 - - October 2019 - - November 2019 - - December 2019 - datasets: - - label: Orders - backgroundColor: "#E7028C" - yAxisID: A - borderColor: "#E7028C" - data: - - 4803 - - 4703 - - 5147 - - 4805 - - 4964 - - 4907 - - 5525 - - 4734 - - 5210 - - 5733 - - 4867 - - 3258 - fill: false - - label: Revenue - backgroundColor: "#0099e5" - yAxisID: B - borderColor: "#0099e5" - data: - - "44492929" - - "42988674" - - "46779741" - - "43873165" - - "45938827" - - "47167482" - - "52133245" - - "43647302" - - "48852965" - - "54670528" - - "46422960" - - "31355558" - fill: false - weekly-example: - value: - labels: - - Week Comm. 02/01/2020 - - Week Comm. 06/01/2020 - - Week Comm. 16/01/2020 - - Week Comm. 20/01/2020 - - Week Comm. 29/01/2020 - datasets: - - label: Orders - backgroundColor: "#E7028C" - yAxisID: A - borderColor: "#E7028C" - data: - - 347 - - 1297 - - 1335 - - 1314 - - 1231 - fill: false - - label: Revenue - backgroundColor: "#0099e5" - yAxisID: B - borderColor: "#0099e5" - data: - - "2980381" - - "13029474" - - "12304326" - - "12530776" - - "10988167" - fill: false - daily-example: - value: - labels: - - 1st January 2020 - - 2nd January 2020 - - 3rd January 2020 - - 4th January 2020 - - 5th January 2020 - - 6th January 2020 - - 7th January 2020 - datasets: - - label: Orders - backgroundColor: "#E7028C" - yAxisID: A - borderColor: "#E7028C" - data: - - 20 - - 157 - - 121 - - 22 - - 27 - - 247 - - 255 - fill: false - - label: Revenue - backgroundColor: "#0099e5" - yAxisID: B - borderColor: "#0099e5" - data: - - "102132" - - "1370854" - - "1096602" - - "161444" - - "249349" - - "2925187" - - "2263469" - fill: false - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - message: - type: string - errors: - type: array - items: - type: object - properties: - from: - type: array - items: - type: string - to: - type: array - items: - type: string - examples: - unprocessable-example: - value: - message: The given data was invalid. - errors: - from: - - The from field is required. - - The from is not a valid date. - to: - - The to field is required. - - The to is not a valid date. - operationId: get-reports-sales - parameters: - - schema: - type: string - in: query - name: from - description: The from date - required: true - - schema: - type: string - in: query - name: to - description: The to date - - schema: - type: string - enum: - - weekly - - monthly - - daily - in: query - name: mode - description: The dataset mode - description: >- - This endpoints returns sales report figures. - - - > Currently this provides data suitable for chartjs.org but this is subject to change and be more decoupled in a future release. - /reports/orders: - get: - summary: Get sales report - tags: - - Reports - responses: - "200": - description: OK - content: - application/json: - schema: - type: array - items: - type: object - properties: - month: - type: string - sub_total: - type: string - delivery_total: - type: string - tax_total: - type: string - order_total: - type: string - discount_total: - type: string - examples: - dataset-example: - value: - - month: March 2020 - sub_total: "2163897" - delivery_total: "145489" - tax_total: "460781" - order_total: "2770167" - discount_total: "0" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - message: - type: string - errors: - type: array - items: - type: object - properties: - from: - type: array - items: - type: string - to: - type: array - items: - type: string - examples: - invalid-missing-data-example: - value: - message: The given data was invalid. - errors: - from: - - The from field is required. - - The from is not a valid date. - to: - - The to field is required. - - The to is not a valid date. - operationId: get-reports-orders - description: Returns a report for orders between a given date range - parameters: - - schema: - type: string - in: query - name: from - description: The from date - - schema: - type: string - in: query - name: to - description: The to date - /reports/orders/customers: - get: - summary: Get Customer Report - tags: - - Reports - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - "202003": - type: object - properties: - label: - type: string - new: - type: integer - returning: - type: integer - total: - type: integer - examples: - basic-example: - value: - "202003": - label: March 2020 - new: 204 - returning: 1980 - total: 2184 - operationId: get-reports-orders-customers - description: Returns a monthly grouped list of new/returning and total customers. - parameters: - - schema: - type: string - in: query - name: from - description: The from date - - schema: - type: string - in: query - name: to - description: The to date - /reports/orders/averages: - get: - summary: Get order averages - tags: - - Reports - responses: - "200": - description: OK - content: - application/json: - schema: - type: array - items: - type: object - properties: - date: - type: string - sub_total: - type: string - delivery_total: - type: string - tax_total: - type: string - order_total: - type: string - discount_total: - type: string - examples: - monthly-example: - value: - - date: March 2020 - sub_total: "8303" - delivery_total: "519" - tax_total: "1762" - order_total: "10584" - discount_total: "6" - weekly-example: - value: - - date: Week Comm. 02/03/2020 - sub_total: "7904" - delivery_total: "535" - tax_total: "1686" - order_total: "10125" - discount_total: "0" - operationId: get-reports-orders-averages - parameters: - - schema: - type: string - in: query - name: from - description: The from date - - schema: - type: string - in: query - name: to - description: The to date - - schema: - type: string - enum: - - weekly - - daily - - yearly - - monthly - in: query - name: mode - description: "" - description: Returns an array of order averages between a given date range. - /reports/products/best-sellers: - get: - summary: Get best selling products - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - date: - type: object - properties: - products: - type: array - items: - type: object - properties: - product_count: - type: integer - description: - type: string - sku: - type: string - month: - type: string - examples: - result-example: - value: - 2020-02-01T10:53:53.000000Z: - products: - - product_count: 149 - description: Bertie botts every flavor beans - sku: bbefb - month: 2020-02-01 - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - message: - type: string - errors: - type: array - items: - type: object - properties: - from: - type: array - items: - type: string - to: - type: array - items: - type: string - examples: - unprocessable-example: - value: - message: The given data was invalid. - errors: - from: - - The from field is required. - to: - - The to field is required. - operationId: get-products-best-sellers - description: Gets best selling products grouped by month. - tags: - - Reports - parameters: - - schema: - type: string - in: query - name: from - description: The from date - - schema: - type: string - in: query - name: to - description: The to date - parameters: [] - "/reports/metrics/{subject}": - parameters: - - schema: - type: string - enum: - - sales - - orders - name: subject - in: path - required: true - get: - summary: Get metric data - tags: - - Reports - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - current_month: - type: string - previous_month: - type: string - today: - type: string - yesterday: - type: string - current_week: - type: string - previous_week: - type: string - examples: - orders-example: - value: - current_month: 0 - previous_month: 1077 - today: 0 - yesterday: 0 - current_week: 0 - previous_week: 0 - sales-example: - value: - current_month: 0 - previous_month: "8626214" - today: 0 - yesterday: 0 - current_week: 0 - previous_week: 0 - operationId: get-reports-metrics-subject - description: Returns metric (KPI) data for either sales or orders. - /saved-searches/product: - get: - summary: Get saved searches - tags: - - Search - responses: - "200": - description: OK - content: - application/json: - schema: - title: SavedSearchCollection - allOf: - - properties: - data: - type: array - items: - title: SavedSearch - type: object - properties: - id: - type: string - name: - type: string - payload: - type: object - x-examples: - saved-search-example: - id: wz6d39dj - name: Red Sweets - payload: - keywords: Red sweets - type: object - operationId: get-saved-searches - description: Returns a list of current saved searches for products - parameters: [] - "/saved-searches/{savedSearchId}": - parameters: - - schema: - type: string - name: savedSearchId - in: path - required: true - get: - summary: Get saved search - tags: - - Search - responses: - "200": - description: OK - content: - application/json: - schema: - title: SavedSearchResponse - type: object - properties: - data: - $ref: "#/paths/~1saved-searches~1product/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - delete: - summary: Delete saved search - operationId: delete-saved-searches-savedSearchId - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - description: Delete a saved search entry by ID. - tags: - - Search - /settings: - get: - summary: Get settings - tags: - - Settings - responses: - "200": - description: OK - content: - application/json: - schema: - title: SettingCollection - type: object - properties: - data: - type: array - items: - title: Setting - type: object - properties: - id: - type: string - name: - type: string - handle: - type: string - content: - type: object - x-examples: - products-example: - id: v8l4pl01 - name: Products - handle: products - content: - transforms: - - large_thumbnail - asset_source: products - operationId: get-settings - description: Retrieves settings that have been defined in the database. - "/settings/{handle}": - parameters: - - schema: - type: string - name: handle - in: path - required: true - get: - summary: Get setting - tags: - - Settings - responses: - "200": - description: OK - content: - application/json: - schema: - title: SettingResponse - type: object - properties: - data: - $ref: "#/paths/~1settings/get/responses/200/content/application~1json/schema/properties/data/items" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-settings-handle - description: Get specific settings. - /shipping: - get: - summary: Get shipping methods - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingMethodCollection - allOf: - - type: object - properties: - data: - type: array - items: - $ref: "#/paths/~1shipping/post/responses/200/content/application~1json/schema/properties/data" - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-shipping - description: Returns a paginated list of shipping methods - parameters: - - schema: - type: integer - in: query - name: per_page - - schema: - type: string - in: query - name: include - post: - summary: Create shipping method - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - data: - title: ShippingMethod - allOf: - - type: object - properties: - type: - type: string - - title: AttributeData - type: object - properties: - attribute_data: - type: object - - title: LocalisedAttributeData - type: object - properties: - name: - type: string - description: - type: string - x-examples: - translated-example: - id: v8l4pl01 - type: regional - name: Standard Delivery - description: Next working day - full-response-example: - id: v8l4pl01 - type: regional - attribute_data: - name: - webstore: - en: Standard Delivery - description: - webstore: - en: Next Working Day - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - type: - type: array - items: - type: string - operationId: post-shipping - requestBody: - content: - application/json: - schema: - type: object - properties: - type: - type: string - enum: - - standard - - dhl - - regional - name: - type: object - properties: - en: - type: string - required: - - type - description: Create a new shipping method. - parameters: [] - "/shipping/{shippingMethodId}": - parameters: - - schema: - type: string - name: shippingMethodId - in: path - required: true - get: - summary: Get shipping methods - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingMethodResponse - type: object - properties: - data: - $ref: "#/paths/~1shipping/post/responses/200/content/application~1json/schema/properties/data" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-shipping-shippingMethodId - description: Returns a shipping method by it's ID. - parameters: - - schema: - type: string - in: query - name: include - put: - summary: Update shipping method - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1shipping~1%7BshippingMethodId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - type: - type: array - items: - type: string - operationId: put-shipping-shippingMethodId - description: Update shipping method by it's ID - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: object - required: - - en - properties: - en: - type: string - type: - type: string - required: - - name - - type - delete: - summary: Delete shipping method - tags: - - Shipping - responses: - "204": - description: No Content - operationId: delete-shipping-shippingMethodId - description: Deletes a shipping method. - /shipping/zones: - get: - summary: Get Shipping Zones - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingZoneCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: ShippingZone - type: object - properties: - id: - type: string - name: - type: string - x-examples: - basic-example: - id: v8l4pl01 - name: Van Delivery Area - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-shipping-zones - description: Returns a paginated list of shipping zones. - parameters: - - schema: - type: string - in: query - name: include - post: - summary: Create Shipping Zone - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingZoneResponse - type: object - properties: - data: - $ref: "#/paths/~1shipping~1zones/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "404": - description: Not Found - operationId: post-shipping-zones - description: Create a new shipping zone - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - required: - - name - "/shipping/zones/{shippingZoneId}": - parameters: - - schema: - type: string - name: shippingZoneId - in: path - required: true - get: - summary: Get Shipping Zone - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1shipping~1zones/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-shipping-zones-shippingZoneId - description: Get a shipping zone by it's ID. - "/shipping/{shippingMethodId}/prices": - parameters: - - schema: - type: string - name: shippingMethodId - in: path - required: true - post: - summary: Add shipping price - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - title: ShippingPriceResponse - type: object - properties: - data: - $ref: "#/paths/~1orders~1%7BorderId%7D~1shipping~1methods/get/responses/200/content/application~1json/schema/properties/data/items" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - rate: - type: array - items: - type: string - zone_id: - type: array - items: - type: string - currency_id: - type: array - items: - type: string - examples: - unprocessable-example: - value: - rate: - - The rate field is required. - zone_id: - - The zone id field is required. - currency_id: - - The currency id field is required. - operationId: post-shipping-id-prices - description: Add a price to a shipping method. - requestBody: - content: - application/json: - schema: - type: object - properties: - rate: - type: number - zone_id: - type: string - currency_id: - type: string - required: - - rate - - zone_id - - currency_id - description: "" - "/shipping/prices/{shippingPriceId}": - parameters: - - schema: - type: string - name: shippingPriceId - in: path - required: true - put: - summary: Update shipping price - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1shipping~1%7BshippingMethodId%7D~1prices/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - rate: - type: array - items: - type: string - zone_id: - type: array - items: - type: string - currency_id: - type: array - items: - type: string - operationId: put-shipping-prices-shippingPriceId - description: Update a shipping price - requestBody: - content: - application/json: - schema: - type: object - properties: - rate: - type: integer - zone_id: - type: string - currency_id: - type: string - required: - - rate - - zone_id - - currency_id - "/shipping/{shippingMethodId}/zones": - parameters: - - schema: - type: string - name: shippingMethodId - in: path - required: true - put: - summary: Update shipping method zones - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1shipping~1%7BshippingMethodId%7D/get/responses/200/content/application~1json/schema" - operationId: put-shipping-shippingMethodId-zones - description: Update a shipping method's zones. - requestBody: - content: - application/json: - schema: - type: object - properties: - zones: - type: array - items: - type: string - required: - - zones - examples: - update-body-example: - value: - zones: - - 1rdf4sdfs - - 567yhg2s3 - "/shipping/{id}/users": - parameters: - - schema: - type: string - name: id - in: path - required: true - put: - summary: Update shipping method users - tags: - - Shipping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1shipping~1%7BshippingMethodId%7D/get/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - users: - type: array - items: - type: string - operationId: put-shipping-id-users - description: Update shipping method users - requestBody: - content: - application/json: - schema: - type: object - properties: - users: - type: array - items: - type: string - /tags: - get: - summary: Get all tags - tags: - - Tags - responses: - "200": - description: OK - content: - application/json: - schema: - title: TagCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Tag - type: object - properties: - id: - type: string - name: - type: string - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-tags - description: Get paginated list of tags - post: - summary: "" - operationId: post-tags - responses: - "200": - description: OK - content: - application/json: - schema: - title: TagResponse - type: object - properties: - data: - $ref: "#/paths/~1tags/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - required: - - name - description: Create a new tag - tags: - - Tags - "/tags/{tagId}": - parameters: - - schema: - type: string - name: tagId - in: path - required: true - get: - summary: Get a tag - tags: - - Tags - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1tags/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-tags-tagId - description: Gets a tag by it's ID - put: - summary: Update a tag - tags: - - Tags - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1tags/post/responses/200/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - "422": - description: Unprocessable Entity - content: - application/json: - schema: - type: object - properties: - name: - type: array - items: - type: string - operationId: put-tags-tagId - description: Updates a tag by it's ID - requestBody: - content: - application/json: - schema: - type: object - properties: - name: - type: string - required: - - name - description: "" - delete: - summary: Delete a tag - tags: - - Tags - responses: - "204": - description: No Content - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: delete-tags-tagId - description: Deletes a tag by it's ID - /search: - get: - summary: Search GetCandy - tags: - - Search - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - title: "" - x-tags: - - Search - properties: - data: - type: array - items: - title: Search - oneOf: - - $ref: "#/paths/~1products/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - - $ref: "#/paths/~1categories/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - meta: - allOf: - - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - - type: object - properties: - aggregation: - type: object - properties: - available: - type: array - items: - title: Aggregation - type: object - properties: - handle: - type: string - doc_count: - type: integer - data: - type: object - buckets: - type: array - items: {} - applied: - type: array - items: - $ref: "#/paths/~1search/get/responses/200/content/application~1json/schema/properties/meta/allOf/1/properties/aggregation/properties/available/items" - links: - $ref: "#/paths/~1recycle-bin~1%7BitemId%7D/delete/responses/404/content/application~1json/schema" - description: "" - operationId: get-search - description: >- - Search across products or categories - - - You can filter across attributes by adding key=value to the search query, for filtering multiple values use key=value1:value2 - parameters: - - schema: - type: string - in: query - name: channel - - schema: - type: string - in: query - name: category - - schema: - type: integer - in: query - name: page - - schema: - type: string - enum: - - categories - - products - default: products - in: query - name: search_type - - schema: - type: string - in: query - name: term - - schema: - type: boolean - in: query - name: rank - description: Whether to rank results based on config - - schema: - type: boolean - default: false - in: query - name: ids_only - description: Will only return result ID's, good for performance - - schema: - type: string - in: query - name: include - - schema: - type: string - in: query - name: sort - - schema: - type: boolean - in: query - name: full_response - /account/password: - post: - summary: Reset password - tags: - - Users - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/paths/~1categories~1reorder/post/responses/200/content/application~1json/schema" - examples: - password-changed-example: - value: - message: Pasword changed - operationId: post-account-password - description: Allows the current user to update their password. - requestBody: - content: - application/json: - schema: - title: PasswordResetBody - type: object - properties: - password_confirmation: - type: string - password: - type: string - current_password: - type: string - required: - - current_password - - password - - password_confirmation - /recycle-bin: - get: - summary: Get records - tags: - - Recycle Bin - responses: - "200": - description: OK - content: - application/json: - schema: - title: RecycleBinCollection - allOf: - - type: object - properties: - data: - type: array - items: - title: Recycle Bin - type: object - properties: - id: - type: string - type: - type: string - name: - type: string - thumbnail: - type: string - deleted_at: - type: string - recyclable: - type: object - - type: object - properties: - meta: - $ref: "#/paths/~1channels/get/responses/200/content/application~1json/schema/allOf/1/properties/meta" - operationId: get-recycle-bin - description: Returns a paginated list of all recycle bin items. - "/recycle-bin/{itemId}": - parameters: - - schema: - type: string - name: itemId - in: path - required: true - get: - summary: Get item - tags: - - Recycle Bin - responses: - "200": - description: OK - content: - application/json: - schema: - title: RecycleBinResponse - type: object - properties: - data: - $ref: "#/paths/~1recycle-bin/get/responses/200/content/application~1json/schema/allOf/0/properties/data/items" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: get-recycle-bin-itemId - description: Retrieves a recycle bin item. - delete: - summary: Delete item - tags: - - Recycle Bin - responses: - "204": - description: No Content - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/paths/~1users/get/responses/401/content/application~1json/schema" - "404": - description: Not Found - content: - application/json: - schema: - title: Links - type: object - properties: - first: - type: string - last: - type: string - prev: - type: string - nullable: true - next: - type: string - nullable: true - operationId: delete-recycle-bin-itemId - description: >- - Remove a recycle bin item - - - > This will also hard delete the model associated with the recycle bin item, this action is not reversable and will cause loss of data. - "/versions/{modelId}/restore": - parameters: - - schema: - type: string - name: modelId - in: path - required: true - post: - summary: Restore model - tags: - - Versioning - responses: - "200": - description: OK - content: - application/json: - schema: - title: VersionResponse - type: object - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/paths/~1channels~1%7BchannelId%7D/get/responses/404/content/application~1json/schema" - operationId: post-versions-modelId-restore - description: Restores a model version - /: - get: - summary: Get root - tags: - - Root - responses: - "200": - description: OK - content: - application/json: - schema: - title: Root - type: object - properties: - version: - type: string - locale: - type: string - channel: - type: string - currency: - type: string - examples: - root-example: - value: - version: 0.9.0_beta - locale: en - channel: - id: 92weawd23 - name: Webstore - handle: webstore - url: http://storefront.test - default: true - currency: - id: q3e33wede - name: British Pound - code: GBP - enabled: true - format: £{price} - decimal_point: . - thousand_point: "," - default: true - operationId: get - description: Returns information about the API -components: - securitySchemes: - auth: - type: oauth2 - flows: - password: - tokenUrl: http://localhost:3000/api/v1/oauth/token - refreshUrl: http://localhost:3000/api/v1/oauth/refresh - scopes: {} - clientCredentials: - tokenUrl: http://localhost:3000/api/v1/oauth/token - scopes: {} - refreshUrl: http://localhost:3000/api/v1/oauth/refresh - description: "" -tags: - - name: Channels - description: Store channels - - name: Categories - description: Catalogue Management - - name: Authentication - description: Everything to authenticate requests - - name: Orders - description: Order Processing - - name: Attributes - description: Catalogue Management - - name: Assets - description: Catalogue Management - - name: Associations - description: Catalogue Management - - name: Baskets - description: Order Processing - - name: Payments - description: Order Processing - - name: Collections - description: Catalogue Management - - name: Customers - description: Order Processing - - name: Discounts - description: Order Processing - - name: Languages - description: System Settings - - name: Layouts - description: Catalogue Management - - name: Products - description: Catalogue Management - - name: Product Variants - description: Catalogue Management - - name: Taxes - description: System Settings diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 10d0d6d09..2dfbd86ab 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -13,46 +13,34 @@ info: servers: - url: 'http://localhost:8000/api/v1' paths: + '/': + $ref: './root/paths/root.yaml' + '/account/password': + $ref: './account/paths/account.password.yaml' '/addresses': $ref: './addresses/paths/addresses.yaml' '/addresses/{addressId}': $ref: './addresses/paths/addresses.id.yaml' - '/channels': - $ref: './channels/paths/channels.yaml' - '/channels/{channelId}': - $ref: './channels/paths/channels.id.yaml' - '/customers': - $ref: './customers/paths/customers.yaml' - '/customers/fields': - $ref: './customers/paths/customers.fields.yaml' - '/customers/{customerId}/users': - $ref: './customers/paths/customers.id.users.yaml' - '/customers/{customerId}/customer-groups': - $ref: './customers/paths/customers.id.customer-groups.yaml' - '/customers/{customerId}': - $ref: './customers/paths/customers.id.yaml' - '/countries': - $ref: './countries/paths/countries.yaml' - '/countries/{countryId}': - $ref: './countries/paths/countries.id.yaml' - '/customer-groups': - $ref: './customer-groups/paths/customer-groups.yaml' - '/customer-groups/{customerGroupId}': - $ref: './customer-groups/paths/customer-groups.id.yaml' - '/languages': - $ref: './languages/paths/languages.yaml' - '/languages/{languageId}': - $ref: './languages/paths/languages.id.yaml' - '/routes': - $ref: './routes/paths/routes.yaml' - '/routes/{routeId}': - $ref: './routes/paths/routes.id.yaml' - '/routes/search': - $ref: './routes/paths/routes.search.yaml' - '/product-families': - $ref: './product-families/paths/product-families.yaml' - '/product-families/{productFamilyId}': - $ref: './product-families/paths/product-families.id.yaml' + '/assets': + $ref: './assets/paths/assets.yaml' + '/assets/simple': + $ref: './assets/paths/assets.simple.yaml' + '/assets/{assetId}/detach/{ownerId}': + $ref: './assets/paths/assets.id.detach.id.yaml' + '/associations/groups': + $ref: './associations/paths/associations.groups.yaml' + '/attributes': + $ref: './attributes/paths/attributes.yaml' + '/attributes/order': + $ref: './attributes/paths/attributes.order.yaml' + '/attributes/{attributeId}': + $ref: './attributes/paths/attributes.id.yaml' + '/attribute-groups': + $ref: './attribute-groups/paths/attribute-groups.yaml' + '/attribute-groups/reorder': + $ref: './attribute-groups/paths/attribute-groups.reorder.yaml' + '/attribute-groups/{attributeGroupId}': + $ref: './attribute-groups/paths/attribute-groups.id.yaml' '/baskets': $ref: './baskets/paths/baskets.yaml' '/baskets/{basketId}/meta': @@ -81,7 +69,66 @@ paths: $ref: './basket-lines/paths/basket-lines.id.add.yaml' '/basket-lines/{basketLineId}/remove': $ref: './basket-lines/paths/basket-lines.id.remove.yaml' -# ### BELOW: Paths that have had OpenAPI spec moved but not moved to actions + '/categories': + $ref: './categories/paths/categories.yaml' + '/categories/{categoryId}': + $ref: './categories/paths/categories.id.yaml' + '/categories/{categoryId}/layouts': + $ref: './categories/paths/categories.id.layouts.yaml' + '/categories/{categoryId}/routes': + $ref: './categories/paths/categories.id.routes.yaml' + '/categories/parent/{parentId}': + $ref: './categories/paths/categories.parent.id.yaml' + '/categories/reorder': + $ref: './categories/paths/categories.reorder.yaml' + '/categories/{categoryId}/products': + $ref: './categories/paths/categories.id.products.yaml' + '/categories/{categoryId}/channels': + $ref: './categories/paths/categories.id.channels.yaml' + '/categories/{categoryId}/drafts': + $ref: './categories/paths/categories.id.drafts.yaml' + '/categories/{categoryId}/customer-groups': + $ref: './categories/paths/categories.id.customer-groups.yaml' + '/channels': + $ref: './channels/paths/channels.yaml' + '/channels/{channelId}': + $ref: './channels/paths/channels.id.yaml' + '/collections/{collectionId}/routes': + $ref: './collections/paths/collections.id.routes.yaml' + '/collections/{collectionId}/products': + $ref: './collections/paths/collections.id.products.yaml' + '/collections': + $ref: './collections/paths/collections.yaml' + '/collections/{collectionId}': + $ref: './collections/paths/collections.id.yaml' + '/countries': + $ref: './countries/paths/countries.yaml' + '/countries/{countryId}': + $ref: './countries/paths/countries.id.yaml' + '/customer-groups': + $ref: './customer-groups/paths/customer-groups.yaml' + '/customer-groups/{customerGroupId}': + $ref: './customer-groups/paths/customer-groups.id.yaml' + '/customers': + $ref: './customers/paths/customers.yaml' + '/customers/fields': + $ref: './customers/paths/customers.fields.yaml' + '/customers/{customerId}/users': + $ref: './customers/paths/customers.id.users.yaml' + '/customers/{customerId}/customer-groups': + $ref: './customers/paths/customers.id.customer-groups.yaml' + '/customers/{customerId}': + $ref: './customers/paths/customers.id.yaml' + '/discounts': + $ref: './discounts/paths/discounts.yaml' + '/discounts/{discountId}': + $ref: './discounts/paths/discounts.id.yaml' + '/languages': + $ref: './languages/paths/languages.yaml' + '/languages/{languageId}': + $ref: './languages/paths/languages.id.yaml' + '/layouts': + $ref: './layouts/paths/layouts.yaml' '/orders': $ref: './orders/paths/orders.yaml' '/orders/{orderId}': @@ -112,6 +159,20 @@ paths: $ref: './orders/paths/orders.lines.id.yaml' '/orders/{orderId}/invoice': $ref: './orders/paths/orders.id.invoice.yaml' + '/payments/{transactionId}/refund': + $ref: './payments/paths/payments.id.refund.yaml' + '/payments/{transactionId}/void': + $ref: './payments/paths/payments.id.void.yaml' + '/payments/3d-secure': + $ref: './payments/paths/payments.3d-secure.yaml' + '/payments/provider': + $ref: './payments/paths/payments.provider.yaml' + '/payments/types': + $ref: './payments/paths/payments.types.yaml' + '/product-families': + $ref: './product-families/paths/product-families.yaml' + '/product-families/{productFamilyId}': + $ref: './product-families/paths/product-families.id.yaml' '/products': $ref: './products/paths/products.yaml' '/products/recommended': @@ -150,82 +211,10 @@ paths: $ref: './products/paths/products.id.drafts.yaml' '/products/{productId}/publish': $ref: './products/paths/products.id.publish.yaml' - '/attributes': - $ref: './attributes/paths/attributes.yaml' - '/attributes/order': - $ref: './attributes/paths/attributes.order.yaml' - '/attributes/{attributeId}': - $ref: './attributes/paths/attributes.id.yaml' - '/attribute-groups': - $ref: './attribute-groups/paths/attribute-groups.yaml' - '/attribute-groups/reorder': - $ref: './attribute-groups/paths/attribute-groups.reorder.yaml' - '/attribute-groups/{attributeGroupId}': - $ref: './attribute-groups/paths/attribute-groups.id.yaml' - '/categories': - $ref: './categories/paths/categories.yaml' - '/categories/{categoryId}': - $ref: './categories/paths/categories.id.yaml' - '/categories/{categoryId}/layouts': - $ref: './categories/paths/categories.id.layouts.yaml' - '/categories/{categoryId}/routes': - $ref: './categories/paths/categories.id.routes.yaml' - '/categories/parent/{parentId}': - $ref: './categories/paths/categories.parent.id.yaml' - '/categories/reorder': - $ref: './categories/paths/categories.reorder.yaml' - '/categories/{categoryId}/products': - $ref: './categories/paths/categories.id.products.yaml' - '/categories/{categoryId}/channels': - $ref: './categories/paths/categories.id.channels.yaml' - '/categories/{categoryId}/drafts': - $ref: './categories/paths/categories.id.drafts.yaml' - '/categories/{categoryId}/customer-groups': - $ref: './categories/paths/categories.id.customer-groups.yaml' - '/assets': - $ref: './assets/paths/assets.yaml' - '/assets/simple': - $ref: './assets/paths/assets.simple.yaml' - '/assets/{assetId}/detach/{ownerId}': - $ref: './assets/paths/assets.id.detach.id.yaml' - '/taxes': - $ref: './taxes/paths/taxes.yaml' - '/taxes/{taxId}': - $ref: './taxes/paths/taxes.id.yaml' - '/associations/groups': - $ref: './associations/paths/associations.groups.yaml' - '/collections/{collectionId}/routes': - $ref: './collections/paths/collections.id.routes.yaml' - '/collections/{collectionId}/products': - $ref: './collections/paths/collections.id.products.yaml' - '/collections': - $ref: './collections/paths/collections.yaml' - '/collections/{collectionId}': - $ref: './collections/paths/collections.id.yaml' - '/discounts': - $ref: './discounts/paths/discounts.yaml' - '/discounts/{discountId}': - $ref: './discounts/paths/discounts.id.yaml' - '/layouts': - $ref: './layouts/paths/layouts.yaml' - '/users': - $ref: './users/paths/users.yaml' - '/users/{userId}': - $ref: './users/paths/users.id.yaml' - '/users/fields': - $ref: './users/paths/users.fields.yaml' - '/users/current': - $ref: './users/paths/users.current.yaml' - '/payments/{transactionId}/refund': - $ref: './payments/paths/payments.id.refund.yaml' - '/payments/{transactionId}/void': - $ref: './payments/paths/payments.id.void.yaml' - '/payments/3d-secure': - $ref: './payments/paths/payments.3d-secure.yaml' - '/payments/provider': - $ref: './payments/paths/payments.provider.yaml' - '/payments/types': - $ref: './payments/paths/payments.types.yaml' + '/recycle-bin': + $ref: './recycle-bin/paths/recycle-bin.yaml' + '/recycle-bin/{itemId}': + $ref: './recycle-bin/paths/recycle-bin.id.yaml' '/reports/sales': $ref: './reports/paths/reports.sales.yaml' '/reports/orders': @@ -238,6 +227,14 @@ paths: $ref: './reports/paths/reports.products.best-sellers.yaml' '/reports/metrics/{subject}': $ref: './reports/paths/reports.metrics.subject.yaml' + '/routes': + $ref: './routes/paths/routes.yaml' + '/routes/{routeId}': + $ref: './routes/paths/routes.id.yaml' + '/routes/search': + $ref: './routes/paths/routes.search.yaml' + '/search': + $ref: './search/paths/search.yaml' '/saved-searches/product': $ref: './saved-searches/paths/saved-searches.product.yaml' '/saved-searches/{savedSearchId}': @@ -266,18 +263,20 @@ paths: $ref: './tags/paths/tags.yaml' '/tags/{tagId}': $ref: './tags/paths/tags.id.yaml' - '/search': - $ref: './search/paths/search.yaml' - '/account/password': - $ref: './account/paths/account.password.yaml' - '/recycle-bin': - $ref: './recycle-bin/paths/recycle-bin.yaml' - '/recycle-bin/{itemId}': - $ref: './recycle-bin/paths/recycle-bin.id.yaml' + '/taxes': + $ref: './taxes/paths/taxes.yaml' + '/taxes/{taxId}': + $ref: './taxes/paths/taxes.id.yaml' + '/users': + $ref: './users/paths/users.yaml' + '/users/{userId}': + $ref: './users/paths/users.id.yaml' + '/users/fields': + $ref: './users/paths/users.fields.yaml' + '/users/current': + $ref: './users/paths/users.current.yaml' '/versions/{modelId}/restore': $ref: './versions/paths/versions.id.restore.yaml' - '/': - $ref: './root/paths/root.yaml' components: securitySchemes: auth: diff --git a/openapi/users/requests/CreateUserBody.yaml b/openapi/users/requests/CreateUserBody.yaml index 2c52b1e8a..763e8d815 100644 --- a/openapi/users/requests/CreateUserBody.yaml +++ b/openapi/users/requests/CreateUserBody.yaml @@ -13,4 +13,10 @@ properties: type: string customer_id: type: string - nullable: false \ No newline at end of file + nullable: false +required: + - email + - firstname + - lastname + - password + - password_confirmation \ No newline at end of file diff --git a/src/Core/Addresses/Actions/UpdateAddressAction.php b/src/Core/Addresses/Actions/UpdateAddressAction.php index 465cb1443..d9a76aac2 100644 --- a/src/Core/Addresses/Actions/UpdateAddressAction.php +++ b/src/Core/Addresses/Actions/UpdateAddressAction.php @@ -2,12 +2,11 @@ namespace GetCandy\Api\Core\Addresses\Actions; -use GetCandy; use DateTime; +use GetCandy; use GetCandy\Api\Core\Addresses\Models\Address; use GetCandy\Api\Core\Addresses\Resources\AddressResource; use GetCandy\Api\Core\Countries\Actions\FetchCountry; -use GetCandy\Api\Core\Customers\Models\Customer; use Illuminate\Support\Arr; use Lorisleiva\Actions\Action; diff --git a/src/Core/Addresses/Models/Address.php b/src/Core/Addresses/Models/Address.php index bd2f31d87..e7642710a 100644 --- a/src/Core/Addresses/Models/Address.php +++ b/src/Core/Addresses/Models/Address.php @@ -73,5 +73,4 @@ public function addresses() { return $this->morphTo(); } - } diff --git a/src/Core/Attributes/Actions/FetchAttribute.php b/src/Core/Attributes/Actions/FetchAttribute.php index 541e6216f..147c3c7b5 100644 --- a/src/Core/Attributes/Actions/FetchAttribute.php +++ b/src/Core/Attributes/Actions/FetchAttribute.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Attributes\Actions; use GetCandy\Api\Core\Attributes\Models\Attribute; -use GetCandy\Api\Core\Foundation\Actions\DecodeIds; use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Http\JsonResponse; diff --git a/src/Core/Attributes/Actions/FetchFilterableAttributes.php b/src/Core/Attributes/Actions/FetchFilterableAttributes.php index fc0274254..d9d642659 100644 --- a/src/Core/Attributes/Actions/FetchFilterableAttributes.php +++ b/src/Core/Attributes/Actions/FetchFilterableAttributes.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Attributes\Actions; use GetCandy\Api\Core\Attributes\Models\Attribute; -use GetCandy\Api\Core\Foundation\Actions\DecodeIds; use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Http\JsonResponse; diff --git a/src/Core/Categories/Services/CategoryService.php b/src/Core/Categories/Services/CategoryService.php index d85b52957..9f83b7888 100644 --- a/src/Core/Categories/Services/CategoryService.php +++ b/src/Core/Categories/Services/CategoryService.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Categories\Services; use GetCandy; -use GetCandy\Api\Core\Search\Actions\IndexObjects; use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; use GetCandy\Api\Core\Categories\Events\CategoryStoredEvent; use GetCandy\Api\Core\Categories\Models\Category; @@ -12,6 +11,7 @@ use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Search\Actions\IndexObjects; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; class CategoryService extends BaseService diff --git a/src/Core/Collections/Services/CollectionService.php b/src/Core/Collections/Services/CollectionService.php index 29e716c1a..3e938bf3a 100644 --- a/src/Core/Collections/Services/CollectionService.php +++ b/src/Core/Collections/Services/CollectionService.php @@ -6,8 +6,8 @@ use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Collections\Models\Collection; -use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; use GetCandy\Api\Core\Search\Actions\Search; @@ -112,7 +112,7 @@ public function getPaginatedData($searchTerm = null, $length = 50, $page = null) 'params' =>[ 'type' => $type, 'term' => 'test', - ] + ], ]); $ids = collect(); diff --git a/src/Core/Customers/Actions/CreateCustomer.php b/src/Core/Customers/Actions/CreateCustomer.php index 27cca6fcc..35699fbb9 100644 --- a/src/Core/Customers/Actions/CreateCustomer.php +++ b/src/Core/Customers/Actions/CreateCustomer.php @@ -3,13 +3,10 @@ namespace GetCandy\Api\Core\Customers\Actions; use GetCandy; -use Illuminate\Support\Arr; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Customers\Models\Customer; use GetCandy\Api\Core\Customers\Resources\CustomerResource; -use GetCandy\Api\Core\Customers\Actions\AttachUserToCustomer; -use GetCandy\Api\Core\Customers\Actions\AttachCustomerToGroups; -use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Arr; class CreateCustomer extends AbstractAction { @@ -61,7 +58,6 @@ public function handle(): Customer ]); } - AttachCustomerToGroups::run([ 'customer_id' => $customer->encoded_id, 'customer_group_ids' => $this->customer_group_ids ?: [FetchDefaultCustomerGroup::run()->encoded_id], diff --git a/src/Core/Customers/Models/Customer.php b/src/Core/Customers/Models/Customer.php index 8339cd0e9..ee54c7a42 100644 --- a/src/Core/Customers/Models/Customer.php +++ b/src/Core/Customers/Models/Customer.php @@ -3,8 +3,8 @@ namespace GetCandy\Api\Core\Customers\Models; use GetCandy; -use GetCandy\Api\Core\Traits\HasAddresses; use GetCandy\Api\Core\Scaffold\BaseModel; +use GetCandy\Api\Core\Traits\HasAddresses; class Customer extends BaseModel { diff --git a/src/Core/Languages/Actions/FetchLanguagesAction.php b/src/Core/Languages/Actions/FetchLanguagesAction.php index 14aac4eae..49b687bd0 100644 --- a/src/Core/Languages/Actions/FetchLanguagesAction.php +++ b/src/Core/Languages/Actions/FetchLanguagesAction.php @@ -39,6 +39,7 @@ public function handle() if ($this->limit) { return Language::paginate($this->limit); } + return Language::all(); } } diff --git a/src/Core/Products/Services/ProductCategoryService.php b/src/Core/Products/Services/ProductCategoryService.php index 44a9d62d7..ca8b4da0c 100644 --- a/src/Core/Products/Services/ProductCategoryService.php +++ b/src/Core/Products/Services/ProductCategoryService.php @@ -3,9 +3,9 @@ namespace GetCandy\Api\Core\Products\Services; use GetCandy; -use GetCandy\Api\Core\Search\Actions\IndexObjects; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Search\Actions\IndexObjects; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; class ProductCategoryService extends BaseService diff --git a/src/Core/Search/Actions/FetchSearchedIds.php b/src/Core/Search/Actions/FetchSearchedIds.php index a9435e397..a189b98bf 100644 --- a/src/Core/Search/Actions/FetchSearchedIds.php +++ b/src/Core/Search/Actions/FetchSearchedIds.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Search\Actions; -use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Foundation\Actions\DecodeIds; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Lorisleiva\Actions\Action; class FetchSearchedIds extends AbstractAction { @@ -43,7 +43,7 @@ public function handle() $parsedIds = $this->delegateTo(DecodeIds::class); $placeholders = implode(',', array_fill(0, count($parsedIds), '?')); // string for the query - $query = $model->with($this->resolveEagerRelations())->whereIn("{$model->getTable()}.id", $parsedIds); + $query = $model->with($this->resolveEagerRelations())->whereIn("{$model->getTable()}.id", $parsedIds); if (count($parsedIds)) { $query = $query->orderByRaw("field({$model->getTable()}.id,{$placeholders})", $parsedIds); diff --git a/src/Core/Search/Actions/IndexDocuments.php b/src/Core/Search/Actions/IndexDocuments.php index fc9330fa9..2a98eae7b 100644 --- a/src/Core/Search/Actions/IndexDocuments.php +++ b/src/Core/Search/Actions/IndexDocuments.php @@ -16,6 +16,7 @@ public function authorize() if (app()->runningInConsole()) { return true; } + return $this->user()->can('index-products'); } diff --git a/src/Core/Search/Actions/IndexObjects.php b/src/Core/Search/Actions/IndexObjects.php index ec4832765..1154e0796 100644 --- a/src/Core/Search/Actions/IndexObjects.php +++ b/src/Core/Search/Actions/IndexObjects.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Search\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Search\SearchManager; +use Lorisleiva\Actions\Action; class IndexObjects extends Action { diff --git a/src/Core/Search/Actions/Search.php b/src/Core/Search/Actions/Search.php index 10b038737..0f5f95a3a 100644 --- a/src/Core/Search/Actions/Search.php +++ b/src/Core/Search/Actions/Search.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Search\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Search\Contracts\SearchManagerContract; +use Lorisleiva\Actions\Action; class Search extends Action { @@ -38,6 +38,7 @@ public function rules() public function handle(SearchManagerContract $search) { $driver = $search->with($this->driver); + return $driver->search($this->request ?? $this->params); } } diff --git a/src/Core/Search/Commands/IndexCategoriesCommand.php b/src/Core/Search/Commands/IndexCategoriesCommand.php index 1debb8494..56361d46e 100644 --- a/src/Core/Search/Commands/IndexCategoriesCommand.php +++ b/src/Core/Search/Commands/IndexCategoriesCommand.php @@ -3,11 +3,11 @@ namespace GetCandy\Api\Core\Search\Commands; use GetCandy\Api\Core\Categories\Models\Category; -use Ramsey\Uuid\Uuid; -use Illuminate\Console\Command; +use GetCandy\Api\Core\Search\Actions\IndexDocuments; use GetCandy\Api\Core\Search\SearchManager; +use Illuminate\Console\Command; use Illuminate\Contracts\Events\Dispatcher; -use GetCandy\Api\Core\Search\Actions\IndexDocuments; +use Ramsey\Uuid\Uuid; class IndexCategoriesCommand extends Command { @@ -45,7 +45,7 @@ public function handle(Dispatcher $events, SearchManager $manager) $batchsize = (int) $this->argument('batchsize'); $total = Category::withoutGlobalScopes()->count(); - $this->output->text('Indexing ' . $total . ' categories in ' . ceil($total / $batchsize) . ' batches'); + $this->output->text('Indexing '.$total.' categories in '.ceil($total / $batchsize).' batches'); $batches = ceil($total / $batchsize); $bar = $this->output->createProgressBar($batches); @@ -56,7 +56,7 @@ public function handle(Dispatcher $events, SearchManager $manager) 'attributes', 'customerGroups', 'channels', - ])->chunk($batchsize, function ($categories, $index) use ($manager, $uuid, $batches, $bar, $events) { + ])->chunk($batchsize, function ($categories, $index) use ($manager, $uuid, $batches, $bar) { IndexDocuments::run([ 'driver' => $manager->with( config('getcandy.search.driver') diff --git a/src/Core/Search/Commands/IndexProductsCommand.php b/src/Core/Search/Commands/IndexProductsCommand.php index 85d6f9901..843962c40 100644 --- a/src/Core/Search/Commands/IndexProductsCommand.php +++ b/src/Core/Search/Commands/IndexProductsCommand.php @@ -2,12 +2,12 @@ namespace GetCandy\Api\Core\Search\Commands; -use Ramsey\Uuid\Uuid; -use Illuminate\Console\Command; -use GetCandy\Api\Core\Search\SearchManager; -use Illuminate\Contracts\Events\Dispatcher; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Actions\IndexDocuments; +use GetCandy\Api\Core\Search\SearchManager; +use Illuminate\Console\Command; +use Illuminate\Contracts\Events\Dispatcher; +use Ramsey\Uuid\Uuid; class IndexProductsCommand extends Command { @@ -45,7 +45,7 @@ public function handle(Dispatcher $events, SearchManager $manager) $batchsize = (int) $this->argument('batchsize'); $total = Product::withoutGlobalScopes()->count(); - $this->output->text('Indexing ' . $total . ' products in ' . ceil($total / $batchsize) . ' batches'); + $this->output->text('Indexing '.$total.' products in '.ceil($total / $batchsize).' batches'); $batches = ceil($total / $batchsize); $bar = $this->output->createProgressBar($batches); @@ -58,7 +58,7 @@ public function handle(Dispatcher $events, SearchManager $manager) 'channels', 'variants.customerPricing', 'categories', - ])->chunk($batchsize, function ($products, $index) use ($manager, $uuid, $batches, $bar, $events) { + ])->chunk($batchsize, function ($products, $index) use ($manager, $uuid, $batches, $bar) { IndexDocuments::run([ 'driver' => $manager->with( config('getcandy.search.driver') diff --git a/src/Core/Search/Commands/ScoreProductsCommand.php b/src/Core/Search/Commands/ScoreProductsCommand.php index a856db978..00a63b3ee 100644 --- a/src/Core/Search/Commands/ScoreProductsCommand.php +++ b/src/Core/Search/Commands/ScoreProductsCommand.php @@ -3,11 +3,11 @@ namespace GetCandy\Api\Core\Search\Commands; use Carbon\Carbon; -use GetCandy\Api\Core\Search\Actions\IndexObjects; -use Illuminate\Support\Facades\DB; use Elastica\Document; use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Search\Actions\IndexObjects; use Illuminate\Console\Command; +use Illuminate\Support\Facades\DB; class ScoreProductsCommand extends Command { diff --git a/src/Core/Search/Drivers/AbstractSearchDriver.php b/src/Core/Search/Drivers/AbstractSearchDriver.php index fe93a0fc5..553ecf800 100644 --- a/src/Core/Search/Drivers/AbstractSearchDriver.php +++ b/src/Core/Search/Drivers/AbstractSearchDriver.php @@ -9,29 +9,30 @@ abstract class AbstractSearchDriver public function onReference($reference) { $this->reference = $reference; + return $this; } /** - * Returns the config for the driver + * Returns the config for the driver. * * @return array */ - abstract function config(); + abstract public function config(); - abstract function index($documents, $final = false); + abstract public function index($documents, $final = false); - abstract function update($documents); + abstract public function update($documents); /** - * Checks if a feature is available for this driver + * Checks if a feature is available for this driver. * * @param string $check * - * @return boolean + * @return bool */ public function hasFeature($check) { - return !!($this->config()['features'][$check] ?? false); + return (bool) ($this->config()['features'][$check] ?? false); } -} \ No newline at end of file +} diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php index fa3f89b07..6096e6e97 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Search\Document; use Illuminate\Database\Eloquent\Model; +use Lorisleiva\Actions\Action; class AbstractDocumentAction extends Action { @@ -30,7 +30,6 @@ protected function getIndexables(Model $model) $categories = $this->getCategories($model); $indexable->set('id', $model->encoded_id); - $groupPricing = []; if (! empty($item['data'])) { @@ -88,6 +87,7 @@ protected function getIndexables(Model $model) $indexables->push($indexable); } } + return $indexables; } @@ -255,7 +255,6 @@ public function getMapping() return $payload; })->toArray(); - return array_merge($attributes, $this->mapping); } } diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php index a758b5208..d0bb843c8 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Index; +use Lorisleiva\Actions\Action; class FetchIndex extends Action { @@ -47,7 +47,7 @@ public function handle() "{$prefix}_{$this->type}_{$language}_{$this->uuid}" ); - if (!$index->exists()) { + if (! $index->exists()) { $index->create([ 'settings' => [ 'analysis' => [ diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchProductMapping.php b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchProductMapping.php index 03ba08772..d520280c0 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchProductMapping.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchProductMapping.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Search\Indexables\ProductIndexable; +use Lorisleiva\Actions\Action; class FetchProductMapping extends Action { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php index 6fa636f37..36b343956 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php @@ -55,7 +55,7 @@ public function handle() ])->pluck('lang'); $customerGroups = FetchCustomerGroups::run([ - 'paginate' => false + 'paginate' => false, ]); $indexes = FetchIndex::run([ @@ -69,7 +69,7 @@ public function handle() foreach ($this->categories as $category) { $indexables = FetchCategoryDocument::run([ 'model' => $category, - 'customer_groups' => $customerGroups + 'customer_groups' => $customerGroups, ]); foreach ($indexables as $document) { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php index 64502e1b8..b9b05f788 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php @@ -56,7 +56,7 @@ public function handle() ])->pluck('lang'); $customerGroups = FetchCustomerGroups::run([ - 'paginate' => false + 'paginate' => false, ]); $indexes = FetchIndex::run([ @@ -70,7 +70,7 @@ public function handle() foreach ($this->products as $product) { $indexables = FetchProductDocument::run([ 'model' => $product, - 'customer_groups' => $customerGroups + 'customer_groups' => $customerGroups, ]); foreach ($indexables as $document) { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php index 28cda3a21..e1299d04e 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Aggregators\Attribute; +use Lorisleiva\Actions\Action; class FetchAggregations extends Action { @@ -39,9 +39,10 @@ public function rules() */ public function handle() { - if (!$this->aggregate) { + if (! $this->aggregate) { return null; } + return FetchFilterableAttributes::run()->map(function ($attribute) { return $attribute->handle; })->filter(function ($attribute) { @@ -54,6 +55,7 @@ public function handle() if (class_exists($classname)) { return app()->make($classname); } + return new Attribute($attribute); })->toArray(); } diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index f5b670b5c..4bac60ff6 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Attributes\Actions\FetchAttribute; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CustomerGroupFilter; +use Lorisleiva\Actions\Action; class FetchFilters extends Action { @@ -42,7 +42,7 @@ public function rules() public function handle() { $applied = collect([ - (new CustomerGroupFilter)->process($this->user()) + (new CustomerGroupFilter)->process($this->user()), ]); foreach ($this->filters ?? [] as $filter => $value) { @@ -56,7 +56,7 @@ public function handle() } /** - * Find and create instance of filter if it exists + * Find and create instance of filter if it exists. * * @param string $type Filter type * @@ -74,7 +74,7 @@ protected function findFilter($type) } $name = ucfirst(camel_case(str_singular($type))).'Filter'; - $classname = $this->filterNamespace . "\\{$name}"; + $classname = $this->filterNamespace."\\{$name}"; if (class_exists($classname)) { return app()->make($classname); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php index 8dafdbb06..5df386457 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php @@ -3,8 +3,8 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; use Elastica\Query\DisMax; -use Elastica\Query\Wildcard; use Elastica\Query\MultiMatch; +use Elastica\Query\Wildcard; use Lorisleiva\Actions\Action; class FetchTerm extends Action @@ -46,7 +46,6 @@ public function handle() $disMaxQuery->setTieBreaker(1); if ($multiMatch = $ranking['multi_match'] ?? null) { - $prev = null; foreach ($multiMatch['types'] ?? [] as $type => $fields) { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 0d2b68907..e135e8634 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -4,14 +4,14 @@ use Elastica\Query; use Elastica\Query\BoolQuery; -use Lorisleiva\Actions\Action; use Elastica\Search as ElasticaSearch; -use GetCandy\Api\Core\Products\Models\Product; -use Illuminate\Pagination\LengthAwarePaginator; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; -use GetCandy\Api\Http\Resources\Products\ProductCollection; use GetCandy\Api\Http\Resources\Categories\CategoryCollection; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use Illuminate\Pagination\LengthAwarePaginator; +use Lorisleiva\Actions\Action; class Search extends Action { @@ -56,7 +56,6 @@ public function rules() /** * Execute the action and return a result. - * */ public function handle() { @@ -104,11 +103,11 @@ public function handle() return in_array($filter->handle, $this->topFilters); }); - $preFilters->each(function ($filter) use ($boolQuery) { - $boolQuery->addFilter( + $preFilters->each(function ($filter) use ($boolQuery) { + $boolQuery->addFilter( $filter->getQuery() ); - }); + }); $postFilters = $filters->filter(function ($filter) { return ! in_array($filter->handle, $this->topFilters); @@ -130,16 +129,16 @@ public function handle() $query->setPostFilter($postFilter); // // $globalAggregation = new \Elastica\Aggregation\GlobalAggregation('all_products'); - foreach ($aggregations as $aggregation) { - if (method_exists($aggregation, 'get')) { - $query->addAggregation( + foreach ($aggregations as $aggregation) { + if (method_exists($aggregation, 'get')) { + $query->addAggregation( $aggregation->addFilters($postFilters)->get($postFilters) ); - // $globalAggregation->addAggregation( + // $globalAggregation->addAggregation( // $agg->addFilters($postFilters)->get($postFilters) // ); - } - } + } + } $query->setQuery($boolQuery); @@ -189,7 +188,6 @@ public function jsonResponse($result, $request) } } - $models = FetchSearchedIds::run([ 'model' => $this->search_type == 'products' ? Product::class : Category::class, 'encoded_ids' => $ids->toArray(), @@ -208,6 +206,7 @@ public function jsonResponse($result, $request) $result->getQuery()->getParam('size'), $this->page ?: 1 ); + return (new $resource($paginator))->additional([ 'meta' => [ 'aggregations' => $result->getAggregations(), diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/SetSorting.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/SetSorting.php index 9b7b95b84..a87311726 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/SetSorting.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/SetSorting.php @@ -11,7 +11,6 @@ class SetSorting extends Action { - protected $sorts = []; /** diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/SetIndexLive.php b/src/Core/Search/Drivers/Elasticsearch/Actions/SetIndexLive.php index dc3382104..7a8cd033c 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/SetIndexLive.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/SetIndexLive.php @@ -68,11 +68,11 @@ public function handle() }); foreach ($existing as $indexName) { - $shouldPreserve = $indexesToPreserve->first(function ($index) use ($indexName, $prefix) { + $shouldPreserve = $indexesToPreserve->first(function ($index) use ($indexName) { return $index == $indexName; }); - if (!$shouldPreserve) { + if (! $shouldPreserve) { $index = $client->getIndex($indexName); $index->delete(); } diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index fee8869b3..fa94f9bc5 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -2,20 +2,19 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch; -use Elastica\Client; use GetCandy\Api\Core\Categories\Models\Category; -use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\FetchClient; -use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\IndexCategories; -use Illuminate\Database\Eloquent\Collection; -use Illuminate\Http\Request; -use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Contracts\Container\Container; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Drivers\AbstractSearchDriver; -use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\SetIndexLive; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\FetchClient; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\IndexCategories; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\IndexProducts; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching\Search; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\SetIndexLive; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Events\IndexingCompleteEvent; +use Illuminate\Contracts\Container\Container; +use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Database\Eloquent\Collection; +use Illuminate\Http\Request; class Elasticsearch extends AbstractSearchDriver { @@ -94,7 +93,7 @@ public function config() 'features' => [ 'faceting', 'aggregates', - ] + ], ]; } -} \ No newline at end of file +} diff --git a/src/Core/Search/Drivers/Elasticsearch/Events/IndexingCompleteEvent.php b/src/Core/Search/Drivers/Elasticsearch/Events/IndexingCompleteEvent.php index eec5f4893..8654c1515 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Events/IndexingCompleteEvent.php +++ b/src/Core/Search/Drivers/Elasticsearch/Events/IndexingCompleteEvent.php @@ -23,4 +23,4 @@ public function __construct($indexes, $type) $this->indexes = $indexes; $this->type = $type; } -} \ No newline at end of file +} diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php index dc60c1107..94490e62e 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters; -use GetCandy; -use Elastica\Query\Term; -use Elastica\Query\Nested; use Elastica\Query\BoolQuery; +use Elastica\Query\Nested; +use Elastica\Query\Term; use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; class CustomerGroupFilter extends AbstractFilter @@ -24,6 +23,7 @@ class CustomerGroupFilter extends AbstractFilter public function process($payload, $type = null) { $this->user = $payload; + return $this; } diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php index 1907c13d1..a36e6b401 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php @@ -1,7 +1,7 @@ actual = $index; $this->language = $language; } - -} \ No newline at end of file +} diff --git a/src/Core/Search/Indexables/AbstractIndexable.php b/src/Core/Search/Indexables/AbstractIndexable.php index 3291428f3..918698ce6 100644 --- a/src/Core/Search/Indexables/AbstractIndexable.php +++ b/src/Core/Search/Indexables/AbstractIndexable.php @@ -45,6 +45,7 @@ public function getSuffix() public function setIndexName($name) { $this->indexName = $name; + return $this; } @@ -304,6 +305,7 @@ public function getMapping() return $payload; })->toArray(); + return $attributes; } diff --git a/src/Core/Search/Listeners/IndexObjectListener.php b/src/Core/Search/Listeners/IndexObjectListener.php index 5b37368b9..1516002c8 100644 --- a/src/Core/Search/Listeners/IndexObjectListener.php +++ b/src/Core/Search/Listeners/IndexObjectListener.php @@ -16,7 +16,7 @@ class IndexObjectListener public function handle(IndexableSavedEvent $event) { IndexObjects::run([ - 'documents' => $event->indexable() + 'documents' => $event->indexable(), ]); } } diff --git a/src/Core/Search/Providers/Elastic/Elastic.php b/src/Core/Search/Providers/Elastic/Elastic.php index 4a6360a44..f9a456c54 100644 --- a/src/Core/Search/Providers/Elastic/Elastic.php +++ b/src/Core/Search/Providers/Elastic/Elastic.php @@ -2,9 +2,8 @@ namespace GetCandy\Api\Core\Search\Providers\Elastic; -use GetCandy\Api\Core\Search\SearchContract; use GetCandy\Api\Core\Search\Providers\Elastic\Types\ProductType; -use GetCandy\Api\Core\Search\Providers\Elastic\AggregationResolver; +use GetCandy\Api\Core\Search\SearchContract; class Elastic implements SearchContract { diff --git a/src/Core/Search/Providers/Elastic/Indexer.php b/src/Core/Search/Providers/Elastic/Indexer.php index 9a5463a83..808911ed1 100644 --- a/src/Core/Search/Providers/Elastic/Indexer.php +++ b/src/Core/Search/Providers/Elastic/Indexer.php @@ -4,14 +4,13 @@ use Carbon\Carbon; use Elastica\Client; -use Elastica\Reindex; use Elastica\Document; +use Elastica\Reindex; use Elastica\Type\Mapping; -use Illuminate\Database\Eloquent\Model; +use GetCandy\Api\Core\Languages\Actions\FetchLanguages; use GetCandy\Api\Core\Scopes\ChannelScope; use GetCandy\Api\Core\Scopes\CustomerGroupScope; -use GetCandy\Api\Core\Languages\Actions\FetchLanguages; -use GetCandy\Api\Core\Languages\Services\LanguageService; +use Illuminate\Database\Eloquent\Model; class Indexer { @@ -22,7 +21,6 @@ class Indexer */ protected $batch = 0; - /** * The indice resolver. * diff --git a/src/Core/Search/Providers/Elastic/Types/BaseType.php b/src/Core/Search/Providers/Elastic/Types/BaseType.php index 11bdf69ba..dbb5470cc 100644 --- a/src/Core/Search/Providers/Elastic/Types/BaseType.php +++ b/src/Core/Search/Providers/Elastic/Types/BaseType.php @@ -2,13 +2,13 @@ namespace GetCandy\Api\Core\Search\Providers\Elastic\Types; -use GetCandy; use Carbon\Carbon; -use GetCandy\Api\Core\Search\Indexable; -use Illuminate\Database\Eloquent\Model; +use GetCandy; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; use GetCandy\Api\Core\Scopes\ChannelScope; use GetCandy\Api\Core\Scopes\CustomerGroupScope; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Search\Indexable; +use Illuminate\Database\Eloquent\Model; abstract class BaseType { diff --git a/src/Core/Search/SearchManager.php b/src/Core/Search/SearchManager.php index 6b6a73e74..89d8a4bb8 100644 --- a/src/Core/Search/SearchManager.php +++ b/src/Core/Search/SearchManager.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Search; -use Illuminate\Support\Manager; use GetCandy\Api\Core\Search\Contracts\SearchManagerContract; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Elasticsearch; +use Illuminate\Support\Manager; class SearchManager extends Manager implements SearchManagerContract { diff --git a/src/Core/Traits/HasCandy.php b/src/Core/Traits/HasCandy.php index 6f65c69ac..1018ad609 100644 --- a/src/Core/Traits/HasCandy.php +++ b/src/Core/Traits/HasCandy.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Core\Traits; -use GetCandy\Api\Core\Addresses\Models\Address; use GetCandy\Api\Core\Baskets\Models\Basket; use GetCandy\Api\Core\Baskets\Models\SavedBasket; use GetCandy\Api\Core\Customers\Models\Customer; diff --git a/src/Core/Users/Actions/FetchUserFields.php b/src/Core/Users/Actions/FetchUserFields.php index 1087990e4..3e57dafad 100644 --- a/src/Core/Users/Actions/FetchUserFields.php +++ b/src/Core/Users/Actions/FetchUserFields.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Users\Actions; -use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use GetCandy\Api\Core\Customers\Actions\FetchCustomerFields; +use GetCandy\Api\Core\Traits\ReturnsJsonResponses; +use Lorisleiva\Actions\Action; class FetchUserFields extends Action { diff --git a/src/Core/Users/Actions/UpdateUser.php b/src/Core/Users/Actions/UpdateUser.php index 568dc2461..13cb1e520 100644 --- a/src/Core/Users/Actions/UpdateUser.php +++ b/src/Core/Users/Actions/UpdateUser.php @@ -36,7 +36,7 @@ public function rules() return [ 'encoded_id' => 'required|string|hashid_is_valid:'.GetCandy::getUserModel(), 'name' => 'string', - 'email' => 'email|unique:users,email,' . $this->userToUpdate->id, + 'email' => 'email|unique:users,email,'.$this->userToUpdate->id, 'password' => 'string|min:6|confirmed', 'language' => 'string', ]; diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 0171d0e8f..801a14c67 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -2,26 +2,26 @@ namespace GetCandy\Api\Http\Controllers\Products; -use Hashids; use Drafting; use GetCandy; -use Illuminate\Http\Request; +use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; +use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\ProductCriteria; -use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Core\Products\Services\ProductService; use GetCandy\Api\Exceptions\InvalidLanguageException; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Products\CreateRequest; use GetCandy\Api\Http\Requests\Products\DeleteRequest; -use GetCandy\Api\Http\Requests\Products\UpdateRequest; -use GetCandy\Api\Core\Products\Services\ProductService; -use Illuminate\Database\Eloquent\ModelNotFoundException; use GetCandy\Api\Http\Requests\Products\DuplicateRequest; +use GetCandy\Api\Http\Requests\Products\UpdateRequest; +use GetCandy\Api\Http\Resources\Products\ProductCollection; use GetCandy\Api\Http\Resources\Products\ProductResource; +use Hashids; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\HttpException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; -use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class ProductController extends BaseController diff --git a/src/Http/Controllers/Products/ProductRouteController.php b/src/Http/Controllers/Products/ProductRouteController.php index ee640453b..c40fbf96d 100644 --- a/src/Http/Controllers/Products/ProductRouteController.php +++ b/src/Http/Controllers/Products/ProductRouteController.php @@ -18,6 +18,7 @@ class ProductRouteController extends BaseController public function store($product, CreateUrlRequest $request) { $result = GetCandy::products()->createUrl($product, $request->all()); + return new RouteResource($result); } diff --git a/src/Http/Controllers/Shipping/ShippingMethodController.php b/src/Http/Controllers/Shipping/ShippingMethodController.php index f5fa32b2b..d5e8c363a 100644 --- a/src/Http/Controllers/Shipping/ShippingMethodController.php +++ b/src/Http/Controllers/Shipping/ShippingMethodController.php @@ -41,6 +41,7 @@ public function show($id, Request $request) } catch (ModelNotFoundException $e) { return $this->errorNotFound(); } + return new ShippingMethodResource($shipping); } diff --git a/src/Http/Requests/ProductVariants/UpdateRequest.php b/src/Http/Requests/ProductVariants/UpdateRequest.php index 071bd882d..188fe669a 100644 --- a/src/Http/Requests/ProductVariants/UpdateRequest.php +++ b/src/Http/Requests/ProductVariants/UpdateRequest.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Http\Requests\ProductVariants; -use Illuminate\Foundation\Http\FormRequest; use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Products\Models\ProductVariant; +use Illuminate\Foundation\Http\FormRequest; class UpdateRequest extends FormRequest { @@ -28,7 +28,7 @@ public function rules(ProductVariant $variant) return [ 'sku' => 'required', 'pricing' => 'array', - 'pricing.*.customer_group_id' => 'required|hashid_is_valid:' . CustomerGroup::class, + 'pricing.*.customer_group_id' => 'required|hashid_is_valid:'.CustomerGroup::class, ]; } } diff --git a/src/Http/Resources/AbstractResource.php b/src/Http/Resources/AbstractResource.php index 64cb50bdb..a4737cee2 100644 --- a/src/Http/Resources/AbstractResource.php +++ b/src/Http/Resources/AbstractResource.php @@ -125,7 +125,7 @@ public function toArray($request) protected function handleOnlyRequestField($attributes) { - if (!request()->filled('only')) { + if (! request()->filled('only')) { return $attributes; } @@ -140,7 +140,7 @@ protected function handleOnlyRequestField($attributes) protected function handleExceptRequestField($attributes) { - if (!request()->filled('except')) { + if (! request()->filled('except')) { return $attributes; } diff --git a/src/Http/Resources/ActivityLog/ActivityResource.php b/src/Http/Resources/ActivityLog/ActivityResource.php index 2d3ba3a88..44d2829a1 100644 --- a/src/Http/Resources/ActivityLog/ActivityResource.php +++ b/src/Http/Resources/ActivityLog/ActivityResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Resources\ActivityLog; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserResource; +use GetCandy\Api\Http\Resources\AbstractResource; class ActivityResource extends AbstractResource { diff --git a/src/Http/Resources/Baskets/BasketResource.php b/src/Http/Resources/Baskets/BasketResource.php index c1ee96211..2e29d7092 100644 --- a/src/Http/Resources/Baskets/BasketResource.php +++ b/src/Http/Resources/Baskets/BasketResource.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Http\Resources\Baskets; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserResource; -use GetCandy\Api\Http\Resources\Orders\OrderResource; +use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Discounts\DiscountCollection; +use GetCandy\Api\Http\Resources\Orders\OrderResource; class BasketResource extends AbstractResource { diff --git a/src/Http/Resources/Orders/OrderResource.php b/src/Http/Resources/Orders/OrderResource.php index 126a78b3d..a11a002d1 100644 --- a/src/Http/Resources/Orders/OrderResource.php +++ b/src/Http/Resources/Orders/OrderResource.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Http\Resources\Orders; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserResource; -use GetCandy\Api\Http\Resources\Baskets\BasketResource; +use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\ActivityLog\ActivityCollection; +use GetCandy\Api\Http\Resources\Baskets\BasketResource; use GetCandy\Api\Http\Resources\Transactions\TransactionCollection; class OrderResource extends AbstractResource diff --git a/src/Http/Resources/Products/ProductCustomerPriceResource.php b/src/Http/Resources/Products/ProductCustomerPriceResource.php index dec6eb90b..625afbc5c 100644 --- a/src/Http/Resources/Products/ProductCustomerPriceResource.php +++ b/src/Http/Resources/Products/ProductCustomerPriceResource.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Http\Resources\Products; +use GetCandy\Api\Core\Customers\Resources\CustomerGroupResource; use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Taxes\TaxResource; -use GetCandy\Api\Core\Customers\Resources\CustomerGroupResource; class ProductCustomerPriceResource extends AbstractResource { diff --git a/src/Http/Resources/Products/ProductTierResource.php b/src/Http/Resources/Products/ProductTierResource.php index 62e510db2..4bb3c2c06 100644 --- a/src/Http/Resources/Products/ProductTierResource.php +++ b/src/Http/Resources/Products/ProductTierResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Resources\Products; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Customers\Resources\CustomerGroupResource; +use GetCandy\Api\Http\Resources\AbstractResource; class ProductTierResource extends AbstractResource { diff --git a/src/Http/Resources/Versioning/VersionResource.php b/src/Http/Resources/Versioning/VersionResource.php index 6e4fd5a3b..058b68afc 100644 --- a/src/Http/Resources/Versioning/VersionResource.php +++ b/src/Http/Resources/Versioning/VersionResource.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Http\Resources\Versioning; -use Hashids; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserResource; +use GetCandy\Api\Http\Resources\AbstractResource; +use Hashids; class VersionResource extends AbstractResource { diff --git a/src/Http/Validators/HashidValidator.php b/src/Http/Validators/HashidValidator.php index 60e0c5626..1a9bea92f 100644 --- a/src/Http/Validators/HashidValidator.php +++ b/src/Http/Validators/HashidValidator.php @@ -25,7 +25,6 @@ public function validForModel($attribute, $value, $parameters, $validator) // Have we passed the class reference through. - if (class_exists($method)) { $result = (bool) (new $method)->decodeId($value); } else { diff --git a/src/Installer/Runners/PreflightRunner.php b/src/Installer/Runners/PreflightRunner.php index e3b9a8858..a924c3d4d 100644 --- a/src/Installer/Runners/PreflightRunner.php +++ b/src/Installer/Runners/PreflightRunner.php @@ -3,8 +3,6 @@ namespace GetCandy\Api\Installer\Runners; use DB; -use Elastica\Client; -use Elastica\Exception\Connection\HttpException; use GetCandy; use GetCandy\Api\Installer\Contracts\InstallRunnerContract; use GetCandy\Api\Installer\Events\PreflightCompletedEvent; diff --git a/src/Providers/SearchServiceProvider.php b/src/Providers/SearchServiceProvider.php index 0a4c64b77..d3a5ea0eb 100644 --- a/src/Providers/SearchServiceProvider.php +++ b/src/Providers/SearchServiceProvider.php @@ -3,11 +3,11 @@ namespace GetCandy\Api\Providers; use GetCandy\Api\Core\Search\Commands\IndexCategoriesCommand; -use GetCandy\Api\Core\Search\Commands\ScoreProductsCommand; -use Illuminate\Support\ServiceProvider; -use GetCandy\Api\Core\Search\SearchManager; use GetCandy\Api\Core\Search\Commands\IndexProductsCommand; +use GetCandy\Api\Core\Search\Commands\ScoreProductsCommand; use GetCandy\Api\Core\Search\Contracts\SearchManagerContract; +use GetCandy\Api\Core\Search\SearchManager; +use Illuminate\Support\ServiceProvider; class SearchServiceProvider extends ServiceProvider { diff --git a/src/Providers/ShippingServiceProvider.php b/src/Providers/ShippingServiceProvider.php index a855e2eef..1cee14530 100644 --- a/src/Providers/ShippingServiceProvider.php +++ b/src/Providers/ShippingServiceProvider.php @@ -2,11 +2,11 @@ namespace GetCandy\Api\Providers; -use Illuminate\Support\ServiceProvider; -use GetCandy\Api\Core\Shipping\ShippingCalculator; -use GetCandy\Api\Core\Shipping\Services\ShippingZoneService; -use GetCandy\Api\Core\Shipping\Services\ShippingPriceService; use GetCandy\Api\Core\Shipping\Services\ShippingMethodService; +use GetCandy\Api\Core\Shipping\Services\ShippingPriceService; +use GetCandy\Api\Core\Shipping\Services\ShippingZoneService; +use GetCandy\Api\Core\Shipping\ShippingCalculator; +use Illuminate\Support\ServiceProvider; class ShippingServiceProvider extends ServiceProvider { From 739049f55ea3405230e4c40a96f9b1412f4b7ae6 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:22:17 +0000 Subject: [PATCH 007/152] Updates and improvements --- ...25_create_payment_provider_users_table.php | 47 +++++++ routes/api.client.php | 3 +- .../Addresses/Actions/UpdateAddressAction.php | 9 +- .../Attributes/Actions/FetchAttributes.php | 7 +- src/Core/Categories/Models/Category.php | 2 +- src/Core/Categories/QueryBuilder.php | 2 +- .../Factories/OrderProcessingFactory.php | 10 +- src/Core/Orders/Services/OrderService.php | 6 - .../Payments/Models/PaymentProviderUser.php | 27 ++++ src/Core/Payments/Providers/Braintree.php | 90 +++++++++++- src/Core/Products/Actions/FetchProduct.php | 82 +++++++++++ src/Core/Products/Drafting/ProductDrafter.php | 13 ++ .../Services/ProductCustomerGroupService.php | 17 +-- .../Actions/DeleteReusablePayment.php | 18 ++- .../Resources/ReusablePaymentCollection.php | 28 ++++ src/Core/Search/Actions/FetchSearchedIds.php | 4 +- .../Actions/AbstractDocumentAction.php | 2 +- .../Actions/Searching/FetchAggregations.php | 16 +-- .../Actions/Searching/MapAggregations.php | 131 ++++++++++++++++++ .../Providers/Elastic/AggregationResolver.php | 50 ------- src/Core/Traits/HasCandy.php | 14 +- src/Core/Users/Actions/FetchCurrentUser.php | 11 +- src/Core/Users/Resources/UserResource.php | 14 +- .../ActivityLog/ActivityLogController.php | 2 +- .../Baskets/BasketLineController.php | 23 ++- .../Categories/CategoryController.php | 2 +- src/Http/Resources/AbstractResource.php | 12 +- src/Http/Resources/Baskets/BasketResource.php | 1 + .../Resources/Baskets/SavedBasketResource.php | 2 +- 29 files changed, 523 insertions(+), 122 deletions(-) create mode 100644 database/migrations/2020_12_21_123125_create_payment_provider_users_table.php create mode 100644 src/Core/Payments/Models/PaymentProviderUser.php create mode 100644 src/Core/Products/Actions/FetchProduct.php create mode 100644 src/Core/ReusablePayments/Resources/ReusablePaymentCollection.php create mode 100644 src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php diff --git a/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php b/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php new file mode 100644 index 000000000..4c5c75c91 --- /dev/null +++ b/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php @@ -0,0 +1,47 @@ +id(); + + $userIdColType = Schema::getColumnType('users', 'id'); + + if ($userIdColType == 'integer') { + $table->unsignedInteger('user_id'); + $table->foreign('user_id')->references('id')->on('users'); + } else { + $table->foreignId('user_id')->constrained(); + } + + $table->string('provider_id')->index(); + $table->string('provider')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('reusable_payments', function (Blueprint $table) { + $table->dropColumn('meta'); + }); + } +} diff --git a/routes/api.client.php b/routes/api.client.php index 70afbe7d2..83273acc0 100644 --- a/routes/api.client.php +++ b/routes/api.client.php @@ -17,7 +17,7 @@ $router->get('collections/{id}', 'Collections\CollectionController@show'); $router->get('categories/{id}', 'Categories\CategoryController@show'); $router->get('products/recommended', 'Products\ProductController@recommended'); -$router->get('products/{product}', 'Products\ProductController@show'); +$router->get('products/{encoded_id}', '\GetCandy\Api\Core\Products\Actions\FetchProduct'); $router->get('products', 'Products\ProductController@index'); /* @@ -49,6 +49,7 @@ */ $router->post('basket-lines', 'Baskets\BasketLineController@store'); $router->put('basket-lines/{id}', 'Baskets\BasketLineController@update'); +$router->delete('basket-lines/{id}', 'Baskets\BasketLineController@destroyLine'); $router->post('basket-lines/{id}/add', 'Baskets\BasketLineController@addQuantity'); $router->post('basket-lines/{id}/remove', 'Baskets\BasketLineController@removeQuantity'); $router->delete('basket-lines', 'Baskets\BasketLineController@destroy'); diff --git a/src/Core/Addresses/Actions/UpdateAddressAction.php b/src/Core/Addresses/Actions/UpdateAddressAction.php index d9a76aac2..cfc02730e 100644 --- a/src/Core/Addresses/Actions/UpdateAddressAction.php +++ b/src/Core/Addresses/Actions/UpdateAddressAction.php @@ -4,11 +4,12 @@ use DateTime; use GetCandy; -use GetCandy\Api\Core\Addresses\Models\Address; -use GetCandy\Api\Core\Addresses\Resources\AddressResource; -use GetCandy\Api\Core\Countries\Actions\FetchCountry; use Illuminate\Support\Arr; use Lorisleiva\Actions\Action; +use GetCandy\Api\Core\Addresses\Models\Address; +use GetCandy\Api\Core\Countries\Models\Country; +use GetCandy\Api\Core\Countries\Actions\FetchCountry; +use GetCandy\Api\Core\Addresses\Resources\AddressResource; class UpdateAddressAction extends Action { @@ -55,7 +56,7 @@ public function rules() 'city' => 'string', 'state' => 'string', 'postal_code' => 'string', - 'country_id' => 'hashid_is_valid:countries', + 'country_id' => 'hashid_is_valid:' . Country::class, 'shipping' => 'boolean', 'billing' => 'boolean', 'default' => 'boolean', diff --git a/src/Core/Attributes/Actions/FetchAttributes.php b/src/Core/Attributes/Actions/FetchAttributes.php index 898785928..937855941 100644 --- a/src/Core/Attributes/Actions/FetchAttributes.php +++ b/src/Core/Attributes/Actions/FetchAttributes.php @@ -24,7 +24,9 @@ public function authorize() */ public function rules() { - return []; + return [ + 'handles' => 'nullable|array|min:0' + ]; } /** @@ -34,6 +36,9 @@ public function rules() */ public function handle() { + if ($this->handles) { + return Attribute::whereIn('handle', $this->handles)->get(); + } return Attribute::get(); } } diff --git a/src/Core/Categories/Models/Category.php b/src/Core/Categories/Models/Category.php index 2c28c8e76..7cad3db56 100644 --- a/src/Core/Categories/Models/Category.php +++ b/src/Core/Categories/Models/Category.php @@ -46,7 +46,7 @@ class Category extends BaseModel * @var array */ protected $fillable = [ - 'attribute_data', 'parent_id', + 'attribute_data', 'parent_id', 'sort', 'range' ]; public function getParentIdAttribute($val) diff --git a/src/Core/Categories/QueryBuilder.php b/src/Core/Categories/QueryBuilder.php index 310865d3d..8c49af635 100644 --- a/src/Core/Categories/QueryBuilder.php +++ b/src/Core/Categories/QueryBuilder.php @@ -29,7 +29,7 @@ public function withDepth($as = 'depth') ->selectRaw('count(1) - 1') ->from($this->model->getTable().' as '.$alias) ->where(function ($query) use ($table, $lft, $rgt, $wrappedAlias) { - $query->whereNull('drafted_at') + $query->whereNull($this->model->getTable() . '.drafted_at') ->whereRaw("{$table}.{$lft} between {$wrappedAlias}.{$lft} and {$wrappedAlias}.{$rgt}"); }); diff --git a/src/Core/Orders/Factories/OrderProcessingFactory.php b/src/Core/Orders/Factories/OrderProcessingFactory.php index 1c32d3491..fdacacd58 100644 --- a/src/Core/Orders/Factories/OrderProcessingFactory.php +++ b/src/Core/Orders/Factories/OrderProcessingFactory.php @@ -225,6 +225,7 @@ public function resolve() $this->order->meta = array_merge($this->order->meta ?? [], $this->meta ?? []); $this->order->company_name = $this->companyName; + $this->order->save(); $response = $driver @@ -279,7 +280,14 @@ protected function processResponse(PaymentResponse $response) event(new OrderProcessedEvent($this->order)); - return $this->order; + return $this->order->load([ + 'lines', + 'lines.variant', + 'shipping', + 'discounts', + 'user.customer', + 'basket.lines.variant.product.assets', + ]); } /** diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index 0b85ad190..394d41936 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -465,12 +465,6 @@ protected function addAddress($id, $data, $type, $user = null) $payload['email'] = $data['email'] ?? null; $payload['phone'] = $data['phone'] ?? null; $data = $payload; - } elseif ($user) { - // TODO: Reassess when we refactor order addresses. - $addressData = $data; - $addressData[$type] = true; - $addressData['postal_code'] = $data['zip']; - CreateAddressAction::run($addressData); } $this->setFields($order, $data, $type); diff --git a/src/Core/Payments/Models/PaymentProviderUser.php b/src/Core/Payments/Models/PaymentProviderUser.php new file mode 100644 index 000000000..85a2d77fb --- /dev/null +++ b/src/Core/Payments/Models/PaymentProviderUser.php @@ -0,0 +1,27 @@ +belongsTo(config('auth.providers.users.model')); + } + + public function scopeProvider($query, $provider) + { + return $query->whereProvider($provider); + } +} diff --git a/src/Core/Payments/Providers/Braintree.php b/src/Core/Payments/Providers/Braintree.php index 451075e88..5713008a8 100644 --- a/src/Core/Payments/Providers/Braintree.php +++ b/src/Core/Payments/Providers/Braintree.php @@ -2,11 +2,13 @@ namespace GetCandy\Api\Core\Payments\Providers; -use Braintree_Exception_NotFound; use Braintree_Gateway; use Braintree_Transaction; -use GetCandy\Api\Core\Payments\Models\Transaction; +use Illuminate\Support\Carbon; +use Braintree_Exception_NotFound; +use Illuminate\Support\Facades\Auth; use GetCandy\Api\Core\Payments\PaymentResponse; +use GetCandy\Api\Core\Payments\Models\Transaction; class Braintree extends AbstractProvider { @@ -43,6 +45,19 @@ protected function setName($name) public function getClientToken() { + $user = Auth::user(); + + if ($user) { + // Does the user have a provider id? + $provider = $user->providerUsers()->provider('braintree')->first(); + + if ($provider) { + return $this->gateway->clientToken()->generate([ + 'customerId' => $provider->provider_id, + ]); + } + } + return $this->gateway->clientToken()->generate(); } @@ -90,13 +105,19 @@ public function charge() $billing = $this->order->billingDetails; $shipping = $this->order->shippingDetails; - $sale = $this->gateway->transaction()->sale([ + $user = $this->order->user; + $customerId = null; + $paymentToken = $this->token; + + $payload = [ 'amount' => $this->order->order_total / 100, - 'paymentMethodNonce' => $this->token, + 'paymentMethodNonce' => $paymentToken, 'merchantAccountId' => $merchant, + 'customerId' => $customerId, 'customer' => [ 'firstName' => $billing['firstname'], 'lastName' => $billing['lastname'], + 'email' => $user ? $user->email : null, ], 'billing' => [ 'firstName' => $billing['firstname'], @@ -117,7 +138,58 @@ public function charge() 'options' => [ 'submitForSettlement' => true, ], - ]); + ]; + // If we have a user, then create a customer in the Vault... + if ($user) { + $providerUser = $user->providerUsers()->provider('braintree')->first(); + + if (!$providerUser) { + $result = $this->gateway->customer()->create([ + 'firstName' => $billing['firstname'], + 'lastName' => $billing['lastname'], + 'email' => $user->email, + 'billingAddress' => [ + 'firstName' => $billing['firstname'], + 'lastName' => $billing['lastname'], + 'locality' => $billing['city'], + 'region' => $billing['county'] ?: $billing['state'], + 'postalCode' => $billing['zip'], + 'streetAddress' => $billing['address'], + ] + ]); + if ($result->success) { + $user->providerUsers()->create([ + 'provider' => 'braintree', + 'provider_id' => $result->customer->id, + ]); + } + $customerId = $result->customer->id; + } else { + $customerId = $providerUser->provider_id; + } + + // Do we want to save this card? + if ($customerId && !empty($this->fields['save'])) { + $result = $this->gateway->paymentMethod()->create([ + 'customerId' => $customerId, + 'paymentMethodNonce' => $this->token + ]); + + $paymentMethod = $result->paymentMethod; + + $user->reusablePayments()->create([ + 'type' => $paymentMethod->cardType, + 'provider' => 'braintree', + 'last_four' => $paymentMethod->last4, + 'token' => $paymentMethod->token, + 'expires_at' => Carbon::createFromFormat('d-m-Y', "01-{$paymentMethod->expirationMonth}-{$paymentMethod->expirationYear}") + ]); + unset($payload['paymentMethodNonce']); + $payload['paymentMethodToken'] = $paymentMethod->token; + } + } + + $sale = $this->gateway->transaction()->sale($payload); if ($sale->success) { $response = new PaymentResponse(true, 'Payment Pending'); @@ -220,4 +292,12 @@ public function void($token) return $result; } + + public function deleteReusablePayment($payment) + { + try { + $this->gateway->paymentMethod()->delete($payment->token); + } catch (\Braintree\Exception\NotFound $e) { + } + } } diff --git a/src/Core/Products/Actions/FetchProduct.php b/src/Core/Products/Actions/FetchProduct.php new file mode 100644 index 000000000..0403223bf --- /dev/null +++ b/src/Core/Products/Actions/FetchProduct.php @@ -0,0 +1,82 @@ + 'integer|required_without:encoded_id', + 'encoded_id' => 'string|hashid_is_valid:'.Product::class.'|required_without:id', + 'sku' => 'nullable|string', + 'draft' => 'nullable|boolean', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\Product|null + */ + public function handle() + { + if ($this->encoded_id && ! $this->sku) { + $this->id = (new Product)->decodeId($this->encoded_id); + } + + $query = Product::query() + ->withCount($this->resolveRelationCounts()) + ->with($this->resolveEagerRelations()); + + if ($this->sku) { + return $query->whereHas('variants', function ($query) { + $query->whereSku($this->sku); + })->first(); + } + + return $query->find($this->id); + } + + /** + * Returns the response from the action. + * + * @param \GetCandy\Api\Core\Products\Models\Product|null $result + * @param \Illuminate\Http\Request $request + * + * @return \GetCandy\Api\Core\Products\Resources\ProductResource|\Illuminate\Http\JsonResponse + */ + public function response($result, $request) + { + if (! $result) { + return $this->errorNotFound(); + } + + return (new ProductResource($result))->only($request->fields); + } +} diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 748f8cd6f..d473ec38c 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -93,7 +93,20 @@ public function firstOrCreate(Model $product) $new->product_id = $newProduct->id; $new->drafted_at = now(); $new->draft_parent_id = $v->id; + $new->save(); + + // Copy customer group pricing... + $groupPricing = $v->customerPricing->map(function ($groupPrice) { + return $groupPrice->only([ + 'customer_group_id', + 'tax_id', + 'price', + 'compare_at_price', + ]); + }); + + $new->customerPricing()->createMany($groupPricing); }); $product->routes->each(function ($r) use ($newProduct) { diff --git a/src/Core/Products/Services/ProductCustomerGroupService.php b/src/Core/Products/Services/ProductCustomerGroupService.php index 188ca9ce4..e2dd8f398 100644 --- a/src/Core/Products/Services/ProductCustomerGroupService.php +++ b/src/Core/Products/Services/ProductCustomerGroupService.php @@ -2,21 +2,16 @@ namespace GetCandy\Api\Core\Products\Services; -use GetCandy\Api\Core\Customers\Services\CustomerGroupService; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroup; +use GetCandy\Api\Core\Customers\Services\CustomerGroupService; class ProductCustomerGroupService extends BaseService { - /** - * @var \GetCandy\Api\Core\Customers\Services\CustomerGroupService - */ - protected $groupService; - - public function __construct(CustomerGroupService $groups) + public function __construct() { $this->model = new Product; - $this->groupService = $groups; } /** @@ -31,7 +26,9 @@ public function store($product, $groups) $product = $this->getByHashedId($product); $groupData = []; foreach ($groups as $group) { - $groupModel = $this->groupService->getByHashedId($group['id']); + $groupModel = FetchCustomerGroup::run([ + 'encoded_id' => $group['id'], + ]); $groupData[$groupModel->id] = [ 'visible' => $group['visible'], 'purchasable' => $group['purchasable'], diff --git a/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php b/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php index 304f24292..57d0be62d 100644 --- a/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php +++ b/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php @@ -2,9 +2,10 @@ namespace GetCandy\Api\Core\ReusablePayments\Actions; -use GetCandy\Api\Core\ReusablePayments\Models\ReusablePayment; -use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use Lorisleiva\Actions\Action; +use GetCandy\Api\Core\Payments\PaymentContract; +use GetCandy\Api\Core\Traits\ReturnsJsonResponses; +use GetCandy\Api\Core\ReusablePayments\Models\ReusablePayment; class DeleteReusablePayment extends Action { @@ -49,13 +50,22 @@ public function rules() * * @return mixed */ - public function handle() + public function handle(PaymentContract $payments) { if (! $this->reusablePayment) { return false; } - return true; + $driver = $payments->with( + $this->reusablePayment->provider + ); + + if (method_exists($driver, 'deleteReusablePayment')) { + $driver->deleteReusablePayment( + $this->reusablePayment + ); + } + return $this->reusablePayment->delete(); } /** diff --git a/src/Core/ReusablePayments/Resources/ReusablePaymentCollection.php b/src/Core/ReusablePayments/Resources/ReusablePaymentCollection.php new file mode 100644 index 000000000..0c8d50778 --- /dev/null +++ b/src/Core/ReusablePayments/Resources/ReusablePaymentCollection.php @@ -0,0 +1,28 @@ + $this->collection, + ]; + } +} diff --git a/src/Core/Search/Actions/FetchSearchedIds.php b/src/Core/Search/Actions/FetchSearchedIds.php index a189b98bf..0e9b83f99 100644 --- a/src/Core/Search/Actions/FetchSearchedIds.php +++ b/src/Core/Search/Actions/FetchSearchedIds.php @@ -43,7 +43,9 @@ public function handle() $parsedIds = $this->delegateTo(DecodeIds::class); $placeholders = implode(',', array_fill(0, count($parsedIds), '?')); // string for the query - $query = $model->with($this->resolveEagerRelations())->whereIn("{$model->getTable()}.id", $parsedIds); + $query = $model->with($this->resolveEagerRelations()) + ->withCount($this->resolveRelationCounts()) + ->whereIn("{$model->getTable()}.id", $parsedIds); if (count($parsedIds)) { $query = $query->orderByRaw("field({$model->getTable()}.id,{$placeholders})", $parsedIds); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php index 6096e6e97..7582f5c9d 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php @@ -167,7 +167,7 @@ protected function getIndexable(Model $model) */ protected function getCategories(Model $model, $lang = 'en') { - $categories = $model->categories; + $categories = $model->refresh()->categories; if (empty($categories)) { return collect([]); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php index e1299d04e..a6d8fc36b 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php @@ -39,23 +39,19 @@ public function rules() */ public function handle() { - if (! $this->aggregate) { - return null; - } + // if (! $this->aggregate) { + // return []; + // } - return FetchFilterableAttributes::run()->map(function ($attribute) { + $results = FetchFilterableAttributes::run()->map(function ($attribute) { return $attribute->handle; - })->filter(function ($attribute) { - return in_array($attribute, $this->aggregate); - })->merge( - collect(['priceRange', 'category']) - )->map(function ($attribute) { + }); + return collect(['priceRange', 'category'])->merge($results)->map(function ($attribute) { $name = ucfirst(camel_case(str_singular($attribute))); $classname = "GetCandy\Api\Core\Search\Drivers\Elastic\Aggregators\\{$name}"; if (class_exists($classname)) { return app()->make($classname); } - return new Attribute($attribute); })->toArray(); } diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php new file mode 100644 index 000000000..d032d3093 --- /dev/null +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php @@ -0,0 +1,131 @@ + 'array|min:0', + ]; + } + + /** + * Execute the action and return a result. + * + * @return array|null + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function handle() + { + $preAggs = collect($this->aggregations)->filter(function ($agg, $key) { + return ! Str::contains($key, '_after'); + }); + $postAggs = collect($this->aggregations)->filter(function ($agg, $key) { + return Str::contains($key, '_after'); + }); + + return [ + 'available' => $this->resolveAggregations($preAggs), + 'applied' => $this->resolveAggregations($postAggs), + ]; + } + + /** + * Returns categories which have been aggregated. + * + * @param array $buckets The category aggregate bucket data + * + * @return \Illuminate\Support\Collection + */ + public function getAggregatedCategories(array $buckets) + { + return GetCandy::categories()->getByHashedIds( + collect($buckets)->map(function ($cat) { + return $cat['key']; + })->toArray() + ); + } + + + protected function resolveAggregations(Collection $aggregations) + { + // Get our attributes here so they're only fetched once + $attributeHandles = $aggregations->mapWithKeys(function ($agg, $key) { + return [str_replace('_after', '', $key) => null]; + })->keys()->all(); + + $attributes = FetchAttributes::run([ + 'handles' => $attributeHandles, + ]); + + $categories = collect(); + + if ($categoryBuckets = Arr::get($aggregations, 'categories.categories.buckets')) { + dd($categoryBuckets); + // Get the categories here so they're only fetched once + $categories = $this->getAggregatedCategories($categoryBuckets); + } + + return $aggregations->mapWithKeys(function ($agg, $key) use ($attributes, $categories) { + $key = str_replace('_after', '', $key); + + $extra = [ + 'handle' => $key, + ]; + + $data = $agg[$key] ?? $agg; + + if ($key == 'categories' || $key == 'category') { + foreach ($data['buckets'] as $bucketIndex => $bucket) { + $category = $categories->first(function ($cat) use ($bucket) { + return $cat->encodedId() == $bucket['key']; + }); + + if ($category) { + $data['buckets'][$bucketIndex]['data'] = new CategoryResource($category); + } else { + unset($data['buckets'][$bucketIndex]); + } + } + } else { + $attribute = $attributes->first(function ($att) use ($key) { + return $att->handle == $key; + }); + if ($attribute) { + $extra['attribute'] = new AttributeResource($attribute); + } + } + + return [$key => array_merge($extra, $data)]; + })->sortBy(function ($agg) { + return ! empty($agg['attribute']) ? $agg['attribute']->position : 0; + }); + } +} diff --git a/src/Core/Search/Providers/Elastic/AggregationResolver.php b/src/Core/Search/Providers/Elastic/AggregationResolver.php index c7a3b1652..b69c04994 100644 --- a/src/Core/Search/Providers/Elastic/AggregationResolver.php +++ b/src/Core/Search/Providers/Elastic/AggregationResolver.php @@ -60,54 +60,4 @@ public function resolve(array $aggregations) 'applied' => $this->resolveAggregations($postAggs), ]; } - - protected function resolveAggregations(Collection $aggregations) - { - // Get our attributes here so they're only fetched once - $attributes = $this->getAggregatedAttributes( - $aggregations->mapWithKeys(function ($agg, $key) { - return [str_replace('_after', '', $key) => null]; - })->keys()->all() - ); - - $categories = collect(); - - if ($categoryBuckets = Arr::get($aggregations, 'categories.categories.buckets')) { - // Get the categories here so they're only fetched once - $categories = $this->getAggregatedCategories($categoryBuckets); - } - - return $aggregations->mapWithKeys(function ($agg, $key) use ($attributes, $categories) { - $key = str_replace('_after', '', $key); - - $extra = [ - 'handle' => $key, - ]; - - $data = $agg[$key] ?? $agg; - - if ($key == 'categories') { - foreach ($data['buckets'] as $bucketIndex => $bucket) { - $category = $categories->first(function ($cat) use ($bucket) { - return $cat->encodedId() == $bucket['key']; - }); - - if ($category) { - $data['buckets'][$bucketIndex]['data'] = new CategoryResource($category); - } else { - unset($data['buckets'][$bucketIndex]); - } - } - } else { - $attribute = $attributes->first(function ($att) use ($key) { - return $att->handle == $key; - }); - $extra['data'] = new AttributeResource($attribute); - } - - return [$key => array_merge($extra, $data)]; - })->sortBy(function ($agg) { - return ! empty($agg['data']) ? $agg['data']->resource->position : 0; - }); - } } diff --git a/src/Core/Traits/HasCandy.php b/src/Core/Traits/HasCandy.php index 1018ad609..aa2786289 100644 --- a/src/Core/Traits/HasCandy.php +++ b/src/Core/Traits/HasCandy.php @@ -2,14 +2,15 @@ namespace GetCandy\Api\Core\Traits; +use Spatie\Permission\Traits\HasRoles; +use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Baskets\Models\SavedBasket; use GetCandy\Api\Core\Customers\Models\Customer; -use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Languages\Models\Language; -use GetCandy\Api\Core\Orders\Models\Order; +use GetCandy\Api\Core\Baskets\Models\SavedBasket; +use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Payments\Models\PaymentProviderUser; use GetCandy\Api\Core\ReusablePayments\Models\ReusablePayment; -use Spatie\Permission\Traits\HasRoles; trait HasCandy { @@ -54,6 +55,11 @@ public function latestBasket() ->orderBy('created_at', 'DESC'); } + public function providerUsers() + { + return $this->hasMany(PaymentProviderUser::class); + } + public function savedBaskets() { return $this->hasManyThrough(SavedBasket::class, Basket::class); diff --git a/src/Core/Users/Actions/FetchCurrentUser.php b/src/Core/Users/Actions/FetchCurrentUser.php index c3a16ce31..067ba1d65 100644 --- a/src/Core/Users/Actions/FetchCurrentUser.php +++ b/src/Core/Users/Actions/FetchCurrentUser.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Core\Users\Actions; +use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Users\Resources\UserResource; -use Lorisleiva\Actions\Action; -class FetchCurrentUser extends Action +class FetchCurrentUser extends AbstractAction { /** * Determine if the user is authorized to make this action. @@ -34,9 +34,10 @@ public function rules() */ public function handle() { - return $this->user()->load([ - 'addresses.country', 'roles.permissions', 'customer', - ]); + return $this->user()->load(array_merge( + $this->resolveEagerRelations(), + ['addresses.country', 'roles.permissions', 'customer', 'savedBaskets.basket.lines'] + )); } /** diff --git a/src/Core/Users/Resources/UserResource.php b/src/Core/Users/Resources/UserResource.php index 0d05d38ad..eb0cb94e5 100644 --- a/src/Core/Users/Resources/UserResource.php +++ b/src/Core/Users/Resources/UserResource.php @@ -2,13 +2,16 @@ namespace GetCandy\Api\Core\Users\Resources; -use GetCandy\Api\Core\Addresses\Resources\AddressCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerResource; use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Acl\RoleCollection; -use GetCandy\Api\Http\Resources\Orders\OrderCollection; use GetCandy\Api\Http\Resources\Orders\OrderResource; +use GetCandy\Api\Http\Resources\Orders\OrderCollection; +use GetCandy\Api\Http\Resources\Baskets\BasketCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerResource; +use GetCandy\Api\Core\Addresses\Resources\AddressCollection; +use GetCandy\Api\Http\Resources\Baskets\SavedBasketCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; +use GetCandy\Api\Core\ReusablePayments\Resources\ReusablePaymentCollection; class UserResource extends AbstractResource { @@ -26,7 +29,10 @@ public function includes() return [ 'customer' => $this->include('customer', CustomerResource::class), 'first_order' => $this->include('firstOrder', OrderResource::class), + 'baskets' => new BasketCollection($this->whenLoaded('baskets')), + 'saved_baskets' => new SavedBasketCollection($this->whenLoaded('savedBaskets')), 'roles' => new RoleCollection($this->whenLoaded('roles')), + 'reusable_payments' => new ReusablePaymentCollection($this->whenLoaded('reusablePayments')), 'groups' => new CustomerGroupCollection($this->whenLoaded('groups')), 'orders' => new OrderCollection($this->whenLoaded('orders')), 'addresses' => new AddressCollection($this->whenLoaded('addresses')), diff --git a/src/Http/Controllers/ActivityLog/ActivityLogController.php b/src/Http/Controllers/ActivityLog/ActivityLogController.php index 12d9f1843..b4bf5097a 100644 --- a/src/Http/Controllers/ActivityLog/ActivityLogController.php +++ b/src/Http/Controllers/ActivityLog/ActivityLogController.php @@ -37,7 +37,7 @@ public function index(Request $request, ActivityLogCriteriaInterface $criteria) $service = app()->getInstance()->make($this->types[$request->type]); $model = $service->getByHashedId($request->id, true); - $logs = $criteria->include(['user.details'])->model($model)->get(); + $logs = $criteria->include(['user.customer'])->model($model)->get(); return new ActivityCollection($logs); } diff --git a/src/Http/Controllers/Baskets/BasketLineController.php b/src/Http/Controllers/Baskets/BasketLineController.php index 15ef6d44b..3522bd66f 100644 --- a/src/Http/Controllers/Baskets/BasketLineController.php +++ b/src/Http/Controllers/Baskets/BasketLineController.php @@ -3,14 +3,15 @@ namespace GetCandy\Api\Http\Controllers\Baskets; use GetCandy; -use GetCandy\Api\Core\Baskets\Factories\BasketLineFactory; +use Illuminate\Http\Request; +use GetCandy\Api\Core\Baskets\Models\BasketLine; use GetCandy\Api\Http\Controllers\BaseController; -use GetCandy\Api\Http\Requests\Baskets\ChangeQuantityRequest; +use GetCandy\Api\Http\Resources\Baskets\BasketResource; +use GetCandy\Api\Http\Requests\Baskets\UpdateLineRequest; +use GetCandy\Api\Core\Baskets\Factories\BasketLineFactory; use GetCandy\Api\Http\Requests\Baskets\CreateLinesRequest; use GetCandy\Api\Http\Requests\Baskets\DeleteLinesRequest; -use GetCandy\Api\Http\Requests\Baskets\UpdateLineRequest; -use GetCandy\Api\Http\Resources\Baskets\BasketResource; -use Illuminate\Support\Facades\Request; +use GetCandy\Api\Http\Requests\Baskets\ChangeQuantityRequest; class BasketLineController extends BaseController { @@ -24,11 +25,11 @@ class BasketLineController extends BaseController */ protected $basketLines; - public function __construct(BasketLineFactory $factory) + public function __construct(BasketLineFactory $factory, Request $request) { $this->factory = $factory; $this->basketLines = GetCandy::basketLines(); - $this->basketLines->setIncludes(Request::get('include')); + $this->basketLines->setIncludes($request->include); } /** @@ -106,4 +107,12 @@ public function destroy(DeleteLinesRequest $request) return new BasketResource($basket); } + + public function destroyLine($id, Request $request) + { + // TODO: Move this to an action. + $realId = (new BasketLine)->decodeId($id); + BasketLine::destroy($realId); + return $this->respondWithNoContent(); + } } diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index d8e469a13..02db437fd 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -35,7 +35,7 @@ public function index(Request $request, CategoryCriteria $criteria) { $criteria ->tree($request->tree) - ->depth($request->depth) + ->depth($request->depth ?: 1) ->include($this->parseIncludes($request->include)) ->limit($request->limit); diff --git a/src/Http/Resources/AbstractResource.php b/src/Http/Resources/AbstractResource.php index a4737cee2..de1abb2a0 100644 --- a/src/Http/Resources/AbstractResource.php +++ b/src/Http/Resources/AbstractResource.php @@ -2,10 +2,11 @@ namespace GetCandy\Api\Http\Resources; -use Illuminate\Http\Resources\Json\AnonymousResourceCollection; -use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Http\Resources\MissingValue; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\Resources\MissingValue; +use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Http\Resources\Json\AnonymousResourceCollection; abstract class AbstractResource extends JsonResource { @@ -164,6 +165,11 @@ protected function relationLoaded($relation) protected function include($relation, $resource) { + if ($relation instanceof Model) { + return [ + 'data' => new $resource($relation), + ]; + } return $this->when($this->relationLoaded($relation), function () use ($relation, $resource) { return ['data' => new $resource($this->whenLoaded($relation))]; }); diff --git a/src/Http/Resources/Baskets/BasketResource.php b/src/Http/Resources/Baskets/BasketResource.php index 2e29d7092..dd1e98e5e 100644 --- a/src/Http/Resources/Baskets/BasketResource.php +++ b/src/Http/Resources/Baskets/BasketResource.php @@ -15,6 +15,7 @@ public function payload() 'id' => $this->encoded_id, 'total' => $this->total_cost, 'sub_total' => $this->sub_total, + 'currency' => $this->currency, 'tax_total' => $this->total_tax, 'discount_total' => round($this->discount_total, 2), 'changed' => $this->changed, diff --git a/src/Http/Resources/Baskets/SavedBasketResource.php b/src/Http/Resources/Baskets/SavedBasketResource.php index c857401db..cba5c4a88 100644 --- a/src/Http/Resources/Baskets/SavedBasketResource.php +++ b/src/Http/Resources/Baskets/SavedBasketResource.php @@ -18,7 +18,7 @@ public function payload() public function includes() { return [ - 'basket' => $this->include(BasketResource::class, $this->getBasket()), + 'basket' => $this->include($this->getBasket(), BasketResource::class), ]; } From 521983b819b5eacd7082d9ed9d29eecaf208b618 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:22:37 +0000 Subject: [PATCH 008/152] Add tests --- database/factories/ProductFactory.php | 16 +++++++++++- database/factories/ProductVariantFactory.php | 22 ++++++++++++++++ .../Products/Actions/FetchProductTest.php | 25 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 database/factories/ProductVariantFactory.php create mode 100644 tests/Unit/Products/Actions/FetchProductTest.php diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 3ff67ee01..20e9c62df 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -1,6 +1,10 @@ define(GetCandy\Api\Core\Products\Models\Product::class, function (Faker $faker) { + +$factory->define(Product::class, function (Faker $faker) { return [ 'attribute_data' => [ 'name' => [ @@ -24,3 +29,12 @@ ], ]; }); + +$factory->afterCreating(Product::class, function ($product, $faker) { + // Set up initial variant + $product->variants()->save(factory(ProductVariant::class)->make()); + + $channel = factory(Channel::class)->create(); + + dd($channel); +}); \ No newline at end of file diff --git a/database/factories/ProductVariantFactory.php b/database/factories/ProductVariantFactory.php new file mode 100644 index 000000000..eb99679da --- /dev/null +++ b/database/factories/ProductVariantFactory.php @@ -0,0 +1,22 @@ +define(GetCandy\Api\Core\Products\Models\ProductVariant::class, function (Faker $faker) { + return [ + 'sku' => $faker->unique()->slug, + 'stock' => $faker->numberBetween(10,2000), + 'price' => $faker->randomNumber(2), + ]; +}); diff --git a/tests/Unit/Products/Actions/FetchProductTest.php b/tests/Unit/Products/Actions/FetchProductTest.php new file mode 100644 index 000000000..888b010d1 --- /dev/null +++ b/tests/Unit/Products/Actions/FetchProductTest.php @@ -0,0 +1,25 @@ +create(); + + $fetchedProduct = FetchProduct::run([ + 'encoded_id' => $product->encoded_id, + ]); + + dd($fetchedProduct); + $this->assertEquals($product->id, $fetchedProduct->id); + } +} From dbd933ec5358b900c11973790f4532b86d3bb375 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:23:45 +0000 Subject: [PATCH 009/152] Add attribute and negate n+1 issue --- src/Http/Resources/Categories/CategoryResource.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Http/Resources/Categories/CategoryResource.php b/src/Http/Resources/Categories/CategoryResource.php index c039ff006..4d04aa45c 100644 --- a/src/Http/Resources/Categories/CategoryResource.php +++ b/src/Http/Resources/Categories/CategoryResource.php @@ -21,11 +21,12 @@ public function payload() 'id' => $this->encodedId(), 'sort' => $this->sort, 'drafted_at' => $this->drafted_at, - 'products_count' => $this->products()->count(), - 'children_count' => $this->children()->count(), + 'products_count' => $this->products_count, + 'children_count' => $this->children_count, 'depth' => $this->depth, 'has_draft' => $this->draft()->exists(), 'left_pos' => $this->_lft, + 'sort' => $this->sort, 'right_pos' => $this->_rgt, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, From 8c422ceb358d90453a0c73ff7efb9ef0ee496157 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:24:36 +0000 Subject: [PATCH 010/152] Tidy up --- src/Core/Routes/Actions/SearchForRoute.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index ad883c92a..2d95ed7e4 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -41,7 +41,9 @@ public function rules(): array */ public function handle() { - $query = Route::whereSlug($this->slug)->with($this->resolveEagerRelations()); + $query = Route::whereSlug($this->slug)->with( + $this->resolveEagerRelations() + ); if ($this->path) { $query->wherePath($this->path); From dc16f3e954cc225df55942c8b7def80b2022cfc0 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:25:37 +0000 Subject: [PATCH 011/152] Aggregation and filter fixes --- .../Actions/Searching/Search.php | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index e135e8634..9f4433bcb 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -4,14 +4,16 @@ use Elastica\Query; use Elastica\Query\BoolQuery; +use Lorisleiva\Actions\Action; use Elastica\Search as ElasticaSearch; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Products\Models\Product; +use Illuminate\Pagination\LengthAwarePaginator; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; -use GetCandy\Api\Http\Resources\Categories\CategoryCollection; use GetCandy\Api\Http\Resources\Products\ProductCollection; -use Illuminate\Pagination\LengthAwarePaginator; -use Lorisleiva\Actions\Action; +use GetCandy\Api\Http\Resources\Attributes\AttributeResource; +use GetCandy\Api\Http\Resources\Categories\CategoryCollection; +use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; class Search extends Action { @@ -46,11 +48,12 @@ public function rules() 'limit' => 'nullable|numeric', 'offset' => 'nullable|numeric', 'search_type' => 'nullable|string', - 'filters' => 'nullable|array', + 'filters' => 'nullable', 'aggregate' => 'nullable|array', 'term' => 'nullable|string', 'language' => 'nullable|string', 'page' => 'nullable|numeric|min:1', + 'category' => 'nullable|string', ]; } @@ -68,14 +71,23 @@ public function handle() $this->set('index', "{$prefix}_{$this->search_type}_{$language}"); } - $this->filters = $this->filters ?: []; + $this->filters = $this->filters ? collect(explode(',', $this->filters))->mapWithKeys(function ($filter) { + list($label, $value) = explode(':', $filter); + return [$label => $value]; + })->toArray() : []; + $this->aggregates = $this->aggregates ?: []; $this->language = $this->language ?: app()->getLocale(); + $this->set('category', $this->category ? explode(':', $this->category) : []); + $client = FetchClient::run(); $term = $this->term ? FetchTerm::run($this->attributes) : null; - $filters = $this->delegateTo(FetchFilters::class); + $filters = FetchFilters::run([ + 'category' => $this->category, + 'filters' => $this->filters + ]); $query = new Query(); $query->setParam('size', $this->limit ?: 100); @@ -92,18 +104,21 @@ public function handle() ]); } - $aggregations = $this->delegateTo(FetchAggregations::class) ?? []; + $aggregations = FetchAggregations::run(); $query = SetExcludedFields::run(['query' => $query]); // Set filters as post filters $postFilter = new BoolQuery; + $preFilters = $filters->filter(function ($filter) { return in_array($filter->handle, $this->topFilters); }); + $preFilters->each(function ($filter) use ($boolQuery) { + // dump($filter->getQuery()); $boolQuery->addFilter( $filter->getQuery() ); @@ -142,12 +157,14 @@ public function handle() $query->setQuery($boolQuery); + $query = SetSorting::run([ 'query' => $query, 'type' => $this->search_type, 'sort' => $this->sort, ]); + $query->setHighlight(config('getcandy.search.highlight') ?? [ 'pre_tags' => [''], 'post_tags' => [''], @@ -188,10 +205,15 @@ public function jsonResponse($result, $request) } } + $aggregations = MapAggregations::run([ + 'aggregations' => $result->getAggregations(), + ]); + $models = FetchSearchedIds::run([ 'model' => $this->search_type == 'products' ? Product::class : Category::class, 'encoded_ids' => $ids->toArray(), 'include' => $request->include, + 'counts' => $request->counts, ]); $resource = ProductCollection::class; @@ -209,7 +231,7 @@ public function jsonResponse($result, $request) return (new $resource($paginator))->additional([ 'meta' => [ - 'aggregations' => $result->getAggregations(), + 'aggregations' => $aggregations, 'highlight' => $result->getQuery()->getParam('highlight'), ], ]); From 879060806edc1dd9f2ef5f1072524598e49d37fb Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 6 Jan 2021 14:44:27 +0000 Subject: [PATCH 012/152] Squashed commit of the following: commit 72fb73dd61c56fa44effc78f48b9c0a8eea9602b Author: Alec Date: Wed Dec 16 11:24:40 2020 +0000 Add inGroup helper commit e53eaee8ef42af37295e8f71da9e62a8720f2ba8 Author: Alec Date: Wed Dec 16 11:23:51 2020 +0000 Add sort to category resource commit 2d67bca01a4936343cc5fc371e1d2b66cf29fd67 Author: Alec Date: Sat Dec 12 14:58:53 2020 +0000 Enable filtering by category on search commit 8527f3eb7532f462622fae2956afeb5f270aa51e Author: Dan Storm Date: Mon Dec 14 09:55:39 2020 +0100 Upgrade testsuite (#335) commit 7118e6796e4583e22dd6d269fcaad723d9b81c72 Author: Glenn Jacobs Date: Mon Nov 16 16:34:59 2020 +0000 Feature/tidy openapi (#326) * Move password reset to Account tag in OpenAPI spec commit e681748e6f265b020423ab51c37821b1fbed33c1 Author: Glenn Jacobs Date: Mon Nov 16 16:14:09 2020 +0000 Feature/tidy openapi (#324) * Build full api spec file in GitHub Actions and remove from repo * Sort OpenAPI spec alphabetically * Apply fixes from StyleCI (#325) Co-authored-by: Glenn Jacobs commit cfecb402d0bd10f6f31d9d36fd42bfcb2b5d58bf Author: Steve Hitchman Date: Mon Nov 16 10:18:40 2020 +0000 Fix merge conflict commit a3699fcf0773ddda283949f716c7e2e686d75e57 Merge: 0821123 5338f75 Author: Steve Hitchman Date: Mon Nov 16 10:12:19 2020 +0000 Merge branch 'develop' commit 0821123df922f4a43c6ccb03baac992c98bcd08b Author: Alec Date: Thu Nov 12 07:57:51 2020 +0000 Merge dev commit 2ccc4c8c428e1357f787098f817d05ec9fd58c07 Author: Alec Date: Tue Nov 10 10:23:33 2020 +0000 Fixes --- composer.json | 6 +- phpunit.xml | 56 ++++++++----------- .../Addresses/Actions/UpdateAddressAction.php | 7 +-- src/Core/Customers/Models/Customer.php | 5 ++ src/Core/Search/Actions/FetchSearchedIds.php | 4 +- .../Actions/Searching/FetchAggregations.php | 1 + .../Actions/Searching/FetchFilters.php | 8 +++ .../Actions/Searching/Search.php | 9 +-- 8 files changed, 46 insertions(+), 50 deletions(-) diff --git a/composer.json b/composer.json index 57d023ef5..af234eac2 100644 --- a/composer.json +++ b/composer.json @@ -31,12 +31,12 @@ "require-dev": { "fzaninotto/faker": "~1.4", "mockery/mockery": "~1.0", - "phpunit/phpunit": "^9.2", "filp/whoops": "~2.0", - "orchestra/testbench": "^6.2", + "orchestra/testbench": "^6.6", "league/openapi-psr7-validator": "^0.7", "neondigital/laravel-openapi-validator": "^0.1", - "brianium/paratest": "^4.1" + "brianium/paratest": "^4.1", + "phpunit/phpunit": "^9.5" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index b23777b38..54e983df5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,37 +1,27 @@ - - - - ./tests/Feature - - - - ./tests/Unit - - - - - - - ./app - - - - - - - - - - + + + + + + + + + diff --git a/src/Core/Addresses/Actions/UpdateAddressAction.php b/src/Core/Addresses/Actions/UpdateAddressAction.php index cfc02730e..3c48fe598 100644 --- a/src/Core/Addresses/Actions/UpdateAddressAction.php +++ b/src/Core/Addresses/Actions/UpdateAddressAction.php @@ -4,13 +4,12 @@ use DateTime; use GetCandy; +use GetCandy\Api\Core\Addresses\Models\Address; +use GetCandy\Api\Core\Addresses\Resources\AddressResource; +use GetCandy\Api\Core\Countries\Actions\FetchCountry; use Illuminate\Support\Arr; use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Addresses\Models\Address; use GetCandy\Api\Core\Countries\Models\Country; -use GetCandy\Api\Core\Countries\Actions\FetchCountry; -use GetCandy\Api\Core\Addresses\Resources\AddressResource; - class UpdateAddressAction extends Action { /** diff --git a/src/Core/Customers/Models/Customer.php b/src/Core/Customers/Models/Customer.php index ee54c7a42..761817b38 100644 --- a/src/Core/Customers/Models/Customer.php +++ b/src/Core/Customers/Models/Customer.php @@ -37,6 +37,11 @@ public function setFieldsAttribute($val) $this->attributes['fields'] = is_string($val) ? $val : json_encode($val); } + public function inGroup($group) + { + return $this->customerGroups()->where('handle', '=', $group)->exists(); + } + public function users() { return $this->hasMany(GetCandy::getUserModel()); diff --git a/src/Core/Search/Actions/FetchSearchedIds.php b/src/Core/Search/Actions/FetchSearchedIds.php index 0e9b83f99..a189b98bf 100644 --- a/src/Core/Search/Actions/FetchSearchedIds.php +++ b/src/Core/Search/Actions/FetchSearchedIds.php @@ -43,9 +43,7 @@ public function handle() $parsedIds = $this->delegateTo(DecodeIds::class); $placeholders = implode(',', array_fill(0, count($parsedIds), '?')); // string for the query - $query = $model->with($this->resolveEagerRelations()) - ->withCount($this->resolveRelationCounts()) - ->whereIn("{$model->getTable()}.id", $parsedIds); + $query = $model->with($this->resolveEagerRelations())->whereIn("{$model->getTable()}.id", $parsedIds); if (count($parsedIds)) { $query = $query->orderByRaw("field({$model->getTable()}.id,{$placeholders})", $parsedIds); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php index a6d8fc36b..00a3f21a3 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php @@ -52,6 +52,7 @@ public function handle() if (class_exists($classname)) { return app()->make($classname); } + return new Attribute($attribute); })->toArray(); } diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index 4bac60ff6..8ed3399f9 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; use GetCandy\Api\Core\Attributes\Actions\FetchAttribute; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CategoryFilter; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CustomerGroupFilter; use Lorisleiva\Actions\Action; @@ -29,6 +30,7 @@ public function rules() { return [ 'filters' => 'array|min:0', + 'category' => 'array|min:0' ]; } @@ -41,10 +43,16 @@ public function rules() */ public function handle() { + $applied = collect([ (new CustomerGroupFilter)->process($this->user()), ]); + if (!empty($this->category)) { + $applied->push( + (new CategoryFilter)->process($this->category) + ); + } foreach ($this->filters ?? [] as $filter => $value) { $object = $this->findFilter($filter); if ($object && $object = $object->process($value, $filter)) { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 9f4433bcb..ac36b0e79 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -4,16 +4,12 @@ use Elastica\Query; use Elastica\Query\BoolQuery; -use Lorisleiva\Actions\Action; use Elastica\Search as ElasticaSearch; -use GetCandy\Api\Core\Products\Models\Product; -use Illuminate\Pagination\LengthAwarePaginator; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Http\Resources\Attributes\AttributeResource; use GetCandy\Api\Http\Resources\Categories\CategoryCollection; -use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; class Search extends Action { @@ -80,7 +76,6 @@ public function handle() $this->language = $this->language ?: app()->getLocale(); $this->set('category', $this->category ? explode(':', $this->category) : []); - $client = FetchClient::run(); $term = $this->term ? FetchTerm::run($this->attributes) : null; @@ -116,7 +111,6 @@ public function handle() return in_array($filter->handle, $this->topFilters); }); - $preFilters->each(function ($filter) use ($boolQuery) { // dump($filter->getQuery()); $boolQuery->addFilter( @@ -216,6 +210,7 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); + $resource = ProductCollection::class; if ($this->search_type == 'categories') { From cc0b1a7c06995c72c12bf0d8e0b5f053d42fb551 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 11:14:10 +0000 Subject: [PATCH 013/152] Fix page/offset --- .../Elasticsearch/Actions/Searching/Search.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index ac36b0e79..c3ce001c3 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -9,7 +9,11 @@ use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; use GetCandy\Api\Http\Resources\Products\ProductCollection; +use GetCandy\Api\Http\Resources\Attributes\AttributeResource; use GetCandy\Api\Http\Resources\Categories\CategoryCollection; +use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; +use Illuminate\Pagination\LengthAwarePaginator; +use Lorisleiva\Actions\Action; class Search extends Action { @@ -43,6 +47,7 @@ public function rules() 'index' => 'nullable|string', 'limit' => 'nullable|numeric', 'offset' => 'nullable|numeric', + 'page' => 'nullable|numeric', 'search_type' => 'nullable|string', 'filters' => 'nullable', 'aggregate' => 'nullable|array', @@ -84,9 +89,12 @@ public function handle() 'filters' => $this->filters ]); + $limit = $this->limit ?: 100; + $offset = $this->offset ?: (($this->page != 1 ?: 1 - 1) * $limit); + $query = new Query(); - $query->setParam('size', $this->limit ?: 100); - $query->setParam('from', $this->offset ?: 0); + $query->setParam('size', $limit); + $query->setParam('from', $offset); $boolQuery = new BoolQuery; From 696ba08bdc634a67b0301396513aa10c3fd7052b Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 11:14:28 +0000 Subject: [PATCH 014/152] Change - to a pipe for multi filtering --- src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php index a36e6b401..1b05b52dd 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php @@ -16,10 +16,9 @@ class TextFilter extends AbstractFilter public function getQuery() { $filter = new BoolQuery; - foreach ($this->value as $value) { - if (strpos($value, '-') && preg_match('/^[0-9-*]+$/', $value)) { - $value = explode('-', $value); + if (strpos($value, '|')) { + $value = explode('|', $value); } if (is_array($value)) { $range = new Range($this->field.'.filter', [ From 558f5c4431569e28045a5fcb5eed4f4d3875416b Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 11:15:36 +0000 Subject: [PATCH 015/152] Change post to put on basket lines --- routes/api.client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.client.php b/routes/api.client.php index 83273acc0..a5132a591 100644 --- a/routes/api.client.php +++ b/routes/api.client.php @@ -51,7 +51,7 @@ $router->put('basket-lines/{id}', 'Baskets\BasketLineController@update'); $router->delete('basket-lines/{id}', 'Baskets\BasketLineController@destroyLine'); $router->post('basket-lines/{id}/add', 'Baskets\BasketLineController@addQuantity'); -$router->post('basket-lines/{id}/remove', 'Baskets\BasketLineController@removeQuantity'); +$router->put('basket-lines/{id}/remove', 'Baskets\BasketLineController@removeQuantity'); $router->delete('basket-lines', 'Baskets\BasketLineController@destroy'); /* From 4bd41388d54206849b92f190a7b896d370a59748 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 13:01:54 +0000 Subject: [PATCH 016/152] Revert channel and customer group scope --- src/Core/Scopes/ChannelScope.php | 26 +++++++++++++++----------- src/Core/Scopes/CustomerGroupScope.php | 21 ++++++++++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/Core/Scopes/ChannelScope.php b/src/Core/Scopes/ChannelScope.php index d35989f35..250f1cca1 100644 --- a/src/Core/Scopes/ChannelScope.php +++ b/src/Core/Scopes/ChannelScope.php @@ -19,20 +19,24 @@ public function apply(Builder $builder, Model $model) { $channel = app()->getInstance()->make(ChannelFactoryInterface::class); $this->resolve(function () use ($builder, $channel) { - $model = $builder->getModel(); - $relation = $model->channels(); + $builder->whereHas('channels', function ($query) use ($channel) { + $query->whereHandle($channel->current()) + ->whereDate('published_at', '<=', now()); + }); + // $model = $builder->getModel(); + // $relation = $model->channels(); - $columnsToSelect = $this->filterColumns($builder, ["{$model->getTable()}.*"]); + // $columnsToSelect = $this->filterColumns($builder, ["{$model->getTable()}.*"]); - if ($columnsToSelect->count()) { - $builder->addSelect($columnsToSelect->toArray()); - } + // if ($columnsToSelect->count()) { + // $builder->addSelect($columnsToSelect->toArray()); + // } - $builder->join($relation->getTable(), function ($join) use ($relation, $model, $channel) { - $join->on("{$model->getTable()}.id", '=', $relation->getExistenceCompareKey()) - ->where("{$relation->getTable()}.channel_id", $channel->getChannel()->id) - ->whereDate("{$relation->getTable()}.published_at", '<=', now()); - })->groupBy($relation->getExistenceCompareKey()); + // $builder->join($relation->getTable(), function ($join) use ($relation, $model, $channel) { + // $join->on("{$model->getTable()}.id", '=', $relation->getExistenceCompareKey()) + // ->where("{$relation->getTable()}.channel_id", $channel->getChannel()->id) + // ->whereDate("{$relation->getTable()}.published_at", '<=', now()); + // })->groupBy($relation->getExistenceCompareKey()); }); } diff --git a/src/Core/Scopes/CustomerGroupScope.php b/src/Core/Scopes/CustomerGroupScope.php index 9fc4305ec..1e2f73ba5 100644 --- a/src/Core/Scopes/CustomerGroupScope.php +++ b/src/Core/Scopes/CustomerGroupScope.php @@ -17,16 +17,19 @@ class CustomerGroupScope extends AbstractScope public function apply(Builder $builder, Model $model) { $this->resolve(function () use ($builder) { - $model = $builder->getModel(); - $relation = $model->customerGroups(); - $selects = []; - - $builder->join($relation->getTable(), function ($join) use ($relation, $model) { - $join->on("{$model->getTable()}.id", '=', $relation->getExistenceCompareKey()) - ->whereIn("{$relation->getTable()}.customer_group_id", $this->getGroups()) - ->where("{$relation->getTable()}.visible", '=', true) - ->groupBy($relation->getExistenceCompareKey()); + $builder->whereHas('customerGroups', function ($q) { + $q->whereIn('customer_groups.id', $this->getGroups())->where('visible', '=', true); }); + // $model = $builder->getModel(); + // $relation = $model->customerGroups(); + // $selects = []; + + // $builder->join($relation->getTable(), function ($join) use ($relation, $model) { + // $join->on("{$model->getTable()}.id", '=', $relation->getExistenceCompareKey()) + // ->whereIn("{$relation->getTable()}.customer_group_id", $this->getGroups()) + // ->where("{$relation->getTable()}.visible", '=', true) + // ->groupBy($relation->getExistenceCompareKey()); + // }); }); } From fd016396d7946e7ae48917d699001f1665743ad5 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 13:02:18 +0000 Subject: [PATCH 017/152] Add parsing and add handle to attribute --- .../Resources/Products/ProductResource.php | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Http/Resources/Products/ProductResource.php b/src/Http/Resources/Products/ProductResource.php index 7dfaa605f..65f8e9218 100644 --- a/src/Http/Resources/Products/ProductResource.php +++ b/src/Http/Resources/Products/ProductResource.php @@ -23,7 +23,8 @@ public function payload() return [ 'id' => $this->encoded_id, 'drafted_at' => $this->drafted_at, - 'option_data' => $this->option_data, + 'option_data' => $this->parseOptionData($this->option_data), + 'variants_count' => $this->variants_count, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; @@ -58,7 +59,20 @@ public function includes() 'customer_groups' => new CustomerGroupCollection($this->whenLoaded('customerGroups')), ]; } - + + protected function parseOptionData($data) + { + $data = $this->sortOptions($data); + foreach ($data as $optionKey => $option) { + $data[$optionKey]['options'] = collect($option['options'] ?? [])->mapWithKeys(function ($option, $handle) { + $option['handle'] = $handle; + return [$handle => $option]; + })->toArray(); + } + + return $data; + } + protected function sortOptions($options) { $options = $options ?? []; From 7651454fe77be62378a38c18892b5bd837510eb9 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 8 Jan 2021 13:36:11 +0000 Subject: [PATCH 018/152] Fix tests --- database/factories/ProductFactory.php | 9 ------- src/Core/Products/Models/ProductVariant.php | 24 +++++++++--------- src/Core/Scopes/AbstractScope.php | 4 +-- .../Resources/Categories/CategoryResource.php | 6 ++--- .../Categories/CategoryControllerTest.php | 8 +----- .../Products/Actions/FetchProductTest.php | 25 ------------------- 6 files changed, 18 insertions(+), 58 deletions(-) delete mode 100644 tests/Unit/Products/Actions/FetchProductTest.php diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index 20e9c62df..bb4c8e81f 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -28,13 +28,4 @@ ], ], ]; -}); - -$factory->afterCreating(Product::class, function ($product, $faker) { - // Set up initial variant - $product->variants()->save(factory(ProductVariant::class)->make()); - - $channel = factory(Channel::class)->create(); - - dd($channel); }); \ No newline at end of file diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index d0ea5452a..c83f5cc73 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -96,18 +96,18 @@ public function getNameAttribute() public function getOptionsAttribute($val) { - // $values = []; - // $option_data = $this->product ? $this->product->option_data : []; - - // foreach (json_decode($val, true) as $option => $value) { - // if (! empty($data = $option_data[$option])) { - // $values[$option] = $data['options'][$value]['values'] ?? [ - // 'en' => null, - // ]; - // } - // } - - return json_decode($val, true); + $values = []; + $option_data = $this->product ? $this->product->option_data : []; + + foreach (json_decode($val, true) as $option => $value) { + if (! empty($data = $option_data[$option])) { + $values[$option] = $data['options'][$value]['values'] ?? [ + 'en' => null, + ]; + } + } + + return $values; } public function setOptionsAttribute($val) diff --git a/src/Core/Scopes/AbstractScope.php b/src/Core/Scopes/AbstractScope.php index cc2e44b94..aee71e7fc 100644 --- a/src/Core/Scopes/AbstractScope.php +++ b/src/Core/Scopes/AbstractScope.php @@ -3,9 +3,9 @@ namespace GetCandy\Api\Core\Scopes; use GetCandy; -use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Scope; +use Illuminate\Database\Eloquent\Builder; +use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; abstract class AbstractScope implements Scope { diff --git a/src/Http/Resources/Categories/CategoryResource.php b/src/Http/Resources/Categories/CategoryResource.php index 4d04aa45c..b483ed953 100644 --- a/src/Http/Resources/Categories/CategoryResource.php +++ b/src/Http/Resources/Categories/CategoryResource.php @@ -21,9 +21,9 @@ public function payload() 'id' => $this->encodedId(), 'sort' => $this->sort, 'drafted_at' => $this->drafted_at, - 'products_count' => $this->products_count, - 'children_count' => $this->children_count, - 'depth' => $this->depth, + 'products_count' => $this->products_count ?: 0, + 'children_count' => $this->children_count ?: 0, + 'depth' => $this->depth ?: 0, 'has_draft' => $this->draft()->exists(), 'left_pos' => $this->_lft, 'sort' => $this->sort, diff --git a/tests/Feature/Http/Categories/CategoryControllerTest.php b/tests/Feature/Http/Categories/CategoryControllerTest.php index 92b5c371f..ee7dc28dc 100644 --- a/tests/Feature/Http/Categories/CategoryControllerTest.php +++ b/tests/Feature/Http/Categories/CategoryControllerTest.php @@ -10,11 +10,6 @@ */ class CategoryControllerTest extends FeatureCase { - /** - * @group fail - * - * @return [type] [return description] - */ public function test_can_list_all_categories() { Category::create([ @@ -24,7 +19,6 @@ public function test_can_list_all_categories() ], ], ]); - $user = $this->admin(); $response = $this->actingAs($user)->json('GET', 'categories'); @@ -37,7 +31,7 @@ public function test_can_show_a_category_by_id() $user = $this->admin(); Category::create([ 'attribute_data' => [ - 'webstore' => [ + 'name' => [ 'en' => 'Test category', ], ], diff --git a/tests/Unit/Products/Actions/FetchProductTest.php b/tests/Unit/Products/Actions/FetchProductTest.php deleted file mode 100644 index 888b010d1..000000000 --- a/tests/Unit/Products/Actions/FetchProductTest.php +++ /dev/null @@ -1,25 +0,0 @@ -create(); - - $fetchedProduct = FetchProduct::run([ - 'encoded_id' => $product->encoded_id, - ]); - - dd($fetchedProduct); - $this->assertEquals($product->id, $fetchedProduct->id); - } -} From 965763d36db33c354219ac069b64bfa9f6ebdeed Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:17:46 +0000 Subject: [PATCH 019/152] Clean up --- .../Drivers/Elasticsearch/Filters/CustomerGroupFilter.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php index 94490e62e..5a866079a 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php @@ -32,9 +32,6 @@ public function getQuery() $filter = new BoolQuery; foreach ($this->getCustomerGroups() as $model) { -// $term = new Term; -// $term->setTerm('customer_groups.id', $model->encodedId()); -// $filter->addShould($term); $cat = new Nested; $cat->setPath('customer_groups'); $term = new Term; @@ -43,6 +40,7 @@ public function getQuery() $cat->setQuery($term); $filter->addShould($cat); + } return $filter; From 9ebb818d4e530ec5e81fa027ffd90a8453fed8a9 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:18:06 +0000 Subject: [PATCH 020/152] =?UTF-8?q?Add=20check=20if=20we=E2=80=99re=20in?= =?UTF-8?q?=20the=20console?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/Scopes/AbstractScope.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Core/Scopes/AbstractScope.php b/src/Core/Scopes/AbstractScope.php index aee71e7fc..fb7a4899b 100644 --- a/src/Core/Scopes/AbstractScope.php +++ b/src/Core/Scopes/AbstractScope.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Core\Scopes; use GetCandy; +use Illuminate\Support\Facades\App; use Illuminate\Database\Eloquent\Scope; use Illuminate\Database\Eloquent\Builder; use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; @@ -50,11 +51,8 @@ public function __construct() */ protected function resolve(\Closure $callback) { - if ( - ! $this->getUser() - || ! $this->canAccessHub() - || ($this->canAccessHub() && ! GetCandy::isHubRequest()) - ) { + $check = ! $this->getUser() || ! $this->canAccessHub() || ($this->canAccessHub() && ! GetCandy::isHubRequest()); + if ($check && !App::runningInConsole()) { $callback(); } } From 410fc2c03a52cf6b25040b00f57ed9ff28f10fdf Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:18:40 +0000 Subject: [PATCH 021/152] If the name column exists already, remove and replace it --- ...9_remove_attribute_data_from_product_family_table.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php b/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php index 0db4b7a33..ac95d8093 100644 --- a/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php +++ b/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php @@ -18,6 +18,15 @@ public function up() $families = ProductFamily::all(); + if (Schema::hasColumn('product_families', 'name')) { + Schema::table('product_families', function (Blueprint $table) { + $table->dropColumn('name'); + }); + Schema::table('product_families', function (Blueprint $table) { + $table->text('name'); + }); + } + foreach ($families as $family) { $data = json_decode($family->attribute_data, true); $name = $data['name'][$channel->handle]['en']; From 90164a34c7becda273b4dc55ae6938add46edf50 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:18:57 +0000 Subject: [PATCH 022/152] Add draft flag --- src/Core/Products/Actions/FetchProduct.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Products/Actions/FetchProduct.php b/src/Core/Products/Actions/FetchProduct.php index 0403223bf..944b53c38 100644 --- a/src/Core/Products/Actions/FetchProduct.php +++ b/src/Core/Products/Actions/FetchProduct.php @@ -54,6 +54,10 @@ public function handle() ->withCount($this->resolveRelationCounts()) ->with($this->resolveEagerRelations()); + if ($this->draft) { + $query->withDrafted(); + } + if ($this->sku) { return $query->whereHas('variants', function ($query) { $query->whereSku($this->sku); From 553c3da396bbc37e6e252c5be0632a19a3893113 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:19:10 +0000 Subject: [PATCH 023/152] Remove global scope bypassing on index --- src/Core/Search/Commands/IndexProductsCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Search/Commands/IndexProductsCommand.php b/src/Core/Search/Commands/IndexProductsCommand.php index 843962c40..14538a40f 100644 --- a/src/Core/Search/Commands/IndexProductsCommand.php +++ b/src/Core/Search/Commands/IndexProductsCommand.php @@ -43,7 +43,7 @@ public function __construct() public function handle(Dispatcher $events, SearchManager $manager) { $batchsize = (int) $this->argument('batchsize'); - $total = Product::withoutGlobalScopes()->count(); + $total = Product::count(); $this->output->text('Indexing '.$total.' products in '.ceil($total / $batchsize).' batches'); @@ -52,7 +52,7 @@ public function handle(Dispatcher $events, SearchManager $manager) $uuid = Uuid::uuid4()->toString(); - Product::withoutGlobalScopes()->with([ + Product::with([ 'attributes', 'customerGroups', 'channels', From bbd6cb4f94e31912dc67204a8f28552881fda283 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:19:19 +0000 Subject: [PATCH 024/152] Use eager loaded categories --- .../Drivers/Elasticsearch/Actions/AbstractDocumentAction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php index 7582f5c9d..6096e6e97 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php @@ -167,7 +167,7 @@ protected function getIndexable(Model $model) */ protected function getCategories(Model $model, $lang = 'en') { - $categories = $model->refresh()->categories; + $categories = $model->categories; if (empty($categories)) { return collect([]); From 4e260b3decda1ce3bb4c74403612acf3b8816508 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:19:59 +0000 Subject: [PATCH 025/152] Add count to pagination --- .../Search/Drivers/Elasticsearch/Actions/Searching/Search.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index c3ce001c3..6aae40ab1 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -234,6 +234,7 @@ public function jsonResponse($result, $request) return (new $resource($paginator))->additional([ 'meta' => [ + 'count' => $models->count(), 'aggregations' => $aggregations, 'highlight' => $result->getQuery()->getParam('highlight'), ], From c8c683458e2d908134c398ff37d16d149e6538e1 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 12:20:07 +0000 Subject: [PATCH 026/152] Tweak offset logic --- .../Search/Drivers/Elasticsearch/Actions/Searching/Search.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 6aae40ab1..45985d293 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -90,7 +90,8 @@ public function handle() ]); $limit = $this->limit ?: 100; - $offset = $this->offset ?: (($this->page != 1 ?: 1 - 1) * $limit); + + $offset = (($this->page ?: 1) - 1) * $limit; $query = new Query(); $query->setParam('size', $limit); From 0bd681bf04780fe05d7bd485068341fdb8c68a1d Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 12 Jan 2021 15:45:34 +0000 Subject: [PATCH 027/152] =?UTF-8?q?Don=E2=80=99t=20send=20up=20billing=20i?= =?UTF-8?q?nfo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Core/Payments/Providers/Braintree.php | 47 ++++++++++++++++++----- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/Core/Payments/Providers/Braintree.php b/src/Core/Payments/Providers/Braintree.php index 5713008a8..f3b18c335 100644 --- a/src/Core/Payments/Providers/Braintree.php +++ b/src/Core/Payments/Providers/Braintree.php @@ -4,6 +4,7 @@ use Braintree_Gateway; use Braintree_Transaction; +use Illuminate\Support\Str; use Illuminate\Support\Carbon; use Braintree_Exception_NotFound; use Illuminate\Support\Facades\Auth; @@ -148,15 +149,16 @@ public function charge() 'firstName' => $billing['firstname'], 'lastName' => $billing['lastname'], 'email' => $user->email, - 'billingAddress' => [ - 'firstName' => $billing['firstname'], - 'lastName' => $billing['lastname'], - 'locality' => $billing['city'], - 'region' => $billing['county'] ?: $billing['state'], - 'postalCode' => $billing['zip'], - 'streetAddress' => $billing['address'], - ] + // 'billingAddress' => [ + // 'firstName' => $billing['firstname'], + // 'lastName' => $billing['lastname'], + // 'locality' => $billing['city'], + // 'region' => $billing['county'] ?: $billing['state'], + // 'postalCode' => $billing['zip'], + // 'streetAddress' => $billing['address'], + // ] ]); + if ($result->success) { $user->providerUsers()->create([ 'provider' => 'braintree', @@ -281,7 +283,34 @@ public function updateTransaction($transaction) public function refund($token, $amount, $description) { - $transaction = Braintree_Transaction::refund($token, $amount); + $result = $this->gateway->transaction()->refund($token, $amount / 100); + + if (!$result->success) { + $error = collect($result->errors->forKey('transaction')->shallowAll())->first(); + // Trying to refund a transaction that isn't settled. + if ($error->code == "91506") { + $result = $this->gateway->transaction()->void($token); + } + } + + $responseT = $result->transaction; + + $transaction = new Transaction; + $transaction->success = $result->success; + $transaction->order()->associate($this->order); + $transaction->merchant = $responseT ? $responseT->merchantAccountId : 'Unknown'; + $transaction->provider = 'Braintree'; + $transaction->driver = 'braintree'; + $transaction->refund = true; + $transaction->status = $responseT ? $result->transaction->status : $result->message; + $transaction->amount = -abs($amount); + $transaction->card_type = $responseT ? ($result->transaction->creditCardDetails->cardType ?? 'Unknown') : 'Unknown'; + $transaction->last_four = $responseT ? ($result->transaction->creditCardDetails->last4 ?? '') : ''; + $transaction->transaction_id = $responseT ? $result->transaction->id : Str::random(); + $transaction->address_matched = false; + $transaction->cvc_matched = false; + $transaction->postcode_matched = false; + $transaction->save(); return $transaction; } From d39f5fdbef5770be7933f974cf55c468713a50ca Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 14 Jan 2021 14:54:49 +0000 Subject: [PATCH 028/152] Fix up discount CRUD --- .../Discounts/Models/DiscountCriteriaItem.php | 18 ++++- .../Discounts/Services/DiscountService.php | 81 +++++++++++++------ .../Discounts/DiscountController.php | 4 +- .../Discounts/DiscountItemResource.php | 2 + .../Resources/Discounts/DiscountResource.php | 2 + 5 files changed, 78 insertions(+), 29 deletions(-) diff --git a/src/Core/Discounts/Models/DiscountCriteriaItem.php b/src/Core/Discounts/Models/DiscountCriteriaItem.php index da7f9fa78..c18c14280 100644 --- a/src/Core/Discounts/Models/DiscountCriteriaItem.php +++ b/src/Core/Discounts/Models/DiscountCriteriaItem.php @@ -2,6 +2,7 @@ namespace GetCandy\Api\Core\Discounts\Models; +use GetCandy; use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\BaseModel; @@ -31,9 +32,22 @@ public function saveEligible($type, $id) { $relation = camel_case(str_plural($type)); + $typeModel = GetCandy::getUserModel(); + + if (method_exists($this, $relation)) { + $realId = (new $typeModel)->decodeId($id); + $this->{$relation}()->sync($realId); + } + } + + public function saveEligibles($type, array $ids) + { + $relation = camel_case(str_plural($type)); + $userModel = GetCandy::getUserModel(); if (method_exists($this, $relation)) { - $realId = (new $type)->decodedId($id); - $this->{$relation}()->attach($realId); + $this->{$relation}()->sync( + (new $userModel)->decodeIds($ids) + ); } } diff --git a/src/Core/Discounts/Services/DiscountService.php b/src/Core/Discounts/Services/DiscountService.php index bca2148d9..ab2228114 100644 --- a/src/Core/Discounts/Services/DiscountService.php +++ b/src/Core/Discounts/Services/DiscountService.php @@ -3,12 +3,13 @@ namespace GetCandy\Api\Core\Discounts\Services; use Carbon\Carbon; -use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; -use GetCandy\Api\Core\Discounts\Discount as DiscountFactory; -use GetCandy\Api\Core\Discounts\Models\Discount; -use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaItem; use GetCandy\Api\Core\Discounts\RewardSet; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Discounts\Models\Discount; +use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaSet; +use GetCandy\Api\Core\Discounts\Discount as DiscountFactory; +use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaItem; +use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; class DiscountService extends BaseService { @@ -42,9 +43,9 @@ public function create(array $data) $discount->status = ! empty($data['status']); $discount->save(); - if (! empty($data['channels']['data'])) { + if (! empty($data['channels'])) { $discount->channels()->sync( - $this->getChannelMapping($data['channels']['data']) + $this->getChannelMapping($data['channels']) ); } @@ -64,28 +65,28 @@ public function update($id, array $data) { $discount = $this->getByHashedId($id); $discount->start_at = Carbon::parse($data['start_at']); - $discount->end_at = Carbon::parse($data['end_at']); $discount->priority = $data['priority']; $discount->stop_rules = $data['stop_rules']; $discount->status = $data['status']; + $discount->attribute_data = $data['attribute_data']; $discount->save(); // event(new AttributableSavedEvent($discount)); - if (isset($data['rewards']['data'])) { + if (isset($data['rewards'])) { $discount->rewards()->delete(); - $this->syncRewards($discount, $data['rewards']['data']); + $this->syncRewards($discount, $data['rewards']); } - if (! empty($data['channels']['data'])) { + if (! empty($data['channels'])) { $discount->channels()->sync( - $this->getChannelMapping($data['channels']['data']) + $this->getChannelMapping($data['channels']) ); } - if (! empty($data['sets']['data'])) { - $this->syncSets($discount, $data['sets']['data']); + if (! empty($data['sets'])) { + $this->syncSets($discount, $data['sets']); } return $discount; @@ -100,27 +101,57 @@ public function update($id, array $data) */ public function syncSets($discount, array $sets) { - //print_r($sets);exit; - $discount->sets()->delete(); + $setIds = []; foreach ($sets as $set) { - $groupModel = $discount->sets()->create([ - 'scope' => $set['scope'], - 'outcome' => (bool) $set['outcome'], - ]); - if (! empty($set['items']['data'])) { - $set['items'] = $set['items']['data']; + if (!empty($set['id'])) { + $id = (new DiscountCriteriaSet)->decodeId($set['id']); + $setModel = DiscountCriteriaSet::find($id); + } else { + $setModel = $discount->sets()->create([ + 'scope' => $set['scope'], + 'outcome' => (bool) $set['outcome'], + ]); } + $setIds[] = $setModel->id; + + if (! empty($set['items'])) { + $set['items'] = $set['items']; + } + + $itemIds = []; + foreach ($set['items'] as $item) { - $model = $groupModel->items()->create($item); + if (!empty($item['id'])) { + $modelId = (new DiscountCriteriaItem)->decodeId($item['id']); + $model = DiscountCriteriaItem::find($modelId); + } else { + $model = $setModel->items()->create($item); + } + $itemIds[] = $model->id; if (! empty($item['eligibles'])) { - foreach ($item['eligibles'] as $eligible) { - $model->saveEligible($item['type'], $eligible); - } + $model->saveEligibles($item['type'], $item['eligibles']); } } + + $setModel->refresh()->items->filter(function ($item) use ($itemIds) { + return !in_array($item->id, $itemIds); + })->each(function ($item) { + $item->eligibles()->delete(); + $item->delete(); + }); } + $discount->refresh()->sets->filter(function ($set) use ($setIds) { + return !in_array($set->id, $setIds); + })->each(function ($set) { + $set->items->each(function ($item) { + $item->eligibles()->delete(); + $item->delete(); + }); + $set->delete(); + }); + return $discount; } diff --git a/src/Http/Controllers/Discounts/DiscountController.php b/src/Http/Controllers/Discounts/DiscountController.php index 34f629790..1f445087a 100644 --- a/src/Http/Controllers/Discounts/DiscountController.php +++ b/src/Http/Controllers/Discounts/DiscountController.php @@ -17,7 +17,7 @@ public function index(Request $request) $paginator = GetCandy::discounts()->getPaginatedData( $request->per_page, $request->current_page, - $request->includes ? explode(',', $request->includes) : null + $request->include ? explode(',', $request->include) : null ); return new DiscountCollection($paginator); @@ -50,7 +50,7 @@ public function show($id, Request $request) try { $discount = GetCandy::discounts()->getByHashedId( $id, - $request->includes ? explode(',', $request->includes) : null + $request->include ? explode(',', $request->include) : null ); } catch (ModelNotFoundException $e) { return $this->errorNotFound(); diff --git a/src/Http/Resources/Discounts/DiscountItemResource.php b/src/Http/Resources/Discounts/DiscountItemResource.php index 1f151d365..8fb32d18b 100644 --- a/src/Http/Resources/Discounts/DiscountItemResource.php +++ b/src/Http/Resources/Discounts/DiscountItemResource.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Http\Resources\Discounts; use GetCandy\Api\Http\Resources\AbstractResource; +use GetCandy\Api\Core\Users\Resources\UserCollection; class DiscountItemResource extends AbstractResource { @@ -18,6 +19,7 @@ public function payload() public function includes() { return [ + 'users' => new UserCollection($this->whenLoaded('users')), ]; } } diff --git a/src/Http/Resources/Discounts/DiscountResource.php b/src/Http/Resources/Discounts/DiscountResource.php index 24b80cc66..096c33267 100644 --- a/src/Http/Resources/Discounts/DiscountResource.php +++ b/src/Http/Resources/Discounts/DiscountResource.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Http\Resources\Discounts; use GetCandy\Api\Http\Resources\AbstractResource; +use GetCandy\Api\Core\Channels\Resources\ChannelCollection; use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; class DiscountResource extends AbstractResource @@ -27,6 +28,7 @@ public function includes() 'rewards' => new DiscountRewardCollection($this->whenLoaded('rewards')), 'sets' => new DiscountSetCollection($this->whenLoaded('sets')), 'attributes' => new AttributeCollection($this->whenLoaded('attributes')), + 'channels' => new ChannelCollection($this->whenLoaded('channels')), ]; } } From c1ce7f5e054214b4ccd0be562625581c0c80bd61 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 14 Jan 2021 14:55:07 +0000 Subject: [PATCH 029/152] Fix issue with passed index on search --- .../Drivers/Elasticsearch/Actions/Searching/Search.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 45985d293..4359c6b5e 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -3,17 +3,18 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; use Elastica\Query; +use Illuminate\Support\Str; use Elastica\Query\BoolQuery; +use Lorisleiva\Actions\Action; use Elastica\Search as ElasticaSearch; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Products\Models\Product; +use Illuminate\Pagination\LengthAwarePaginator; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; use GetCandy\Api\Http\Resources\Products\ProductCollection; use GetCandy\Api\Http\Resources\Attributes\AttributeResource; use GetCandy\Api\Http\Resources\Categories\CategoryCollection; use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; -use Illuminate\Pagination\LengthAwarePaginator; -use Lorisleiva\Actions\Action; class Search extends Action { @@ -69,7 +70,8 @@ public function handle() $prefix = config('getcandy.search.index_prefix'); $language = app()->getLocale(); - $this->set('index', "{$prefix}_{$this->search_type}_{$language}"); + $index = Str::plural($this->search_type); + $this->set('index', "{$prefix}_{$index}_{$language}"); } $this->filters = $this->filters ? collect(explode(',', $this->filters))->mapWithKeys(function ($filter) { From 587f9a31096e5c0b59bf71b5a6ef4054ec238c45 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 15 Jan 2021 10:16:00 +0000 Subject: [PATCH 030/152] Set default channels on discount when creating --- src/Core/Discounts/Services/DiscountService.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Core/Discounts/Services/DiscountService.php b/src/Core/Discounts/Services/DiscountService.php index ab2228114..b43776e05 100644 --- a/src/Core/Discounts/Services/DiscountService.php +++ b/src/Core/Discounts/Services/DiscountService.php @@ -5,6 +5,7 @@ use Carbon\Carbon; use GetCandy\Api\Core\Discounts\RewardSet; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Channels\Models\Channel; use GetCandy\Api\Core\Discounts\Models\Discount; use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaSet; use GetCandy\Api\Core\Discounts\Discount as DiscountFactory; @@ -47,6 +48,12 @@ public function create(array $data) $discount->channels()->sync( $this->getChannelMapping($data['channels']) ); + } else { + $discount->channels()->sync(Channel::select('id')->get()->mapWithKeys(function ($c) { + return [$c->id => [ + 'published_at' => null, + ]]; + })->toArray()); } event(new AttributableSavedEvent($discount)); From 79012ad0b2b46099f79652ded5c6c059bbe31d0c Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 15 Jan 2021 10:21:40 +0000 Subject: [PATCH 031/152] Make name nullable --- ...0_120359_remove_attribute_data_from_product_family_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php b/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php index ac95d8093..bce98db82 100644 --- a/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php +++ b/database/migrations/2020_03_10_120359_remove_attribute_data_from_product_family_table.php @@ -23,7 +23,7 @@ public function up() $table->dropColumn('name'); }); Schema::table('product_families', function (Blueprint $table) { - $table->text('name'); + $table->text('name')->nullable(); }); } From 61a2251abe4f3c2d19c5f1e825697acf61ee9142 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Fri, 15 Jan 2021 10:26:36 +0000 Subject: [PATCH 032/152] Apply fixes from StyleCI (#347) Co-authored-by: Alec Ritson --- database/factories/ProductFactory.php | 6 +---- database/factories/ProductVariantFactory.php | 2 +- ...25_create_payment_provider_users_table.php | 2 -- .../Addresses/Actions/UpdateAddressAction.php | 5 +++-- .../Attributes/Actions/FetchAttributes.php | 3 ++- src/Core/Categories/Models/Category.php | 2 +- src/Core/Categories/QueryBuilder.php | 2 +- .../Discounts/Services/DiscountService.php | 18 +++++++-------- .../Factories/OrderProcessingFactory.php | 1 - src/Core/Orders/Services/OrderService.php | 1 - src/Core/Payments/Providers/Braintree.php | 22 +++++++++---------- src/Core/Products/Actions/FetchProduct.php | 3 --- .../Services/ProductCustomerGroupService.php | 5 ++--- .../Actions/DeleteReusablePayment.php | 5 +++-- src/Core/Scopes/AbstractScope.php | 8 +++---- .../Actions/Searching/FetchAggregations.php | 1 + .../Actions/Searching/FetchFilters.php | 5 ++--- .../Actions/Searching/MapAggregations.php | 9 ++++---- .../Actions/Searching/Search.php | 21 +++++++----------- .../Filters/CustomerGroupFilter.php | 1 - .../Providers/Elastic/AggregationResolver.php | 4 ---- src/Core/Traits/HasCandy.php | 8 +++---- src/Core/Users/Resources/UserResource.php | 12 +++++----- .../Baskets/BasketLineController.php | 11 +++++----- src/Http/Resources/AbstractResource.php | 7 +++--- .../Discounts/DiscountItemResource.php | 2 +- .../Resources/Discounts/DiscountResource.php | 2 +- .../Resources/Products/ProductResource.php | 1 + 28 files changed, 76 insertions(+), 93 deletions(-) diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index bb4c8e81f..e902fcbe5 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -1,10 +1,7 @@ define(Product::class, function (Faker $faker) { return [ 'attribute_data' => [ @@ -28,4 +24,4 @@ ], ], ]; -}); \ No newline at end of file +}); diff --git a/database/factories/ProductVariantFactory.php b/database/factories/ProductVariantFactory.php index eb99679da..e7a73bfdb 100644 --- a/database/factories/ProductVariantFactory.php +++ b/database/factories/ProductVariantFactory.php @@ -16,7 +16,7 @@ $factory->define(GetCandy\Api\Core\Products\Models\ProductVariant::class, function (Faker $faker) { return [ 'sku' => $faker->unique()->slug, - 'stock' => $faker->numberBetween(10,2000), + 'stock' => $faker->numberBetween(10, 2000), 'price' => $faker->randomNumber(2), ]; }); diff --git a/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php b/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php index 4c5c75c91..0cb0eb630 100644 --- a/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php +++ b/database/migrations/2020_12_21_123125_create_payment_provider_users_table.php @@ -1,9 +1,7 @@ 'string', 'state' => 'string', 'postal_code' => 'string', - 'country_id' => 'hashid_is_valid:' . Country::class, + 'country_id' => 'hashid_is_valid:'.Country::class, 'shipping' => 'boolean', 'billing' => 'boolean', 'default' => 'boolean', diff --git a/src/Core/Attributes/Actions/FetchAttributes.php b/src/Core/Attributes/Actions/FetchAttributes.php index 937855941..3d893744f 100644 --- a/src/Core/Attributes/Actions/FetchAttributes.php +++ b/src/Core/Attributes/Actions/FetchAttributes.php @@ -25,7 +25,7 @@ public function authorize() public function rules() { return [ - 'handles' => 'nullable|array|min:0' + 'handles' => 'nullable|array|min:0', ]; } @@ -39,6 +39,7 @@ public function handle() if ($this->handles) { return Attribute::whereIn('handle', $this->handles)->get(); } + return Attribute::get(); } } diff --git a/src/Core/Categories/Models/Category.php b/src/Core/Categories/Models/Category.php index 7cad3db56..8065f6380 100644 --- a/src/Core/Categories/Models/Category.php +++ b/src/Core/Categories/Models/Category.php @@ -46,7 +46,7 @@ class Category extends BaseModel * @var array */ protected $fillable = [ - 'attribute_data', 'parent_id', 'sort', 'range' + 'attribute_data', 'parent_id', 'sort', 'range', ]; public function getParentIdAttribute($val) diff --git a/src/Core/Categories/QueryBuilder.php b/src/Core/Categories/QueryBuilder.php index 8c49af635..3176f842a 100644 --- a/src/Core/Categories/QueryBuilder.php +++ b/src/Core/Categories/QueryBuilder.php @@ -29,7 +29,7 @@ public function withDepth($as = 'depth') ->selectRaw('count(1) - 1') ->from($this->model->getTable().' as '.$alias) ->where(function ($query) use ($table, $lft, $rgt, $wrappedAlias) { - $query->whereNull($this->model->getTable() . '.drafted_at') + $query->whereNull($this->model->getTable().'.drafted_at') ->whereRaw("{$table}.{$lft} between {$wrappedAlias}.{$lft} and {$wrappedAlias}.{$rgt}"); }); diff --git a/src/Core/Discounts/Services/DiscountService.php b/src/Core/Discounts/Services/DiscountService.php index b43776e05..0869c1d48 100644 --- a/src/Core/Discounts/Services/DiscountService.php +++ b/src/Core/Discounts/Services/DiscountService.php @@ -3,14 +3,14 @@ namespace GetCandy\Api\Core\Discounts\Services; use Carbon\Carbon; -use GetCandy\Api\Core\Discounts\RewardSet; -use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Discounts\Models\Discount; -use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaSet; use GetCandy\Api\Core\Discounts\Discount as DiscountFactory; +use GetCandy\Api\Core\Discounts\Models\Discount; use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaItem; -use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; +use GetCandy\Api\Core\Discounts\Models\DiscountCriteriaSet; +use GetCandy\Api\Core\Discounts\RewardSet; +use GetCandy\Api\Core\Scaffold\BaseService; class DiscountService extends BaseService { @@ -111,7 +111,7 @@ public function syncSets($discount, array $sets) $setIds = []; foreach ($sets as $set) { - if (!empty($set['id'])) { + if (! empty($set['id'])) { $id = (new DiscountCriteriaSet)->decodeId($set['id']); $setModel = DiscountCriteriaSet::find($id); } else { @@ -129,7 +129,7 @@ public function syncSets($discount, array $sets) $itemIds = []; foreach ($set['items'] as $item) { - if (!empty($item['id'])) { + if (! empty($item['id'])) { $modelId = (new DiscountCriteriaItem)->decodeId($item['id']); $model = DiscountCriteriaItem::find($modelId); } else { @@ -142,7 +142,7 @@ public function syncSets($discount, array $sets) } $setModel->refresh()->items->filter(function ($item) use ($itemIds) { - return !in_array($item->id, $itemIds); + return ! in_array($item->id, $itemIds); })->each(function ($item) { $item->eligibles()->delete(); $item->delete(); @@ -150,7 +150,7 @@ public function syncSets($discount, array $sets) } $discount->refresh()->sets->filter(function ($set) use ($setIds) { - return !in_array($set->id, $setIds); + return ! in_array($set->id, $setIds); })->each(function ($set) { $set->items->each(function ($item) { $item->eligibles()->delete(); diff --git a/src/Core/Orders/Factories/OrderProcessingFactory.php b/src/Core/Orders/Factories/OrderProcessingFactory.php index fdacacd58..78963f788 100644 --- a/src/Core/Orders/Factories/OrderProcessingFactory.php +++ b/src/Core/Orders/Factories/OrderProcessingFactory.php @@ -225,7 +225,6 @@ public function resolve() $this->order->meta = array_merge($this->order->meta ?? [], $this->meta ?? []); $this->order->company_name = $this->companyName; - $this->order->save(); $response = $driver diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index 394d41936..4d4d3e5de 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -7,7 +7,6 @@ use DB; use GetCandy; use GetCandy\Api\Core\ActivityLog\Interfaces\ActivityLogFactoryInterface; -use GetCandy\Api\Core\Addresses\Actions\CreateAddressAction; use GetCandy\Api\Core\Addresses\Actions\FetchAddressAction; use GetCandy\Api\Core\Baskets\Models\Basket; use GetCandy\Api\Core\Baskets\Services\BasketService; diff --git a/src/Core/Payments/Providers/Braintree.php b/src/Core/Payments/Providers/Braintree.php index f3b18c335..309662070 100644 --- a/src/Core/Payments/Providers/Braintree.php +++ b/src/Core/Payments/Providers/Braintree.php @@ -2,14 +2,14 @@ namespace GetCandy\Api\Core\Payments\Providers; +use Braintree_Exception_NotFound; use Braintree_Gateway; use Braintree_Transaction; -use Illuminate\Support\Str; +use GetCandy\Api\Core\Payments\Models\Transaction; +use GetCandy\Api\Core\Payments\PaymentResponse; use Illuminate\Support\Carbon; -use Braintree_Exception_NotFound; use Illuminate\Support\Facades\Auth; -use GetCandy\Api\Core\Payments\PaymentResponse; -use GetCandy\Api\Core\Payments\Models\Transaction; +use Illuminate\Support\Str; class Braintree extends AbstractProvider { @@ -144,7 +144,7 @@ public function charge() if ($user) { $providerUser = $user->providerUsers()->provider('braintree')->first(); - if (!$providerUser) { + if (! $providerUser) { $result = $this->gateway->customer()->create([ 'firstName' => $billing['firstname'], 'lastName' => $billing['lastname'], @@ -170,11 +170,11 @@ public function charge() $customerId = $providerUser->provider_id; } - // Do we want to save this card? - if ($customerId && !empty($this->fields['save'])) { + // Do we want to save this card? + if ($customerId && ! empty($this->fields['save'])) { $result = $this->gateway->paymentMethod()->create([ 'customerId' => $customerId, - 'paymentMethodNonce' => $this->token + 'paymentMethodNonce' => $this->token, ]); $paymentMethod = $result->paymentMethod; @@ -184,7 +184,7 @@ public function charge() 'provider' => 'braintree', 'last_four' => $paymentMethod->last4, 'token' => $paymentMethod->token, - 'expires_at' => Carbon::createFromFormat('d-m-Y', "01-{$paymentMethod->expirationMonth}-{$paymentMethod->expirationYear}") + 'expires_at' => Carbon::createFromFormat('d-m-Y', "01-{$paymentMethod->expirationMonth}-{$paymentMethod->expirationYear}"), ]); unset($payload['paymentMethodNonce']); $payload['paymentMethodToken'] = $paymentMethod->token; @@ -285,10 +285,10 @@ public function refund($token, $amount, $description) { $result = $this->gateway->transaction()->refund($token, $amount / 100); - if (!$result->success) { + if (! $result->success) { $error = collect($result->errors->forKey('transaction')->shallowAll())->first(); // Trying to refund a transaction that isn't settled. - if ($error->code == "91506") { + if ($error->code == '91506') { $result = $this->gateway->transaction()->void($token); } } diff --git a/src/Core/Products/Actions/FetchProduct.php b/src/Core/Products/Actions/FetchProduct.php index 944b53c38..1f14d775a 100644 --- a/src/Core/Products/Actions/FetchProduct.php +++ b/src/Core/Products/Actions/FetchProduct.php @@ -5,10 +5,7 @@ use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Traits\ReturnsJsonResponses; -use GetCandy\Api\Core\Products\Models\ProductFamily; -use Illuminate\Database\Eloquent\ModelNotFoundException; use GetCandy\Api\Http\Resources\Products\ProductResource; -use GetCandy\Api\Core\Products\Resources\ProductFamilyResource; class FetchProduct extends AbstractAction { diff --git a/src/Core/Products/Services/ProductCustomerGroupService.php b/src/Core/Products/Services/ProductCustomerGroupService.php index e2dd8f398..a27bb76a4 100644 --- a/src/Core/Products/Services/ProductCustomerGroupService.php +++ b/src/Core/Products/Services/ProductCustomerGroupService.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Core\Products\Services; -use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroup; -use GetCandy\Api\Core\Customers\Services\CustomerGroupService; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Scaffold\BaseService; class ProductCustomerGroupService extends BaseService { diff --git a/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php b/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php index 57d0be62d..5eac44527 100644 --- a/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php +++ b/src/Core/ReusablePayments/Actions/DeleteReusablePayment.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Core\ReusablePayments\Actions; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Payments\PaymentContract; -use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use GetCandy\Api\Core\ReusablePayments\Models\ReusablePayment; +use GetCandy\Api\Core\Traits\ReturnsJsonResponses; +use Lorisleiva\Actions\Action; class DeleteReusablePayment extends Action { @@ -65,6 +65,7 @@ public function handle(PaymentContract $payments) $this->reusablePayment ); } + return $this->reusablePayment->delete(); } diff --git a/src/Core/Scopes/AbstractScope.php b/src/Core/Scopes/AbstractScope.php index fb7a4899b..930c34559 100644 --- a/src/Core/Scopes/AbstractScope.php +++ b/src/Core/Scopes/AbstractScope.php @@ -3,10 +3,10 @@ namespace GetCandy\Api\Core\Scopes; use GetCandy; -use Illuminate\Support\Facades\App; -use Illuminate\Database\Eloquent\Scope; -use Illuminate\Database\Eloquent\Builder; use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Scope; +use Illuminate\Support\Facades\App; abstract class AbstractScope implements Scope { @@ -52,7 +52,7 @@ public function __construct() protected function resolve(\Closure $callback) { $check = ! $this->getUser() || ! $this->canAccessHub() || ($this->canAccessHub() && ! GetCandy::isHubRequest()); - if ($check && !App::runningInConsole()) { + if ($check && ! App::runningInConsole()) { $callback(); } } diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php index 00a3f21a3..cae8995c8 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchAggregations.php @@ -46,6 +46,7 @@ public function handle() $results = FetchFilterableAttributes::run()->map(function ($attribute) { return $attribute->handle; }); + return collect(['priceRange', 'category'])->merge($results)->map(function ($attribute) { $name = ucfirst(camel_case(str_singular($attribute))); $classname = "GetCandy\Api\Core\Search\Drivers\Elastic\Aggregators\\{$name}"; diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index 8ed3399f9..17c5303c9 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -30,7 +30,7 @@ public function rules() { return [ 'filters' => 'array|min:0', - 'category' => 'array|min:0' + 'category' => 'array|min:0', ]; } @@ -43,12 +43,11 @@ public function rules() */ public function handle() { - $applied = collect([ (new CustomerGroupFilter)->process($this->user()), ]); - if (!empty($this->category)) { + if (! empty($this->category)) { $applied->push( (new CategoryFilter)->process($this->category) ); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php index d032d3093..5e2dbaffe 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/MapAggregations.php @@ -3,13 +3,13 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; use GetCandy; +use GetCandy\Api\Core\Attributes\Actions\FetchAttributes; +use GetCandy\Api\Http\Resources\Attributes\AttributeResource; +use GetCandy\Api\Http\Resources\Categories\CategoryResource; use Illuminate\Support\Arr; +use Illuminate\Support\Collection; use Illuminate\Support\Str; use Lorisleiva\Actions\Action; -use Illuminate\Support\Collection; -use GetCandy\Api\Core\Attributes\Actions\FetchAttributes; -use GetCandy\Api\Http\Resources\Categories\CategoryResource; -use GetCandy\Api\Http\Resources\Attributes\AttributeResource; class MapAggregations extends Action { @@ -73,7 +73,6 @@ public function getAggregatedCategories(array $buckets) ); } - protected function resolveAggregations(Collection $aggregations) { // Get our attributes here so they're only fetched once diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 4359c6b5e..bf3ad63cc 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -3,18 +3,16 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; use Elastica\Query; -use Illuminate\Support\Str; use Elastica\Query\BoolQuery; -use Lorisleiva\Actions\Action; use Elastica\Search as ElasticaSearch; -use GetCandy\Api\Core\Products\Models\Product; -use Illuminate\Pagination\LengthAwarePaginator; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Search\Actions\FetchSearchedIds; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Http\Resources\Attributes\AttributeResource; use GetCandy\Api\Http\Resources\Categories\CategoryCollection; -use GetCandy\Api\Core\Attributes\Actions\FetchFilterableAttributes; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Str; +use Lorisleiva\Actions\Action; class Search extends Action { @@ -75,7 +73,8 @@ public function handle() } $this->filters = $this->filters ? collect(explode(',', $this->filters))->mapWithKeys(function ($filter) { - list($label, $value) = explode(':', $filter); + [$label, $value] = explode(':', $filter); + return [$label => $value]; })->toArray() : []; @@ -88,7 +87,7 @@ public function handle() $term = $this->term ? FetchTerm::run($this->attributes) : null; $filters = FetchFilters::run([ 'category' => $this->category, - 'filters' => $this->filters + 'filters' => $this->filters, ]); $limit = $this->limit ?: 100; @@ -117,7 +116,6 @@ public function handle() // Set filters as post filters $postFilter = new BoolQuery; - $preFilters = $filters->filter(function ($filter) { return in_array($filter->handle, $this->topFilters); }); @@ -162,14 +160,12 @@ public function handle() $query->setQuery($boolQuery); - $query = SetSorting::run([ 'query' => $query, 'type' => $this->search_type, 'sort' => $this->sort, ]); - $query->setHighlight(config('getcandy.search.highlight') ?? [ 'pre_tags' => [''], 'post_tags' => [''], @@ -221,7 +217,6 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); - $resource = ProductCollection::class; if ($this->search_type == 'categories') { diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php index 5a866079a..04b785646 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php @@ -40,7 +40,6 @@ public function getQuery() $cat->setQuery($term); $filter->addShould($cat); - } return $filter; diff --git a/src/Core/Search/Providers/Elastic/AggregationResolver.php b/src/Core/Search/Providers/Elastic/AggregationResolver.php index b69c04994..4d5a86dbb 100644 --- a/src/Core/Search/Providers/Elastic/AggregationResolver.php +++ b/src/Core/Search/Providers/Elastic/AggregationResolver.php @@ -3,10 +3,6 @@ namespace GetCandy\Api\Core\Search\Providers\Elastic; use GetCandy; -use GetCandy\Api\Http\Resources\Attributes\AttributeResource; -use GetCandy\Api\Http\Resources\Categories\CategoryResource; -use Illuminate\Support\Arr; -use Illuminate\Support\Collection; use Illuminate\Support\Str; class AggregationResolver diff --git a/src/Core/Traits/HasCandy.php b/src/Core/Traits/HasCandy.php index aa2786289..da48d6f39 100644 --- a/src/Core/Traits/HasCandy.php +++ b/src/Core/Traits/HasCandy.php @@ -2,15 +2,15 @@ namespace GetCandy\Api\Core\Traits; -use Spatie\Permission\Traits\HasRoles; -use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Customers\Models\Customer; -use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Baskets\Models\SavedBasket; +use GetCandy\Api\Core\Customers\Models\Customer; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Languages\Models\Language; +use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Payments\Models\PaymentProviderUser; use GetCandy\Api\Core\ReusablePayments\Models\ReusablePayment; +use Spatie\Permission\Traits\HasRoles; trait HasCandy { diff --git a/src/Core/Users/Resources/UserResource.php b/src/Core/Users/Resources/UserResource.php index eb0cb94e5..a62e90e43 100644 --- a/src/Core/Users/Resources/UserResource.php +++ b/src/Core/Users/Resources/UserResource.php @@ -2,16 +2,16 @@ namespace GetCandy\Api\Core\Users\Resources; +use GetCandy\Api\Core\Addresses\Resources\AddressCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerResource; +use GetCandy\Api\Core\ReusablePayments\Resources\ReusablePaymentCollection; use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Acl\RoleCollection; -use GetCandy\Api\Http\Resources\Orders\OrderResource; -use GetCandy\Api\Http\Resources\Orders\OrderCollection; use GetCandy\Api\Http\Resources\Baskets\BasketCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerResource; -use GetCandy\Api\Core\Addresses\Resources\AddressCollection; use GetCandy\Api\Http\Resources\Baskets\SavedBasketCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; -use GetCandy\Api\Core\ReusablePayments\Resources\ReusablePaymentCollection; +use GetCandy\Api\Http\Resources\Orders\OrderCollection; +use GetCandy\Api\Http\Resources\Orders\OrderResource; class UserResource extends AbstractResource { diff --git a/src/Http/Controllers/Baskets/BasketLineController.php b/src/Http/Controllers/Baskets/BasketLineController.php index 3522bd66f..f17aa0b4f 100644 --- a/src/Http/Controllers/Baskets/BasketLineController.php +++ b/src/Http/Controllers/Baskets/BasketLineController.php @@ -3,15 +3,15 @@ namespace GetCandy\Api\Http\Controllers\Baskets; use GetCandy; -use Illuminate\Http\Request; +use GetCandy\Api\Core\Baskets\Factories\BasketLineFactory; use GetCandy\Api\Core\Baskets\Models\BasketLine; use GetCandy\Api\Http\Controllers\BaseController; -use GetCandy\Api\Http\Resources\Baskets\BasketResource; -use GetCandy\Api\Http\Requests\Baskets\UpdateLineRequest; -use GetCandy\Api\Core\Baskets\Factories\BasketLineFactory; +use GetCandy\Api\Http\Requests\Baskets\ChangeQuantityRequest; use GetCandy\Api\Http\Requests\Baskets\CreateLinesRequest; use GetCandy\Api\Http\Requests\Baskets\DeleteLinesRequest; -use GetCandy\Api\Http\Requests\Baskets\ChangeQuantityRequest; +use GetCandy\Api\Http\Requests\Baskets\UpdateLineRequest; +use GetCandy\Api\Http\Resources\Baskets\BasketResource; +use Illuminate\Http\Request; class BasketLineController extends BaseController { @@ -113,6 +113,7 @@ public function destroyLine($id, Request $request) // TODO: Move this to an action. $realId = (new BasketLine)->decodeId($id); BasketLine::destroy($realId); + return $this->respondWithNoContent(); } } diff --git a/src/Http/Resources/AbstractResource.php b/src/Http/Resources/AbstractResource.php index de1abb2a0..1c4dbe2b8 100644 --- a/src/Http/Resources/AbstractResource.php +++ b/src/Http/Resources/AbstractResource.php @@ -2,11 +2,11 @@ namespace GetCandy\Api\Http\Resources; -use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\Resources\MissingValue; -use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; +use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Http\Resources\MissingValue; +use Illuminate\Support\Collection; abstract class AbstractResource extends JsonResource { @@ -170,6 +170,7 @@ protected function include($relation, $resource) 'data' => new $resource($relation), ]; } + return $this->when($this->relationLoaded($relation), function () use ($relation, $resource) { return ['data' => new $resource($this->whenLoaded($relation))]; }); diff --git a/src/Http/Resources/Discounts/DiscountItemResource.php b/src/Http/Resources/Discounts/DiscountItemResource.php index 8fb32d18b..4ccf358a3 100644 --- a/src/Http/Resources/Discounts/DiscountItemResource.php +++ b/src/Http/Resources/Discounts/DiscountItemResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Resources\Discounts; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserCollection; +use GetCandy\Api\Http\Resources\AbstractResource; class DiscountItemResource extends AbstractResource { diff --git a/src/Http/Resources/Discounts/DiscountResource.php b/src/Http/Resources/Discounts/DiscountResource.php index 096c33267..3b417f4a2 100644 --- a/src/Http/Resources/Discounts/DiscountResource.php +++ b/src/Http/Resources/Discounts/DiscountResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Resources\Discounts; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Channels\Resources\ChannelCollection; +use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; class DiscountResource extends AbstractResource diff --git a/src/Http/Resources/Products/ProductResource.php b/src/Http/Resources/Products/ProductResource.php index 65f8e9218..c3fc971ee 100644 --- a/src/Http/Resources/Products/ProductResource.php +++ b/src/Http/Resources/Products/ProductResource.php @@ -66,6 +66,7 @@ protected function parseOptionData($data) foreach ($data as $optionKey => $option) { $data[$optionKey]['options'] = collect($option['options'] ?? [])->mapWithKeys(function ($option, $handle) { $option['handle'] = $handle; + return [$handle => $option]; })->toArray(); } From e7388d1bbc4ee1515ab3c4e57eacd0b258e9409e Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 15 Jan 2021 13:11:24 +0000 Subject: [PATCH 033/152] Fix shipping estimate endpoint --- src/Http/Controllers/Shipping/ShippingPriceController.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Http/Controllers/Shipping/ShippingPriceController.php b/src/Http/Controllers/Shipping/ShippingPriceController.php index cc0abaa96..891b3e9aa 100644 --- a/src/Http/Controllers/Shipping/ShippingPriceController.php +++ b/src/Http/Controllers/Shipping/ShippingPriceController.php @@ -71,8 +71,7 @@ public function update($id, StoreRequest $request) public function estimate(EstimateRequest $request) { $result = GetCandy::shippingPrices()->estimate($request->amount, $request->zip, $request->limit); - - return new ShippingPriceResource($result); + return new ShippingPriceCollection($result); } /** From 7fe181c9f2c1b4229f00ab27c499b0d1a0da2ed5 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 15 Jan 2021 13:26:12 +0000 Subject: [PATCH 034/152] Import missing class --- src/Core/Orders/Listeners/SyncWithBasketListener.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Orders/Listeners/SyncWithBasketListener.php b/src/Core/Orders/Listeners/SyncWithBasketListener.php index 25c0a8aa1..6401bb3d6 100644 --- a/src/Core/Orders/Listeners/SyncWithBasketListener.php +++ b/src/Core/Orders/Listeners/SyncWithBasketListener.php @@ -2,8 +2,9 @@ namespace GetCandy\Api\Core\Orders\Listeners; -use GetCandy\Api\Core\Baskets\Events\BasketStoredEvent; use GetCandy\Api\Core\Discounts\Factory; +use GetCandy\Api\Core\Baskets\Events\BasketStoredEvent; +use GetCandy\Api\Core\Orders\Interfaces\OrderFactoryInterface; class SyncWithBasketListener { From 600c8d04219b411048151adb5c5d5b88342a44aa Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 18 Jan 2021 13:45:12 +0000 Subject: [PATCH 035/152] Adjust imports --- .../Products/ProductController.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 801a14c67..5d311853a 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -2,27 +2,28 @@ namespace GetCandy\Api\Http\Controllers\Products; +use Hashids; use Drafting; use GetCandy; -use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; -use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; +use Illuminate\Http\Request; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\ProductCriteria; -use GetCandy\Api\Core\Products\Services\ProductService; -use GetCandy\Api\Exceptions\InvalidLanguageException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Exceptions\InvalidLanguageException; use GetCandy\Api\Http\Requests\Products\CreateRequest; use GetCandy\Api\Http\Requests\Products\DeleteRequest; -use GetCandy\Api\Http\Requests\Products\DuplicateRequest; use GetCandy\Api\Http\Requests\Products\UpdateRequest; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Http\Resources\Products\ProductResource; -use Hashids; +use GetCandy\Api\Core\Products\Services\ProductService; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Illuminate\Http\Request; +use GetCandy\Api\Http\Requests\Products\DuplicateRequest; +use GetCandy\Api\Http\Resources\Products\ProductResource; use Symfony\Component\HttpKernel\Exception\HttpException; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; +use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; class ProductController extends BaseController { From a01241377907101eb049daf192950e0aab6262f1 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 18 Jan 2021 13:45:24 +0000 Subject: [PATCH 036/152] Only reindex products if we have any --- src/Core/Categories/Drafting/CategoryDrafter.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 8621aeff3..4219e2156 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -56,9 +56,12 @@ public function publish(Model $category) $category->save(); // Update all products... - IndexObjects::run([ - 'documents' => $category->products, - ]); + if ($category->products->count()) { + IndexObjects::run([ + 'documents' => $category->products, + ]); + } + event(new ModelPublishedEvent($category)); From d0a4062881fd56512bd19e863388afab91fc604a Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:11:51 +0000 Subject: [PATCH 037/152] Fix asset uploading for YouTube and urls --- src/Core/Assets/Drivers/BaseUrlDriver.php | 11 ++++++-- .../Assets/Services/AssetTransformService.php | 25 +++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Core/Assets/Drivers/BaseUrlDriver.php b/src/Core/Assets/Drivers/BaseUrlDriver.php index e302f0712..d6bf24a84 100644 --- a/src/Core/Assets/Drivers/BaseUrlDriver.php +++ b/src/Core/Assets/Drivers/BaseUrlDriver.php @@ -64,6 +64,8 @@ public function process(array $data, Model $model = null) $asset = $this->prepare(); + $asset->save(); + if ($model) { if ($model->assets()->count()) { // Get anything that isn't an "application"; @@ -74,10 +76,15 @@ public function process(array $data, Model $model = null) } else { $asset->primary = true; } - $model->assets()->attach($asset); + $asset->save(); + $model->assets()->attach($asset, [ + 'primary' => ! $model->assets()->images()->exists(), + 'assetable_type' => get_class($model), + 'position' => $model->assets()->count() + 1, + ]); } - $asset->save(); + dispatch(new GenerateTransforms($asset)); return $asset; diff --git a/src/Core/Assets/Services/AssetTransformService.php b/src/Core/Assets/Services/AssetTransformService.php index b4a58afe8..d56ecbd47 100644 --- a/src/Core/Assets/Services/AssetTransformService.php +++ b/src/Core/Assets/Services/AssetTransformService.php @@ -2,15 +2,16 @@ namespace GetCandy\Api\Core\Assets\Services; +use Image; +use Storage; use Carbon\Carbon; +use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Assets\Models\AssetTransform; -use GetCandy\Api\Core\Assets\Models\Transform; use GetCandy\Api\Core\Scaffold\BaseService; -use Illuminate\Contracts\Filesystem\FileNotFoundException; -use Image; +use GetCandy\Api\Core\Assets\Models\Transform; +use GetCandy\Api\Core\Assets\Models\AssetTransform; use Intervention\Image\Exception\NotReadableException; -use Storage; +use Illuminate\Contracts\Filesystem\FileNotFoundException; class AssetTransformService extends BaseService { @@ -88,14 +89,24 @@ protected function process($transformer, $asset) // Determine where to put this puppy... $thumbPath = $path.'/'.str_plural($transformer->handle); + $assetTransform = new AssetTransform; $assetTransform->asset()->associate($asset); $assetTransform->transform()->associate($transformer); + $filename = $transformer->handle.'_'.($asset->external ? $asset->location.'.jpg' : $asset->filename); + + $i = 1; + + while (DB::table('asset_transforms')->where('filename', '=', $filename)->exists()) { + $name = pathinfo($filename, PATHINFO_FILENAME); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $filename = "{$name}_{$i}.{$ext}"; + } + $assetTransform->location = $thumbPath; - $assetTransform->filename = $transformer->handle.'_'.($asset->external ? $asset->location.'.jpg' : $asset->filename); + $assetTransform->filename = $filename; $assetTransform->file_exists = true; - $assetTransform->save(); Storage::disk($source->disk)->put( From 933299b4b24f7d755a3610a6a27198ea078f01c7 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:12:38 +0000 Subject: [PATCH 038/152] Update root category nested set before deleting --- src/Core/Categories/Drafting/CategoryDrafter.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 4219e2156..fea935674 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -50,7 +50,20 @@ public function publish(Model $category) // Delete routes $parent->routes()->delete(); - $parent->forceDelete(); + + // Move any direct children on to the published category + DB::transaction(function ($query) use ($parent, $category) { + $parent->children->each(function ($node) use ($category) { + $node->parent()->associate($category)->save(); + }); + }); + + $parent->update([ + '_lft' => null, + '_rgt' => null + ]); + $parent->refresh()->forceDelete(); + // $parent->refresh()->forceDelete(); $category->drafted_at = null; $category->save(); From 9d8c12e9061ba85654e4ed96beedc033499b2a04 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:12:47 +0000 Subject: [PATCH 039/152] Add fillable --- src/Core/Categories/Models/Category.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Categories/Models/Category.php b/src/Core/Categories/Models/Category.php index 8065f6380..b86bd8976 100644 --- a/src/Core/Categories/Models/Category.php +++ b/src/Core/Categories/Models/Category.php @@ -46,7 +46,7 @@ class Category extends BaseModel * @var array */ protected $fillable = [ - 'attribute_data', 'parent_id', 'sort', 'range', + 'attribute_data', 'parent_id', 'sort', 'range', '_lft', '_rgt', ]; public function getParentIdAttribute($val) From 47a4798538468d89266b24f7119e005da7cb0529 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:12:53 +0000 Subject: [PATCH 040/152] Add loading --- src/Core/Products/Services/ProductVariantService.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Core/Products/Services/ProductVariantService.php b/src/Core/Products/Services/ProductVariantService.php index 16273f0de..513351620 100644 --- a/src/Core/Products/Services/ProductVariantService.php +++ b/src/Core/Products/Services/ProductVariantService.php @@ -99,7 +99,13 @@ public function create($id, array $data) ]); } - return $product->load('variants'); + return $product->load([ + 'variants.customerPricing.group', + 'variants.customerPricing.tax', + 'variants.image.transforms', + 'variants.tax', + 'variants.tiers.group', + ]); } public function canAddToBasket($variantId, $quantity) From d7cfe1f17a3089d699ec6086ac6f9047c8d1fce2 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:13:25 +0000 Subject: [PATCH 041/152] Improve filtering --- .../Actions/AbstractDocumentAction.php | 12 ++++----- .../Actions/Searching/FetchFilters.php | 7 ++++- .../Elasticsearch/Filters/ChannelFilter.php | 19 ++++++++++--- .../Filters/CustomerGroupFilter.php | 27 +++++++++++++------ .../Search/Indexables/ProductIndexable.php | 13 +++++++++ 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php index 6096e6e97..1548a0649 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php @@ -192,13 +192,13 @@ protected function getCategories(Model $model, $lang = 'en') protected function getCustomerGroups(Model $model, $lang = 'en') { - $groups = $model->customerGroups->filter(function ($group) { - return $group->pivot->purchasable && $group->pivot->visible; - })->map(function ($item) { + $groups = $model->customerGroups->map(function ($item) { return [ 'id' => $item->encodedId(), 'handle' => $item->handle, 'name' => $item->name, + 'visible' => (bool) $item->pivot->visible, + 'purchasable' => (bool) $item->pivot->purchasable, ]; })->toArray(); @@ -207,13 +207,13 @@ protected function getCustomerGroups(Model $model, $lang = 'en') protected function getChannels(Model $model, $lang = 'en') { - $channels = $model->channels->filter(function ($channel) { - return $channel->published_at <= now(); - })->map(function ($item) { + + $channels = $model->channels->map(function ($item) { return [ 'id' => $item->encodedId(), 'handle' => $item->handle, 'name' => $item->name, + 'published_at' => $item->pivot->published_at ]; })->toArray(); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index 17c5303c9..a34a36d7f 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -2,10 +2,12 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; +use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Attributes\Actions\FetchAttribute; +use GetCandy\Api\Core\Channels\Actions\FetchCurrentChannel; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\ChannelFilter; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CategoryFilter; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CustomerGroupFilter; -use Lorisleiva\Actions\Action; class FetchFilters extends Action { @@ -43,8 +45,11 @@ public function rules() */ public function handle() { + $currentChannel = FetchCurrentChannel::run(); + $applied = collect([ (new CustomerGroupFilter)->process($this->user()), + (new ChannelFilter)->process($currentChannel), ]); if (! empty($this->category)) { diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php index 225363005..aad415dec 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php @@ -2,9 +2,10 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters; -use Elastica\Query\BoolQuery; -use Elastica\Query\Nested; use Elastica\Query\Term; +use Elastica\Query\Range; +use Elastica\Query\Nested; +use Elastica\Query\BoolQuery; class ChannelFilter extends AbstractFilter { @@ -17,6 +18,7 @@ class ChannelFilter extends AbstractFilter public function process($payload, $type = null) { $this->channel = $payload; + return $this; } public function getQuery() @@ -27,10 +29,21 @@ public function getQuery() $cat->setPath('channels'); $term = new Term; - $term->setTerm('channels.handle', $this->channel); + $term->setTerm('channels.handle', $this->channel->handle); $cat->setQuery($term); + $dateRange = new Nested; + $dateRange->setPath('channels'); + + $range = new Range; + $range->addField('channels.published_at', [ + 'lte' => 'now' + ]); + + $dateRange->setQuery($range); + + $filter->addMust($dateRange); $filter->addMust($cat); return $filter; diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php index 04b785646..9a20dfc72 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php @@ -2,9 +2,10 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters; -use Elastica\Query\BoolQuery; -use Elastica\Query\Nested; use Elastica\Query\Term; +use Elastica\Query\Match; +use Elastica\Query\Nested; +use Elastica\Query\BoolQuery; use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; class CustomerGroupFilter extends AbstractFilter @@ -32,16 +33,26 @@ public function getQuery() $filter = new BoolQuery; foreach ($this->getCustomerGroups() as $model) { - $cat = new Nested; - $cat->setPath('customer_groups'); - $term = new Term; - $term->setTerm('customer_groups.id', $model->encodedId()); + $nested = new Nested; + $nested->setPath('customer_groups'); + + $bool = new BoolQuery; - $cat->setQuery($term); + $groupIdMatch = new Match; + $groupIdMatch->setField('customer_groups.id', $model->encodedId()); - $filter->addShould($cat); + $visibleMatch = new Match; + $visibleMatch->setField('customer_groups.visible', true); + + $bool->addMust($groupIdMatch); + $bool->addMust($visibleMatch); + + $nested->setQuery($bool); + + $filter->addShould($nested); } + return $filter; } diff --git a/src/Core/Search/Indexables/ProductIndexable.php b/src/Core/Search/Indexables/ProductIndexable.php index 7b9fb82fb..b98a9fe6b 100644 --- a/src/Core/Search/Indexables/ProductIndexable.php +++ b/src/Core/Search/Indexables/ProductIndexable.php @@ -62,6 +62,14 @@ public function getMapping() 'type' => 'keyword', 'index' => true, ], + 'purchasable' => [ + 'type' => 'boolean', + 'index' => true, + ], + 'visible' => [ + 'type' => 'boolean', + 'index' => true, + ], ], ], 'channels' => [ @@ -78,6 +86,11 @@ public function getMapping() 'type' => 'keyword', 'index' => true, ], + 'published_at' => [ + 'type' => 'date', + 'format' => 'yyyy-MM-dd HH:mm:ss', + 'index' => true, + ], ], ], 'pricing' => [ From 06b3fb20aa2d3ce9885210ec92071cfaba0bf691 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:13:37 +0000 Subject: [PATCH 042/152] Allow empty array on relations --- src/Http/Requests/Products/Associations/CreateRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Requests/Products/Associations/CreateRequest.php b/src/Http/Requests/Products/Associations/CreateRequest.php index 95e8c6ab3..b348e41d3 100644 --- a/src/Http/Requests/Products/Associations/CreateRequest.php +++ b/src/Http/Requests/Products/Associations/CreateRequest.php @@ -26,7 +26,7 @@ public function rules() return [ 'relations.*.association_id' => 'required|hashid_is_valid:products', 'relations.*.type' => 'required|hashid_is_valid:association_groups', - 'relations' => 'required|array', + 'relations' => 'array|min:0', ]; } } From 2765d8f3a4c17f3c147b09362c3e5e50f85a01e4 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:13:46 +0000 Subject: [PATCH 043/152] Set default depth to 0 (root) --- src/Http/Controllers/Categories/CategoryController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index 02db437fd..c6c0cd175 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -35,7 +35,7 @@ public function index(Request $request, CategoryCriteria $criteria) { $criteria ->tree($request->tree) - ->depth($request->depth ?: 1) + ->depth($request->depth ?: 0) ->include($this->parseIncludes($request->include)) ->limit($request->limit); From d35f91301c5e01f46e85abc3aa47c95934bb8af6 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 10:20:40 +0000 Subject: [PATCH 044/152] Fix PSR-4 --- src/Http/Resources/Plugins/PluginCollection.php | 2 +- src/Http/Resources/Plugins/PluginResource.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Resources/Plugins/PluginCollection.php b/src/Http/Resources/Plugins/PluginCollection.php index 77af12b5d..4b50d7e68 100644 --- a/src/Http/Resources/Plugins/PluginCollection.php +++ b/src/Http/Resources/Plugins/PluginCollection.php @@ -1,6 +1,6 @@ Date: Wed, 20 Jan 2021 10:32:41 +0000 Subject: [PATCH 045/152] Apply fixes from StyleCI (#351) Co-authored-by: Alec Ritson --- src/Core/Assets/Drivers/BaseUrlDriver.php | 1 - .../Assets/Services/AssetTransformService.php | 15 ++++++------- .../Categories/Drafting/CategoryDrafter.php | 3 +-- .../Listeners/SyncWithBasketListener.php | 2 +- .../Actions/AbstractDocumentAction.php | 3 +-- .../Actions/Searching/FetchFilters.php | 4 ++-- .../Elasticsearch/Filters/ChannelFilter.php | 9 ++++---- .../Filters/CustomerGroupFilter.php | 4 +--- .../Products/ProductController.php | 22 +++++++++---------- .../Shipping/ShippingPriceController.php | 1 + 10 files changed, 30 insertions(+), 34 deletions(-) diff --git a/src/Core/Assets/Drivers/BaseUrlDriver.php b/src/Core/Assets/Drivers/BaseUrlDriver.php index d6bf24a84..f79e4cc81 100644 --- a/src/Core/Assets/Drivers/BaseUrlDriver.php +++ b/src/Core/Assets/Drivers/BaseUrlDriver.php @@ -84,7 +84,6 @@ public function process(array $data, Model $model = null) ]); } - dispatch(new GenerateTransforms($asset)); return $asset; diff --git a/src/Core/Assets/Services/AssetTransformService.php b/src/Core/Assets/Services/AssetTransformService.php index d56ecbd47..d70fb1e4c 100644 --- a/src/Core/Assets/Services/AssetTransformService.php +++ b/src/Core/Assets/Services/AssetTransformService.php @@ -2,16 +2,16 @@ namespace GetCandy\Api\Core\Assets\Services; -use Image; -use Storage; use Carbon\Carbon; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Assets\Models\Transform; use GetCandy\Api\Core\Assets\Models\AssetTransform; -use Intervention\Image\Exception\NotReadableException; +use GetCandy\Api\Core\Assets\Models\Transform; +use GetCandy\Api\Core\Scaffold\BaseService; use Illuminate\Contracts\Filesystem\FileNotFoundException; +use Illuminate\Support\Facades\DB; +use Image; +use Intervention\Image\Exception\NotReadableException; +use Storage; class AssetTransformService extends BaseService { @@ -89,7 +89,6 @@ protected function process($transformer, $asset) // Determine where to put this puppy... $thumbPath = $path.'/'.str_plural($transformer->handle); - $assetTransform = new AssetTransform; $assetTransform->asset()->associate($asset); $assetTransform->transform()->associate($transformer); @@ -100,7 +99,7 @@ protected function process($transformer, $asset) while (DB::table('asset_transforms')->where('filename', '=', $filename)->exists()) { $name = pathinfo($filename, PATHINFO_FILENAME); - $ext = pathinfo($filename, PATHINFO_EXTENSION); + $ext = pathinfo($filename, PATHINFO_EXTENSION); $filename = "{$name}_{$i}.{$ext}"; } diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index fea935674..0c0826ae4 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -60,7 +60,7 @@ public function publish(Model $category) $parent->update([ '_lft' => null, - '_rgt' => null + '_rgt' => null, ]); $parent->refresh()->forceDelete(); // $parent->refresh()->forceDelete(); @@ -75,7 +75,6 @@ public function publish(Model $category) ]); } - event(new ModelPublishedEvent($category)); return $category; diff --git a/src/Core/Orders/Listeners/SyncWithBasketListener.php b/src/Core/Orders/Listeners/SyncWithBasketListener.php index 6401bb3d6..fd54fab74 100644 --- a/src/Core/Orders/Listeners/SyncWithBasketListener.php +++ b/src/Core/Orders/Listeners/SyncWithBasketListener.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Orders\Listeners; -use GetCandy\Api\Core\Discounts\Factory; use GetCandy\Api\Core\Baskets\Events\BasketStoredEvent; +use GetCandy\Api\Core\Discounts\Factory; use GetCandy\Api\Core\Orders\Interfaces\OrderFactoryInterface; class SyncWithBasketListener diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php index 1548a0649..200e1485e 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/AbstractDocumentAction.php @@ -207,13 +207,12 @@ protected function getCustomerGroups(Model $model, $lang = 'en') protected function getChannels(Model $model, $lang = 'en') { - $channels = $model->channels->map(function ($item) { return [ 'id' => $item->encodedId(), 'handle' => $item->handle, 'name' => $item->name, - 'published_at' => $item->pivot->published_at + 'published_at' => $item->pivot->published_at, ]; })->toArray(); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index a34a36d7f..d44734c02 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -2,12 +2,12 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; -use Lorisleiva\Actions\Action; use GetCandy\Api\Core\Attributes\Actions\FetchAttribute; use GetCandy\Api\Core\Channels\Actions\FetchCurrentChannel; -use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\ChannelFilter; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CategoryFilter; +use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\ChannelFilter; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CustomerGroupFilter; +use Lorisleiva\Actions\Action; class FetchFilters extends Action { diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php index aad415dec..7037e87e3 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/ChannelFilter.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters; -use Elastica\Query\Term; -use Elastica\Query\Range; -use Elastica\Query\Nested; use Elastica\Query\BoolQuery; +use Elastica\Query\Nested; +use Elastica\Query\Range; +use Elastica\Query\Term; class ChannelFilter extends AbstractFilter { @@ -18,6 +18,7 @@ class ChannelFilter extends AbstractFilter public function process($payload, $type = null) { $this->channel = $payload; + return $this; } @@ -38,7 +39,7 @@ public function getQuery() $range = new Range; $range->addField('channels.published_at', [ - 'lte' => 'now' + 'lte' => 'now', ]); $dateRange->setQuery($range); diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php index 9a20dfc72..edfa1a6b2 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/CustomerGroupFilter.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters; -use Elastica\Query\Term; +use Elastica\Query\BoolQuery; use Elastica\Query\Match; use Elastica\Query\Nested; -use Elastica\Query\BoolQuery; use GetCandy\Api\Core\Customers\Actions\FetchDefaultCustomerGroup; class CustomerGroupFilter extends AbstractFilter @@ -52,7 +51,6 @@ public function getQuery() $filter->addShould($nested); } - return $filter; } diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 5d311853a..6c2ab2968 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -2,28 +2,28 @@ namespace GetCandy\Api\Http\Controllers\Products; -use Hashids; use Drafting; use GetCandy; -use Illuminate\Http\Request; +use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; +use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\ProductCriteria; -use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Core\Products\Services\ProductService; use GetCandy\Api\Exceptions\InvalidLanguageException; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Products\CreateRequest; use GetCandy\Api\Http\Requests\Products\DeleteRequest; -use GetCandy\Api\Http\Requests\Products\UpdateRequest; -use GetCandy\Api\Core\Products\Services\ProductService; -use Illuminate\Database\Eloquent\ModelNotFoundException; use GetCandy\Api\Http\Requests\Products\DuplicateRequest; +use GetCandy\Api\Http\Requests\Products\UpdateRequest; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; use GetCandy\Api\Http\Resources\Products\ProductResource; +use Hashids; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\HttpException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; -use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; class ProductController extends BaseController { diff --git a/src/Http/Controllers/Shipping/ShippingPriceController.php b/src/Http/Controllers/Shipping/ShippingPriceController.php index 891b3e9aa..dd5e1eb5b 100644 --- a/src/Http/Controllers/Shipping/ShippingPriceController.php +++ b/src/Http/Controllers/Shipping/ShippingPriceController.php @@ -71,6 +71,7 @@ public function update($id, StoreRequest $request) public function estimate(EstimateRequest $request) { $result = GetCandy::shippingPrices()->estimate($request->amount, $request->zip, $request->limit); + return new ShippingPriceCollection($result); } From 780884a4ef3c44f5d182fdc159469d49315cb974 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:57:11 +0000 Subject: [PATCH 046/152] =?UTF-8?q?Only=20apply=20filters=20if=20we?= =?UTF-8?q?=E2=80=99re=20not=20on=20the=20hub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Elasticsearch/Actions/Searching/FetchFilters.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index d44734c02..99a21ecb8 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -2,6 +2,7 @@ namespace GetCandy\Api\Core\Search\Drivers\Elasticsearch\Actions\Searching; +use GetCandy; use GetCandy\Api\Core\Attributes\Actions\FetchAttribute; use GetCandy\Api\Core\Channels\Actions\FetchCurrentChannel; use GetCandy\Api\Core\Search\Drivers\Elasticsearch\Filters\CategoryFilter; @@ -49,7 +50,6 @@ public function handle() $applied = collect([ (new CustomerGroupFilter)->process($this->user()), - (new ChannelFilter)->process($currentChannel), ]); if (! empty($this->category)) { @@ -57,6 +57,12 @@ public function handle() (new CategoryFilter)->process($this->category) ); } + + if (!GetCandy::isHubRequest()) { + $applied->push( + (new ChannelFilter)->process($currentChannel) + ); + } foreach ($this->filters ?? [] as $filter => $value) { $object = $this->findFilter($filter); if ($object && $object = $object->process($value, $filter)) { From 6e0c2a862910b4646e2ca18da3855c9f2fa1e9ff Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:57:24 +0000 Subject: [PATCH 047/152] Add method to delete from index --- .../Drivers/Elasticsearch/Elasticsearch.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index fa94f9bc5..5bcb84ba5 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -72,6 +72,29 @@ public function update($documents) $this->onReference($reference)->index($documents, false); } + public function delete($documents) { + if (! $documents instanceof Collection) { + $documents = collect([$documents]); + } + + $client = FetchClient::run(); + + $prefix = config('getcandy.search.index_prefix'); + + $type = get_class($documents->first()) == Product::class ? 'products' : 'categories'; + + $existing = collect($client->getStatus()->getIndexNames())->filter(function ($indexName) use ($prefix, $type) { + return strpos($indexName, "{$prefix}_{$type}") !== false; + })->first(); + + + $index = $client->getIndex($existing); + + $documents->each(function ($document) use ($index) { + $index->deleteById($document->encoded_id); + }); + } + public function search($data) { if ($data instanceof Request) { From c1e1184b980bba8eabc5a96a28abf524a0a5b540 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:57:39 +0000 Subject: [PATCH 048/152] Pass user through when setting billing address --- src/Http/Controllers/Orders/OrderController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/Orders/OrderController.php b/src/Http/Controllers/Orders/OrderController.php index 2883ab883..8226ed3a0 100644 --- a/src/Http/Controllers/Orders/OrderController.php +++ b/src/Http/Controllers/Orders/OrderController.php @@ -355,7 +355,7 @@ public function addContact($orderId, Request $request) public function billingAddress($id, StoreAddressRequest $request) { try { - $order = GetCandy::orders()->setBilling($id, $request->all()); + $order = GetCandy::orders()->setBilling($id, $request->all(), $request->user()); } catch (ModelNotFoundException $e) { return $this->errorNotFound(); } From ee17a2224d9ea33c9dcc1ec8fe344bb1370acc9c Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:57:54 +0000 Subject: [PATCH 049/152] Add counts to search id fetching --- src/Core/Search/Actions/FetchSearchedIds.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Search/Actions/FetchSearchedIds.php b/src/Core/Search/Actions/FetchSearchedIds.php index a189b98bf..1adea2ab3 100644 --- a/src/Core/Search/Actions/FetchSearchedIds.php +++ b/src/Core/Search/Actions/FetchSearchedIds.php @@ -29,6 +29,7 @@ public function rules() 'model' => 'required', 'encoded_ids' => 'array|min:0', 'include' => 'nullable', + 'counts' => 'nullable', ]; } @@ -43,7 +44,7 @@ public function handle() $parsedIds = $this->delegateTo(DecodeIds::class); $placeholders = implode(',', array_fill(0, count($parsedIds), '?')); // string for the query - $query = $model->with($this->resolveEagerRelations())->whereIn("{$model->getTable()}.id", $parsedIds); + $query = $model->with($this->resolveEagerRelations())->withCount($this->resolveRelationCounts())->whereIn("{$model->getTable()}.id", $parsedIds); if (count($parsedIds)) { $query = $query->orderByRaw("field({$model->getTable()}.id,{$placeholders})", $parsedIds); From fbed3bb53604032a6a50217e2b761e9aaed68945 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:58:18 +0000 Subject: [PATCH 050/152] Save address against user --- src/Core/Orders/Services/OrderService.php | 49 ++++++++++++++++------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index 4d4d3e5de..5df851710 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -2,29 +2,31 @@ namespace GetCandy\Api\Core\Orders\Services; -use Auth; -use Carbon\Carbon; use DB; +use PDF; +use Auth; use GetCandy; -use GetCandy\Api\Core\ActivityLog\Interfaces\ActivityLogFactoryInterface; -use GetCandy\Api\Core\Addresses\Actions\FetchAddressAction; +use Carbon\Carbon; +use GetCandy\Api\Core\Orders\Models\Order; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Baskets\Services\BasketService; -use GetCandy\Api\Core\Currencies\Interfaces\CurrencyConverterInterface; -use GetCandy\Api\Core\Orders\Events\OrderBeforeSavedEvent; -use GetCandy\Api\Core\Orders\Events\OrderProcessedEvent; +use GetCandy\Api\Core\Orders\Models\OrderDiscount; use GetCandy\Api\Core\Orders\Events\OrderSavedEvent; -use GetCandy\Api\Core\Orders\Exceptions\BasketHasPlacedOrderException; -use GetCandy\Api\Core\Orders\Exceptions\IncompleteOrderException; -use GetCandy\Api\Core\Orders\Interfaces\OrderServiceInterface; use GetCandy\Api\Core\Orders\Jobs\OrderNotification; -use GetCandy\Api\Core\Orders\Models\Order; -use GetCandy\Api\Core\Orders\Models\OrderDiscount; +use GetCandy\Api\Core\Baskets\Services\BasketService; +use GetCandy\Api\Core\Countries\Actions\FetchCountry; use GetCandy\Api\Core\Payments\Services\PaymentService; use GetCandy\Api\Core\Pricing\PriceCalculatorInterface; +use GetCandy\Api\Core\Orders\Events\OrderProcessedEvent; +use GetCandy\Api\Core\Orders\Events\OrderBeforeSavedEvent; +use GetCandy\Api\Core\Addresses\Actions\FetchAddressAction; +use GetCandy\Api\Core\Addresses\Actions\CreateAddressAction; +use GetCandy\Api\Core\Orders\Interfaces\OrderServiceInterface; use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; -use GetCandy\Api\Core\Scaffold\BaseService; -use PDF; +use GetCandy\Api\Core\Orders\Exceptions\IncompleteOrderException; +use GetCandy\Api\Core\Orders\Exceptions\BasketHasPlacedOrderException; +use GetCandy\Api\Core\Currencies\Interfaces\CurrencyConverterInterface; +use GetCandy\Api\Core\ActivityLog\Interfaces\ActivityLogFactoryInterface; class OrderService extends BaseService implements OrderServiceInterface { @@ -464,6 +466,23 @@ protected function addAddress($id, $data, $type, $user = null) $payload['email'] = $data['email'] ?? null; $payload['phone'] = $data['phone'] ?? null; $data = $payload; + } elseif ($user) { + // TODO: Reassess when we refactor order addresses. + $addressData = $data; + $addressData[$type] = true; + $addressData['postal_code'] = $data['zip']; + $addressData['state'] = $data['county']; + // Does this address already exist for the user? Check postcode... + $existing = $user->addresses() + ->where('postal_code', '=', $addressData['postal_code']) + ->where($type, '=', true) + ->exists(); + $addressData['country_id'] = FetchCountry::run([ + 'name' => $addressData['country'], + ])->encoded_id; + if (!$existing) { + CreateAddressAction::run($addressData); + } } $this->setFields($order, $data, $type); From a6231721496bb55943b29472ee7b8d6fbc613b75 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:58:43 +0000 Subject: [PATCH 051/152] Remove product from index with hard deleting --- src/Core/Products/Observers/ProductObserver.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Core/Products/Observers/ProductObserver.php b/src/Core/Products/Observers/ProductObserver.php index 1e13dcd0c..2195bf00f 100644 --- a/src/Core/Products/Observers/ProductObserver.php +++ b/src/Core/Products/Observers/ProductObserver.php @@ -2,8 +2,9 @@ namespace GetCandy\Api\Core\Products\Observers; -use GetCandy\Api\Core\Assets\Services\AssetService; +use GetCandy\Api\Core\Search\SearchManager; use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Assets\Services\AssetService; class ProductObserver { @@ -12,9 +13,12 @@ class ProductObserver */ protected $assets; - public function __construct(AssetService $assets) + protected $search; + + public function __construct(AssetService $assets, SearchManager $search) { $this->assets = $assets; + $this->search = $search; } /** @@ -33,6 +37,8 @@ public function deleted(Product $product) $product->categories()->detach(); $product->routes()->forceDelete(); $product->recommendations()->forceDelete(); + $driver = $this->search->with(config('getcandy.search.driver')); + $driver->delete($product); } } } From 531f6ff063153f75a119d43cbd26d05372ec1852 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:58:56 +0000 Subject: [PATCH 052/152] Add category observer --- .../Categories/Observers/CategoryObserver.php | 35 +++++++++++++++++++ src/Providers/CategoryServiceProvider.php | 8 +++-- 2 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/Core/Categories/Observers/CategoryObserver.php diff --git a/src/Core/Categories/Observers/CategoryObserver.php b/src/Core/Categories/Observers/CategoryObserver.php new file mode 100644 index 000000000..b974301c5 --- /dev/null +++ b/src/Core/Categories/Observers/CategoryObserver.php @@ -0,0 +1,35 @@ +assets = $assets; + $this->search = $search; + } + + /** + * Handle the User "deleted" event. + * + * @param \GetCandy\Api\Core\Categories\Models\Category $category + * @return void + */ + public function deleted(Category $category) + { + $driver = $this->search->with(config('getcandy.search.driver')); + $driver->delete($category); + } +} diff --git a/src/Providers/CategoryServiceProvider.php b/src/Providers/CategoryServiceProvider.php index 059ac590b..605bece0b 100644 --- a/src/Providers/CategoryServiceProvider.php +++ b/src/Providers/CategoryServiceProvider.php @@ -3,11 +3,13 @@ namespace GetCandy\Api\Providers; use Drafting; +use Versioning; +use Illuminate\Support\ServiceProvider; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Categories\Drafting\CategoryDrafter; use GetCandy\Api\Core\Categories\Services\CategoryService; +use GetCandy\Api\Core\Categories\Observers\CategoryObserver; use GetCandy\Api\Core\Categories\Versioning\CategoryVersioner; -use Illuminate\Support\ServiceProvider; -use Versioning; class CategoryServiceProvider extends ServiceProvider { @@ -24,5 +26,7 @@ public function boot() $this->app->bind('getcandy.categories', function ($app) { return $app->make(CategoryService::class); }); + + Category::observe(CategoryObserver::class); } } From a587f94d072aa9a340b335d34e4dbcef695e2311 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 14:59:19 +0000 Subject: [PATCH 053/152] Remove permission check for the time --- src/Core/Addresses/Policies/AddressPolicy.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Addresses/Policies/AddressPolicy.php b/src/Core/Addresses/Policies/AddressPolicy.php index b768ea710..9c0c5c4e1 100644 --- a/src/Core/Addresses/Policies/AddressPolicy.php +++ b/src/Core/Addresses/Policies/AddressPolicy.php @@ -16,7 +16,7 @@ class AddressPolicy */ public function create(?User $user) { - return $user->can('create-address'); + return true; } public function update(?User $user, Address $address) From f54f6df029c3326c46b489fb5a4e55f2775f9205 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 15:39:15 +0000 Subject: [PATCH 054/152] Only delete if we have an index --- .../Search/Drivers/Elasticsearch/Elasticsearch.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index 5bcb84ba5..01c82d495 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -76,7 +76,6 @@ public function delete($documents) { if (! $documents instanceof Collection) { $documents = collect([$documents]); } - $client = FetchClient::run(); $prefix = config('getcandy.search.index_prefix'); @@ -87,12 +86,13 @@ public function delete($documents) { return strpos($indexName, "{$prefix}_{$type}") !== false; })->first(); + if ($existing) { + $index = $client->getIndex($existing); - $index = $client->getIndex($existing); - - $documents->each(function ($document) use ($index) { - $index->deleteById($document->encoded_id); - }); + $documents->each(function ($document) use ($index) { + $index->deleteById($document->encoded_id); + }); + } } public function search($data) From 5d463cc810bc05d47b517912c6fdc7404f2e021f Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 20 Jan 2021 15:39:31 +0000 Subject: [PATCH 055/152] Set up a mock transport for Elasticsearch --- tests/Stubs/MockTransport.php | 46 +++++++++++++++++++++++++++++++++++ tests/TestCase.php | 37 +++++++++++++++------------- 2 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 tests/Stubs/MockTransport.php diff --git a/tests/Stubs/MockTransport.php b/tests/Stubs/MockTransport.php new file mode 100644 index 000000000..6d537c4aa --- /dev/null +++ b/tests/Stubs/MockTransport.php @@ -0,0 +1,46 @@ + 0, + 'timed_out' => false, + 'indices' => [], + '_shards' => [ + 'total' => 0, + 'successful' => 0, + 'failed' => 0, + ], + 'hits' => [ + 'total' => [ + 'value' => 0, + ], + 'max_score' => null, + 'hits' => [], + ], + 'params' => $params, + ]; + + return new Response(JSON::stringify($response)); + } +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 895a458c7..d22931ecf 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,28 +3,30 @@ namespace Tests; use Closure; -use Facades\GetCandy\Api\Core\Pricing\PriceCalculator; -use Facades\GetCandy\Api\Core\Taxes\TaxCalculator; -use GetCandy\Api\Core\Baskets\Factories\BasketFactory; +use Tests\Stubs\User; +use Illuminate\Support\Fluent; +use Tests\Stubs\MockTransport; +use Illuminate\Encryption\Encrypter; +use Elastica\Transport\NullTransport; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\SQLiteConnection; +use Vinkla\Hashids\HashidsServiceProvider; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Baskets\Models\BasketLine; -use GetCandy\Api\Core\Channels\Interfaces\ChannelFactoryInterface; -use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Currencies\Facades\CurrencyConverter; use GetCandy\Api\Core\Facades\GetCandyFacade; -use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Providers\ApiServiceProvider; -use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\SQLiteBuilder; -use Illuminate\Database\SQLiteConnection; -use Illuminate\Encryption\Encrypter; -use Illuminate\Support\Fluent; +use GetCandy\Api\Core\Channels\Models\Channel; +use GetCandy\Api\Providers\ApiServiceProvider; +use GetCandy\Api\Core\Baskets\Models\BasketLine; +use Spatie\Permission\PermissionServiceProvider; use NeonDigital\Drafting\DraftingServiceProvider; -use NeonDigital\Versioning\VersioningServiceProvider; +use Facades\GetCandy\Api\Core\Taxes\TaxCalculator; use Spatie\Activitylog\ActivitylogServiceProvider; -use Spatie\Permission\PermissionServiceProvider; -use Tests\Stubs\User; -use Vinkla\Hashids\HashidsServiceProvider; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use NeonDigital\Versioning\VersioningServiceProvider; +use Facades\GetCandy\Api\Core\Pricing\PriceCalculator; +use GetCandy\Api\Core\Baskets\Factories\BasketFactory; +use GetCandy\Api\Core\Currencies\Facades\CurrencyConverter; +use GetCandy\Api\Core\Channels\Interfaces\ChannelFactoryInterface; abstract class TestCase extends \Orchestra\Testbench\TestCase { @@ -116,6 +118,7 @@ protected function getEnvironmentSetUp($app) 'services.sagepay.vendor' => 'SagePay', 'getcandy' => require __DIR__.'/../config/getcandy.php', 'app.key' => Encrypter::generateKey(null), + 'getcandy.search.client_config.elastic.transport' => MockTransport::class ]; foreach ($config as $key => $value) { From 60439ce535dea63e0ca795ea6884bdd0bdee91ed Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Thu, 21 Jan 2021 08:11:00 +0000 Subject: [PATCH 056/152] Apply fixes from StyleCI (#353) Co-authored-by: Alec Ritson --- .../Categories/Observers/CategoryObserver.php | 4 +- src/Core/Orders/Services/OrderService.php | 36 ++++++++--------- .../Products/Observers/ProductObserver.php | 4 +- .../Actions/Searching/FetchFilters.php | 2 +- .../Drivers/Elasticsearch/Elasticsearch.php | 3 +- src/Providers/CategoryServiceProvider.php | 8 ++-- tests/Stubs/MockTransport.php | 4 +- tests/TestCase.php | 39 +++++++++---------- 8 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/Core/Categories/Observers/CategoryObserver.php b/src/Core/Categories/Observers/CategoryObserver.php index b974301c5..a2e02ada4 100644 --- a/src/Core/Categories/Observers/CategoryObserver.php +++ b/src/Core/Categories/Observers/CategoryObserver.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Categories\Observers; -use GetCandy\Api\Core\Search\SearchManager; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Assets\Services\AssetService; +use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Search\SearchManager; class CategoryObserver { diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index 5df851710..d5e72e7ae 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -2,31 +2,31 @@ namespace GetCandy\Api\Core\Orders\Services; -use DB; -use PDF; use Auth; -use GetCandy; use Carbon\Carbon; -use GetCandy\Api\Core\Orders\Models\Order; -use GetCandy\Api\Core\Scaffold\BaseService; +use DB; +use GetCandy; +use GetCandy\Api\Core\ActivityLog\Interfaces\ActivityLogFactoryInterface; +use GetCandy\Api\Core\Addresses\Actions\CreateAddressAction; +use GetCandy\Api\Core\Addresses\Actions\FetchAddressAction; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Orders\Models\OrderDiscount; -use GetCandy\Api\Core\Orders\Events\OrderSavedEvent; -use GetCandy\Api\Core\Orders\Jobs\OrderNotification; use GetCandy\Api\Core\Baskets\Services\BasketService; use GetCandy\Api\Core\Countries\Actions\FetchCountry; -use GetCandy\Api\Core\Payments\Services\PaymentService; -use GetCandy\Api\Core\Pricing\PriceCalculatorInterface; -use GetCandy\Api\Core\Orders\Events\OrderProcessedEvent; +use GetCandy\Api\Core\Currencies\Interfaces\CurrencyConverterInterface; use GetCandy\Api\Core\Orders\Events\OrderBeforeSavedEvent; -use GetCandy\Api\Core\Addresses\Actions\FetchAddressAction; -use GetCandy\Api\Core\Addresses\Actions\CreateAddressAction; +use GetCandy\Api\Core\Orders\Events\OrderProcessedEvent; +use GetCandy\Api\Core\Orders\Events\OrderSavedEvent; +use GetCandy\Api\Core\Orders\Exceptions\BasketHasPlacedOrderException; +use GetCandy\Api\Core\Orders\Exceptions\IncompleteOrderException; use GetCandy\Api\Core\Orders\Interfaces\OrderServiceInterface; +use GetCandy\Api\Core\Orders\Jobs\OrderNotification; +use GetCandy\Api\Core\Orders\Models\Order; +use GetCandy\Api\Core\Orders\Models\OrderDiscount; +use GetCandy\Api\Core\Payments\Services\PaymentService; +use GetCandy\Api\Core\Pricing\PriceCalculatorInterface; use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; -use GetCandy\Api\Core\Orders\Exceptions\IncompleteOrderException; -use GetCandy\Api\Core\Orders\Exceptions\BasketHasPlacedOrderException; -use GetCandy\Api\Core\Currencies\Interfaces\CurrencyConverterInterface; -use GetCandy\Api\Core\ActivityLog\Interfaces\ActivityLogFactoryInterface; +use GetCandy\Api\Core\Scaffold\BaseService; +use PDF; class OrderService extends BaseService implements OrderServiceInterface { @@ -480,7 +480,7 @@ protected function addAddress($id, $data, $type, $user = null) $addressData['country_id'] = FetchCountry::run([ 'name' => $addressData['country'], ])->encoded_id; - if (!$existing) { + if (! $existing) { CreateAddressAction::run($addressData); } } diff --git a/src/Core/Products/Observers/ProductObserver.php b/src/Core/Products/Observers/ProductObserver.php index 2195bf00f..28e02dc7f 100644 --- a/src/Core/Products/Observers/ProductObserver.php +++ b/src/Core/Products/Observers/ProductObserver.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Products\Observers; -use GetCandy\Api\Core\Search\SearchManager; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Assets\Services\AssetService; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Search\SearchManager; class ProductObserver { diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php index 99a21ecb8..e788a06ce 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchFilters.php @@ -58,7 +58,7 @@ public function handle() ); } - if (!GetCandy::isHubRequest()) { + if (! GetCandy::isHubRequest()) { $applied->push( (new ChannelFilter)->process($currentChannel) ); diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index 01c82d495..e3354764b 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -72,7 +72,8 @@ public function update($documents) $this->onReference($reference)->index($documents, false); } - public function delete($documents) { + public function delete($documents) + { if (! $documents instanceof Collection) { $documents = collect([$documents]); } diff --git a/src/Providers/CategoryServiceProvider.php b/src/Providers/CategoryServiceProvider.php index 605bece0b..08a547d4d 100644 --- a/src/Providers/CategoryServiceProvider.php +++ b/src/Providers/CategoryServiceProvider.php @@ -3,13 +3,13 @@ namespace GetCandy\Api\Providers; use Drafting; -use Versioning; -use Illuminate\Support\ServiceProvider; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Categories\Drafting\CategoryDrafter; -use GetCandy\Api\Core\Categories\Services\CategoryService; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Categories\Observers\CategoryObserver; +use GetCandy\Api\Core\Categories\Services\CategoryService; use GetCandy\Api\Core\Categories\Versioning\CategoryVersioner; +use Illuminate\Support\ServiceProvider; +use Versioning; class CategoryServiceProvider extends ServiceProvider { diff --git a/tests/Stubs/MockTransport.php b/tests/Stubs/MockTransport.php index 6d537c4aa..a45d01f02 100644 --- a/tests/Stubs/MockTransport.php +++ b/tests/Stubs/MockTransport.php @@ -3,14 +3,12 @@ namespace Tests\Stubs; use Elastica\JSON; -use Elastica\Request; use Elastica\Response; use Elastica\Transport\NullTransport; // Declaration of Tests\Stubs\MockTransport::_getGuzzleClient($baseUrl, $persistent = true) must be compatible with Elastica\Transpo // rt\Guzzle::_getGuzzleClient(bool $persistent = true): GuzzleHttp\Client - class MockTransport extends NullTransport { /** @@ -43,4 +41,4 @@ public function generateDefaultResponse(array $params): Response return new Response(JSON::stringify($response)); } -} \ No newline at end of file +} diff --git a/tests/TestCase.php b/tests/TestCase.php index d22931ecf..cc7d43711 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,30 +3,29 @@ namespace Tests; use Closure; -use Tests\Stubs\User; -use Illuminate\Support\Fluent; -use Tests\Stubs\MockTransport; -use Illuminate\Encryption\Encrypter; -use Elastica\Transport\NullTransport; -use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\SQLiteConnection; -use Vinkla\Hashids\HashidsServiceProvider; +use Facades\GetCandy\Api\Core\Pricing\PriceCalculator; +use Facades\GetCandy\Api\Core\Taxes\TaxCalculator; +use GetCandy\Api\Core\Baskets\Factories\BasketFactory; use GetCandy\Api\Core\Baskets\Models\Basket; -use GetCandy\Api\Core\Facades\GetCandyFacade; -use Illuminate\Database\Schema\SQLiteBuilder; +use GetCandy\Api\Core\Baskets\Models\BasketLine; +use GetCandy\Api\Core\Channels\Interfaces\ChannelFactoryInterface; use GetCandy\Api\Core\Channels\Models\Channel; +use GetCandy\Api\Core\Currencies\Facades\CurrencyConverter; +use GetCandy\Api\Core\Facades\GetCandyFacade; +use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Providers\ApiServiceProvider; -use GetCandy\Api\Core\Baskets\Models\BasketLine; -use Spatie\Permission\PermissionServiceProvider; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\SQLiteBuilder; +use Illuminate\Database\SQLiteConnection; +use Illuminate\Encryption\Encrypter; +use Illuminate\Support\Fluent; use NeonDigital\Drafting\DraftingServiceProvider; -use Facades\GetCandy\Api\Core\Taxes\TaxCalculator; -use Spatie\Activitylog\ActivitylogServiceProvider; -use GetCandy\Api\Core\Products\Models\ProductVariant; use NeonDigital\Versioning\VersioningServiceProvider; -use Facades\GetCandy\Api\Core\Pricing\PriceCalculator; -use GetCandy\Api\Core\Baskets\Factories\BasketFactory; -use GetCandy\Api\Core\Currencies\Facades\CurrencyConverter; -use GetCandy\Api\Core\Channels\Interfaces\ChannelFactoryInterface; +use Spatie\Activitylog\ActivitylogServiceProvider; +use Spatie\Permission\PermissionServiceProvider; +use Tests\Stubs\MockTransport; +use Tests\Stubs\User; +use Vinkla\Hashids\HashidsServiceProvider; abstract class TestCase extends \Orchestra\Testbench\TestCase { @@ -118,7 +117,7 @@ protected function getEnvironmentSetUp($app) 'services.sagepay.vendor' => 'SagePay', 'getcandy' => require __DIR__.'/../config/getcandy.php', 'app.key' => Encrypter::generateKey(null), - 'getcandy.search.client_config.elastic.transport' => MockTransport::class + 'getcandy.search.client_config.elastic.transport' => MockTransport::class, ]; foreach ($config as $key => $value) { From cfa2fc71055b61efa28cf92d47a9bda44d5ca41b Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 21 Jan 2021 20:02:26 +0000 Subject: [PATCH 057/152] Updates --- src/Core/Orders/Services/OrderService.php | 5 +++-- src/Core/Products/Actions/UpdateProductFamily.php | 4 +++- src/Core/Routes/Actions/SearchForRoute.php | 2 +- src/Http/Requests/Orders/StoreAddressRequest.php | 3 ++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index d5e72e7ae..2eb2e9e6b 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -460,9 +460,10 @@ protected function addAddress($id, $data, $type, $user = null) 'city', 'county', 'state', - 'country', - 'zip', ]); + $payload['country'] = $shipping->country->name; + $payload['zip'] = $shipping->postal_code; + $payload['county'] = $shipping->state; $payload['email'] = $data['email'] ?? null; $payload['phone'] = $data['phone'] ?? null; $data = $payload; diff --git a/src/Core/Products/Actions/UpdateProductFamily.php b/src/Core/Products/Actions/UpdateProductFamily.php index 8c5c578f8..d5b4cb928 100644 --- a/src/Core/Products/Actions/UpdateProductFamily.php +++ b/src/Core/Products/Actions/UpdateProductFamily.php @@ -46,7 +46,9 @@ public function rules(): array public function handle() { $productFamily = $this->delegateTo(FetchProductFamily::class); - $productFamily->update($this->validated()); + $productFamily->update([ + 'name' => $this->name, + ]); if ($this->attribute_ids) { AttachModelToAttributes::run([ diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index 2d95ed7e4..265ddebec 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -43,7 +43,7 @@ public function handle() { $query = Route::whereSlug($this->slug)->with( $this->resolveEagerRelations() - ); + )->withCount($this->resolveRelationCounts()); if ($this->path) { $query->wherePath($this->path); diff --git a/src/Http/Requests/Orders/StoreAddressRequest.php b/src/Http/Requests/Orders/StoreAddressRequest.php index 1104dd93d..d400140cb 100644 --- a/src/Http/Requests/Orders/StoreAddressRequest.php +++ b/src/Http/Requests/Orders/StoreAddressRequest.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Http\Requests\Orders; use GetCandy\Api\Http\Requests\FormRequest; +use GetCandy\Api\Core\Addresses\Models\Address; class StoreAddressRequest extends FormRequest { @@ -33,7 +34,7 @@ public function rules() return [ 'firstname' => 'required_without:address_id|max:20', 'lastname' => 'required_without:address_id|max:20', - 'address_id' => 'hashid_is_valid:addresses', + 'address_id' => 'hashid_is_valid:' . Address::class, 'address' => 'required_without:address_id|max:40', 'city' => 'required_without:address_id|max:40', 'county' => 'required_without_all:address_id,state|max:40', From 9897b20f07860e8cf4e549580d3d0719c683434f Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 22 Jan 2021 09:43:33 +0000 Subject: [PATCH 058/152] Make depth 1 by default --- src/Http/Controllers/Categories/CategoryController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index c6c0cd175..02db437fd 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -35,7 +35,7 @@ public function index(Request $request, CategoryCriteria $criteria) { $criteria ->tree($request->tree) - ->depth($request->depth ?: 0) + ->depth($request->depth ?: 1) ->include($this->parseIncludes($request->include)) ->limit($request->limit); From 402b7ace194b985cac55740988ef53f752caf17b Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 22 Jan 2021 09:44:57 +0000 Subject: [PATCH 059/152] StyleCI fix --- src/Http/Requests/Orders/StoreAddressRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Requests/Orders/StoreAddressRequest.php b/src/Http/Requests/Orders/StoreAddressRequest.php index d400140cb..ce9265632 100644 --- a/src/Http/Requests/Orders/StoreAddressRequest.php +++ b/src/Http/Requests/Orders/StoreAddressRequest.php @@ -34,7 +34,7 @@ public function rules() return [ 'firstname' => 'required_without:address_id|max:20', 'lastname' => 'required_without:address_id|max:20', - 'address_id' => 'hashid_is_valid:' . Address::class, + 'address_id' => 'hashid_is_valid:'.Address::class, 'address' => 'required_without:address_id|max:40', 'city' => 'required_without:address_id|max:40', 'county' => 'required_without_all:address_id,state|max:40', From 91f262327db779890f3a43b46ccd548b676d1cc3 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Fri, 22 Jan 2021 09:49:57 +0000 Subject: [PATCH 060/152] Apply fixes from StyleCI (#355) Co-authored-by: Alec Ritson --- src/Http/Requests/Orders/StoreAddressRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Requests/Orders/StoreAddressRequest.php b/src/Http/Requests/Orders/StoreAddressRequest.php index ce9265632..61cffef93 100644 --- a/src/Http/Requests/Orders/StoreAddressRequest.php +++ b/src/Http/Requests/Orders/StoreAddressRequest.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Requests\Orders; -use GetCandy\Api\Http\Requests\FormRequest; use GetCandy\Api\Core\Addresses\Models\Address; +use GetCandy\Api\Http\Requests\FormRequest; class StoreAddressRequest extends FormRequest { From d974bb3c28467c484bb0109eabe81c8cabfdac6d Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 25 Jan 2021 15:17:00 +0000 Subject: [PATCH 061/152] Only cancel if full refund --- src/Core/Payments/Providers/Braintree.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Core/Payments/Providers/Braintree.php b/src/Core/Payments/Providers/Braintree.php index 309662070..2e9092854 100644 --- a/src/Core/Payments/Providers/Braintree.php +++ b/src/Core/Payments/Providers/Braintree.php @@ -285,7 +285,11 @@ public function refund($token, $amount, $description) { $result = $this->gateway->transaction()->refund($token, $amount / 100); - if (! $result->success) { + $transactionModel = Transaction::where('transaction_id', '=', $token)->first(); + + $fullRefund = $amount == $transactionModel->amount; + + if (! $result->success && $fullRefund) { $error = collect($result->errors->forKey('transaction')->shallowAll())->first(); // Trying to refund a transaction that isn't settled. if ($error->code == '91506') { From c12c6c3af94e9cda1f706c212e238698f7b31c25 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:00:25 +0000 Subject: [PATCH 062/152] Add nullable to fields --- src/Core/Addresses/Actions/CreateAddressAction.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/Addresses/Actions/CreateAddressAction.php b/src/Core/Addresses/Actions/CreateAddressAction.php index 7abb7815b..f5c8d077c 100644 --- a/src/Core/Addresses/Actions/CreateAddressAction.php +++ b/src/Core/Addresses/Actions/CreateAddressAction.php @@ -43,12 +43,12 @@ public function rules() 'salutation' => 'string', 'firstname' => 'required|string', 'lastname' => 'required|string', - 'company_name' => 'string', + 'company_name' => 'nullable|string', 'email' => 'email', 'phone' => 'numeric', 'address' => 'required|string', - 'address_two' => 'string', - 'address_three' => 'string', + 'address_two' => 'nullable|string', + 'address_three' => 'nullable|string', 'city' => 'required|string', 'state' => 'required|string', 'postal_code' => 'required|string', From 9f9d20731d32dbb00db8cf6f663ad01ff0ca74d5 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:00:50 +0000 Subject: [PATCH 063/152] Add payment provider actions --- routes/api.client.php | 5 +- .../Payments/Actions/FetchPaymentProvider.php | 69 +++++++++++++++++++ .../Payments/Actions/FetchPaymentTypes.php | 54 +++++++++++++++ src/Core/Payments/Actions/FetchProviders.php | 51 ++++++++++++++ 4 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 src/Core/Payments/Actions/FetchPaymentProvider.php create mode 100644 src/Core/Payments/Actions/FetchPaymentTypes.php create mode 100644 src/Core/Payments/Actions/FetchProviders.php diff --git a/routes/api.client.php b/routes/api.client.php index a5132a591..b9c28605b 100644 --- a/routes/api.client.php +++ b/routes/api.client.php @@ -107,8 +107,9 @@ */ $router->post('payments/3d-secure', 'Payments\PaymentController@validateThreeD'); $router->get('payments/provider', 'Payments\PaymentController@provider'); -$router->get('payments/providers', 'Payments\PaymentController@providers'); -$router->get('payments/types', 'Payments\PaymentTypeController@index'); +$router->get('payments/providers', '\GetCandy\Api\Core\Payments\Actions\FetchProviders'); +$router->get('payments/providers/{handle}', '\GetCandy\Api\Core\Payments\Actions\FetchPaymentProvider'); +$router->get('payments/types', '\GetCandy\Api\Core\Payments\Actions\FetchPaymentTypes'); /* * Routes diff --git a/src/Core/Payments/Actions/FetchPaymentProvider.php b/src/Core/Payments/Actions/FetchPaymentProvider.php new file mode 100644 index 000000000..1a20a4295 --- /dev/null +++ b/src/Core/Payments/Actions/FetchPaymentProvider.php @@ -0,0 +1,69 @@ +order_id ? $orders->id($this->order_id)->first() : null; + + try { + $provider = $payments->with($handle)->order($order); + } catch (\InvalidArgumentException $e) { + return null; + } + + return $provider; + } + + /** + * Returns the response from the action. + * + * @param \GetCandy\Api\Core\Addresses\Models\Address $result + * @param \Illuminate\Http\Request $request + * + * @return json + */ + public function response($result, $request) + { + if (!$result) { + return $this->errorNotFound(); + } + return new PaymentProviderResource($result); + } +} diff --git a/src/Core/Payments/Actions/FetchPaymentTypes.php b/src/Core/Payments/Actions/FetchPaymentTypes.php new file mode 100644 index 000000000..87317038f --- /dev/null +++ b/src/Core/Payments/Actions/FetchPaymentTypes.php @@ -0,0 +1,54 @@ +json([], 204); + } +} From 6183a384b5ddae63f0ad274a16b986937b269060 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:01:06 +0000 Subject: [PATCH 064/152] Add action to rebuild tree --- src/Core/Categories/Actions/RebuildTree.php | 55 +++++++++++++++++++ .../Commands/RebuildTreeCommand.php | 48 ++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 src/Core/Categories/Actions/RebuildTree.php create mode 100644 src/Core/Categories/Commands/RebuildTreeCommand.php diff --git a/src/Core/Categories/Actions/RebuildTree.php b/src/Core/Categories/Actions/RebuildTree.php new file mode 100644 index 000000000..20a09d978 --- /dev/null +++ b/src/Core/Categories/Actions/RebuildTree.php @@ -0,0 +1,55 @@ +get()->map(function ($category) { + return $this->map($category); + }); + + // Pass false as we don't want to delete any drafts. + Category::rebuildTree($categories->toArray(), false); + } + + protected function map($category) + { + return [ + 'id' => $category->id, + 'children' => $category->children->map(function ($category) { + return $this->map($category); + })->toArray() + ]; + } +} diff --git a/src/Core/Categories/Commands/RebuildTreeCommand.php b/src/Core/Categories/Commands/RebuildTreeCommand.php new file mode 100644 index 000000000..2ab84a1c8 --- /dev/null +++ b/src/Core/Categories/Commands/RebuildTreeCommand.php @@ -0,0 +1,48 @@ + Date: Thu, 11 Feb 2021 13:01:18 +0000 Subject: [PATCH 065/152] Add some maintenance migrations --- ...2_05_093543_add_company_name_to_orders.php | 32 +++++++++++++++++++ ...2_remove_country_column_from_addresses.php | 28 ++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 database/migrations/2021_02_05_093543_add_company_name_to_orders.php create mode 100644 database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php diff --git a/database/migrations/2021_02_05_093543_add_company_name_to_orders.php b/database/migrations/2021_02_05_093543_add_company_name_to_orders.php new file mode 100644 index 000000000..9fcbbff7e --- /dev/null +++ b/database/migrations/2021_02_05_093543_add_company_name_to_orders.php @@ -0,0 +1,32 @@ +dropColumn('company_name'); + $table->string('billing_company_name')->after('billing_email')->nullable()->index(); + $table->string('shipping_company_name')->after('shipping_email')->nullable()->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('orders', function (Blueprint $table) { + $table->string('company_name')->nullable(); + $table->dropIfExists('billing_company_name'); + $table->dropIfExists('shipping_company_name'); + }); + } +} diff --git a/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php b/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php new file mode 100644 index 000000000..d9e16c2e5 --- /dev/null +++ b/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php @@ -0,0 +1,28 @@ +dropColumn('country'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('addresses', function (Blueprint $table) { + $table->string('country'); + }); + } +} From bd0d189bfb3f5e3ac1534d929ab3ade4b306a9a3 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:01:30 +0000 Subject: [PATCH 066/152] Typehint request --- .../Controllers/Payments/PaymentController.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Http/Controllers/Payments/PaymentController.php b/src/Http/Controllers/Payments/PaymentController.php index ea2eded77..eb9b31a1a 100644 --- a/src/Http/Controllers/Payments/PaymentController.php +++ b/src/Http/Controllers/Payments/PaymentController.php @@ -3,21 +3,22 @@ namespace GetCandy\Api\Http\Controllers\Payments; use GetCandy; -use GetCandy\Api\Core\Payments\Exceptions\AlreadyRefundedException; -use GetCandy\Api\Core\Payments\Exceptions\TransactionAmountException; -use GetCandy\Api\Core\Payments\Models\Transaction; +use Illuminate\Http\Request; use GetCandy\Api\Http\Controllers\BaseController; -use GetCandy\Api\Http\Requests\Payments\RefundRequest; -use GetCandy\Api\Http\Requests\Payments\ValidateThreeDRequest; +use GetCandy\Api\Core\Payments\Models\Transaction; use GetCandy\Api\Http\Requests\Payments\VoidRequest; use GetCandy\Api\Http\Resources\Orders\OrderResource; +use GetCandy\Api\Http\Requests\Payments\RefundRequest; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use GetCandy\Api\Http\Requests\Payments\ValidateThreeDRequest; use GetCandy\Api\Http\Resources\Payments\PaymentProviderResource; use GetCandy\Api\Http\Resources\Transactions\TransactionResource; -use Illuminate\Database\Eloquent\ModelNotFoundException; +use GetCandy\Api\Core\Payments\Exceptions\AlreadyRefundedException; +use GetCandy\Api\Core\Payments\Exceptions\TransactionAmountException; class PaymentController extends BaseController { - public function provider() + public function provider(Request $request) { return new PaymentProviderResource( GetCandy::payments()->getProvider() From 067beb755174b0b983240a044daa64ac67ee3b9d Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:01:42 +0000 Subject: [PATCH 067/152] Register the rebuild tree command --- src/Providers/CategoryServiceProvider.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Providers/CategoryServiceProvider.php b/src/Providers/CategoryServiceProvider.php index 08a547d4d..c7ac4b461 100644 --- a/src/Providers/CategoryServiceProvider.php +++ b/src/Providers/CategoryServiceProvider.php @@ -3,13 +3,14 @@ namespace GetCandy\Api\Providers; use Drafting; -use GetCandy\Api\Core\Categories\Drafting\CategoryDrafter; +use Versioning; +use Illuminate\Support\ServiceProvider; use GetCandy\Api\Core\Categories\Models\Category; -use GetCandy\Api\Core\Categories\Observers\CategoryObserver; +use GetCandy\Api\Core\Categories\Commands\RebuildTreeCommand; +use GetCandy\Api\Core\Categories\Drafting\CategoryDrafter; use GetCandy\Api\Core\Categories\Services\CategoryService; +use GetCandy\Api\Core\Categories\Observers\CategoryObserver; use GetCandy\Api\Core\Categories\Versioning\CategoryVersioner; -use Illuminate\Support\ServiceProvider; -use Versioning; class CategoryServiceProvider extends ServiceProvider { @@ -28,5 +29,11 @@ public function boot() }); Category::observe(CategoryObserver::class); + + if ($this->app->runningInConsole()) { + $this->commands([ + RebuildTreeCommand::class, + ]); + } } } From be03b26015a6959de2c29a06a296e4715d093582 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:02:17 +0000 Subject: [PATCH 068/152] Run drafter in a transaction --- .../Products/ProductController.php | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 6c2ab2968..286c1e2bf 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -2,28 +2,29 @@ namespace GetCandy\Api\Http\Controllers\Products; +use Hashids; use Drafting; use GetCandy; -use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; -use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\ProductCriteria; -use GetCandy\Api\Core\Products\Services\ProductService; -use GetCandy\Api\Exceptions\InvalidLanguageException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Exceptions\InvalidLanguageException; use GetCandy\Api\Http\Requests\Products\CreateRequest; use GetCandy\Api\Http\Requests\Products\DeleteRequest; -use GetCandy\Api\Http\Requests\Products\DuplicateRequest; use GetCandy\Api\Http\Requests\Products\UpdateRequest; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; -use GetCandy\Api\Http\Resources\Products\ProductResource; -use Hashids; +use GetCandy\Api\Core\Products\Services\ProductService; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Illuminate\Http\Request; +use GetCandy\Api\Http\Requests\Products\DuplicateRequest; +use GetCandy\Api\Http\Resources\Products\ProductResource; use Symfony\Component\HttpKernel\Exception\HttpException; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; +use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; class ProductController extends BaseController { @@ -109,7 +110,9 @@ public function publishDraft($id, Request $request) } $product = $this->service->findById($id[0], [], true); - Drafting::with('products')->publish($product); + DB::transaction(function () use ($product) { + Drafting::with('products')->publish($product); + }); return new ProductResource($product->load($this->parseIncludes($request->include))); } From 22b51f448dd05d68bd51131ffcc56521851160f5 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:04:13 +0000 Subject: [PATCH 069/152] Routing fixes --- src/Core/Routes/Actions/CreateRoute.php | 2 +- src/Core/Routes/Actions/FetchRoute.php | 13 ++++--- src/Core/Routes/Actions/UpdateRoute.php | 34 +++++++++++++------ .../Controllers/Orders/OrderController.php | 1 - 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/Core/Routes/Actions/CreateRoute.php b/src/Core/Routes/Actions/CreateRoute.php index 6d172b36f..63d2b7763 100644 --- a/src/Core/Routes/Actions/CreateRoute.php +++ b/src/Core/Routes/Actions/CreateRoute.php @@ -32,7 +32,7 @@ public function rules() 'slug' => 'required|unique_with:routes,path,'.$this->path, 'path' => 'unique_with:routes,slug,'.$this->slug, 'element' => 'required', - 'lang' => 'required|string', + 'locale' => 'required|string', 'default' => 'boolean', 'redirect' => 'boolean', ]; diff --git a/src/Core/Routes/Actions/FetchRoute.php b/src/Core/Routes/Actions/FetchRoute.php index 4b093b57f..23452f686 100644 --- a/src/Core/Routes/Actions/FetchRoute.php +++ b/src/Core/Routes/Actions/FetchRoute.php @@ -32,6 +32,7 @@ public function rules(): array return [ 'id' => 'integer|required_without_all:encoded_id,search', 'encoded_id' => 'string|hashid_is_valid:'.Route::class.'|required_without_all:id,search', + 'draft' => 'nullable', 'search' => 'required_without_all:encoded_id,id', ]; } @@ -49,14 +50,18 @@ public function handle() if (! $this->id) { $query = Route::getQuery(); - return $this->compileSearchQuery($query, $this->search)->first(); } + $route = Route::with($this->resolveEagerRelations()) + ->withCount($this->resolveRelationCounts()); + + if ($this->draft) { + $route = $route->withDrafted(); + } + try { - return Route::with($this->resolveEagerRelations()) - ->withCount($this->resolveRelationCounts()) - ->findOrFail($this->id); + return $route->findOrFail($this->id); } catch (ModelNotFoundException $e) { if (! $this->runningAs('controller')) { throw $e; diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index 474efd8be..0aa569e37 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -2,13 +2,17 @@ namespace GetCandy\Api\Core\Routes\Actions; -use GetCandy\Api\Core\Foundation\Actions\DecodeId; +use Illuminate\Validation\Rule; +use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Foundation\Actions\DecodeId; +use GetCandy\Api\Core\Routes\Resources\RouteResource; class UpdateRoute extends AbstractAction { + protected $route; + /** * Determine if the user is authorized to make this action. * @@ -26,14 +30,27 @@ public function authorize() */ public function rules(): array { - $routeId = DecodeId::run([ + $this->route = FetchRoute::run([ 'encoded_id' => $this->encoded_id, - 'model' => Route::class, + 'draft' => true, ]); return [ - 'slug' => 'required|unique_with:routes,path,'.$this->path.','.$routeId, - 'path' => 'unique_with:routes,slug,'.$this->slug.','.$routeId, + 'slug' => [ + 'required', + function ($attribute, $value, $fail) { + $ids = [ + $this->route->id, + ]; + if ($this->route->publishedParent) { + $ids[] = $this->route->publishedParent->id; + } + $result = DB::table('routes')->wherePath($this->path)->whereSlug($value)->whereNotIn('id', $ids)->exists(); + if ($result) { + $fail(); + } + }, + ], 'lang' => 'nullable|string', 'description' => 'nullable|string', 'default' => 'boolean', @@ -48,10 +65,7 @@ public function rules(): array */ public function handle() { - $route = $this->delegateTo(FetchRoute::class); - $route->update($this->validated()); - - return $route; + return $this->route->update($this->validated()); } /** diff --git a/src/Http/Controllers/Orders/OrderController.php b/src/Http/Controllers/Orders/OrderController.php index 8226ed3a0..241b72cf3 100644 --- a/src/Http/Controllers/Orders/OrderController.php +++ b/src/Http/Controllers/Orders/OrderController.php @@ -213,7 +213,6 @@ public function process( ->customerReference($request->customer_reference) ->meta($request->meta ?? []) ->notes($request->notes) - ->companyName($request->company_name) ->payload($request->data ?: []) ->resolve(); From 9edf949b57529228375178aa611e46be35d4e01d Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:04:34 +0000 Subject: [PATCH 070/152] Use `include` for current user --- src/Core/Users/Actions/FetchCurrentUser.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Core/Users/Actions/FetchCurrentUser.php b/src/Core/Users/Actions/FetchCurrentUser.php index 067ba1d65..1e8c907af 100644 --- a/src/Core/Users/Actions/FetchCurrentUser.php +++ b/src/Core/Users/Actions/FetchCurrentUser.php @@ -34,10 +34,7 @@ public function rules() */ public function handle() { - return $this->user()->load(array_merge( - $this->resolveEagerRelations(), - ['addresses.country', 'roles.permissions', 'customer', 'savedBaskets.basket.lines'] - )); + return $this->user()->load($this->resolveEagerRelations()); } /** From 3324b49d372dd1103f7192bdac7aea241822c9a8 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:04:43 +0000 Subject: [PATCH 071/152] Init action --- src/Core/Users/Actions/FetchUserAddresses.php | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/Core/Users/Actions/FetchUserAddresses.php diff --git a/src/Core/Users/Actions/FetchUserAddresses.php b/src/Core/Users/Actions/FetchUserAddresses.php new file mode 100644 index 000000000..23b827341 --- /dev/null +++ b/src/Core/Users/Actions/FetchUserAddresses.php @@ -0,0 +1,65 @@ +user_id) { + return $this->user()->can('manage-users'); + } + return $this->user(); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'user_id' => 'nullable', + ]; + } + + /** + * Execute the action and return a result. + * + * @return mixed + */ + public function handle() + { + if ($this->user_id) { + return FetchUser::run([ + 'encoded_id' => $this->user_id, + 'include' => 'addresses.country', + ])->addresses; + } + return $this->user()->load('addresses.country')->addresses; + } + + /** + * Returns the response from the action. + * + * @param $result + * @param \Illuminate\Http\Request $request + * @return \GetCandy\Api\Core\Users\Resources\UserResource + */ + public function response($result, $request) + { + return new AddressCollection($result); + } +} From ddf3db458573e1486b58ae27f36c01e050e4376c Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:05:01 +0000 Subject: [PATCH 072/152] Update to support multiple pipe values --- .../Elasticsearch/Filters/TextFilter.php | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php index 1b05b52dd..cfb9c6da8 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php @@ -21,11 +21,22 @@ public function getQuery() $value = explode('|', $value); } if (is_array($value)) { - $range = new Range($this->field.'.filter', [ - 'gte' => (int) $value[0], - 'lte' => $value[1] == '*' ? null : (int) $value[1], - ]); - $filter->addShould($range); + // If the first value is not numeric, then we assume we are + // matching across multiple text values for a field, not a range. + if (!is_numeric($value[0])) { + foreach ($value as $val) { + $match = new Match; + $match->setFieldAnalyzer($this->field.'.filter', 'keyword'); + $match->setFieldQuery($this->field.'.filter', $val); + $filter->addShould($match); + } + } else { + $range = new Range($this->field.'.filter', [ + 'gte' => (int) $value[0], + 'lte' => $value[1] == '*' ? null : (int) $value[1], + ]); + $filter->addShould($range); + } } else { $match = new Match; $match->setFieldAnalyzer($this->field.'.filter', 'keyword'); From e0d6663ef19f2d04e227fc32e9be31d150326306 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:05:15 +0000 Subject: [PATCH 073/152] Add stripe intents driver --- src/Core/Payments/PaymentManager.php | 19 ++- src/Core/Payments/Providers/StripeIntents.php | 136 ++++++++++++++++++ 2 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 src/Core/Payments/Providers/StripeIntents.php diff --git a/src/Core/Payments/PaymentManager.php b/src/Core/Payments/PaymentManager.php index 81cbb604a..aec572499 100644 --- a/src/Core/Payments/PaymentManager.php +++ b/src/Core/Payments/PaymentManager.php @@ -2,11 +2,12 @@ namespace GetCandy\Api\Core\Payments; -use GetCandy\Api\Core\Payments\Providers\Braintree; -use GetCandy\Api\Core\Payments\Providers\Offline; +use Illuminate\Support\Manager; use GetCandy\Api\Core\Payments\Providers\PayPal; +use GetCandy\Api\Core\Payments\Providers\Offline; use GetCandy\Api\Core\Payments\Providers\SagePay; -use Illuminate\Support\Manager; +use GetCandy\Api\Core\Payments\Providers\Braintree; +use GetCandy\Api\Core\Payments\Providers\StripeIntents; class PaymentManager extends Manager implements PaymentContract { @@ -45,6 +46,18 @@ public function createSagepayDriver() ); } + /** + * Create the sagepay driver. + * + * @return \GetCandy\Api\Core\Payments\Providers\SagePay + */ + public function createStripeIntentsDriver() + { + return $this->buildProvider( + StripeIntents::class + ); + } + /** * Create the sagepay driver. * diff --git a/src/Core/Payments/Providers/StripeIntents.php b/src/Core/Payments/Providers/StripeIntents.php new file mode 100644 index 000000000..33d98aa8f --- /dev/null +++ b/src/Core/Payments/Providers/StripeIntents.php @@ -0,0 +1,136 @@ +name; + } + + protected function setName($name) + { + $this->name = $name; + + return $this; + } + + public function validate($token) + { + return true; + } + + public function getClientToken() + { + $client = $this->getClient(); + + // Do we already have a payment intent for this order? + $meta = $this->order->meta; + + $paymentIntentToken = $meta['stripe_payment_intent'] ?? null; + + if (!$paymentIntentToken) { + $intent = $client->paymentIntents->create([ + 'amount' => $this->order->order_total, + 'currency' => $this->order->currency, + // Verify your integration in this guide by including this parameter + 'metadata' => ['integration_check' => 'accept_a_payment'], + ]); + $meta['stripe_payment_intent'] = $intent->id; + $this->order->update([ + 'meta' => $meta + ]); + return $intent->client_secret; + } else { + $intent = $client->paymentIntents->retrieve( + $paymentIntentToken, + [] + ); + if ($intent->amount != $this->order->order_total) { + $client->paymentIntents->update( + $intent->id, + [ + 'amount' => $this->order->order_total + ] + ); + } + } + + return $intent->client_secret; + } + + public function updateTransaction($transaction) + { + return true; + } + + public function getClient() + { + return new StripeClient(config('services.stripe.key')); + } + + public function charge() + { + $order = $this->order; + $meta = $this->order->meta; + $paymentIntentToken = $meta['stripe_payment_intent'] ?? null; + + if (!$paymentIntentToken) { + return new PaymentResponse(false); + } + + $paymentIntent = $this->getPaymentIntent($paymentIntentToken); + + if ($paymentIntent->status != 'succeeded') { + return new PaymentResponse(true); + } + + $paymentMethod = $this->getClient()->paymentMethods->retrieve($paymentIntent->payment_method); + $threedchecks = $paymentMethod->card->checks; + + $transaction = new Transaction; + $transaction->amount = $paymentIntent->amount; + $transaction->order_id = $this->order->id; + $transaction->transaction_id = $paymentIntent->id; + $transaction->success = true; + $transaction->last_four = $paymentMethod->card->last4; + $transaction->merchant = 'Stripe'; + $transaction->driver = 'stripe-intents'; + $transaction->provider = 'Stripe'; + $transaction->card_type = $paymentMethod->card->brand; + $transaction->address_matched = $threedchecks->address_line1_check === 'pass'; + $transaction->cvc_matched = $threedchecks->cvc_check === 'pass'; + $transaction->threed_secure = $paymentMethod->card->three_d_secure_usage->supported ?? false; + $transaction->postcode_matched = $threedchecks->address_postal_code_check === 'pass'; + $transaction->status = $paymentIntent->status; + $transaction->save(); + // Get the payment method + + $response = new PaymentResponse(true, 'Payment Success', []); + return $response->transaction($transaction); + } + + protected function getPaymentIntent($intentId) + { + return $this->getClient()->paymentIntents->retrieve( + $intentId, + [] + ); + } + + public function refund($token, $amount, $description) + { + return true; + } +} From 4dca575cce2d1eeef326d44a3860f021fd8de5bd Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:05:39 +0000 Subject: [PATCH 074/152] Fixes and remove generic company_name field --- .../Orders/Factories/OrderProcessingFactory.php | 15 --------------- src/Core/Orders/Models/Order.php | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Core/Orders/Factories/OrderProcessingFactory.php b/src/Core/Orders/Factories/OrderProcessingFactory.php index 78963f788..4dcd3e9ae 100644 --- a/src/Core/Orders/Factories/OrderProcessingFactory.php +++ b/src/Core/Orders/Factories/OrderProcessingFactory.php @@ -73,13 +73,6 @@ class OrderProcessingFactory implements OrderProcessingFactoryInterface */ protected $meta; - /** - * The company name for the order. - * - * @var string - */ - protected $companyName; - /** * The customer reference. * @@ -184,13 +177,6 @@ public function customerReference($ref = null) return $this; } - public function companyName($name = null) - { - $this->companyName = $name; - - return $this; - } - /** * Set the value of order. * @@ -223,7 +209,6 @@ public function resolve() $this->order->type = $this->type ?: $driver->getName(); $this->order->meta = array_merge($this->order->meta ?? [], $this->meta ?? []); - $this->order->company_name = $this->companyName; $this->order->save(); diff --git a/src/Core/Orders/Models/Order.php b/src/Core/Orders/Models/Order.php index 019315579..78f8c07be 100644 --- a/src/Core/Orders/Models/Order.php +++ b/src/Core/Orders/Models/Order.php @@ -233,7 +233,7 @@ public function getTotalAttribute() public function getDetails($type) { return collect($this->attributes)->filter(function ($value, $key) use ($type) { - return strpos($key, $type.'_') === 0; + return strpos($key, $type.'_') === 0 && $key != 'shipping_method'; })->mapWithKeys(function ($item, $key) use ($type) { $newkey = str_replace($type.'_', '', $key); From a10b8619d6742a28d4db8ceb967f61dd48413549 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:05:56 +0000 Subject: [PATCH 075/152] Check for settings asset source --- src/Core/Assets/Drivers/Image.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Assets/Drivers/Image.php b/src/Core/Assets/Drivers/Image.php index 3251a1ccb..11add81e0 100644 --- a/src/Core/Assets/Drivers/Image.php +++ b/src/Core/Assets/Drivers/Image.php @@ -21,7 +21,7 @@ public function process(array $data, $model = null) { $assetSources = GetCandy::assetSources(); - if ($model) { + if ($model && !empty($model->settings['asset_source'])) { $source = GetCandy::assetSources()->getByHandle($model->settings['asset_source']); } else { $source = $assetSources->getDefaultRecord(); From 7677d6da38eadcdb9efa04e8084a4b608c1f43ba Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:06:10 +0000 Subject: [PATCH 076/152] Add endpoint for user addresses --- openapi/user/paths/user.addresses.yaml | 13 +++++++++++++ openapi/user/responses/UserAddressesResponse.yaml | 8 ++++++++ routes/api.php | 3 +++ 3 files changed, 24 insertions(+) create mode 100644 openapi/user/paths/user.addresses.yaml create mode 100644 openapi/user/responses/UserAddressesResponse.yaml diff --git a/openapi/user/paths/user.addresses.yaml b/openapi/user/paths/user.addresses.yaml new file mode 100644 index 000000000..667a8e392 --- /dev/null +++ b/openapi/user/paths/user.addresses.yaml @@ -0,0 +1,13 @@ +get: + summary: Get user addresses + tags: + - User + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/UserAddressesResponse.yaml' + operationId: get-user-addresses + description: This will return any addresses associated to the current user diff --git a/openapi/user/responses/UserAddressesResponse.yaml b/openapi/user/responses/UserAddressesResponse.yaml new file mode 100644 index 000000000..bc2c4ad39 --- /dev/null +++ b/openapi/user/responses/UserAddressesResponse.yaml @@ -0,0 +1,8 @@ +title: UserAddressesResponse +type: object +properties: + data: + type: array + items: + $ref: '../../addresses/models/Address.yaml' +description: '' diff --git a/routes/api.php b/routes/api.php index 5e4fafef9..5d44639bf 100644 --- a/routes/api.php +++ b/routes/api.php @@ -315,10 +315,13 @@ $router->get('users/fields', '\GetCandy\Api\Core\Users\Actions\FetchUserFields'); $router->get('users/current', '\GetCandy\Api\Core\Users\Actions\FetchCurrentUser'); + $router->get('user/addresses', '\GetCandy\Api\Core\Users\Actions\FetchUserAddresses'); + $router->get('users', '\GetCandy\Api\Core\Users\Actions\FetchUsers'); $router->get('users/{encoded_id}', '\GetCandy\Api\Core\Users\Actions\FetchUser'); $router->put('users/{encoded_id}', '\GetCandy\Api\Core\Users\Actions\UpdateUser'); + /* * Reusable payments */ From f988fddd9821a59098a5881dac6bdce1f38d7a52 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:06:17 +0000 Subject: [PATCH 077/152] Update spec --- openapi/openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 2dfbd86ab..a258ecb13 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -273,6 +273,8 @@ paths: $ref: './users/paths/users.id.yaml' '/users/fields': $ref: './users/paths/users.fields.yaml' + '/user/addresses': + $ref: './user/paths/user.addresses.yaml' '/users/current': $ref: './users/paths/users.current.yaml' '/versions/{modelId}/restore': From e57d63925b0b34905b4eee74f07774c72d782604 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:06:35 +0000 Subject: [PATCH 078/152] Add timestamps --- src/Core/Shipping/Models/ShippingMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Shipping/Models/ShippingMethod.php b/src/Core/Shipping/Models/ShippingMethod.php index df221aa2f..1510b7c7c 100644 --- a/src/Core/Shipping/Models/ShippingMethod.php +++ b/src/Core/Shipping/Models/ShippingMethod.php @@ -43,7 +43,7 @@ protected static function boot() public function zones() { - return $this->belongsToMany(ShippingZone::class, 'shipping_method_zones'); + return $this->belongsToMany(ShippingZone::class, 'shipping_method_zones')->withTimestamps(); } public function users() From 941abfb292dcfa95a21975fae6895746f7f3293b Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:06:46 +0000 Subject: [PATCH 079/152] Shipping updates --- .../Services/ShippingMethodService.php | 26 ++++++++++++++----- .../Shipping/Services/ShippingZoneService.php | 12 ++++++--- .../Shipping/ShippingZoneResource.php | 2 ++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/Core/Shipping/Services/ShippingMethodService.php b/src/Core/Shipping/Services/ShippingMethodService.php index 753a7b9b0..de8ff30f4 100644 --- a/src/Core/Shipping/Services/ShippingMethodService.php +++ b/src/Core/Shipping/Services/ShippingMethodService.php @@ -3,12 +3,14 @@ namespace GetCandy\Api\Core\Shipping\Services; use GetCandy; -use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; -use GetCandy\Api\Core\Baskets\Services\BasketService; +use Illuminate\Pipeline\Pipeline; use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Shipping\Models\ShippingMethod; +use GetCandy\Api\Core\Channels\Models\Channel; use GetCandy\Api\Core\Shipping\ShippingCalculator; -use Illuminate\Pipeline\Pipeline; +use GetCandy\Api\Core\Shipping\Models\ShippingZone; +use GetCandy\Api\Core\Baskets\Services\BasketService; +use GetCandy\Api\Core\Shipping\Models\ShippingMethod; +use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; class ShippingMethodService extends BaseService { @@ -49,6 +51,12 @@ public function create(array $data) $shipping->channels()->sync( $this->getChannelMapping($data['channels']['data']) ); + } else { + $shipping->channels()->sync(Channel::select('id')->get()->mapWithKeys(function ($c) { + return [$c->id => [ + 'published_at' => null, + ]]; + })->toArray()); } event(new AttributableSavedEvent($shipping)); @@ -69,12 +77,18 @@ public function update($id, array $data) $shipping->attribute_data = $data['attribute_data']; $shipping->type = $data['type']; - if (! empty($data['channels']['data'])) { + if (! empty($data['channels'])) { $shipping->channels()->sync( - $this->getChannelMapping($data['channels']['data']) + $this->getChannelMapping($data['channels']) ); } + $shipping->zones()->sync( + collect($data['zones'] ?? [])->map(function ($zone) { + return (new ShippingZone)->decodeId($zone['id']); + }) + ); + $shipping->save(); return $shipping; diff --git a/src/Core/Shipping/Services/ShippingZoneService.php b/src/Core/Shipping/Services/ShippingZoneService.php index c12cc9afa..573b7471c 100644 --- a/src/Core/Shipping/Services/ShippingZoneService.php +++ b/src/Core/Shipping/Services/ShippingZoneService.php @@ -4,6 +4,8 @@ use GetCandy; use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Countries\Models\Country; +use GetCandy\Api\Core\Foundation\Actions\DecodeIds; use GetCandy\Api\Core\Shipping\Models\ShippingZone; class ShippingZoneService extends BaseService @@ -50,7 +52,7 @@ public function getByHashedId($id, $includes = null) $query = $this->model; if ($includes) { - $query->with($includes); + $query = $query->with($includes); } return $query->findOrFail($id); @@ -71,9 +73,11 @@ public function update($id, array $data) $shipping->countries()->detach(); if (! empty($data['countries'])) { - $shipping->countries()->attach( - GetCandy::countries()->getDecodedIds($data['countries']) - ); + $countryIds = DecodeIds::run([ + 'encoded_ids' => $data['countries'], + 'model' => Country::class + ]); + $shipping->countries()->sync($countryIds); } $shipping->save(); diff --git a/src/Http/Resources/Shipping/ShippingZoneResource.php b/src/Http/Resources/Shipping/ShippingZoneResource.php index 4b0c2d12d..86b0621f0 100644 --- a/src/Http/Resources/Shipping/ShippingZoneResource.php +++ b/src/Http/Resources/Shipping/ShippingZoneResource.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Http\Resources\Shipping; use GetCandy\Api\Http\Resources\AbstractResource; +use GetCandy\Api\Core\Countries\Resources\CountryCollection; class ShippingZoneResource extends AbstractResource { @@ -18,6 +19,7 @@ public function includes() { return [ 'regions' => new ShippingRegionCollection($this->whenLoaded('regions')), + 'countries' => new CountryCollection($this->whenLoaded('countries')), ]; } } From 315eaf91a1580473ecb73e65ae84b23edf5e0dbf Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:06:54 +0000 Subject: [PATCH 080/152] Fixes --- src/Core/Products/Models/ProductVariant.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index c83f5cc73..04849576d 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -8,10 +8,11 @@ use GetCandy\Api\Core\Taxes\Models\Tax; use GetCandy\Api\Core\Traits\HasAttributes; use GetCandy\Api\Core\Traits\Lockable; +use NeonDigital\Drafting\Draftable; class ProductVariant extends BaseModel { - use HasAttributes, Lockable; + use HasAttributes, Lockable, Draftable; /** * The Hashid connection name for enconding the id. @@ -26,6 +27,7 @@ class ProductVariant extends BaseModel * @var array */ protected $fillable = [ + 'asset_id', 'options', 'price', 'incoming', @@ -99,7 +101,10 @@ public function getOptionsAttribute($val) $values = []; $option_data = $this->product ? $this->product->option_data : []; - foreach (json_decode($val, true) as $option => $value) { + if (!is_array($val)) { + $val = json_decode($val, true); + } + foreach ($val as $option => $value) { if (! empty($data = $option_data[$option])) { $values[$option] = $data['options'][$value]['values'] ?? [ 'en' => null, From aaff3d20f6d4d9ae43c8a74655b5821814d7225b Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Feb 2021 13:07:06 +0000 Subject: [PATCH 081/152] Drafting refactoring --- .../Categories/Drafting/CategoryDrafter.php | 102 ++++++++------- src/Core/Events/ModelPublishedEvent.php | 9 +- .../Resources/PaymentProviderCollection.php | 28 +++++ .../Resources/PaymentProviderResource.php | 25 ++++ .../Resources/PaymentTypeCollection.php | 28 +++++ .../Resources/PaymentTypeResource.php | 18 +++ .../Actions/Drafting/UpdateAssets.php | 47 +++++++ .../Actions/Drafting/UpdateChannels.php | 48 +++++++ .../Actions/Drafting/UpdateCustomerGroups.php | 50 ++++++++ .../Drafting/UpdateProductAssociations.php | 66 ++++++++++ .../UpdateProductVariantCustomerPricing.php | 55 ++++++++ .../Drafting/UpdateProductVariantTiers.php | 54 ++++++++ .../Drafting/UpdateProductVariants.php | 70 +++++++++++ .../Actions/Drafting/UpdateRoutes.php | 52 ++++++++ src/Core/Products/Drafting/ProductDrafter.php | 117 ++++++++++++------ 15 files changed, 679 insertions(+), 90 deletions(-) create mode 100644 src/Core/Payments/Resources/PaymentProviderCollection.php create mode 100644 src/Core/Payments/Resources/PaymentProviderResource.php create mode 100644 src/Core/Payments/Resources/PaymentTypeCollection.php create mode 100644 src/Core/Payments/Resources/PaymentTypeResource.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateAssets.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateChannels.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateCustomerGroups.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateProductAssociations.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateProductVariantTiers.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateProductVariants.php create mode 100644 src/Core/Products/Actions/Drafting/UpdateRoutes.php diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 0c0826ae4..854e23e53 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -21,63 +21,73 @@ public function publish(Model $category) // Publish this category and remove the parent. $parent = $category->publishedParent; - // Get any current versions and assign them to this new category. - - foreach ($parent->versions as $version) { - $version->update([ - 'versionable_id' => $category->id, - ]); - } - // Create a version of the parent before we publish these changes - Versioning::with('categories')->create($parent, null, $category->id); - - // Move the activities onto the draft - if ($parent->activities) { - $parent->activities->each(function ($a) use ($category) { - $a->update(['subject_id' => $category->id]); - }); + Versioning::with('categories')->create($parent, null, $parent->id); + + $parent->attribute_data = $category->attribute_data; + $parent->sort = $category->sort; + $parent->layout_id = $category->layout_id; + $parent->save(); + + /** + * Here we go through any routes the draft has and if they have a published + * parent counterpart. We update it and then remove the draft route. + * + * If the parent doesn't exist then we reassign the new route to the published record. + */ + foreach ($category->routes as $route) { + if ($route->publishedParent) { + $route->publishedParent->update($route->toArray()); + $route->forceDelete(); + } else { + $route->update([ + 'element_id' => $parent->id + ]); + } } - // Activate any routes - $routeIds = $category->routes()->onlyDrafted()->get()->pluck('id')->toArray(); - - DB::table('routes') - ->whereIn('id', $routeIds) - ->update([ - 'drafted_at' => null, - ]); - - // Delete routes - $parent->routes()->delete(); - - // Move any direct children on to the published category - DB::transaction(function ($query) use ($parent, $category) { - $parent->children->each(function ($node) use ($category) { - $node->parent()->associate($category)->save(); - }); - }); - - $parent->update([ - '_lft' => null, - '_rgt' => null, + /** + * Go through the draft channels and update our parent. + */ + $channels = $category->channels->mapWithKeys(function ($channel) { + return [$channel->id => [ + 'published_at' => $channel->pivot->published_at + ]]; + })->toArray(); + $parent->channels()->sync($channels); + + $customerGroups = $category->customerGroups->mapWithKeys(function ($group) { + return [$group->id => [ + 'purchasable' => $group->pivot->purchasable, + 'visible' => $group->pivot->visible, + ]]; + })->toArray(); + + $parent->customerGroups()->sync($customerGroups); + + /** + * Go through and assign any products that are for the draft to the parent. + */ + $category->products()->update([ + 'category_id' => $parent->id ]); - $parent->refresh()->forceDelete(); - // $parent->refresh()->forceDelete(); - $category->drafted_at = null; - $category->save(); + // Fire off an event so plugins can update anything their side too. + event(new ModelPublishedEvent($category, $parent)); + + // // Update all products... + $parent = $parent->refresh(); - // Update all products... - if ($category->products->count()) { + if ($parent->products->count()) { IndexObjects::run([ - 'documents' => $category->products, + 'documents' => $parent->products, ]); } - event(new ModelPublishedEvent($category)); + $category->forceDelete(); + // event(new ModelPublishedEvent($category)); - return $category; + return $parent; } /** diff --git a/src/Core/Events/ModelPublishedEvent.php b/src/Core/Events/ModelPublishedEvent.php index 35f4e1c2d..b707e7257 100644 --- a/src/Core/Events/ModelPublishedEvent.php +++ b/src/Core/Events/ModelPublishedEvent.php @@ -9,7 +9,9 @@ class ModelPublishedEvent { use SerializesModels; - public $model; + public $draft; + + public $parent; /** * Create a new event instance. @@ -17,8 +19,9 @@ class ModelPublishedEvent * @param \Illuminate\Database\Eloquent\Model $model * @return void */ - public function __construct(Model $model) + public function __construct(Model $draft, Model $parent) { - $this->model = $model; + $this->draft = $draft; + $this->parent = $parent; } } diff --git a/src/Core/Payments/Resources/PaymentProviderCollection.php b/src/Core/Payments/Resources/PaymentProviderCollection.php new file mode 100644 index 000000000..dd3d975bf --- /dev/null +++ b/src/Core/Payments/Resources/PaymentProviderCollection.php @@ -0,0 +1,28 @@ + $this->collection, + ]; + } +} diff --git a/src/Core/Payments/Resources/PaymentProviderResource.php b/src/Core/Payments/Resources/PaymentProviderResource.php new file mode 100644 index 000000000..2b9943a62 --- /dev/null +++ b/src/Core/Payments/Resources/PaymentProviderResource.php @@ -0,0 +1,25 @@ + $this->resource->getName(), + ]; + + if (method_exists($this->resource, 'getClientToken')) { + $data['client_token'] = $this->resource->getClientToken(); + } + + if (method_exists($this->resource, 'getTokenExpiry')) { + $data['exires_at'] = $this->resource->getTokenExpiry(); + } + + return $data; + } +} diff --git a/src/Core/Payments/Resources/PaymentTypeCollection.php b/src/Core/Payments/Resources/PaymentTypeCollection.php new file mode 100644 index 000000000..20a9b8ae4 --- /dev/null +++ b/src/Core/Payments/Resources/PaymentTypeCollection.php @@ -0,0 +1,28 @@ + $this->collection, + ]; + } +} diff --git a/src/Core/Payments/Resources/PaymentTypeResource.php b/src/Core/Payments/Resources/PaymentTypeResource.php new file mode 100644 index 000000000..1ac0c4d0b --- /dev/null +++ b/src/Core/Payments/Resources/PaymentTypeResource.php @@ -0,0 +1,18 @@ + $this->encoded_id, + 'name' => $this->name, + 'handle' => $this->handle, + 'driver' => $this->driver, + ]; + } +} diff --git a/src/Core/Products/Actions/Drafting/UpdateAssets.php b/src/Core/Products/Actions/Drafting/UpdateAssets.php new file mode 100644 index 000000000..ce0a6b734 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateAssets.php @@ -0,0 +1,47 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Get the asset ids we want to sync up + $incomingAssetIds = $this->draft->assets->pluck('id')->toArray(); + $this->parent->assets()->sync($incomingAssetIds); + // Clean up on Aisle 4 + $this->draft->assets()->detach(); + + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateChannels.php b/src/Core/Products/Actions/Drafting/UpdateChannels.php new file mode 100644 index 000000000..1a065703a --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateChannels.php @@ -0,0 +1,48 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $channels = $this->draft->channels->mapWithKeys(function ($channel) { + return [$channel->id => [ + 'published_at' => $channel->pivot->published_at + ]]; + })->toArray(); + $this->parent->channels()->sync($channels); + + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateCustomerGroups.php b/src/Core/Products/Actions/Drafting/UpdateCustomerGroups.php new file mode 100644 index 000000000..1bfe2077b --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateCustomerGroups.php @@ -0,0 +1,50 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $customerGroups = $this->draft->customerGroups->mapWithKeys(function ($group) { + return [$group->id => [ + 'purchasable' => $group->pivot->purchasable, + 'visible' => $group->pivot->visible, + ]]; + })->toArray(); + + $this->parent->customerGroups()->sync($customerGroups); + + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateProductAssociations.php b/src/Core/Products/Actions/Drafting/UpdateProductAssociations.php new file mode 100644 index 000000000..f65a9fb23 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateProductAssociations.php @@ -0,0 +1,66 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'product' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductFamily + */ + public function handle() + { + $this->product->associations() + ->whereNotIn( + 'association_id', + $this->draft->associations->pluck('association_id')->toArray() + )->delete(); + + foreach ($this->draft->associations as $incoming) { + // Does this parent already have this association? + // If so we just need to update the group + $existing = $this->product->associations->first(function ($assoc) use ($incoming) { + return $assoc->association_id = $incoming->association_id; + }); + if ($existing) { + $existing->update($incoming->toArray()); + continue; + } + // If it doesn't exist, reassign the product_id + $incoming->update([ + 'product_id' => $this->product->id, + ]); + } + + return $this->product; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php b/src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php new file mode 100644 index 000000000..880c5b662 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php @@ -0,0 +1,55 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductVariant + */ + public function handle() + { + foreach ($this->draft->customerPricing as $incoming) { + $existing = $this->parent->customerPricing->first(function ($existing) use ($incoming) { + return $existing->customer_group_id === $incoming->customer_group_id; + }); + if ($existing) { + $existing->update($price->toArray()); + $incoming->forceDelete(); + continue; + } + $incoming->update([ + 'product_variant_id' => $this->parent->id + ]); + } + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateProductVariantTiers.php b/src/Core/Products/Actions/Drafting/UpdateProductVariantTiers.php new file mode 100644 index 000000000..d694fa1d6 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateProductVariantTiers.php @@ -0,0 +1,54 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductVariant + */ + public function handle() + { + foreach ($this->draft->tiers as $incoming) { + $existing = $this->parent->customerPricing->first(function ($existing) use ($incoming) { + return $existing->customer_group_id === $incoming->customer_group_id; + }); + if ($existing) { + $existing->update($price->toArray()); + $incoming->forceDelete(); + continue; + } + $incoming->update([ + 'product_variant_id' => $this->parent->id + ]); + } + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateProductVariants.php b/src/Core/Products/Actions/Drafting/UpdateProductVariants.php new file mode 100644 index 000000000..7319ff5e4 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateProductVariants.php @@ -0,0 +1,70 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'product' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductFamily + */ + public function handle() + { + $variants = $this->draft->variants()->onlyDrafted()->get(); + + foreach ($variants as $incoming) { + if ($incoming->publishedParent) { + $parent = $incoming->publishedParent; + $parent->update($incoming->toArray()); + + UpdateProductVariantCustomerPricing::run([ + 'draft' => $incoming, + 'parent' => $parent, + ]); + + UpdateProductVariantTiers::run([ + 'draft' => $incoming, + 'parent' => $parent, + ]); + + dd(1); + } else { + $incoming->update([ + 'product_id' => $this->product->id, + ]); + } + } + + return $this->product->refresh(); + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateRoutes.php b/src/Core/Products/Actions/Drafting/UpdateRoutes.php new file mode 100644 index 000000000..51b64a5e2 --- /dev/null +++ b/src/Core/Products/Actions/Drafting/UpdateRoutes.php @@ -0,0 +1,52 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->draft->routes as $route) { + if ($route->publishedParent) { + $route->publishedParent->update($route->toArray()); + $route->forceDelete(); + } else { + $route->update([ + 'element_id' => $this->parent->id + ]); + } + } + + return $this->parent; + } +} \ No newline at end of file diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index d473ec38c..a29f29793 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -3,11 +3,16 @@ namespace GetCandy\Api\Core\Products\Drafting; use DB; -use GetCandy\Api\Core\Products\Events\ProductCreatedEvent; -use GetCandy\Api\Core\Products\Models\Product; +use Versioning; use Illuminate\Database\Eloquent\Model; +use GetCandy\Api\Core\Products\Models\Product; use NeonDigital\Drafting\Interfaces\DrafterInterface; -use Versioning; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateAssets; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateRoutes; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateChannels; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateCustomerGroups; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateProductVariants; +use GetCandy\Api\Core\Products\Actions\Drafting\UpdateProductAssociations; class ProductDrafter implements DrafterInterface { @@ -20,50 +25,80 @@ public function publish(Model $product) { // Publish this product and remove the parent. $parent = $product->publishedParent; - // Get any current versions and assign them to this new product. - foreach ($parent->versions as $version) { - $version->update([ - 'versionable_id' => $product->id, - ]); - } + // Get any current versions and assign them to this new product. // Create a version of the parent before we publish these changes - Versioning::with('products')->create($parent, null, $product->id); - - // Move the activities onto the draft - $parent->activities->each(function ($a) use ($product) { - $a->update(['subject_id' => $product->id]); - }); + Versioning::with('products')->create($parent, null, $parent->id); - // Activate any product variants - $variantIds = $product->variants->pluck('id')->toArray(); - - DB::table('product_variants') - ->whereIn('id', $variantIds) - ->update([ - 'drafted_at' => null, - ]); - - // Activate any routes - $routeIds = $product->routes()->onlyDrafted()->get()->pluck('id')->toArray(); - - DB::table('routes') - ->whereIn('id', $routeIds) - ->update([ - 'drafted_at' => null, - ]); - - // Delete routes - // $parent->routes()->delete(); - - $parent->forceDelete(); + // Update any attributes etc + $parent->attribute_data = $product->attribute_data; + $parent->option_data = $product->option_data; + $parent->product_family_id = $product->product_family_id; + $parent->layout_id = $product->layout_id; + $parent->group_pricing = $product->group_pricing; - $product->drafted_at = null; - $product->save(); - - event(new ProductCreatedEvent($product)); + $parent->save(); + // Activate any product variants + UpdateProductVariants::run([ + 'draft' => $product, + 'product' => $parent, + ]); + + /** + * Go through the draft channels and update our parent. + */ + UpdateChannels::run([ + 'draft' => $product, + 'parent' => $parent, + ]); + UpdateCustomerGroups::run([ + 'draft' => $product, + 'parent' => $parent, + ]); + + /** + * Here we go through any routes the draft has and if they have a published + * parent counterpart. We update it and then remove the draft route. + * + * If the parent doesn't exist then we reassign the new route to the published record. + */ + UpdateRoutes::run([ + 'draft' => $product, + 'parent' => $parent, + ]); + + /** + * Go through any assets and sync with the parent. + */ + UpdateAssets::run([ + 'draft' => $product, + 'parent' => $parent, + ]); + + // Product Associations + // Delete any associations we don't have anymore... + UpdateProductAssociations::run([ + 'draft' => $product, + 'product' => $parent + ]); + + // Categories + $existingCategories = $parent->categories; + + // Sync product categories to the parent. + $parent->categories()->sync( + $product->categories->pluck('id') + ); + // Collections + $parent->collections()->sync( + $product->collections->pluck('id') + ); + // Delete the draft we had. + $product->forceDelete(); + + dd('End'); return $product; } From d9f9e5148f9400ae17076eb339910952735f3c08 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:07:48 +0000 Subject: [PATCH 082/152] Fix wildcard search --- .../Drivers/Elasticsearch/Actions/Searching/FetchTerm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php index 5df386457..ac99cbaa5 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/FetchTerm.php @@ -64,7 +64,7 @@ public function handle() } } - $skuTerm = strtolower($this->text); + $skuTerm = strtolower($this->term); $wildcard = new Wildcard('sku.lowercase', "*{$skuTerm}*"); $disMaxQuery->addQuery($wildcard); From 2968148ed95014bc1387d11404839a96450d3cb1 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:08:06 +0000 Subject: [PATCH 083/152] Search optimisations --- .../Drivers/Elasticsearch/Actions/Searching/Search.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index bf3ad63cc..b19075221 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -62,19 +62,16 @@ public function rules() */ public function handle() { - $this->set('search_type', $this->search_type ?: 'products'); + $this->set('search_type', $this->search_type ? Str::plural($this->search_type) : 'products'); if (! $this->index) { $prefix = config('getcandy.search.index_prefix'); $language = app()->getLocale(); - - $index = Str::plural($this->search_type); - $this->set('index', "{$prefix}_{$index}_{$language}"); + $this->set('index', "{$prefix}_{$this->search_type}_{$language}"); } $this->filters = $this->filters ? collect(explode(',', $this->filters))->mapWithKeys(function ($filter) { [$label, $value] = explode(':', $filter); - return [$label => $value]; })->toArray() : []; @@ -177,6 +174,8 @@ public function handle() ], ]); + $query = $query->setSource(false)->setStoredFields([]); + $search = new ElasticaSearch($client); return $search @@ -217,6 +216,7 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); + $resource = ProductCollection::class; if ($this->search_type == 'categories') { From 206bdd8d91a5eafed0c89f95742f467bc0b1c71e Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:09:04 +0000 Subject: [PATCH 084/152] Drafting Refactor --- .../paths/categories.id.publish.yaml | 30 ++ routes/api.php | 2 +- .../Actions/CreateDraftCategory.php | 62 ++++ .../Categories/Drafting/CategoryDrafter.php | 194 +++++------ src/Core/Drafting/Actions/DraftAssets.php | 50 +++ src/Core/Drafting/Actions/DraftCategories.php | 44 +++ src/Core/Drafting/Actions/DraftChannels.php | 49 +++ .../Drafting/Actions/DraftCustomerGroups.php | 50 +++ .../Actions/DraftProductAssociations.php | 47 +++ .../DraftProductVariantCustomerPricing.php | 52 +++ .../Actions/DraftProductVariantTiers.php | 50 +++ .../Drafting/Actions/DraftProductVariants.php | 64 ++++ src/Core/Drafting/Actions/DraftRoutes.php | 49 +++ .../Actions/PublishAssets.php} | 13 +- .../Actions/PublishChannels.php} | 4 +- .../Actions/PublishCustomerGroups.php} | 4 +- .../Actions/PublishProductAssociations.php} | 19 +- .../PublishProductVariantCustomerPricing.php} | 5 +- .../Actions/PublishProductVariantTiers.php} | 8 +- .../Actions/PublishProductVariants.php} | 23 +- .../Actions/PublishRoutes.php} | 4 +- src/Core/Drafting/BaseDrafter.php | 35 ++ src/Core/Products/Actions/PublishProduct.php | 58 ++++ src/Core/Products/Drafting/ProductDrafter.php | 321 ++++++------------ src/Core/Products/Models/Product.php | 6 +- .../Products/ProductController.php | 9 +- .../Products/ProductVariantResource.php | 3 + 27 files changed, 875 insertions(+), 380 deletions(-) create mode 100644 openapi/categories/paths/categories.id.publish.yaml create mode 100644 src/Core/Categories/Actions/CreateDraftCategory.php create mode 100644 src/Core/Drafting/Actions/DraftAssets.php create mode 100644 src/Core/Drafting/Actions/DraftCategories.php create mode 100644 src/Core/Drafting/Actions/DraftChannels.php create mode 100644 src/Core/Drafting/Actions/DraftCustomerGroups.php create mode 100644 src/Core/Drafting/Actions/DraftProductAssociations.php create mode 100644 src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php create mode 100644 src/Core/Drafting/Actions/DraftProductVariantTiers.php create mode 100644 src/Core/Drafting/Actions/DraftProductVariants.php create mode 100644 src/Core/Drafting/Actions/DraftRoutes.php rename src/Core/{Products/Actions/Drafting/UpdateAssets.php => Drafting/Actions/PublishAssets.php} (68%) rename src/Core/{Products/Actions/Drafting/UpdateChannels.php => Drafting/Actions/PublishChannels.php} (90%) rename src/Core/{Products/Actions/Drafting/UpdateCustomerGroups.php => Drafting/Actions/PublishCustomerGroups.php} (90%) rename src/Core/{Products/Actions/Drafting/UpdateProductAssociations.php => Drafting/Actions/PublishProductAssociations.php} (67%) rename src/Core/{Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php => Drafting/Actions/PublishProductVariantCustomerPricing.php} (87%) rename src/Core/{Products/Actions/Drafting/UpdateProductVariantTiers.php => Drafting/Actions/PublishProductVariantTiers.php} (80%) rename src/Core/{Products/Actions/Drafting/UpdateProductVariants.php => Drafting/Actions/PublishProductVariants.php} (62%) rename src/Core/{Products/Actions/Drafting/UpdateRoutes.php => Drafting/Actions/PublishRoutes.php} (91%) create mode 100644 src/Core/Products/Actions/PublishProduct.php diff --git a/openapi/categories/paths/categories.id.publish.yaml b/openapi/categories/paths/categories.id.publish.yaml new file mode 100644 index 000000000..85620c061 --- /dev/null +++ b/openapi/categories/paths/categories.id.publish.yaml @@ -0,0 +1,30 @@ +parameters: + - schema: + type: string + name: categoryId + in: path + required: true +post: + summary: Publishes a category draft + tags: + - Categories + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/CategoryResponse.yaml' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '../../global/responses/ApiError.yaml' + operationId: publish-category-draft + description: Publishes a category draft + parameters: + - schema: + type: string + in: query + name: include diff --git a/routes/api.php b/routes/api.php index 5d44639bf..67be7306b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -78,7 +78,7 @@ $router->get('categories/parent/{parentID?}', 'Categories\CategoryController@getByParent'); $router->post('categories/reorder', 'Categories\CategoryController@reorder'); $router->post('categories/{category}/products/attach', 'Products\ProductCategoryController@attach'); - $router->post('categories/{category}/drafts', 'Categories\CategoryController@createDraft'); + $router->post('categories/{category}/drafts', '\GetCandy\Api\Core\Categories\Actions\CreateDraftCategory'); $router->put('categories/{category}/products', 'Categories\CategoryController@putProducts'); $router->post('categories/{category}/channels', 'Categories\CategoryController@putChannels'); $router->post('categories/{category}/customer-groups', 'Categories\CategoryController@putCustomerGroups'); diff --git a/src/Core/Categories/Actions/CreateDraftCategory.php b/src/Core/Categories/Actions/CreateDraftCategory.php new file mode 100644 index 000000000..c13e4efd2 --- /dev/null +++ b/src/Core/Categories/Actions/CreateDraftCategory.php @@ -0,0 +1,62 @@ +decodeId($category); + $category = Category::find($realId); + + if (! $category) { + return null; + } + + $draft = Drafting::with('categories')->firstOrCreate($category); + + return $draft->load($this->resolveEagerRelations()); + } + + public function response ($response, $request) + { + if (!$response) { + return $this->errorNotFound(); + } + + return new CategoryResource($response); + } +} diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 854e23e53..543b36ca7 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -3,101 +3,82 @@ namespace GetCandy\Api\Core\Categories\Drafting; use DB; +use Versioning; +use Illuminate\Database\Eloquent\Model; use GetCandy\Api\Core\Drafting\BaseDrafter; use GetCandy\Api\Core\Events\ModelPublishedEvent; use GetCandy\Api\Core\Search\Actions\IndexObjects; -use Illuminate\Database\Eloquent\Model; +use GetCandy\Api\Core\Drafting\Actions\DraftAssets; +use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Drafting\Actions\DraftChannels; +use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; use NeonDigital\Drafting\Interfaces\DrafterInterface; -use Versioning; +use GetCandy\Api\Core\Drafting\Actions\PublishChannels; +use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; class CategoryDrafter extends BaseDrafter implements DrafterInterface { - public function create(Model $model) + public function publish(Model $draft) { - } + return DB::transaction(function () use ($draft) { + // Publish this category and remove the parent. + $parent = $draft->publishedParent; + + // Create a version of the parent before we publish these changes + Versioning::with('categories')->create($parent, null, $parent->id); + + $parent->attribute_data = $draft->attribute_data; + $parent->sort = $draft->sort; + $parent->layout_id = $draft->layout_id; + $parent->save(); + + $this->callActions(array_merge([ + PublishRoutes::class, + PublishChannels::class, + PublishCustomerGroups::class, + PublishAssets::class, + ], $this->extendedPublishActions), [ + 'draft' => $draft, + 'parent' => $parent, + ]); - public function publish(Model $category) - { - // Publish this category and remove the parent. - $parent = $category->publishedParent; - - // Create a version of the parent before we publish these changes - Versioning::with('categories')->create($parent, null, $parent->id); - - $parent->attribute_data = $category->attribute_data; - $parent->sort = $category->sort; - $parent->layout_id = $category->layout_id; - $parent->save(); - - /** - * Here we go through any routes the draft has and if they have a published - * parent counterpart. We update it and then remove the draft route. - * - * If the parent doesn't exist then we reassign the new route to the published record. - */ - foreach ($category->routes as $route) { - if ($route->publishedParent) { - $route->publishedParent->update($route->toArray()); - $route->forceDelete(); - } else { - $route->update([ - 'element_id' => $parent->id - ]); - } - } - - /** - * Go through the draft channels and update our parent. - */ - $channels = $category->channels->mapWithKeys(function ($channel) { - return [$channel->id => [ - 'published_at' => $channel->pivot->published_at - ]]; - })->toArray(); - $parent->channels()->sync($channels); - - $customerGroups = $category->customerGroups->mapWithKeys(function ($group) { - return [$group->id => [ - 'purchasable' => $group->pivot->purchasable, - 'visible' => $group->pivot->visible, - ]]; - })->toArray(); - - $parent->customerGroups()->sync($customerGroups); - - /** - * Go through and assign any products that are for the draft to the parent. - */ - $category->products()->update([ - 'category_id' => $parent->id - ]); - - // Fire off an event so plugins can update anything their side too. - event(new ModelPublishedEvent($category, $parent)); - - // // Update all products... - $parent = $parent->refresh(); - - if ($parent->products->count()) { - IndexObjects::run([ - 'documents' => $parent->products, + $parent->products()->sync([]); + + $parent->products()->sync($draft->products()->groupBy('product_id')->pluck('product_id'), true); + + /** + * Go through and assign any products that are for the draft to the parent. + */ + $draft->products()->update([ + 'category_id' => $parent->id ]); - } - $category->forceDelete(); - // event(new ModelPublishedEvent($category)); + // Fire off an event so plugins can update anything their side too. + event(new ModelPublishedEvent($draft, $parent)); + + // // Update all products... + $parent = $parent->refresh(); + + // if ($parent->products->count()) { + // IndexObjects::dispatch([ + // 'documents' => $parent->products, + // ]); + // } + + $draft->forceDelete(); + + return $parent; + }); - return $parent; } - /** - * @param \Illuminate\Database\Eloquent\Model $category - * @return \Illuminate\Database\Eloquent\Model - */ - public function firstOrCreate(Model $category) + public function create(Model $parent) { - return $category->draft ?: DB::transaction(function () use ($category) { - $category = $category->load([ + return DB::transaction(function () use ($parent) { + + $parent = $parent->load([ 'children', 'products', 'channels', @@ -107,35 +88,31 @@ public function firstOrCreate(Model $category) 'attributes', ]); - $newCategory = $category->replicate(); - $newCategory->drafted_at = now(); - $newCategory->draft_parent_id = $category->id; - $newCategory->_lft = $category->_lft; - $newCategory->_rgt = $category->_rgt; - $newCategory->parent_id = $category->parent_id; - $newCategory->save(); - - $newCategory->products()->attach($category->products->pluck('id')); - - $category->routes->each(function ($r) use ($newCategory) { - $new = $r->replicate(); - $new->element_id = $newCategory->id; - $new->element_type = get_class($newCategory); - $new->drafted_at = now(); - $new->draft_parent_id = $r->id; - $new->save(); - }); + $draft = $parent->replicate(); + $draft->drafted_at = now(); + $draft->draft_parent_id = $parent->id; + $draft->_lft = $parent->_lft; + $draft->_rgt = $parent->_rgt; + $draft->parent_id = $parent->parent_id; + $draft->save(); + + $this->callActions(array_merge([ + DraftRoutes::class, + DraftAssets::class, + DraftChannels::class, + DraftCustomerGroups::class, + ], $this->extendedDraftActions), [ + 'draft' => $draft, + 'parent' => $parent, + ]); - $category->attributes->each(function ($model) use ($newCategory) { - $newCategory->attributes()->attach($model); - }); + $draft->products()->sync($parent->products()->groupBy('product_id')->pluck('product_id')); - $this->processAssets($category, $newCategory); - $this->processChannels($category, $newCategory); - $this->processCustomerGroups($category, $newCategory); - $newCategory->refresh(); + $parent->attributes->each(function ($model) use ($draft) { + $draft->attributes()->attach($model); + }); - return $newCategory->load([ + return $draft->refresh()->load([ 'children', 'products', 'channels', @@ -146,4 +123,9 @@ public function firstOrCreate(Model $category) ]); }); } + + public function firstOrCreate(Model $parent) + { + return $parent->draft ?: $this->create($parent); + } } diff --git a/src/Core/Drafting/Actions/DraftAssets.php b/src/Core/Drafting/Actions/DraftAssets.php new file mode 100644 index 000000000..06acad175 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftAssets.php @@ -0,0 +1,50 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->parent->assets as $asset) { + $this->draft->assets()->attach( + $asset->id, + [ + 'primary' => $asset->pivot->primary, + 'assetable_type' => $asset->pivot->assetable_type, + ] + ); + } + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftCategories.php b/src/Core/Drafting/Actions/DraftCategories.php new file mode 100644 index 000000000..8ded62172 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftCategories.php @@ -0,0 +1,44 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->parent->categories as $category) { + $this->draft->categories()->attach($category); + } + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftChannels.php b/src/Core/Drafting/Actions/DraftChannels.php new file mode 100644 index 000000000..1085cd893 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftChannels.php @@ -0,0 +1,49 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $channels = $this->parent->channels->mapWithKeys(function ($channel) { + return [$channel->id => [ + 'published_at' => $channel->pivot->published_at + ]]; + })->toArray(); + + $this->draft->channels()->sync($channels); + + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftCustomerGroups.php b/src/Core/Drafting/Actions/DraftCustomerGroups.php new file mode 100644 index 000000000..d7b4a9d58 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftCustomerGroups.php @@ -0,0 +1,50 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $customerGroups = $this->parent->customerGroups->mapWithKeys(function ($group) { + return [$group->id => [ + 'purchasable' => $group->pivot->purchasable, + 'visible' => $group->pivot->visible, + ]]; + })->toArray(); + + $this->draft->customerGroups()->sync($customerGroups); + + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftProductAssociations.php b/src/Core/Drafting/Actions/DraftProductAssociations.php new file mode 100644 index 000000000..8f76d1818 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftProductAssociations.php @@ -0,0 +1,47 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\Product + */ + public function handle() + { + $this->parent->associations->each(function ($model) { + $assoc = $model->replicate(); + $assoc->product_id = $this->draft->id; + $assoc->save(); + }); + + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php b/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php new file mode 100644 index 000000000..d8f580a9c --- /dev/null +++ b/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php @@ -0,0 +1,52 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductVariant + */ + public function handle() + { + $this->draft->customerPricing()->createMany( + $this->parent->customerPricing->map(function ($groupPrice) { + return $groupPrice->only([ + 'customer_group_id', + 'tax_id', + 'price', + 'compare_at_price', + ]); + }) + ); + + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftProductVariantTiers.php b/src/Core/Drafting/Actions/DraftProductVariantTiers.php new file mode 100644 index 000000000..04f9f2a17 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftProductVariantTiers.php @@ -0,0 +1,50 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductVariant + */ + public function handle() + { + $this->draft->tiers()->createMany( + $this->parent->tiers->map(function ($tierPrice) { + return $tierPrice->only([ + 'customer_group_id', + 'lower_limit', + 'price' + ]); + }) + ); + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftProductVariants.php b/src/Core/Drafting/Actions/DraftProductVariants.php new file mode 100644 index 000000000..1932ad7b7 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftProductVariants.php @@ -0,0 +1,64 @@ +user()->can('manage-products'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\Product + */ + public function handle() + { + $this->parent->variants->each(function ($parentVariant) { + $draftVariant = $parentVariant->replicate(); + + $draftVariant->product_id = $this->draft->id; + $draftVariant->drafted_at = now(); + $draftVariant->draft_parent_id = $parentVariant->id; + + $draftVariant->save(); + + DraftProductVariantCustomerPricing::run([ + 'draft' => $draftVariant, + 'parent' => $parentVariant, + ]); + + DraftProductVariantTiers::run([ + 'draft' => $draftVariant, + 'parent' => $parentVariant, + ]); + }); + + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Drafting/Actions/DraftRoutes.php b/src/Core/Drafting/Actions/DraftRoutes.php new file mode 100644 index 000000000..5dc2a8c66 --- /dev/null +++ b/src/Core/Drafting/Actions/DraftRoutes.php @@ -0,0 +1,49 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'draft' => 'required', + 'parent' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $this->parent->routes->each(function ($parentRoute) { + $draftRoute = $parentRoute->replicate(); + $draftRoute->element_id = $this->draft->id; + $draftRoute->element_type = get_class($this->draft); + $draftRoute->drafted_at = now(); + $draftRoute->draft_parent_id = $parentRoute->id; + $draftRoute->save(); + }); + return $this->draft; + } +} \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateAssets.php b/src/Core/Drafting/Actions/PublishAssets.php similarity index 68% rename from src/Core/Products/Actions/Drafting/UpdateAssets.php rename to src/Core/Drafting/Actions/PublishAssets.php index ce0a6b734..2e08198d6 100644 --- a/src/Core/Products/Actions/Drafting/UpdateAssets.php +++ b/src/Core/Drafting/Actions/PublishAssets.php @@ -1,10 +1,10 @@ draft->assets->pluck('id')->toArray(); - $this->parent->assets()->sync($incomingAssetIds); + // Detach any assets. + $this->parent->assets()->detach(); + + $this->draft->assets->each(function ($asset) { + $this->parent->assets()->attach($asset->id, $asset->pivot->only(['position', 'primary', 'assetable_type'])); + }); // Clean up on Aisle 4 $this->draft->assets()->detach(); diff --git a/src/Core/Products/Actions/Drafting/UpdateChannels.php b/src/Core/Drafting/Actions/PublishChannels.php similarity index 90% rename from src/Core/Products/Actions/Drafting/UpdateChannels.php rename to src/Core/Drafting/Actions/PublishChannels.php index 1a065703a..136135229 100644 --- a/src/Core/Products/Actions/Drafting/UpdateChannels.php +++ b/src/Core/Drafting/Actions/PublishChannels.php @@ -1,10 +1,10 @@ 'required', - 'product' => 'required', + 'parent' => 'required', ]; } /** * Execute the action and return a result. * - * @return \GetCandy\Api\Core\Products\Models\ProductFamily + * @return \GetCandy\Api\Core\Products\Models\Product */ public function handle() { - $this->product->associations() + $this->parent->associations() ->whereNotIn( 'association_id', $this->draft->associations->pluck('association_id')->toArray() @@ -48,7 +45,7 @@ public function handle() foreach ($this->draft->associations as $incoming) { // Does this parent already have this association? // If so we just need to update the group - $existing = $this->product->associations->first(function ($assoc) use ($incoming) { + $existing = $this->parent->associations->first(function ($assoc) use ($incoming) { return $assoc->association_id = $incoming->association_id; }); if ($existing) { @@ -57,10 +54,10 @@ public function handle() } // If it doesn't exist, reassign the product_id $incoming->update([ - 'product_id' => $this->product->id, + 'product_id' => $this->parent->id, ]); } - return $this->product; + return $this->parent; } } \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php similarity index 87% rename from src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php rename to src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php index 880c5b662..9da240d9b 100644 --- a/src/Core/Products/Actions/Drafting/UpdateProductVariantCustomerPricing.php +++ b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php @@ -1,11 +1,10 @@ draft->tiers as $incoming) { - $existing = $this->parent->customerPricing->first(function ($existing) use ($incoming) { + $existing = $this->parent->tiers->first(function ($existing) use ($incoming) { return $existing->customer_group_id === $incoming->customer_group_id; }); if ($existing) { - $existing->update($price->toArray()); + $existing->update($incoming->toArray()); $incoming->forceDelete(); continue; } diff --git a/src/Core/Products/Actions/Drafting/UpdateProductVariants.php b/src/Core/Drafting/Actions/PublishProductVariants.php similarity index 62% rename from src/Core/Products/Actions/Drafting/UpdateProductVariants.php rename to src/Core/Drafting/Actions/PublishProductVariants.php index 7319ff5e4..b3871c7f2 100644 --- a/src/Core/Products/Actions/Drafting/UpdateProductVariants.php +++ b/src/Core/Drafting/Actions/PublishProductVariants.php @@ -1,14 +1,11 @@ 'required', - 'product' => 'required', + 'parent' => 'required', ]; } /** * Execute the action and return a result. * - * @return \GetCandy\Api\Core\Products\Models\ProductFamily + * @return \GetCandy\Api\Core\Products\Models\Product */ public function handle() { @@ -47,24 +44,22 @@ public function handle() $parent = $incoming->publishedParent; $parent->update($incoming->toArray()); - UpdateProductVariantCustomerPricing::run([ + PublishProductVariantCustomerPricing::run([ 'draft' => $incoming, 'parent' => $parent, ]); - UpdateProductVariantTiers::run([ + PublishProductVariantTiers::run([ 'draft' => $incoming, 'parent' => $parent, ]); - - dd(1); } else { $incoming->update([ - 'product_id' => $this->product->id, + 'product_id' => $this->parent->id, ]); } } - return $this->product->refresh(); + return $this->parent->refresh(); } } \ No newline at end of file diff --git a/src/Core/Products/Actions/Drafting/UpdateRoutes.php b/src/Core/Drafting/Actions/PublishRoutes.php similarity index 91% rename from src/Core/Products/Actions/Drafting/UpdateRoutes.php rename to src/Core/Drafting/Actions/PublishRoutes.php index 51b64a5e2..26cc04b97 100644 --- a/src/Core/Products/Actions/Drafting/UpdateRoutes.php +++ b/src/Core/Drafting/Actions/PublishRoutes.php @@ -1,10 +1,10 @@ addAction('extendedDraftActions', $action); + } + + public function addPublishAction($action) + { + return $this->addAction('extendedPublishActions', $action); + } + + protected function addAction($target, $incoming) + { + if (is_array($incoming)) { + $this->{$target} = array_merge($this->{$target}, $incoming); + return; + } + array_push($this->{$target}, $incoming); + } + + protected function callActions(array $actions, array $params = []) + { + foreach ($actions as $action) { + if (!class_exists($action)) { + Log::error("Tried to call action ${action} but it doesn't exist"); + continue; + } + call_user_func("{$action}::run", $params); + } + } + /** * Process the assets for a duplicated product. * diff --git a/src/Core/Products/Actions/PublishProduct.php b/src/Core/Products/Actions/PublishProduct.php new file mode 100644 index 000000000..ac660753d --- /dev/null +++ b/src/Core/Products/Actions/PublishProduct.php @@ -0,0 +1,58 @@ + + +user()->can('manage-product'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules(): array + { + return []; + } + + /** + * Execute the action and return a result. + * + * @return \GetCandy\Api\Core\Products\Models\ProductFamily + */ + public function handle() + { + + } + + /** + * Returns the response from the action. + * + * @param \GetCandy\Api\Core\Products\Models\ProductFamily $result + * @param \Illuminate\Http\Request $request + * + * @return \GetCandy\Api\Core\Products\Resources\ProductFamilyResource + */ + public function response($result, $request) + { + return new ProductResource($result); + } +} diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index a29f29793..5c2d42d20 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -5,262 +5,135 @@ use DB; use Versioning; use Illuminate\Database\Eloquent\Model; -use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Drafting\BaseDrafter; +use GetCandy\Api\Core\Drafting\Actions\DraftAssets; +use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Drafting\Actions\DraftChannels; +use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; use NeonDigital\Drafting\Interfaces\DrafterInterface; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateAssets; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateRoutes; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateChannels; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateCustomerGroups; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateProductVariants; -use GetCandy\Api\Core\Products\Actions\Drafting\UpdateProductAssociations; +use GetCandy\Api\Core\Drafting\Actions\DraftCategories; +use GetCandy\Api\Core\Drafting\Actions\PublishChannels; +use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; +use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; +use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; +use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; -class ProductDrafter implements DrafterInterface -{ - public function create(Model $product) - { - dd('Hello!'); - } - - public function publish(Model $product) - { - // Publish this product and remove the parent. - $parent = $product->publishedParent; - - // Get any current versions and assign them to this new product. - - // Create a version of the parent before we publish these changes - Versioning::with('products')->create($parent, null, $parent->id); - - // Update any attributes etc - $parent->attribute_data = $product->attribute_data; - $parent->option_data = $product->option_data; - $parent->product_family_id = $product->product_family_id; - $parent->layout_id = $product->layout_id; - $parent->group_pricing = $product->group_pricing; - - $parent->save(); - - // Activate any product variants - UpdateProductVariants::run([ - 'draft' => $product, - 'product' => $parent, - ]); - - /** - * Go through the draft channels and update our parent. - */ - UpdateChannels::run([ - 'draft' => $product, - 'parent' => $parent, - ]); - UpdateCustomerGroups::run([ - 'draft' => $product, - 'parent' => $parent, - ]); - - /** - * Here we go through any routes the draft has and if they have a published - * parent counterpart. We update it and then remove the draft route. - * - * If the parent doesn't exist then we reassign the new route to the published record. - */ - UpdateRoutes::run([ - 'draft' => $product, - 'parent' => $parent, - ]); - - /** - * Go through any assets and sync with the parent. - */ - UpdateAssets::run([ - 'draft' => $product, - 'parent' => $parent, - ]); - - // Product Associations - // Delete any associations we don't have anymore... - UpdateProductAssociations::run([ - 'draft' => $product, - 'product' => $parent - ]); - - // Categories - $existingCategories = $parent->categories; - - // Sync product categories to the parent. - $parent->categories()->sync( - $product->categories->pluck('id') - ); - // Collections - $parent->collections()->sync( - $product->collections->pluck('id') - ); - // Delete the draft we had. - $product->forceDelete(); - - dd('End'); - return $product; - } - /** - * Duplicate a product. - * - * @param \Illuminate\Database\Eloquent\Model $product - * @return \Illuminate\Database\Eloquent\Model - */ - public function firstOrCreate(Model $product) +class ProductDrafter extends BaseDrafter implements DrafterInterface +{ + public function create(Model $parent) { - return $product->draft ?: DB::transaction(function () use ($product) { - $product = $product->load([ + return DB::transaction(function () use ($parent) { + $parent = $parent->load([ 'variants', 'categories', 'routes', 'channels', 'customerGroups', ]); - $newProduct = $product->replicate(); - $newProduct->drafted_at = now(); - $newProduct->draft_parent_id = $product->id; - $newProduct->save(); - - $product->variants->each(function ($v) use ($newProduct) { - $new = $v->replicate(); - $new->product_id = $newProduct->id; - $new->drafted_at = now(); - $new->draft_parent_id = $v->id; - - $new->save(); - - // Copy customer group pricing... - $groupPricing = $v->customerPricing->map(function ($groupPrice) { - return $groupPrice->only([ - 'customer_group_id', - 'tax_id', - 'price', - 'compare_at_price', - ]); - }); - - $new->customerPricing()->createMany($groupPricing); - }); - - $product->routes->each(function ($r) use ($newProduct) { - $new = $r->replicate(); - $new->element_id = $newProduct->id; - $new->element_type = get_class($newProduct); - $new->drafted_at = now(); - $new->draft_parent_id = $r->id; - $new->save(); - }); - - $product->attributes->each(function ($model) use ($newProduct) { - $newProduct->attributes()->attach($model); - }); + $draft = $parent->replicate(); + $draft->drafted_at = now(); + $draft->draft_parent_id = $parent->id; + $draft->save(); + + $this->callActions(array_merge([ + DraftProductVariants::class, + DraftRoutes::class, + DraftProductAssociations::class, + DraftAssets::class, + DraftCategories::class, + DraftChannels::class, + DraftCustomerGroups::class, + ], $this->extendedDraftActions), [ + 'draft' => $draft, + 'parent' => $parent, + ]); - $product->associations->each(function ($model) use ($newProduct) { - $assoc = $model->replicate(); - $assoc->product_id = $newProduct->id; - $assoc->save(); + // Not sure if this is something we need to worry about now as drafting has changed. + // Potentially deprecated in a later release... + $parent->attributes->each(function ($model) use ($draft) { + $draft->attributes()->attach($model); }); - $newProduct->refresh(); - $this->processAssets($product, $newProduct); - $this->processCategories($product, $newProduct); - $this->processChannels($product, $newProduct); - $this->processCustomerGroups($product, $newProduct); - $newProduct->refresh(); - - return $newProduct->load([ - 'variants', - 'channels', + return $draft->refresh()->load([ + 'variants.publishedParent', + 'categories', 'routes', + 'channels', 'customerGroups', ]); }); } /** - * Process the assets for a duplicated product. - * - * @param \GetCandy\Api\Core\Products\Models\Product $oldProduct - * @param \GetCandy\Api\Core\Products\Models\Product $newProduct - * @return void - */ - protected function processAssets($oldProduct, &$newProduct) - { - foreach ($oldProduct->assets as $asset) { - $newProduct->assets()->attach( - $asset->id, - [ - 'primary' => $asset->pivot->primary, - 'assetable_type' => $asset->pivot->assetable_type, - ] - ); - } - } - - /** - * Process the duplicated product categories. + * Duplicate a product. * - * @param \GetCandy\Api\Core\Products\Models\Product $oldProduct - * @param \GetCandy\Api\Core\Products\Models\Product $newProduct - * @return void + * @param \Illuminate\Database\Eloquent\Model $product + * @return \Illuminate\Database\Eloquent\Model */ - protected function processCategories($oldProduct, &$newProduct) + public function firstOrCreate(Model $parent) { - $currentCategories = $oldProduct->categories; - foreach ($currentCategories as $category) { - $newProduct->categories()->attach($category); - } + return $parent->draft ?: $this->create($parent); } - /** - * Process the customer groups for the duplicated product. - * - * @param \GetCandy\Api\Core\Products\Models\Product $oldProduct - * @param \GetCandy\Api\Core\Products\Models\Product $newProduct - * @return void - */ - protected function processCustomerGroups($oldProduct, &$newProduct) + public function publish(Model $draft) { - // Need to associate all the channels the current product has - // but make sure they are not active to start with. - $groups = $oldProduct->customerGroups; - - $newGroups = collect(); + return DB::transaction(function () use ($draft) { + // Publish this product and remove the parent. + $parent = $draft->publishedParent->load( + 'variants', + 'categories', + 'routes', + 'channels', + 'customerGroups', + ); - foreach ($groups as $group) { - // \DB::table() - $newGroups->put($group->id, [ - 'visible' => $group->pivot->visible, - 'purchasable' => $group->pivot->purchasable, + // Get any current versions and assign them to this new product. + + // Create a version of the parent before we publish these changes + Versioning::with('products')->create($parent, null, $parent->id); + + // Publish any attributes etc + $parent->attribute_data = $draft->attribute_data; + $parent->option_data = $draft->option_data; + $parent->product_family_id = $draft->product_family_id; + $parent->layout_id = $draft->layout_id; + $parent->group_pricing = $draft->group_pricing; + + $parent->save(); + + $this->callActions(array_merge([ + PublishProductVariants::class, + PublishChannels::class, + PublishCustomerGroups::class, + PublishRoutes::class, + PublishAssets::class, + PublishProductAssociations::class, + ], $this->extendedPublishActions), [ + 'draft' => $draft, + 'parent' => $parent, ]); - } - $newProduct->customerGroups()->sync($newGroups->toArray()); - } - /** - * Process channels for a duplicated product. - * - * @param \GetCandy\Api\Core\Products\Models\Product $oldProduct - * @param \GetCandy\Api\Core\Products\Models\Product $newProduct - * @return void - */ - protected function processChannels($oldProduct, &$newProduct) - { - // Need to associate all the channels the current product has - // but make sure they are not active to start with. - $channels = $oldProduct->channels; + // Categories + $existingCategories = $parent->categories; - $newChannels = collect(); + // Sync product categories to the parent. + $parent->categories()->sync( + $draft->categories->pluck('id') + ); + // Collections + $parent->collections()->sync( + $draft->collections->pluck('id') + ); - foreach ($channels as $channel) { - $newChannels->put($channel->id, [ - 'published_at' => now(), - ]); - } + // Delete the draft we had. + $draft->forceDelete(); - $newProduct->channels()->sync($newChannels->toArray()); + return $parent; + }); } } diff --git a/src/Core/Products/Models/Product.php b/src/Core/Products/Models/Product.php index 51555e250..6b7f052c9 100644 --- a/src/Core/Products/Models/Product.php +++ b/src/Core/Products/Models/Product.php @@ -125,7 +125,7 @@ public function setOptionDataAttribute($value) $value = json_decode($value, true); } - foreach ($value as $option) { + foreach ($value ?? [] as $option) { $label = reset($option['label']); $options[str_slug($label)] = $option; $childOptions = []; @@ -186,12 +186,12 @@ public function layout() public function variants() { - return $this->hasMany(ProductVariant::class); + return $this->hasMany(ProductVariant::class)->withDrafted(); } public function firstVariant() { - return $this->hasOne(ProductVariant::class); + return $this->hasOne(ProductVariant::class)->withDrafted(); } public function categories() diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 286c1e2bf..5f75bf06b 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -93,6 +93,7 @@ public function show($idOrSku, Request $request) public function createDraft($id, Request $request) { $id = Hashids::connection('product')->decode($id); + if (empty($id[0])) { return $this->errorNotFound(); } @@ -110,9 +111,11 @@ public function publishDraft($id, Request $request) } $product = $this->service->findById($id[0], [], true); - DB::transaction(function () use ($product) { - Drafting::with('products')->publish($product); - }); + if (!$product) { + return $this->errorNotFound(); + } + + $product = Drafting::with('products')->publish($product); return new ProductResource($product->load($this->parseIncludes($request->include))); } diff --git a/src/Http/Resources/Products/ProductVariantResource.php b/src/Http/Resources/Products/ProductVariantResource.php index 3c931e55c..492c2ec5d 100644 --- a/src/Http/Resources/Products/ProductVariantResource.php +++ b/src/Http/Resources/Products/ProductVariantResource.php @@ -22,6 +22,7 @@ public function payload() return [ 'id' => $this->encodedId(), + 'drafted_at' => $this->drafted_at, 'sku' => $this->sku, 'backorder' => $this->backorder, 'requires_shipping' => (bool) $this->requires_shipping, @@ -69,6 +70,8 @@ public function includes() 'tiers' => new ProductTierCollection($this->whenLoaded('tiers')), 'customer_pricing' => new ProductCustomerPriceCollection($this->whenLoaded('customerPricing')), 'tax' => $this->include('tax', TaxResource::class), + 'draft' => $this->include('draft', self::class), + 'published_parent' => $this->include('publishedParent', self::class), ]; } } From 247fc63ea1aacf1075609291fcb5620d11731c2a Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:09:37 +0000 Subject: [PATCH 085/152] Allow attribute data to be null on update --- src/Core/Categories/Services/CategoryService.php | 5 ++++- src/Http/Requests/Categories/UpdateRequest.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Core/Categories/Services/CategoryService.php b/src/Core/Categories/Services/CategoryService.php index 9f83b7888..d994d8b69 100644 --- a/src/Core/Categories/Services/CategoryService.php +++ b/src/Core/Categories/Services/CategoryService.php @@ -127,7 +127,10 @@ public function create(array $data) public function update($hashedId, array $data) { $model = $this->getByHashedId($hashedId, true); - $model->attribute_data = $data['attribute_data']; + + if (!empty($data['attribute_data'])) { + $model->attribute_data = $data['attribute_data']; + } if (! empty($data['customer_groups'])) { $groupData = $this->mapCustomerGroupData($data['customer_groups']['data']); diff --git a/src/Http/Requests/Categories/UpdateRequest.php b/src/Http/Requests/Categories/UpdateRequest.php index a6149ad0d..8d45d2d01 100644 --- a/src/Http/Requests/Categories/UpdateRequest.php +++ b/src/Http/Requests/Categories/UpdateRequest.php @@ -24,7 +24,7 @@ public function authorize() public function rules() { return [ - 'attribute_data' => 'required|array', + 'attribute_data' => 'nullable|array', ]; } } From f27ebd0bfe541a5418ff5d70f809f26e3a81c7e6 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:09:51 +0000 Subject: [PATCH 086/152] Start migration to actions --- .../Categories/CategoryController.php | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index 02db437fd..6ced11560 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -61,23 +61,6 @@ public function children($id, Request $request, CategoryCriteria $criteria) return new CategoryCollection($query->get()); } - public function createDraft($id, Request $request) - { - $id = Hashids::connection('main')->decode($id); - if (empty($id[0])) { - return $this->errorNotFound(); - } - $category = $this->service->findById($id[0], [], false); - - if (! $category) { - return $this->errorNotFound(); - } - - $draft = \Drafting::with('categories')->firstOrCreate($category); - - return new CategoryResource($draft->load($this->parseIncludes($request->include))); - } - public function show($id, Request $request) { $id = (new Category)->decodeId($id); @@ -194,9 +177,12 @@ public function publishDraft($id, Request $request) } $category = $this->service->findById($id[0], [], true); - \DB::transaction(function () use ($category) { - Drafting::with('categories')->publish($category); - }); + if (!$category) { + return $this->errorNotFound(); + } + + + $category = Drafting::with('categories')->publish($category); return new CategoryResource($category->load($this->parseIncludes($request->include))); } From 84de763535b7f6d314051d607f625735fbabfaf4 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 12:14:47 +0000 Subject: [PATCH 087/152] Update spec --- .../paths/categories.id.customer-groups.yaml | 5 +--- .../examples/code/fetch-channel/laravel.php | 7 +++++ openapi/channels/models/Channel.yaml | 7 ----- openapi/channels/paths/channels.id.yaml | 16 +++-------- .../channels/responses/ChannelResponse.yaml | 3 +- openapi/currencies/models/Currency.yaml | 21 ++++++++++++++ openapi/currencies/paths/currencies.yaml | 19 +++++++++++++ .../currencies/responses/CountryResponse.yaml | 7 +++++ .../responses/CurrencyCollection.yaml | 14 ++++++++++ openapi/openapi.yaml | 28 +++++++------------ .../paths/payments.providers.handle.yaml | 24 ++++++++++++++++ 11 files changed, 108 insertions(+), 43 deletions(-) create mode 100644 openapi/channels/examples/code/fetch-channel/laravel.php create mode 100644 openapi/currencies/models/Currency.yaml create mode 100644 openapi/currencies/paths/currencies.yaml create mode 100644 openapi/currencies/responses/CountryResponse.yaml create mode 100644 openapi/currencies/responses/CurrencyCollection.yaml create mode 100644 openapi/payments/paths/payments.providers.handle.yaml diff --git a/openapi/categories/paths/categories.id.customer-groups.yaml b/openapi/categories/paths/categories.id.customer-groups.yaml index 19bcd4feb..8ac172bae 100644 --- a/openapi/categories/paths/categories.id.customer-groups.yaml +++ b/openapi/categories/paths/categories.id.customer-groups.yaml @@ -20,10 +20,7 @@ post: content: application/json: schema: - type: object - properties: - '': - $ref: '../../global/responses/ApiError.yaml' + $ref: '../../global/responses/ApiError.yaml' operationId: post-categories-categoryId-customer-groups requestBody: content: diff --git a/openapi/channels/examples/code/fetch-channel/laravel.php b/openapi/channels/examples/code/fetch-channel/laravel.php new file mode 100644 index 000000000..7fefc7d20 --- /dev/null +++ b/openapi/channels/examples/code/fetch-channel/laravel.php @@ -0,0 +1,7 @@ +use GetCandy\Api\Core\Channels\Actions\FetchChannel; + +FetchChannel::run([ + 'id' => 1, // Required without encoded_id or handle PHP_EOL + 'encoded_id' => '1AftLawd3d', // Required without id or handle + 'handle' => 'webstore' // Required if not using one of the above +]); \ No newline at end of file diff --git a/openapi/channels/models/Channel.yaml b/openapi/channels/models/Channel.yaml index 0e04542bd..ca928a2a1 100644 --- a/openapi/channels/models/Channel.yaml +++ b/openapi/channels/models/Channel.yaml @@ -7,13 +7,6 @@ description: |- - collections - discounts - shippingMethods -x-examples: - webstore: - id: y3g6v91o - name: Webstore - handle: webstore - url: 'localhost:8080' - default: true properties: id: type: string diff --git a/openapi/channels/paths/channels.id.yaml b/openapi/channels/paths/channels.id.yaml index eac41574b..720e8320b 100644 --- a/openapi/channels/paths/channels.id.yaml +++ b/openapi/channels/paths/channels.id.yaml @@ -6,6 +6,10 @@ parameters: required: true get: summary: Get the channel resource + x-code-samples: + - lang: PHP + source: + $ref: ../examples/code/fetch-channel/laravel.php responses: '200': description: OK @@ -13,18 +17,6 @@ get: application/json: schema: $ref: '../responses/ChannelResponse.yaml' - examples: - example-1: - value: - data: - id: y3g6v91o - name: Webstore - handle: webstore - url: 'http://webstore.test' - default: true - published_at: null - meta: - lang: en '404': description: Not Found content: diff --git a/openapi/channels/responses/ChannelResponse.yaml b/openapi/channels/responses/ChannelResponse.yaml index 9cc6fa627..210424cd6 100644 --- a/openapi/channels/responses/ChannelResponse.yaml +++ b/openapi/channels/responses/ChannelResponse.yaml @@ -2,5 +2,4 @@ title: ChannelResponse type: object properties: data: - $ref: '../models/Channel.yaml' - \ No newline at end of file + $ref: '../models/Channel.yaml' \ No newline at end of file diff --git a/openapi/currencies/models/Currency.yaml b/openapi/currencies/models/Currency.yaml new file mode 100644 index 000000000..81c690132 --- /dev/null +++ b/openapi/currencies/models/Currency.yaml @@ -0,0 +1,21 @@ +title: Country +type: object +properties: + id: + type: string + code: + type: string + name: + type: string + enabled: + type: boolean + format: + type: string + exchange_rate: + type: string + decimal_point: + type: string + thousand_point: + type: string + default: + type: boolean \ No newline at end of file diff --git a/openapi/currencies/paths/currencies.yaml b/openapi/currencies/paths/currencies.yaml new file mode 100644 index 000000000..b4bdb20c7 --- /dev/null +++ b/openapi/currencies/paths/currencies.yaml @@ -0,0 +1,19 @@ +get: + summary: Get all currencies + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/CurrencyCollection.yaml' + operationId: get-currencies + parameters: + - schema: + type: string + in: query + name: include + description: Comma separated includes for the resource + description: Gets a paginated list of all currencies + tags: + - Currencies \ No newline at end of file diff --git a/openapi/currencies/responses/CountryResponse.yaml b/openapi/currencies/responses/CountryResponse.yaml new file mode 100644 index 000000000..2f66ebf56 --- /dev/null +++ b/openapi/currencies/responses/CountryResponse.yaml @@ -0,0 +1,7 @@ +title: CurrencyResponse +type: object +properties: + data: + $ref: '../models/Currency.yaml' +x-tags: + - Currencies \ No newline at end of file diff --git a/openapi/currencies/responses/CurrencyCollection.yaml b/openapi/currencies/responses/CurrencyCollection.yaml new file mode 100644 index 000000000..5e02bc7f4 --- /dev/null +++ b/openapi/currencies/responses/CurrencyCollection.yaml @@ -0,0 +1,14 @@ +title: CurrencyCollection +allOf: + - type: object + properties: + data: + type: array + items: + $ref: '../models/Currency.yaml' + - type: object + properties: + meta: + type: object + $ref: '../../global/models/Pagination.yaml' +description: '' diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index a258ecb13..1870787b6 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -87,6 +87,8 @@ paths: $ref: './categories/paths/categories.id.channels.yaml' '/categories/{categoryId}/drafts': $ref: './categories/paths/categories.id.drafts.yaml' + '/categories/{categoryId}/publish': + $ref: './categories/paths/categories.id.publish.yaml' '/categories/{categoryId}/customer-groups': $ref: './categories/paths/categories.id.customer-groups.yaml' '/channels': @@ -119,6 +121,8 @@ paths: $ref: './customers/paths/customers.id.customer-groups.yaml' '/customers/{customerId}': $ref: './customers/paths/customers.id.yaml' + '/currencies': + $ref: './currencies/paths/currencies.yaml' '/discounts': $ref: './discounts/paths/discounts.yaml' '/discounts/{discountId}': @@ -167,6 +171,8 @@ paths: $ref: './payments/paths/payments.3d-secure.yaml' '/payments/provider': $ref: './payments/paths/payments.provider.yaml' + '/payments/providers/{handle}': + $ref: './payments/paths/payments.providers.handle.yaml' '/payments/types': $ref: './payments/paths/payments.types.yaml' '/product-families': @@ -279,33 +285,19 @@ paths: $ref: './users/paths/users.current.yaml' '/versions/{modelId}/restore': $ref: './versions/paths/versions.id.restore.yaml' -components: - securitySchemes: - auth: - type: oauth2 - flows: - password: - tokenUrl: 'http://localhost:3000/api/v1/oauth/token' - refreshUrl: 'http://localhost:3000/api/v1/oauth/refresh' - scopes: {} - clientCredentials: - tokenUrl: 'http://localhost:3000/api/v1/oauth/token' - scopes: {} - refreshUrl: 'http://localhost:3000/api/v1/oauth/refresh' - description: '' tags: + - name: Account + description: Account management + - name: Assets + description: Catalogue Management - name: Channels description: Store channels - name: Categories description: Catalogue Management - - name: Authentication - description: Everything to authenticate requests - name: Orders description: Order Processing - name: Attributes description: Catalogue Management - - name: Assets - description: Catalogue Management - name: Associations description: Catalogue Management - name: Baskets diff --git a/openapi/payments/paths/payments.providers.handle.yaml b/openapi/payments/paths/payments.providers.handle.yaml new file mode 100644 index 000000000..cab951421 --- /dev/null +++ b/openapi/payments/paths/payments.providers.handle.yaml @@ -0,0 +1,24 @@ +parameters: + - schema: + type: string + name: handle + in: path + required: true +get: + summary: Get Payment Provider by it's handle + tags: + - Payments + operationId: get-payment-provider + description: 'Gets a payment provider from the given handle' + parameters: + - schema: + type: string + in: query + name: order_id + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/PaymentProviderResponse.yaml' From 5db6c221ade5d7a2a6641bdad4f2a24c7625bc05 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 14:41:00 +0000 Subject: [PATCH 088/152] Add and fix drafting testing --- database/factories/AssetFactory.php | 31 +++++++++++ database/factories/AssetSourceFactory.php | 25 +++++++++ .../factories/AssociationGroupFactory.php | 24 ++++++++ database/factories/CategoryFactory.php | 25 +++++++++ database/factories/TaxFactory.php | 23 ++++++++ ...lling_shipping_company_name_to_orders.php} | 2 +- src/Core/Assets/Models/Asset.php | 9 +-- src/Core/Drafting/Actions/DraftAssets.php | 1 + .../Drafting/Actions/DraftProductVariants.php | 4 +- .../Actions/PublishProductVariants.php | 6 +- .../Products/Models/ProductCustomerPrice.php | 1 + src/Core/Products/Models/ProductVariant.php | 1 + src/Core/Routes/Actions/UpdateRoute.php | 6 +- src/Core/Routes/Models/Route.php | 2 +- src/Http/Resources/Assets/AssetResource.php | 2 +- .../Customers/AttachUserToCustomerTest.php | 2 +- .../Actions/Routes/UpdateRouteTest.php | 2 +- .../Categories/CategoryControllerTest.php | 6 +- .../Actions/SetCurrentChannelTest.php | 2 +- .../Unit/Drafting/Actions/DraftAssetsTest.php | 53 ++++++++++++++++++ .../Drafting/Actions/DraftCategoriesTest.php | 41 ++++++++++++++ .../Drafting/Actions/DraftChannelsTest.php | 42 ++++++++++++++ .../Actions/DraftCustomerGroupsTest.php | 43 +++++++++++++++ .../Actions/DraftProductAssociationsTest.php | 46 ++++++++++++++++ ...DraftProductVariantCustomerPricingTest.php | 55 +++++++++++++++++++ .../Actions/DraftProductVariantTiersTest.php | 52 ++++++++++++++++++ .../Actions/DraftProductVariantsTest.php | 43 +++++++++++++++ .../Unit/Drafting/Actions/DraftRoutesTest.php | 41 ++++++++++++++ .../Drafting/Actions/PublishAssetsTest.php | 51 +++++++++++++++++ .../Drafting/Actions/PublishChannelsTest.php | 42 ++++++++++++++ .../Actions/PublishCustomerGroupsTest.php | 44 +++++++++++++++ .../PublishProductAssociationsTest.php | 46 ++++++++++++++++ ...blishProductVariantCustomerPricingTest.php | 55 +++++++++++++++++++ .../PublishProductVariantTiersTest.php | 55 +++++++++++++++++++ .../Actions/PublishProductVariantsTest.php | 43 +++++++++++++++ .../Drafting/Actions/PublishRoutesTest.php | 41 ++++++++++++++ tests/Unit/Routes/Actions/CreateRouteTest.php | 12 ++-- tests/Unit/Routes/Actions/UpdateRouteTest.php | 8 +-- 38 files changed, 953 insertions(+), 34 deletions(-) create mode 100644 database/factories/AssetFactory.php create mode 100644 database/factories/AssetSourceFactory.php create mode 100644 database/factories/AssociationGroupFactory.php create mode 100644 database/factories/CategoryFactory.php create mode 100644 database/factories/TaxFactory.php rename database/migrations/{2021_02_05_093543_add_company_name_to_orders.php => 2021_02_05_093543_add_billing_shipping_company_name_to_orders.php} (93%) create mode 100644 tests/Unit/Drafting/Actions/DraftAssetsTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftCategoriesTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftChannelsTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftProductVariantsTest.php create mode 100644 tests/Unit/Drafting/Actions/DraftRoutesTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishAssetsTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishChannelsTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishProductVariantsTest.php create mode 100644 tests/Unit/Drafting/Actions/PublishRoutesTest.php diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php new file mode 100644 index 000000000..c79e7fa30 --- /dev/null +++ b/database/factories/AssetFactory.php @@ -0,0 +1,31 @@ +define(Asset::class, function (Faker $faker) { + return [ + 'location' => $faker->word, + 'kind' => 'image', + 'sub_kind' => 'jpg', + 'width' => $faker->randomNumber, + 'height' => $faker->randomNumber, + 'title' => $faker->word, + 'original_filename' => "{$faker->word}.jpg", + 'caption' => $faker->word, + 'size' => $faker->randomNumber, + 'extension' => 'jpg', + 'filename' => "{$faker->unique()->word}.jpg", + ]; +}); diff --git a/database/factories/AssetSourceFactory.php b/database/factories/AssetSourceFactory.php new file mode 100644 index 000000000..7af6164ae --- /dev/null +++ b/database/factories/AssetSourceFactory.php @@ -0,0 +1,25 @@ +define(AssetSource::class, function (Faker $faker) { + return [ + 'name' => $faker->word, + 'handle' => $faker->word, + 'disk' => 'public', + 'default' => $faker->boolean, + 'path' => $faker->word + ]; +}); diff --git a/database/factories/AssociationGroupFactory.php b/database/factories/AssociationGroupFactory.php new file mode 100644 index 000000000..c1bd670b0 --- /dev/null +++ b/database/factories/AssociationGroupFactory.php @@ -0,0 +1,24 @@ +define(AssociationGroup::class, function (Faker $faker) { + $name = $faker->unique()->company; + return [ + 'name' => $name, + 'handle' => Str::slug($name) + ]; +}); diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php new file mode 100644 index 000000000..bcd0aab29 --- /dev/null +++ b/database/factories/CategoryFactory.php @@ -0,0 +1,25 @@ +define(Category::class, function (Faker $faker) { + return [ + 'attribute_data' => [ + 'name' => [ + 'en' => $faker->word + ] + ] + ]; +}); diff --git a/database/factories/TaxFactory.php b/database/factories/TaxFactory.php new file mode 100644 index 000000000..8a705b4ea --- /dev/null +++ b/database/factories/TaxFactory.php @@ -0,0 +1,23 @@ +define(Tax::class, function (Faker $faker) { + return [ + 'name' => $faker->word, + 'percentage' => $faker->numberBetween(0, 20), + 'default' => $faker->boolean, + ]; +}); diff --git a/database/migrations/2021_02_05_093543_add_company_name_to_orders.php b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php similarity index 93% rename from database/migrations/2021_02_05_093543_add_company_name_to_orders.php rename to database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php index 9fcbbff7e..0cd850727 100644 --- a/database/migrations/2021_02_05_093543_add_company_name_to_orders.php +++ b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php @@ -4,7 +4,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -class AddCompanyNameToOrders extends Migration +class AddBillingShippingCompanyNameToOrders extends Migration { /** * Run the migrations. diff --git a/src/Core/Assets/Models/Asset.php b/src/Core/Assets/Models/Asset.php index fe79a7a60..f71f7fdaf 100644 --- a/src/Core/Assets/Models/Asset.php +++ b/src/Core/Assets/Models/Asset.php @@ -36,14 +36,7 @@ class Asset extends BaseModel 'kind', 'external', ]; - - public function toArray() - { - return array_merge(parent::toArray(), [ - 'url' => Storage::disk($this->source->disk)->url($this->location.'/'.$this->filename), - ]); - } - + public function scopeImages($query) { return $query->where('kind', '!=', 'application'); diff --git a/src/Core/Drafting/Actions/DraftAssets.php b/src/Core/Drafting/Actions/DraftAssets.php index 06acad175..8fa8e198e 100644 --- a/src/Core/Drafting/Actions/DraftAssets.php +++ b/src/Core/Drafting/Actions/DraftAssets.php @@ -41,6 +41,7 @@ public function handle() $asset->id, [ 'primary' => $asset->pivot->primary, + 'position' => $asset->pivot->position, 'assetable_type' => $asset->pivot->assetable_type, ] ); diff --git a/src/Core/Drafting/Actions/DraftProductVariants.php b/src/Core/Drafting/Actions/DraftProductVariants.php index 1932ad7b7..54999579e 100644 --- a/src/Core/Drafting/Actions/DraftProductVariants.php +++ b/src/Core/Drafting/Actions/DraftProductVariants.php @@ -48,12 +48,12 @@ public function handle() $draftVariant->save(); - DraftProductVariantCustomerPricing::run([ + (new DraftProductVariantCustomerPricing)->actingAs($this->user())->run([ 'draft' => $draftVariant, 'parent' => $parentVariant, ]); - DraftProductVariantTiers::run([ + (new DraftProductVariantTiers)->actingAs($this->user())->run([ 'draft' => $draftVariant, 'parent' => $parentVariant, ]); diff --git a/src/Core/Drafting/Actions/PublishProductVariants.php b/src/Core/Drafting/Actions/PublishProductVariants.php index b3871c7f2..070f1bbbf 100644 --- a/src/Core/Drafting/Actions/PublishProductVariants.php +++ b/src/Core/Drafting/Actions/PublishProductVariants.php @@ -37,19 +37,19 @@ public function rules() */ public function handle() { - $variants = $this->draft->variants()->onlyDrafted()->get(); + $variants = $this->draft->variants()->withDrafted()->get(); foreach ($variants as $incoming) { if ($incoming->publishedParent) { $parent = $incoming->publishedParent; $parent->update($incoming->toArray()); - PublishProductVariantCustomerPricing::run([ + (new PublishProductVariantCustomerPricing)->actingAs($this->user())->run([ 'draft' => $incoming, 'parent' => $parent, ]); - PublishProductVariantTiers::run([ + (new PublishProductVariantTiers)->actingAs($this->user())->run([ 'draft' => $incoming, 'parent' => $parent, ]); diff --git a/src/Core/Products/Models/ProductCustomerPrice.php b/src/Core/Products/Models/ProductCustomerPrice.php index 7e2757a90..d3f17e98e 100644 --- a/src/Core/Products/Models/ProductCustomerPrice.php +++ b/src/Core/Products/Models/ProductCustomerPrice.php @@ -19,6 +19,7 @@ class ProductCustomerPrice extends BaseModel 'tax_id', 'price', 'compare_at_price', + 'product_variant_id' ]; /** diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index 04849576d..72290f259 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -28,6 +28,7 @@ class ProductVariant extends BaseModel */ protected $fillable = [ 'asset_id', + 'product_id', 'options', 'price', 'incoming', diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index 0aa569e37..92a694e2d 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -47,7 +47,7 @@ function ($attribute, $value, $fail) { } $result = DB::table('routes')->wherePath($this->path)->whereSlug($value)->whereNotIn('id', $ids)->exists(); if ($result) { - $fail(); + $fail('The path and slug have already been taken'); } }, ], @@ -65,7 +65,9 @@ function ($attribute, $value, $fail) { */ public function handle() { - return $this->route->update($this->validated()); + $this->route->update($this->validated()); + + return $this->route; } /** diff --git a/src/Core/Routes/Models/Route.php b/src/Core/Routes/Models/Route.php index 5dd145c79..5f52ffe82 100644 --- a/src/Core/Routes/Models/Route.php +++ b/src/Core/Routes/Models/Route.php @@ -24,7 +24,7 @@ class Route extends BaseModel * @var array */ protected $fillable = [ - 'slug', 'default', 'redirect', 'description', 'locale', 'path', + 'slug', 'default', 'redirect', 'description', 'locale', 'path', 'element_type', 'element_id' ]; /** diff --git a/src/Http/Resources/Assets/AssetResource.php b/src/Http/Resources/Assets/AssetResource.php index 98edc53a2..761a46af2 100644 --- a/src/Http/Resources/Assets/AssetResource.php +++ b/src/Http/Resources/Assets/AssetResource.php @@ -26,7 +26,7 @@ public function payload() 'thumbnail' => $this->getThumbnail($this->resource), 'position' => (int) ($pivot ? $pivot->position : 1), 'primary' => (bool) ($pivot ? $pivot->primary : false), - 'url' => $this->url, + 'url' => Storage::disk($this->source->disk)->url($this->location.'/'.$this->filename), ]; if (! $this->external) { diff --git a/tests/Feature/Actions/Customers/AttachUserToCustomerTest.php b/tests/Feature/Actions/Customers/AttachUserToCustomerTest.php index 9f810cf29..79a128ba3 100644 --- a/tests/Feature/Actions/Customers/AttachUserToCustomerTest.php +++ b/tests/Feature/Actions/Customers/AttachUserToCustomerTest.php @@ -7,7 +7,7 @@ use Tests\Stubs\User; /** - * @group foo + * @group customers */ class AttachUserToCustomerTest extends FeatureCase { diff --git a/tests/Feature/Actions/Routes/UpdateRouteTest.php b/tests/Feature/Actions/Routes/UpdateRouteTest.php index 86716e458..178fc6040 100644 --- a/tests/Feature/Actions/Routes/UpdateRouteTest.php +++ b/tests/Feature/Actions/Routes/UpdateRouteTest.php @@ -1,6 +1,6 @@ assertResponseValid($response, '/categories/{categoryId}', 'put'); } - + public function test_validation_works_on_update() { $user = $this->admin(); @@ -93,7 +93,9 @@ public function test_validation_works_on_update() ]); $category = Category::withoutGlobalScopes()->first(); $categoryId = $category->encodedId(); - $response = $this->actingAs($user)->json('PUT', "categories/{$categoryId}"); + $response = $this->actingAs($user)->json('PUT', "categories/{$categoryId}", [ + 'attribute_data' => 'TESTSSTRING' + ]); $response->assertStatus(422); $this->assertResponseValid($response, '/categories/{categoryId}', 'put'); } diff --git a/tests/Unit/Channels/Actions/SetCurrentChannelTest.php b/tests/Unit/Channels/Actions/SetCurrentChannelTest.php index 9db66475a..7f52dbaac 100644 --- a/tests/Unit/Channels/Actions/SetCurrentChannelTest.php +++ b/tests/Unit/Channels/Actions/SetCurrentChannelTest.php @@ -8,7 +8,7 @@ use Tests\Feature\FeatureCase; /** - * @group foo + * @group channels */ class SetCurrentChannelTest extends FeatureCase { diff --git a/tests/Unit/Drafting/Actions/DraftAssetsTest.php b/tests/Unit/Drafting/Actions/DraftAssetsTest.php new file mode 100644 index 000000000..66ea08f66 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftAssetsTest.php @@ -0,0 +1,53 @@ +admin(); + + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(AssetSource::class)->create()->each(function ($source) use ($product) { + $source->assets()->createMany( + factory(Asset::class, 2)->make()->toArray() + ); + + foreach ($source->assets as $asset) { + $product->assets()->attach($asset->id, [ + 'primary' => 1, + 'position' => 1, + 'assetable_type' => Product::class, + ]); + } + }); + + $this->assertCount(2, $product->assets); + $this->assertCount(0, $draft->assets); + + (new DraftAssets)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(2, $draft->refresh()->assets); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftCategoriesTest.php b/tests/Unit/Drafting/Actions/DraftCategoriesTest.php new file mode 100644 index 000000000..ad7dc968c --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftCategoriesTest.php @@ -0,0 +1,41 @@ +admin(); + + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(Category::class, 2)->create()->each(function ($category) use ($product) { + $product->categories()->attach($category); + }); + + + $this->assertCount(2, $product->categories); + $this->assertCount(0, $draft->categories); + + (new DraftCategories)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(2, $draft->refresh()->categories); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftChannelsTest.php b/tests/Unit/Drafting/Actions/DraftChannelsTest.php new file mode 100644 index 000000000..1407f2916 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftChannelsTest.php @@ -0,0 +1,42 @@ +admin(); + + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(Channel::class, 2)->create()->each(function ($channel) use ($product) { + $product->channels()->attach($channel->id, [ + 'published_at' => now() + ]); + }); + + $this->assertCount(2, $product->channels); + $this->assertCount(0, $draft->channels); + + (new DraftChannels)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(2, $draft->refresh()->channels); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php b/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php new file mode 100644 index 000000000..e5c170517 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php @@ -0,0 +1,43 @@ +admin(); + + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(CustomerGroup::class, 2)->create()->each(function ($group) use ($product) { + $product->customerGroups()->attach($group->id, [ + 'purchasable' => true, + 'visible' => true, + ]); + }); + + $this->assertCount(2, $product->customerGroups); + $this->assertCount(0, $draft->customerGroups); + + (new DraftCustomerGroups)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(2, $draft->refresh()->customerGroups); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php b/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php new file mode 100644 index 000000000..b530e8cc8 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php @@ -0,0 +1,46 @@ +admin(); + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + $group = factory(AssociationGroup::class)->create(); + + factory(Product::class, 15)->create()->each(function ($p) use ($group, $product) { + $assoc = new ProductAssociation; + $assoc->group()->associate($group); + $assoc->association()->associate($p); + $assoc->parent()->associate($product); + $assoc->save(); + }); + + $this->assertCount(15, $product->associations); + $this->assertCount(0, $draft->associations); + + (new DraftProductAssociations)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(15, $draft->refresh()->associations); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php new file mode 100644 index 000000000..8b5869f6c --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php @@ -0,0 +1,55 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + $tax = factory(Tax::class)->create(); + + $product = factory(Product::class)->create(); + + $variant = factory(ProductVariant::class)->create([ + 'product_id' => $product->id + ]); + + $pricing = new ProductCustomerPrice; + $pricing->product_variant_id = $variant->id; + $pricing->customer_group_id = $customerGroup->id; + $pricing->tax_id = $tax->id; + $pricing->price = 30; + $pricing->save(); + + + $draft = $variant->replicate(); + $draft->save(); + $draft->update([ + 'draft_parent_id' => $variant->id, + 'drafted_at' => now() + ]); + + $this->assertCount(1, $variant->customerPricing); + $this->assertCount(0, $draft->customerPricing); + + (new DraftProductVariantCustomerPricing)->actingAs($user)->run([ + 'parent' => $variant, + 'draft' => $draft, + ]); + + $this->assertCount(1, $draft->refresh()->customerPricing); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php new file mode 100644 index 000000000..098983414 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php @@ -0,0 +1,52 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + + $product = factory(Product::class)->create(); + + $variant = factory(ProductVariant::class)->create([ + 'product_id' => $product->id + ]); + $draft = $variant->replicate(); + + $tiers = [ + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 1, 'price' => 1.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 2, 'price' => 2.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 3, 'price' => 3.00], + ]; + + $variant->tiers()->createMany($tiers); + + $draft->save(); + $draft->update([ + 'draft_parent_id' => $variant->id, + 'drafted_at' => now() + ]); + + $this->assertCount(count($tiers), $variant->tiers); + $this->assertCount(0, $draft->tiers); + + (new DraftProductVariantTiers)->actingAs($user)->run([ + 'parent' => $variant, + 'draft' => $draft, + ]); + + $this->assertCount(count($tiers), $draft->refresh()->tiers); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php new file mode 100644 index 000000000..f677e0106 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php @@ -0,0 +1,43 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + + $product = factory(Product::class)->create(); + + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(ProductVariant::class)->create([ + 'product_id' => $product->id + ]); + + $this->assertCount(1, $product->variants); + $this->assertCount(0, $draft->variants); + + (new DraftProductVariants)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(1, $draft->refresh()->variants); + } +} diff --git a/tests/Unit/Drafting/Actions/DraftRoutesTest.php b/tests/Unit/Drafting/Actions/DraftRoutesTest.php new file mode 100644 index 000000000..c209349e9 --- /dev/null +++ b/tests/Unit/Drafting/Actions/DraftRoutesTest.php @@ -0,0 +1,41 @@ +admin(); + + $product = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $product->id, + ]); + + factory(Route::class, 2)->create([ + 'element_type' => Product::class, + 'element_id' => $product->id, + ]); + + $this->assertCount(2, $product->routes); + $this->assertCount(0, $draft->routes); + + (new DraftRoutes)->actingAs($user)->run([ + 'parent' => $product, + 'draft' => $draft, + ]); + + $this->assertCount(2, $draft->refresh()->routes); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishAssetsTest.php b/tests/Unit/Drafting/Actions/PublishAssetsTest.php new file mode 100644 index 000000000..ceeb62c41 --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishAssetsTest.php @@ -0,0 +1,51 @@ +admin(); + + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + factory(AssetSource::class)->create()->each(function ($source) use ($draft) { + $source->assets()->createMany( + factory(Asset::class, 2)->make()->toArray() + ); + + foreach ($source->assets as $asset) { + $draft->assets()->attach($asset->id, [ + 'primary' => 1, + 'position' => 1, + 'assetable_type' => Product::class, + ]); + } + }); + + $this->assertCount(2, $draft->assets); + $this->assertCount(0, $parent->assets); + + (new PublishAssets)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(2, $parent->refresh()->assets); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishChannelsTest.php b/tests/Unit/Drafting/Actions/PublishChannelsTest.php new file mode 100644 index 000000000..b39919762 --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishChannelsTest.php @@ -0,0 +1,42 @@ +admin(); + + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + factory(Channel::class, 2)->create()->each(function ($channel) use ($draft) { + $draft->channels()->attach($channel->id, [ + 'published_at' => now() + ]); + }); + + $this->assertCount(2, $draft->channels); + $this->assertCount(0, $parent->channels); + + (new PublishChannels)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(2, $parent->refresh()->channels); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php b/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php new file mode 100644 index 000000000..f0e30d552 --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php @@ -0,0 +1,44 @@ +admin(); + + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + factory(CustomerGroup::class, 2)->create()->each(function ($group) use ($draft) { + $draft->customerGroups()->attach($group->id, [ + 'purchasable' => true, + 'visible' => true, + ]); + }); + + $this->assertCount(2, $draft->customerGroups); + $this->assertCount(0, $parent->customerGroups); + + (new PublishCustomerGroups)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(2, $parent->refresh()->customerGroups); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php b/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php new file mode 100644 index 000000000..11b9004b5 --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php @@ -0,0 +1,46 @@ +admin(); + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + $group = factory(AssociationGroup::class)->create(); + + factory(Product::class, 15)->create()->each(function ($p) use ($group, $draft) { + $assoc = new ProductAssociation; + $assoc->group()->associate($group); + $assoc->association()->associate($p); + $assoc->parent()->associate($draft); + $assoc->save(); + }); + + $this->assertCount(15, $draft->associations); + $this->assertCount(0, $parent->associations); + + (new PublishProductAssociations)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(15, $parent->refresh()->associations); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php new file mode 100644 index 000000000..5ac429c18 --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php @@ -0,0 +1,55 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + $tax = factory(Tax::class)->create(); + + $product = factory(Product::class)->create(); + + $parent = factory(ProductVariant::class)->create([ + 'product_id' => $product->id + ]); + + + $draft = $parent->replicate(); + $draft->save(); + $draft->update([ + 'draft_parent_id' => $parent->id, + 'drafted_at' => now() + ]); + + $pricing = new ProductCustomerPrice; + $pricing->product_variant_id = $draft->id; + $pricing->customer_group_id = $customerGroup->id; + $pricing->tax_id = $tax->id; + $pricing->price = 30; + $pricing->save(); + + $this->assertCount(1, $draft->customerPricing); + $this->assertCount(0, $parent->customerPricing); + + (new PublishProductVariantCustomerPricing)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(1, $parent->refresh()->customerPricing); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php new file mode 100644 index 000000000..67ea313fc --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php @@ -0,0 +1,55 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + + $product = factory(Product::class)->create(); + + $parent = factory(ProductVariant::class)->create([ + 'product_id' => $product->id + ]); + $draft = $parent->replicate(); + + $tiers = [ + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 1, 'price' => 1.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 2, 'price' => 2.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 3, 'price' => 3.00], + ]; + + $draft->save(); + $draft->update([ + 'draft_parent_id' => $parent->id, + 'drafted_at' => now() + ]); + + $draft->tiers()->createMany($tiers); + + + + $this->assertCount(count($tiers), $draft->tiers); + $this->assertCount(0, $parent->tiers); + + (new PublishProductVariantTiers)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(count($tiers), $parent->refresh()->tiers); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php new file mode 100644 index 000000000..b05050c2b --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php @@ -0,0 +1,43 @@ +admin(); + $customerGroup = factory(CustomerGroup::class)->create(); + + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + factory(ProductVariant::class)->create([ + 'product_id' => $draft->id + ]); + + $this->assertCount(1, $draft->variants); + + $this->assertCount(0, $parent->variants); + + (new PublishProductVariants)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(1, $parent->refresh()->variants); + } +} diff --git a/tests/Unit/Drafting/Actions/PublishRoutesTest.php b/tests/Unit/Drafting/Actions/PublishRoutesTest.php new file mode 100644 index 000000000..e3b48597c --- /dev/null +++ b/tests/Unit/Drafting/Actions/PublishRoutesTest.php @@ -0,0 +1,41 @@ +admin(); + + $parent = factory(Product::class)->create(); + $draft = factory(Product::class)->create(); + $draft->update([ + 'drafted_at' => now(), + 'draft_parent_id' => $parent->id, + ]); + + factory(Route::class, 2)->create([ + 'element_type' => Product::class, + 'element_id' => $draft->id, + ]); + + $this->assertCount(2, $draft->routes); + $this->assertCount(0, $parent->routes); + + (new PublishRoutes)->actingAs($user)->run([ + 'parent' => $parent, + 'draft' => $draft, + ]); + + $this->assertCount(2, $parent->refresh()->routes); + } +} diff --git a/tests/Unit/Routes/Actions/CreateRouteTest.php b/tests/Unit/Routes/Actions/CreateRouteTest.php index cc335d26e..e5c511004 100644 --- a/tests/Unit/Routes/Actions/CreateRouteTest.php +++ b/tests/Unit/Routes/Actions/CreateRouteTest.php @@ -1,6 +1,6 @@ actingAs($user)->run([ 'slug' => 'foo-bar', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); @@ -37,7 +37,7 @@ public function test_cant_create_duplicate_routes() (new CreateRoute)->actingAs($user)->run([ 'slug' => 'foo-bar', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); @@ -46,7 +46,7 @@ public function test_cant_create_duplicate_routes() (new CreateRoute)->actingAs($user)->run([ 'slug' => 'foo-bar', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); @@ -54,7 +54,7 @@ public function test_cant_create_duplicate_routes() 'slug' => 'foo-bar', 'path' => 'bar-baz', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); @@ -64,7 +64,7 @@ public function test_cant_create_duplicate_routes() 'slug' => 'foo-bar', 'path' => 'bar-baz', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); } diff --git a/tests/Unit/Routes/Actions/UpdateRouteTest.php b/tests/Unit/Routes/Actions/UpdateRouteTest.php index 95a29e8fd..233466a22 100644 --- a/tests/Unit/Routes/Actions/UpdateRouteTest.php +++ b/tests/Unit/Routes/Actions/UpdateRouteTest.php @@ -1,6 +1,6 @@ assertEquals('bar', $route->slug); } - /** - * @group bah - */ + public function test_cant_update_route_to_another_resources_values() { $user = $this->admin(); @@ -94,7 +92,7 @@ public function test_cant_update_route_to_another_resources_values() 'encoded_id' => $route->encoded_id, 'slug' => 'foo', 'element' => $product, - 'lang' => 'en', + 'locale' => 'en', 'default' => true, ]); } From c05165ffcb74ae4722e253b3a46d638550773b0f Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 16 Feb 2021 14:42:59 +0000 Subject: [PATCH 089/152] Apply fixes from StyleCI (#358) Co-authored-by: Alec Ritson --- database/factories/AssetSourceFactory.php | 2 +- .../factories/AssociationGroupFactory.php | 5 ++-- database/factories/CategoryFactory.php | 6 ++--- routes/api.php | 1 - src/Core/Assets/Drivers/Image.php | 2 +- src/Core/Assets/Models/Asset.php | 2 +- .../Actions/CreateDraftCategory.php | 6 ++--- src/Core/Categories/Actions/RebuildTree.php | 4 +-- .../Commands/RebuildTreeCommand.php | 7 +----- .../Categories/Drafting/CategoryDrafter.php | 22 ++++++++-------- .../Categories/Services/CategoryService.php | 2 +- src/Core/Drafting/Actions/DraftAssets.php | 3 ++- src/Core/Drafting/Actions/DraftCategories.php | 3 ++- src/Core/Drafting/Actions/DraftChannels.php | 4 +-- .../Drafting/Actions/DraftCustomerGroups.php | 2 +- .../Actions/DraftProductAssociations.php | 2 +- .../DraftProductVariantCustomerPricing.php | 2 +- .../Actions/DraftProductVariantTiers.php | 5 ++-- .../Drafting/Actions/DraftProductVariants.php | 5 +--- src/Core/Drafting/Actions/DraftRoutes.php | 3 ++- src/Core/Drafting/Actions/PublishAssets.php | 2 +- src/Core/Drafting/Actions/PublishChannels.php | 4 +-- .../Actions/PublishCustomerGroups.php | 2 +- .../Actions/PublishProductAssociations.php | 2 +- .../PublishProductVariantCustomerPricing.php | 5 ++-- .../Actions/PublishProductVariantTiers.php | 5 ++-- .../Actions/PublishProductVariants.php | 3 +-- src/Core/Drafting/Actions/PublishRoutes.php | 4 +-- src/Core/Drafting/BaseDrafter.php | 3 ++- .../Payments/Actions/FetchPaymentProvider.php | 10 ++++---- .../Payments/Actions/FetchPaymentTypes.php | 3 +-- src/Core/Payments/Actions/FetchProviders.php | 1 - src/Core/Payments/PaymentManager.php | 6 ++--- src/Core/Payments/Providers/StripeIntents.php | 15 +++++------ src/Core/Products/Actions/PublishProduct.php | 5 ---- src/Core/Products/Drafting/ProductDrafter.php | 24 ++++++++---------- .../Products/Models/ProductCustomerPrice.php | 2 +- src/Core/Products/Models/ProductVariant.php | 2 +- src/Core/Routes/Actions/FetchRoute.php | 1 + src/Core/Routes/Actions/UpdateRoute.php | 7 ++---- src/Core/Routes/Models/Route.php | 2 +- .../Actions/Searching/Search.php | 4 +-- .../Elasticsearch/Filters/TextFilter.php | 4 +-- .../Services/ShippingMethodService.php | 12 ++++----- .../Shipping/Services/ShippingZoneService.php | 4 +-- src/Core/Users/Actions/FetchUserAddresses.php | 6 +++-- .../Categories/CategoryController.php | 3 +-- .../Payments/PaymentController.php | 14 +++++------ .../Products/ProductController.php | 25 +++++++++---------- .../Shipping/ShippingZoneResource.php | 2 +- src/Providers/CategoryServiceProvider.php | 8 +++--- .../Categories/CategoryControllerTest.php | 4 +-- .../Unit/Drafting/Actions/DraftAssetsTest.php | 6 ++--- .../Drafting/Actions/DraftCategoriesTest.php | 5 ++-- .../Drafting/Actions/DraftChannelsTest.php | 6 ++--- .../Actions/DraftCustomerGroupsTest.php | 4 +-- .../Actions/DraftProductAssociationsTest.php | 6 ++--- ...DraftProductVariantCustomerPricingTest.php | 15 ++++++----- .../Actions/DraftProductVariantTiersTest.php | 10 ++++---- .../Actions/DraftProductVariantsTest.php | 8 +++--- .../Unit/Drafting/Actions/DraftRoutesTest.php | 6 ++--- .../Drafting/Actions/PublishAssetsTest.php | 4 +-- .../Drafting/Actions/PublishChannelsTest.php | 6 ++--- .../Actions/PublishCustomerGroupsTest.php | 5 ++-- .../PublishProductAssociationsTest.php | 6 ++--- ...blishProductVariantCustomerPricingTest.php | 15 ++++++----- .../PublishProductVariantTiersTest.php | 13 ++++------ .../Actions/PublishProductVariantsTest.php | 8 +++--- .../Drafting/Actions/PublishRoutesTest.php | 6 ++--- tests/Unit/Routes/Actions/UpdateRouteTest.php | 1 - 70 files changed, 194 insertions(+), 218 deletions(-) diff --git a/database/factories/AssetSourceFactory.php b/database/factories/AssetSourceFactory.php index 7af6164ae..1b216d62b 100644 --- a/database/factories/AssetSourceFactory.php +++ b/database/factories/AssetSourceFactory.php @@ -20,6 +20,6 @@ 'handle' => $faker->word, 'disk' => 'public', 'default' => $faker->boolean, - 'path' => $faker->word + 'path' => $faker->word, ]; }); diff --git a/database/factories/AssociationGroupFactory.php b/database/factories/AssociationGroupFactory.php index c1bd670b0..f1b56275c 100644 --- a/database/factories/AssociationGroupFactory.php +++ b/database/factories/AssociationGroupFactory.php @@ -1,8 +1,8 @@ define(AssociationGroup::class, function (Faker $faker) { $name = $faker->unique()->company; + return [ 'name' => $name, - 'handle' => Str::slug($name) + 'handle' => Str::slug($name), ]; }); diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index bcd0aab29..0ef3f2269 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -18,8 +18,8 @@ return [ 'attribute_data' => [ 'name' => [ - 'en' => $faker->word - ] - ] + 'en' => $faker->word, + ], + ], ]; }); diff --git a/routes/api.php b/routes/api.php index 67be7306b..e16fcf533 100644 --- a/routes/api.php +++ b/routes/api.php @@ -321,7 +321,6 @@ $router->get('users/{encoded_id}', '\GetCandy\Api\Core\Users\Actions\FetchUser'); $router->put('users/{encoded_id}', '\GetCandy\Api\Core\Users\Actions\UpdateUser'); - /* * Reusable payments */ diff --git a/src/Core/Assets/Drivers/Image.php b/src/Core/Assets/Drivers/Image.php index 11add81e0..2fa025304 100644 --- a/src/Core/Assets/Drivers/Image.php +++ b/src/Core/Assets/Drivers/Image.php @@ -21,7 +21,7 @@ public function process(array $data, $model = null) { $assetSources = GetCandy::assetSources(); - if ($model && !empty($model->settings['asset_source'])) { + if ($model && ! empty($model->settings['asset_source'])) { $source = GetCandy::assetSources()->getByHandle($model->settings['asset_source']); } else { $source = $assetSources->getDefaultRecord(); diff --git a/src/Core/Assets/Models/Asset.php b/src/Core/Assets/Models/Asset.php index f71f7fdaf..f66c8ec6a 100644 --- a/src/Core/Assets/Models/Asset.php +++ b/src/Core/Assets/Models/Asset.php @@ -36,7 +36,7 @@ class Asset extends BaseModel 'kind', 'external', ]; - + public function scopeImages($query) { return $query->where('kind', '!=', 'application'); diff --git a/src/Core/Categories/Actions/CreateDraftCategory.php b/src/Core/Categories/Actions/CreateDraftCategory.php index c13e4efd2..35bfb4dcb 100644 --- a/src/Core/Categories/Actions/CreateDraftCategory.php +++ b/src/Core/Categories/Actions/CreateDraftCategory.php @@ -3,8 +3,8 @@ namespace GetCandy\Api\Core\Categories\Actions; use Drafting; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use GetCandy\Api\Http\Resources\Categories\CategoryResource; @@ -51,9 +51,9 @@ public function handle($category) return $draft->load($this->resolveEagerRelations()); } - public function response ($response, $request) + public function response($response, $request) { - if (!$response) { + if (! $response) { return $this->errorNotFound(); } diff --git a/src/Core/Categories/Actions/RebuildTree.php b/src/Core/Categories/Actions/RebuildTree.php index 20a09d978..3708f08be 100644 --- a/src/Core/Categories/Actions/RebuildTree.php +++ b/src/Core/Categories/Actions/RebuildTree.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Categories\Actions; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Scaffold\AbstractAction; class RebuildTree extends AbstractAction { @@ -49,7 +49,7 @@ protected function map($category) 'id' => $category->id, 'children' => $category->children->map(function ($category) { return $this->map($category); - })->toArray() + })->toArray(), ]; } } diff --git a/src/Core/Categories/Commands/RebuildTreeCommand.php b/src/Core/Categories/Commands/RebuildTreeCommand.php index 2ab84a1c8..e86f1ef2e 100644 --- a/src/Core/Categories/Commands/RebuildTreeCommand.php +++ b/src/Core/Categories/Commands/RebuildTreeCommand.php @@ -2,13 +2,8 @@ namespace GetCandy\Api\Core\Categories\Commands; -use Ramsey\Uuid\Uuid; -use Illuminate\Console\Command; -use GetCandy\Api\Core\Search\SearchManager; -use Illuminate\Contracts\Events\Dispatcher; -use GetCandy\Api\Core\Categories\Models\Category; -use GetCandy\Api\Core\Search\Actions\IndexDocuments; use GetCandy\Api\Core\Categories\Actions\RebuildTree; +use Illuminate\Console\Command; class RebuildTreeCommand extends Command { diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 543b36ca7..e8b15e13e 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -3,20 +3,20 @@ namespace GetCandy\Api\Core\Categories\Drafting; use DB; -use Versioning; -use Illuminate\Database\Eloquent\Model; -use GetCandy\Api\Core\Drafting\BaseDrafter; -use GetCandy\Api\Core\Events\ModelPublishedEvent; -use GetCandy\Api\Core\Search\Actions\IndexObjects; use GetCandy\Api\Core\Drafting\Actions\DraftAssets; -use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; use GetCandy\Api\Core\Drafting\Actions\DraftChannels; +use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; use GetCandy\Api\Core\Drafting\Actions\PublishAssets; -use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; -use NeonDigital\Drafting\Interfaces\DrafterInterface; use GetCandy\Api\Core\Drafting\Actions\PublishChannels; -use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; +use GetCandy\Api\Core\Drafting\BaseDrafter; +use GetCandy\Api\Core\Events\ModelPublishedEvent; +use GetCandy\Api\Core\Search\Actions\IndexObjects; +use Illuminate\Database\Eloquent\Model; +use NeonDigital\Drafting\Interfaces\DrafterInterface; +use Versioning; class CategoryDrafter extends BaseDrafter implements DrafterInterface { @@ -52,7 +52,7 @@ public function publish(Model $draft) * Go through and assign any products that are for the draft to the parent. */ $draft->products()->update([ - 'category_id' => $parent->id + 'category_id' => $parent->id, ]); // Fire off an event so plugins can update anything their side too. @@ -71,13 +71,11 @@ public function publish(Model $draft) return $parent; }); - } public function create(Model $parent) { return DB::transaction(function () use ($parent) { - $parent = $parent->load([ 'children', 'products', diff --git a/src/Core/Categories/Services/CategoryService.php b/src/Core/Categories/Services/CategoryService.php index d994d8b69..4887a91dd 100644 --- a/src/Core/Categories/Services/CategoryService.php +++ b/src/Core/Categories/Services/CategoryService.php @@ -128,7 +128,7 @@ public function update($hashedId, array $data) { $model = $this->getByHashedId($hashedId, true); - if (!empty($data['attribute_data'])) { + if (! empty($data['attribute_data'])) { $model->attribute_data = $data['attribute_data']; } diff --git a/src/Core/Drafting/Actions/DraftAssets.php b/src/Core/Drafting/Actions/DraftAssets.php index 8fa8e198e..280dba6d2 100644 --- a/src/Core/Drafting/Actions/DraftAssets.php +++ b/src/Core/Drafting/Actions/DraftAssets.php @@ -46,6 +46,7 @@ public function handle() ] ); } + return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftCategories.php b/src/Core/Drafting/Actions/DraftCategories.php index 8ded62172..51f063bea 100644 --- a/src/Core/Drafting/Actions/DraftCategories.php +++ b/src/Core/Drafting/Actions/DraftCategories.php @@ -39,6 +39,7 @@ public function handle() foreach ($this->parent->categories as $category) { $this->draft->categories()->attach($category); } + return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftChannels.php b/src/Core/Drafting/Actions/DraftChannels.php index 1085cd893..3a4274fe8 100644 --- a/src/Core/Drafting/Actions/DraftChannels.php +++ b/src/Core/Drafting/Actions/DraftChannels.php @@ -38,7 +38,7 @@ public function handle() { $channels = $this->parent->channels->mapWithKeys(function ($channel) { return [$channel->id => [ - 'published_at' => $channel->pivot->published_at + 'published_at' => $channel->pivot->published_at, ]]; })->toArray(); @@ -46,4 +46,4 @@ public function handle() return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftCustomerGroups.php b/src/Core/Drafting/Actions/DraftCustomerGroups.php index d7b4a9d58..b200c7d17 100644 --- a/src/Core/Drafting/Actions/DraftCustomerGroups.php +++ b/src/Core/Drafting/Actions/DraftCustomerGroups.php @@ -47,4 +47,4 @@ public function handle() return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftProductAssociations.php b/src/Core/Drafting/Actions/DraftProductAssociations.php index 8f76d1818..718c083d5 100644 --- a/src/Core/Drafting/Actions/DraftProductAssociations.php +++ b/src/Core/Drafting/Actions/DraftProductAssociations.php @@ -44,4 +44,4 @@ public function handle() return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php b/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php index d8f580a9c..bedb1827a 100644 --- a/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php +++ b/src/Core/Drafting/Actions/DraftProductVariantCustomerPricing.php @@ -49,4 +49,4 @@ public function handle() return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftProductVariantTiers.php b/src/Core/Drafting/Actions/DraftProductVariantTiers.php index 04f9f2a17..8cb78c9da 100644 --- a/src/Core/Drafting/Actions/DraftProductVariantTiers.php +++ b/src/Core/Drafting/Actions/DraftProductVariantTiers.php @@ -41,10 +41,11 @@ public function handle() return $tierPrice->only([ 'customer_group_id', 'lower_limit', - 'price' + 'price', ]); }) ); + return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftProductVariants.php b/src/Core/Drafting/Actions/DraftProductVariants.php index 54999579e..e185d8129 100644 --- a/src/Core/Drafting/Actions/DraftProductVariants.php +++ b/src/Core/Drafting/Actions/DraftProductVariants.php @@ -2,10 +2,7 @@ namespace GetCandy\Api\Core\Drafting\Actions; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Drafting\Actions\DraftProductVariantTiers; -use GetCandy\Api\Core\Drafting\Actions\DraftProductVariantCustomerPricing; class DraftProductVariants extends AbstractAction { @@ -61,4 +58,4 @@ public function handle() return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/DraftRoutes.php b/src/Core/Drafting/Actions/DraftRoutes.php index 5dc2a8c66..eef8174e4 100644 --- a/src/Core/Drafting/Actions/DraftRoutes.php +++ b/src/Core/Drafting/Actions/DraftRoutes.php @@ -44,6 +44,7 @@ public function handle() $draftRoute->draft_parent_id = $parentRoute->id; $draftRoute->save(); }); + return $this->draft; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishAssets.php b/src/Core/Drafting/Actions/PublishAssets.php index 2e08198d6..16037900d 100644 --- a/src/Core/Drafting/Actions/PublishAssets.php +++ b/src/Core/Drafting/Actions/PublishAssets.php @@ -47,4 +47,4 @@ public function handle() return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishChannels.php b/src/Core/Drafting/Actions/PublishChannels.php index 136135229..5a95099b8 100644 --- a/src/Core/Drafting/Actions/PublishChannels.php +++ b/src/Core/Drafting/Actions/PublishChannels.php @@ -38,11 +38,11 @@ public function handle() { $channels = $this->draft->channels->mapWithKeys(function ($channel) { return [$channel->id => [ - 'published_at' => $channel->pivot->published_at + 'published_at' => $channel->pivot->published_at, ]]; })->toArray(); $this->parent->channels()->sync($channels); return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishCustomerGroups.php b/src/Core/Drafting/Actions/PublishCustomerGroups.php index 10c43f578..457946585 100644 --- a/src/Core/Drafting/Actions/PublishCustomerGroups.php +++ b/src/Core/Drafting/Actions/PublishCustomerGroups.php @@ -47,4 +47,4 @@ public function handle() return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishProductAssociations.php b/src/Core/Drafting/Actions/PublishProductAssociations.php index e55eb47e1..42521c78e 100644 --- a/src/Core/Drafting/Actions/PublishProductAssociations.php +++ b/src/Core/Drafting/Actions/PublishProductAssociations.php @@ -60,4 +60,4 @@ public function handle() return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php index 9da240d9b..20ad34503 100644 --- a/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php +++ b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php @@ -46,9 +46,10 @@ public function handle() continue; } $incoming->update([ - 'product_variant_id' => $this->parent->id + 'product_variant_id' => $this->parent->id, ]); } + return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishProductVariantTiers.php b/src/Core/Drafting/Actions/PublishProductVariantTiers.php index 9d75e68df..ca8bc4e68 100644 --- a/src/Core/Drafting/Actions/PublishProductVariantTiers.php +++ b/src/Core/Drafting/Actions/PublishProductVariantTiers.php @@ -46,9 +46,10 @@ public function handle() continue; } $incoming->update([ - 'product_variant_id' => $this->parent->id + 'product_variant_id' => $this->parent->id, ]); } + return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishProductVariants.php b/src/Core/Drafting/Actions/PublishProductVariants.php index 070f1bbbf..3ca59e3ec 100644 --- a/src/Core/Drafting/Actions/PublishProductVariants.php +++ b/src/Core/Drafting/Actions/PublishProductVariants.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Core\Drafting\Actions; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\AbstractAction; class PublishProductVariants extends AbstractAction @@ -62,4 +61,4 @@ public function handle() return $this->parent->refresh(); } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/Actions/PublishRoutes.php b/src/Core/Drafting/Actions/PublishRoutes.php index 26cc04b97..07dce9648 100644 --- a/src/Core/Drafting/Actions/PublishRoutes.php +++ b/src/Core/Drafting/Actions/PublishRoutes.php @@ -42,11 +42,11 @@ public function handle() $route->forceDelete(); } else { $route->update([ - 'element_id' => $this->parent->id + 'element_id' => $this->parent->id, ]); } } return $this->parent; } -} \ No newline at end of file +} diff --git a/src/Core/Drafting/BaseDrafter.php b/src/Core/Drafting/BaseDrafter.php index e945c7a26..e00d7022b 100644 --- a/src/Core/Drafting/BaseDrafter.php +++ b/src/Core/Drafting/BaseDrafter.php @@ -23,6 +23,7 @@ protected function addAction($target, $incoming) { if (is_array($incoming)) { $this->{$target} = array_merge($this->{$target}, $incoming); + return; } array_push($this->{$target}, $incoming); @@ -31,7 +32,7 @@ protected function addAction($target, $incoming) protected function callActions(array $actions, array $params = []) { foreach ($actions as $action) { - if (!class_exists($action)) { + if (! class_exists($action)) { Log::error("Tried to call action ${action} but it doesn't exist"); continue; } diff --git a/src/Core/Payments/Actions/FetchPaymentProvider.php b/src/Core/Payments/Actions/FetchPaymentProvider.php index 1a20a4295..b19644b5e 100644 --- a/src/Core/Payments/Actions/FetchPaymentProvider.php +++ b/src/Core/Payments/Actions/FetchPaymentProvider.php @@ -2,12 +2,11 @@ namespace GetCandy\Api\Core\Payments\Actions; -use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Addresses\Models\Address; -use GetCandy\Api\Core\Payments\PaymentContract; -use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use GetCandy\Api\Core\Orders\Interfaces\OrderCriteriaInterface; +use GetCandy\Api\Core\Payments\PaymentContract; use GetCandy\Api\Core\Payments\Resources\PaymentProviderResource; +use GetCandy\Api\Core\Traits\ReturnsJsonResponses; +use Lorisleiva\Actions\Action; class FetchPaymentProvider extends Action { @@ -61,9 +60,10 @@ public function handle($handle, OrderCriteriaInterface $orders, PaymentContract */ public function response($result, $request) { - if (!$result) { + if (! $result) { return $this->errorNotFound(); } + return new PaymentProviderResource($result); } } diff --git a/src/Core/Payments/Actions/FetchPaymentTypes.php b/src/Core/Payments/Actions/FetchPaymentTypes.php index 87317038f..76f28ab30 100644 --- a/src/Core/Payments/Actions/FetchPaymentTypes.php +++ b/src/Core/Payments/Actions/FetchPaymentTypes.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Core\Payments\Actions; -use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Addresses\Models\Address; use GetCandy\Api\Core\Payments\Models\PaymentType; use GetCandy\Api\Core\Payments\Resources\PaymentTypeCollection; +use Lorisleiva\Actions\Action; class FetchPaymentTypes extends Action { diff --git a/src/Core/Payments/Actions/FetchProviders.php b/src/Core/Payments/Actions/FetchProviders.php index 33587056a..c696ebe46 100644 --- a/src/Core/Payments/Actions/FetchProviders.php +++ b/src/Core/Payments/Actions/FetchProviders.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Core\Payments\Actions; -use GetCandy\Api\Core\Addresses\Models\Address; use Lorisleiva\Actions\Action; class FetchProviders extends Action diff --git a/src/Core/Payments/PaymentManager.php b/src/Core/Payments/PaymentManager.php index aec572499..9533ff784 100644 --- a/src/Core/Payments/PaymentManager.php +++ b/src/Core/Payments/PaymentManager.php @@ -2,12 +2,12 @@ namespace GetCandy\Api\Core\Payments; -use Illuminate\Support\Manager; -use GetCandy\Api\Core\Payments\Providers\PayPal; +use GetCandy\Api\Core\Payments\Providers\Braintree; use GetCandy\Api\Core\Payments\Providers\Offline; +use GetCandy\Api\Core\Payments\Providers\PayPal; use GetCandy\Api\Core\Payments\Providers\SagePay; -use GetCandy\Api\Core\Payments\Providers\Braintree; use GetCandy\Api\Core\Payments\Providers\StripeIntents; +use Illuminate\Support\Manager; class PaymentManager extends Manager implements PaymentContract { diff --git a/src/Core/Payments/Providers/StripeIntents.php b/src/Core/Payments/Providers/StripeIntents.php index 33d98aa8f..77f5daa08 100644 --- a/src/Core/Payments/Providers/StripeIntents.php +++ b/src/Core/Payments/Providers/StripeIntents.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Core\Payments\Providers; -use Stripe\StripeClient; -use Stripe\PaymentIntent; -use GetCandy\Api\Core\Payments\PaymentResponse; use GetCandy\Api\Core\Payments\Models\Transaction; +use GetCandy\Api\Core\Payments\PaymentResponse; +use Stripe\StripeClient; class StripeIntents extends AbstractProvider { @@ -40,7 +39,7 @@ public function getClientToken() $paymentIntentToken = $meta['stripe_payment_intent'] ?? null; - if (!$paymentIntentToken) { + if (! $paymentIntentToken) { $intent = $client->paymentIntents->create([ 'amount' => $this->order->order_total, 'currency' => $this->order->currency, @@ -49,8 +48,9 @@ public function getClientToken() ]); $meta['stripe_payment_intent'] = $intent->id; $this->order->update([ - 'meta' => $meta + 'meta' => $meta, ]); + return $intent->client_secret; } else { $intent = $client->paymentIntents->retrieve( @@ -61,7 +61,7 @@ public function getClientToken() $client->paymentIntents->update( $intent->id, [ - 'amount' => $this->order->order_total + 'amount' => $this->order->order_total, ] ); } @@ -86,7 +86,7 @@ public function charge() $meta = $this->order->meta; $paymentIntentToken = $meta['stripe_payment_intent'] ?? null; - if (!$paymentIntentToken) { + if (! $paymentIntentToken) { return new PaymentResponse(false); } @@ -118,6 +118,7 @@ public function charge() // Get the payment method $response = new PaymentResponse(true, 'Payment Success', []); + return $response->transaction($transaction); } diff --git a/src/Core/Products/Actions/PublishProduct.php b/src/Core/Products/Actions/PublishProduct.php index ac660753d..07973a142 100644 --- a/src/Core/Products/Actions/PublishProduct.php +++ b/src/Core/Products/Actions/PublishProduct.php @@ -5,11 +5,7 @@ namespace GetCandy\Api\Core\Products\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Foundation\Actions\DecodeId; -use GetCandy\Api\Core\Products\Models\ProductFamily; use GetCandy\Api\Http\Resources\Products\ProductResource; -use GetCandy\Api\Core\Products\Resources\ProductFamilyResource; -use GetCandy\Api\Core\Attributes\Actions\AttachModelToAttributes; class PublishProduct extends AbstractAction { @@ -40,7 +36,6 @@ public function rules(): array */ public function handle() { - } /** diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 5c2d42d20..d3e5ecb25 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -3,24 +3,23 @@ namespace GetCandy\Api\Core\Products\Drafting; use DB; -use Versioning; -use Illuminate\Database\Eloquent\Model; -use GetCandy\Api\Core\Drafting\BaseDrafter; use GetCandy\Api\Core\Drafting\Actions\DraftAssets; -use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; -use GetCandy\Api\Core\Drafting\Actions\DraftChannels; -use GetCandy\Api\Core\Drafting\Actions\PublishAssets; -use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; -use NeonDigital\Drafting\Interfaces\DrafterInterface; use GetCandy\Api\Core\Drafting\Actions\DraftCategories; -use GetCandy\Api\Core\Drafting\Actions\PublishChannels; +use GetCandy\Api\Core\Drafting\Actions\DraftChannels; use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; +use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Drafting\Actions\PublishChannels; use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; -use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; -use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; - +use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; +use GetCandy\Api\Core\Drafting\BaseDrafter; +use Illuminate\Database\Eloquent\Model; +use NeonDigital\Drafting\Interfaces\DrafterInterface; +use Versioning; class ProductDrafter extends BaseDrafter implements DrafterInterface { @@ -117,7 +116,6 @@ public function publish(Model $draft) 'parent' => $parent, ]); - // Categories $existingCategories = $parent->categories; diff --git a/src/Core/Products/Models/ProductCustomerPrice.php b/src/Core/Products/Models/ProductCustomerPrice.php index d3f17e98e..e40e050f9 100644 --- a/src/Core/Products/Models/ProductCustomerPrice.php +++ b/src/Core/Products/Models/ProductCustomerPrice.php @@ -19,7 +19,7 @@ class ProductCustomerPrice extends BaseModel 'tax_id', 'price', 'compare_at_price', - 'product_variant_id' + 'product_variant_id', ]; /** diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index 72290f259..a4cfb148e 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -102,7 +102,7 @@ public function getOptionsAttribute($val) $values = []; $option_data = $this->product ? $this->product->option_data : []; - if (!is_array($val)) { + if (! is_array($val)) { $val = json_decode($val, true); } foreach ($val as $option => $value) { diff --git a/src/Core/Routes/Actions/FetchRoute.php b/src/Core/Routes/Actions/FetchRoute.php index 23452f686..494cd7824 100644 --- a/src/Core/Routes/Actions/FetchRoute.php +++ b/src/Core/Routes/Actions/FetchRoute.php @@ -50,6 +50,7 @@ public function handle() if (! $this->id) { $query = Route::getQuery(); + return $this->compileSearchQuery($query, $this->search)->first(); } diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index 92a694e2d..ae7c8cb7f 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -2,12 +2,9 @@ namespace GetCandy\Api\Core\Routes\Actions; -use Illuminate\Validation\Rule; -use Illuminate\Support\Facades\DB; -use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Foundation\Actions\DecodeId; use GetCandy\Api\Core\Routes\Resources\RouteResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class UpdateRoute extends AbstractAction { diff --git a/src/Core/Routes/Models/Route.php b/src/Core/Routes/Models/Route.php index 5f52ffe82..4546fc448 100644 --- a/src/Core/Routes/Models/Route.php +++ b/src/Core/Routes/Models/Route.php @@ -24,7 +24,7 @@ class Route extends BaseModel * @var array */ protected $fillable = [ - 'slug', 'default', 'redirect', 'description', 'locale', 'path', 'element_type', 'element_id' + 'slug', 'default', 'redirect', 'description', 'locale', 'path', 'element_type', 'element_id', ]; /** diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index b19075221..9487cbc4a 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -62,7 +62,7 @@ public function rules() */ public function handle() { - $this->set('search_type', $this->search_type ? Str::plural($this->search_type) : 'products'); + $this->set('search_type', $this->search_type ? Str::plural($this->search_type) : 'products'); if (! $this->index) { $prefix = config('getcandy.search.index_prefix'); @@ -72,6 +72,7 @@ public function handle() $this->filters = $this->filters ? collect(explode(',', $this->filters))->mapWithKeys(function ($filter) { [$label, $value] = explode(':', $filter); + return [$label => $value]; })->toArray() : []; @@ -216,7 +217,6 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); - $resource = ProductCollection::class; if ($this->search_type == 'categories') { diff --git a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php index cfb9c6da8..71dc87e39 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php +++ b/src/Core/Search/Drivers/Elasticsearch/Filters/TextFilter.php @@ -23,8 +23,8 @@ public function getQuery() if (is_array($value)) { // If the first value is not numeric, then we assume we are // matching across multiple text values for a field, not a range. - if (!is_numeric($value[0])) { - foreach ($value as $val) { + if (! is_numeric($value[0])) { + foreach ($value as $val) { $match = new Match; $match->setFieldAnalyzer($this->field.'.filter', 'keyword'); $match->setFieldQuery($this->field.'.filter', $val); diff --git a/src/Core/Shipping/Services/ShippingMethodService.php b/src/Core/Shipping/Services/ShippingMethodService.php index de8ff30f4..aee3d3208 100644 --- a/src/Core/Shipping/Services/ShippingMethodService.php +++ b/src/Core/Shipping/Services/ShippingMethodService.php @@ -3,14 +3,14 @@ namespace GetCandy\Api\Core\Shipping\Services; use GetCandy; -use Illuminate\Pipeline\Pipeline; -use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Shipping\ShippingCalculator; -use GetCandy\Api\Core\Shipping\Models\ShippingZone; +use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; use GetCandy\Api\Core\Baskets\Services\BasketService; +use GetCandy\Api\Core\Channels\Models\Channel; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Shipping\Models\ShippingMethod; -use GetCandy\Api\Core\Attributes\Events\AttributableSavedEvent; +use GetCandy\Api\Core\Shipping\Models\ShippingZone; +use GetCandy\Api\Core\Shipping\ShippingCalculator; +use Illuminate\Pipeline\Pipeline; class ShippingMethodService extends BaseService { diff --git a/src/Core/Shipping/Services/ShippingZoneService.php b/src/Core/Shipping/Services/ShippingZoneService.php index 573b7471c..5cf3d14d2 100644 --- a/src/Core/Shipping/Services/ShippingZoneService.php +++ b/src/Core/Shipping/Services/ShippingZoneService.php @@ -3,9 +3,9 @@ namespace GetCandy\Api\Core\Shipping\Services; use GetCandy; -use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Countries\Models\Country; use GetCandy\Api\Core\Foundation\Actions\DecodeIds; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Shipping\Models\ShippingZone; class ShippingZoneService extends BaseService @@ -75,7 +75,7 @@ public function update($id, array $data) if (! empty($data['countries'])) { $countryIds = DecodeIds::run([ 'encoded_ids' => $data['countries'], - 'model' => Country::class + 'model' => Country::class, ]); $shipping->countries()->sync($countryIds); } diff --git a/src/Core/Users/Actions/FetchUserAddresses.php b/src/Core/Users/Actions/FetchUserAddresses.php index 23b827341..24be13b3a 100644 --- a/src/Core/Users/Actions/FetchUserAddresses.php +++ b/src/Core/Users/Actions/FetchUserAddresses.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Users\Actions; -use Lorisleiva\Actions\Action; -use GetCandy\Api\Core\Traits\ReturnsJsonResponses; use GetCandy\Api\Core\Addresses\Resources\AddressCollection; +use GetCandy\Api\Core\Traits\ReturnsJsonResponses; +use Lorisleiva\Actions\Action; class FetchUserAddresses extends Action { @@ -20,6 +20,7 @@ public function authorize() if ($this->user_id) { return $this->user()->can('manage-users'); } + return $this->user(); } @@ -48,6 +49,7 @@ public function handle() 'include' => 'addresses.country', ])->addresses; } + return $this->user()->load('addresses.country')->addresses; } diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index 6ced11560..c7f0df840 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -177,11 +177,10 @@ public function publishDraft($id, Request $request) } $category = $this->service->findById($id[0], [], true); - if (!$category) { + if (! $category) { return $this->errorNotFound(); } - $category = Drafting::with('categories')->publish($category); return new CategoryResource($category->load($this->parseIncludes($request->include))); diff --git a/src/Http/Controllers/Payments/PaymentController.php b/src/Http/Controllers/Payments/PaymentController.php index eb9b31a1a..11de483e9 100644 --- a/src/Http/Controllers/Payments/PaymentController.php +++ b/src/Http/Controllers/Payments/PaymentController.php @@ -3,18 +3,18 @@ namespace GetCandy\Api\Http\Controllers\Payments; use GetCandy; -use Illuminate\Http\Request; -use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Core\Payments\Exceptions\AlreadyRefundedException; +use GetCandy\Api\Core\Payments\Exceptions\TransactionAmountException; use GetCandy\Api\Core\Payments\Models\Transaction; -use GetCandy\Api\Http\Requests\Payments\VoidRequest; -use GetCandy\Api\Http\Resources\Orders\OrderResource; +use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Payments\RefundRequest; -use Illuminate\Database\Eloquent\ModelNotFoundException; use GetCandy\Api\Http\Requests\Payments\ValidateThreeDRequest; +use GetCandy\Api\Http\Requests\Payments\VoidRequest; +use GetCandy\Api\Http\Resources\Orders\OrderResource; use GetCandy\Api\Http\Resources\Payments\PaymentProviderResource; use GetCandy\Api\Http\Resources\Transactions\TransactionResource; -use GetCandy\Api\Core\Payments\Exceptions\AlreadyRefundedException; -use GetCandy\Api\Core\Payments\Exceptions\TransactionAmountException; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Request; class PaymentController extends BaseController { diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index 5f75bf06b..cf9a66eb3 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -2,29 +2,28 @@ namespace GetCandy\Api\Http\Controllers\Products; -use Hashids; use Drafting; use GetCandy; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; +use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\ProductCriteria; -use GetCandy\Api\Http\Controllers\BaseController; +use GetCandy\Api\Core\Products\Services\ProductService; use GetCandy\Api\Exceptions\InvalidLanguageException; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Products\CreateRequest; use GetCandy\Api\Http\Requests\Products\DeleteRequest; -use GetCandy\Api\Http\Requests\Products\UpdateRequest; -use GetCandy\Api\Core\Products\Services\ProductService; -use Illuminate\Database\Eloquent\ModelNotFoundException; use GetCandy\Api\Http\Requests\Products\DuplicateRequest; +use GetCandy\Api\Http\Requests\Products\UpdateRequest; +use GetCandy\Api\Http\Resources\Products\ProductCollection; +use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; use GetCandy\Api\Http\Resources\Products\ProductResource; +use Hashids; +use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\HttpException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; -use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Core\Baskets\Interfaces\BasketCriteriaInterface; -use GetCandy\Api\Core\Products\Factories\ProductDuplicateFactory; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use GetCandy\Api\Http\Resources\Products\ProductRecommendationCollection; class ProductController extends BaseController { @@ -111,7 +110,7 @@ public function publishDraft($id, Request $request) } $product = $this->service->findById($id[0], [], true); - if (!$product) { + if (! $product) { return $this->errorNotFound(); } diff --git a/src/Http/Resources/Shipping/ShippingZoneResource.php b/src/Http/Resources/Shipping/ShippingZoneResource.php index 86b0621f0..0875987a5 100644 --- a/src/Http/Resources/Shipping/ShippingZoneResource.php +++ b/src/Http/Resources/Shipping/ShippingZoneResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Http\Resources\Shipping; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Countries\Resources\CountryCollection; +use GetCandy\Api\Http\Resources\AbstractResource; class ShippingZoneResource extends AbstractResource { diff --git a/src/Providers/CategoryServiceProvider.php b/src/Providers/CategoryServiceProvider.php index c7ac4b461..2e1a0f223 100644 --- a/src/Providers/CategoryServiceProvider.php +++ b/src/Providers/CategoryServiceProvider.php @@ -3,14 +3,14 @@ namespace GetCandy\Api\Providers; use Drafting; -use Versioning; -use Illuminate\Support\ServiceProvider; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Categories\Commands\RebuildTreeCommand; use GetCandy\Api\Core\Categories\Drafting\CategoryDrafter; -use GetCandy\Api\Core\Categories\Services\CategoryService; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Categories\Observers\CategoryObserver; +use GetCandy\Api\Core\Categories\Services\CategoryService; use GetCandy\Api\Core\Categories\Versioning\CategoryVersioner; +use Illuminate\Support\ServiceProvider; +use Versioning; class CategoryServiceProvider extends ServiceProvider { diff --git a/tests/Feature/Http/Categories/CategoryControllerTest.php b/tests/Feature/Http/Categories/CategoryControllerTest.php index d619364e0..8e32e295b 100644 --- a/tests/Feature/Http/Categories/CategoryControllerTest.php +++ b/tests/Feature/Http/Categories/CategoryControllerTest.php @@ -80,7 +80,7 @@ public function test_can_update_a_category() $this->assertResponseValid($response, '/categories/{categoryId}', 'put'); } - + public function test_validation_works_on_update() { $user = $this->admin(); @@ -94,7 +94,7 @@ public function test_validation_works_on_update() $category = Category::withoutGlobalScopes()->first(); $categoryId = $category->encodedId(); $response = $this->actingAs($user)->json('PUT', "categories/{$categoryId}", [ - 'attribute_data' => 'TESTSSTRING' + 'attribute_data' => 'TESTSSTRING', ]); $response->assertStatus(422); $this->assertResponseValid($response, '/categories/{categoryId}', 'put'); diff --git a/tests/Unit/Drafting/Actions/DraftAssetsTest.php b/tests/Unit/Drafting/Actions/DraftAssetsTest.php index 66ea08f66..ce299e378 100644 --- a/tests/Unit/Drafting/Actions/DraftAssetsTest.php +++ b/tests/Unit/Drafting/Actions/DraftAssetsTest.php @@ -2,13 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Products\Models\Product; -use Illuminate\Validation\ValidationException; use GetCandy\Api\Core\Assets\Models\AssetSource; -use GetCandy\Api\Core\Routes\Actions\CreateRoute; use GetCandy\Api\Core\Drafting\Actions\DraftAssets; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/DraftCategoriesTest.php b/tests/Unit/Drafting/Actions/DraftCategoriesTest.php index ad7dc968c..8a05041d1 100644 --- a/tests/Unit/Drafting/Actions/DraftCategoriesTest.php +++ b/tests/Unit/Drafting/Actions/DraftCategoriesTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Drafting\Actions\DraftCategories; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting @@ -27,7 +27,6 @@ public function test_can_draft_model_categories() $product->categories()->attach($category); }); - $this->assertCount(2, $product->categories); $this->assertCount(0, $draft->categories); diff --git a/tests/Unit/Drafting/Actions/DraftChannelsTest.php b/tests/Unit/Drafting/Actions/DraftChannelsTest.php index 1407f2916..a71448432 100644 --- a/tests/Unit/Drafting/Actions/DraftChannelsTest.php +++ b/tests/Unit/Drafting/Actions/DraftChannelsTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Drafting\Actions\DraftChannels; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting @@ -25,7 +25,7 @@ public function test_can_draft_model_channels() factory(Channel::class, 2)->create()->each(function ($channel) use ($product) { $product->channels()->attach($channel->id, [ - 'published_at' => now() + 'published_at' => now(), ]); }); diff --git a/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php b/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php index e5c170517..b134f02a9 100644 --- a/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php +++ b/tests/Unit/Drafting/Actions/DraftCustomerGroupsTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php b/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php index b530e8cc8..e8d827188 100644 --- a/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php +++ b/tests/Unit/Drafting/Actions/DraftProductAssociationsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\Products\Models\ProductAssociation; use GetCandy\Api\Core\Associations\Models\AssociationGroup; use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductAssociation; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php index 8b5869f6c..33fee3c50 100644 --- a/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php +++ b/tests/Unit/Drafting/Actions/DraftProductVariantCustomerPricingTest.php @@ -2,13 +2,13 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; use GetCandy\Api\Core\Drafting\Actions\DraftProductVariantCustomerPricing; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use GetCandy\Api\Core\Taxes\Models\Tax; +use Tests\TestCase; /** * @group drafting @@ -24,7 +24,7 @@ public function test_can_draft_variant_customer_pricing() $product = factory(Product::class)->create(); $variant = factory(ProductVariant::class)->create([ - 'product_id' => $product->id + 'product_id' => $product->id, ]); $pricing = new ProductCustomerPrice; @@ -34,12 +34,11 @@ public function test_can_draft_variant_customer_pricing() $pricing->price = 30; $pricing->save(); - $draft = $variant->replicate(); $draft->save(); $draft->update([ 'draft_parent_id' => $variant->id, - 'drafted_at' => now() + 'drafted_at' => now(), ]); $this->assertCount(1, $variant->customerPricing); diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php index 098983414..a5c64ba19 100644 --- a/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php +++ b/tests/Unit/Drafting/Actions/DraftProductVariantTiersTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Core\Drafting\Actions\DraftProductVariantTiers; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use Tests\TestCase; /** * @group drafting @@ -21,7 +21,7 @@ public function test_can_draft_variant_tiers() $product = factory(Product::class)->create(); $variant = factory(ProductVariant::class)->create([ - 'product_id' => $product->id + 'product_id' => $product->id, ]); $draft = $variant->replicate(); @@ -36,7 +36,7 @@ public function test_can_draft_variant_tiers() $draft->save(); $draft->update([ 'draft_parent_id' => $variant->id, - 'drafted_at' => now() + 'drafted_at' => now(), ]); $this->assertCount(count($tiers), $variant->tiers); diff --git a/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php b/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php index f677e0106..4652a6dcd 100644 --- a/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php +++ b/tests/Unit/Drafting/Actions/DraftProductVariantsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use Tests\TestCase; /** * @group drafting @@ -27,7 +27,7 @@ public function test_can_draft_variant_customer_pricing() ]); factory(ProductVariant::class)->create([ - 'product_id' => $product->id + 'product_id' => $product->id, ]); $this->assertCount(1, $product->variants); diff --git a/tests/Unit/Drafting/Actions/DraftRoutesTest.php b/tests/Unit/Drafting/Actions/DraftRoutesTest.php index c209349e9..2ab4583d2 100644 --- a/tests/Unit/Drafting/Actions/DraftRoutesTest.php +++ b/tests/Unit/Drafting/Actions/DraftRoutesTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Routes\Models\Route; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/PublishAssetsTest.php b/tests/Unit/Drafting/Actions/PublishAssetsTest.php index ceeb62c41..b7ce511bb 100644 --- a/tests/Unit/Drafting/Actions/PublishAssetsTest.php +++ b/tests/Unit/Drafting/Actions/PublishAssetsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Assets\Models\AssetSource; use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/PublishChannelsTest.php b/tests/Unit/Drafting/Actions/PublishChannelsTest.php index b39919762..685343f59 100644 --- a/tests/Unit/Drafting/Actions/PublishChannelsTest.php +++ b/tests/Unit/Drafting/Actions/PublishChannelsTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Drafting\Actions\PublishChannels; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting @@ -25,7 +25,7 @@ public function test_can_publish_model_channels() factory(Channel::class, 2)->create()->each(function ($channel) use ($draft) { $draft->channels()->attach($channel->id, [ - 'published_at' => now() + 'published_at' => now(), ]); }); diff --git a/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php b/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php index f0e30d552..058040cb9 100644 --- a/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php +++ b/tests/Unit/Drafting/Actions/PublishCustomerGroupsTest.php @@ -2,11 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; +use GetCandy\Api\Core\Products\Models\Product; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php b/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php index 11b9004b5..8c745b8a1 100644 --- a/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php +++ b/tests/Unit/Drafting/Actions/PublishProductAssociationsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\Products\Models\ProductAssociation; use GetCandy\Api\Core\Associations\Models\AssociationGroup; use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductAssociation; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php index 5ac429c18..b6f182081 100644 --- a/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php +++ b/tests/Unit/Drafting/Actions/PublishProductVariantCustomerPricingTest.php @@ -2,13 +2,13 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; use GetCandy\Api\Core\Drafting\Actions\PublishProductVariantCustomerPricing; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use GetCandy\Api\Core\Taxes\Models\Tax; +use Tests\TestCase; /** * @group drafting @@ -24,15 +24,14 @@ public function test_can_publish_variant_customer_pricing() $product = factory(Product::class)->create(); $parent = factory(ProductVariant::class)->create([ - 'product_id' => $product->id + 'product_id' => $product->id, ]); - $draft = $parent->replicate(); $draft->save(); $draft->update([ 'draft_parent_id' => $parent->id, - 'drafted_at' => now() + 'drafted_at' => now(), ]); $pricing = new ProductCustomerPrice; diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php index 67ea313fc..f40faafdf 100644 --- a/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php +++ b/tests/Unit/Drafting/Actions/PublishProductVariantTiersTest.php @@ -2,12 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Core\Drafting\Actions\DraftProductVariantTiers; use GetCandy\Api\Core\Drafting\Actions\PublishProductVariantTiers; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use Tests\TestCase; /** * @group drafting @@ -22,7 +21,7 @@ public function test_can_publish_variant_tiers() $product = factory(Product::class)->create(); $parent = factory(ProductVariant::class)->create([ - 'product_id' => $product->id + 'product_id' => $product->id, ]); $draft = $parent->replicate(); @@ -35,13 +34,11 @@ public function test_can_publish_variant_tiers() $draft->save(); $draft->update([ 'draft_parent_id' => $parent->id, - 'drafted_at' => now() + 'drafted_at' => now(), ]); $draft->tiers()->createMany($tiers); - - $this->assertCount(count($tiers), $draft->tiers); $this->assertCount(0, $parent->tiers); diff --git a/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php b/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php index b05050c2b..d52bf888d 100644 --- a/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php +++ b/tests/Unit/Drafting/Actions/PublishProductVariantsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; -use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductVariant; +use Tests\TestCase; /** * @group drafting @@ -26,7 +26,7 @@ public function test_can_publish_product_variants() ]); factory(ProductVariant::class)->create([ - 'product_id' => $draft->id + 'product_id' => $draft->id, ]); $this->assertCount(1, $draft->variants); diff --git a/tests/Unit/Drafting/Actions/PublishRoutesTest.php b/tests/Unit/Drafting/Actions/PublishRoutesTest.php index e3b48597c..f0a87928a 100644 --- a/tests/Unit/Drafting/Actions/PublishRoutesTest.php +++ b/tests/Unit/Drafting/Actions/PublishRoutesTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Drafting\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Routes\Models\Route; +use Tests\TestCase; /** * @group drafting diff --git a/tests/Unit/Routes/Actions/UpdateRouteTest.php b/tests/Unit/Routes/Actions/UpdateRouteTest.php index 233466a22..b754bdbd2 100644 --- a/tests/Unit/Routes/Actions/UpdateRouteTest.php +++ b/tests/Unit/Routes/Actions/UpdateRouteTest.php @@ -69,7 +69,6 @@ public function test_can_update_slug_and_path_to_the_same_values() $this->assertEquals('bar', $route->slug); } - public function test_cant_update_route_to_another_resources_values() { $user = $this->admin(); From c1ea5e007a75e7b6494e0890daf08d16a37bb75c Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 14:58:15 +0000 Subject: [PATCH 090/152] Add initial 0.12 changelog --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57db9a4fb..709c2a18f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,60 @@ + +## 0.12.0 +### Upgrading + +Update the composer package + +```bash +$ composer update @getcandy/candy-api +``` + +### High Impact Changes + +#### Maintenance Migrations + +Some columns have been added/removed from the database. The tables/columns affected are: + +- `orders` + - Removed `company_name` column as it wasn't being used and we have other columns for that now + - Added `billing_company_name` and `shipping_company_name` columns. +- `countries` + - Remove `country` column in favour of a `country_id` relationship + +#### Eager loading relations for the current user + +Previously when returning the current user via `users/current` there was some hard coded includes, this has been replaced to allow the `include` query parameter. +You should update any calls to this endpoint if you rely on included resources. The previous default includes were: + +```php +['addresses.country', 'roles.permissions', 'customer', 'savedBaskets.basket.lines'] +``` + +### Drafting has changed + +The way drafting previously worked has now been refactored to be less destructive. You should reindex your products before going back into the hub to get everything in sync. + +You can do this by running `php artisan candy:products:reindex` and `php artisan candy:categories:reindex` + +### 🐞 Fixes +- Fixed an issue that was causing a indefinite wildcard search on products +- Allow certain fields to be nullable on a customer address (`company_name`, `address_two`, `address_three`) +- Fixed some issues on route creation +- Fixed issue where shipping method relationships were not having their timestamps updated + +### ⭐ Improvements + +- Slight optimisation for Elasticsearch and the fields it returns +- Drafting and Publishing of a draft will now run in a transaction, you can also extend the drafting functionality in your plugins. + +### 🏗️ Additions + +- Added endpoint to get a payment provider via it's given ID +- Added Stripe Payment Intents provider +- Added a `RebuildTree` action and command for categories, so if your category tree is messed up you can run `candy:categories:rebuild` +- Added `user/addresses` endpoint to get the current users saved addresses + +--- + # 0.3.8 - [added] Added CategoryStoredEvent when editing categories From c9e71845f617ebfd03d7bea500d5f7530fbbf1b4 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 16 Feb 2021 15:06:59 +0000 Subject: [PATCH 091/152] Remove empty line --- src/Core/Products/Drafting/ProductDrafter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 4777dc9ab..24cbf441e 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -33,7 +33,7 @@ public function create(Model $parent) 'channels', 'customerGroups', ]); - + $draft = $parent->replicate(); $draft->drafted_at = now(); $draft->draft_parent_id = $parent->id; From c8d2d44d6237581b724ebd7daab3cf2b3f88b2f3 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Feb 2021 10:19:39 +0000 Subject: [PATCH 092/152] Fixes --- openapi/assets/paths/assets.reorder.yaml | 15 ++++ .../assets/requests/ReorderAssetsBody.yaml | 18 +++++ openapi/openapi.yaml | 2 + routes/api.php | 1 + src/Core/Assets/Actions/ReorderAssets.php | 78 +++++++++++++++++++ src/Core/Assets/Drivers/BaseUploadDriver.php | 10 ++- src/Core/Assets/Drivers/BaseUrlDriver.php | 18 ----- src/Core/Assets/Models/Assetable.php | 2 + src/Core/Drafting/Actions/PublishRoutes.php | 6 +- .../Products/Versioning/ProductVersioner.php | 7 +- src/Core/Routes/Actions/UpdateRoute.php | 17 +++- src/Core/Routes/Services/RouteService.php | 9 +++ src/Core/Scaffold/BaseService.php | 14 +++- src/Core/Search/Actions/Search.php | 1 + .../Actions/Searching/Search.php | 58 +++++++++++++- .../Drivers/Elasticsearch/Elasticsearch.php | 2 + src/Core/Traits/Assetable.php | 2 +- .../ProductVariants/UpdateRequest.php | 2 + src/Http/Requests/Products/UpdateRequest.php | 5 +- 19 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 openapi/assets/paths/assets.reorder.yaml create mode 100644 openapi/assets/requests/ReorderAssetsBody.yaml create mode 100644 src/Core/Assets/Actions/ReorderAssets.php diff --git a/openapi/assets/paths/assets.reorder.yaml b/openapi/assets/paths/assets.reorder.yaml new file mode 100644 index 000000000..645f61bdf --- /dev/null +++ b/openapi/assets/paths/assets.reorder.yaml @@ -0,0 +1,15 @@ +post: + summary: Reorder Assets + tags: + - Assets + responses: + '204': + description: No Content + operationId: reorder-assets + requestBody: + content: + multipart/form-data: + schema: + $ref: '../requests/ReorderAssetsBody.yaml' + description: '' + description: Reorder assets for a model diff --git a/openapi/assets/requests/ReorderAssetsBody.yaml b/openapi/assets/requests/ReorderAssetsBody.yaml new file mode 100644 index 000000000..01d82ba61 --- /dev/null +++ b/openapi/assets/requests/ReorderAssetsBody.yaml @@ -0,0 +1,18 @@ +title: ReorderAssetsBody +type: object +properties: + assets: + type: array + items: + type: object + properties: + id: + type: string + position: + type: integer + primary: + type: integer + assetable_id: + type: string + assetable_type: + type: string diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 1870787b6..cb1a49914 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -25,6 +25,8 @@ paths: $ref: './assets/paths/assets.yaml' '/assets/simple': $ref: './assets/paths/assets.simple.yaml' + '/assets/reorder': + $ref: './assets/paths/assets.reorder.yaml' '/assets/{assetId}/detach/{ownerId}': $ref: './assets/paths/assets.id.detach.id.yaml' '/associations/groups': diff --git a/routes/api.php b/routes/api.php index e16fcf533..d14b168ef 100644 --- a/routes/api.php +++ b/routes/api.php @@ -27,6 +27,7 @@ $router->put('assets', 'Assets\AssetController@updateAll'); $router->post('assets/simple', 'Assets\AssetController@storeSimple'); + $router->post('assets/reorder', '\GetCandy\Api\Core\Assets\Actions\ReorderAssets'); $router->post('assets/{assetId}/detach/{ownerId}', 'Assets\AssetController@detach'); $router->resource('assets', 'Assets\AssetController', [ 'except' => ['index', 'edit', 'create', 'show'], diff --git a/src/Core/Assets/Actions/ReorderAssets.php b/src/Core/Assets/Actions/ReorderAssets.php new file mode 100644 index 000000000..932729f4b --- /dev/null +++ b/src/Core/Assets/Actions/ReorderAssets.php @@ -0,0 +1,78 @@ +user()->can('manage-drafts'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'assets' => 'required|array', + 'assets.*.id' => 'required', + 'assets.*.position' => 'required', + 'assets.*.primary' => 'nullable', + 'assetable_type' => 'required', + 'assetable_id' => 'required' + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + switch ($this->assetable_type) { + case 'category': + $realId = (new Category)->decodeId($this->assetable_id); + $model = Category::withDrafted()->find($realId); + break; + default: + $realId = (new Product)->decodeId($this->assetable_id); + $model = Product::withDrafted()->find($realId); + break; + } + + $assets = collect($this->assets)->mapWithKeys(function ($asset) { + $assetId = (new Asset)->decodeId($asset['id']); + unset($asset['id']); + return [ + $assetId => $asset + ]; + }); + + $model->assets()->sync($assets->toArray()); + } + + public function response($result, $request) + { + return $this->respondWithNoContent(); + } +} diff --git a/src/Core/Assets/Drivers/BaseUploadDriver.php b/src/Core/Assets/Drivers/BaseUploadDriver.php index 4ecdb9f58..7d6913cdc 100644 --- a/src/Core/Assets/Drivers/BaseUploadDriver.php +++ b/src/Core/Assets/Drivers/BaseUploadDriver.php @@ -88,8 +88,14 @@ public function process(array $data, $model) $source = GetCandy::assetSources()->getByHandle($model->settings['asset_source']); $asset = $this->prepare($data, $source); $data['file']->storeAs($asset->location, $asset->filename, $source->disk); - $model->assets()->save($asset); - + $asset->save(); + if ($model) { + $model->assets()->attach($asset, [ + 'primary' => ! $model->assets()->images()->exists(), + 'assetable_type' => get_class($model), + 'position' => $model->assets()->count() + 1, + ]); + } return $asset; } } diff --git a/src/Core/Assets/Drivers/BaseUrlDriver.php b/src/Core/Assets/Drivers/BaseUrlDriver.php index f79e4cc81..a682f829a 100644 --- a/src/Core/Assets/Drivers/BaseUrlDriver.php +++ b/src/Core/Assets/Drivers/BaseUrlDriver.php @@ -66,24 +66,6 @@ public function process(array $data, Model $model = null) $asset->save(); - if ($model) { - if ($model->assets()->count()) { - // Get anything that isn't an "application"; - $image = $model->assets()->where('kind', '!=', 'application')->first(); - if (! $image) { - $asset->primary = true; - } - } else { - $asset->primary = true; - } - $asset->save(); - $model->assets()->attach($asset, [ - 'primary' => ! $model->assets()->images()->exists(), - 'assetable_type' => get_class($model), - 'position' => $model->assets()->count() + 1, - ]); - } - dispatch(new GenerateTransforms($asset)); return $asset; diff --git a/src/Core/Assets/Models/Assetable.php b/src/Core/Assets/Models/Assetable.php index 962886005..cb4f9cdbd 100644 --- a/src/Core/Assets/Models/Assetable.php +++ b/src/Core/Assets/Models/Assetable.php @@ -6,6 +6,8 @@ class Assetable extends Pivot { + protected $table = 'assetables'; + public function asset() { return $this->belongsTo(Asset::class); diff --git a/src/Core/Drafting/Actions/PublishRoutes.php b/src/Core/Drafting/Actions/PublishRoutes.php index 07dce9648..16d930508 100644 --- a/src/Core/Drafting/Actions/PublishRoutes.php +++ b/src/Core/Drafting/Actions/PublishRoutes.php @@ -37,9 +37,13 @@ public function rules() public function handle() { foreach ($this->draft->routes as $route) { + // dd($route->publishedParent); if ($route->publishedParent) { - $route->publishedParent->update($route->toArray()); + $route->publishedParent->update( + $route->only(['default', 'redirect', 'slug', 'locale', 'description', 'path']) + ); $route->forceDelete(); + // dd($route); } else { $route->update([ 'element_id' => $this->parent->id, diff --git a/src/Core/Products/Versioning/ProductVersioner.php b/src/Core/Products/Versioning/ProductVersioner.php index e3e6fb653..dd87b1e4a 100644 --- a/src/Core/Products/Versioning/ProductVersioner.php +++ b/src/Core/Products/Versioning/ProductVersioner.php @@ -63,8 +63,9 @@ public function create(Model $product, $relationId = null, $originatorId = null) $this->createFromObject($group, $version->id, $data); } + // dd($product->routes); // Routes - foreach ($product->routes()->get() as $route) { + foreach ($product->routes as $route) { $this->createFromObject($route, $version->id); } @@ -78,6 +79,7 @@ public function create(Model $product, $relationId = null, $originatorId = null) public function restore($version) { + $current = $version->versionable; // Do we already have a draft?? @@ -86,6 +88,7 @@ public function restore($version) if ($draft) { $draft->forceDelete(); } + // Okay so, hydrate this draft... $data = $version->model_data; unset($data['id']); @@ -95,7 +98,6 @@ public function restore($version) // Make it a draft $product->drafted_at = now(); $product->draft_parent_id = $version->versionable_id; - $product->save(); $product->attribute_data = $data['attribute_data']; $product->save(); @@ -133,7 +135,6 @@ public function restore($version) $route->drafted_at = now(); $route->draft_parent_id = $relation->versionable_id; $route->save(); - break; case Asset::class: $data = $relation->model_data; diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index ae7c8cb7f..014afabbd 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -2,9 +2,10 @@ namespace GetCandy\Api\Core\Routes\Actions; -use GetCandy\Api\Core\Routes\Resources\RouteResource; -use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Routes\Models\Route; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Routes\Resources\RouteResource; class UpdateRoute extends AbstractAction { @@ -64,6 +65,18 @@ public function handle() { $this->route->update($this->validated()); + if ($this->route->default) { + // Need to make sure we unset any defaults of any siblings + // as we can only have one + Route::whereElementType($this->route->element_type) + ->whereElementId($this->route->element_id) + ->where('id', '!=', $this->route->id) + ->update([ + 'default' => false, + ]); + } + + return $this->route; } diff --git a/src/Core/Routes/Services/RouteService.php b/src/Core/Routes/Services/RouteService.php index 1a1b7f036..571e53619 100644 --- a/src/Core/Routes/Services/RouteService.php +++ b/src/Core/Routes/Services/RouteService.php @@ -34,8 +34,17 @@ public function update($hashedId, array $data) $model = $this->getByHashedId($hashedId); $model->slug = $data['slug']; $model->default = $data['default']; + + // Cannot be a default route and a redirect. + if (!empty($data['default'])) { + $model->redirect = false; + } + $model->save(); + + \Log::debug('hit'); + return $model; } diff --git a/src/Core/Scaffold/BaseService.php b/src/Core/Scaffold/BaseService.php index 1ac385c38..732491c85 100644 --- a/src/Core/Scaffold/BaseService.php +++ b/src/Core/Scaffold/BaseService.php @@ -446,12 +446,24 @@ public function createUrl($hashedId, array $data) } } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { } + + $existingDefault = $model->routes->first(function ($route) { + return $route->default; + }); + + if ($existingDefault && !empty($data['default'])) { + $existingDefault->update([ + 'default' => false, + ]); + } + $route = $model->routes()->create([ 'locale' => $data['locale'], + 'path' => $data['path'] ?? null, 'slug' => $data['slug'], 'description' => ! empty($data['description']) ? $data['description'] : null, 'redirect' => ! empty($data['redirect']) ? true : false, - 'default' => false, + 'default' => $data['default'] ?? false, ]); return $route; diff --git a/src/Core/Search/Actions/Search.php b/src/Core/Search/Actions/Search.php index 0f5f95a3a..a7e6bf5fa 100644 --- a/src/Core/Search/Actions/Search.php +++ b/src/Core/Search/Actions/Search.php @@ -37,6 +37,7 @@ public function rules() */ public function handle(SearchManagerContract $search) { + \Log::debug("Fetching search driver: " . now()->format('v')); $driver = $search->with($this->driver); return $driver->search($this->request ?? $this->params); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index d0c8be152..66d12fcd8 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -25,6 +25,9 @@ class Search extends Action 'category-filter', ]; + + protected $start; + /** * Determine if the user is authorized to make this action. * @@ -62,8 +65,11 @@ public function rules() */ public function handle() { + $this->start = now(); $this->set('search_type', $this->search_type ? Str::plural($this->search_type) : 'products'); + \Log::debug("Start: " . now()->subMillisecond($this->start->format('v'))->format('v')); + if (! $this->index) { $prefix = config('getcandy.search.index_prefix'); $language = app()->getLocale(); @@ -81,9 +87,16 @@ public function handle() $this->language = $this->language ?: app()->getLocale(); $this->set('category', $this->category ? explode(':', $this->category) : []); + \Log::debug("Building client: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $client = FetchClient::run(); + \Log::debug("Adding search term: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $term = $this->term ? FetchTerm::run($this->attributes) : null; + + \Log::debug("Fetching filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $filters = FetchFilters::run([ 'category' => $this->category, 'filters' => $this->filters, @@ -99,6 +112,8 @@ public function handle() $boolQuery = new BoolQuery; + \Log::debug("Setting term and suggestion: " . now()->subMillisecond($this->start->format('v'))->format('v')); + if ($term) { $boolQuery->addMust($term); @@ -108,6 +123,9 @@ public function handle() ]); } + \Log::debug("Fetching aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $aggregations = FetchAggregations::run(); $query = SetExcludedFields::run(['query' => $query]); @@ -115,6 +133,8 @@ public function handle() // Set filters as post filters $postFilter = new BoolQuery; + \Log::debug("Applying pre filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $preFilters = $filters->filter(function ($filter) { return in_array($filter->handle, $this->topFilters); }); @@ -126,6 +146,9 @@ public function handle() ); }); + \Log::debug("Applying post filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $postFilters = $filters->filter(function ($filter) { return ! in_array($filter->handle, $this->topFilters); }); @@ -145,6 +168,9 @@ public function handle() $query->setPostFilter($postFilter); + \Log::debug("Applying aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + // // $globalAggregation = new \Elastica\Aggregation\GlobalAggregation('all_products'); foreach ($aggregations as $aggregation) { if (method_exists($aggregation, 'get')) { @@ -157,14 +183,22 @@ public function handle() } } + \Log::debug("Setting query: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $query->setQuery($boolQuery); + \Log::debug("Set sorting: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $query = SetSorting::run([ 'query' => $query, 'type' => $this->search_type, 'sort' => $this->sort, ]); + \Log::debug("Set highlighting: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $query->setHighlight(config('getcandy.search.highlight') ?? [ 'pre_tags' => [''], 'post_tags' => [''], @@ -178,9 +212,13 @@ public function handle() $query = $query->setSource(false)->setStoredFields([]); + \Log::debug("Initialising the search HTTP client: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $search = new ElasticaSearch($client); - return $search + \Log::debug("Before ES Query: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $result = $search ->addIndex( $this->index ?: config('getcandy.search.index') ) @@ -188,6 +226,10 @@ public function handle() ElasticaSearch::OPTION_SEARCH_TYPE, ElasticaSearch::OPTION_SEARCH_TYPE_DFS_QUERY_THEN_FETCH )->search($query); + + \Log::debug("After ES Query: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + return $result; } /** @@ -198,6 +240,7 @@ public function handle() */ public function jsonResponse($result, $request) { + \Log::debug("Got response: " . now()->subMillisecond($this->start->format('v'))->format('v')); $ids = collect(); $results = collect($result->getResults()); @@ -207,10 +250,15 @@ public function jsonResponse($result, $request) } } + \Log::debug("Mapping Aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $aggregations = MapAggregations::run([ 'aggregations' => $result->getAggregations(), ]); + \Log::debug("Fetching searched IDs: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $models = FetchSearchedIds::run([ 'model' => $this->search_type == 'products' ? Product::class : Category::class, 'encoded_ids' => $ids->toArray(), @@ -218,6 +266,8 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); + \Log::debug("Got IDs: " . now()->subMillisecond($this->start->format('v'))->format('v')); + $resource = ProductCollection::class; if ($this->search_type == 'categories') { @@ -231,12 +281,16 @@ public function jsonResponse($result, $request) $this->page ?: 1 ); - return (new $resource($paginator))->additional([ + \Log::debug("Building response : " . now()->subMillisecond($this->start->format('v'))->format('v')); + + $response = (new $resource($paginator))->additional([ 'meta' => [ 'count' => $models->count(), 'aggregations' => $aggregations, 'highlight' => $result->getQuery()->getParam('highlight'), ], ]); + + return $response; } } diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index e3354764b..5a1bc34c0 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -99,6 +99,8 @@ public function delete($documents) public function search($data) { if ($data instanceof Request) { + \Log::debug("Running search as a controller action: " . now()->format('v')); + return (new Search)->runAsController($data); } diff --git a/src/Core/Traits/Assetable.php b/src/Core/Traits/Assetable.php index 13516fec1..38a4847f1 100644 --- a/src/Core/Traits/Assetable.php +++ b/src/Core/Traits/Assetable.php @@ -18,7 +18,7 @@ public function assets() 'position', 'primary', 'assetable_type', - ])->orderBy('position', 'asc'); + ])->withTimestamps()->orderBy('position', 'asc'); } public function primaryAsset() diff --git a/src/Http/Requests/ProductVariants/UpdateRequest.php b/src/Http/Requests/ProductVariants/UpdateRequest.php index 188fe669a..2b9b5d3cc 100644 --- a/src/Http/Requests/ProductVariants/UpdateRequest.php +++ b/src/Http/Requests/ProductVariants/UpdateRequest.php @@ -29,6 +29,8 @@ public function rules(ProductVariant $variant) 'sku' => 'required', 'pricing' => 'array', 'pricing.*.customer_group_id' => 'required|hashid_is_valid:'.CustomerGroup::class, + 'tiers' => 'nullable|array', + 'tiers.*.lower_limit' => 'numeric|min:2' ]; } } diff --git a/src/Http/Requests/Products/UpdateRequest.php b/src/Http/Requests/Products/UpdateRequest.php index 46422ba7b..27d791e85 100644 --- a/src/Http/Requests/Products/UpdateRequest.php +++ b/src/Http/Requests/Products/UpdateRequest.php @@ -3,9 +3,10 @@ namespace GetCandy\Api\Http\Requests\Products; use GetCandy; +use GetCandy\Api\Http\Requests\FormRequest; +use GetCandy\Api\Core\Products\Models\ProductFamily; use GetCandy\Api\Core\Channels\Actions\FetchDefaultChannel; use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; -use GetCandy\Api\Http\Requests\FormRequest; class UpdateRequest extends FormRequest { @@ -27,7 +28,7 @@ public function authorize() public function rules() { $ruleset = [ - 'family_id' => 'hashid_is_valid:product_families', + 'family_id' => 'hashid_is_valid:' . ProductFamily::class, 'layout_id' => 'hashid_is_valid:layouts', 'attribute_data' => 'array', ]; From 5029cdf26cfc68af702ee7786d7966997dc7fe4c Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Feb 2021 14:41:07 +0000 Subject: [PATCH 093/152] Fix some publishing issues --- src/Core/Drafting/Actions/PublishProductAssociations.php | 4 +++- .../Drafting/Actions/PublishProductVariantCustomerPricing.php | 4 +++- src/Core/Drafting/Actions/PublishProductVariantTiers.php | 4 +++- src/Core/Drafting/Actions/PublishProductVariants.php | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Core/Drafting/Actions/PublishProductAssociations.php b/src/Core/Drafting/Actions/PublishProductAssociations.php index 42521c78e..524a06056 100644 --- a/src/Core/Drafting/Actions/PublishProductAssociations.php +++ b/src/Core/Drafting/Actions/PublishProductAssociations.php @@ -49,7 +49,9 @@ public function handle() return $assoc->association_id = $incoming->association_id; }); if ($existing) { - $existing->update($incoming->toArray()); + $existing->update( + collect($incoming->toArray())->except(['id', 'product_id'])->toArray() + ); continue; } // If it doesn't exist, reassign the product_id diff --git a/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php index 20ad34503..06cc4fcc6 100644 --- a/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php +++ b/src/Core/Drafting/Actions/PublishProductVariantCustomerPricing.php @@ -41,7 +41,9 @@ public function handle() return $existing->customer_group_id === $incoming->customer_group_id; }); if ($existing) { - $existing->update($price->toArray()); + $existing->update( + collect($incoming->toArray())->except(['id', 'product_variant_id'])->toArray() + ); $incoming->forceDelete(); continue; } diff --git a/src/Core/Drafting/Actions/PublishProductVariantTiers.php b/src/Core/Drafting/Actions/PublishProductVariantTiers.php index ca8bc4e68..3106de0ab 100644 --- a/src/Core/Drafting/Actions/PublishProductVariantTiers.php +++ b/src/Core/Drafting/Actions/PublishProductVariantTiers.php @@ -41,7 +41,9 @@ public function handle() return $existing->customer_group_id === $incoming->customer_group_id; }); if ($existing) { - $existing->update($incoming->toArray()); + $existing->update( + collect($incoming->toArray())->except(['id', 'product_variant_id'])->toArray() + ); $incoming->forceDelete(); continue; } diff --git a/src/Core/Drafting/Actions/PublishProductVariants.php b/src/Core/Drafting/Actions/PublishProductVariants.php index 3ca59e3ec..f972b9040 100644 --- a/src/Core/Drafting/Actions/PublishProductVariants.php +++ b/src/Core/Drafting/Actions/PublishProductVariants.php @@ -41,7 +41,9 @@ public function handle() foreach ($variants as $incoming) { if ($incoming->publishedParent) { $parent = $incoming->publishedParent; - $parent->update($incoming->toArray()); + $parent->update( + collect($incoming->toArray())->except(['id', 'product_id'])->toArray() + ); (new PublishProductVariantCustomerPricing)->actingAs($this->user())->run([ 'draft' => $incoming, From fa5a16ed0011d3ca70f7337dc9fe7237f9fa527d Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Feb 2021 14:41:15 +0000 Subject: [PATCH 094/152] Fix factory --- database/factories/ProductFactory.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php index e902fcbe5..3f382971d 100644 --- a/database/factories/ProductFactory.php +++ b/database/factories/ProductFactory.php @@ -18,9 +18,7 @@ return [ 'attribute_data' => [ 'name' => [ - 'webstore' => [ - 'en' => $faker->name, - ], + 'en' => $faker->name, ], ], ]; From 7816f34838246389055d069bbe64217cedace176 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Feb 2021 14:59:54 +0000 Subject: [PATCH 095/152] Allow path to be passed through on update --- src/Core/Routes/Actions/UpdateRoute.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index 014afabbd..71a7c6546 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -34,6 +34,7 @@ public function rules(): array ]); return [ + 'path' => 'nullable', 'slug' => [ 'required', function ($attribute, $value, $fail) { From 92ce28efee0729fa47b9761e0cc1ff8dbf866722 Mon Sep 17 00:00:00 2001 From: Alec Date: Sun, 21 Feb 2021 14:27:38 +0000 Subject: [PATCH 096/152] Versioning updates --- .../RestoreProductVariantCustomerPricing.php | 59 +++++ .../Versioning/RestoreProductVariantTiers.php | 59 +++++ .../Versioning/VersionProductAssociations.php | 48 ++++ .../VersionProductVariantCustomerPricing.php | 48 ++++ .../Versioning/VersionProductVariantTiers.php | 48 ++++ .../Versioning/VersionProductVariants.php | 60 +++++ src/Core/Products/Drafting/ProductDrafter.php | 2 +- src/Core/Products/Models/ProductVariant.php | 6 +- .../Versioning/ProductVariantVersioner.php | 10 - .../Products/Versioning/ProductVersioner.php | 221 ++++++++---------- src/Core/Versioning/Actions/CreateVersion.php | 62 +++++ src/Core/Versioning/Actions/RestoreAssets.php | 53 +++++ .../Versioning/Actions/RestoreChannels.php | 58 +++++ .../Actions/RestoreCustomerGroups.php | 57 +++++ .../Actions/RestoreProductVariants.php | 93 ++++++++ src/Core/Versioning/Actions/RestoreRoutes.php | 52 +++++ src/Core/Versioning/Actions/VersionAssets.php | 71 ++++++ .../Versioning/Actions/VersionCategories.php | 49 ++++ .../Versioning/Actions/VersionChannels.php | 49 ++++ .../Versioning/Actions/VersionCollections.php | 48 ++++ .../Actions/VersionCustomerGroups.php | 49 ++++ src/Core/Versioning/Actions/VersionRoutes.php | 48 ++++ src/Core/Versioning/BaseVersioner.php | 42 ++++ .../Resources/Versioning/VersionResource.php | 8 +- .../VersionProductAssociationsTest.php | 52 +++++ ...rsionProductVariantCustomerPricingTest.php | 58 +++++ .../VersionProductVariantTiersTest.php | 55 +++++ .../Versioning/VersionProductVariantsTest.php | 48 ++++ .../Versioning/Actions/CreateVersionTest.php | 33 +++ .../Versioning/Actions/VersionAssetsTest.php | 55 +++++ .../Actions/VersionCategoriesTest.php | 52 +++++ .../Actions/VersionChannelsTest.php | 54 +++++ .../Actions/VersionCustomerGroupsTest.php | 56 +++++ .../Versioning/Actions/VersionRoutesTest.php | 56 +++++ 34 files changed, 1674 insertions(+), 145 deletions(-) create mode 100644 src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php create mode 100644 src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php create mode 100644 src/Core/Products/Actions/Versioning/VersionProductAssociations.php create mode 100644 src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php create mode 100644 src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php create mode 100644 src/Core/Products/Actions/Versioning/VersionProductVariants.php create mode 100644 src/Core/Versioning/Actions/CreateVersion.php create mode 100644 src/Core/Versioning/Actions/RestoreAssets.php create mode 100644 src/Core/Versioning/Actions/RestoreChannels.php create mode 100644 src/Core/Versioning/Actions/RestoreCustomerGroups.php create mode 100644 src/Core/Versioning/Actions/RestoreProductVariants.php create mode 100644 src/Core/Versioning/Actions/RestoreRoutes.php create mode 100644 src/Core/Versioning/Actions/VersionAssets.php create mode 100644 src/Core/Versioning/Actions/VersionCategories.php create mode 100644 src/Core/Versioning/Actions/VersionChannels.php create mode 100644 src/Core/Versioning/Actions/VersionCollections.php create mode 100644 src/Core/Versioning/Actions/VersionCustomerGroups.php create mode 100644 src/Core/Versioning/Actions/VersionRoutes.php create mode 100644 src/Core/Versioning/BaseVersioner.php create mode 100644 tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php create mode 100644 tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php create mode 100644 tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php create mode 100644 tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php create mode 100644 tests/Unit/Versioning/Actions/CreateVersionTest.php create mode 100644 tests/Unit/Versioning/Actions/VersionAssetsTest.php create mode 100644 tests/Unit/Versioning/Actions/VersionCategoriesTest.php create mode 100644 tests/Unit/Versioning/Actions/VersionChannelsTest.php create mode 100644 tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php create mode 100644 tests/Unit/Versioning/Actions/VersionRoutesTest.php diff --git a/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php b/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php new file mode 100644 index 000000000..38698505f --- /dev/null +++ b/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php @@ -0,0 +1,59 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Remove all the customer prices as we're going to re add them. + $this->draft->customerPricing()->delete(); + + $groups = FetchCustomerGroups::run([ + 'paginate' => false, + ])->pluck('id'); + + // We can only restore tiered pricing for customer groups + // that still exist in the db, we don't really want to assume anything. + $this->versions->filter(function ($version) use ($groups) { + return $groups->contains($version->model_data['customer_group_id']); + })->each(function ($version) { + $data = collect($version->model_data)->except(['id', 'product_variant_id', 'created_at', 'updated_at']); + $this->draft->customerPricing()->create($data->toArray()); + }); + + return $this->draft; + } +} diff --git a/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php b/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php new file mode 100644 index 000000000..e573d35a3 --- /dev/null +++ b/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php @@ -0,0 +1,59 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Remove all the tiers we're going to re add them. + $this->draft->tiers()->delete(); + + $groups = FetchCustomerGroups::run([ + 'paginate' => false, + ])->pluck('id'); + + // We can only restore tiered pricing for customer groups + // that still exist in the db, we don't really want to assume anything. + $this->versions->filter(function ($version) use ($groups) { + return $groups->contains($version->model_data['customer_group_id']); + })->each(function ($version) { + $data = collect($version->model_data)->except(['id', 'product_variant_id']); + $this->draft->tiers()->create($data->toArray()); + }); + + return $this->draft; + } +} diff --git a/src/Core/Products/Actions/Versioning/VersionProductAssociations.php b/src/Core/Products/Actions/Versioning/VersionProductAssociations.php new file mode 100644 index 000000000..2d81b5b78 --- /dev/null +++ b/src/Core/Products/Actions/Versioning/VersionProductAssociations.php @@ -0,0 +1,48 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->associations as $assoc) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $assoc, + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php b/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php new file mode 100644 index 000000000..cddcd392e --- /dev/null +++ b/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php @@ -0,0 +1,48 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'variant' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->variant->customerPricing as $pricing) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $pricing, + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php b/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php new file mode 100644 index 000000000..445c3e419 --- /dev/null +++ b/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php @@ -0,0 +1,48 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'variant' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->variant->tiers as $tier) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $tier, + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariants.php b/src/Core/Products/Actions/Versioning/VersionProductVariants.php new file mode 100644 index 000000000..fc5024d32 --- /dev/null +++ b/src/Core/Products/Actions/Versioning/VersionProductVariants.php @@ -0,0 +1,60 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'product' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Create our base version. + foreach ($this->product->variants as $variant) { + $variantVersion = (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $variant, + 'relation' => $this->version, + ]); + (new VersionProductVariantTiers)->actingAs($this->user())->run([ + 'version' => $variantVersion, + 'variant' => $variant, + ]); + (new VersionProductVariantCustomerPricing)->actingAs($this->user())->run([ + 'version' => $variantVersion, + 'variant' => $variant, + ]); + } + + return $this->version; + } +} diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 24cbf441e..bf474e334 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -94,7 +94,7 @@ public function publish(Model $draft) // Get any current versions and assign them to this new product. // Create a version of the parent before we publish these changes - Versioning::with('products')->create($parent, null, $parent->id); + Versioning::with('products')->create($parent); // Publish any attributes etc $parent->attribute_data = $draft->attribute_data; diff --git a/src/Core/Products/Models/ProductVariant.php b/src/Core/Products/Models/ProductVariant.php index a4cfb148e..b9d6888ac 100644 --- a/src/Core/Products/Models/ProductVariant.php +++ b/src/Core/Products/Models/ProductVariant.php @@ -101,11 +101,10 @@ public function getOptionsAttribute($val) { $values = []; $option_data = $this->product ? $this->product->option_data : []; - if (! is_array($val)) { $val = json_decode($val, true); } - foreach ($val as $option => $value) { + foreach ($val ?? [] as $option => $value) { if (! empty($data = $option_data[$option])) { $values[$option] = $data['options'][$value]['values'] ?? [ 'en' => null, @@ -120,6 +119,9 @@ public function setOptionsAttribute($val) { $options = []; + if (! is_array($val)) { + $val = json_decode($val, true); + } if (! $this->id) { foreach ($val as $option => $value) { if (is_array($value)) { diff --git a/src/Core/Products/Versioning/ProductVariantVersioner.php b/src/Core/Products/Versioning/ProductVariantVersioner.php index a32fcfbe8..7341f0791 100644 --- a/src/Core/Products/Versioning/ProductVariantVersioner.php +++ b/src/Core/Products/Versioning/ProductVariantVersioner.php @@ -12,16 +12,6 @@ class ProductVariantVersioner extends AbstractVersioner implements VersionerInte public function create(Model $variant, $relationId = null) { $version = $this->createFromObject($variant, $relationId); - - // Tiers - foreach ($variant->tiers ?? [] as $tier) { - $this->createFromObject($tier, $version->id); - } - - // Prices - foreach ($variant->customerPricing as $price) { - $this->createFromObject($price, $version->id); - } } public function restore($version, $parent = null) diff --git a/src/Core/Products/Versioning/ProductVersioner.php b/src/Core/Products/Versioning/ProductVersioner.php index dd87b1e4a..391e9e8c9 100644 --- a/src/Core/Products/Versioning/ProductVersioner.php +++ b/src/Core/Products/Versioning/ProductVersioner.php @@ -3,157 +3,118 @@ namespace GetCandy\Api\Core\Products\Versioning; use Auth; +use Drafting; +use Versioning; +use Illuminate\Support\Facades\Log; +use Illuminate\Database\Eloquent\Model; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Versioning\BaseVersioner; +use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Core\Routes\Models\Route; -use Illuminate\Database\Eloquent\Model; -use NeonDigital\Versioning\Interfaces\VersionerInterface; -use NeonDigital\Versioning\Version; -use NeonDigital\Versioning\Versioners\AbstractVersioner; -use Versioning; - -class ProductVersioner extends AbstractVersioner implements VersionerInterface +use GetCandy\Api\Core\Versioning\Actions\CreateVersion; +use GetCandy\Api\Core\Versioning\Actions\RestoreAssets; +use GetCandy\Api\Core\Versioning\Actions\RestoreRoutes; +use GetCandy\Api\Core\Versioning\Actions\VersionAssets; +use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; +use GetCandy\Api\Core\Versioning\Actions\RestoreChannel; +use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; +use GetCandy\Api\Core\Versioning\Actions\VersionChannels; +use GetCandy\Api\Core\Versioning\Actions\VersionCategories; +use GetCandy\Api\Core\Versioning\Actions\VersionCollections; +use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; +use GetCandy\Api\Core\Versioning\Actions\VersionCustomerGroups; +use GetCandy\Api\Core\Versioning\Actions\RestoreProductVariants; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductAssociations; + +class ProductVersioner extends BaseVersioner { - public function create(Model $product, $relationId = null, $originatorId = null) + public function create(Model $model, Model $originator = null) { $userId = Auth::user() ? Auth::user()->id : null; - $attributes = $product->getAttributes(); - - if (is_string($attributes['attribute_data'])) { - $attributes['attribute_data'] = json_decode($attributes['attribute_data'], true); - } - // Base model - $version = new Version; - $version->user_id = $userId; - $version->versionable_type = get_class($product); - $version->versionable_id = $originatorId ?: $product->id; - $version->model_data = json_encode($attributes); - $version->save(); - - // Channels - foreach ($product->channels as $channel) { - $data = array_merge($channel->getAttributes(), [ - 'pivot' => $channel->pivot->getAttributes(), - ]); - $this->createFromObject($channel, $version->id, $data); - } - - // Variants - foreach ($product->variants as $variant) { - Versioning::with('product_variants')->create($variant, $version->id); - } - - // Categories - foreach ($product->categories as $category) { - $data = array_merge($category->getAttributes(), [ - 'pivot' => $category->pivot->getAttributes(), - ]); - $this->createFromObject($category, $version->id, $data); - } - - foreach ($product->customerGroups as $group) { - $data = array_merge($group->getAttributes(), [ - 'pivot' => $group->pivot->getAttributes(), - ]); - $this->createFromObject($group, $version->id, $data); - } - - // dd($product->routes); - // Routes - foreach ($product->routes as $route) { - $this->createFromObject($route, $version->id); - } - - // Assets - foreach ($product->assets as $asset) { - Versioning::with('assets')->create($asset, $version->id); - } + $version = CreateVersion::run([ + 'originator' => $originator, + 'model' => $model, + ]); + + $this->callActions([ + VersionChannels::class, + VersionCategories::class, + VersionCustomerGroups::class, + VersionProductAssociations::class, + VersionRoutes::class, + VersionAssets::class, + VersionCollections::class, + ], [ + 'model' => $model, + 'version' => $version, + ]); + + VersionProductVariants::run([ + 'product' => $model, + 'version' => $version, + ]); return $version; } public function restore($version) { - - $current = $version->versionable; + $product = $version->versionable; // Do we already have a draft?? - $draft = $current->draft; + $draft = $product->draft; + // This is the new draft so...remove it. if ($draft) { $draft->forceDelete(); } - // Okay so, hydrate this draft... - $data = $version->model_data; - unset($data['id']); - $product = new Product; - $product->forceFill($data); - - // Make it a draft - $product->drafted_at = now(); - $product->draft_parent_id = $version->versionable_id; - $product->attribute_data = $data['attribute_data']; - $product->save(); - - foreach ($version->relations as $relation) { - $type = $relation->versionable_type; - $data = $relation->model_data; - - switch ($type) { - case ProductVariant::class: - Versioning::with('product_variants')->restore($relation, $product); - break; - case Channel::class: - $product->channels()->sync([ - $data['id'] => [ - 'published_at' => $data['pivot']['published_at'] ?? now(), - ], - ]); - break; - case CustomerGroup::class: - $product->customerGroups()->sync([ - $data['id'] => [ - 'purchasable' => $data['pivot']['purchasable'] ?? 1, - 'visible' => $data['pivot']['visible'] ?? 1, - ], - ]); - break; - case Category::class: - $product->categories()->sync($data['id']); - break; - case Route::class: - $route = new Route; - $route->fill($relation->model_data); - $route->element_type = get_class($product); - $route->element_id = $product->id; - $route->drafted_at = now(); - $route->draft_parent_id = $relation->versionable_id; - $route->save(); - break; - case Asset::class: - $data = $relation->model_data; - - if (! Asset::find($data['asset_id'])) { + // Create a new draft for the product + $draft = Drafting::with('products')->firstOrCreate($product->refresh()); + $draft->save(); + + $attributes = collect($version->model_data)->except(['id', 'drafted_at', 'draft_parent_id']); + $draft->update($attributes->toArray()); + + // Group our relations by versionable type so we can send them all + // through in bulk to a single action. Makes it easier so we don't have + // to worry about continuously overriding ourselves. + $groupedRelations = $version->relations->groupBy('versionable_type') + ->each(function ($versions, $type) use ($draft) { + $action = null; + switch ($type) { + case Channel::class: + $action = RestoreChannels::class; break; - } - - $product->assets()->attach($data['asset_id'], [ - 'position' => $data['position'] ?? 1, - 'primary' => $data['primary'] ?? true, - ]); - - break; - default: - break; - } - } - - return $product; + case ProductVariant::class: + $action = RestoreProductVariants::class; + break; + case CustomerGroup::class: + $action = RestoreCustomerGroups::class; + break; + case Route::class: + $action = RestoreRoutes::class; + break; + case Asset::class: + $action = RestoreAssets::class; + break; + } + if (!$action) { + Log::error("Unable to restore for {$type}"); + return; + } + (new $action)->run([ + 'versions' => $versions, + 'draft' => $draft, + ]); + }); + + return $draft; } } diff --git a/src/Core/Versioning/Actions/CreateVersion.php b/src/Core/Versioning/Actions/CreateVersion.php new file mode 100644 index 000000000..2c2fd8434 --- /dev/null +++ b/src/Core/Versioning/Actions/CreateVersion.php @@ -0,0 +1,62 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'originator' => 'nullable', + 'model' => 'required', + 'model_data' => 'nullable|array', + 'relation' => 'nullable', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $version = new Version; + $version->user_id = $this->user()->id; + $version->versionable_type = get_class($this->model); + $version->versionable_id = $this->originator->id ?? $this->model->id; + + if ($this->relation) { + $version->relation_id = $this->relation->id; + } + + $attributes = $this->model_data ?: $this->model->getAttributes(); + + if (!empty($attributes['attribute_data']) && is_string($attributes['attribute_data'])) { + $attributes['attribute_data'] = json_decode($attributes['attribute_data'], true); + } + + $version->model_data = json_encode($attributes); + $version->save(); + + return $version; + } +} diff --git a/src/Core/Versioning/Actions/RestoreAssets.php b/src/Core/Versioning/Actions/RestoreAssets.php new file mode 100644 index 000000000..9ae366492 --- /dev/null +++ b/src/Core/Versioning/Actions/RestoreAssets.php @@ -0,0 +1,53 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $assets = $this->versions->filter(function ($version) { + return Asset::whereId($version->versionable_id)->exists(); + })->mapWithKeys(function ($version) { + $data = collect($version->model_data); + return [ + $version->versionable_id => $data->only(['position', 'primary']) + ]; + }); + + $this->draft->assets()->sync($assets->toArray()); + return $this->draft; + } +} diff --git a/src/Core/Versioning/Actions/RestoreChannels.php b/src/Core/Versioning/Actions/RestoreChannels.php new file mode 100644 index 000000000..cf8f3b86f --- /dev/null +++ b/src/Core/Versioning/Actions/RestoreChannels.php @@ -0,0 +1,58 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Get all the channels that exist in the database. + $channels = FetchChannels::run([ + 'paginate' => false + ])->pluck('id'); + + // Only try and restore channels that exist in the database. + $this->versions->filter(function ($version) use ($channels) { + return $channels->contains($version->versionable_id); + })->each(function ($version) { + $this->draft->channels()->updateExistingPivot( + $version->versionable_id, + collect($version->model_data)->only('published_at')->toArray() + ); + }); + + return $this->draft; + } +} diff --git a/src/Core/Versioning/Actions/RestoreCustomerGroups.php b/src/Core/Versioning/Actions/RestoreCustomerGroups.php new file mode 100644 index 000000000..bd1f1028e --- /dev/null +++ b/src/Core/Versioning/Actions/RestoreCustomerGroups.php @@ -0,0 +1,57 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Get all the channels that exist in the database. + $groups = FetchCustomerGroups::run([ + 'paginate' => false + ])->pluck('id'); + + // Only try and restore channels that exist in the database. + $this->versions->filter(function ($version) use ($groups) { + return $groups->contains($version->versionable_id); + })->each(function ($version) { + $this->draft->customerGroups()->updateExistingPivot( + $version->versionable_id, + collect($version->model_data)->only(['visible', 'purchasable'])->toArray() + ); + }); + + return $this->draft; + } +} diff --git a/src/Core/Versioning/Actions/RestoreProductVariants.php b/src/Core/Versioning/Actions/RestoreProductVariants.php new file mode 100644 index 000000000..85895c26b --- /dev/null +++ b/src/Core/Versioning/Actions/RestoreProductVariants.php @@ -0,0 +1,93 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Just remove existing variants. + $this->draft->variants()->delete(); + + $taxes = Tax::get(); + + $variants = $this->versions->map(function ($version) use ($taxes) { + $data = collect($version->model_data)->except(['id', 'product_id']); + + // Do we have the tax record that exists? + $taxRecordExists = $taxes->contains('id', $data['tax_id']); + + if (!$taxRecordExists) { + $data['tax_id'] = $taxes->first(function($tax) { + return $tax->default; + })->id; + } + $data['options'] = $data['options'] ?? []; + + $data['product_id'] = $this->draft->id; + + $variant = $this->draft->variants()->create($data->toArray()); + + $version->relations->groupBy('versionable_type') + ->each(function ($versions, $type) use ($variant) { + $action = null; + switch ($type) { + case ProductPricingTier::class: + $action = RestoreProductVariantTiers::class; + break; + case ProductCustomerPrice::class: + $action = RestoreProductVariantCustomerPricing::class; + break; + } + if (!$action) { + Log::error("Unable to restore for {$type}"); + return; + } + (new $action)->run([ + 'versions' => $versions, + 'draft' => $variant, + ]); + }); + + return; + }); + + return $this->draft; + } +} diff --git a/src/Core/Versioning/Actions/RestoreRoutes.php b/src/Core/Versioning/Actions/RestoreRoutes.php new file mode 100644 index 000000000..d70b49df3 --- /dev/null +++ b/src/Core/Versioning/Actions/RestoreRoutes.php @@ -0,0 +1,52 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'versions' => 'required', + 'draft' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Revove any existing routes for this draft. + $this->draft->routes()->forceDelete(); + + $routes = $this->versions->map(function ($version) { + return collect($version->model_data)->only(['slug', 'redirect', 'path', 'locale', 'default', 'description']); + }); + + $this->draft->routes()->createMany($routes->toArray()); + + return $this->draft; + } +} diff --git a/src/Core/Versioning/Actions/VersionAssets.php b/src/Core/Versioning/Actions/VersionAssets.php new file mode 100644 index 000000000..c3a3682e8 --- /dev/null +++ b/src/Core/Versioning/Actions/VersionAssets.php @@ -0,0 +1,71 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->assets as $asset) { + // Okay so due to assets being quite fluid, we need to take a copy of the asset + // and store in a dedicated versions folder. That way if it gets deleted we can retrieve + // it and use it. If we can't copy the file for whatever reason. Then hopefully it'll be there still... + $source = $asset->source; + $target = "versions/{$asset->location}"; + $disk = $source->disk; + + // Don't copy transforms here, we'll do that if we have to restore it... + try { + Storage::disk($source->disk)->copy("{$asset->location}/{$asset->filename}", "{$target}/{$asset->filename}"); + } catch (FileNotFoundException $e) { + } catch (FileExistsException $e) { + // Hey, it exists, so don't worry. + } + + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $asset, + 'model_data' => array_merge([ + 'version_location' => $target, + 'disk' => $disk, + ], $asset->getAttributes(), $asset->pivot->toArray()), + 'relation' => $this->version, + ]); + } + + return $this->version; + } +} diff --git a/src/Core/Versioning/Actions/VersionCategories.php b/src/Core/Versioning/Actions/VersionCategories.php new file mode 100644 index 000000000..fc17b53e4 --- /dev/null +++ b/src/Core/Versioning/Actions/VersionCategories.php @@ -0,0 +1,49 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->categories as $category) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $category, + 'model_data' => $category->pivot->only(['position']), + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Versioning/Actions/VersionChannels.php b/src/Core/Versioning/Actions/VersionChannels.php new file mode 100644 index 000000000..1ab511fb9 --- /dev/null +++ b/src/Core/Versioning/Actions/VersionChannels.php @@ -0,0 +1,49 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->channels as $channel) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $channel, + 'model_data' => $channel->pivot->only('published_at'), + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Versioning/Actions/VersionCollections.php b/src/Core/Versioning/Actions/VersionCollections.php new file mode 100644 index 000000000..33ba6eecf --- /dev/null +++ b/src/Core/Versioning/Actions/VersionCollections.php @@ -0,0 +1,48 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->collections as $collection) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $collection, + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Versioning/Actions/VersionCustomerGroups.php b/src/Core/Versioning/Actions/VersionCustomerGroups.php new file mode 100644 index 000000000..f2e13f411 --- /dev/null +++ b/src/Core/Versioning/Actions/VersionCustomerGroups.php @@ -0,0 +1,49 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->customerGroups as $group) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $group, + 'model_data' => $group->pivot->only(['purchasable', 'visible']), + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Versioning/Actions/VersionRoutes.php b/src/Core/Versioning/Actions/VersionRoutes.php new file mode 100644 index 000000000..204fd8c5f --- /dev/null +++ b/src/Core/Versioning/Actions/VersionRoutes.php @@ -0,0 +1,48 @@ +user()->can('manage-versions'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'version' => 'required', + 'model' => 'required', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + foreach ($this->model->routes as $route) { + (new CreateVersion)->actingAs($this->user())->run([ + 'model' => $route, + 'relation' => $this->version, + ]); + } + return $this->version; + } +} diff --git a/src/Core/Versioning/BaseVersioner.php b/src/Core/Versioning/BaseVersioner.php new file mode 100644 index 000000000..f77951814 --- /dev/null +++ b/src/Core/Versioning/BaseVersioner.php @@ -0,0 +1,42 @@ +addAction('extendedVersionActions', $action); + } + + public function addRestoreAction($action) + { + return $this->addAction('extendedRestoreActions', $action); + } + + protected function addAction($target, $incoming) + { + if (is_array($incoming)) { + $this->{$target} = array_merge($this->{$target}, $incoming); + + return; + } + array_push($this->{$target}, $incoming); + } + + protected function callActions(array $actions, array $params = []) + { + foreach ($actions as $action) { + if (! class_exists($action)) { + Log::error("Tried to call action ${action} but it doesn't exist"); + continue; + } + call_user_func("{$action}::run", $params); + } + } +} diff --git a/src/Http/Resources/Versioning/VersionResource.php b/src/Http/Resources/Versioning/VersionResource.php index 058b68afc..5fc28f906 100644 --- a/src/Http/Resources/Versioning/VersionResource.php +++ b/src/Http/Resources/Versioning/VersionResource.php @@ -2,9 +2,10 @@ namespace GetCandy\Api\Http\Resources\Versioning; -use GetCandy\Api\Core\Users\Resources\UserResource; -use GetCandy\Api\Http\Resources\AbstractResource; use Hashids; +use GetCandy\Api\Http\Resources\AbstractResource; +use GetCandy\Api\Core\Users\Resources\UserResource; +use GetCandy\Api\Http\Resources\Versioning\VersionCollection; class VersionResource extends AbstractResource { @@ -12,6 +13,8 @@ public function payload() { return [ 'id' => Hashids::encode($this->id), + 'model_data' => $this->model_data, + 'versionable_type' => class_basename($this->versionable_type), 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, ]; @@ -21,6 +24,7 @@ public function includes() { return [ 'user' => $this->include('user', UserResource::class), + 'relations' => new VersionCollection($this->whenLoaded('relations')) ]; } } diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php new file mode 100644 index 000000000..9024a88f0 --- /dev/null +++ b/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php @@ -0,0 +1,52 @@ +admin(); + + $product = factory(Product::class)->create(); + + $group = factory(AssociationGroup::class)->create(); + + factory(Product::class, 15)->create()->each(function ($p) use ($group, $product) { + $assoc = new ProductAssociation; + $assoc->group()->associate($group); + $assoc->association()->associate($p); + $assoc->parent()->associate($product); + $assoc->save(); + }); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionProductAssociations)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + foreach ($product->associations as $assoc) { + $versionedAssoc = $version->relations->first(function ($relation) use ($assoc) { + return $relation->versionable_id == $assoc->id && $relation->versionable_type == get_class($assoc); + }); + foreach ($versionedAssoc->model_data as $attribute => $value) { + $this->assertEquals($assoc->getAttributes()[$attribute], $value); + } + } + } +} diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php new file mode 100644 index 000000000..be161f250 --- /dev/null +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php @@ -0,0 +1,58 @@ +admin(); + + $product = factory(Product::class)->create(); + $customerGroup = factory(CustomerGroup::class)->create(); + $tax = factory(Tax::class)->create(); + + $variant = factory(ProductVariant::class)->create([ + 'product_id' => $product->id, + ]); + + for ($i=0; $i < 2; $i++) { + $pricing = new ProductCustomerPrice; + $pricing->product_variant_id = $variant->id; + $pricing->customer_group_id = $customerGroup->id; + $pricing->tax_id = $tax->id; + $pricing->price = 30; + $pricing->save(); + } + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $variant + ]); + + (new VersionProductVariantCustomerPricing)->actingAs($user)->run([ + 'version' => $version, + 'variant' => $variant, + ]); + + foreach ($variant->customerPricing as $price) { + $versionedPrice = $version->relations->first(function ($relation) use ($price) { + return $relation->versionable_id == $price->id && $relation->versionable_type == get_class($price); + }); + foreach ($versionedPrice->model_data as $attribute => $value) { + $this->assertEquals($price->getAttributes()[$attribute], $value); + } + } + } +} diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php new file mode 100644 index 000000000..52d1cb15a --- /dev/null +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php @@ -0,0 +1,55 @@ +admin(); + + $product = factory(Product::class)->create(); + $customerGroup = factory(CustomerGroup::class)->create(); + + $variant = factory(ProductVariant::class)->create([ + 'product_id' => $product->id, + ]); + + $tiers = [ + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 1, 'price' => 1.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 2, 'price' => 2.00], + ['customer_group_id' => $customerGroup->id, 'lower_limit' => 3, 'price' => 3.00], + ]; + + $variant->tiers()->createMany($tiers); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $variant + ]); + + (new VersionProductVariantTiers)->actingAs($user)->run([ + 'version' => $version, + 'variant' => $variant, + ]); + + foreach ($variant->tiers as $tier) { + $versionedTier = $version->relations->first(function ($relation) use ($tier) { + return $relation->versionable_id == $tier->id && $relation->versionable_type == get_class($tier); + }); + foreach ($versionedTier->model_data as $attribute => $value) { + $this->assertEquals($tier->getAttributes()[$attribute], $value); + } + } + } +} diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php new file mode 100644 index 000000000..222f9ffeb --- /dev/null +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php @@ -0,0 +1,48 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(ProductVariant::class, 2)->create([ + 'product_id' => $product->id, + ]); + + $this->assertCount(2, $product->variants); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionProductVariants)->actingAs($user)->run([ + 'version' => $version, + 'product' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($product->variants as $variant) { + $versionedVariant = $version->relations->first(function ($relation) use ($variant) { + return $relation->versionable_id == $variant->id && $relation->versionable_type == get_class($variant); + }); + foreach ($versionedVariant->model_data as $attribute => $value) { + $this->assertEquals($variant->getAttributes()[$attribute], $value); + } + } + } +} diff --git a/tests/Unit/Versioning/Actions/CreateVersionTest.php b/tests/Unit/Versioning/Actions/CreateVersionTest.php new file mode 100644 index 000000000..8d6ffdbae --- /dev/null +++ b/tests/Unit/Versioning/Actions/CreateVersionTest.php @@ -0,0 +1,33 @@ +admin(); + + $model = factory(Product::class)->create(); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $model + ]); + + $this->assertEquals($model->id, $version->versionable_id); + $this->assertEquals(get_class($model), $version->versionable_type); + + $versionModelData = $version->model_data; + + foreach ($versionModelData as $attribute => $value) { + $this->assertEquals($model->{$attribute}, $value); + } + } +} diff --git a/tests/Unit/Versioning/Actions/VersionAssetsTest.php b/tests/Unit/Versioning/Actions/VersionAssetsTest.php new file mode 100644 index 000000000..e15aa6594 --- /dev/null +++ b/tests/Unit/Versioning/Actions/VersionAssetsTest.php @@ -0,0 +1,55 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(AssetSource::class)->create()->each(function ($source) use ($product) { + $source->assets()->createMany( + factory(Asset::class, 2)->make()->toArray() + ); + + foreach ($source->assets as $asset) { + $product->assets()->attach($asset->id, [ + 'primary' => 1, + 'position' => 1, + 'assetable_type' => Product::class, + ]); + } + }); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionAssets)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($version->relations as $relation) { + $this->assertEquals(Asset::class, $relation->versionable_type); + // Make sure we have a version location + $this->assertNotNull($relation->model_data['version_location'] ?? null); + } + } +} diff --git a/tests/Unit/Versioning/Actions/VersionCategoriesTest.php b/tests/Unit/Versioning/Actions/VersionCategoriesTest.php new file mode 100644 index 000000000..f61af18ce --- /dev/null +++ b/tests/Unit/Versioning/Actions/VersionCategoriesTest.php @@ -0,0 +1,52 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(Category::class, 2)->create()->each(function ($category) use ($product) { + $product->categories()->attach($category); + }); + + $this->assertCount(2, $product->categories); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionCategories)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($version->relations as $relation) { + $this->assertEquals(Category::class, $relation->versionable_type); + } + + // Make sure our version has the correct channels + foreach ($product->categories as $category) { + $versionable = $version->relations->first(function ($version) use ($category) { + return $version->versionable_id == $category->id && $version->versionable_type === get_class($category); + }); + $this->assertNotNull($version); + $this->assertEquals($category->pivot->position, $versionable->model_data['position']); + } + } +} diff --git a/tests/Unit/Versioning/Actions/VersionChannelsTest.php b/tests/Unit/Versioning/Actions/VersionChannelsTest.php new file mode 100644 index 000000000..976439048 --- /dev/null +++ b/tests/Unit/Versioning/Actions/VersionChannelsTest.php @@ -0,0 +1,54 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(Channel::class, 2)->create()->each(function ($channel) use ($product) { + $product->channels()->attach($channel->id, [ + 'published_at' => now(), + ]); + }); + + $this->assertCount(2, $product->channels); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionChannels)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($version->relations as $relation) { + $this->assertEquals(Channel::class, $relation->versionable_type); + } + + // Make sure our version has the correct channels + foreach ($product->channels as $productChannel) { + $versionable = $version->relations->first(function ($version) use ($productChannel) { + return $version->versionable_id == $productChannel->id && $version->versionable_type === get_class($productChannel); + }); + $this->assertNotNull($version); + $this->assertEquals($productChannel->pivot->published_at, $versionable->model_data['published_at']); + } + } +} diff --git a/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php b/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php new file mode 100644 index 000000000..32957cdb5 --- /dev/null +++ b/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php @@ -0,0 +1,56 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(CustomerGroup::class, 2)->create()->each(function ($group) use ($product) { + $product->customerGroups()->attach($group->id, [ + 'purchasable' => true, + 'visible' => true, + ]); + }); + + $this->assertCount(2, $product->customerGroups); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionCustomerGroups)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($version->relations as $relation) { + $this->assertEquals(CustomerGroup::class, $relation->versionable_type); + } + + // Make sure our version has the correct channels + foreach ($product->customerGroups as $group) { + $versionable = $version->relations->first(function ($version) use ($group) { + return $version->versionable_id == $group->id && $version->versionable_type === get_class($group); + }); + $this->assertNotNull($version); + $this->assertEquals($group->pivot->purchasable, $versionable->model_data['purchasable']); + $this->assertEquals($group->pivot->visible, $versionable->model_data['visible']); + } + } +} diff --git a/tests/Unit/Versioning/Actions/VersionRoutesTest.php b/tests/Unit/Versioning/Actions/VersionRoutesTest.php new file mode 100644 index 000000000..602cc4b5d --- /dev/null +++ b/tests/Unit/Versioning/Actions/VersionRoutesTest.php @@ -0,0 +1,56 @@ +admin(); + + $product = factory(Product::class)->create(); + + factory(Route::class, 2)->create([ + 'element_type' => Product::class, + 'element_id' => $product->id, + ]); + + $this->assertCount(2, $product->routes); + + $version = (new CreateVersion)->actingAs($user)->run([ + 'model' => $product + ]); + + (new VersionRoutes)->actingAs($user)->run([ + 'version' => $version, + 'model' => $product, + ]); + + $this->assertCount(2, $version->relations); + + foreach ($version->relations as $relation) { + $this->assertEquals(Route::class, $relation->versionable_type); + } + + // Make sure our version has the correct channels + foreach ($product->routes as $route) { + $versionable = $version->relations->first(function ($version) use ($route) { + return $version->versionable_id == $route->id && $version->versionable_type === get_class($route); + }); + $this->assertNotNull($version); + + foreach ($route->getAttributes() as $attribute => $value) { + $this->assertEquals($value, $versionable->model_data[$attribute]); + } + } + } +} From f0b923f17f5772892a9725be8496a98731e65d24 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 22 Feb 2021 07:58:19 +0000 Subject: [PATCH 097/152] =?UTF-8?q?Don=E2=80=99t=20override=20draft=20rela?= =?UTF-8?q?tion=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Http/Controllers/Products/ProductController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/Products/ProductController.php b/src/Http/Controllers/Products/ProductController.php index cf9a66eb3..c8c4ee983 100644 --- a/src/Http/Controllers/Products/ProductController.php +++ b/src/Http/Controllers/Products/ProductController.php @@ -99,7 +99,7 @@ public function createDraft($id, Request $request) $product = $this->service->findById($id[0], [], false); $draft = Drafting::with('products')->firstOrCreate($product); - return new ProductResource($draft->load($this->parseIncludes($request->include))); + return new ProductResource($draft); } public function publishDraft($id, Request $request) From 7330a283d3d9d4c0fd7ad61b303b89f27e69b5ed Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 22 Feb 2021 09:11:29 +0000 Subject: [PATCH 098/152] Handle passing of handle as ownertype --- src/Core/Assets/Services/AssetService.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Core/Assets/Services/AssetService.php b/src/Core/Assets/Services/AssetService.php index bf4cf1c93..d02ad6cf1 100644 --- a/src/Core/Assets/Services/AssetService.php +++ b/src/Core/Assets/Services/AssetService.php @@ -3,11 +3,13 @@ namespace GetCandy\Api\Core\Assets\Services; use GetCandy; -use GetCandy\Api\Core\Assets\Jobs\CleanUpAssetFiles; -use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Scaffold\BaseService; use Illuminate\Database\Eloquent\Model; use Symfony\Component\Finder\SplFileInfo; +use GetCandy\Api\Core\Assets\Models\Asset; +use GetCandy\Api\Core\Scaffold\BaseService; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Assets\Jobs\CleanUpAssetFiles; class AssetService extends BaseService { @@ -130,6 +132,11 @@ public function getAssets(Model $assetable, $params = []) */ public function detach($assetId, $ownerId, $ownerType) { + if ($ownerType == 'product') { + $ownerType = Product::class; + } else if ($ownerType == 'category') { + $ownerType = Category::class; + } $ownerId = (new $ownerType)->decodeId($ownerId); $ownerModel = (new $ownerType)->withoutGlobalScopes()->find($ownerId); From e59af769b22a7167f8de16ef58a4c30be695ef14 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 22 Feb 2021 10:05:15 +0000 Subject: [PATCH 099/152] Category draft and version updates --- .../Categories/Drafting/CategoryDrafter.php | 1 - .../Versioning/CategoryVersioner.php | 100 +++++++++++++----- src/Core/Routes/Actions/DeleteRoute.php | 11 +- .../Categories/CategoryController.php | 4 + 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index 5c60c4382..b1624e22d 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -25,7 +25,6 @@ public function publish(Model $draft) return DB::transaction(function () use ($draft) { // Publish this category and remove the parent. $parent = $draft->publishedParent; - // Create a version of the parent before we publish these changes Versioning::with('categories')->create($parent, null, $parent->id); diff --git a/src/Core/Categories/Versioning/CategoryVersioner.php b/src/Core/Categories/Versioning/CategoryVersioner.php index 8a27d765a..b33c2146f 100644 --- a/src/Core/Categories/Versioning/CategoryVersioner.php +++ b/src/Core/Categories/Versioning/CategoryVersioner.php @@ -4,49 +4,95 @@ use Auth; use Drafting; +use Illuminate\Support\Facades\Log; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Arr; -use NeonDigital\Versioning\Interfaces\VersionerInterface; -use NeonDigital\Versioning\Version; -use NeonDigital\Versioning\Versioners\AbstractVersioner; +use GetCandy\Api\Core\Assets\Models\Asset; +use GetCandy\Api\Core\Routes\Models\Route; +use GetCandy\Api\Core\Channels\Models\Channel; +use GetCandy\Api\Core\Versioning\BaseVersioner; +use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Versioning\Actions\CreateVersion; +use GetCandy\Api\Core\Versioning\Actions\RestoreAssets; +use GetCandy\Api\Core\Versioning\Actions\RestoreRoutes; +use GetCandy\Api\Core\Versioning\Actions\VersionAssets; +use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; +use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; +use GetCandy\Api\Core\Versioning\Actions\VersionChannels; +use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; +use GetCandy\Api\Core\Versioning\Actions\VersionCustomerGroups; -class CategoryVersioner extends AbstractVersioner implements VersionerInterface +class CategoryVersioner extends BaseVersioner { - public function create(Model $category, $relationId = null, $originatorId = null) + public function create(Model $category, Model $originator = null) { $userId = Auth::user() ? Auth::user()->id : null; - $attributes = $category->getAttributes(); - - if (is_string($attributes['attribute_data'])) { - $attributes['attribute_data'] = json_decode($attributes['attribute_data'], true); - } - - // Base model - $version = new Version; - $version->user_id = $userId; - $version->versionable_type = get_class($category); - $version->versionable_id = $originatorId ?: $category->id; - $version->model_data = json_encode($attributes); - $version->save(); + $version = CreateVersion::run([ + 'originator' => $originator, + 'model' => $category, + ]); + $this->callActions([ + VersionChannels::class, + VersionCustomerGroups::class, + VersionRoutes::class, + VersionAssets::class + ], [ + 'model' => $category, + 'version' => $version, + ]); return $version; } public function restore($version) { - $current = $version->versionable; + $category = $version->versionable; + + // Do we already have a draft?? + $draft = $category->draft; // This is the new draft so...remove it. - $category = Drafting::with('categories')->firstOrCreate($current); + if ($draft) { + $draft->forceDelete(); + } + + // Create a new draft for the product + $draft = Drafting::with('categories')->firstOrCreate($category->refresh()); + $draft->save(); - // Okay so, hydrate this draft... - $data = $version->model_data; - $category->forceFill(Arr::except($data, ['id', 'drafted_at', 'draft_parent_id'])); + $attributes = collect($version->model_data)->except(['id', 'drafted_at', 'draft_parent_id']); + $draft->update($attributes->toArray()); - $category->attribute_data = $data['attribute_data']; - $category->save(); + // Group our relations by versionable type so we can send them all + // through in bulk to a single action. Makes it easier so we don't have + // to worry about continuously overriding ourselves. + $groupedRelations = $version->relations->groupBy('versionable_type') + ->each(function ($versions, $type) use ($draft) { + $action = null; + switch ($type) { + case Channel::class: + $action = RestoreChannels::class; + break; + case CustomerGroup::class: + $action = RestoreCustomerGroups::class; + break; + case Route::class: + $action = RestoreRoutes::class; + break; + case Asset::class: + $action = RestoreAssets::class; + break; + } + if (!$action) { + Log::error("Unable to restore for {$type}"); + return; + } + (new $action)->run([ + 'versions' => $versions, + 'draft' => $draft, + ]); + }); - return $category; + return $draft; } } diff --git a/src/Core/Routes/Actions/DeleteRoute.php b/src/Core/Routes/Actions/DeleteRoute.php index d39fc1af7..770cd1f5d 100644 --- a/src/Core/Routes/Actions/DeleteRoute.php +++ b/src/Core/Routes/Actions/DeleteRoute.php @@ -36,9 +36,14 @@ public function rules() */ public function handle() { - $route = $this->delegateTo(FetchRoute::class); - - return $route->delete(); + $route = (new FetchRoute)->actingAs( + $this->user() + )->run([ + 'encoded_id' => $this->encoded_id, + 'draft' => true, + ]); + + return $route->forceDelete(); } /** diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index c7f0df840..868c93c52 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -181,6 +181,10 @@ public function publishDraft($id, Request $request) return $this->errorNotFound(); } + if (!$category->drafted_at) { + return $this->errorUnprocessable('Category is not a draft'); + } + $category = Drafting::with('categories')->publish($category); return new CategoryResource($category->load($this->parseIncludes($request->include))); From 186ad7875173c385b2cba853272beb3b96c47815 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 26 Feb 2021 09:11:04 +0000 Subject: [PATCH 100/152] Reporting updates --- openapi/assets/paths/assets.reorder.yaml | 2 +- openapi/openapi.yaml | 2 + .../paths/reports.customers.spending.yaml | 24 ++ .../responses/CustomerSpendingResponse.yaml | 10 + routes/api.php | 7 +- .../Customers/Actions/FetchCustomerGroups.php | 13 +- src/Core/GetCandy.php | 6 + src/Core/Orders/Models/Order.php | 2 + src/Core/Orders/Services/OrderService.php | 1 + .../Actions/GetCustomerGroupReport.php | 178 ++++++++++++ .../Actions/GetCustomerSpendingReport.php | 81 ++++++ .../Actions/GetOrderAveragesReport.php | 154 +++++++++++ .../Reports/Actions/GetProductBestSellers.php | 66 +++++ src/Core/Reports/Actions/GetUserReport.php | 202 ++++++++++++++ src/Core/Reports/Providers/Orders.php | 256 +++++++++++------- src/Core/Root/Actions/FetchRoot.php | 3 + src/Core/Users/Resources/UserResource.php | 1 + src/Http/Resources/Assets/AssetResource.php | 2 +- 18 files changed, 908 insertions(+), 102 deletions(-) create mode 100644 openapi/reports/paths/reports.customers.spending.yaml create mode 100644 openapi/reports/paths/responses/CustomerSpendingResponse.yaml create mode 100644 src/Core/Reports/Actions/GetCustomerGroupReport.php create mode 100644 src/Core/Reports/Actions/GetCustomerSpendingReport.php create mode 100644 src/Core/Reports/Actions/GetOrderAveragesReport.php create mode 100644 src/Core/Reports/Actions/GetProductBestSellers.php create mode 100644 src/Core/Reports/Actions/GetUserReport.php diff --git a/openapi/assets/paths/assets.reorder.yaml b/openapi/assets/paths/assets.reorder.yaml index 645f61bdf..cff412c92 100644 --- a/openapi/assets/paths/assets.reorder.yaml +++ b/openapi/assets/paths/assets.reorder.yaml @@ -8,7 +8,7 @@ post: operationId: reorder-assets requestBody: content: - multipart/form-data: + application/json: schema: $ref: '../requests/ReorderAssetsBody.yaml' description: '' diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index cb1a49914..8ecd62fac 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -225,6 +225,8 @@ paths: $ref: './recycle-bin/paths/recycle-bin.id.yaml' '/reports/sales': $ref: './reports/paths/reports.sales.yaml' + '/reports/customers/spending': + $ref: './reports/paths/reports.customers.spending.yaml' '/reports/orders': $ref: './reports/paths/reports.orders.yaml' '/reports/orders/customers': diff --git a/openapi/reports/paths/reports.customers.spending.yaml b/openapi/reports/paths/reports.customers.spending.yaml new file mode 100644 index 000000000..3c9ae299d --- /dev/null +++ b/openapi/reports/paths/reports.customers.spending.yaml @@ -0,0 +1,24 @@ +get: + summary: Get customer spending report + tags: + - Reports + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/CustomerSpendingResponse.yaml' + operationId: customer-spending-report + parameters: + - schema: + type: string + in: query + name: from + description: The from date + - schema: + type: string + in: query + name: to + description: The to date + description: Get customer spending report \ No newline at end of file diff --git a/openapi/reports/paths/responses/CustomerSpendingResponse.yaml b/openapi/reports/paths/responses/CustomerSpendingResponse.yaml new file mode 100644 index 000000000..50da71041 --- /dev/null +++ b/openapi/reports/paths/responses/CustomerSpendingResponse.yaml @@ -0,0 +1,10 @@ +title: CustomerSpendingResponse +type: object +properties: + period: + type: object + sub_total: + type: string + data: + type: array + diff --git a/routes/api.php b/routes/api.php index d14b168ef..d9595dbe5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -227,8 +227,11 @@ $router->get('/sales', 'ReportController@sales'); $router->get('/orders', 'ReportController@orders'); $router->get('/orders/customers', 'ReportController@orderCustomers'); - $router->get('/orders/averages', 'ReportController@orderAverages'); - $router->get('/products/best-sellers', 'ReportController@bestSellers'); + $router->get('/customers/spending', '\GetCandy\Api\Core\Reports\Actions\GetCustomerSpendingReport'); + $router->get('/customer-groups', '\GetCandy\Api\Core\Reports\Actions\GetCustomerGroupReport'); + $router->get('/orders/averages', '\GetCandy\Api\Core\Reports\Actions\GetOrderAveragesReport'); + $router->get('/products/best-sellers', '\GetCandy\Api\Core\Reports\Actions\GetProductBestSellers'); + $router->get('/users/{userId}', '\GetCandy\Api\Core\Reports\Actions\GetUserReport'); $router->get('/metrics/{subject}', 'ReportController@metrics'); }); diff --git a/src/Core/Customers/Actions/FetchCustomerGroups.php b/src/Core/Customers/Actions/FetchCustomerGroups.php index 24f452347..c32a02575 100644 --- a/src/Core/Customers/Actions/FetchCustomerGroups.php +++ b/src/Core/Customers/Actions/FetchCustomerGroups.php @@ -29,6 +29,7 @@ public function rules() { return [ 'per_page' => 'numeric|max:200', + 'exclude' => 'nullable|array|min:0', 'paginate' => 'boolean', ]; } @@ -42,12 +43,18 @@ public function handle() { $includes = $this->resolveEagerRelations(); + $query = CustomerGroup::with($includes); + + if ($this->exclude && count($this->exclude)) { + $query->whereNotIn('handle', $this->exclude); + } + if (! $this->paginate) { - return CustomerGroup::with($includes)->get(); + return $query->get(); } - return CustomerGroup::with($includes) - ->withCount( + + return $query->withCount( $this->resolveRelationCounts() )->paginate($this->per_page ?? 50); } diff --git a/src/Core/GetCandy.php b/src/Core/GetCandy.php index 7f114dc74..1d34d79f2 100644 --- a/src/Core/GetCandy.php +++ b/src/Core/GetCandy.php @@ -54,6 +54,12 @@ public static function version() })->version; } + public static function maxUploadSize() + { + $max_upload = min((int)ini_get('post_max_size'), (int)ini_get('upload_max_filesize')); + return $max_upload * 1024; + } + /** * Sets whether it's a Hub request or not. * diff --git a/src/Core/Orders/Models/Order.php b/src/Core/Orders/Models/Order.php index 78f8c07be..b97ad4c48 100644 --- a/src/Core/Orders/Models/Order.php +++ b/src/Core/Orders/Models/Order.php @@ -194,6 +194,8 @@ public function scopeSearch($qb, $keywords) $qb->orWhereIn('id', $matches) ->orWhereIn('contact_email', $matches) + ->orWhere('billing_company_name', '=', $keywords) + ->orWhere('shipping_company_name', '=', $keywords) ->orWhereIn('reference', $matches); return $qb; diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index 2eb2e9e6b..c913291fb 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -452,6 +452,7 @@ protected function addAddress($id, $data, $type, $user = null) 'encoded_id' => $data['address_id'], ]); $payload = $shipping->only([ + 'company_name', 'firstname', 'lastname', 'address', diff --git a/src/Core/Reports/Actions/GetCustomerGroupReport.php b/src/Core/Reports/Actions/GetCustomerGroupReport.php new file mode 100644 index 000000000..ad22da5e9 --- /dev/null +++ b/src/Core/Reports/Actions/GetCustomerGroupReport.php @@ -0,0 +1,178 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $query = Order::whereNotNull('placed_at'); + + // $displayFormat = $formats['display']; + // $queryFormat = $formats['format']; + + // Get our customer groups. + $groups = FetchCustomerGroups::run([ + 'paginate' => false, + ]); + + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + $report = $groups->mapWithKeys(function ($group) use ($period) { + + $guestOrders = null; + + if ($group->default) { + $guestOrders = $this->getGuestOrdersResult(); + } + + $result = Order::whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $this->from, + $this->to, + ])->join('users', 'users.id', '=', 'orders.user_id') + ->join('customers', 'customers.id', '=', 'users.customer_id') + ->join('customer_customer_group', function ($join) use ($group) { + $join->on('customer_customer_group.customer_id', '=', 'customers.id') + ->where('customer_customer_group.customer_group_id', '=', $group->id); + }) + ->select($this->getSelectStatement())->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '%Y%m')"), + 'customer_customer_group.customer_group_id' + )->orderBy($this->getOrderByClause(), 'desc')->get()->map(function ($row) use ($guestOrders) { + if (!$guestOrders) { + return $row; + } + // Find the guest orders that match our monthstamp + $match = $guestOrders->first(function ($guestRow) use ($row) { + return $guestRow->monthstamp == $row->monthstamp; + }); + + if (!$match) { + return $row; + } + + $row->order_total += $match->order_total; + $row->delivery_total += $match->delivery_total; + $row->discount_total += $match->discount_total; + $row->sub_total += $match->sub_total; + $row->tax_total += $match->tax_total; + $row->order_count += $match->order_count; + + return $row; + }); + + $dates = collect(); + + + foreach ($period as $date) { + $report = $result->first(function ($month) use ($date) { + return $month->monthstamp == $date->format('Ym'); + }); + + if (!$report) { + $report = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'month' => $date->format('F'), + 'year' => $date->format('Y'), + 'tax_total' => 0, + ]; + } + $dates->push($report); + } + + return [$group->handle => [ + 'label' => $group->name, + 'handle' => $group->handle, + 'default' => (bool) $group->default, + 'data' => $dates, + ]]; + }); + + return [ + 'period' => collect($period->toArray())->map(function ($date) { + return [ + 'label' => $date->format('F Y'), + 'date' => $date, + ]; + }), + 'data' => $report, + ]; + } + + protected function getSelectStatement() + { + return [ + DB::RAW('COUNT(*) as order_count'), + DB::RAW('SUM(order_total) as order_total'), + DB::RAW('SUM(delivery_total) as delivery_total'), + DB::RAW('SUM(discount_total) as discount_total'), + DB::RAW('SUM(sub_total) as sub_total'), + DB::RAW('SUM(tax_total) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '%M') as month"), + DB::RAW("DATE_FORMAT(placed_at, '%Y') as year"), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp") + ]; + } + + protected function getGroupByClause() + { + return DB::RAW("DATE_FORMAT(placed_at, '%Y%m')"); + } + + protected function getOrderByClause() + { + return DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"); + } + + protected function getGuestOrdersResult() + { + return Order::whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $this->from, + $this->to, + ])->where('sub_total', '>', 0)->whereNull('user_id')->select( + $this->getSelectStatement() + )->groupBy( + $this->getGroupByClause() + )->orderBy($this->getOrderByClause(), 'desc')->get(); + } +} diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php new file mode 100644 index 000000000..196f230ed --- /dev/null +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -0,0 +1,81 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $result = Order::whereNotNull('placed_at') + ->select( + DB::RAW('SUM(sub_total) as sub_total'), + 'billing_email as email', + 'billing_firstname as firstname', + 'billing_lastname as lastname', + 'billing_company_name as company_name', + 'users.id as user_id' + )->whereNotNull('billing_email')->whereBetween('placed_at', [ + $this->from, + $this->to, + ])->where('sub_total', '>', 10000) + ->leftJoin('users', function ($join) { + $join->on('users.id', '=', 'orders.user_id')->whereNotNull('orders.user_id'); + }) + ->groupBy('billing_email')->orderBy('sub_total', 'desc') + ->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'asc') + ->paginate(50); + + $items = $result->getCollection()->map(function ($row) { + $userModel = GetCandy::getUserModel(); + return array_merge($row->toArray(), [ + 'user_id' => $row->user_id ? (new $userModel)->encode($row->user_id) : null, + ]); + }); +; + + $result->setCollection($items); + + + return [ + 'period' => [ + 'from' => $this->from, + 'to' => $this->to, + ], + 'data' => $result + ]; + } +} diff --git a/src/Core/Reports/Actions/GetOrderAveragesReport.php b/src/Core/Reports/Actions/GetOrderAveragesReport.php new file mode 100644 index 000000000..2f2b6d45e --- /dev/null +++ b/src/Core/Reports/Actions/GetOrderAveragesReport.php @@ -0,0 +1,154 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date|after:from' + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + // Get our customer groups. + $groups = FetchCustomerGroups::run([ + 'exclude' => config('getcandy.reports.customer_groups.exclude', []), + 'paginate' => false, + ]); + + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + return [ + 'period' => collect($period->toArray())->map(function ($date) { + return [ + 'label' => $date->format('F Y'), + 'date' => $date, + ]; + }), + 'data' => $groups->mapWithKeys(function ($group) use ($period) { + $guestOrders = null; + + if ($group->default) { + $guestOrders = $this->getInitialQuery()->whereNull('user_id')->select( + DB::RAW('ROUND(AVG(order_total), 0) as order_total'), + DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), + DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), + DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), + DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as date") + )->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '%Y%m')") + )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + } + + $result = $this->getInitialQuery()->join('users', 'users.id', '=', 'orders.user_id') + ->join('customers', 'customers.id', '=', 'users.customer_id') + ->join('customer_customer_group', function ($join) use ($group) { + $join->on('customer_customer_group.customer_id', '=', 'customers.id') + ->where('customer_customer_group.customer_group_id', '=', $group->id); + }) + ->select( + DB::RAW('ROUND(AVG(order_total), 0) as order_total'), + DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), + DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), + DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), + DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as date") + )->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '%Y%m')"), + 'customer_customer_group.customer_group_id' + )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + + $months = collect(); + + foreach ($period as $date) { + $record = $result->first(function ($row) use ($date) { + return $date->format('Ym') === $row->date; + }); + if (!$record) { + $record = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'tax_total' => 0, + 'date' => $date->format('Ym'), + ]; + } + $months->push($record); + } + + return [ + $group->handle => [ + 'label' => $group->name, + 'handle' => $group->handle, + 'default' => $group->default, + 'data' => $months->map(function ($order) use ($guestOrders) { + $data = [ + 'date' => $order->date, + 'sub_total' => (int) $order->sub_total, + 'delivery_total' => (int) $order->delivery_total, + 'tax_total' => (int) $order->tax_total, + 'order_total' => (int) $order->order_total, + 'discount_total' => (int) $order->discount_total, + ]; + + if ($guestOrders) { + $period = $guestOrders->first(function ($orders) use ($order) { + return $order->date == $orders->date; + }); + if ($period) { + $data['sub_total'] += $period->sub_total; + $data['delivery_total'] += $period->delivery_total; + $data['tax_total'] += $period->tax_total; + $data['order_total'] += $period->order_total; + $data['discount_total'] += $period->discount_total; + } + } + + return $data; + }) + ] + ]; + }) + ]; + } + + protected function getInitialQuery() + { + return DB::table('orders')->whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $this->from, + $this->to, + ]); + } +} diff --git a/src/Core/Reports/Actions/GetProductBestSellers.php b/src/Core/Reports/Actions/GetProductBestSellers.php new file mode 100644 index 000000000..91776dd1e --- /dev/null +++ b/src/Core/Reports/Actions/GetProductBestSellers.php @@ -0,0 +1,66 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date|after:from', + 'term' => 'nullable|string', + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle() + { + $query = DB::table('order_lines') + ->select( + DB::RAW('SUM(quantity) as quantity'), + DB::RAW('SUM(line_total) as sub_total'), + 'description', + 'sku', + DB::RAW("DATE_FORMAT(placed_at, '%Y-%m-01') as month") + ) + ->join('orders', 'orders.id', '=', 'order_lines.order_id') + ->whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $this->from, + $this->to + ])->whereIsManual(0) + ->whereIsShipping(0) + ->groupBy('sku') + ->orderBy( + DB::RAW('SUM(quantity)'), 'desc' + ); + + if ($this->term) { + $query->where('sku', 'LIKE', "%{$this->term}%"); + } + return $query->paginate(50); + } +} diff --git a/src/Core/Reports/Actions/GetUserReport.php b/src/Core/Reports/Actions/GetUserReport.php new file mode 100644 index 000000000..b7dc12665 --- /dev/null +++ b/src/Core/Reports/Actions/GetUserReport.php @@ -0,0 +1,202 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'date|before:to', + 'to' => 'date|after:from', + ]; + } + + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle($userId) + { + $this->set('from', Carbon::parse($this->from)); + $this->set('to', Carbon::parse($this->to)); + $userModel = GetCandy::getUserModel(); + + $userId = (new $userModel)->decodeId($userId); + + $this->user = (new $userModel)->find($userId); + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + // Top products ordered over time period + $productLines = $this->getProductOrderLines(); + $userSpending = $this->getUserSpending(); + + + return [ + 'period' => collect($period->toArray())->map(function ($date) { + return [ + 'label' => $date->format('F Y'), + 'date' => $date, + ]; + }), + 'metrics' => ['data' => $this->getMetrics()], + 'purchasing_report' => ['data' => $productLines], + 'spending' => ['data' => $userSpending] + ]; + // Order totals over time period. + } + + protected function getMetrics() + { + $result = DB::table('orders') + ->select( + DB::RAW('COUNT(*) as order_count'), + DB::RAW('SUM(order_total) as order_total'), + DB::RAW('SUM(delivery_total) as delivery_total'), + DB::RAW('SUM(discount_total) as discount_total'), + DB::RAW('SUM(sub_total) as sub_total'), + DB::RAW('SUM(tax_total) as tax_total'), + ) + ->whereNotNull('placed_at') + ->groupBy('billing_email') + ->where('billing_email', '=', $this->user->email) + ->first(); + + if (!$result) { + return (object) [ + 'order_total' => 0, + 'discount_total' => 0, + 'order_count' => 0, + 'sub_total' => 0, + ]; + } + + return $result; + } + + protected function getUserSpendingQuery($from, $to) + { + return DB::table('orders') + ->select( + DB::RAW('COUNT(*) as order_count'), + DB::RAW('SUM(order_total) as order_total'), + DB::RAW('SUM(delivery_total) as delivery_total'), + DB::RAW('SUM(discount_total) as discount_total'), + DB::RAW('SUM(sub_total) as sub_total'), + DB::RAW('SUM(tax_total) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '%M') as month"), + DB::RAW("DATE_FORMAT(placed_at, '%Y') as year"), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp") + ) + ->whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $from, + $to + ])->groupBy(DB::RAW("DATE_FORMAT(placed_at, '%Y%m')")) + ->where('billing_email', '=', $this->user->email); + } + protected function getUserSpending() + { + $currentPeriodRows = $this->getUserSpendingQuery($this->from, $this->to)->get(); + $currentPeriod = collect(); + + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + foreach ($period as $date) { + $report = $currentPeriodRows->first(function ($month) use ($date) { + return $month->monthstamp == $date->format('Ym'); + }); + if (!$report) { + $report = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'month' => $date->format('F'), + 'year' => $date->format('Y'), + 'tax_total' => 0, + ]; + } + $currentPeriod->push($report); + } + + $previousPeriodRows = $this->getUserSpendingQuery($this->from->subYear(), $this->to->subYear())->get(); + $previousPeriod = collect(); + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + foreach ($period as $date) { + $report = $previousPeriodRows->first(function ($month) use ($date) { + return $month->monthstamp == $date->format('Ym'); + }); + if (!$report) { + $report = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'month' => $date->format('F'), + 'year' => $date->format('Y'), + 'tax_total' => 0, + ]; + } + $previousPeriod->push($report); + } + + return [ + 'current_period' => ['data' => $currentPeriod], + 'previous_period' => ['data' => $previousPeriod] + ]; + } + + protected function getProductOrderLines() + { + return DB::table('order_lines') + ->select( + DB::RAW('COUNT(*) as order_count'), + DB::RAW('SUM(quantity) as quantity'), + DB::RAW('SUM(line_total) as sub_total'), + 'description', + 'order_lines.sku', + DB::RAW("MAX(DATE_FORMAT(placed_at, '%Y-%m-%d')) as last_ordered") + ) + ->join('orders', 'orders.id', '=', 'order_lines.order_id') + ->leftJoin('product_variants', 'product_variants.sku', '=', 'order_lines.sku') + ->leftJoin('products', 'products.id', '=', 'product_variants.product_id') + ->whereNotNull('placed_at') + ->whereBetween('placed_at', [ + $this->from, + $this->to + ])->whereIsManual(0) + ->whereIsShipping(0) + ->where('billing_email', '=', $this->user->email) + ->groupBy('order_lines.sku') + ->orderBy(DB::RAW('sub_total'), 'desc')->get(); + } +} diff --git a/src/Core/Reports/Providers/Orders.php b/src/Core/Reports/Providers/Orders.php index 5bceb8e68..038387f72 100644 --- a/src/Core/Reports/Providers/Orders.php +++ b/src/Core/Reports/Providers/Orders.php @@ -2,8 +2,10 @@ namespace GetCandy\Api\Core\Reports\Providers; -use Carbon\Carbon; use DB; +use Carbon\Carbon; +use Carbon\CarbonPeriod; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; class Orders extends AbstractProvider { @@ -13,28 +15,80 @@ public function get() $labels = []; // Get all orders for the last six months. - $orders = $this->getOrderQuery() + $results = $this->getOrderQuery($this->from, $this->to) ->select( DB::RAW('SUM(order_total) as order_total'), DB::RAW('SUM(delivery_total) as delivery_total'), DB::RAW('SUM(discount_total) as discount_total'), DB::RAW('SUM(sub_total) as sub_total'), DB::RAW('SUM(tax_total) as tax_total'), - DB::RAW("DATE_FORMAT(placed_at, '%M %Y') as month") + DB::RAW("DATE_FORMAT(placed_at, '%M') as month"), + DB::RAW("DATE_FORMAT(placed_at, '%Y') as year"), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp") )->groupBy( DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')") )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); - return $orders->map(function ($order) { - return [ - 'month' => $order->month, - 'sub_total' => $order->sub_total, - 'delivery_total' => $order->delivery_total, - 'tax_total' => $order->tax_total, - 'order_total' => $order->order_total, - 'discount_total' => $order->discount_total, - ]; - }); + $currentPeriod = collect(); + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + foreach ($period as $date) { + // Find our records for this period. + $report = $results->first(function ($month) use ($date) { + return $month->monthstamp == $date->format('Ym'); + }); + if (!$report) { + $report = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'month' => $date->format('F'), + 'year' => $date->format('Y'), + 'tax_total' => 0, + ]; + } + $currentPeriod->push($report); + } + + $results = $this->getOrderQuery($this->from->subYear(), $this->to->subYear()) + ->select( + DB::RAW('SUM(order_total) as order_total'), + DB::RAW('SUM(delivery_total) as delivery_total'), + DB::RAW('SUM(discount_total) as discount_total'), + DB::RAW('SUM(sub_total) as sub_total'), + DB::RAW('SUM(tax_total) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '%M') as month"), + DB::RAW("DATE_FORMAT(placed_at, '%Y') as year"), + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp") + )->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')") + )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + + $previousPeriod = collect(); + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + + foreach ($period as $date) { + // Find our records for this period. + $report = $results->first(function ($month) use ($date) { + return $month->monthstamp == $date->format('Ym'); + }); + if (!$report) { + $report = (object) [ + 'order_total' => 0, + 'delivery_total' => 0, + 'discount_total' => 0, + 'sub_total' => 0, + 'month' => $date->format('F'), + 'year' => $date->format('Y'), + 'tax_total' => 0, + ]; + } + $previousPeriod->push($report); + } + return [ + 'currentPeriod' => $currentPeriod, + 'previousPeriod' => $previousPeriod, + ]; } public function customers() @@ -82,93 +136,105 @@ public function averages() $formats = $this->getDateFormat(); $displayFormat = $formats['display']; $queryFormat = $formats['format']; - $orders = $this->getOrderQuery() - ->select( - DB::RAW('ROUND(AVG(order_total), 0) as order_total'), - DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), - DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), - DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), - DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), - DB::RAW("DATE_FORMAT(placed_at, '{$displayFormat}') as date") - )->groupBy( - DB::RAW("DATE_FORMAT(placed_at, '{$queryFormat}')") - )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); - return $orders->map(function ($order) { - return [ - 'date' => $order->date, - 'sub_total' => $order->sub_total, - 'delivery_total' => $order->delivery_total, - 'tax_total' => $order->tax_total, - 'order_total' => $order->order_total, - 'discount_total' => $order->discount_total, - ]; - }); - } + // Get our customer groups. + $groups = FetchCustomerGroups::run([ + 'paginate' => false, + ]); - public function bestSellers($limit = 50) - { - $stats = DB::table('order_lines') - ->select( - DB::RAW('COUNT(*) as product_count'), - 'description', - 'sku', - DB::RAW("DATE_FORMAT(placed_at, '%Y-%m-01') as month") - ) - ->join('orders', 'orders.id', '=', 'order_lines.order_id') - ->whereIsManual(0) - ->whereIsShipping(0) - ->whereNotNull('placed_at') - ->whereBetween('placed_at', [ - $this->from, - $this->to, - ])->orderBy( - DB::RAW('COUNT(*)'), 'desc' - )->groupBy( - 'sku', - DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')") - ); - - $result = $stats->get() - ->groupBy('month') - ->sortKeysDesc() - ->mapWithKeys(function ($rows, $month) { - return [ - Carbon::createFromFormat('Y-m-d', $month)->toIsoString() => [ - 'products' => $rows->slice(0, 10), - ], - ]; - }); + return $groups->mapWithKeys(function ($group) use ($queryFormat, $displayFormat) { + $query = $this->getOrderQuery(); + + $guestOrders = null; + + if ($group->default) { + $guestOrders = $this->getOrderQuery()->whereNull('user_id')->select( + DB::RAW('ROUND(AVG(order_total), 0) as order_total'), + DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), + DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), + DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), + DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '{$displayFormat}') as date") + )->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '{$queryFormat}')") + )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + } + + $result = $this->getOrderQuery()-> + join('users', 'users.id', '=', 'orders.user_id') + ->join('customers', 'customers.id', '=', 'users.customer_id') + ->join('customer_customer_group', function ($join) use ($group) { + $join->on('customer_customer_group.customer_id', '=', 'customers.id') + ->where('customer_customer_group.customer_group_id', '=', $group->id); + }) + ->select( + DB::RAW('ROUND(AVG(order_total), 0) as order_total'), + DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), + DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), + DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), + DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), + DB::RAW("DATE_FORMAT(placed_at, '{$displayFormat}') as date") + )->groupBy( + DB::RAW("DATE_FORMAT(placed_at, '{$queryFormat}')"), + 'customer_customer_group.customer_group_id' + )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + + return [$group->handle => [ + 'label' => $group->name, + 'handle' => $group->handle, + 'default' => $group->default, + 'data' => $result->map(function ($order) use ($guestOrders) { + $data = [ + 'date' => $order->date, + 'sub_total' => (int) $order->sub_total, + 'delivery_total' => (int) $order->delivery_total, + 'tax_total' => (int) $order->tax_total, + 'order_total' => (int) $order->order_total, + 'discount_total' => (int) $order->discount_total, + ]; - // $products = collect(); - - // /** - // * [ - // * product => 'Screw', - // * sku => 1231234 - // * months => [] - // * ] - // * - // */ - // foreach ($result as $month) { - // foreach ($month['products'] as $product) { - // if (empty($products[$product->sku])) { - // $products[$product->sku] = [ - // 'product' => $product->description, - // 'sku' => $product->sku, - // 'months' => collect(), - // ]; - // } - // $products[$product->sku]['months']->push([ - // 'month' => Carbon::createFromFormat('Y-m-d', $product->month)->toIsoString(), - // 'count' => $product->product_count, - // ]); - // } - // } - - return $result; + if ($guestOrders) { + $period = $guestOrders->first(function ($orders) use ($order) { + return $order->date == $orders->date; + }); + if ($period) { + $data['sub_total'] += $period->sub_total; + $data['delivery_total'] += $period->delivery_total; + $data['tax_total'] += $period->tax_total; + $data['order_total'] += $period->order_total; + $data['discount_total'] += $period->discount_total; + } + } + + return $data; + }) + ]]; + }); + // $orders = $this->getOrderQuery() + // ->select( + // DB::RAW('ROUND(AVG(order_total), 0) as order_total'), + // DB::RAW('ROUND(AVG(delivery_total), 0) as delivery_total'), + // DB::RAW('ROUND(AVG(discount_total), 0) as discount_total'), + // DB::RAW('ROUND(AVG(sub_total), 0) as sub_total'), + // DB::RAW('ROUND(AVG(tax_total), 0) as tax_total'), + // DB::RAW("DATE_FORMAT(placed_at, '{$displayFormat}') as date") + // )->groupBy( + // DB::RAW("DATE_FORMAT(placed_at, '{$queryFormat}')") + // )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); + + // return $orders->map(function ($order) { + // return [ + // 'date' => $order->date, + // 'sub_total' => $order->sub_total, + // 'delivery_total' => $order->delivery_total, + // 'tax_total' => $order->tax_total, + // 'order_total' => $order->order_total, + // 'discount_total' => $order->discount_total, + // ]; + // }); } + public function metrics() { Carbon::useMonthsOverflow(false); diff --git a/src/Core/Root/Actions/FetchRoot.php b/src/Core/Root/Actions/FetchRoot.php index c04d8e8db..da7d88894 100644 --- a/src/Core/Root/Actions/FetchRoot.php +++ b/src/Core/Root/Actions/FetchRoot.php @@ -37,8 +37,11 @@ public function rules() */ public function handle(CurrencyConverterInterface $currency) { + + return [ 'version' => GetCandy::version(), + 'max_upload_size' => GetCandy::maxUploadSize(), 'locale' => app()->getLocale(), 'channel' => $this->delegateTo(FetchCurrentChannel::class), 'currency' => new CurrencyResource($currency->get()), diff --git a/src/Core/Users/Resources/UserResource.php b/src/Core/Users/Resources/UserResource.php index a62e90e43..ba102e45f 100644 --- a/src/Core/Users/Resources/UserResource.php +++ b/src/Core/Users/Resources/UserResource.php @@ -21,6 +21,7 @@ public function payload() 'id' => $this->encoded_id, 'email' => $this->email, 'name' => $this->name, + 'created_at' => $this->created_at, ]; } diff --git a/src/Http/Resources/Assets/AssetResource.php b/src/Http/Resources/Assets/AssetResource.php index 761a46af2..578e4b793 100644 --- a/src/Http/Resources/Assets/AssetResource.php +++ b/src/Http/Resources/Assets/AssetResource.php @@ -24,7 +24,7 @@ public function payload() 'kind' => $this->kind, 'external' => (bool) $this->external, 'thumbnail' => $this->getThumbnail($this->resource), - 'position' => (int) ($pivot ? $pivot->position : 1), + 'position' => (int) ($pivot ? $pivot->position : 99), 'primary' => (bool) ($pivot ? $pivot->primary : false), 'url' => Storage::disk($this->source->disk)->url($this->location.'/'.$this->filename), ]; From 52ff88f1f87e3d7a99aff71b0744809db3b4d085 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 26 Feb 2021 09:26:16 +0000 Subject: [PATCH 101/152] Only drop if the country exists --- ..._02_08_082522_remove_country_column_from_addresses.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php b/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php index d9e16c2e5..0df19d85a 100644 --- a/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php +++ b/database/migrations/2021_02_08_082522_remove_country_column_from_addresses.php @@ -11,9 +11,11 @@ class RemoveCountryColumnFromAddresses extends Migration */ public function up() { - Schema::table('addresses', function (Blueprint $table) { - $table->dropColumn('country'); - }); + if (Schema::hasColumn('addresses', 'country')) { + Schema::table('addresses', function (Blueprint $table) { + $table->dropColumn('country'); + }); + } } /** From 22620cc678e159762b97b30b7a9a3a08c216b7d5 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 26 Feb 2021 10:15:51 +0000 Subject: [PATCH 102/152] Fixes to migrations --- ...33_add_set_null_on_delete_on_order_lines.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php b/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php index 48052a62c..721ece333 100644 --- a/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php +++ b/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php @@ -1,8 +1,9 @@ dropForeign('order_lines_product_variant_id_foreign'); + }); + } catch (QueryException $e) { + // + } Schema::table('order_lines', function (Blueprint $table) { - $table->dropForeign('order_lines_product_variant_id_foreign'); $table->foreign('product_variant_id') - ->references('id')->on('product_variants') - ->onDelete('SET NULL'); + ->references('id')->on('product_variants') + ->onDelete('SET NULL'); }); } From 0b0447963981c62b489315f025f2c9f6141dd66e Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 26 Feb 2021 12:42:21 +0000 Subject: [PATCH 103/152] Check for column before adding --- ...093543_add_billing_shipping_company_name_to_orders.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php index 0cd850727..bf84abd38 100644 --- a/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php +++ b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php @@ -13,8 +13,12 @@ public function up() { Schema::table('orders', function (Blueprint $table) { $table->dropColumn('company_name'); - $table->string('billing_company_name')->after('billing_email')->nullable()->index(); - $table->string('shipping_company_name')->after('shipping_email')->nullable()->index(); + if (!Schema::hasColumn('orders', 'billing_company_name')) { + $table->string('billing_company_name')->after('billing_email')->nullable()->index(); + } + if (!Schema::hasColumn('orders', 'shipping_company_name')) { + $table->string('shipping_company_name')->after('billing_email')->nullable()->index(); + } }); } From dbe5dc9242e71d7803792d33910deb96e30654dc Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 1 Mar 2021 11:09:22 +0000 Subject: [PATCH 104/152] Add numeric validation to price field --- src/Http/Requests/ProductVariants/UpdateRequest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Requests/ProductVariants/UpdateRequest.php b/src/Http/Requests/ProductVariants/UpdateRequest.php index 2b9b5d3cc..00235a440 100644 --- a/src/Http/Requests/ProductVariants/UpdateRequest.php +++ b/src/Http/Requests/ProductVariants/UpdateRequest.php @@ -27,6 +27,7 @@ public function rules(ProductVariant $variant) { return [ 'sku' => 'required', + 'price' => 'numeric', 'pricing' => 'array', 'pricing.*.customer_group_id' => 'required|hashid_is_valid:'.CustomerGroup::class, 'tiers' => 'nullable|array', From 9b3f5aad54b02f4346ca386fe50b2c0b16a60ac9 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 1 Mar 2021 12:13:57 +0000 Subject: [PATCH 105/152] Add Codacy yaml --- .codacy.yaml | 5 +++++ webpack.mix.js | 20 -------------------- 2 files changed, 5 insertions(+), 20 deletions(-) create mode 100644 .codacy.yaml delete mode 100644 webpack.mix.js diff --git a/.codacy.yaml b/.codacy.yaml new file mode 100644 index 000000000..c14f3f00a --- /dev/null +++ b/.codacy.yaml @@ -0,0 +1,5 @@ +--- +exclude_paths: + - "**/*.md" + - "**/*.js" + - "**/*.json" \ No newline at end of file diff --git a/webpack.mix.js b/webpack.mix.js deleted file mode 100644 index 24abb30bd..000000000 --- a/webpack.mix.js +++ /dev/null @@ -1,20 +0,0 @@ -const { mix } = require('laravel-mix'); -require('laravel-mix-purgecss'); - -/* - |-------------------------------------------------------------------------- - | Mix Asset Management - |-------------------------------------------------------------------------- - | - | Mix provides a clean, fluent API for defining some Webpack build steps - | for your Laravel application. By default, we are compiling the Sass - | file for the application as well as bundling up all the JS files. - | - */ - -mix.js('resources/assets/js/app.js', 'public/js') - .sass('resources/assets/sass/app.scss', 'public/css') - .copy('resources/assets/icons', 'public/icons') - .copy('resources/assets/images', 'public/images') - // .purgeCss() - .version(); \ No newline at end of file From 759c4261aae9614b0b622dff2b679ac75ae1009c Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 1 Mar 2021 12:17:02 +0000 Subject: [PATCH 106/152] Ignore tests on codacy --- .codacy.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codacy.yaml b/.codacy.yaml index c14f3f00a..481290bc1 100644 --- a/.codacy.yaml +++ b/.codacy.yaml @@ -2,4 +2,5 @@ exclude_paths: - "**/*.md" - "**/*.js" - - "**/*.json" \ No newline at end of file + - "**/*.json" + - "tests/**/*" \ No newline at end of file From 83163e8ca65b6b606ba12630c341f565359618cd Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 1 Mar 2021 12:19:37 +0000 Subject: [PATCH 107/152] Remove logging --- src/Core/Search/Actions/Search.php | 1 - .../Actions/Searching/Search.php | 49 +------------------ .../Drivers/Elasticsearch/Elasticsearch.php | 2 - 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/src/Core/Search/Actions/Search.php b/src/Core/Search/Actions/Search.php index a7e6bf5fa..0f5f95a3a 100644 --- a/src/Core/Search/Actions/Search.php +++ b/src/Core/Search/Actions/Search.php @@ -37,7 +37,6 @@ public function rules() */ public function handle(SearchManagerContract $search) { - \Log::debug("Fetching search driver: " . now()->format('v')); $driver = $search->with($this->driver); return $driver->search($this->request ?? $this->params); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index 66d12fcd8..fdd1c6547 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -68,8 +68,6 @@ public function handle() $this->start = now(); $this->set('search_type', $this->search_type ? Str::plural($this->search_type) : 'products'); - \Log::debug("Start: " . now()->subMillisecond($this->start->format('v'))->format('v')); - if (! $this->index) { $prefix = config('getcandy.search.index_prefix'); $language = app()->getLocale(); @@ -87,16 +85,10 @@ public function handle() $this->language = $this->language ?: app()->getLocale(); $this->set('category', $this->category ? explode(':', $this->category) : []); - \Log::debug("Building client: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $client = FetchClient::run(); - \Log::debug("Adding search term: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $term = $this->term ? FetchTerm::run($this->attributes) : null; - \Log::debug("Fetching filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $filters = FetchFilters::run([ 'category' => $this->category, 'filters' => $this->filters, @@ -112,8 +104,6 @@ public function handle() $boolQuery = new BoolQuery; - \Log::debug("Setting term and suggestion: " . now()->subMillisecond($this->start->format('v'))->format('v')); - if ($term) { $boolQuery->addMust($term); @@ -123,9 +113,6 @@ public function handle() ]); } - \Log::debug("Fetching aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $aggregations = FetchAggregations::run(); $query = SetExcludedFields::run(['query' => $query]); @@ -133,8 +120,6 @@ public function handle() // Set filters as post filters $postFilter = new BoolQuery; - \Log::debug("Applying pre filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $preFilters = $filters->filter(function ($filter) { return in_array($filter->handle, $this->topFilters); }); @@ -146,9 +131,6 @@ public function handle() ); }); - \Log::debug("Applying post filters: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $postFilters = $filters->filter(function ($filter) { return ! in_array($filter->handle, $this->topFilters); }); @@ -168,9 +150,6 @@ public function handle() $query->setPostFilter($postFilter); - \Log::debug("Applying aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - // // $globalAggregation = new \Elastica\Aggregation\GlobalAggregation('all_products'); foreach ($aggregations as $aggregation) { if (method_exists($aggregation, 'get')) { @@ -183,22 +162,14 @@ public function handle() } } - \Log::debug("Setting query: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $query->setQuery($boolQuery); - \Log::debug("Set sorting: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $query = SetSorting::run([ 'query' => $query, 'type' => $this->search_type, 'sort' => $this->sort, ]); - \Log::debug("Set highlighting: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $query->setHighlight(config('getcandy.search.highlight') ?? [ 'pre_tags' => [''], 'post_tags' => [''], @@ -212,13 +183,9 @@ public function handle() $query = $query->setSource(false)->setStoredFields([]); - \Log::debug("Initialising the search HTTP client: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $search = new ElasticaSearch($client); - \Log::debug("Before ES Query: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $result = $search + return $search ->addIndex( $this->index ?: config('getcandy.search.index') ) @@ -226,10 +193,6 @@ public function handle() ElasticaSearch::OPTION_SEARCH_TYPE, ElasticaSearch::OPTION_SEARCH_TYPE_DFS_QUERY_THEN_FETCH )->search($query); - - \Log::debug("After ES Query: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - return $result; } /** @@ -240,7 +203,6 @@ public function handle() */ public function jsonResponse($result, $request) { - \Log::debug("Got response: " . now()->subMillisecond($this->start->format('v'))->format('v')); $ids = collect(); $results = collect($result->getResults()); @@ -250,15 +212,10 @@ public function jsonResponse($result, $request) } } - \Log::debug("Mapping Aggregations: " . now()->subMillisecond($this->start->format('v'))->format('v')); - - $aggregations = MapAggregations::run([ 'aggregations' => $result->getAggregations(), ]); - \Log::debug("Fetching searched IDs: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $models = FetchSearchedIds::run([ 'model' => $this->search_type == 'products' ? Product::class : Category::class, 'encoded_ids' => $ids->toArray(), @@ -266,8 +223,6 @@ public function jsonResponse($result, $request) 'counts' => $request->counts, ]); - \Log::debug("Got IDs: " . now()->subMillisecond($this->start->format('v'))->format('v')); - $resource = ProductCollection::class; if ($this->search_type == 'categories') { @@ -281,8 +236,6 @@ public function jsonResponse($result, $request) $this->page ?: 1 ); - \Log::debug("Building response : " . now()->subMillisecond($this->start->format('v'))->format('v')); - $response = (new $resource($paginator))->additional([ 'meta' => [ 'count' => $models->count(), diff --git a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php index 5a1bc34c0..e3354764b 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php +++ b/src/Core/Search/Drivers/Elasticsearch/Elasticsearch.php @@ -99,8 +99,6 @@ public function delete($documents) public function search($data) { if ($data instanceof Request) { - \Log::debug("Running search as a controller action: " . now()->format('v')); - return (new Search)->runAsController($data); } From 895b197b500b92ecccbf51cfd7a43d1f084e58f9 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Mon, 1 Mar 2021 12:20:12 +0000 Subject: [PATCH 108/152] Apply fixes from StyleCI (#360) Co-authored-by: Alec Ritson --- ..._add_set_null_on_delete_on_order_lines.php | 4 +-- ...illing_shipping_company_name_to_orders.php | 4 +-- src/Core/Assets/Actions/ReorderAssets.php | 10 +++---- src/Core/Assets/Drivers/BaseUploadDriver.php | 1 + src/Core/Assets/Services/AssetService.php | 12 ++++---- .../Versioning/CategoryVersioner.php | 20 +++++++------ .../Customers/Actions/FetchCustomerGroups.php | 1 - src/Core/Drafting/Actions/PublishRoutes.php | 2 +- src/Core/GetCandy.php | 3 +- .../RestoreProductVariantCustomerPricing.php | 3 +- .../Versioning/RestoreProductVariantTiers.php | 3 +- .../Versioning/VersionProductAssociations.php | 1 + .../VersionProductVariantCustomerPricing.php | 1 + .../Versioning/VersionProductVariantTiers.php | 1 + .../Versioning/VersionProductVariants.php | 4 +-- .../Products/Versioning/ProductVersioner.php | 30 +++++++++---------- .../Actions/GetCustomerGroupReport.php | 15 ++++------ .../Actions/GetCustomerSpendingReport.php | 8 ++--- .../Actions/GetOrderAveragesReport.php | 28 ++++++++--------- .../Reports/Actions/GetProductBestSellers.php | 5 ++-- src/Core/Reports/Actions/GetUserReport.php | 22 +++++++------- src/Core/Reports/Providers/Orders.php | 10 +++---- src/Core/Root/Actions/FetchRoot.php | 2 -- src/Core/Routes/Actions/UpdateRoute.php | 5 ++-- src/Core/Routes/Services/RouteService.php | 5 ++-- src/Core/Scaffold/BaseService.php | 2 +- .../Actions/Searching/Search.php | 1 - src/Core/Versioning/Actions/CreateVersion.php | 4 +-- src/Core/Versioning/Actions/RestoreAssets.php | 5 ++-- .../Versioning/Actions/RestoreChannels.php | 5 ++-- .../Actions/RestoreCustomerGroups.php | 4 +-- .../Actions/RestoreProductVariants.php | 21 +++++++------ src/Core/Versioning/Actions/RestoreRoutes.php | 2 -- src/Core/Versioning/Actions/VersionAssets.php | 3 +- .../Versioning/Actions/VersionCategories.php | 2 +- .../Versioning/Actions/VersionChannels.php | 2 +- .../Versioning/Actions/VersionCollections.php | 2 +- .../Actions/VersionCustomerGroups.php | 2 +- src/Core/Versioning/Actions/VersionRoutes.php | 2 +- .../Categories/CategoryController.php | 2 +- .../ProductVariants/UpdateRequest.php | 2 +- src/Http/Requests/Products/UpdateRequest.php | 6 ++-- .../Resources/Versioning/VersionResource.php | 7 ++--- .../VersionProductAssociationsTest.php | 11 ++++--- ...rsionProductVariantCustomerPricingTest.php | 14 ++++----- .../VersionProductVariantTiersTest.php | 9 +++--- .../Versioning/VersionProductVariantsTest.php | 6 ++-- .../Versioning/Actions/CreateVersionTest.php | 4 +-- .../Versioning/Actions/VersionAssetsTest.php | 7 ++--- .../Actions/VersionCategoriesTest.php | 6 ++-- .../Actions/VersionChannelsTest.php | 4 +-- .../Actions/VersionCustomerGroupsTest.php | 6 ++-- .../Versioning/Actions/VersionRoutesTest.php | 6 ++-- 53 files changed, 163 insertions(+), 184 deletions(-) diff --git a/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php b/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php index 721ece333..e1c71da93 100644 --- a/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php +++ b/database/migrations/2020_01_06_163333_add_set_null_on_delete_on_order_lines.php @@ -1,9 +1,9 @@ dropColumn('company_name'); - if (!Schema::hasColumn('orders', 'billing_company_name')) { + if (! Schema::hasColumn('orders', 'billing_company_name')) { $table->string('billing_company_name')->after('billing_email')->nullable()->index(); } - if (!Schema::hasColumn('orders', 'shipping_company_name')) { + if (! Schema::hasColumn('orders', 'shipping_company_name')) { $table->string('shipping_company_name')->after('billing_email')->nullable()->index(); } }); diff --git a/src/Core/Assets/Actions/ReorderAssets.php b/src/Core/Assets/Actions/ReorderAssets.php index 932729f4b..1e7299db3 100644 --- a/src/Core/Assets/Actions/ReorderAssets.php +++ b/src/Core/Assets/Actions/ReorderAssets.php @@ -2,13 +2,10 @@ namespace GetCandy\Api\Core\Assets\Actions; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Assets\Models\Assetable; +use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Categories\Models\Category; -use GetCandy\Api\Core\Foundation\Actions\DecodeId; use GetCandy\Api\Core\Traits\ReturnsJsonResponses; class ReorderAssets extends AbstractAction @@ -38,7 +35,7 @@ public function rules() 'assets.*.position' => 'required', 'assets.*.primary' => 'nullable', 'assetable_type' => 'required', - 'assetable_id' => 'required' + 'assetable_id' => 'required', ]; } @@ -63,8 +60,9 @@ public function handle() $assets = collect($this->assets)->mapWithKeys(function ($asset) { $assetId = (new Asset)->decodeId($asset['id']); unset($asset['id']); + return [ - $assetId => $asset + $assetId => $asset, ]; }); diff --git a/src/Core/Assets/Drivers/BaseUploadDriver.php b/src/Core/Assets/Drivers/BaseUploadDriver.php index 7d6913cdc..f71563337 100644 --- a/src/Core/Assets/Drivers/BaseUploadDriver.php +++ b/src/Core/Assets/Drivers/BaseUploadDriver.php @@ -96,6 +96,7 @@ public function process(array $data, $model) 'position' => $model->assets()->count() + 1, ]); } + return $asset; } } diff --git a/src/Core/Assets/Services/AssetService.php b/src/Core/Assets/Services/AssetService.php index d02ad6cf1..326887f1f 100644 --- a/src/Core/Assets/Services/AssetService.php +++ b/src/Core/Assets/Services/AssetService.php @@ -3,13 +3,13 @@ namespace GetCandy\Api\Core\Assets\Services; use GetCandy; -use Illuminate\Database\Eloquent\Model; -use Symfony\Component\Finder\SplFileInfo; +use GetCandy\Api\Core\Assets\Jobs\CleanUpAssetFiles; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Categories\Models\Category; -use GetCandy\Api\Core\Assets\Jobs\CleanUpAssetFiles; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Scaffold\BaseService; +use Illuminate\Database\Eloquent\Model; +use Symfony\Component\Finder\SplFileInfo; class AssetService extends BaseService { @@ -134,7 +134,7 @@ public function detach($assetId, $ownerId, $ownerType) { if ($ownerType == 'product') { $ownerType = Product::class; - } else if ($ownerType == 'category') { + } elseif ($ownerType == 'category') { $ownerType = Category::class; } $ownerId = (new $ownerType)->decodeId($ownerId); diff --git a/src/Core/Categories/Versioning/CategoryVersioner.php b/src/Core/Categories/Versioning/CategoryVersioner.php index b33c2146f..371404c23 100644 --- a/src/Core/Categories/Versioning/CategoryVersioner.php +++ b/src/Core/Categories/Versioning/CategoryVersioner.php @@ -4,22 +4,22 @@ use Auth; use Drafting; -use Illuminate\Support\Facades\Log; -use Illuminate\Database\Eloquent\Model; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Versioning\BaseVersioner; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\RestoreAssets; +use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; +use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; use GetCandy\Api\Core\Versioning\Actions\RestoreRoutes; use GetCandy\Api\Core\Versioning\Actions\VersionAssets; -use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; -use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; use GetCandy\Api\Core\Versioning\Actions\VersionChannels; -use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; use GetCandy\Api\Core\Versioning\Actions\VersionCustomerGroups; +use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; +use GetCandy\Api\Core\Versioning\BaseVersioner; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Log; class CategoryVersioner extends BaseVersioner { @@ -36,11 +36,12 @@ public function create(Model $category, Model $originator = null) VersionChannels::class, VersionCustomerGroups::class, VersionRoutes::class, - VersionAssets::class + VersionAssets::class, ], [ 'model' => $category, 'version' => $version, ]); + return $version; } @@ -83,8 +84,9 @@ public function restore($version) $action = RestoreAssets::class; break; } - if (!$action) { + if (! $action) { Log::error("Unable to restore for {$type}"); + return; } (new $action)->run([ diff --git a/src/Core/Customers/Actions/FetchCustomerGroups.php b/src/Core/Customers/Actions/FetchCustomerGroups.php index c32a02575..615779e82 100644 --- a/src/Core/Customers/Actions/FetchCustomerGroups.php +++ b/src/Core/Customers/Actions/FetchCustomerGroups.php @@ -53,7 +53,6 @@ public function handle() return $query->get(); } - return $query->withCount( $this->resolveRelationCounts() )->paginate($this->per_page ?? 50); diff --git a/src/Core/Drafting/Actions/PublishRoutes.php b/src/Core/Drafting/Actions/PublishRoutes.php index 16d930508..0992c7fa5 100644 --- a/src/Core/Drafting/Actions/PublishRoutes.php +++ b/src/Core/Drafting/Actions/PublishRoutes.php @@ -43,7 +43,7 @@ public function handle() $route->only(['default', 'redirect', 'slug', 'locale', 'description', 'path']) ); $route->forceDelete(); - // dd($route); + // dd($route); } else { $route->update([ 'element_id' => $this->parent->id, diff --git a/src/Core/GetCandy.php b/src/Core/GetCandy.php index 1d34d79f2..047d3cf95 100644 --- a/src/Core/GetCandy.php +++ b/src/Core/GetCandy.php @@ -56,7 +56,8 @@ public static function version() public static function maxUploadSize() { - $max_upload = min((int)ini_get('post_max_size'), (int)ini_get('upload_max_filesize')); + $max_upload = min((int) ini_get('post_max_size'), (int) ini_get('upload_max_filesize')); + return $max_upload * 1024; } diff --git a/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php b/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php index 38698505f..cc5b229ff 100644 --- a/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php +++ b/src/Core/Products/Actions/Versioning/RestoreProductVariantCustomerPricing.php @@ -2,9 +2,8 @@ namespace GetCandy\Api\Core\Products\Actions\Versioning; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Scaffold\AbstractAction; class RestoreProductVariantCustomerPricing extends AbstractAction { diff --git a/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php b/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php index e573d35a3..2a4c0c3e7 100644 --- a/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php +++ b/src/Core/Products/Actions/Versioning/RestoreProductVariantTiers.php @@ -2,9 +2,8 @@ namespace GetCandy\Api\Core\Products\Actions\Versioning; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Scaffold\AbstractAction; class RestoreProductVariantTiers extends AbstractAction { diff --git a/src/Core/Products/Actions/Versioning/VersionProductAssociations.php b/src/Core/Products/Actions/Versioning/VersionProductAssociations.php index 2d81b5b78..dfa82c816 100644 --- a/src/Core/Products/Actions/Versioning/VersionProductAssociations.php +++ b/src/Core/Products/Actions/Versioning/VersionProductAssociations.php @@ -43,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php b/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php index cddcd392e..d88b96bbe 100644 --- a/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php +++ b/src/Core/Products/Actions/Versioning/VersionProductVariantCustomerPricing.php @@ -43,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php b/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php index 445c3e419..0673056b5 100644 --- a/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php +++ b/src/Core/Products/Actions/Versioning/VersionProductVariantTiers.php @@ -43,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Products/Actions/Versioning/VersionProductVariants.php b/src/Core/Products/Actions/Versioning/VersionProductVariants.php index fc5024d32..e4851d1f0 100644 --- a/src/Core/Products/Actions/Versioning/VersionProductVariants.php +++ b/src/Core/Products/Actions/Versioning/VersionProductVariants.php @@ -4,8 +4,6 @@ use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantTiers; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantCustomerPricing; class VersionProductVariants extends AbstractAction { @@ -40,7 +38,7 @@ public function rules() public function handle() { // Create our base version. - foreach ($this->product->variants as $variant) { + foreach ($this->product->variants as $variant) { $variantVersion = (new CreateVersion)->actingAs($this->user())->run([ 'model' => $variant, 'relation' => $this->version, diff --git a/src/Core/Products/Versioning/ProductVersioner.php b/src/Core/Products/Versioning/ProductVersioner.php index 391e9e8c9..0b426eda9 100644 --- a/src/Core/Products/Versioning/ProductVersioner.php +++ b/src/Core/Products/Versioning/ProductVersioner.php @@ -4,32 +4,29 @@ use Auth; use Drafting; -use Versioning; -use Illuminate\Support\Facades\Log; -use Illuminate\Database\Eloquent\Model; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Channels\Models\Channel; -use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\Versioning\BaseVersioner; -use GetCandy\Api\Core\Categories\Models\Category; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductAssociations; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\Models\ProductVariant; +use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\RestoreAssets; +use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; +use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; +use GetCandy\Api\Core\Versioning\Actions\RestoreProductVariants; use GetCandy\Api\Core\Versioning\Actions\RestoreRoutes; use GetCandy\Api\Core\Versioning\Actions\VersionAssets; -use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; -use GetCandy\Api\Core\Versioning\Actions\RestoreChannel; -use GetCandy\Api\Core\Versioning\Actions\RestoreChannels; -use GetCandy\Api\Core\Versioning\Actions\VersionChannels; use GetCandy\Api\Core\Versioning\Actions\VersionCategories; +use GetCandy\Api\Core\Versioning\Actions\VersionChannels; use GetCandy\Api\Core\Versioning\Actions\VersionCollections; -use GetCandy\Api\Core\Versioning\Actions\RestoreCustomerGroups; use GetCandy\Api\Core\Versioning\Actions\VersionCustomerGroups; -use GetCandy\Api\Core\Versioning\Actions\RestoreProductVariants; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductAssociations; +use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; +use GetCandy\Api\Core\Versioning\BaseVersioner; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Log; class ProductVersioner extends BaseVersioner { @@ -105,8 +102,9 @@ public function restore($version) $action = RestoreAssets::class; break; } - if (!$action) { + if (! $action) { Log::error("Unable to restore for {$type}"); + return; } (new $action)->run([ diff --git a/src/Core/Reports/Actions/GetCustomerGroupReport.php b/src/Core/Reports/Actions/GetCustomerGroupReport.php index ad22da5e9..e4cf4e28c 100644 --- a/src/Core/Reports/Actions/GetCustomerGroupReport.php +++ b/src/Core/Reports/Actions/GetCustomerGroupReport.php @@ -3,11 +3,10 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use Illuminate\Support\Facades\DB; class GetCustomerGroupReport extends AbstractAction { @@ -54,7 +53,6 @@ public function handle() $period = CarbonPeriod::create($this->from, '1 month', $this->to); $report = $groups->mapWithKeys(function ($group) use ($period) { - $guestOrders = null; if ($group->default) { @@ -75,7 +73,7 @@ public function handle() DB::RAW("DATE_FORMAT(placed_at, '%Y%m')"), 'customer_customer_group.customer_group_id' )->orderBy($this->getOrderByClause(), 'desc')->get()->map(function ($row) use ($guestOrders) { - if (!$guestOrders) { + if (! $guestOrders) { return $row; } // Find the guest orders that match our monthstamp @@ -83,7 +81,7 @@ public function handle() return $guestRow->monthstamp == $row->monthstamp; }); - if (!$match) { + if (! $match) { return $row; } @@ -99,13 +97,12 @@ public function handle() $dates = collect(); - foreach ($period as $date) { $report = $result->first(function ($month) use ($date) { return $month->monthstamp == $date->format('Ym'); }); - if (!$report) { + if (! $report) { $report = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -149,7 +146,7 @@ protected function getSelectStatement() DB::RAW('SUM(tax_total) as tax_total'), DB::RAW("DATE_FORMAT(placed_at, '%M') as month"), DB::RAW("DATE_FORMAT(placed_at, '%Y') as year"), - DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp") + DB::RAW("DATE_FORMAT(placed_at, '%Y%m') as monthstamp"), ]; } diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php index 196f230ed..4b3ae3c52 100644 --- a/src/Core/Reports/Actions/GetCustomerSpendingReport.php +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -3,10 +3,9 @@ namespace GetCandy\Api\Core\Reports\Actions; use GetCandy; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetCustomerSpendingReport extends AbstractAction { @@ -61,21 +60,20 @@ public function handle() $items = $result->getCollection()->map(function ($row) { $userModel = GetCandy::getUserModel(); + return array_merge($row->toArray(), [ 'user_id' => $row->user_id ? (new $userModel)->encode($row->user_id) : null, ]); }); -; $result->setCollection($items); - return [ 'period' => [ 'from' => $this->from, 'to' => $this->to, ], - 'data' => $result + 'data' => $result, ]; } } diff --git a/src/Core/Reports/Actions/GetOrderAveragesReport.php b/src/Core/Reports/Actions/GetOrderAveragesReport.php index 2f2b6d45e..618c88ee4 100644 --- a/src/Core/Reports/Actions/GetOrderAveragesReport.php +++ b/src/Core/Reports/Actions/GetOrderAveragesReport.php @@ -3,9 +3,9 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use Illuminate\Support\Facades\DB; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetOrderAveragesReport extends AbstractAction { @@ -28,7 +28,7 @@ public function rules() { return [ 'from' => 'nullable|date', - 'to' => 'nullable|date|after:from' + 'to' => 'nullable|date|after:from', ]; } @@ -56,7 +56,7 @@ public function handle() }), 'data' => $groups->mapWithKeys(function ($group) use ($period) { $guestOrders = null; - + if ($group->default) { $guestOrders = $this->getInitialQuery()->whereNull('user_id')->select( DB::RAW('ROUND(AVG(order_total), 0) as order_total'), @@ -69,7 +69,7 @@ public function handle() DB::RAW("DATE_FORMAT(placed_at, '%Y%m')") )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); } - + $result = $this->getInitialQuery()->join('users', 'users.id', '=', 'orders.user_id') ->join('customers', 'customers.id', '=', 'users.customer_id') ->join('customer_customer_group', function ($join) use ($group) { @@ -87,14 +87,14 @@ public function handle() DB::RAW("DATE_FORMAT(placed_at, '%Y%m')"), 'customer_customer_group.customer_group_id' )->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'desc')->get(); - + $months = collect(); - + foreach ($period as $date) { $record = $result->first(function ($row) use ($date) { return $date->format('Ym') === $row->date; }); - if (!$record) { + if (! $record) { $record = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -106,7 +106,7 @@ public function handle() } $months->push($record); } - + return [ $group->handle => [ 'label' => $group->name, @@ -121,7 +121,7 @@ public function handle() 'order_total' => (int) $order->order_total, 'discount_total' => (int) $order->discount_total, ]; - + if ($guestOrders) { $period = $guestOrders->first(function ($orders) use ($order) { return $order->date == $orders->date; @@ -134,12 +134,12 @@ public function handle() $data['discount_total'] += $period->discount_total; } } - + return $data; - }) - ] + }), + ], ]; - }) + }), ]; } diff --git a/src/Core/Reports/Actions/GetProductBestSellers.php b/src/Core/Reports/Actions/GetProductBestSellers.php index 91776dd1e..5ff19a67e 100644 --- a/src/Core/Reports/Actions/GetProductBestSellers.php +++ b/src/Core/Reports/Actions/GetProductBestSellers.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetProductBestSellers extends AbstractAction { @@ -50,7 +50,7 @@ public function handle() ->whereNotNull('placed_at') ->whereBetween('placed_at', [ $this->from, - $this->to + $this->to, ])->whereIsManual(0) ->whereIsShipping(0) ->groupBy('sku') @@ -61,6 +61,7 @@ public function handle() if ($this->term) { $query->where('sku', 'LIKE', "%{$this->term}%"); } + return $query->paginate(50); } } diff --git a/src/Core/Reports/Actions/GetUserReport.php b/src/Core/Reports/Actions/GetUserReport.php index b7dc12665..8695bfb8c 100644 --- a/src/Core/Reports/Actions/GetUserReport.php +++ b/src/Core/Reports/Actions/GetUserReport.php @@ -3,12 +3,11 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use Illuminate\Support\Carbon; use GetCandy; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; class GetUserReport extends AbstractAction { @@ -37,7 +36,6 @@ public function rules() ]; } - /** * Execute the action and return a result. * @@ -58,7 +56,6 @@ public function handle($userId) $productLines = $this->getProductOrderLines(); $userSpending = $this->getUserSpending(); - return [ 'period' => collect($period->toArray())->map(function ($date) { return [ @@ -68,7 +65,7 @@ public function handle($userId) }), 'metrics' => ['data' => $this->getMetrics()], 'purchasing_report' => ['data' => $productLines], - 'spending' => ['data' => $userSpending] + 'spending' => ['data' => $userSpending], ]; // Order totals over time period. } @@ -89,7 +86,7 @@ protected function getMetrics() ->where('billing_email', '=', $this->user->email) ->first(); - if (!$result) { + if (! $result) { return (object) [ 'order_total' => 0, 'discount_total' => 0, @@ -118,10 +115,11 @@ protected function getUserSpendingQuery($from, $to) ->whereNotNull('placed_at') ->whereBetween('placed_at', [ $from, - $to + $to, ])->groupBy(DB::RAW("DATE_FORMAT(placed_at, '%Y%m')")) ->where('billing_email', '=', $this->user->email); } + protected function getUserSpending() { $currentPeriodRows = $this->getUserSpendingQuery($this->from, $this->to)->get(); @@ -133,7 +131,7 @@ protected function getUserSpending() $report = $currentPeriodRows->first(function ($month) use ($date) { return $month->monthstamp == $date->format('Ym'); }); - if (!$report) { + if (! $report) { $report = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -155,7 +153,7 @@ protected function getUserSpending() $report = $previousPeriodRows->first(function ($month) use ($date) { return $month->monthstamp == $date->format('Ym'); }); - if (!$report) { + if (! $report) { $report = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -171,7 +169,7 @@ protected function getUserSpending() return [ 'current_period' => ['data' => $currentPeriod], - 'previous_period' => ['data' => $previousPeriod] + 'previous_period' => ['data' => $previousPeriod], ]; } @@ -192,7 +190,7 @@ protected function getProductOrderLines() ->whereNotNull('placed_at') ->whereBetween('placed_at', [ $this->from, - $this->to + $this->to, ])->whereIsManual(0) ->whereIsShipping(0) ->where('billing_email', '=', $this->user->email) diff --git a/src/Core/Reports/Providers/Orders.php b/src/Core/Reports/Providers/Orders.php index 038387f72..9078e6c4e 100644 --- a/src/Core/Reports/Providers/Orders.php +++ b/src/Core/Reports/Providers/Orders.php @@ -2,9 +2,9 @@ namespace GetCandy\Api\Core\Reports\Providers; -use DB; use Carbon\Carbon; use Carbon\CarbonPeriod; +use DB; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; class Orders extends AbstractProvider @@ -36,7 +36,7 @@ public function get() $report = $results->first(function ($month) use ($date) { return $month->monthstamp == $date->format('Ym'); }); - if (!$report) { + if (! $report) { $report = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -72,7 +72,7 @@ public function get() $report = $results->first(function ($month) use ($date) { return $month->monthstamp == $date->format('Ym'); }); - if (!$report) { + if (! $report) { $report = (object) [ 'order_total' => 0, 'delivery_total' => 0, @@ -85,6 +85,7 @@ public function get() } $previousPeriod->push($report); } + return [ 'currentPeriod' => $currentPeriod, 'previousPeriod' => $previousPeriod, @@ -207,7 +208,7 @@ public function averages() } return $data; - }) + }), ]]; }); // $orders = $this->getOrderQuery() @@ -234,7 +235,6 @@ public function averages() // }); } - public function metrics() { Carbon::useMonthsOverflow(false); diff --git a/src/Core/Root/Actions/FetchRoot.php b/src/Core/Root/Actions/FetchRoot.php index da7d88894..220ad33ad 100644 --- a/src/Core/Root/Actions/FetchRoot.php +++ b/src/Core/Root/Actions/FetchRoot.php @@ -37,8 +37,6 @@ public function rules() */ public function handle(CurrencyConverterInterface $currency) { - - return [ 'version' => GetCandy::version(), 'max_upload_size' => GetCandy::maxUploadSize(), diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index 71a7c6546..e45ac008b 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -2,10 +2,10 @@ namespace GetCandy\Api\Core\Routes\Actions; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Routes\Resources\RouteResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class UpdateRoute extends AbstractAction { @@ -77,7 +77,6 @@ public function handle() ]); } - return $this->route; } diff --git a/src/Core/Routes/Services/RouteService.php b/src/Core/Routes/Services/RouteService.php index 571e53619..dc7cb9afc 100644 --- a/src/Core/Routes/Services/RouteService.php +++ b/src/Core/Routes/Services/RouteService.php @@ -36,14 +36,13 @@ public function update($hashedId, array $data) $model->default = $data['default']; // Cannot be a default route and a redirect. - if (!empty($data['default'])) { + if (! empty($data['default'])) { $model->redirect = false; } $model->save(); - - \Log::debug('hit'); + \Log::debug('hit'); return $model; } diff --git a/src/Core/Scaffold/BaseService.php b/src/Core/Scaffold/BaseService.php index 732491c85..0a99cd492 100644 --- a/src/Core/Scaffold/BaseService.php +++ b/src/Core/Scaffold/BaseService.php @@ -451,7 +451,7 @@ public function createUrl($hashedId, array $data) return $route->default; }); - if ($existingDefault && !empty($data['default'])) { + if ($existingDefault && ! empty($data['default'])) { $existingDefault->update([ 'default' => false, ]); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php index fdd1c6547..6f9e6dfef 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/Searching/Search.php @@ -25,7 +25,6 @@ class Search extends Action 'category-filter', ]; - protected $start; /** diff --git a/src/Core/Versioning/Actions/CreateVersion.php b/src/Core/Versioning/Actions/CreateVersion.php index 2c2fd8434..dd06e849b 100644 --- a/src/Core/Versioning/Actions/CreateVersion.php +++ b/src/Core/Versioning/Actions/CreateVersion.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Versioning\Actions; -use NeonDigital\Versioning\Version; use GetCandy\Api\Core\Scaffold\AbstractAction; +use NeonDigital\Versioning\Version; class CreateVersion extends AbstractAction { @@ -50,7 +50,7 @@ public function handle() $attributes = $this->model_data ?: $this->model->getAttributes(); - if (!empty($attributes['attribute_data']) && is_string($attributes['attribute_data'])) { + if (! empty($attributes['attribute_data']) && is_string($attributes['attribute_data'])) { $attributes['attribute_data'] = json_decode($attributes['attribute_data'], true); } diff --git a/src/Core/Versioning/Actions/RestoreAssets.php b/src/Core/Versioning/Actions/RestoreAssets.php index 9ae366492..1e38d8c67 100644 --- a/src/Core/Versioning/Actions/RestoreAssets.php +++ b/src/Core/Versioning/Actions/RestoreAssets.php @@ -4,7 +4,6 @@ use GetCandy\Api\Core\Assets\Models\Asset; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Channels\Actions\FetchChannels; class RestoreAssets extends AbstractAction { @@ -42,12 +41,14 @@ public function handle() return Asset::whereId($version->versionable_id)->exists(); })->mapWithKeys(function ($version) { $data = collect($version->model_data); + return [ - $version->versionable_id => $data->only(['position', 'primary']) + $version->versionable_id => $data->only(['position', 'primary']), ]; }); $this->draft->assets()->sync($assets->toArray()); + return $this->draft; } } diff --git a/src/Core/Versioning/Actions/RestoreChannels.php b/src/Core/Versioning/Actions/RestoreChannels.php index cf8f3b86f..1571f1d0d 100644 --- a/src/Core/Versioning/Actions/RestoreChannels.php +++ b/src/Core/Versioning/Actions/RestoreChannels.php @@ -2,9 +2,8 @@ namespace GetCandy\Api\Core\Versioning\Actions; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Channels\Actions\FetchChannels; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; +use GetCandy\Api\Core\Scaffold\AbstractAction; class RestoreChannels extends AbstractAction { @@ -40,7 +39,7 @@ public function handle() { // Get all the channels that exist in the database. $channels = FetchChannels::run([ - 'paginate' => false + 'paginate' => false, ])->pluck('id'); // Only try and restore channels that exist in the database. diff --git a/src/Core/Versioning/Actions/RestoreCustomerGroups.php b/src/Core/Versioning/Actions/RestoreCustomerGroups.php index bd1f1028e..797ffa925 100644 --- a/src/Core/Versioning/Actions/RestoreCustomerGroups.php +++ b/src/Core/Versioning/Actions/RestoreCustomerGroups.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Versioning\Actions; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Scaffold\AbstractAction; class RestoreCustomerGroups extends AbstractAction { @@ -39,7 +39,7 @@ public function handle() { // Get all the channels that exist in the database. $groups = FetchCustomerGroups::run([ - 'paginate' => false + 'paginate' => false, ])->pluck('id'); // Only try and restore channels that exist in the database. diff --git a/src/Core/Versioning/Actions/RestoreProductVariants.php b/src/Core/Versioning/Actions/RestoreProductVariants.php index 85895c26b..6cf2aca78 100644 --- a/src/Core/Versioning/Actions/RestoreProductVariants.php +++ b/src/Core/Versioning/Actions/RestoreProductVariants.php @@ -2,13 +2,13 @@ namespace GetCandy\Api\Core\Versioning\Actions; -use Illuminate\Support\Facades\Log; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Products\Models\ProductPricingTier; -use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; -use GetCandy\Api\Core\Products\Actions\Versioning\RestoreProductVariantTiers; use GetCandy\Api\Core\Products\Actions\Versioning\RestoreProductVariantCustomerPricing; +use GetCandy\Api\Core\Products\Actions\Versioning\RestoreProductVariantTiers; +use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; +use GetCandy\Api\Core\Products\Models\ProductPricingTier; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Taxes\Models\Tax; +use Illuminate\Support\Facades\Log; class RestoreProductVariants extends AbstractAction { @@ -53,8 +53,8 @@ public function handle() // Do we have the tax record that exists? $taxRecordExists = $taxes->contains('id', $data['tax_id']); - if (!$taxRecordExists) { - $data['tax_id'] = $taxes->first(function($tax) { + if (! $taxRecordExists) { + $data['tax_id'] = $taxes->first(function ($tax) { return $tax->default; })->id; } @@ -75,8 +75,9 @@ public function handle() $action = RestoreProductVariantCustomerPricing::class; break; } - if (!$action) { + if (! $action) { Log::error("Unable to restore for {$type}"); + return; } (new $action)->run([ @@ -84,8 +85,6 @@ public function handle() 'draft' => $variant, ]); }); - - return; }); return $this->draft; diff --git a/src/Core/Versioning/Actions/RestoreRoutes.php b/src/Core/Versioning/Actions/RestoreRoutes.php index d70b49df3..ac32c6a45 100644 --- a/src/Core/Versioning/Actions/RestoreRoutes.php +++ b/src/Core/Versioning/Actions/RestoreRoutes.php @@ -3,8 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Channels\Actions\FetchChannels; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class RestoreRoutes extends AbstractAction { diff --git a/src/Core/Versioning/Actions/VersionAssets.php b/src/Core/Versioning/Actions/VersionAssets.php index c3a3682e8..dec9aa66f 100644 --- a/src/Core/Versioning/Actions/VersionAssets.php +++ b/src/Core/Versioning/Actions/VersionAssets.php @@ -2,11 +2,10 @@ namespace GetCandy\Api\Core\Versioning\Actions; +use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Support\Facades\Storage; use League\Flysystem\FileExistsException; use League\Flysystem\FileNotFoundException; -use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionAssets extends AbstractAction { diff --git a/src/Core/Versioning/Actions/VersionCategories.php b/src/Core/Versioning/Actions/VersionCategories.php index fc17b53e4..265419f86 100644 --- a/src/Core/Versioning/Actions/VersionCategories.php +++ b/src/Core/Versioning/Actions/VersionCategories.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionCategories extends AbstractAction { @@ -44,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Versioning/Actions/VersionChannels.php b/src/Core/Versioning/Actions/VersionChannels.php index 1ab511fb9..d463ad538 100644 --- a/src/Core/Versioning/Actions/VersionChannels.php +++ b/src/Core/Versioning/Actions/VersionChannels.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionChannels extends AbstractAction { @@ -44,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Versioning/Actions/VersionCollections.php b/src/Core/Versioning/Actions/VersionCollections.php index 33ba6eecf..756c6b539 100644 --- a/src/Core/Versioning/Actions/VersionCollections.php +++ b/src/Core/Versioning/Actions/VersionCollections.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionCollections extends AbstractAction { @@ -43,6 +42,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Versioning/Actions/VersionCustomerGroups.php b/src/Core/Versioning/Actions/VersionCustomerGroups.php index f2e13f411..19c7527d9 100644 --- a/src/Core/Versioning/Actions/VersionCustomerGroups.php +++ b/src/Core/Versioning/Actions/VersionCustomerGroups.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionCustomerGroups extends AbstractAction { @@ -44,6 +43,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Core/Versioning/Actions/VersionRoutes.php b/src/Core/Versioning/Actions/VersionRoutes.php index 204fd8c5f..1f8d28502 100644 --- a/src/Core/Versioning/Actions/VersionRoutes.php +++ b/src/Core/Versioning/Actions/VersionRoutes.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Versioning\Actions; use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; class VersionRoutes extends AbstractAction { @@ -43,6 +42,7 @@ public function handle() 'relation' => $this->version, ]); } + return $this->version; } } diff --git a/src/Http/Controllers/Categories/CategoryController.php b/src/Http/Controllers/Categories/CategoryController.php index 868c93c52..9bb70a2ca 100644 --- a/src/Http/Controllers/Categories/CategoryController.php +++ b/src/Http/Controllers/Categories/CategoryController.php @@ -181,7 +181,7 @@ public function publishDraft($id, Request $request) return $this->errorNotFound(); } - if (!$category->drafted_at) { + if (! $category->drafted_at) { return $this->errorUnprocessable('Category is not a draft'); } diff --git a/src/Http/Requests/ProductVariants/UpdateRequest.php b/src/Http/Requests/ProductVariants/UpdateRequest.php index 00235a440..69980f9ce 100644 --- a/src/Http/Requests/ProductVariants/UpdateRequest.php +++ b/src/Http/Requests/ProductVariants/UpdateRequest.php @@ -31,7 +31,7 @@ public function rules(ProductVariant $variant) 'pricing' => 'array', 'pricing.*.customer_group_id' => 'required|hashid_is_valid:'.CustomerGroup::class, 'tiers' => 'nullable|array', - 'tiers.*.lower_limit' => 'numeric|min:2' + 'tiers.*.lower_limit' => 'numeric|min:2', ]; } } diff --git a/src/Http/Requests/Products/UpdateRequest.php b/src/Http/Requests/Products/UpdateRequest.php index 27d791e85..b02fefe3d 100644 --- a/src/Http/Requests/Products/UpdateRequest.php +++ b/src/Http/Requests/Products/UpdateRequest.php @@ -3,10 +3,10 @@ namespace GetCandy\Api\Http\Requests\Products; use GetCandy; -use GetCandy\Api\Http\Requests\FormRequest; -use GetCandy\Api\Core\Products\Models\ProductFamily; use GetCandy\Api\Core\Channels\Actions\FetchDefaultChannel; use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; +use GetCandy\Api\Core\Products\Models\ProductFamily; +use GetCandy\Api\Http\Requests\FormRequest; class UpdateRequest extends FormRequest { @@ -28,7 +28,7 @@ public function authorize() public function rules() { $ruleset = [ - 'family_id' => 'hashid_is_valid:' . ProductFamily::class, + 'family_id' => 'hashid_is_valid:'.ProductFamily::class, 'layout_id' => 'hashid_is_valid:layouts', 'attribute_data' => 'array', ]; diff --git a/src/Http/Resources/Versioning/VersionResource.php b/src/Http/Resources/Versioning/VersionResource.php index 5fc28f906..fb4a4d0d4 100644 --- a/src/Http/Resources/Versioning/VersionResource.php +++ b/src/Http/Resources/Versioning/VersionResource.php @@ -2,10 +2,9 @@ namespace GetCandy\Api\Http\Resources\Versioning; -use Hashids; -use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Core\Users\Resources\UserResource; -use GetCandy\Api\Http\Resources\Versioning\VersionCollection; +use GetCandy\Api\Http\Resources\AbstractResource; +use Hashids; class VersionResource extends AbstractResource { @@ -24,7 +23,7 @@ public function includes() { return [ 'user' => $this->include('user', UserResource::class), - 'relations' => new VersionCollection($this->whenLoaded('relations')) + 'relations' => new VersionCollection($this->whenLoaded('relations')), ]; } } diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php index 9024a88f0..035dbe87d 100644 --- a/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php +++ b/tests/Unit/Products/Actions/Versioning/VersionProductAssociationsTest.php @@ -2,13 +2,12 @@ namespace Tests\Unit\Products\Actions\Versioning; -use Tests\TestCase; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\Versioning\Actions\CreateVersion; -use GetCandy\Api\Core\Products\Models\ProductAssociation; use GetCandy\Api\Core\Associations\Models\AssociationGroup; use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductAssociations; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductAssociation; +use GetCandy\Api\Core\Versioning\Actions\CreateVersion; +use Tests\TestCase; /** * @group versionings @@ -32,7 +31,7 @@ public function test_can_version_product_associations() }); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionProductAssociations)->actingAs($user)->run([ diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php index be161f250..a2537e477 100644 --- a/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantCustomerPricingTest.php @@ -2,14 +2,14 @@ namespace Tests\Unit\Products\Actions\Versioning; -use Tests\TestCase; -use GetCandy\Api\Core\Taxes\Models\Tax; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantCustomerPricing; +use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; use GetCandy\Api\Core\Products\Models\ProductVariant; +use GetCandy\Api\Core\Taxes\Models\Tax; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; -use GetCandy\Api\Core\Products\Models\ProductCustomerPrice; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantCustomerPricing; +use Tests\TestCase; /** * @group versioning @@ -28,7 +28,7 @@ public function test_can_version_variant_customer_pricing() 'product_id' => $product->id, ]); - for ($i=0; $i < 2; $i++) { + for ($i = 0; $i < 2; $i++) { $pricing = new ProductCustomerPrice; $pricing->product_variant_id = $variant->id; $pricing->customer_group_id = $customerGroup->id; @@ -38,7 +38,7 @@ public function test_can_version_variant_customer_pricing() } $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $variant + 'model' => $variant, ]); (new VersionProductVariantCustomerPricing)->actingAs($user)->run([ diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php index 52d1cb15a..b02352926 100644 --- a/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantTiersTest.php @@ -2,13 +2,12 @@ namespace Tests\Unit\Products\Actions\Versioning; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantTiers; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariantTiers; +use Tests\TestCase; /** * @group versioning @@ -35,7 +34,7 @@ public function test_can_version_variant_tiers() $variant->tiers()->createMany($tiers); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $variant + 'model' => $variant, ]); (new VersionProductVariantTiers)->actingAs($user)->run([ diff --git a/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php b/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php index 222f9ffeb..8d475cc7a 100644 --- a/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php +++ b/tests/Unit/Products/Actions/Versioning/VersionProductVariantsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Products\Actions\Versioning; -use Tests\TestCase; +use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\Models\ProductVariant; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; -use GetCandy\Api\Core\Products\Actions\Versioning\VersionProductVariants; +use Tests\TestCase; /** * @group versioning @@ -26,7 +26,7 @@ public function test_can_version_variants() $this->assertCount(2, $product->variants); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionProductVariants)->actingAs($user)->run([ diff --git a/tests/Unit/Versioning/Actions/CreateVersionTest.php b/tests/Unit/Versioning/Actions/CreateVersionTest.php index 8d6ffdbae..941e2c26e 100644 --- a/tests/Unit/Versioning/Actions/CreateVersionTest.php +++ b/tests/Unit/Versioning/Actions/CreateVersionTest.php @@ -2,9 +2,9 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; +use Tests\TestCase; /** * @group versioning @@ -18,7 +18,7 @@ public function test_can_create_a_version_of_a_model() $model = factory(Product::class)->create(); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $model + 'model' => $model, ]); $this->assertEquals($model->id, $version->versionable_id); diff --git a/tests/Unit/Versioning/Actions/VersionAssetsTest.php b/tests/Unit/Versioning/Actions/VersionAssetsTest.php index e15aa6594..5f1b3f140 100644 --- a/tests/Unit/Versioning/Actions/VersionAssetsTest.php +++ b/tests/Unit/Versioning/Actions/VersionAssetsTest.php @@ -2,13 +2,12 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Routes\Models\Route; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Assets\Models\AssetSource; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\VersionAssets; +use Tests\TestCase; /** * @group versionings @@ -36,7 +35,7 @@ public function test_can_version_model_routes() }); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionAssets)->actingAs($user)->run([ diff --git a/tests/Unit/Versioning/Actions/VersionCategoriesTest.php b/tests/Unit/Versioning/Actions/VersionCategoriesTest.php index f61af18ce..bde984a9b 100644 --- a/tests/Unit/Versioning/Actions/VersionCategoriesTest.php +++ b/tests/Unit/Versioning/Actions/VersionCategoriesTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Categories\Models\Category; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\VersionCategories; +use Tests\TestCase; /** * @group versioning @@ -26,7 +26,7 @@ public function test_can_version_model_categories() $this->assertCount(2, $product->categories); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionCategories)->actingAs($user)->run([ diff --git a/tests/Unit/Versioning/Actions/VersionChannelsTest.php b/tests/Unit/Versioning/Actions/VersionChannelsTest.php index 976439048..560bb35be 100644 --- a/tests/Unit/Versioning/Actions/VersionChannelsTest.php +++ b/tests/Unit/Versioning/Actions/VersionChannelsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; use GetCandy\Api\Core\Channels\Models\Channel; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\VersionChannels; +use Tests\TestCase; /** * @group versioning @@ -28,7 +28,7 @@ public function test_can_create_a_version_of_a_model() $this->assertCount(2, $product->channels); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionChannels)->actingAs($user)->run([ diff --git a/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php b/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php index 32957cdb5..9b38af4f2 100644 --- a/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php +++ b/tests/Unit/Versioning/Actions/VersionCustomerGroupsTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\VersionCustomerGroups; +use Tests\TestCase; /** * @group versioning @@ -29,7 +29,7 @@ public function test_can_create_a_version_of_a_model() $this->assertCount(2, $product->customerGroups); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionCustomerGroups)->actingAs($user)->run([ diff --git a/tests/Unit/Versioning/Actions/VersionRoutesTest.php b/tests/Unit/Versioning/Actions/VersionRoutesTest.php index 602cc4b5d..779498ad5 100644 --- a/tests/Unit/Versioning/Actions/VersionRoutesTest.php +++ b/tests/Unit/Versioning/Actions/VersionRoutesTest.php @@ -2,11 +2,11 @@ namespace Tests\Unit\Versioning\Actions; -use Tests\TestCase; -use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Products\Models\Product; +use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Versioning\Actions\CreateVersion; use GetCandy\Api\Core\Versioning\Actions\VersionRoutes; +use Tests\TestCase; /** * @group versioning @@ -27,7 +27,7 @@ public function test_can_version_model_routes() $this->assertCount(2, $product->routes); $version = (new CreateVersion)->actingAs($user)->run([ - 'model' => $product + 'model' => $product, ]); (new VersionRoutes)->actingAs($user)->run([ From e025b6a209c797f7f9e644e9f59554fda23710c1 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 1 Mar 2021 12:25:13 +0000 Subject: [PATCH 109/152] Ignore database codacy --- .codacy.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.codacy.yaml b/.codacy.yaml index 481290bc1..2d25ddc84 100644 --- a/.codacy.yaml +++ b/.codacy.yaml @@ -3,4 +3,5 @@ exclude_paths: - "**/*.md" - "**/*.js" - "**/*.json" - - "tests/**/*" \ No newline at end of file + - "tests/**/*" + - "database/**/*" \ No newline at end of file From c9c17e74ab604be1e3bb22ee99c0024c6ae433a6 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 2 Mar 2021 13:40:42 +0000 Subject: [PATCH 110/152] Return invoice download --- config/getcandy.php | 6 ++++++ src/Core/Orders/Services/OrderService.php | 7 +++++++ src/Http/Controllers/Orders/OrderController.php | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/config/getcandy.php b/config/getcandy.php index d8c96c2fd..b0865daa3 100644 --- a/config/getcandy.php +++ b/config/getcandy.php @@ -58,6 +58,12 @@ 'table_columns' => [ 'name', 'reference', 'account_no', 'contact_email', 'type', 'account', 'order_total', 'delivery_total', 'zone', 'date', ], + 'invoicing' => [ + 'pdf' => [ + 'pipelines' => [ + ] + ] + ], 'statuses' => [ /* diff --git a/src/Core/Orders/Services/OrderService.php b/src/Core/Orders/Services/OrderService.php index c913291fb..ac9dc9270 100644 --- a/src/Core/Orders/Services/OrderService.php +++ b/src/Core/Orders/Services/OrderService.php @@ -26,6 +26,7 @@ use GetCandy\Api\Core\Pricing\PriceCalculatorInterface; use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; use GetCandy\Api\Core\Scaffold\BaseService; +use Illuminate\Pipeline\Pipeline; use PDF; class OrderService extends BaseService implements OrderServiceInterface @@ -839,6 +840,12 @@ public function getPdf($order) $discount->total = $total; } + $pipes = config('getcandy.orders.invoicing.pdf.pipelines', []); + + $data = app(Pipeline::class)->send($data)->through($pipes)->then(function ($data) { + return $data; + }); + $pdf = PDF::loadView(config('getcandy.invoicing.pdf', 'hub::pdf.order-invoice'), $data); return $pdf; diff --git a/src/Http/Controllers/Orders/OrderController.php b/src/Http/Controllers/Orders/OrderController.php index 241b72cf3..754b6bac4 100644 --- a/src/Http/Controllers/Orders/OrderController.php +++ b/src/Http/Controllers/Orders/OrderController.php @@ -409,7 +409,7 @@ public function invoice($id, Request $request) } $pdf = GetCandy::orders()->getPdf($order); - return new PdfResource($pdf); + return $pdf->download(); } /** From 0d81818ccc56a361df5be55a028d977bd44f2aad Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 9 Mar 2021 09:03:21 +0000 Subject: [PATCH 111/152] Update spec --- openapi/openapi.yaml | 6 ++++++ openapi/products/paths/products.variants.yaml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 8ecd62fac..463019362 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -219,6 +219,8 @@ paths: $ref: './products/paths/products.id.drafts.yaml' '/products/{productId}/publish': $ref: './products/paths/products.id.publish.yaml' + '/products/{productId}/variants': + $ref: './products/paths/products.id.variants.yaml' '/recycle-bin': $ref: './recycle-bin/paths/recycle-bin.yaml' '/recycle-bin/{itemId}': @@ -227,6 +229,10 @@ paths: $ref: './reports/paths/reports.sales.yaml' '/reports/customers/spending': $ref: './reports/paths/reports.customers.spending.yaml' + '/reports/users/{userId}': + $ref: './reports/paths/reports.users.id.yaml' + '/reports/customer-groups': + $ref: './reports/paths/reports.customer-groups.yaml' '/reports/orders': $ref: './reports/paths/reports.orders.yaml' '/reports/orders/customers': diff --git a/openapi/products/paths/products.variants.yaml b/openapi/products/paths/products.variants.yaml index 93fa62391..eb00fd2ed 100644 --- a/openapi/products/paths/products.variants.yaml +++ b/openapi/products/paths/products.variants.yaml @@ -15,4 +15,4 @@ get: type: string in: query name: include - description: Get a paginated list of all product variants in the system + description: Get a paginated list of all product variants in the system \ No newline at end of file From 3c12929b7de85ef198f4b3472a196e8def5a0036 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 9 Mar 2021 09:03:41 +0000 Subject: [PATCH 112/152] Update spec --- .../products/paths/products.id.variants.yaml | 29 ++++++++++++++ .../requests/ProductVariantsCreateBody.yaml | 40 +++++++++++++++++++ .../paths/reports.customer-groups.yaml | 24 +++++++++++ openapi/reports/paths/reports.users.id.yaml | 29 ++++++++++++++ .../CustomerGroupReportResponse.yaml | 3 ++ .../responses/CustomerSpendingResponse.yaml | 7 ++++ openapi/reports/responses/UserReport.yaml | 3 ++ 7 files changed, 135 insertions(+) create mode 100644 openapi/products/paths/products.id.variants.yaml create mode 100644 openapi/products/requests/ProductVariantsCreateBody.yaml create mode 100644 openapi/reports/paths/reports.customer-groups.yaml create mode 100644 openapi/reports/paths/reports.users.id.yaml create mode 100644 openapi/reports/responses/CustomerGroupReportResponse.yaml create mode 100644 openapi/reports/responses/CustomerSpendingResponse.yaml create mode 100644 openapi/reports/responses/UserReport.yaml diff --git a/openapi/products/paths/products.id.variants.yaml b/openapi/products/paths/products.id.variants.yaml new file mode 100644 index 000000000..f81ff25fc --- /dev/null +++ b/openapi/products/paths/products.id.variants.yaml @@ -0,0 +1,29 @@ +parameters: + - schema: + type: string + name: productId + in: path + required: true +post: + summary: Post Product Variants + tags: + - Product Variants + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/ProductVariantCollection.yaml' + operationId: post-product-variants + requestBody: + content: + application/json: + schema: + $ref: '../requests/ProductVariantsCreateBody.yaml' + parameters: + - schema: + type: string + in: query + name: include + description: Create product variants diff --git a/openapi/products/requests/ProductVariantsCreateBody.yaml b/openapi/products/requests/ProductVariantsCreateBody.yaml new file mode 100644 index 000000000..878d64255 --- /dev/null +++ b/openapi/products/requests/ProductVariantsCreateBody.yaml @@ -0,0 +1,40 @@ +title: ProductVariantsCreateBody +type: object +properties: + options: + type: array + items: + type: object + properties: + position: + type: integer + label: + type: object + properties: + en: + type: string + options: + type: array + items: + type: object + properties: + position: + type: integer + label: + type: object + properties: + en: + type: string + variants: + type: array + items: + type: object + properties: + label: + type: string + price: + type: string + sku: + type: string + inventory: + type: integer diff --git a/openapi/reports/paths/reports.customer-groups.yaml b/openapi/reports/paths/reports.customer-groups.yaml new file mode 100644 index 000000000..6383aafdf --- /dev/null +++ b/openapi/reports/paths/reports.customer-groups.yaml @@ -0,0 +1,24 @@ +get: + summary: Get customer group report + tags: + - Reports + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/CustomerGroupReportResponse.yaml' + operationId: customer-group-report + parameters: + - schema: + type: string + in: query + name: from + description: The from date + - schema: + type: string + in: query + name: to + description: The to date + description: Get customer group report \ No newline at end of file diff --git a/openapi/reports/paths/reports.users.id.yaml b/openapi/reports/paths/reports.users.id.yaml new file mode 100644 index 000000000..e25e11a7e --- /dev/null +++ b/openapi/reports/paths/reports.users.id.yaml @@ -0,0 +1,29 @@ +get: + summary: Get a report for a user + tags: + - Reports + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/UserReport.yaml' + operationId: user-report + parameters: + - schema: + type: string + name: userId + in: path + required: true + - schema: + type: string + in: query + name: from + description: The from date + - schema: + type: string + in: query + name: to + description: The to date + description: Get user report \ No newline at end of file diff --git a/openapi/reports/responses/CustomerGroupReportResponse.yaml b/openapi/reports/responses/CustomerGroupReportResponse.yaml new file mode 100644 index 000000000..fa07eda79 --- /dev/null +++ b/openapi/reports/responses/CustomerGroupReportResponse.yaml @@ -0,0 +1,3 @@ +title: CustomerGroupResponse +type: object +properties: {} diff --git a/openapi/reports/responses/CustomerSpendingResponse.yaml b/openapi/reports/responses/CustomerSpendingResponse.yaml new file mode 100644 index 000000000..43410dfbc --- /dev/null +++ b/openapi/reports/responses/CustomerSpendingResponse.yaml @@ -0,0 +1,7 @@ +title: CustomerSpendingResponse +type: object +properties: + period: + type: object + sub_total: + type: string diff --git a/openapi/reports/responses/UserReport.yaml b/openapi/reports/responses/UserReport.yaml new file mode 100644 index 000000000..78a857ff9 --- /dev/null +++ b/openapi/reports/responses/UserReport.yaml @@ -0,0 +1,3 @@ +title: UserReport +type: object +properties: {} From 77c8c242660eacce4d5870dd9ff286a3cba85a63 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 9 Mar 2021 11:11:37 +0000 Subject: [PATCH 113/152] Update guzzle --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index af234eac2..6e870a516 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "neondigital/laravel-versioning": "dev-master", "staudenmeir/eloquent-has-many-deep": "^1.13", "laravel/legacy-factories": "^1.0.4", - "guzzlehttp/guzzle": "^7.1.0" + "guzzlehttp/guzzle": "^7.2.0" }, "require-dev": { "fzaninotto/faker": "~1.4", From 358489846519de27727a560962d958ecef55ba43 Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 9 Mar 2021 11:12:00 +0000 Subject: [PATCH 114/152] Update user fetching and reports --- .../Actions/GetCustomerSpendingReport.php | 26 ++++++++++--------- src/Core/Users/Actions/FetchUsers.php | 11 +++++++- src/Core/Users/Resources/UserResource.php | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php index 4b3ae3c52..e29495482 100644 --- a/src/Core/Reports/Actions/GetCustomerSpendingReport.php +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -29,6 +29,7 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date', + 'export' => 'nullable|boolean' ]; } @@ -55,25 +56,26 @@ public function handle() $join->on('users.id', '=', 'orders.user_id')->whereNotNull('orders.user_id'); }) ->groupBy('billing_email')->orderBy('sub_total', 'desc') - ->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'asc') - ->paginate(50); + ->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'asc'); - $items = $result->getCollection()->map(function ($row) { - $userModel = GetCandy::getUserModel(); - - return array_merge($row->toArray(), [ - 'user_id' => $row->user_id ? (new $userModel)->encode($row->user_id) : null, - ]); - }); - - $result->setCollection($items); + if (!$this->export) { + $result = $result->paginate(50); + $items = $result->getCollection()->map(function ($row) { + $userModel = GetCandy::getUserModel(); + + return array_merge($row->toArray(), [ + 'user_id' => $row->user_id ? (new $userModel)->encode($row->user_id) : null, + ]); + }); + $result->setCollection($items); + } return [ 'period' => [ 'from' => $this->from, 'to' => $this->to, ], - 'data' => $result, + 'data' => $this->export ? $result->get() : $result, ]; } } diff --git a/src/Core/Users/Actions/FetchUsers.php b/src/Core/Users/Actions/FetchUsers.php index fe792c6b9..50d80f5a6 100644 --- a/src/Core/Users/Actions/FetchUsers.php +++ b/src/Core/Users/Actions/FetchUsers.php @@ -30,6 +30,7 @@ public function rules() return [ 'per_page' => 'numeric|max:200', 'paginate' => 'boolean', + 'sort' => 'nullable|string', 'keywords' => 'nullable|string', 'ids' => 'array', ]; @@ -44,7 +45,9 @@ public function handle() { $userModel = GetCandy::getUserModel(); - $query = (new $userModel)->with(['customer']); + $sorting = $this->sort ? explode(':', $this->sort) : null; + + $query = (new $userModel)->select('users.*')->with(['customer'])->leftJoin('customers', 'customers.id', '=', 'users.customer_id'); if ($this->keywords) { $keywords = explode(' ', $this->keywords); foreach ($keywords as $keyword) { @@ -64,10 +67,16 @@ public function handle() $query = $query->whereIn('id', $realIds); } + + if ($sorting) { + $query->orderBy($sorting[0], $sorting[1]); + } + if (! $this->paginate) { return $query->get(); } + return $query->paginate($this->per_page ?? 50); } diff --git a/src/Core/Users/Resources/UserResource.php b/src/Core/Users/Resources/UserResource.php index ba102e45f..c0d941869 100644 --- a/src/Core/Users/Resources/UserResource.php +++ b/src/Core/Users/Resources/UserResource.php @@ -20,7 +20,7 @@ public function payload() return [ 'id' => $this->encoded_id, 'email' => $this->email, - 'name' => $this->name, + 'name' => $this->name ?: "{$this->firstname} {$this->lastname}", 'created_at' => $this->created_at, ]; } From ec1935a9350d51a16d23dcd70db25b244e237dac Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Tue, 9 Mar 2021 11:19:35 +0000 Subject: [PATCH 115/152] Fix OpenAPI spec error (#361) * Fix OpenAPI spec error * Sorting tags into groups for API Reference build * Remove incorrect conflict change --- openapi/openapi.yaml | 86 +++++++++++-------- .../responses/CustomerSpendingResponse.yaml | 10 --- 2 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 openapi/reports/paths/responses/CustomerSpendingResponse.yaml diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 463019362..166bd0736 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,15 +1,16 @@ openapi: 3.0.0 -x-samples-languages: ['curl', 'node', 'php', 'javascript', 'python'] info: title: GetCandy contact: name: GetCandy url: 'https://getcandy.io' - email: support@getcandy.io - version: 1.0.0 + version: 0.12.0 license: name: MIT description: The GetCandy API + x-logo: + url: "https://getcandy-docs.vercel.app/getcandy_logo.svg" + altText: "GetCandy API" servers: - url: 'http://localhost:8000/api/v1' paths: @@ -295,38 +296,47 @@ paths: $ref: './users/paths/users.current.yaml' '/versions/{modelId}/restore': $ref: './versions/paths/versions.id.restore.yaml' -tags: - - name: Account - description: Account management - - name: Assets - description: Catalogue Management - - name: Channels - description: Store channels - - name: Categories - description: Catalogue Management - - name: Orders - description: Order Processing - - name: Attributes - description: Catalogue Management - - name: Associations - description: Catalogue Management - - name: Baskets - description: Order Processing - - name: Payments - description: Order Processing - - name: Collections - description: Catalogue Management - - name: Customers - description: Order Processing - - name: Discounts - description: Order Processing - - name: Languages - description: System Settings - - name: Layouts - description: Catalogue Management - - name: Products - description: Catalogue Management - - name: Product Variants - description: Catalogue Management - - name: Taxes - description: System Settings +x-tagGroups: + - name: General + tags: + - Root + - Countries + - Currencies + - Channels + - Languages + - Recycle Bin + - Settings + - Tags + - name: Catalogue Management + tags: + - Assets + - Associations + - Attributes + - Categories + - Collections + - Layouts + - Product Families + - Product Variants + - Products + - Routes + - Search + - Taxes + - Versioning + - name: User Management + tags: + - Account + - Addresses + - Customer Groups + - Customers + - User + - Users + - name: Order Processing + tags: + - Baskets + - Discounts + - Orders + - Payments + - Shipping + - name: Reports + tags: + - Reports diff --git a/openapi/reports/paths/responses/CustomerSpendingResponse.yaml b/openapi/reports/paths/responses/CustomerSpendingResponse.yaml deleted file mode 100644 index 50da71041..000000000 --- a/openapi/reports/paths/responses/CustomerSpendingResponse.yaml +++ /dev/null @@ -1,10 +0,0 @@ -title: CustomerSpendingResponse -type: object -properties: - period: - type: object - sub_total: - type: string - data: - type: array - From 4875541e9928689ecd44a399b56dfa27c3b96829 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 10 Mar 2021 10:57:25 +0000 Subject: [PATCH 116/152] Fire indexable event --- src/Core/Products/Drafting/ProductDrafter.php | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index bf474e334..5c5b5f674 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -3,23 +3,24 @@ namespace GetCandy\Api\Core\Products\Drafting; use DB; +use Versioning; +use Illuminate\Database\Eloquent\Model; +use GetCandy\Api\Core\Drafting\BaseDrafter; use GetCandy\Api\Core\Drafting\Actions\DraftAssets; -use GetCandy\Api\Core\Drafting\Actions\DraftCategories; -use GetCandy\Api\Core\Drafting\Actions\DraftChannels; -use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; -use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; -use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Drafting\Actions\DraftChannels; use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; +use NeonDigital\Drafting\Interfaces\DrafterInterface; +use GetCandy\Api\Core\Drafting\Actions\DraftCategories; use GetCandy\Api\Core\Drafting\Actions\PublishChannels; +use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; -use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; -use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; -use GetCandy\Api\Core\Drafting\BaseDrafter; -use Illuminate\Database\Eloquent\Model; -use NeonDigital\Drafting\Interfaces\DrafterInterface; -use Versioning; +use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; +use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; class ProductDrafter extends BaseDrafter implements DrafterInterface { @@ -132,6 +133,8 @@ public function publish(Model $draft) // Delete the draft we had. $draft->forceDelete(); + IndexableSavedEvent::dispatch($parent); + return $parent; }); } From e30def7bdae85b92de046ba3a62497e4ebd5eb88 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 10 Mar 2021 10:57:34 +0000 Subject: [PATCH 117/152] Trim the SKU --- src/Core/Products/Services/ProductService.php | 2 +- src/Core/Products/Services/ProductVariantService.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Products/Services/ProductService.php b/src/Core/Products/Services/ProductService.php index 8fd260e47..6acd6ad51 100644 --- a/src/Core/Products/Services/ProductService.php +++ b/src/Core/Products/Services/ProductService.php @@ -226,7 +226,7 @@ public function create(array $data) 'options' => [], 'stock' => $data['stock'] ?? 0, 'incoming' => $data['incoming'] ?? 0, - 'sku' => $sku, + 'sku' => trim($sku), 'price' => $data['price'], 'pricing' => $this->getPriceMapping($data['price']), 'min_qty' => $data['min_qty'] ?? 1, diff --git a/src/Core/Products/Services/ProductVariantService.php b/src/Core/Products/Services/ProductVariantService.php index 513351620..45efac528 100644 --- a/src/Core/Products/Services/ProductVariantService.php +++ b/src/Core/Products/Services/ProductVariantService.php @@ -65,7 +65,7 @@ public function create($id, array $data) $variant = $product->variants()->create([ 'price' => $newVariant['price'], - 'sku' => $sku, + 'sku' => trim($sku), 'stock' => $newVariant['inventory'], 'options' => $newVariant['options'], ]); From 478ada4f9e5f15096376c2822badf6f7d0a527e9 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 10 Mar 2021 11:33:20 +0000 Subject: [PATCH 118/152] Fix drafting --- .../Drafting/Actions/PublishProductVariants.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Core/Drafting/Actions/PublishProductVariants.php b/src/Core/Drafting/Actions/PublishProductVariants.php index f972b9040..e64626368 100644 --- a/src/Core/Drafting/Actions/PublishProductVariants.php +++ b/src/Core/Drafting/Actions/PublishProductVariants.php @@ -38,12 +38,16 @@ public function handle() { $variants = $this->draft->variants()->withDrafted()->get(); + $skus = collect([]); + foreach ($variants as $incoming) { if ($incoming->publishedParent) { $parent = $incoming->publishedParent; - $parent->update( - collect($incoming->toArray())->except(['id', 'product_id'])->toArray() - ); + $modelData = collect($incoming->toArray())->except(['id', 'product_id', 'options'])->toArray(); + // We don't want to interact with the accessor so we have to do this. + // TODO: Remove the options accessor junk + $modelData['options'] = $incoming->getAttributes()['options']; + $parent->update($modelData); (new PublishProductVariantCustomerPricing)->actingAs($this->user())->run([ 'draft' => $incoming, @@ -59,8 +63,12 @@ public function handle() 'product_id' => $this->parent->id, ]); } + $skus->push($incoming->sku); } + // Any skus we dont have, remove from the parent. + $this->parent->variants()->whereNotIn('sku', $skus->toArray())->delete(); + return $this->parent->refresh(); } } From abe613dc36e466bb5e330a0488f271ecf9075b32 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 10 Mar 2021 12:13:57 +0000 Subject: [PATCH 119/152] Add action to get current customer groups --- .../Actions/FetchCurrentCustomerGroups.php | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/Core/Customers/Actions/FetchCurrentCustomerGroups.php diff --git a/src/Core/Customers/Actions/FetchCurrentCustomerGroups.php b/src/Core/Customers/Actions/FetchCurrentCustomerGroups.php new file mode 100644 index 000000000..e072f4b39 --- /dev/null +++ b/src/Core/Customers/Actions/FetchCurrentCustomerGroups.php @@ -0,0 +1,57 @@ +user(); + $defaultGroup = FetchDefaultCustomerGroup::run(); + $guestGroups = [$defaultGroup->id]; + if (! $user) { + return $guestGroups; + } + + $hubAccess = $user->hasAnyRole(['admin']); + + if (($hubAccess && GetCandy::isHubRequest()) || + (! GetCandy::isHubRequest() && ! $hubAccess) + ) { + return $user->customer->customerGroups->pluck('id')->toArray(); + } + + return $guestGroups; + } +} From 8a6102dd832a81ef11627e4a710949b87d91529b Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 10 Mar 2021 13:47:48 +0000 Subject: [PATCH 120/152] Make threshold 1 for displaying --- src/Core/Reports/Actions/GetCustomerSpendingReport.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php index e29495482..9ef620eba 100644 --- a/src/Core/Reports/Actions/GetCustomerSpendingReport.php +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -3,9 +3,10 @@ namespace GetCandy\Api\Core\Reports\Actions; use GetCandy; +use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; -use Illuminate\Support\Facades\DB; class GetCustomerSpendingReport extends AbstractAction { @@ -51,7 +52,7 @@ public function handle() )->whereNotNull('billing_email')->whereBetween('placed_at', [ $this->from, $this->to, - ])->where('sub_total', '>', 10000) + ])->where('sub_total', '>', 100) ->leftJoin('users', function ($join) { $join->on('users.id', '=', 'orders.user_id')->whereNotNull('orders.user_id'); }) From 39b1842153d867a9c28bb71c79506c4a4c480462 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Mar 2021 07:56:02 +0000 Subject: [PATCH 121/152] Return element no matter what --- src/Core/RecycleBin/Models/RecycleBin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/RecycleBin/Models/RecycleBin.php b/src/Core/RecycleBin/Models/RecycleBin.php index 0c26d06ad..ffaf5186d 100644 --- a/src/Core/RecycleBin/Models/RecycleBin.php +++ b/src/Core/RecycleBin/Models/RecycleBin.php @@ -24,6 +24,6 @@ class RecycleBin extends BaseModel */ public function recyclable() { - return $this->morphTo()->onlyTrashed(); + return $this->morphTo()->onlyTrashed()->withoutGlobalScopes(); } } From f0b64d0cdb04dffaed842454d0b2f54c045b6b09 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Mar 2021 10:21:31 +0000 Subject: [PATCH 122/152] Fixes to asset id association --- .../Services/ProductVariantService.php | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Core/Products/Services/ProductVariantService.php b/src/Core/Products/Services/ProductVariantService.php index 45efac528..b4141223f 100644 --- a/src/Core/Products/Services/ProductVariantService.php +++ b/src/Core/Products/Services/ProductVariantService.php @@ -3,12 +3,13 @@ namespace GetCandy\Api\Core\Products\Services; use GetCandy; -use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Assets\Models\Asset; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Foundation\Actions\DecodeId; -use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; +use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Core\Products\Models\ProductVariant; -use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; class ProductVariantService extends BaseService { @@ -161,20 +162,25 @@ public function update($hashedId, array $data) // Get the product variants $variants = $variant->product->variants; + if (! empty($data['asset_id'])) { + $data['asset_id'] = (new Asset)->decodeId($data['asset_id']); + } + $variant->fill($data); $thumbnailId = null; - if (! empty($data['image'])) { - $imageId = $data['image']['id']; - } elseif (! empty($data['image_id'])) { - $imageId = $data['image_id']; - } + // if (! empty($data['image'])) { + // $imageId = $data['image']['id']; + // } elseif (! empty($data['image_id'])) { + // $imageId = $data['image_id']; + // } + + // if (! empty($imageId)) { + // $asset = GetCandy::assets()->getByHashedId($imageId); + // $variant->image()->associate($asset); + // } - if (! empty($imageId)) { - $asset = GetCandy::assets()->getByHashedId($imageId); - $variant->image()->associate($asset); - } if (! empty($data['tax_id'])) { $variant->tax()->associate( From fb85c23f4af9177cc64946b0eb6cccd0d5d977e7 Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 11 Mar 2021 12:46:24 +0000 Subject: [PATCH 123/152] Add check before we remove the column --- ..._05_093543_add_billing_shipping_company_name_to_orders.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php index 41ec5b585..7a9e7c1b4 100644 --- a/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php +++ b/database/migrations/2021_02_05_093543_add_billing_shipping_company_name_to_orders.php @@ -12,7 +12,9 @@ class AddBillingShippingCompanyNameToOrders extends Migration public function up() { Schema::table('orders', function (Blueprint $table) { - $table->dropColumn('company_name'); + if (Schema::hasColumn('orders', 'company_name')) { + $table->dropColumn('company_name'); + } if (! Schema::hasColumn('orders', 'billing_company_name')) { $table->string('billing_company_name')->after('billing_email')->nullable()->index(); } From 8bf1059dee41f2d67e1f13d0b8ebc1281407d2f8 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 15 Mar 2021 18:03:01 +0000 Subject: [PATCH 124/152] Add report exports --- ..._03_15_110640_add_report_exports_table.php | 39 ++++++ routes/api.php | 12 +- .../Reports/Actions/DownloadReportExport.php | 33 +++++ src/Core/Reports/Actions/ExportReport.php | 68 ++++++++++ .../Actions/GetCustomerGroupReport.php | 49 +++++++- .../Actions/GetCustomerSpendingReport.php | 51 +++++++- .../Actions/GetNewVsReturningReport.php | 95 ++++++++++++++ .../Actions/GetOrderAveragesReport.php | 49 +++++++- .../Reports/Actions/GetOrderTotalsReport.php | 118 ++++++++++++++++++ .../Reports/Actions/GetProductBestSellers.php | 51 +++++++- src/Core/Reports/Actions/GetReportExports.php | 27 ++++ src/Core/Reports/Actions/GetSalesReport.php | 112 +++++++++++++++++ src/Core/Reports/Mail/ReportExported.php | 35 ++++++ src/Core/Reports/Models/ReportExport.php | 33 +++++ .../Resources/ReportExportCollection.php | 11 ++ .../Resources/ReportExportResource.php | 37 ++++++ .../Controllers/Reports/ReportController.php | 85 ------------- 17 files changed, 808 insertions(+), 97 deletions(-) create mode 100644 database/migrations/2021_03_15_110640_add_report_exports_table.php create mode 100644 src/Core/Reports/Actions/DownloadReportExport.php create mode 100644 src/Core/Reports/Actions/ExportReport.php create mode 100644 src/Core/Reports/Actions/GetNewVsReturningReport.php create mode 100644 src/Core/Reports/Actions/GetOrderTotalsReport.php create mode 100644 src/Core/Reports/Actions/GetReportExports.php create mode 100644 src/Core/Reports/Actions/GetSalesReport.php create mode 100644 src/Core/Reports/Mail/ReportExported.php create mode 100644 src/Core/Reports/Models/ReportExport.php create mode 100644 src/Core/Reports/Resources/ReportExportCollection.php create mode 100644 src/Core/Reports/Resources/ReportExportResource.php diff --git a/database/migrations/2021_03_15_110640_add_report_exports_table.php b/database/migrations/2021_03_15_110640_add_report_exports_table.php new file mode 100644 index 000000000..83038c1bb --- /dev/null +++ b/database/migrations/2021_03_15_110640_add_report_exports_table.php @@ -0,0 +1,39 @@ +id(); + $userIdColType = Schema::getColumnType('users', 'id'); + if ($userIdColType == 'integer') { + $table->unsignedInteger('user_id'); + $table->foreign('user_id')->references('id')->on('users'); + } else { + $table->foreignId('user_id')->constrained(); + } + $table->string('report')->index(); + $table->string('filename')->nullable(); + $table->string('path')->nullable(); + $table->datetime('started_at')->nullable(); + $table->datetime('completed_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('report_exports'); + } +} diff --git a/routes/api.php b/routes/api.php index d9595dbe5..295e85dc4 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,8 @@ prefix('reports')->namespace('Reports')->group(function ($router) { - $router->get('/sales', 'ReportController@sales'); - $router->get('/orders', 'ReportController@orders'); - $router->get('/orders/customers', 'ReportController@orderCustomers'); + $router->get('exports', '\GetCandy\Api\Core\Reports\Actions\GetReportExports'); + $router->get('/sales', '\GetCandy\Api\Core\Reports\Actions\GetSalesReport'); + $router->get('/orders', '\GetCandy\Api\Core\Reports\Actions\GetOrderTotalsReport'); + $router->get('/orders/customers', '\GetCandy\Api\Core\Reports\Actions\GetNewVsReturningReport'); $router->get('/customers/spending', '\GetCandy\Api\Core\Reports\Actions\GetCustomerSpendingReport'); $router->get('/customer-groups', '\GetCandy\Api\Core\Reports\Actions\GetCustomerGroupReport'); $router->get('/orders/averages', '\GetCandy\Api\Core\Reports\Actions\GetOrderAveragesReport'); $router->get('/products/best-sellers', '\GetCandy\Api\Core\Reports\Actions\GetProductBestSellers'); $router->get('/users/{userId}', '\GetCandy\Api\Core\Reports\Actions\GetUserReport'); $router->get('/metrics/{subject}', 'ReportController@metrics'); + $router->get('exports/download/{id}', '\GetCandy\Api\Core\Reports\Actions\DownloadReportExport') + ->withoutMiddleware(['auth:api', 'auth:sanctum'])->name('export.download'); }); /* diff --git a/src/Core/Reports/Actions/DownloadReportExport.php b/src/Core/Reports/Actions/DownloadReportExport.php new file mode 100644 index 000000000..cde4af955 --- /dev/null +++ b/src/Core/Reports/Actions/DownloadReportExport.php @@ -0,0 +1,33 @@ +request->hasValidSignature(); + } + public function rules() + { + return []; + } + + public function handle($id) + { + $realId = (new ReportExport)->decodeId($id); + $export = ReportExport::findOrFail($realId); + + try { + return Storage::download("{$export->path}/{$export->filename}"); + } catch (\Exception $e) { + abort(404); + } + } +} \ No newline at end of file diff --git a/src/Core/Reports/Actions/ExportReport.php b/src/Core/Reports/Actions/ExportReport.php new file mode 100644 index 000000000..ea65558cd --- /dev/null +++ b/src/Core/Reports/Actions/ExportReport.php @@ -0,0 +1,68 @@ + 'required|string', + 'export' => 'required', + 'args' => 'required', + ]; + } + + public function handle() + { + $args = array_merge($this->args, [ + 'export' => false, + 'paginate' => false, + ]); + + $report = (new $this->report)->actingAs($this->user()); + + if (method_exists($this->report, 'getExportData')) { + $result = $report->getExportData($args); + } else { + $result = $report->run($args); + } + + $result = $result['data'] ?? $result; + + // Create our export file... + $filename = $report->getExportFilename() . '.csv'; + $location = 'reporting/exports/' . now()->format('Y/m/d'); + + Storage::put("{$location}/{$filename}", null); + + $fp = fopen(storage_path("app/{$location}/{$filename}"), 'wb'); + + fputcsv($fp, $report->getCsvHeaders()); + + foreach ( $result as $row ) { + $val = $report->getCsvRow($row); + fputcsv($fp, $val); + } + + $url = URL::signedRoute( + 'export.download', ['id' => $this->export->encoded_id] + ); + + Mail::to($this->user()->email)->queue( + new ReportExported($url) + ); + + $this->export->update([ + 'filename' => $filename, + 'path' => $location, + 'completed_at' => now(), + ]); + } +} \ No newline at end of file diff --git a/src/Core/Reports/Actions/GetCustomerGroupReport.php b/src/Core/Reports/Actions/GetCustomerGroupReport.php index e4cf4e28c..31b7855c9 100644 --- a/src/Core/Reports/Actions/GetCustomerGroupReport.php +++ b/src/Core/Reports/Actions/GetCustomerGroupReport.php @@ -3,10 +3,13 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; -use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Reports\Models\ReportExport; +use GetCandy\Api\Core\Reports\Actions\ExportReport; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Reports\Resources\ReportExportResource; class GetCustomerGroupReport extends AbstractAction { @@ -30,9 +33,36 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date', + 'paginate' => 'nullable' ]; } + public function getCsvHeaders() + { + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + $headers = ['Customer Group']; + foreach ($period as $date) { + $headers[] = $date->format('F Y'); + } + return $headers; + } + + public function getExportFilename() + { + return 'customer-group-report' . $this->from . '-' . $this->to; + } + + public function getCsvRow($row) + { + $data = [$row['label']]; + + foreach($row['data'] as $item) { + $data[] = $item->sub_total / 100; + } + + return $data; + } + /** * Execute the action and return a result. * @@ -40,6 +70,21 @@ public function rules() */ public function handle() { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'customer-group', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + $query = Order::whereNotNull('placed_at'); // $displayFormat = $formats['display']; diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php index 9ef620eba..85a4fa1bf 100644 --- a/src/Core/Reports/Actions/GetCustomerSpendingReport.php +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -7,6 +7,8 @@ use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Models\ReportExport; +use GetCandy\Api\Core\Reports\Resources\ReportExportResource; class GetCustomerSpendingReport extends AbstractAction { @@ -30,7 +32,33 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date', - 'export' => 'nullable|boolean' + 'export' => 'nullable|boolean', + 'paginate' => 'nullable', + ]; + } + + public function getCsvHeaders () + { + return [ + 'has_account', + 'name', + 'email', + 'total_spent', + ]; + } + + public function getExportFilename() + { + return 'customer-spending_' . $this->from . '-' . $this->to; + } + + public function getCsvRow($row) + { + return [ + !!$row->user_id, + "{$row->firstname} {$row->lastname}", + $row->email, + $row->sub_total / 100 ]; } @@ -41,6 +69,23 @@ public function rules() */ public function handle() { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'customer-spending', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + + $paginate = $this->get('paginate', true); + $result = Order::whereNotNull('placed_at') ->select( DB::RAW('SUM(sub_total) as sub_total'), @@ -59,7 +104,7 @@ public function handle() ->groupBy('billing_email')->orderBy('sub_total', 'desc') ->orderBy(DB::RAW("DATE_FORMAT(placed_at, '%Y-%m')"), 'asc'); - if (!$this->export) { + if ($paginate) { $result = $result->paginate(50); $items = $result->getCollection()->map(function ($row) { $userModel = GetCandy::getUserModel(); @@ -76,7 +121,7 @@ public function handle() 'from' => $this->from, 'to' => $this->to, ], - 'data' => $this->export ? $result->get() : $result, + 'data' => $paginate ? $result : $result->get(), ]; } } diff --git a/src/Core/Reports/Actions/GetNewVsReturningReport.php b/src/Core/Reports/Actions/GetNewVsReturningReport.php new file mode 100644 index 000000000..d5bd993db --- /dev/null +++ b/src/Core/Reports/Actions/GetNewVsReturningReport.php @@ -0,0 +1,95 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date', + 'paginate' => 'nullable', + 'mode' => 'nullable' + ]; + } + + public function getCsvHeaders() + { + return [ + 'Month', + 'New', + 'Returning', + 'Total', + ]; + } + + public function getExportFilename() + { + return 'new_vs_returning_customers_' . $this->from . '-' . $this->to; + } + + public function getCsvRow($row) + { + return [ + $row['label'], + $row['new'], + $row['returning'], + $row['total'], + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle(ReportManagerContract $reports) + { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'new-vs-returning-customers-report', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + + $report = $reports->with('orders') + ->mode($this->mode ?: 'monthly') + ->between( + Carbon::parse($this->from), + Carbon::parse($this->to) + )->customers(); + + return $report; + } +} diff --git a/src/Core/Reports/Actions/GetOrderAveragesReport.php b/src/Core/Reports/Actions/GetOrderAveragesReport.php index 618c88ee4..ca83e2124 100644 --- a/src/Core/Reports/Actions/GetOrderAveragesReport.php +++ b/src/Core/Reports/Actions/GetOrderAveragesReport.php @@ -3,9 +3,12 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; -use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Models\ReportExport; +use GetCandy\Api\Core\Reports\Actions\ExportReport; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Reports\Resources\ReportExportResource; class GetOrderAveragesReport extends AbstractAction { @@ -29,9 +32,36 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date|after:from', + 'export' => 'nullable|boolean' ]; } + public function getCsvHeaders() + { + $period = CarbonPeriod::create($this->from, '1 month', $this->to); + $headers = ['Customer Group']; + foreach ($period as $date) { + $headers[] = $date->format('F Y'); + } + return $headers; + } + + public function getExportFilename() + { + return 'order-averages_' . $this->from . '-' . $this->to; + } + + public function getCsvRow($row) + { + $data = [$row['label']]; + + foreach($row['data'] as $item) { + $data[] = $item['sub_total'] / 100; + } + + return $data; + } + /** * Execute the action and return a result. * @@ -39,6 +69,21 @@ public function rules() */ public function handle() { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'order-averages', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + // Get our customer groups. $groups = FetchCustomerGroups::run([ 'exclude' => config('getcandy.reports.customer_groups.exclude', []), diff --git a/src/Core/Reports/Actions/GetOrderTotalsReport.php b/src/Core/Reports/Actions/GetOrderTotalsReport.php new file mode 100644 index 000000000..1f421fb1e --- /dev/null +++ b/src/Core/Reports/Actions/GetOrderTotalsReport.php @@ -0,0 +1,118 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date', + 'paginate' => 'nullable', + 'mode' => 'nullable' + ]; + } + + public function getCsvHeaders() + { + return [ + 'Month', + 'Current Period', + 'Previous Period' + ]; + } + + public function getExportFilename() + { + return 'order-totals_' . $this->from . '-' . $this->to; + } + + public function getExportData($args) + { + $result = $this->run($args); + + $currentPeriod = $result['currentPeriod']; + $previousPeriod = $result['previousPeriod']; + + $data = []; + + foreach ($currentPeriod as $index => $totals) { + + $previous = collect($previousPeriod)->first(function ($t) use ($totals) { + return $t->month === $totals->month && $t->year == $totals->year - 1; + }); + + $data[] = [ + 'month' => "{$totals->month} {$totals->year}", + 'sub_total' => $totals->sub_total / 100, + 'previous_sub_total' => ($previous->sub_total ?? 0) / 100 + ]; + } + + return $data; + } + + public function getCsvRow($row) + { + return [ + $row['month'], + $row['sub_total'], + $row['previous_sub_total'], + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle(ReportManagerContract $reports) + { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'sales-report', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + + $report = $reports->with('orders') + ->mode($this->mode ?: 'monthly') + ->between( + Carbon::parse($this->from), + Carbon::parse($this->to) + )->get(); + + return $report; + } +} diff --git a/src/Core/Reports/Actions/GetProductBestSellers.php b/src/Core/Reports/Actions/GetProductBestSellers.php index 5ff19a67e..60dfc87db 100644 --- a/src/Core/Reports/Actions/GetProductBestSellers.php +++ b/src/Core/Reports/Actions/GetProductBestSellers.php @@ -2,8 +2,11 @@ namespace GetCandy\Api\Core\Reports\Actions; -use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Models\ReportExport; +use GetCandy\Api\Core\Reports\Actions\ExportReport; +use GetCandy\Api\Core\Reports\Resources\ReportExportResource; class GetProductBestSellers extends AbstractAction { @@ -28,6 +31,33 @@ public function rules() 'from' => 'nullable|date', 'to' => 'nullable|date|after:from', 'term' => 'nullable|string', + 'export' => 'nullable', + 'paginate' => 'nullable', + ]; + } + + public function getCsvHeaders () + { + return [ + 'product', + 'sku', + 'quantity', + 'sub_total', + ]; + } + + public function getExportFilename() + { + return 'product-best-sellers_' . $this->from . '_' . $this->to; + } + + public function getCsvRow($row) + { + return [ + $row->description, + $row->sku, + $row->quantity, + $row->sub_total / 100 ]; } @@ -38,6 +68,21 @@ public function rules() */ public function handle() { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'product-best-sellers', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + $query = DB::table('order_lines') ->select( DB::RAW('SUM(quantity) as quantity'), @@ -62,6 +107,8 @@ public function handle() $query->where('sku', 'LIKE', "%{$this->term}%"); } - return $query->paginate(50); + $paginate = $this->get('paginate', true); + + return $paginate ? $query->paginate(50) : $query->get(); } } diff --git a/src/Core/Reports/Actions/GetReportExports.php b/src/Core/Reports/Actions/GetReportExports.php new file mode 100644 index 000000000..7e5c086e6 --- /dev/null +++ b/src/Core/Reports/Actions/GetReportExports.php @@ -0,0 +1,27 @@ +user()->id)->whereNotNull('completed_at')->paginate(25); + } + + public function response($result, $request) + { + return new ReportExportCollection($result); + } +} \ No newline at end of file diff --git a/src/Core/Reports/Actions/GetSalesReport.php b/src/Core/Reports/Actions/GetSalesReport.php new file mode 100644 index 000000000..5436d64db --- /dev/null +++ b/src/Core/Reports/Actions/GetSalesReport.php @@ -0,0 +1,112 @@ +user()->can('view-reports'); + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'from' => 'nullable|date', + 'to' => 'nullable|date', + 'paginate' => 'nullable', + 'mode' => 'nullable' + ]; + } + + public function getCsvHeaders() + { + return [ + 'Month', + 'Orders', + 'Revenue' + ]; + } + + public function getExportFilename() + { + return 'order-sales_' . $this->from . '-' . $this->to; + } + + public function getExportData($args) + { + $result = $this->run($args); + + $orders = $result['datasets'][0]['data']; + $revenue = $result['datasets'][1]['data']; + + $data = []; + + foreach ($orders as $index => $orderValue) { + $data[] = [ + 'month' => $result['labels'][$index], + 'orders' => $orderValue, + 'revenue' => $revenue[$index] / 100 + ]; + } + return $data; + } + + public function getCsvRow($row) + { + return [ + $row['month'], + $row['orders'], + $row['revenue'], + ]; + } + + /** + * Execute the action and return a result. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function handle(ReportManagerContract $reports) + { + if ($this->export) { + // Create the export + $export = ReportExport::create([ + 'user_id' => $this->user()->id, + 'report' => 'sales-report', + 'started_at' => now(), + ]); + ExportReport::dispatch([ + 'report' => self::class, + 'export' => $export, + 'args' => $this->validated(), + ]); + return new ReportExportResource($export); + } + + $report = $reports->with('sales') + ->mode($this->mode ?: 'monthly') + ->between( + Carbon::parse($this->from), + Carbon::parse($this->to) + )->get(); + + return $report; + } +} diff --git a/src/Core/Reports/Mail/ReportExported.php b/src/Core/Reports/Mail/ReportExported.php new file mode 100644 index 000000000..a660886fc --- /dev/null +++ b/src/Core/Reports/Mail/ReportExported.php @@ -0,0 +1,35 @@ +link = $link; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->view('report-exported'); + } +} diff --git a/src/Core/Reports/Models/ReportExport.php b/src/Core/Reports/Models/ReportExport.php new file mode 100644 index 000000000..f99228df6 --- /dev/null +++ b/src/Core/Reports/Models/ReportExport.php @@ -0,0 +1,33 @@ +belongsTo(GetCandy::getUserModel()); + } +} diff --git a/src/Core/Reports/Resources/ReportExportCollection.php b/src/Core/Reports/Resources/ReportExportCollection.php new file mode 100644 index 000000000..6878f78fb --- /dev/null +++ b/src/Core/Reports/Resources/ReportExportCollection.php @@ -0,0 +1,11 @@ +path) { + try { + $content = base64_encode(Storage::get("{$this->path}/{$this->filename}")); + } catch (\Exception $e) { + + } + } + return [ + 'id' => $this->encodedId(), + 'filename' => $this->filename, + 'report' => $this->report, + 'path' => $this->path, + 'content' => $content, + 'started_at' => $this->started_at, + 'completed_at' => $this->completed_at, + ]; + } +} \ No newline at end of file diff --git a/src/Http/Controllers/Reports/ReportController.php b/src/Http/Controllers/Reports/ReportController.php index c4dec6bc1..e14e48c50 100644 --- a/src/Http/Controllers/Reports/ReportController.php +++ b/src/Http/Controllers/Reports/ReportController.php @@ -9,95 +9,10 @@ class ReportController extends BaseController { - public function sales(Request $request, ReportManagerContract $reports) - { - $this->validate($request, [ - 'from' => 'required|date', - 'to' => 'required|date', - ]); - - $report = $reports->with('sales') - ->mode($request->mode ?: 'monthly') - ->between( - Carbon::parse($request->from), - Carbon::parse($request->to) - )->get(); - - return response()->json($report); - } - public function metrics(Request $request, ReportManagerContract $reports) { $report = $reports->with($request->subject)->metrics(); return response()->json($report); } - - public function orders(Request $request, ReportManagerContract $reports) - { - $this->validate($request, [ - 'from' => 'required|date', - 'to' => 'required|date|after:from', - ]); - - $report = $reports->with('orders') - ->mode($request->mode ?: 'monthly') - ->between( - Carbon::parse($request->from), - Carbon::parse($request->to) - )->get(); - - return response()->json($report); - } - - public function orderCustomers(Request $request, ReportManagerContract $reports) - { - $this->validate($request, [ - 'from' => 'required|date', - 'to' => 'required|date|after:from', - ]); - - $report = $reports->with('orders') - ->mode($request->mode ?: 'monthly') - ->between( - Carbon::parse($request->from), - Carbon::parse($request->to) - )->customers(); - - return response()->json($report); - } - - public function orderAverages(Request $request, ReportManagerContract $reports) - { - $this->validate($request, [ - 'from' => 'required|date', - 'to' => 'required|date|after:from', - ]); - - $report = $reports->with('orders') - ->mode($request->mode ?: 'monthly') - ->between( - Carbon::parse($request->from), - Carbon::parse($request->to) - )->averages(); - - return response()->json($report); - } - - public function bestSellers(Request $request, ReportManagerContract $reports) - { - $this->validate($request, [ - 'from' => 'required|date', - 'to' => 'required|date|after:from', - ]); - - $report = $reports->with('orders') - ->mode($request->mode ?: 'monthly') - ->between( - Carbon::parse($request->from), - Carbon::parse($request->to) - )->bestSellers($request->limit); - - return response()->json($report); - } } From d143f4650e00c0617cd29a2fd2722285c4a59d2c Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 16 Mar 2021 08:58:54 +0000 Subject: [PATCH 125/152] Update CHANGELOG.md --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 709c2a18f..42f778362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,11 +40,17 @@ You can do this by running `php artisan candy:products:reindex` and `php artisan - Allow certain fields to be nullable on a customer address (`company_name`, `address_two`, `address_three`) - Fixed some issues on route creation - Fixed issue where shipping method relationships were not having their timestamps updated +- Fixes to some migrations +- Fixed an issue where the recycle bin item wasn't returned on the relationship +- Fixed and issue where the indexable event wasn't being triggered when publishing a resource +- Fixes to drafting and publishing of resources +- Fixed an issue where `path` wasn't updating when updating a route ### ⭐ Improvements - Slight optimisation for Elasticsearch and the fields it returns - Drafting and Publishing of a draft will now run in a transaction, you can also extend the drafting functionality in your plugins. +- SKU uses `trim` when being saved ### 🏗️ Additions @@ -52,6 +58,10 @@ You can do this by running `php artisan candy:products:reindex` and `php artisan - Added Stripe Payment Intents provider - Added a `RebuildTree` action and command for categories, so if your category tree is messed up you can run `candy:categories:rebuild` - Added `user/addresses` endpoint to get the current users saved addresses +- Added initial report exporting logic, this will now run and exporter in the background and email you when ready to download. +- Add some additional reports + - Average spending across customer groups + - Total spending across customer groups --- @@ -74,4 +84,4 @@ Add lockfile to .gitignore # 0.3.4 - [changed] Changed `type` to `search_type` to be more explicit. Also `search_type` is no longer a required field and will default to a product search -- [changed] Changed `includes` on search to `include` \ No newline at end of file +- [changed] Changed `includes` on search to `include` From 9247e27d4062a2cdef3cd2aa0b9d646f87ecd7f5 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Tue, 16 Mar 2021 08:59:25 +0000 Subject: [PATCH 126/152] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42f778362..f50fb97db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Update the composer package $ composer update @getcandy/candy-api ``` +```bash +$ php artisan migrate +``` + ### High Impact Changes #### Maintenance Migrations From 8319958eca38c181ced35799df7903298390c2d2 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 08:07:02 +0000 Subject: [PATCH 127/152] Move shipping method index to an action --- routes/api.client.php | 2 +- .../Shipping/Actions/FetchShippingMethods.php | 69 +++++++++++++++++++ .../Shipping/ShippingMethodController.php | 13 ---- 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/Core/Shipping/Actions/FetchShippingMethods.php diff --git a/routes/api.client.php b/routes/api.client.php index b9c28605b..fc4cf6180 100644 --- a/routes/api.client.php +++ b/routes/api.client.php @@ -129,7 +129,7 @@ /* * Shipping */ -$router->get('shipping', 'Shipping\ShippingMethodController@index'); +$router->get('shipping', '\GetCandy\Api\Core\Shipping\Actions\FetchShippingMethods'); $router->get('shipping/prices/estimate', 'Shipping\ShippingPriceController@estimate'); /* diff --git a/src/Core/Shipping/Actions/FetchShippingMethods.php b/src/Core/Shipping/Actions/FetchShippingMethods.php new file mode 100644 index 000000000..3e666efd4 --- /dev/null +++ b/src/Core/Shipping/Actions/FetchShippingMethods.php @@ -0,0 +1,69 @@ +paginate = $this->paginate === null ?: $this->paginate; + + return true; + } + + /** + * Get the validation rules that apply to the action. + * + * @return array + */ + public function rules() + { + return [ + 'per_page' => 'numeric|max:200', + 'paginate' => 'boolean', + ]; + } + + /** + * Execute the action and return a result. + * + * @return mixed + */ + public function handle() + { + $includes = $this->resolveEagerRelations(); + + $query = ShippingMethod::with($includes); + + if (! $this->paginate) { + return $query->get(); + } + + return $query->withCount( + $this->resolveRelationCounts() + )->paginate($this->per_page ?? 50); + } + + /** + * Returns the response from the action. + * + * @param \GetCandy\Api\Core\Routes\Models\Route|Illuminate\Pagination\LengthAwarePaginator $result + * @param \Illuminate\Http\Request $request + * + * @return \GetCandy\Api\Http\Resources\Shipping\ShippingMethodCollection + */ + public function response($result, $request) + { + return new ShippingMethodCollection($result); + } +} diff --git a/src/Http/Controllers/Shipping/ShippingMethodController.php b/src/Http/Controllers/Shipping/ShippingMethodController.php index d5e8c363a..5f52b0593 100644 --- a/src/Http/Controllers/Shipping/ShippingMethodController.php +++ b/src/Http/Controllers/Shipping/ShippingMethodController.php @@ -14,19 +14,6 @@ class ShippingMethodController extends BaseController { - /** - * Returns a listing of shipping methods. - * - * @param \Illuminate\Http\Request $request - * @return \GetCandy\Api\Http\Resources\Shipping\ShippingMethodCollection - */ - public function index(Request $request) - { - $methods = GetCandy::shippingMethods()->getPaginatedData($request->per_page, $request->current_page); - - return new ShippingMethodCollection($methods); - } - /** * Handles the request to show a shipping method based on it's hashed ID. * From 6983e476a154955fbcdb755b8f876fb274865d11 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 08:10:26 +0000 Subject: [PATCH 128/152] Add reward resources --- .../DiscountRewardProductCollection.php | 16 +++++++++++++ .../DiscountRewardProductResource.php | 24 +++++++++++++++++++ .../Discounts/DiscountRewardResource.php | 2 ++ 3 files changed, 42 insertions(+) create mode 100644 src/Http/Resources/Discounts/DiscountRewardProductCollection.php create mode 100644 src/Http/Resources/Discounts/DiscountRewardProductResource.php diff --git a/src/Http/Resources/Discounts/DiscountRewardProductCollection.php b/src/Http/Resources/Discounts/DiscountRewardProductCollection.php new file mode 100644 index 000000000..499880293 --- /dev/null +++ b/src/Http/Resources/Discounts/DiscountRewardProductCollection.php @@ -0,0 +1,16 @@ + $this->encoded_id, + 'quantity' => $this->quantity, + ]; + } + + public function includes() + { + return [ + 'product' => $this->include('product', ProductResource::class), + ]; + } +} diff --git a/src/Http/Resources/Discounts/DiscountRewardResource.php b/src/Http/Resources/Discounts/DiscountRewardResource.php index 7bc1a2329..0bf87435b 100644 --- a/src/Http/Resources/Discounts/DiscountRewardResource.php +++ b/src/Http/Resources/Discounts/DiscountRewardResource.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Http\Resources\Discounts; use GetCandy\Api\Http\Resources\AbstractResource; +use GetCandy\Api\Http\Resources\Products\ProductCollection; class DiscountRewardResource extends AbstractResource { @@ -18,6 +19,7 @@ public function payload() public function includes() { return [ + 'products' => new DiscountRewardProductCollection($this->whenLoaded('products')), ]; } } From 2a3c3afd53fa09051e1ffaa9d1ff17a76e573e5c Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 08:32:16 +0000 Subject: [PATCH 129/152] Check placed_at is null for active order --- src/Core/Baskets/Models/Basket.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Baskets/Models/Basket.php b/src/Core/Baskets/Models/Basket.php index f99a8c18f..b27580677 100644 --- a/src/Core/Baskets/Models/Basket.php +++ b/src/Core/Baskets/Models/Basket.php @@ -112,7 +112,7 @@ public function order() public function activeOrder() { - return $this->hasOne(Order::class, 'basket_id'); + return $this->hasOne(Order::class, 'basket_id')->whereNull('placed_at'); } public function placedOrder() From b0cd8f2b079f0944f162024ccd944f32cf4d222d Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 11:05:41 +0000 Subject: [PATCH 130/152] Return exchange rate --- src/Http/Resources/Currencies/CurrencyResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Resources/Currencies/CurrencyResource.php b/src/Http/Resources/Currencies/CurrencyResource.php index 2cc1ed78e..b91f457c8 100644 --- a/src/Http/Resources/Currencies/CurrencyResource.php +++ b/src/Http/Resources/Currencies/CurrencyResource.php @@ -16,6 +16,7 @@ public function payload() 'format' => $this->format, 'decimal_point' => $this->decimal_point, 'thousand_point' => $this->thousand_point, + 'exchange_rate' => $this->exchange_rate, 'default' => (bool) $this->default, ]; } From 92b51b33ededb92a6e372e0875e2a81eff201ebc Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Wed, 17 Mar 2021 11:09:26 +0000 Subject: [PATCH 131/152] Apply fixes from StyleCI (#362) Co-authored-by: Alec Ritson --- config/getcandy.php | 4 ++-- routes/api.php | 3 --- .../Actions/FetchCurrentCustomerGroups.php | 5 +--- src/Core/Products/Drafting/ProductDrafter.php | 24 +++++++++---------- .../Services/ProductVariantService.php | 7 +++--- .../Reports/Actions/DownloadReportExport.php | 9 ++++--- src/Core/Reports/Actions/ExportReport.php | 14 +++++------ .../Actions/GetCustomerGroupReport.php | 15 ++++++------ .../Actions/GetCustomerSpendingReport.php | 16 ++++++------- .../Actions/GetNewVsReturningReport.php | 12 +++++----- .../Actions/GetOrderAveragesReport.php | 15 ++++++------ .../Reports/Actions/GetOrderTotalsReport.php | 19 +++++++-------- .../Reports/Actions/GetProductBestSellers.php | 12 +++++----- src/Core/Reports/Actions/GetReportExports.php | 6 ++--- src/Core/Reports/Actions/GetSalesReport.php | 19 ++++++++------- src/Core/Reports/Mail/ReportExported.php | 1 - .../Resources/ReportExportCollection.php | 3 +-- .../Resources/ReportExportResource.php | 6 ++--- .../Shipping/Actions/FetchShippingMethods.php | 1 - src/Core/Users/Actions/FetchUsers.php | 2 -- .../Controllers/Orders/OrderController.php | 1 - .../Controllers/Reports/ReportController.php | 1 - .../Shipping/ShippingMethodController.php | 1 - .../DiscountRewardProductCollection.php | 1 - .../Discounts/DiscountRewardResource.php | 1 - 25 files changed, 90 insertions(+), 108 deletions(-) diff --git a/config/getcandy.php b/config/getcandy.php index b0865daa3..6b5818498 100644 --- a/config/getcandy.php +++ b/config/getcandy.php @@ -61,8 +61,8 @@ 'invoicing' => [ 'pdf' => [ 'pipelines' => [ - ] - ] + ], + ], ], 'statuses' => [ diff --git a/routes/api.php b/routes/api.php index 295e85dc4..158bb01ad 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,8 +1,5 @@ hasAnyRole(['admin']); - + if (($hubAccess && GetCandy::isHubRequest()) || (! GetCandy::isHubRequest() && ! $hubAccess) ) { diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 5c5b5f674..0fb6bb4ad 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -3,24 +3,24 @@ namespace GetCandy\Api\Core\Products\Drafting; use DB; -use Versioning; -use Illuminate\Database\Eloquent\Model; -use GetCandy\Api\Core\Drafting\BaseDrafter; use GetCandy\Api\Core\Drafting\Actions\DraftAssets; -use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; -use GetCandy\Api\Core\Drafting\Actions\DraftChannels; -use GetCandy\Api\Core\Drafting\Actions\PublishAssets; -use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; -use NeonDigital\Drafting\Interfaces\DrafterInterface; use GetCandy\Api\Core\Drafting\Actions\DraftCategories; -use GetCandy\Api\Core\Drafting\Actions\PublishChannels; -use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use GetCandy\Api\Core\Drafting\Actions\DraftChannels; use GetCandy\Api\Core\Drafting\Actions\DraftCustomerGroups; +use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; use GetCandy\Api\Core\Drafting\Actions\DraftProductVariants; +use GetCandy\Api\Core\Drafting\Actions\DraftRoutes; +use GetCandy\Api\Core\Drafting\Actions\PublishAssets; +use GetCandy\Api\Core\Drafting\Actions\PublishChannels; use GetCandy\Api\Core\Drafting\Actions\PublishCustomerGroups; -use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; -use GetCandy\Api\Core\Drafting\Actions\DraftProductAssociations; use GetCandy\Api\Core\Drafting\Actions\PublishProductAssociations; +use GetCandy\Api\Core\Drafting\Actions\PublishProductVariants; +use GetCandy\Api\Core\Drafting\Actions\PublishRoutes; +use GetCandy\Api\Core\Drafting\BaseDrafter; +use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use Illuminate\Database\Eloquent\Model; +use NeonDigital\Drafting\Interfaces\DrafterInterface; +use Versioning; class ProductDrafter extends BaseDrafter implements DrafterInterface { diff --git a/src/Core/Products/Services/ProductVariantService.php b/src/Core/Products/Services/ProductVariantService.php index b4141223f..6864f304c 100644 --- a/src/Core/Products/Services/ProductVariantService.php +++ b/src/Core/Products/Services/ProductVariantService.php @@ -4,12 +4,12 @@ use GetCandy; use GetCandy\Api\Core\Assets\Models\Asset; -use GetCandy\Api\Core\Scaffold\BaseService; -use GetCandy\Api\Core\Foundation\Actions\DecodeId; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Foundation\Actions\DecodeId; +use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; use GetCandy\Api\Core\Products\Models\ProductVariant; +use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; -use GetCandy\Api\Core\Products\Factories\ProductVariantFactory; class ProductVariantService extends BaseService { @@ -181,7 +181,6 @@ public function update($hashedId, array $data) // $variant->image()->associate($asset); // } - if (! empty($data['tax_id'])) { $variant->tax()->associate( GetCandy::taxes()->getByHashedId($data['tax_id']) diff --git a/src/Core/Reports/Actions/DownloadReportExport.php b/src/Core/Reports/Actions/DownloadReportExport.php index cde4af955..9e2b36013 100644 --- a/src/Core/Reports/Actions/DownloadReportExport.php +++ b/src/Core/Reports/Actions/DownloadReportExport.php @@ -2,11 +2,9 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\URL; -use Illuminate\Support\Facades\Storage; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Reports\Models\ReportExport; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\Storage; class DownloadReportExport extends AbstractAction { @@ -14,6 +12,7 @@ public function authorize() { return $this->request->hasValidSignature(); } + public function rules() { return []; @@ -30,4 +29,4 @@ public function handle($id) abort(404); } } -} \ No newline at end of file +} diff --git a/src/Core/Reports/Actions/ExportReport.php b/src/Core/Reports/Actions/ExportReport.php index ea65558cd..12a94c753 100644 --- a/src/Core/Reports/Actions/ExportReport.php +++ b/src/Core/Reports/Actions/ExportReport.php @@ -2,11 +2,11 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Facades\URL; +use GetCandy\Api\Core\Reports\Mail\ReportExported; +use GetCandy\Api\Core\Scaffold\AbstractAction; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Storage; -use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Reports\Mail\ReportExported; +use Illuminate\Support\Facades\URL; class ExportReport extends AbstractAction { @@ -37,8 +37,8 @@ public function handle() $result = $result['data'] ?? $result; // Create our export file... - $filename = $report->getExportFilename() . '.csv'; - $location = 'reporting/exports/' . now()->format('Y/m/d'); + $filename = $report->getExportFilename().'.csv'; + $location = 'reporting/exports/'.now()->format('Y/m/d'); Storage::put("{$location}/{$filename}", null); @@ -46,7 +46,7 @@ public function handle() fputcsv($fp, $report->getCsvHeaders()); - foreach ( $result as $row ) { + foreach ($result as $row) { $val = $report->getCsvRow($row); fputcsv($fp, $val); } @@ -65,4 +65,4 @@ public function handle() 'completed_at' => now(), ]); } -} \ No newline at end of file +} diff --git a/src/Core/Reports/Actions/GetCustomerGroupReport.php b/src/Core/Reports/Actions/GetCustomerGroupReport.php index 31b7855c9..4137b505c 100644 --- a/src/Core/Reports/Actions/GetCustomerGroupReport.php +++ b/src/Core/Reports/Actions/GetCustomerGroupReport.php @@ -3,13 +3,12 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use Illuminate\Support\Facades\DB; +use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; use GetCandy\Api\Core\Orders\Models\Order; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; -use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetCustomerGroupReport extends AbstractAction { @@ -33,7 +32,7 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date', - 'paginate' => 'nullable' + 'paginate' => 'nullable', ]; } @@ -44,19 +43,20 @@ public function getCsvHeaders() foreach ($period as $date) { $headers[] = $date->format('F Y'); } + return $headers; } public function getExportFilename() { - return 'customer-group-report' . $this->from . '-' . $this->to; + return 'customer-group-report'.$this->from.'-'.$this->to; } public function getCsvRow($row) { $data = [$row['label']]; - foreach($row['data'] as $item) { + foreach ($row['data'] as $item) { $data[] = $item->sub_total / 100; } @@ -82,6 +82,7 @@ public function handle() 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Actions/GetCustomerSpendingReport.php b/src/Core/Reports/Actions/GetCustomerSpendingReport.php index 85a4fa1bf..5d9b16ff4 100644 --- a/src/Core/Reports/Actions/GetCustomerSpendingReport.php +++ b/src/Core/Reports/Actions/GetCustomerSpendingReport.php @@ -3,12 +3,11 @@ namespace GetCandy\Api\Core\Reports\Actions; use GetCandy; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\DB; use GetCandy\Api\Core\Orders\Models\Order; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Reports\Models\ReportExport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetCustomerSpendingReport extends AbstractAction { @@ -37,7 +36,7 @@ public function rules() ]; } - public function getCsvHeaders () + public function getCsvHeaders() { return [ 'has_account', @@ -49,16 +48,16 @@ public function getCsvHeaders () public function getExportFilename() { - return 'customer-spending_' . $this->from . '-' . $this->to; + return 'customer-spending_'.$this->from.'-'.$this->to; } public function getCsvRow($row) { return [ - !!$row->user_id, + (bool) $row->user_id, "{$row->firstname} {$row->lastname}", $row->email, - $row->sub_total / 100 + $row->sub_total / 100, ]; } @@ -81,6 +80,7 @@ public function handle() 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } @@ -108,7 +108,7 @@ public function handle() $result = $result->paginate(50); $items = $result->getCollection()->map(function ($row) { $userModel = GetCandy::getUserModel(); - + return array_merge($row->toArray(), [ 'user_id' => $row->user_id ? (new $userModel)->encode($row->user_id) : null, ]); diff --git a/src/Core/Reports/Actions/GetNewVsReturningReport.php b/src/Core/Reports/Actions/GetNewVsReturningReport.php index d5bd993db..f5458aa98 100644 --- a/src/Core/Reports/Actions/GetNewVsReturningReport.php +++ b/src/Core/Reports/Actions/GetNewVsReturningReport.php @@ -2,12 +2,11 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Carbon; -use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; -use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Carbon; class GetNewVsReturningReport extends AbstractAction { @@ -32,7 +31,7 @@ public function rules() 'from' => 'nullable|date', 'to' => 'nullable|date', 'paginate' => 'nullable', - 'mode' => 'nullable' + 'mode' => 'nullable', ]; } @@ -48,7 +47,7 @@ public function getCsvHeaders() public function getExportFilename() { - return 'new_vs_returning_customers_' . $this->from . '-' . $this->to; + return 'new_vs_returning_customers_'.$this->from.'-'.$this->to; } public function getCsvRow($row) @@ -80,6 +79,7 @@ public function handle(ReportManagerContract $reports) 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Actions/GetOrderAveragesReport.php b/src/Core/Reports/Actions/GetOrderAveragesReport.php index ca83e2124..c78de02e6 100644 --- a/src/Core/Reports/Actions/GetOrderAveragesReport.php +++ b/src/Core/Reports/Actions/GetOrderAveragesReport.php @@ -3,12 +3,11 @@ namespace GetCandy\Api\Core\Reports\Actions; use Carbon\CarbonPeriod; -use Illuminate\Support\Facades\DB; -use GetCandy\Api\Core\Scaffold\AbstractAction; -use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; +use GetCandy\Api\Core\Reports\Models\ReportExport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetOrderAveragesReport extends AbstractAction { @@ -32,7 +31,7 @@ public function rules() return [ 'from' => 'nullable|date', 'to' => 'nullable|date|after:from', - 'export' => 'nullable|boolean' + 'export' => 'nullable|boolean', ]; } @@ -43,19 +42,20 @@ public function getCsvHeaders() foreach ($period as $date) { $headers[] = $date->format('F Y'); } + return $headers; } public function getExportFilename() { - return 'order-averages_' . $this->from . '-' . $this->to; + return 'order-averages_'.$this->from.'-'.$this->to; } public function getCsvRow($row) { $data = [$row['label']]; - foreach($row['data'] as $item) { + foreach ($row['data'] as $item) { $data[] = $item['sub_total'] / 100; } @@ -81,6 +81,7 @@ public function handle() 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Actions/GetOrderTotalsReport.php b/src/Core/Reports/Actions/GetOrderTotalsReport.php index 1f421fb1e..c1b43fdf8 100644 --- a/src/Core/Reports/Actions/GetOrderTotalsReport.php +++ b/src/Core/Reports/Actions/GetOrderTotalsReport.php @@ -2,12 +2,11 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Carbon; -use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; -use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Carbon; class GetOrderTotalsReport extends AbstractAction { @@ -32,7 +31,7 @@ public function rules() 'from' => 'nullable|date', 'to' => 'nullable|date', 'paginate' => 'nullable', - 'mode' => 'nullable' + 'mode' => 'nullable', ]; } @@ -41,13 +40,13 @@ public function getCsvHeaders() return [ 'Month', 'Current Period', - 'Previous Period' + 'Previous Period', ]; } public function getExportFilename() { - return 'order-totals_' . $this->from . '-' . $this->to; + return 'order-totals_'.$this->from.'-'.$this->to; } public function getExportData($args) @@ -59,8 +58,7 @@ public function getExportData($args) $data = []; - foreach ($currentPeriod as $index => $totals) { - + foreach ($currentPeriod as $index => $totals) { $previous = collect($previousPeriod)->first(function ($t) use ($totals) { return $t->month === $totals->month && $t->year == $totals->year - 1; }); @@ -68,7 +66,7 @@ public function getExportData($args) $data[] = [ 'month' => "{$totals->month} {$totals->year}", 'sub_total' => $totals->sub_total / 100, - 'previous_sub_total' => ($previous->sub_total ?? 0) / 100 + 'previous_sub_total' => ($previous->sub_total ?? 0) / 100, ]; } @@ -103,6 +101,7 @@ public function handle(ReportManagerContract $reports) 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Actions/GetProductBestSellers.php b/src/Core/Reports/Actions/GetProductBestSellers.php index 60dfc87db..0f34903b3 100644 --- a/src/Core/Reports/Actions/GetProductBestSellers.php +++ b/src/Core/Reports/Actions/GetProductBestSellers.php @@ -2,11 +2,10 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Facades\DB; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Facades\DB; class GetProductBestSellers extends AbstractAction { @@ -36,7 +35,7 @@ public function rules() ]; } - public function getCsvHeaders () + public function getCsvHeaders() { return [ 'product', @@ -48,7 +47,7 @@ public function getCsvHeaders () public function getExportFilename() { - return 'product-best-sellers_' . $this->from . '_' . $this->to; + return 'product-best-sellers_'.$this->from.'_'.$this->to; } public function getCsvRow($row) @@ -57,7 +56,7 @@ public function getCsvRow($row) $row->description, $row->sku, $row->quantity, - $row->sub_total / 100 + $row->sub_total / 100, ]; } @@ -80,6 +79,7 @@ public function handle() 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Actions/GetReportExports.php b/src/Core/Reports/Actions/GetReportExports.php index 7e5c086e6..b05e66e0a 100644 --- a/src/Core/Reports/Actions/GetReportExports.php +++ b/src/Core/Reports/Actions/GetReportExports.php @@ -2,11 +2,9 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\Storage; -use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Reports\Models\ReportExport; use GetCandy\Api\Core\Reports\Resources\ReportExportCollection; +use GetCandy\Api\Core\Scaffold\AbstractAction; class GetReportExports extends AbstractAction { @@ -24,4 +22,4 @@ public function response($result, $request) { return new ReportExportCollection($result); } -} \ No newline at end of file +} diff --git a/src/Core/Reports/Actions/GetSalesReport.php b/src/Core/Reports/Actions/GetSalesReport.php index 5436d64db..814c2b372 100644 --- a/src/Core/Reports/Actions/GetSalesReport.php +++ b/src/Core/Reports/Actions/GetSalesReport.php @@ -2,12 +2,11 @@ namespace GetCandy\Api\Core\Reports\Actions; -use Illuminate\Support\Carbon; -use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; use GetCandy\Api\Core\Reports\Models\ReportExport; -use GetCandy\Api\Core\Reports\Actions\ExportReport; use GetCandy\Api\Core\Reports\Resources\ReportExportResource; -use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; +use GetCandy\Api\Core\Scaffold\AbstractAction; +use Illuminate\Support\Carbon; class GetSalesReport extends AbstractAction { @@ -32,7 +31,7 @@ public function rules() 'from' => 'nullable|date', 'to' => 'nullable|date', 'paginate' => 'nullable', - 'mode' => 'nullable' + 'mode' => 'nullable', ]; } @@ -41,13 +40,13 @@ public function getCsvHeaders() return [ 'Month', 'Orders', - 'Revenue' + 'Revenue', ]; } public function getExportFilename() { - return 'order-sales_' . $this->from . '-' . $this->to; + return 'order-sales_'.$this->from.'-'.$this->to; } public function getExportData($args) @@ -59,13 +58,14 @@ public function getExportData($args) $data = []; - foreach ($orders as $index => $orderValue) { + foreach ($orders as $index => $orderValue) { $data[] = [ 'month' => $result['labels'][$index], 'orders' => $orderValue, - 'revenue' => $revenue[$index] / 100 + 'revenue' => $revenue[$index] / 100, ]; } + return $data; } @@ -97,6 +97,7 @@ public function handle(ReportManagerContract $reports) 'export' => $export, 'args' => $this->validated(), ]); + return new ReportExportResource($export); } diff --git a/src/Core/Reports/Mail/ReportExported.php b/src/Core/Reports/Mail/ReportExported.php index a660886fc..a7a5678ba 100644 --- a/src/Core/Reports/Mail/ReportExported.php +++ b/src/Core/Reports/Mail/ReportExported.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Core\Reports\Mail; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; diff --git a/src/Core/Reports/Resources/ReportExportCollection.php b/src/Core/Reports/Resources/ReportExportCollection.php index 6878f78fb..0805a06d9 100644 --- a/src/Core/Reports/Resources/ReportExportCollection.php +++ b/src/Core/Reports/Resources/ReportExportCollection.php @@ -3,9 +3,8 @@ namespace GetCandy\Api\Core\Reports\Resources; use Illuminate\Http\Resources\Json\ResourceCollection; -use GetCandy\Api\Core\Reports\Resources\ReportExportResource; class ReportExportCollection extends ResourceCollection { public $collects = ReportExportResource::class; -} \ No newline at end of file +} diff --git a/src/Core/Reports/Resources/ReportExportResource.php b/src/Core/Reports/Resources/ReportExportResource.php index cf3ec3130..e08a8f110 100644 --- a/src/Core/Reports/Resources/ReportExportResource.php +++ b/src/Core/Reports/Resources/ReportExportResource.php @@ -2,8 +2,8 @@ namespace GetCandy\Api\Core\Reports\Resources; -use Illuminate\Support\Facades\Storage; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Support\Facades\Storage; class ReportExportResource extends JsonResource { @@ -21,9 +21,9 @@ public function toArray($request) try { $content = base64_encode(Storage::get("{$this->path}/{$this->filename}")); } catch (\Exception $e) { - } } + return [ 'id' => $this->encodedId(), 'filename' => $this->filename, @@ -34,4 +34,4 @@ public function toArray($request) 'completed_at' => $this->completed_at, ]; } -} \ No newline at end of file +} diff --git a/src/Core/Shipping/Actions/FetchShippingMethods.php b/src/Core/Shipping/Actions/FetchShippingMethods.php index 3e666efd4..62fdb0cad 100644 --- a/src/Core/Shipping/Actions/FetchShippingMethods.php +++ b/src/Core/Shipping/Actions/FetchShippingMethods.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Core\Shipping\Actions; -use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Shipping\Models\ShippingMethod; use GetCandy\Api\Http\Resources\Shipping\ShippingMethodCollection; diff --git a/src/Core/Users/Actions/FetchUsers.php b/src/Core/Users/Actions/FetchUsers.php index 50d80f5a6..6d6579dc2 100644 --- a/src/Core/Users/Actions/FetchUsers.php +++ b/src/Core/Users/Actions/FetchUsers.php @@ -67,7 +67,6 @@ public function handle() $query = $query->whereIn('id', $realIds); } - if ($sorting) { $query->orderBy($sorting[0], $sorting[1]); } @@ -76,7 +75,6 @@ public function handle() return $query->get(); } - return $query->paginate($this->per_page ?? 50); } diff --git a/src/Http/Controllers/Orders/OrderController.php b/src/Http/Controllers/Orders/OrderController.php index 754b6bac4..dc13ea60b 100644 --- a/src/Http/Controllers/Orders/OrderController.php +++ b/src/Http/Controllers/Orders/OrderController.php @@ -25,7 +25,6 @@ use GetCandy\Api\Http\Requests\Orders\Shipping\AddShippingRequest; use GetCandy\Api\Http\Requests\Orders\StoreAddressRequest; use GetCandy\Api\Http\Requests\Orders\UpdateRequest; -use GetCandy\Api\Http\Resources\Files\PdfResource; use GetCandy\Api\Http\Resources\Orders\OrderCollection; use GetCandy\Api\Http\Resources\Orders\OrderExportResource; use GetCandy\Api\Http\Resources\Orders\OrderResource; diff --git a/src/Http/Controllers/Reports/ReportController.php b/src/Http/Controllers/Reports/ReportController.php index e14e48c50..1224fd30b 100644 --- a/src/Http/Controllers/Reports/ReportController.php +++ b/src/Http/Controllers/Reports/ReportController.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Http\Controllers\Reports; -use Carbon\Carbon; use GetCandy\Api\Core\Reports\Contracts\ReportManagerContract; use GetCandy\Api\Http\Controllers\BaseController; use Illuminate\Http\Request; diff --git a/src/Http/Controllers/Shipping/ShippingMethodController.php b/src/Http/Controllers/Shipping/ShippingMethodController.php index 5f52b0593..88dc1b321 100644 --- a/src/Http/Controllers/Shipping/ShippingMethodController.php +++ b/src/Http/Controllers/Shipping/ShippingMethodController.php @@ -6,7 +6,6 @@ use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Shipping\CreateRequest; use GetCandy\Api\Http\Requests\Shipping\UpdateRequest; -use GetCandy\Api\Http\Resources\Shipping\ShippingMethodCollection; use GetCandy\Api\Http\Resources\Shipping\ShippingMethodResource; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\Request; diff --git a/src/Http/Resources/Discounts/DiscountRewardProductCollection.php b/src/Http/Resources/Discounts/DiscountRewardProductCollection.php index 499880293..090ae09c8 100644 --- a/src/Http/Resources/Discounts/DiscountRewardProductCollection.php +++ b/src/Http/Resources/Discounts/DiscountRewardProductCollection.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Http\Resources\Discounts; use GetCandy\Api\Http\Resources\AbstractCollection; -use GetCandy\Api\Http\Resources\Discounts\DiscountRewardProductResource; class DiscountRewardProductCollection extends AbstractCollection { diff --git a/src/Http/Resources/Discounts/DiscountRewardResource.php b/src/Http/Resources/Discounts/DiscountRewardResource.php index 0bf87435b..18b0a0b7f 100644 --- a/src/Http/Resources/Discounts/DiscountRewardResource.php +++ b/src/Http/Resources/Discounts/DiscountRewardResource.php @@ -3,7 +3,6 @@ namespace GetCandy\Api\Http\Resources\Discounts; use GetCandy\Api\Http\Resources\AbstractResource; -use GetCandy\Api\Http\Resources\Products\ProductCollection; class DiscountRewardResource extends AbstractResource { From 7e6a358244cd80a2ab82ad59d77b6e6831049e43 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 11:15:47 +0000 Subject: [PATCH 132/152] Handle multiple emails if billing/shipping differ --- src/Core/Orders/Jobs/OrderNotification.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Core/Orders/Jobs/OrderNotification.php b/src/Core/Orders/Jobs/OrderNotification.php index 44f4bfde8..0577376ed 100644 --- a/src/Core/Orders/Jobs/OrderNotification.php +++ b/src/Core/Orders/Jobs/OrderNotification.php @@ -40,15 +40,21 @@ public function handle() $contactEmail = ($this->order->user ? $this->order->user->email : null); } + $emails = [$contactEmail]; + + if ($this->order->shipping_email != $this->order->billing_email) { + $emails[$this->order->shipping_email]; + } + $mailer = new $mailer($this->order); foreach ($this->content as $key => $value) { $mailer->with($key, $value); } if ($mailQueue = config('getcandy.mail.queue', null)) { - Mail::to($contactEmail)->queue($mailer->onQueue($mailQueue)); + Mail::to($emails)->queue($mailer->onQueue($mailQueue)); } else { - Mail::to($contactEmail)->send($mailer); + Mail::to($emails)->send($mailer); } } } From 7f67fc924234c28444ec08611be072b16954a770 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 17 Mar 2021 12:11:27 +0000 Subject: [PATCH 133/152] Fix array access --- src/Core/Orders/Jobs/OrderNotification.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Orders/Jobs/OrderNotification.php b/src/Core/Orders/Jobs/OrderNotification.php index 0577376ed..52c1cd216 100644 --- a/src/Core/Orders/Jobs/OrderNotification.php +++ b/src/Core/Orders/Jobs/OrderNotification.php @@ -43,7 +43,7 @@ public function handle() $emails = [$contactEmail]; if ($this->order->shipping_email != $this->order->billing_email) { - $emails[$this->order->shipping_email]; + $emails[] = $this->order->shipping_email; } $mailer = new $mailer($this->order); From c831d92f58362aa1c87877743d928033b4a5d8ff Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Thu, 18 Mar 2021 09:47:34 +0000 Subject: [PATCH 134/152] Language Refactoring (#363) * Refactor langauges * Update changelog * Fix indexing * Apply fixes from StyleCI (#364) Co-authored-by: Alec Ritson * Adjust regex pattern * Use proper values on factory * rename $locale * Update tests * Remove unit group * Adjust language change wording Co-authored-by: Alec Ritson --- CHANGELOG.md | 3 +- composer.json | 1 + database/factories/LanguageFactory.php | 8 +-- ..._03_17_121602_refactor_languages_table.php | 29 ++++++++++ database/seeds/LanguageTableSeeder.php | 6 +- openapi/languages/models/Language.yaml | 6 +- openapi/languages/paths/languages.id.yaml | 3 +- src/Core/Languages/Actions/CreateLanguage.php | 9 ++- src/Core/Languages/Models/Language.php | 8 ++- .../Languages/Resources/LanguageResource.php | 3 +- .../Elasticsearch/Actions/FetchIndex.php | 1 + .../Elasticsearch/Actions/IndexProducts.php | 6 +- src/Core/Search/Providers/Elastic/Indexer.php | 6 +- .../Providers/Elastic/InteractsWithIndex.php | 2 +- src/Core/Traits/HasAttributes.php | 4 +- src/Http/Middleware/SetLocaleMiddleware.php | 40 ++++++-------- .../Requests/Attributes/CreateRequest.php | 2 +- .../Requests/Attributes/UpdateRequest.php | 2 +- src/Http/Requests/Products/UpdateRequest.php | 2 +- src/Http/Requests/Tags/CreateRequest.php | 2 +- src/Http/Requests/Tags/UpdateRequest.php | 2 +- ...aleValidator.php => LanguageValidator.php} | 8 +-- src/Installer/Runners/LanguageRunner.php | 3 +- src/Installer/Runners/UserRunner.php | 4 +- src/Providers/ApiServiceProvider.php | 2 +- .../Actions/Languages/CreateLanguageTest.php | 55 ++++++++++++++++++- .../Installer/Runners/LanguageRunnerTest.php | 3 +- .../FetchEnabledLanguageByCodeTest.php | 12 ++-- .../Languages/Actions/FetchLanguagesTest.php | 14 ++--- 29 files changed, 160 insertions(+), 86 deletions(-) create mode 100644 database/migrations/2021_03_17_121602_refactor_languages_table.php rename src/Http/Validators/{LocaleValidator.php => LanguageValidator.php} (81%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f50fb97db..fa34bae44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,8 @@ You can do this by running `php artisan candy:products:reindex` and `php artisan - Slight optimisation for Elasticsearch and the fields it returns - Drafting and Publishing of a draft will now run in a transaction, you can also extend the drafting functionality in your plugins. - SKU uses `trim` when being saved - +- Languages have been refactored and simplified so now we only rely on `code`. The `lang` column has been replaced by `code` and the `iso` column has been removed. +- When detecting the language to use for API responses, we now parse the `accept-language` header properly. ### 🏗️ Additions - Added endpoint to get a payment provider via it's given ID diff --git a/composer.json b/composer.json index 6e870a516..d86fe8097 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,7 @@ "doctrine/dbal": "2.9.2", "ruflin/elastica": "^7.0", "spatie/laravel-permission": "^3.17", + "supportpal/accept-language-parser": "^0.1", "vinkla/hashids": "^9.0", "paypal/rest-api-sdk-php": "*", "laravel/helpers": "1.3.0", diff --git a/database/factories/LanguageFactory.php b/database/factories/LanguageFactory.php index 69341acdc..7b3b0f4f0 100644 --- a/database/factories/LanguageFactory.php +++ b/database/factories/LanguageFactory.php @@ -2,7 +2,6 @@ use Faker\Generator as Faker; use GetCandy\Api\Core\Languages\Models\Language; -use Illuminate\Support\Str; /* |-------------------------------------------------------------------------- @@ -16,12 +15,9 @@ */ $factory->define(Language::class, function (Faker $faker) { - $name = $faker->unique()->company; - return [ - 'name' => $name, - 'lang' => ucfirst($name), - 'iso' => Str::slug($name), + 'name' => $faker->unique()->country, + 'code' => $faker->languageCode, 'default' => $faker->boolean, 'enabled' => $faker->boolean, ]; diff --git a/database/migrations/2021_03_17_121602_refactor_languages_table.php b/database/migrations/2021_03_17_121602_refactor_languages_table.php new file mode 100644 index 000000000..639cdfe93 --- /dev/null +++ b/database/migrations/2021_03_17_121602_refactor_languages_table.php @@ -0,0 +1,29 @@ +dropColumn('iso'); + }); + Schema::table('languages', function (Blueprint $table) { + $table->renameColumn('lang', 'code'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + // One way migration + } +} diff --git a/database/seeds/LanguageTableSeeder.php b/database/seeds/LanguageTableSeeder.php index 4300f6ae4..985afea98 100644 --- a/database/seeds/LanguageTableSeeder.php +++ b/database/seeds/LanguageTableSeeder.php @@ -15,15 +15,13 @@ class LanguageTableSeeder extends Seeder public function run() { Language::forceCreate([ - 'lang' => 'en', - 'iso' => 'gb', + 'code' => 'en', 'name' => 'English', 'default' => true, ]); Language::forceCreate([ - 'lang' => 'fr', - 'iso' => 'fr', + 'code' => 'fr', 'name' => 'Français', 'default' => false, ]); diff --git a/openapi/languages/models/Language.yaml b/openapi/languages/models/Language.yaml index 47bb2e479..3cd9354ab 100644 --- a/openapi/languages/models/Language.yaml +++ b/openapi/languages/models/Language.yaml @@ -4,12 +4,10 @@ properties: id: type: string example: y3g6v91o - iso: - type: string - example: gb - lang: + code: type: string example: en + pattern: '^[a-zA-Z0-9-]*$' name: type: string example: English diff --git a/openapi/languages/paths/languages.id.yaml b/openapi/languages/paths/languages.id.yaml index 7f3ad3c2c..bb7f5f830 100644 --- a/openapi/languages/paths/languages.id.yaml +++ b/openapi/languages/paths/languages.id.yaml @@ -40,8 +40,7 @@ put: data: id: 6z8m9gmj name: English - lang: en - iso: gb + code: en default: true enabled: true current: false diff --git a/src/Core/Languages/Actions/CreateLanguage.php b/src/Core/Languages/Actions/CreateLanguage.php index f10eb5d39..5fe842fbc 100644 --- a/src/Core/Languages/Actions/CreateLanguage.php +++ b/src/Core/Languages/Actions/CreateLanguage.php @@ -26,8 +26,13 @@ public function authorize() public function rules() { return [ - 'lang' => 'required|string', - 'iso' => 'required|string|unique:languages,iso', + 'code' => [ + 'required', + 'string', + 'unique:languages,code', + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language + 'regex:/^[a-zA-Z0-9-]*$/', + ], 'name' => 'required|string', 'default' => 'boolean', 'enabled' => 'boolean', diff --git a/src/Core/Languages/Models/Language.php b/src/Core/Languages/Models/Language.php index d71b0be32..311c783f8 100644 --- a/src/Core/Languages/Models/Language.php +++ b/src/Core/Languages/Models/Language.php @@ -19,7 +19,7 @@ class Language extends BaseModel * @var array */ protected $fillable = [ - 'lang', 'iso', 'name', 'default', 'enabled', + 'code', 'name', 'default', 'enabled', ]; public function scopeDefault($query) @@ -48,6 +48,10 @@ public function scopeLang($query, $lang) public function scopeCode($query, $code) { - return $query->whereIso($code); + if (! is_array($code)) { + $code = [$code]; + } + + return $query->whereIn('code', $code); } } diff --git a/src/Core/Languages/Resources/LanguageResource.php b/src/Core/Languages/Resources/LanguageResource.php index ece7479ce..2d6bd89af 100644 --- a/src/Core/Languages/Resources/LanguageResource.php +++ b/src/Core/Languages/Resources/LanguageResource.php @@ -11,8 +11,7 @@ public function payload() return [ 'id' => $this->encoded_id, 'name' => $this->name, - 'lang' => $this->lang, - 'iso' => $this->iso, + 'code' => $this->code, 'default' => (bool) $this->default, 'enabled' => (bool) $this->enabled, 'current' => (bool) $this->current, diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php index d0bb843c8..24bfa1618 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/FetchIndex.php @@ -43,6 +43,7 @@ public function handle() $indexes = []; $prefix = config('getcandy.search.index_prefix', 'getcandy'); foreach ($this->languages as $language) { + $language = strtolower($language); $index = $client->getIndex( "{$prefix}_{$this->type}_{$language}_{$this->uuid}" ); diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php index b9b05f788..096fc7546 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexProducts.php @@ -53,7 +53,7 @@ public function handle() $languages = FetchLanguages::run([ 'paginate' => false, - ])->pluck('lang'); + ])->pluck('code'); $customerGroups = FetchCustomerGroups::run([ 'paginate' => false, @@ -92,6 +92,10 @@ public function handle() return new Document($document->getId(), $document->getData()); }); + if (! $docs->count()) { + continue; + } + $bulk = new Bulk($client); $bulk->setIndex($index->actual); $bulk->addDocuments($docs->toArray()); diff --git a/src/Core/Search/Providers/Elastic/Indexer.php b/src/Core/Search/Providers/Elastic/Indexer.php index 808911ed1..16d35139e 100644 --- a/src/Core/Search/Providers/Elastic/Indexer.php +++ b/src/Core/Search/Providers/Elastic/Indexer.php @@ -59,7 +59,7 @@ public function reindex($model, $batchSize = 1000) $aliases = []; foreach ($languages as $language) { - $alias = $index.'_'.$language->lang; + $alias = $index.'_'.$language->code; $newIndex = $alias."_{$suffix}"; $this->createIndex($alias."_{$suffix}", $type); $aliases[$alias] = $alias."_{$suffix}"; @@ -195,7 +195,7 @@ public function indexObject(Model $model) $indexables = $type->getIndexDocument($model); foreach ($langs as $lang) { - $alias = $index.'_'.$lang->lang; + $alias = $index.'_'.$lang->code; $indices = $status->getIndicesWithAlias($alias); @@ -235,7 +235,7 @@ public function indexObjects($models) $indexables = $type->getIndexDocument($model); foreach ($langs as $lang) { - $alias = $index.'_'.$lang->lang; + $alias = $index.'_'.$lang->code; $indices = $status->getIndicesWithAlias($alias); diff --git a/src/Core/Search/Providers/Elastic/InteractsWithIndex.php b/src/Core/Search/Providers/Elastic/InteractsWithIndex.php index 93ec0997f..891ace1d7 100644 --- a/src/Core/Search/Providers/Elastic/InteractsWithIndex.php +++ b/src/Core/Search/Providers/Elastic/InteractsWithIndex.php @@ -53,7 +53,7 @@ protected function getDefaultIndex() { $defaultLang = FetchDefaultLanguage::run(); - return $this->getBaseIndexName()."_{$defaultLang->lang}"; + return $this->getBaseIndexName()."_{$defaultLang->code}"; } /** diff --git a/src/Core/Traits/HasAttributes.php b/src/Core/Traits/HasAttributes.php index f9eaeb853..b2e13c9e4 100644 --- a/src/Core/Traits/HasAttributes.php +++ b/src/Core/Traits/HasAttributes.php @@ -59,7 +59,7 @@ public function attribute($handle, $channel = null, $locale = null) return; } elseif (is_null($this->attribute_data[$handle][$channel][$userLocale])) { $channel = 'webstore'; - $locale = $locale->lang; + $locale = $locale->code; } } @@ -126,7 +126,7 @@ public function getDataMapping() 'paginate' => false, ]); foreach ($languages as $lang) { - $languagesArray[$lang->lang] = null; + $languagesArray[$lang->code] = null; } // Get our channels $channels = FetchChannels::run([ diff --git a/src/Http/Middleware/SetLocaleMiddleware.php b/src/Http/Middleware/SetLocaleMiddleware.php index 08a612145..44664713b 100644 --- a/src/Http/Middleware/SetLocaleMiddleware.php +++ b/src/Http/Middleware/SetLocaleMiddleware.php @@ -5,7 +5,7 @@ use Closure; use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; use GetCandy\Api\Core\Languages\Actions\FetchEnabledLanguageByCode; -use Locale; +use SupportPal\AcceptLanguageParser\Parser; class SetLocaleMiddleware { @@ -18,29 +18,25 @@ class SetLocaleMiddleware */ public function handle($request, Closure $next) { - $locale = $request->header('accept-language'); - - $defaultLanguage = FetchDefaultLanguage::run()->lang; - - if (! $locale) { - $locale = $defaultLanguage; - } else { - if (extension_loaded('intl')) { - $languages = explode(',', Locale::getPrimaryLanguage($locale)); - } else { - $languages = explode(',', $locale); - } - $requestedLocale = FetchEnabledLanguageByCode::run([ - 'code' => $languages[0] ?? $languages, - ]); - if (! $requestedLocale) { - $locale = $defaultLanguage; - } else { - $locale = $requestedLocale->lang; - } + $defaultLanguage = FetchDefaultLanguage::run(); + $parser = new Parser($request->header('accept-language')); + + $language = collect($parser->parse())->first(); + + $code = $defaultLanguage->code; + if ($language) { + $code = $language->code(); + } + + $languageModel = FetchEnabledLanguageByCode::run([ + 'code' => $code, + ]); + + if (! $languageModel) { + $code = $defaultLanguage->code; } - app()->setLocale($locale); + app()->setLocale($code); return $next($request); } diff --git a/src/Http/Requests/Attributes/CreateRequest.php b/src/Http/Requests/Attributes/CreateRequest.php index 49dc3bab7..7e4fdad65 100644 --- a/src/Http/Requests/Attributes/CreateRequest.php +++ b/src/Http/Requests/Attributes/CreateRequest.php @@ -27,7 +27,7 @@ public function rules(Attribute $attribute) { return [ 'group_id' => 'required', - 'name' => 'array|required|valid_locales', + 'name' => 'array|required|valid_language', 'handle' => 'required|unique:attributes,handle', ]; } diff --git a/src/Http/Requests/Attributes/UpdateRequest.php b/src/Http/Requests/Attributes/UpdateRequest.php index 023c664b4..d6cbbc027 100644 --- a/src/Http/Requests/Attributes/UpdateRequest.php +++ b/src/Http/Requests/Attributes/UpdateRequest.php @@ -28,7 +28,7 @@ public function rules() $decodedId = GetCandy::attributes()->getDecodedId($this->attribute); return [ - 'name' => 'required|array|valid_locales', + 'name' => 'required|array|valid_language', 'filterable' => 'boolean', 'searchable' => 'boolean', 'position' => 'integer', diff --git a/src/Http/Requests/Products/UpdateRequest.php b/src/Http/Requests/Products/UpdateRequest.php index b02fefe3d..acb011f34 100644 --- a/src/Http/Requests/Products/UpdateRequest.php +++ b/src/Http/Requests/Products/UpdateRequest.php @@ -39,7 +39,7 @@ public function rules() foreach ($attributes as $attribute) { if ($attribute->required) { - $rulestring = 'attribute_data.'.$attribute->handle.'.'.$defaultChannel->handle.'.'.$defaultLanguage->lang; + $rulestring = 'attribute_data.'.$attribute->handle.'.'.$defaultChannel->handle.'.'.$defaultLanguage->code; // $ruleset[$rulestring] = 'required'; } } diff --git a/src/Http/Requests/Tags/CreateRequest.php b/src/Http/Requests/Tags/CreateRequest.php index 41d067653..8d279d9f6 100644 --- a/src/Http/Requests/Tags/CreateRequest.php +++ b/src/Http/Requests/Tags/CreateRequest.php @@ -25,7 +25,7 @@ public function authorize() public function rules(Tag $tag) { return [ - 'name' => 'array|required|valid_locales', + 'name' => 'array|required|valid_language', ]; } } diff --git a/src/Http/Requests/Tags/UpdateRequest.php b/src/Http/Requests/Tags/UpdateRequest.php index f31cf8c55..4ad0cde20 100644 --- a/src/Http/Requests/Tags/UpdateRequest.php +++ b/src/Http/Requests/Tags/UpdateRequest.php @@ -24,7 +24,7 @@ public function authorize() public function rules() { return [ - 'name' => 'required|array|valid_locales', + 'name' => 'required|array|valid_language', ]; } } diff --git a/src/Http/Validators/LocaleValidator.php b/src/Http/Validators/LanguageValidator.php similarity index 81% rename from src/Http/Validators/LocaleValidator.php rename to src/Http/Validators/LanguageValidator.php index 6f91e7b9d..eba6e2b4f 100644 --- a/src/Http/Validators/LocaleValidator.php +++ b/src/Http/Validators/LanguageValidator.php @@ -4,7 +4,7 @@ use GetCandy\Api\Core\Languages\Actions\FetchLanguages; -class LocaleValidator +class LanguageValidator { /** * Validates the name for an attribute doesn't exist in the same group. @@ -20,15 +20,15 @@ public function validate($attribute, $value, $parameters, $validator) if (! is_array($value)) { return false; } - $locales = array_keys($value); + $codes = array_keys($value); $languages = FetchLanguages::run([ 'paginate' => false, 'search' => [ - 'lang' => $locales, + 'code' => $codes, ], ]); - return $languages->count() === count($locales); + return $languages->count() === count($codes); } } diff --git a/src/Installer/Runners/LanguageRunner.php b/src/Installer/Runners/LanguageRunner.php index e8b6bd2f1..94f94bbb7 100644 --- a/src/Installer/Runners/LanguageRunner.php +++ b/src/Installer/Runners/LanguageRunner.php @@ -16,8 +16,7 @@ public function __construct() { $this->availableLanguages = collect([ 'gb' => [ - 'lang' => 'en', - 'iso' => 'gb', + 'code' => 'en', 'name' => 'English', 'default' => true, ], diff --git a/src/Installer/Runners/UserRunner.php b/src/Installer/Runners/UserRunner.php index 02470f18d..c18c2f6c3 100644 --- a/src/Installer/Runners/UserRunner.php +++ b/src/Installer/Runners/UserRunner.php @@ -10,9 +10,7 @@ class UserRunner extends AbstractRunner implements InstallRunnerContract { public function run() { - if (! DB::table('roles')->count()) { - $this->installRoles(); - } + $this->installRoles(); $model = config('auth.providers.users.model'); diff --git a/src/Providers/ApiServiceProvider.php b/src/Providers/ApiServiceProvider.php index e28a3e310..fd91f01ef 100644 --- a/src/Providers/ApiServiceProvider.php +++ b/src/Providers/ApiServiceProvider.php @@ -127,7 +127,7 @@ protected function mapValidators() Validator::extend('valid_structure', 'GetCandy\Api\Http\Validators\AttributeValidator@validateData'); Validator::extend('unique_category_attribute', 'GetCandy\Api\Http\Validators\CategoriesValidator@uniqueCategoryAttributeData'); Validator::extend('check_coupon', 'GetCandy\Api\Core\Discounts\Validators\DiscountValidator@checkCoupon'); - Validator::extend('valid_locales', 'GetCandy\Api\Http\Validators\LocaleValidator@validate'); + Validator::extend('valid_language', 'GetCandy\Api\Http\Validators\LanguageValidator@validate'); Validator::extend('enabled', 'GetCandy\Api\Http\Validators\BaseValidator@enabled'); Validator::extend('asset_url', 'GetCandy\Api\Http\Validators\AssetValidator@validAssetUrl'); Validator::extend('valid_discount', 'GetCandy\Api\Core\Discounts\Validators\DiscountValidator@validate'); diff --git a/tests/Feature/Actions/Languages/CreateLanguageTest.php b/tests/Feature/Actions/Languages/CreateLanguageTest.php index 2fef174c8..b5d48781a 100644 --- a/tests/Feature/Actions/Languages/CreateLanguageTest.php +++ b/tests/Feature/Actions/Languages/CreateLanguageTest.php @@ -14,8 +14,7 @@ public function test_can_run_action_as_controller() $user = $this->admin(); $response = $this->actingAs($user)->json('POST', 'languages', [ - 'lang' => 'en', - 'iso' => 'gba', + 'code' => 'gbr', 'name' => 'English', 'default' => 1, 'enabled' => 1, @@ -25,4 +24,56 @@ public function test_can_run_action_as_controller() $this->assertResponseValid($response, '/languages', 'post'); } + + public function test_validation_works_on_code_field() + { + $user = $this->admin(); + + $codes = [ + '{}tg12}', + '={}2ed]', + '1234}', + '1{@3{5}', + 'semicolon;', + '1{@3{5}-', + ]; + + foreach ($codes as $code) { + $response = $this->actingAs($user)->json('POST', 'languages', [ + 'code' => $code, + 'name' => 'English', + 'default' => 1, + 'enabled' => 1, + ]); + + $response->assertStatus(422); + + $this->assertResponseValid($response, '/languages', 'post'); + } + } + + public function test_validation_allows_code_pattern() + { + $user = $this->admin(); + + $codes = [ + 'DE-DE-12', + '2dw3e-2', + 'DEGB', + '09-DE', + ]; + + foreach ($codes as $code) { + $response = $this->actingAs($user)->json('POST', 'languages', [ + 'code' => $code, + 'name' => 'English', + 'default' => 1, + 'enabled' => 1, + ]); + + $response->assertStatus(201); + + $this->assertResponseValid($response, '/languages', 'post'); + } + } } diff --git a/tests/Unit/Installer/Runners/LanguageRunnerTest.php b/tests/Unit/Installer/Runners/LanguageRunnerTest.php index 1c90d98cd..72c46c899 100644 --- a/tests/Unit/Installer/Runners/LanguageRunnerTest.php +++ b/tests/Unit/Installer/Runners/LanguageRunnerTest.php @@ -22,8 +22,7 @@ public function test_install_can_run() $runner->run(); $this->assertDatabaseHas('languages', [ - 'lang' => 'en', - 'iso' => 'gb', + 'code' => 'en', 'name' => 'English', 'default' => 1, 'enabled' => 1, diff --git a/tests/Unit/Languages/Actions/FetchEnabledLanguageByCodeTest.php b/tests/Unit/Languages/Actions/FetchEnabledLanguageByCodeTest.php index 886ce0063..03a8ffc59 100644 --- a/tests/Unit/Languages/Actions/FetchEnabledLanguageByCodeTest.php +++ b/tests/Unit/Languages/Actions/FetchEnabledLanguageByCodeTest.php @@ -11,21 +11,21 @@ */ class FetchEnabledLanguageByCodeTest extends TestCase { - public function test_can_attach_user_to_customer_record() + public function test_can_fetch_languages() { $user = $this->admin(); $languageA = factory(Language::class)->create([ 'enabled' => false, - 'iso' => 'gba', + 'code' => 'gba', ]); $languageB = factory(Language::class)->create([ 'enabled' => true, - 'iso' => 'sk', + 'code' => 'sk', ]); $languageC = factory(Language::class)->create([ 'enabled' => false, - 'iso' => 'dk', + 'code' => 'dk', ]); $this->assertFalse($languageA->enabled); @@ -33,13 +33,13 @@ public function test_can_attach_user_to_customer_record() $this->assertFalse($languageC->enabled); $language = FetchEnabledLanguageByCode::run([ - 'code' => $languageB->iso, + 'code' => $languageB->code, ]); $this->assertEquals($languageB->id, $language->id); $language = FetchEnabledLanguageByCode::run([ - 'code' => $languageC->iso, + 'code' => $languageC->code, ]); $this->assertNull($language); diff --git a/tests/Unit/Languages/Actions/FetchLanguagesTest.php b/tests/Unit/Languages/Actions/FetchLanguagesTest.php index a10591351..e10636aa9 100644 --- a/tests/Unit/Languages/Actions/FetchLanguagesTest.php +++ b/tests/Unit/Languages/Actions/FetchLanguagesTest.php @@ -56,29 +56,25 @@ public function test_can_search_languages() $user = $this->admin(); $languageA = factory(Language::class)->create([ - 'lang' => 'en', - 'iso' => 'gba', + 'code' => 'gba', 'enabled' => true, ]); $languageB = factory(Language::class)->create([ - 'lang' => 'sk', - 'iso' => 'sk', + 'code' => 'sk', 'enabled' => true, ]); $languageC = factory(Language::class)->create([ - 'lang' => 'dk', - 'iso' => 'dk', + 'code' => 'dk', 'enabled' => true, ]); - $languages = FetchLanguages::run([ 'paginate' => false, 'search' => [ - 'lang' => ['en', 'sk'], + 'code' => ['en', 'sk'], ], ]); - $test = Language::whereIn('lang', ['en', 'sk'])->count(); + $test = Language::whereIn('code', ['en', 'sk'])->count(); $this->assertEquals($test, $languages->count()); } From 72a739957cee978655ed4662e41ca0321a1cd44a Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 18 Mar 2021 10:06:46 +0000 Subject: [PATCH 135/152] Trigger indexable even when restoring --- src/Core/RecycleBin/Services/RecycleBinService.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Core/RecycleBin/Services/RecycleBinService.php b/src/Core/RecycleBin/Services/RecycleBinService.php index 9ee1e876a..69a572c12 100644 --- a/src/Core/RecycleBin/Services/RecycleBinService.php +++ b/src/Core/RecycleBin/Services/RecycleBinService.php @@ -3,8 +3,9 @@ namespace GetCandy\Api\Core\RecycleBin\Services; use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\RecycleBin\Interfaces\RecycleBinServiceInterface; use GetCandy\Api\Core\RecycleBin\Models\RecycleBin; +use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use GetCandy\Api\Core\RecycleBin\Interfaces\RecycleBinServiceInterface; class RecycleBinService implements RecycleBinServiceInterface { @@ -40,6 +41,7 @@ public function restore($id) $item = $this->findById($id); if ($item->recyclable) { $item->recyclable->restore(); + IndexableSavedEvent::dispatch($item->recyclable); $item->delete(); } } From aaaa441f65a8ea8c7b46898e25e153a2c3c6948a Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 18 Mar 2021 10:06:56 +0000 Subject: [PATCH 136/152] Add keyword search to product families --- src/Core/Products/Actions/FetchProductFamilies.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Core/Products/Actions/FetchProductFamilies.php b/src/Core/Products/Actions/FetchProductFamilies.php index d64472bfa..b4672be5f 100644 --- a/src/Core/Products/Actions/FetchProductFamilies.php +++ b/src/Core/Products/Actions/FetchProductFamilies.php @@ -30,6 +30,7 @@ public function rules() return [ 'per_page' => 'numeric|max:200', 'paginate' => 'boolean', + 'keywords' => 'nullable', ]; } @@ -48,6 +49,10 @@ public function handle() return $query->get(); } + if ($this->keywords) { + $query->where('name', 'LIKE', "%{$this->keywords}%"); + } + return $query->withCount( $this->resolveRelationCounts() )->paginate($this->per_page ?? 50); From 9a601f7beaa3b155c12db7fa790289b112da908a Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 18 Mar 2021 15:06:50 +0000 Subject: [PATCH 137/152] Add phone email to mass assignment --- src/Core/Addresses/Models/Address.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Core/Addresses/Models/Address.php b/src/Core/Addresses/Models/Address.php index e7642710a..ee838914a 100644 --- a/src/Core/Addresses/Models/Address.php +++ b/src/Core/Addresses/Models/Address.php @@ -39,6 +39,9 @@ public function getFieldsAttribute() 'address_three', 'city', 'state', + 'state', + 'phone', + 'email', 'country_id', 'postal_code', ]); From 71c15f76d2d005ec1eac7c566876f1f2c0e4e09f Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 18 Mar 2021 15:07:05 +0000 Subject: [PATCH 138/152] Hotfix `lang` attribute reference --- .../Search/Drivers/Elasticsearch/Actions/IndexCategories.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php index 36b343956..9893dfdaf 100644 --- a/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php +++ b/src/Core/Search/Drivers/Elasticsearch/Actions/IndexCategories.php @@ -52,7 +52,7 @@ public function handle() $languages = FetchLanguages::run([ 'paginate' => false, - ])->pluck('lang'); + ])->pluck('code'); $customerGroups = FetchCustomerGroups::run([ 'paginate' => false, From d5dca984ed4b6a88083ee074ffb490a299341c20 Mon Sep 17 00:00:00 2001 From: Alec Ritson Date: Fri, 19 Mar 2021 10:11:02 +0000 Subject: [PATCH 139/152] Feat/route refactoring (#366) * Refactor langauges * Update changelog * Fix indexing * Apply fixes from StyleCI (#364) Co-authored-by: Alec Ritson * Merge in 0.12 * Initial route refactoring * Apply fixes from StyleCI (#365) Co-authored-by: Alec Ritson * Squashed commit of the following: commit 71c15f76d2d005ec1eac7c566876f1f2c0e4e09f Author: Alec Date: Thu Mar 18 15:07:05 2021 +0000 Hotfix `lang` attribute reference commit 9a601f7beaa3b155c12db7fa790289b112da908a Author: Alec Date: Thu Mar 18 15:06:50 2021 +0000 Add phone email to mass assignment commit aaaa441f65a8ea8c7b46898e25e153a2c3c6948a Author: Alec Date: Thu Mar 18 10:06:56 2021 +0000 Add keyword search to product families commit 72a739957cee978655ed4662e41ca0321a1cd44a Author: Alec Date: Thu Mar 18 10:06:46 2021 +0000 Trigger indexable even when restoring commit c831d92f58362aa1c87877743d928033b4a5d8ff Author: Alec Ritson Date: Thu Mar 18 09:47:34 2021 +0000 Language Refactoring (#363) * Refactor langauges * Update changelog * Fix indexing * Apply fixes from StyleCI (#364) Co-authored-by: Alec Ritson * Adjust regex pattern * Use proper values on factory * rename $locale * Update tests * Remove unit group * Adjust language change wording Co-authored-by: Alec Ritson * Apply fixes from StyleCI (#367) Co-authored-by: Alec Ritson * Make language id required * Update to AliasResolver * Update changelog * Apply fixes from StyleCI (#368) Co-authored-by: Alec Ritson * Styling update * Styleci lint * change from `language_id` to `language_code` * Fix tests for unique constraint Co-authored-by: Alec Ritson --- CHANGELOG.md | 28 ++++- database/factories/LanguageFactory.php | 2 +- database/factories/RouteFactory.php | 2 +- ...021_03_17_144530_refactor_routes_table.php | 51 +++++++++ ...900_add_unique_index_to_language_codes.php | 28 +++++ openapi/routes/models/Route.yaml | 14 +-- openapi/routes/paths/routes.search.yaml | 20 +++- openapi/routes/paths/routes.yaml | 22 +++- openapi/routes/requests/CreateRouteBody.yaml | 11 +- routes/api.php | 4 +- .../Exceptions/AliasResolutionException.php | 9 ++ .../RecycleBin/Services/RecycleBinService.php | 2 +- src/Core/Routes/Actions/CreateRoute.php | 54 ++++++++-- src/Core/Routes/Actions/FetchRoute.php | 2 +- src/Core/Routes/Actions/SearchForRoute.php | 21 ++-- src/Core/Routes/Actions/UpdateRoute.php | 11 +- src/Core/Routes/Models/Route.php | 8 +- src/Core/Routes/Resources/RouteResource.php | 13 ++- src/Core/Routes/Services/RouteService.php | 102 ------------------ src/Core/Scaffold/AliasResolver.php | 36 +++++++ .../Categories/CategoryRouteController.php | 23 ---- .../Products/ProductRouteController.php | 31 ------ .../Resources/Products/ProductResource.php | 2 +- .../Actions/Routes/SearchRouteTest.php | 20 +++- tests/Feature/FeatureCase.php | 2 +- .../Actions/FetchDefaultLanguageTest.php | 3 + .../Languages/Actions/FetchLanguagesTest.php | 3 + tests/Unit/Routes/Actions/CreateRouteTest.php | 39 +++---- tests/Unit/Routes/Actions/UpdateRouteTest.php | 70 +----------- tests/Unit/Scaffold/AliasResolverTest.php | 39 +++++++ 30 files changed, 369 insertions(+), 303 deletions(-) create mode 100644 database/migrations/2021_03_17_144530_refactor_routes_table.php create mode 100644 database/migrations/2021_03_19_092900_add_unique_index_to_language_codes.php create mode 100644 src/Core/Exceptions/AliasResolutionException.php delete mode 100644 src/Core/Routes/Services/RouteService.php create mode 100644 src/Core/Scaffold/AliasResolver.php delete mode 100644 src/Http/Controllers/Categories/CategoryRouteController.php delete mode 100644 src/Http/Controllers/Products/ProductRouteController.php create mode 100644 tests/Unit/Scaffold/AliasResolverTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index fa34bae44..5e524c63d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,33 @@ You should update any calls to this endpoint if you rely on included resources. The way drafting previously worked has now been refactored to be less destructive. You should reindex your products before going back into the hub to get everything in sync. -You can do this by running `php artisan candy:products:reindex` and `php artisan candy:categories:reindex` +You can do this by running `php artisan candy:products:reindex` and `php artisan candy:categories:reindex`H + +### Route searching + +The way you search for routes has changed on the API. We have removed the `path` column and also the `locale` column in favour of a `language_id` relation. + +When you search for a route, previously you would do something like: +```javascript +const { data } = await axios('routes/search', { + params: { + slug: 'slug-for-the-product', + path: null, + include: 'element' + } +}) +``` +This should now be changed to: +```javascript +const { data } = await axios('routes/search', { + params: { + slug: 'slug-for-the-product', + language_code: 'en', + element_type: 'product', + include: 'element' + } +}) +``` ### 🐞 Fixes - Fixed an issue that was causing a indefinite wildcard search on products diff --git a/database/factories/LanguageFactory.php b/database/factories/LanguageFactory.php index 7b3b0f4f0..15cdbc0f7 100644 --- a/database/factories/LanguageFactory.php +++ b/database/factories/LanguageFactory.php @@ -17,7 +17,7 @@ $factory->define(Language::class, function (Faker $faker) { return [ 'name' => $faker->unique()->country, - 'code' => $faker->languageCode, + 'code' => $faker->unique()->languageCode, 'default' => $faker->boolean, 'enabled' => $faker->boolean, ]; diff --git a/database/factories/RouteFactory.php b/database/factories/RouteFactory.php index 4895a6736..8340991bf 100644 --- a/database/factories/RouteFactory.php +++ b/database/factories/RouteFactory.php @@ -20,8 +20,8 @@ return [ 'element_type' => Product::class, 'element_id' => 1, + 'language_id' => 1, 'slug' => Str::slug($faker->word), - 'path' => Str::slug($faker->word), 'default' => $faker->boolean, ]; }); diff --git a/database/migrations/2021_03_17_144530_refactor_routes_table.php b/database/migrations/2021_03_17_144530_refactor_routes_table.php new file mode 100644 index 000000000..c99ba1887 --- /dev/null +++ b/database/migrations/2021_03_17_144530_refactor_routes_table.php @@ -0,0 +1,51 @@ +groupBy('locale')->pluck('locale')->mapWithKeys(function ($locale) { + return [$locale => Language::whereCode($locale)->first()]; + }); + + Schema::table('routes', function (Blueprint $table) { + $table->integer('language_id')->after('id')->unsigned()->nullable(); + $table->foreign('language_id')->references('id')->on('languages'); + }); + + foreach ($languages as $locale => $language) { + DB::table('routes')->whereLocale($locale)->update([ + 'language_id' => $language->id, + ]); + } + + Schema::table('routes', function (Blueprint $table) { + $table->dropColumn('locale'); + }); + + Schema::table('routes', function (Blueprint $table) { + $table->dropColumn('path'); + }); + + Schema::table('routes', function (Blueprint $table) { + $table->index(['element_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::dropIfExists('report_exports'); + } +} diff --git a/database/migrations/2021_03_19_092900_add_unique_index_to_language_codes.php b/database/migrations/2021_03_19_092900_add_unique_index_to_language_codes.php new file mode 100644 index 000000000..0a18e7f3d --- /dev/null +++ b/database/migrations/2021_03_19_092900_add_unique_index_to_language_codes.php @@ -0,0 +1,28 @@ +unique(['code']); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('languages', function (Blueprint $table) { + $table->dropIndex(['code']); + }); + } +} diff --git a/openapi/routes/models/Route.yaml b/openapi/routes/models/Route.yaml index 756c8b4cb..632f0bc4d 100644 --- a/openapi/routes/models/Route.yaml +++ b/openapi/routes/models/Route.yaml @@ -3,10 +3,6 @@ description: |- properties: id: type: string - element_type: - type: string - element_id: - type: integer default: type: boolean default: false @@ -15,18 +11,14 @@ properties: default: false slug: type: string - path: - type: string - nullable: true - locale: - type: string description: type: string nullable: true + type: + type: string drafted_at: type: string - draft_parent_id: - type: integer + nullable: true x-tags: - Routes type: object \ No newline at end of file diff --git a/openapi/routes/paths/routes.search.yaml b/openapi/routes/paths/routes.search.yaml index 6a66e16a1..68a3c45b0 100644 --- a/openapi/routes/paths/routes.search.yaml +++ b/openapi/routes/paths/routes.search.yaml @@ -15,5 +15,23 @@ get: application/json: schema: $ref: '../../global/responses/ApiError.yaml' + '422': + description: Unprocessable Entity operationId: get-routes-search - description: Get a Route by searching via slug or path + parameters: + - schema: + type: string + in: query + name: slug + required: true + - schema: + type: string + in: query + name: element_type + required: true + - schema: + type: string + in: query + name: language_code + required: true + description: Get a Route by searching via the slug and element_type diff --git a/openapi/routes/paths/routes.yaml b/openapi/routes/paths/routes.yaml index 7822bbeb2..d91d07575 100644 --- a/openapi/routes/paths/routes.yaml +++ b/openapi/routes/paths/routes.yaml @@ -10,4 +10,24 @@ get: schema: $ref: '../responses/RouteCollection.yaml' operationId: get-routes - description: Returns a paginated list of Routes \ No newline at end of file + description: Returns a paginated list of Routes +post: + summary: Create Route + tags: + - Routes + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../responses/RouteResponse.yaml' + operationId: create-route + requestBody: + content: + multipart/form-data: + schema: + $ref: '../requests/CreateRouteBody.yaml' + examples: {} + description: '' + description: Creates a route \ No newline at end of file diff --git a/openapi/routes/requests/CreateRouteBody.yaml b/openapi/routes/requests/CreateRouteBody.yaml index aecc74518..1a00affae 100644 --- a/openapi/routes/requests/CreateRouteBody.yaml +++ b/openapi/routes/requests/CreateRouteBody.yaml @@ -11,13 +11,12 @@ properties: type: boolean slug: type: string - path: - type: string - locale: + language_id: type: string description: type: string required: - - name - - lang - - iso \ No newline at end of file + - slug + - element_id + - element_type + - language_id \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 158bb01ad..055ddf8da 100644 --- a/routes/api.php +++ b/routes/api.php @@ -84,8 +84,6 @@ $router->post('categories/{category}/channels', 'Categories\CategoryController@putChannels'); $router->post('categories/{category}/customer-groups', 'Categories\CategoryController@putCustomerGroups'); $router->put('categories/{category}/layouts', 'Categories\LayoutController@store'); - - $router->post('categories/{category}/routes', 'Categories\CategoryRouteController@store'); $router->post('categories/{id}/publish', 'Categories\CategoryController@publishDraft'); $router->resource('categories', 'Categories\CategoryController', [ 'except' => ['index', 'edit', 'create', 'show'], @@ -205,7 +203,6 @@ $router->post('/{product}/redirects', 'ProductRedirectController@store'); $router->post('/{product}/attributes', 'ProductAttributeController@update'); $router->post('/{product}/collections', 'ProductCollectionController@update'); - $router->post('/{product}/routes', 'ProductRouteController@store'); $router->post('/{product}/categories', 'ProductCategoryController@update'); $router->post('/{product}/channels', 'ProductChannelController@store'); $router->delete('/{product}/categories/{category}', 'ProductCategoryController@destroy'); @@ -267,6 +264,7 @@ 'prefix' => 'routes', ], function ($route) { $route->get('/', '\GetCandy\Api\Core\Routes\Actions\FetchRoutes'); + $route->post('/', '\GetCandy\Api\Core\Routes\Actions\CreateRoute'); $route->delete('{encoded_id}', '\GetCandy\Api\Core\Routes\Actions\DeleteRoute'); $route->put('{encoded_id}', '\GetCandy\Api\Core\Routes\Actions\UpdateRoute'); }); diff --git a/src/Core/Exceptions/AliasResolutionException.php b/src/Core/Exceptions/AliasResolutionException.php new file mode 100644 index 000000000..eb6d01458 --- /dev/null +++ b/src/Core/Exceptions/AliasResolutionException.php @@ -0,0 +1,9 @@ +set('element_type', AliasResolver::resolve($this->element_type)); + return [ - 'slug' => 'required|unique_with:routes,path,'.$this->path, - 'path' => 'unique_with:routes,slug,'.$this->slug, - 'element' => 'required', - 'locale' => 'required|string', + 'slug' => 'required|unique_with:routes,element_type,'.$this->element_type, + 'element_type' => [ + 'required', + 'string', + function () { + return class_exists($this->element_type); + }, + ], + 'element_id' => [ + 'required', + 'string', + function () { + return (new $this->element_type)->decodeId($this->element_id); + }, + ], + 'language_id' => 'required|string|hashid_is_valid:'.Language::class, 'default' => 'boolean', 'redirect' => 'boolean', ]; @@ -45,11 +61,31 @@ public function rules() */ public function handle() { - $this->element->routes()->create( - Arr::except($this->validated(), ['element']), - ); + $elementId = (new $this->element_type)->decodeId($this->element_id); + $languageId = (new Language)->decodeId($this->language_id); + + $route = Route::create([ + 'element_id' => $elementId, + 'element_type' => $this->element_type, + 'default' => $this->default, + 'redirect' => $this->redirect, + 'language_id' => $languageId, + 'slug' => $this->slug, + ]); + + if ($route->default) { + // Need to make sure we unset any defaults of any siblings + // as we can only have one + Route::whereElementType($route->element_type) + ->whereElementId($route->element_id) + ->where('id', '!=', $route->id) + ->where('language_id', '=', $route->language_id) + ->update([ + 'default' => false, + ]); + } - return $this->element; + return $route; } /** diff --git a/src/Core/Routes/Actions/FetchRoute.php b/src/Core/Routes/Actions/FetchRoute.php index 494cd7824..2e141a6fd 100644 --- a/src/Core/Routes/Actions/FetchRoute.php +++ b/src/Core/Routes/Actions/FetchRoute.php @@ -3,9 +3,9 @@ namespace GetCandy\Api\Core\Routes\Actions; use GetCandy\Api\Core\Routes\Models\Route; +use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Scaffold\AbstractAction; use GetCandy\Api\Core\Traits\ReturnsJsonResponses; -use GetCandy\Api\Http\Resources\Routes\RouteResource; use Illuminate\Database\Eloquent\ModelNotFoundException; class FetchRoute extends AbstractAction diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index 265ddebec..b09d0ed77 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -2,10 +2,12 @@ namespace GetCandy\Api\Core\Routes\Actions; +use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Routes\Models\Route; +use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Scaffold\AbstractAction; +use GetCandy\Api\Core\Scaffold\AliasResolver; use GetCandy\Api\Core\Traits\ReturnsJsonResponses; -use GetCandy\Api\Http\Resources\Routes\RouteResource; class SearchForRoute extends AbstractAction { @@ -30,7 +32,8 @@ public function rules(): array { return [ 'slug' => 'required|string', - 'path' => 'nullable|string', + 'element_type' => 'required|string', + 'language_code' => 'required|exists:languages,code', ]; } @@ -41,13 +44,17 @@ public function rules(): array */ public function handle() { + $elementType = AliasResolver::resolve($this->element_type); + $languageId = (new Language)->decodeId($this->language_id); + $query = Route::whereSlug($this->slug)->with( $this->resolveEagerRelations() - )->withCount($this->resolveRelationCounts()); - - if ($this->path) { - $query->wherePath($this->path); - } + )->whereElementType($elementType) + ->whereHas('language', function ($query) { + $query->whereCode($this->language_code); + })->withCount( + $this->resolveRelationCounts() + ); return $query->first(); } diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index e45ac008b..f60d3a9ae 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -2,6 +2,7 @@ namespace GetCandy\Api\Core\Routes\Actions; +use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Scaffold\AbstractAction; @@ -44,13 +45,13 @@ function ($attribute, $value, $fail) { if ($this->route->publishedParent) { $ids[] = $this->route->publishedParent->id; } - $result = DB::table('routes')->wherePath($this->path)->whereSlug($value)->whereNotIn('id', $ids)->exists(); + $result = DB::table('routes')->whereElementType($this->route->element_type)->whereSlug($value)->whereNotIn('id', $ids)->exists(); if ($result) { - $fail('The path and slug have already been taken'); + $fail('This slug has already been taken for this element type'); } }, ], - 'lang' => 'nullable|string', + 'language_id' => 'nullable|string|hashid_is_valid:'.Language::class, 'description' => 'nullable|string', 'default' => 'boolean', 'redirect' => 'boolean', @@ -64,7 +65,9 @@ function ($attribute, $value, $fail) { */ public function handle() { - $this->route->update($this->validated()); + $attributes = $this->validated(); + $attributes['language_id'] = (new Language)->decodeId($this->language_id); + $this->route->update($attributes); if ($this->route->default) { // Need to make sure we unset any defaults of any siblings diff --git a/src/Core/Routes/Models/Route.php b/src/Core/Routes/Models/Route.php index 4546fc448..0340ed763 100644 --- a/src/Core/Routes/Models/Route.php +++ b/src/Core/Routes/Models/Route.php @@ -2,6 +2,7 @@ namespace GetCandy\Api\Core\Routes\Models; +use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Scaffold\BaseModel; use Illuminate\Database\Eloquent\SoftDeletes; use NeonDigital\Drafting\Draftable; @@ -24,7 +25,7 @@ class Route extends BaseModel * @var array */ protected $fillable = [ - 'slug', 'default', 'redirect', 'description', 'locale', 'path', 'element_type', 'element_id', + 'slug', 'default', 'redirect', 'description', 'element_type', 'element_id', 'language_id', ]; /** @@ -36,4 +37,9 @@ public function element() { return $this->morphTo(); } + + public function language() + { + return $this->belongsTo(Language::class); + } } diff --git a/src/Core/Routes/Resources/RouteResource.php b/src/Core/Routes/Resources/RouteResource.php index f6507561a..607b5b235 100644 --- a/src/Core/Routes/Resources/RouteResource.php +++ b/src/Core/Routes/Resources/RouteResource.php @@ -2,6 +2,7 @@ namespace GetCandy\Api\Core\Routes\Resources; +use GetCandy\Api\Core\Languages\Resources\LanguageResource; use GetCandy\Api\Http\Resources\AbstractResource; class RouteResource extends AbstractResource @@ -12,18 +13,18 @@ public function payload() 'id' => $this->encodedId(), 'default' => (bool) $this->default, 'redirect' => (bool) $this->redirect, - 'locale' => $this->locale, - 'path' => $this->path, 'slug' => $this->slug, 'description' => $this->description, 'type' => str_slug(class_basename($this->element_type)), + 'drafted_at' => $this->drafted_at, ]; } public function includes() { return [ - 'element' => ['data' => $this->whenLoaded('element', function () { + 'language' => $this->include('language', LanguageResource::class), + 'element' => $this->whenLoaded('element', function () { // Need to guess the element $class = class_basename(get_class($this->element)); $resource = 'GetCandy\Api\Http\Resources\\'.str_plural($class).'\\'.$class.'Resource'; @@ -47,9 +48,11 @@ public function includes() $resourceClass = implode('\\', $classSegments); if (class_exists($resourceClass)) { - return new $resourceClass($this->element); + return [ + 'data' => new $resourceClass($this->element), + ]; } - })], + }), ]; } } diff --git a/src/Core/Routes/Services/RouteService.php b/src/Core/Routes/Services/RouteService.php deleted file mode 100644 index dc7cb9afc..000000000 --- a/src/Core/Routes/Services/RouteService.php +++ /dev/null @@ -1,102 +0,0 @@ -model = new Route; - } - - /** - * Gets a route by a given slug. - * - * @param string $slug - * @return \GetCandy\Api\Core\Routes\Models\Route - * - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public function getBySlug($slug) - { - $route = $this->model->where('slug', '=', $slug)->firstOrFail(); - app()->setLocale($route->locale); - - return $route; - } - - public function update($hashedId, array $data) - { - $model = $this->getByHashedId($hashedId); - $model->slug = $data['slug']; - $model->default = $data['default']; - - // Cannot be a default route and a redirect. - if (! empty($data['default'])) { - $model->redirect = false; - } - - $model->save(); - - \Log::debug('hit'); - - return $model; - } - - public function slugExists($slug, $path = null) - { - $query = $this->model->where('slug', '=', $slug); - - if ($path) { - $query = $query->where('path', '=', $path); - } - - return $query->exists(); - } - - /** - * @param string $hashedId - * @return bool - * - * @throws \GetCandy\Api\Exceptions\MinimumRecordRequiredException - */ - public function delete($hashedId) - { - $route = $this->getByHashedId($hashedId, true); - if (! $route) { - abort(404); - } - // if ($route->element->routes->count() == 1) { - // throw new MinimumRecordRequiredException( - // trans('getcandy::exceptions.minimum_record_required') - // ); - // } - - // if ($route->default) { - // $newDefault = $route->element->routes->where('default', '=', false)->first(); - // $newDefault->default = true; - // $newDefault->save(); - // } - - return $route->delete(); - } - - /** - * Gets a new suggested default model. - * - * @return \GetCandy\Api\Core\Routes\Models\Route - */ - public function getNewSuggestedDefault() - { - return $this->model->where('default', '=', false)->where('enabled', '=', true)->first(); - } - - public function uniqueSlug($slug, $path = null) - { - return ! ($this->model->where('slug', $slug)->where('path', $path)->exists()); - } -} diff --git a/src/Core/Scaffold/AliasResolver.php b/src/Core/Scaffold/AliasResolver.php new file mode 100644 index 000000000..25e9fc441 --- /dev/null +++ b/src/Core/Scaffold/AliasResolver.php @@ -0,0 +1,36 @@ + Product::class, + 'category' => Category::class, + ]; + + public static function addAliases($key, $value) + { + if (isset(self::$aliases[$key])) { + throw new InvalidArgumentException; + } + self::$aliases[$key] = $value; + } + + public static function resolve($alias) + { + if (class_exists($alias)) { + return $alias; + } + if (empty(self::$aliases[$alias]) || ! class_exists(self::$aliases[$alias])) { + throw new AliasResolutionException("Unable to resolve alias \"{$alias}\" into a usable class"); + } + + return self::$aliases[$alias]; + } +} diff --git a/src/Http/Controllers/Categories/CategoryRouteController.php b/src/Http/Controllers/Categories/CategoryRouteController.php deleted file mode 100644 index e1c15cf7e..000000000 --- a/src/Http/Controllers/Categories/CategoryRouteController.php +++ /dev/null @@ -1,23 +0,0 @@ -createUrl($category, $request->all()); - - return new RouteResource($result); - } -} diff --git a/src/Http/Controllers/Products/ProductRouteController.php b/src/Http/Controllers/Products/ProductRouteController.php deleted file mode 100644 index c40fbf96d..000000000 --- a/src/Http/Controllers/Products/ProductRouteController.php +++ /dev/null @@ -1,31 +0,0 @@ -createUrl($product, $request->all()); - - return new RouteResource($result); - } - - public function update($product, UpdateUrlsRequest $request) - { - GetCandy::products()->saveUrls($product, $request->urls); - - return $this->respondWithNoContent(); - } -} diff --git a/src/Http/Resources/Products/ProductResource.php b/src/Http/Resources/Products/ProductResource.php index c3fc971ee..9b0dc8332 100644 --- a/src/Http/Resources/Products/ProductResource.php +++ b/src/Http/Resources/Products/ProductResource.php @@ -5,6 +5,7 @@ use GetCandy\Api\Core\Channels\Resources\ChannelCollection; use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; use GetCandy\Api\Core\Products\Resources\ProductFamilyResource; +use GetCandy\Api\Core\Routes\Resources\RouteCollection; use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Assets\AssetCollection; use GetCandy\Api\Http\Resources\Assets\AssetResource; @@ -13,7 +14,6 @@ use GetCandy\Api\Http\Resources\Collections\CollectionCollection; use GetCandy\Api\Http\Resources\Discounts\DiscountModelCollection; use GetCandy\Api\Http\Resources\Layouts\LayoutResource; -use GetCandy\Api\Http\Resources\Routes\RouteCollection; use GetCandy\Api\Http\Resources\Versioning\VersionCollection; class ProductResource extends AbstractResource diff --git a/tests/Feature/Actions/Routes/SearchRouteTest.php b/tests/Feature/Actions/Routes/SearchRouteTest.php index 5b1d99fb2..2779513d4 100644 --- a/tests/Feature/Actions/Routes/SearchRouteTest.php +++ b/tests/Feature/Actions/Routes/SearchRouteTest.php @@ -6,10 +6,26 @@ use Tests\Feature\FeatureCase; /** - * @group routess + * @group fail */ class SearchRouteTest extends FeatureCase { + public function test_validation_runs() + { + $user = $this->admin(); + + $route = factory(Route::class)->create(); + + $response = $this->actingAs($user)->json('GET', 'routes/search', [ + 'slug' => $route->slug, + 'element_type' => $route->element_type, + ]); + + $response->assertStatus(422); + + $this->assertResponseValid($response, '/routes/search', 'get'); + } + public function test_can_run_action_as_controller() { $user = $this->admin(); @@ -18,6 +34,8 @@ public function test_can_run_action_as_controller() $response = $this->actingAs($user)->json('GET', 'routes/search', [ 'slug' => $route->slug, + 'element_type' => $route->element_type, + 'language_code' => $route->language->code, ]); $response->assertStatus(200); diff --git a/tests/Feature/FeatureCase.php b/tests/Feature/FeatureCase.php index 0a33a071f..0808267d4 100644 --- a/tests/Feature/FeatureCase.php +++ b/tests/Feature/FeatureCase.php @@ -29,7 +29,7 @@ public function setUp(): void // https://github.com/cebe/php-openapi/pull/67 once this is resolved we can use the non full again $this->buildOpenApiValidator( - realpath(__DIR__.'/../../openapi/openapi-full.yaml') + realpath(__DIR__.'/../../openapi/openapi.yaml') ); GetCandy::router(); } diff --git a/tests/Unit/Languages/Actions/FetchDefaultLanguageTest.php b/tests/Unit/Languages/Actions/FetchDefaultLanguageTest.php index 35f72583e..102e73d24 100644 --- a/tests/Unit/Languages/Actions/FetchDefaultLanguageTest.php +++ b/tests/Unit/Languages/Actions/FetchDefaultLanguageTest.php @@ -17,12 +17,15 @@ public function test_can_attach_user_to_customer_record() $languageA = factory(Language::class)->create([ 'default' => false, + 'code' => 'en-a', ]); $languageB = factory(Language::class)->create([ 'default' => true, + 'code' => 'en-b', ]); $languageC = factory(Language::class)->create([ 'default' => false, + 'code' => 'en-c', ]); $this->assertFalse($languageA->default); diff --git a/tests/Unit/Languages/Actions/FetchLanguagesTest.php b/tests/Unit/Languages/Actions/FetchLanguagesTest.php index e10636aa9..84db1ec21 100644 --- a/tests/Unit/Languages/Actions/FetchLanguagesTest.php +++ b/tests/Unit/Languages/Actions/FetchLanguagesTest.php @@ -17,12 +17,15 @@ public function test_can_fetch_all_langauges() $languageA = factory(Language::class)->create([ 'default' => false, + 'code' => 'en-a', ]); $languageB = factory(Language::class)->create([ 'default' => true, + 'code' => 'en-b', ]); $languageC = factory(Language::class)->create([ 'default' => false, + 'code' => 'en-c', ]); $languages = FetchLanguages::run([ diff --git a/tests/Unit/Routes/Actions/CreateRouteTest.php b/tests/Unit/Routes/Actions/CreateRouteTest.php index e5c511004..38088233e 100644 --- a/tests/Unit/Routes/Actions/CreateRouteTest.php +++ b/tests/Unit/Routes/Actions/CreateRouteTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Routes\Actions; +use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Routes\Actions\CreateRoute; use Illuminate\Validation\ValidationException; @@ -17,12 +18,15 @@ public function test_can_create_route() $user = $this->admin(); $product = factory(Product::class)->create(); + $language = factory(Language::class)->create(); $route = (new CreateRoute)->actingAs($user)->run([ 'slug' => 'foo-bar', - 'element' => $product, - 'locale' => 'en', + 'element_id' => $product->encoded_id, + 'element_type' => get_class($product), + 'language_id' => $language->encoded_id, 'default' => true, + 'redirect' => false, ]); $this->assertCount(1, $product->load('routes')->routes); @@ -33,39 +37,26 @@ public function test_cant_create_duplicate_routes() $user = $this->admin(); $product = factory(Product::class)->create(); + $language = factory(Language::class)->create(); (new CreateRoute)->actingAs($user)->run([ 'slug' => 'foo-bar', - 'element' => $product, - 'locale' => 'en', + 'element_id' => $product->encoded_id, + 'element_type' => get_class($product), + 'language_id' => $language->encoded_id, 'default' => true, + 'redirect' => false, ]); $this->expectException(ValidationException::class); (new CreateRoute)->actingAs($user)->run([ 'slug' => 'foo-bar', - 'element' => $product, - 'locale' => 'en', - 'default' => true, - ]); - - (new CreateRoute)->actingAs($user)->run([ - 'slug' => 'foo-bar', - 'path' => 'bar-baz', - 'element' => $product, - 'locale' => 'en', - 'default' => true, - ]); - - $this->expectException(ValidationException::class); - - (new CreateRoute)->actingAs($user)->run([ - 'slug' => 'foo-bar', - 'path' => 'bar-baz', - 'element' => $product, - 'locale' => 'en', + 'element_id' => $product->encoded_id, + 'element_type' => get_class($product), + 'language_id' => $language->encoded_id, 'default' => true, + 'redirect' => false, ]); } } diff --git a/tests/Unit/Routes/Actions/UpdateRouteTest.php b/tests/Unit/Routes/Actions/UpdateRouteTest.php index b754bdbd2..4c7c267a8 100644 --- a/tests/Unit/Routes/Actions/UpdateRouteTest.php +++ b/tests/Unit/Routes/Actions/UpdateRouteTest.php @@ -2,10 +2,10 @@ namespace Tests\Unit\Routes\Actions; +use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Routes\Actions\UpdateRoute; use GetCandy\Api\Core\Routes\Models\Route; -use Illuminate\Validation\ValidationException; use Tests\TestCase; /** @@ -18,81 +18,17 @@ public function test_can_update_route() $user = $this->admin(); $product = factory(Product::class)->create(); + $language = factory(Language::class)->create(); $route = factory(Route::class)->create(); $route = (new UpdateRoute)->actingAs($user)->run([ 'encoded_id' => $route->encoded_id, 'slug' => 'foo-bar', - 'element' => $product, - 'lang' => 'en', + 'language_id' => $language->encoded_id, 'default' => true, ]); $this->assertEquals('foo-bar', $route->slug); } - - public function test_can_update_slug_and_path_to_the_same_values() - { - $user = $this->admin(); - - $product = factory(Product::class)->create(); - - $route = factory(Route::class)->create([ - 'path' => 'foo', - 'slug' => 'bar', - ]); - - (new UpdateRoute)->actingAs($user)->run([ - 'encoded_id' => $route->encoded_id, - 'slug' => 'bar', - 'path' => 'foo', - 'element' => $product, - 'lang' => 'en', - 'default' => true, - ]); - - $product = factory(Product::class)->create(); - - $route = factory(Route::class)->create([ - 'slug' => 'bar', - ]); - - (new UpdateRoute)->actingAs($user)->run([ - 'encoded_id' => $route->encoded_id, - 'slug' => 'bar', - 'element' => $product, - 'lang' => 'en', - 'default' => true, - ]); - - $this->assertEquals('bar', $route->slug); - } - - public function test_cant_update_route_to_another_resources_values() - { - $user = $this->admin(); - - $product = factory(Product::class)->create(); - - $route = factory(Route::class)->create([ - 'slug' => 'bar', - 'path' => null, - ]); - - factory(Route::class)->create([ - 'slug' => 'foo', - 'path' => null, - ]); - - $this->expectException(ValidationException::class); - - $route = (new UpdateRoute)->actingAs($user)->run([ - 'encoded_id' => $route->encoded_id, - 'slug' => 'foo', - 'element' => $product, - 'locale' => 'en', - 'default' => true, - ]); - } } diff --git a/tests/Unit/Scaffold/AliasResolverTest.php b/tests/Unit/Scaffold/AliasResolverTest.php new file mode 100644 index 000000000..e5f70a3aa --- /dev/null +++ b/tests/Unit/Scaffold/AliasResolverTest.php @@ -0,0 +1,39 @@ +assertEquals(Product::class, AliasResolver::resolve('product')); + $this->assertEquals(Category::class, AliasResolver::resolve('category')); + } + + public function test_can_add_additional_classes_to_be_resolved() + { + AliasResolver::addAliases('language', Language::class); + $this->assertEquals(Language::class, AliasResolver::resolve('language')); + } + + public function test_fully_qualified_classnames_with_resolve_themselves() + { + $this->assertEquals(Product::class, Product::class); + } + + public function test_exception_is_thrown_when_unable_to_resolve_alias() + { + $this->expectException(AliasResolutionException::class); + AliasResolver::resolve('foobar'); + } +} From 23060f559c518d11ac9bffa7903c61646d99d7f5 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Mar 2021 11:21:23 +0000 Subject: [PATCH 140/152] Fixes to drafting and route creation --- src/Core/Drafting/Actions/DraftRoutes.php | 1 + src/Core/Routes/Actions/CreateRoute.php | 2 +- src/Core/Routes/Actions/SearchForRoute.php | 1 - src/Core/Routes/Actions/UpdateRoute.php | 4 +++- src/Core/Routes/Resources/RouteResource.php | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Core/Drafting/Actions/DraftRoutes.php b/src/Core/Drafting/Actions/DraftRoutes.php index eef8174e4..253301c15 100644 --- a/src/Core/Drafting/Actions/DraftRoutes.php +++ b/src/Core/Drafting/Actions/DraftRoutes.php @@ -38,6 +38,7 @@ public function handle() { $this->parent->routes->each(function ($parentRoute) { $draftRoute = $parentRoute->replicate(); + $draftRoute->id = null; $draftRoute->element_id = $this->draft->id; $draftRoute->element_type = get_class($this->draft); $draftRoute->drafted_at = now(); diff --git a/src/Core/Routes/Actions/CreateRoute.php b/src/Core/Routes/Actions/CreateRoute.php index e5b5412ef..64b320d46 100644 --- a/src/Core/Routes/Actions/CreateRoute.php +++ b/src/Core/Routes/Actions/CreateRoute.php @@ -98,6 +98,6 @@ public function handle() */ public function response($result, $request) { - return new RouteResource($result); + return new RouteResource($result->load($this->resolveEagerRelations())); } } diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index b09d0ed77..4cbf17769 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -45,7 +45,6 @@ public function rules(): array public function handle() { $elementType = AliasResolver::resolve($this->element_type); - $languageId = (new Language)->decodeId($this->language_id); $query = Route::whereSlug($this->slug)->with( $this->resolveEagerRelations() diff --git a/src/Core/Routes/Actions/UpdateRoute.php b/src/Core/Routes/Actions/UpdateRoute.php index f60d3a9ae..87205415d 100644 --- a/src/Core/Routes/Actions/UpdateRoute.php +++ b/src/Core/Routes/Actions/UpdateRoute.php @@ -66,7 +66,9 @@ function ($attribute, $value, $fail) { public function handle() { $attributes = $this->validated(); - $attributes['language_id'] = (new Language)->decodeId($this->language_id); + if ($this->language_id) { + $attributes['language_id'] = (new Language)->decodeId($this->language_id); + } $this->route->update($attributes); if ($this->route->default) { diff --git a/src/Core/Routes/Resources/RouteResource.php b/src/Core/Routes/Resources/RouteResource.php index 607b5b235..94d2bf66d 100644 --- a/src/Core/Routes/Resources/RouteResource.php +++ b/src/Core/Routes/Resources/RouteResource.php @@ -29,7 +29,7 @@ public function includes() $class = class_basename(get_class($this->element)); $resource = 'GetCandy\Api\Http\Resources\\'.str_plural($class).'\\'.$class.'Resource'; if (class_exists($resource)) { - return new $resource($this->element); + return ['data' => new $resource($this->element)]; } // Try and guess relative to the actual class From 14114e0450074858400ffb04a6b2abf03a6af642 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Mar 2021 12:06:56 +0000 Subject: [PATCH 141/152] Update references to RouteResource --- src/Http/Controllers/Routes/RouteController.php | 8 ++++---- src/Http/Resources/Categories/CategoryResource.php | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Http/Controllers/Routes/RouteController.php b/src/Http/Controllers/Routes/RouteController.php index aeb233a28..56ea0f874 100644 --- a/src/Http/Controllers/Routes/RouteController.php +++ b/src/Http/Controllers/Routes/RouteController.php @@ -3,14 +3,14 @@ namespace GetCandy\Api\Http\Controllers\Routes; use GetCandy; +use Illuminate\Http\Request; use GetCandy\Api\Core\Routes\RouteCriteria; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Routes\UpdateRequest; -use GetCandy\Api\Http\Resources\Routes\RouteCollection; -use GetCandy\Api\Http\Resources\Routes\RouteResource; +use GetCandy\Api\Core\Routes\Resources\RouteResource; +use GetCandy\Api\Core\Routes\Resources\RouteCollection; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Illuminate\Http\Request; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class RouteController extends BaseController diff --git a/src/Http/Resources/Categories/CategoryResource.php b/src/Http/Resources/Categories/CategoryResource.php index b483ed953..4b1aa9ccc 100644 --- a/src/Http/Resources/Categories/CategoryResource.php +++ b/src/Http/Resources/Categories/CategoryResource.php @@ -2,16 +2,16 @@ namespace GetCandy\Api\Http\Resources\Categories; -use GetCandy\Api\Core\Channels\Resources\ChannelCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; use GetCandy\Api\Http\Resources\AbstractResource; -use GetCandy\Api\Http\Resources\Assets\AssetCollection; use GetCandy\Api\Http\Resources\Assets\AssetResource; -use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; +use GetCandy\Api\Core\Routes\Resources\RouteCollection; +use GetCandy\Api\Http\Resources\Assets\AssetCollection; use GetCandy\Api\Http\Resources\Layouts\LayoutResource; +use GetCandy\Api\Core\Channels\Resources\ChannelCollection; use GetCandy\Api\Http\Resources\Products\ProductCollection; -use GetCandy\Api\Http\Resources\Routes\RouteCollection; use GetCandy\Api\Http\Resources\Versioning\VersionCollection; +use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; class CategoryResource extends AbstractResource { From ebc118e6a8e382e7b05371e01c0ad56c484210ca Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Mar 2021 14:29:17 +0000 Subject: [PATCH 142/152] Recycle bin updates --- src/Core/RecycleBin/Models/RecycleBin.php | 3 ++- .../RecycleBin/Services/RecycleBinService.php | 18 ++++++++++++++---- .../RecycleBin/RecycleBinController.php | 2 +- .../RecycleBin/RecycleBinResource.php | 4 +++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Core/RecycleBin/Models/RecycleBin.php b/src/Core/RecycleBin/Models/RecycleBin.php index ffaf5186d..12081028a 100644 --- a/src/Core/RecycleBin/Models/RecycleBin.php +++ b/src/Core/RecycleBin/Models/RecycleBin.php @@ -3,6 +3,7 @@ namespace GetCandy\Api\Core\RecycleBin\Models; use GetCandy\Api\Core\Scaffold\BaseModel; +use Illuminate\Database\Eloquent\SoftDeletingScope; class RecycleBin extends BaseModel { @@ -24,6 +25,6 @@ class RecycleBin extends BaseModel */ public function recyclable() { - return $this->morphTo()->onlyTrashed()->withoutGlobalScopes(); + return $this->morphTo()->withoutGlobalScope(SoftDeletingScope::class); } } diff --git a/src/Core/RecycleBin/Services/RecycleBinService.php b/src/Core/RecycleBin/Services/RecycleBinService.php index 3afb9f380..607021af7 100644 --- a/src/Core/RecycleBin/Services/RecycleBinService.php +++ b/src/Core/RecycleBin/Services/RecycleBinService.php @@ -18,11 +18,21 @@ class RecycleBinService implements RecycleBinServiceInterface * @param array $includes * @return \Illuminate\Pagination\LengthAwarePaginator */ - public function getItems($paginated = true, $perPage = 25, $terms = null, $includes = []) + public function getItems($paginated = true, $perPage = 25, $term = null, $includes = []) { - $query = RecycleBin::whereDoesntHaveMorph('recyclable', [ - Product::class, - ]); + $query = RecycleBin::whereHasMorph('recyclable', [ + Product::class + ], function ($query, $type) use ($term) { + if (!$term) { + return; + } + if ($type == Product::class) { + dd($term); + } + }); + + // dd($products->get()); + if (! $paginated) { return $query->get(); diff --git a/src/Http/Controllers/RecycleBin/RecycleBinController.php b/src/Http/Controllers/RecycleBin/RecycleBinController.php index ee5977d4b..6406ebe95 100644 --- a/src/Http/Controllers/RecycleBin/RecycleBinController.php +++ b/src/Http/Controllers/RecycleBin/RecycleBinController.php @@ -28,7 +28,7 @@ public function index(Request $request) $items = $this->service->getItems( $request->page ?: 1, $request->per_page ?: 25, - $request->terms + $request->term ); return new RecycleBinCollection($items); diff --git a/src/Http/Resources/RecycleBin/RecycleBinResource.php b/src/Http/Resources/RecycleBin/RecycleBinResource.php index 7a4108ec4..4382246c5 100644 --- a/src/Http/Resources/RecycleBin/RecycleBinResource.php +++ b/src/Http/Resources/RecycleBin/RecycleBinResource.php @@ -15,7 +15,9 @@ public function toArray($request) 'name' => $this->recyclable->getRecycleName(), 'thumbnail' => $this->recyclable->getRecycleThumbnail(), 'deleted_at' => $this->recyclable->deleted_at, - 'recyclable' => $this->whenLoaded('recyclable', new DynamicResource($this->recyclable)), + 'recyclable' => $this->whenLoaded('recyclable', [ + 'data' => new DynamicResource($this->recyclable) + ]), ]; } } From dbfc2f5c6b6c8d6ca10bad0b7a42157c4c643241 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 19 Mar 2021 14:43:10 +0000 Subject: [PATCH 143/152] Fix recycle bin searching --- .../RecycleBin/Services/RecycleBinService.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Core/RecycleBin/Services/RecycleBinService.php b/src/Core/RecycleBin/Services/RecycleBinService.php index 607021af7..0350d60d3 100644 --- a/src/Core/RecycleBin/Services/RecycleBinService.php +++ b/src/Core/RecycleBin/Services/RecycleBinService.php @@ -3,9 +3,11 @@ namespace GetCandy\Api\Core\RecycleBin\Services; use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\RecycleBin\Interfaces\RecycleBinServiceInterface; use GetCandy\Api\Core\RecycleBin\Models\RecycleBin; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; +use GetCandy\Api\Core\Channels\Actions\FetchCurrentChannel; +use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; +use GetCandy\Api\Core\RecycleBin\Interfaces\RecycleBinServiceInterface; class RecycleBinService implements RecycleBinServiceInterface { @@ -20,20 +22,23 @@ class RecycleBinService implements RecycleBinServiceInterface */ public function getItems($paginated = true, $perPage = 25, $term = null, $includes = []) { + $channel = FetchCurrentChannel::run(); + $language = FetchDefaultLanguage::run(); $query = RecycleBin::whereHasMorph('recyclable', [ Product::class - ], function ($query, $type) use ($term) { + ], function ($query, $type) use ($term, $channel, $language) { if (!$term) { return; } if ($type == Product::class) { - dd($term); + $query->leftJoin('product_variants', 'product_variants.product_id', '=', 'products.id') + ->where(function ($queryTwo) use ($channel, $term, $language) { + $queryTwo->orWhere("attribute_data->name->{$channel->handle}->{$language->code}", 'LIKE', "%{$term}%") + ->orWhere('sku', 'LIKE', "%{$term}%"); + }); } }); - // dd($products->get()); - - if (! $paginated) { return $query->get(); } From 6350ce0d1da16b2161bc6ad5b7c956970a7d1e30 Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 22 Mar 2021 13:34:34 +0000 Subject: [PATCH 144/152] Spec update --- openapi/channels/paths/channels.id.yaml | 5 -- openapi/channels/paths/channels.yaml | 14 +---- .../channels/requests/CreateChannelBody.yaml | 5 +- openapi/global/models/Pagination.yaml | 13 +++- openapi/languages/paths/languages.yaml | 5 +- .../requests/CreateLanguageBody.yaml | 14 +++-- openapi/openapi.yaml | 59 ++++++++++++++++++- openapi/recycle-bin/models/RecycleBin.yaml | 4 ++ openapi/recycle-bin/paths/recycle-bin.id.yaml | 1 + openapi/recycle-bin/paths/recycle-bin.yaml | 4 ++ openapi/routes/paths/routes.yaml | 5 ++ .../versions/paths/versions.id.restore.yaml | 2 +- 12 files changed, 104 insertions(+), 27 deletions(-) diff --git a/openapi/channels/paths/channels.id.yaml b/openapi/channels/paths/channels.id.yaml index 720e8320b..d9129654f 100644 --- a/openapi/channels/paths/channels.id.yaml +++ b/openapi/channels/paths/channels.id.yaml @@ -25,11 +25,6 @@ get: $ref: '../../global/responses/ApiError.yaml' operationId: get-channels-channelId description: '' - parameters: - - schema: - type: string - in: query - name: includes tags: - Channels put: diff --git a/openapi/channels/paths/channels.yaml b/openapi/channels/paths/channels.yaml index 62d6b6ee4..18b5485c6 100644 --- a/openapi/channels/paths/channels.yaml +++ b/openapi/channels/paths/channels.yaml @@ -9,16 +9,8 @@ get: $ref: '../responses/ChannelCollection.yaml' operationId: get-channels parameters: - - schema: - type: string - in: query - name: includes - description: Comma separated includes for the resource - - schema: - type: number - in: query - name: per_page - description: How many results per page + - $ref: '../../openapi.yaml#/components/parameters/perPageParam' + - $ref: '../../openapi.yaml#/components/parameters/pageParam' description: Gets a paginated list of all channel tags: - Channels @@ -41,7 +33,7 @@ post: operationId: post-channels requestBody: content: - multipart/form-data: + application/json: schema: $ref: '../requests/CreateChannelBody.yaml' examples: {} diff --git a/openapi/channels/requests/CreateChannelBody.yaml b/openapi/channels/requests/CreateChannelBody.yaml index 8302cc0ce..147f7dde6 100644 --- a/openapi/channels/requests/CreateChannelBody.yaml +++ b/openapi/channels/requests/CreateChannelBody.yaml @@ -10,4 +10,7 @@ properties: nullable: true default: type: boolean - nullable: true \ No newline at end of file + nullable: true +required: + - name + - handle \ No newline at end of file diff --git a/openapi/global/models/Pagination.yaml b/openapi/global/models/Pagination.yaml index bbe5c72e9..375fe4d09 100644 --- a/openapi/global/models/Pagination.yaml +++ b/openapi/global/models/Pagination.yaml @@ -23,4 +23,15 @@ properties: type: integer nullable: true total: - type: integer \ No newline at end of file + type: integer + links: + type: array + items: + type: object + properties: + url: + type: string + label: + type: string + active: + type: boolean \ No newline at end of file diff --git a/openapi/languages/paths/languages.yaml b/openapi/languages/paths/languages.yaml index 5feb1786a..12bf41f95 100644 --- a/openapi/languages/paths/languages.yaml +++ b/openapi/languages/paths/languages.yaml @@ -11,6 +11,9 @@ get: $ref: '../responses/LanguageCollection.yaml' operationId: get-languages description: Returns a paginated list of Languages + parameters: + - $ref: '../../openapi.yaml#/components/parameters/perPageParam' + - $ref: '../../openapi.yaml#/components/parameters/pageParam' post: summary: Create Language tags: @@ -32,6 +35,6 @@ post: description: Create a new language requestBody: content: - multipart/form-data: + application/json: schema: $ref: '../requests/CreateLanguageBody.yaml' diff --git a/openapi/languages/requests/CreateLanguageBody.yaml b/openapi/languages/requests/CreateLanguageBody.yaml index 59d4336eb..78d56418f 100644 --- a/openapi/languages/requests/CreateLanguageBody.yaml +++ b/openapi/languages/requests/CreateLanguageBody.yaml @@ -3,18 +3,20 @@ type: object properties: name: type: string - lang: + example: 'English' + code: type: string - iso: - type: string - description: Unique + example: 'en-gb' + pattern: '^[a-zA-Z0-9-]*$' enabled: type: boolean + default: false default: type: boolean + default: false current: type: boolean + default: false required: - name - - lang - - iso \ No newline at end of file + - code \ No newline at end of file diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 166bd0736..5496a8f42 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1,4 +1,29 @@ openapi: 3.0.0 +components: + parameters: + includeParam: + in: query + name: include + required: false + schema: + type: string + description: Which related resources to include in the response. + perPageParam: + in: query + name: per_page + required: false + schema: + type: integer + minimum: 1 + description: The number of resources to show per page on the response + pageParam: + in: query + name: page + required: false + schema: + type: integer + minimum: 1 + description: The current page when returning resources. info: title: GetCandy contact: @@ -296,6 +321,38 @@ paths: $ref: './users/paths/users.current.yaml' '/versions/{modelId}/restore': $ref: './versions/paths/versions.id.restore.yaml' +tags: + - name: Routes + description: |- + ### Available Includes + | Include | Example | Description + | ----- | --- | --- | + | `element` | `element.variants` | The included element to return in the response. | + | `language` | | The language related to the route | + + When including `element` you will have access to all the available includes for that resource. For example if `element` was a Product, you would be able to also load `element.variants` + - name: Products + description: |- + ### Available Includes + | Include | Description | + | ----- | --- | + | `attributes` | The attributes related to the product | + | `draft` | The draft resource for the product (if it exists) | + | `layout` | The associated layout resource | + | `publishedParent` | If this product is a draft, this will be the published (live) version | + | `assets` | Any assets related to the product | + | `family` | The ProductFamily this product belongs to | + | `routes` | The Product's routes | + | `channels` | The channels related to the product | + | `firstVariant` | The first variant associated in the database, uses the first variant ID to determine | + | `primaryAsset` | Returns the primary asset if it exists | + | `categories` | Categories associated to the product | + | `variants` | The product variants | + | `discounts` | Any discounts available for this product | + | `collections` | Any collections this product is associated to | + | `associations` | Any product associations e.g. Cross Sell / Up Sell | + | `versions` | The past versions of this product | + | `customerGroups` | The associated customer groups | x-tagGroups: - name: General tags: @@ -339,4 +396,4 @@ x-tagGroups: - Shipping - name: Reports tags: - - Reports + - Reports \ No newline at end of file diff --git a/openapi/recycle-bin/models/RecycleBin.yaml b/openapi/recycle-bin/models/RecycleBin.yaml index d0042f489..8fa9e0e9d 100644 --- a/openapi/recycle-bin/models/RecycleBin.yaml +++ b/openapi/recycle-bin/models/RecycleBin.yaml @@ -3,13 +3,17 @@ type: object properties: id: type: string + example: gy6wjdwp1q type: type: string name: type: string + example: Marathon Bar thumbnail: type: string + example: "http:\/\/cdn.test/products\/1990\/11\/12\/thumbnails\/thumbnail_8exmokdn4wb79l51.jpg" deleted_at: type: string + example: "1990-07-19T14:24:37.000000Z" recyclable: type: object diff --git a/openapi/recycle-bin/paths/recycle-bin.id.yaml b/openapi/recycle-bin/paths/recycle-bin.id.yaml index e0f101d4a..b92d8afa6 100644 --- a/openapi/recycle-bin/paths/recycle-bin.id.yaml +++ b/openapi/recycle-bin/paths/recycle-bin.id.yaml @@ -2,6 +2,7 @@ parameters: - schema: type: string name: itemId + description: The ID representing the row in the `recycle_bin` table. in: path required: true get: diff --git a/openapi/recycle-bin/paths/recycle-bin.yaml b/openapi/recycle-bin/paths/recycle-bin.yaml index d49a9a4da..d57ddea64 100644 --- a/openapi/recycle-bin/paths/recycle-bin.yaml +++ b/openapi/recycle-bin/paths/recycle-bin.yaml @@ -2,6 +2,10 @@ get: summary: Get records tags: - Recycle Bin + parameters: + - $ref: '../../openapi.yaml#/components/parameters/perPageParam' + - $ref: '../../openapi.yaml#/components/parameters/pageParam' + - $ref: '../../openapi.yaml#/components/parameters/includeParam' responses: '200': description: OK diff --git a/openapi/routes/paths/routes.yaml b/openapi/routes/paths/routes.yaml index d91d07575..50069a180 100644 --- a/openapi/routes/paths/routes.yaml +++ b/openapi/routes/paths/routes.yaml @@ -9,6 +9,11 @@ get: application/json: schema: $ref: '../responses/RouteCollection.yaml' + + parameters: + - $ref: '../../openapi.yaml#/components/parameters/perPageParam' + - $ref: '../../openapi.yaml#/components/parameters/pageParam' + - $ref: '../../openapi.yaml#/components/parameters/includeParam' operationId: get-routes description: Returns a paginated list of Routes post: diff --git a/openapi/versions/paths/versions.id.restore.yaml b/openapi/versions/paths/versions.id.restore.yaml index 7176f0f7d..be4aef050 100644 --- a/openapi/versions/paths/versions.id.restore.yaml +++ b/openapi/versions/paths/versions.id.restore.yaml @@ -1,7 +1,7 @@ parameters: - schema: type: string - name: modelId + name: versionId in: path required: true post: From cffbc4320f1875e81ef94d1b03480e87d2b70dfa Mon Sep 17 00:00:00 2001 From: Alec Date: Mon, 22 Mar 2021 14:13:48 +0000 Subject: [PATCH 145/152] Fix tests --- openapi/global/models/Pagination.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openapi/global/models/Pagination.yaml b/openapi/global/models/Pagination.yaml index 375fe4d09..3b4e3287e 100644 --- a/openapi/global/models/Pagination.yaml +++ b/openapi/global/models/Pagination.yaml @@ -31,7 +31,10 @@ properties: properties: url: type: string + nullable: true label: type: string + nullable: true active: - type: boolean \ No newline at end of file + type: boolean + nullable: true \ No newline at end of file From 1829b898ba8a485da88e3afc2e33eebe6d7d2d4c Mon Sep 17 00:00:00 2001 From: Alec Date: Tue, 23 Mar 2021 11:23:49 +0000 Subject: [PATCH 146/152] Add activity log spec update --- openapi/activity-log/models/ActivityLog.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openapi/activity-log/models/ActivityLog.yaml b/openapi/activity-log/models/ActivityLog.yaml index e9a50190c..c0fdd96fe 100644 --- a/openapi/activity-log/models/ActivityLog.yaml +++ b/openapi/activity-log/models/ActivityLog.yaml @@ -3,13 +3,21 @@ type: object properties: id: type: string + example: 181398 type: type: string + example: 'default' description: type: string + example: 'status-update' properties: - type: string + type: object + example: '{ + "previous": "payment-received", + "new": "refunded" + }' created_at: type: string + example: '2020-03-24T10:11:12.000000Z' user: $ref: '../../users/responses/UserResponse.yaml' From 7fe420c1aefde952220a52828a8e8b2baab20fa3 Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 24 Mar 2021 10:26:13 +0000 Subject: [PATCH 147/152] Merge in master --- .github/workflows/github-readme-sync.yaml | 22 ------- CHANGELOG.md | 64 +------------------ README.md | 2 +- ...021_03_17_144530_refactor_routes_table.php | 10 ++- openapi/openapi.yaml | 2 +- .../Categories/Drafting/CategoryDrafter.php | 6 +- .../Categories/Observers/CategoryObserver.php | 3 + .../Categories/Services/CategoryService.php | 14 +++- src/Core/Customers/Models/Customer.php | 2 +- src/Core/Drafting/Actions/PublishRoutes.php | 2 +- src/Core/Products/Drafting/ProductDrafter.php | 6 +- src/Core/Products/Services/ProductService.php | 13 +++- .../RecycleBin/Services/RecycleBinService.php | 10 +-- src/Core/Routes/Resources/RouteResource.php | 2 + .../Controllers/Routes/RouteController.php | 8 +-- .../Resources/Categories/CategoryResource.php | 10 +-- .../RecycleBin/RecycleBinResource.php | 2 +- src/Installer/Runners/UserRunner.php | 12 +++- 18 files changed, 75 insertions(+), 115 deletions(-) delete mode 100644 .github/workflows/github-readme-sync.yaml diff --git a/.github/workflows/github-readme-sync.yaml b/.github/workflows/github-readme-sync.yaml deleted file mode 100644 index f72088356..000000000 --- a/.github/workflows/github-readme-sync.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: Sync OAS to ReadMe -on: - push: - branches: - - master -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta - with: - node-version: '12' - - run: npm install -g speccy - - run: speccy resolve openapi/openapi.yaml -o openapi/openapi-full.yaml - - run: npm install rdme -g - - run: rdme swagger openapi/openapi-full.yaml --version=v1.0 --key=${{ secrets.README_OAS_KEY }} --id=5f3af1461c57aa9fb9721010 -# - uses: readmeio/github-readme-sync@2.0.0 -# with: -# readme-oas-key: ${{ secrets.README_OAS_KEY }} -# oas-file-path: openapi/openapi-full.yaml -# api-version: 'v1.0.0' diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e524c63d..66da73197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,68 +2,7 @@ ## 0.12.0 ### Upgrading -Update the composer package - -```bash -$ composer update @getcandy/candy-api -``` - -```bash -$ php artisan migrate -``` - -### High Impact Changes - -#### Maintenance Migrations - -Some columns have been added/removed from the database. The tables/columns affected are: - -- `orders` - - Removed `company_name` column as it wasn't being used and we have other columns for that now - - Added `billing_company_name` and `shipping_company_name` columns. -- `countries` - - Remove `country` column in favour of a `country_id` relationship - -#### Eager loading relations for the current user - -Previously when returning the current user via `users/current` there was some hard coded includes, this has been replaced to allow the `include` query parameter. -You should update any calls to this endpoint if you rely on included resources. The previous default includes were: - -```php -['addresses.country', 'roles.permissions', 'customer', 'savedBaskets.basket.lines'] -``` - -### Drafting has changed - -The way drafting previously worked has now been refactored to be less destructive. You should reindex your products before going back into the hub to get everything in sync. - -You can do this by running `php artisan candy:products:reindex` and `php artisan candy:categories:reindex`H - -### Route searching - -The way you search for routes has changed on the API. We have removed the `path` column and also the `locale` column in favour of a `language_id` relation. - -When you search for a route, previously you would do something like: -```javascript -const { data } = await axios('routes/search', { - params: { - slug: 'slug-for-the-product', - path: null, - include: 'element' - } -}) -``` -This should now be changed to: -```javascript -const { data } = await axios('routes/search', { - params: { - slug: 'slug-for-the-product', - language_code: 'en', - element_type: 'product', - include: 'element' - } -}) -``` +For a full guide on how to upgrade, see the [full documentation](https://docs.getcandy.io/api/prologue/upgrading.html#v0-12). ### 🐞 Fixes - Fixed an issue that was causing a indefinite wildcard search on products @@ -75,6 +14,7 @@ const { data } = await axios('routes/search', { - Fixed and issue where the indexable event wasn't being triggered when publishing a resource - Fixes to drafting and publishing of resources - Fixed an issue where `path` wasn't updating when updating a route +- Fixed an issue where the customer was not attached to the initial user on install ### ⭐ Improvements diff --git a/README.md b/README.md index f3e77c4f5..b7dc58a3a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Build amazing online stores with full control over functionality and user experi ## 📖 Documentation -For installation instructions and usage details, please take a look at the **[official guides](https://guides.getcandy.io/)**. +For installation instructions and usage details, please take a look at the **[official guides](https://docs.getcandy.io/)**. ## 📄 License GetCandy API is open-sourced software licensed under the [Apache License 2.0](LICENSE) diff --git a/database/migrations/2021_03_17_144530_refactor_routes_table.php b/database/migrations/2021_03_17_144530_refactor_routes_table.php index c99ba1887..9480813a5 100644 --- a/database/migrations/2021_03_17_144530_refactor_routes_table.php +++ b/database/migrations/2021_03_17_144530_refactor_routes_table.php @@ -14,7 +14,15 @@ class RefactorRoutesTable extends Migration public function up() { $languages = DB::table('routes')->groupBy('locale')->pluck('locale')->mapWithKeys(function ($locale) { - return [$locale => Language::whereCode($locale)->first()]; + $language = Language::whereCode($locale)->first(); + + // If we can't find a language, then we use the first one we can get hold of. + // Routes will need a language id. + if (!$language) { + $language = Language::whereDefault(true)->first(); + } + + return [$locale => $language]; }); Schema::table('routes', function (Blueprint $table) { diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 5496a8f42..24db33ad1 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -396,4 +396,4 @@ x-tagGroups: - Shipping - name: Reports tags: - - Reports \ No newline at end of file + - Reports diff --git a/src/Core/Categories/Drafting/CategoryDrafter.php b/src/Core/Categories/Drafting/CategoryDrafter.php index b1624e22d..f346c5493 100644 --- a/src/Core/Categories/Drafting/CategoryDrafter.php +++ b/src/Core/Categories/Drafting/CategoryDrafter.php @@ -80,7 +80,8 @@ public function create(Model $parent) 'children', 'products', 'channels', - 'routes', + 'routes.publishedParent', + 'routes.draft', 'assets', 'customerGroups', 'attributes', @@ -114,7 +115,8 @@ public function create(Model $parent) 'children', 'products', 'channels', - 'routes', + 'routes.publishedParent', + 'routes.draft', 'assets', 'customerGroups', 'attributes', diff --git a/src/Core/Categories/Observers/CategoryObserver.php b/src/Core/Categories/Observers/CategoryObserver.php index a2e02ada4..88d8c74fd 100644 --- a/src/Core/Categories/Observers/CategoryObserver.php +++ b/src/Core/Categories/Observers/CategoryObserver.php @@ -29,6 +29,9 @@ public function __construct(AssetService $assets, SearchManager $search) */ public function deleted(Category $category) { + $category->channels()->detach(); + $category->assets()->detach(); + $category->routes()->forceDelete(); $driver = $this->search->with(config('getcandy.search.driver')); $driver->delete($category); } diff --git a/src/Core/Categories/Services/CategoryService.php b/src/Core/Categories/Services/CategoryService.php index 4887a91dd..7af1f8e59 100644 --- a/src/Core/Categories/Services/CategoryService.php +++ b/src/Core/Categories/Services/CategoryService.php @@ -9,6 +9,8 @@ use GetCandy\Api\Core\Channels\Models\Channel; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroup; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; +use GetCandy\Api\Core\Routes\Actions\CreateRoute; use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Search\Actions\IndexObjects; @@ -107,9 +109,15 @@ public function create(array $data) ]); } - $urls = $this->getUniqueUrl($data['url'], $data['path'] ?? null); - - $category->routes()->createMany($urls); + $language = FetchDefaultLanguage::run(); + CreateRoute::run([ + 'element_type' => Category::class, + 'element_id' => $category->encoded_id, + 'language_id' => $language->encoded_id, + 'slug' => $data['url'], + 'default' => true, + 'redirect' => false, + ]); // If a parent id exists then add the category to the parent if (! empty($data['parent']['id'])) { diff --git a/src/Core/Customers/Models/Customer.php b/src/Core/Customers/Models/Customer.php index 761817b38..bd993e6bc 100644 --- a/src/Core/Customers/Models/Customer.php +++ b/src/Core/Customers/Models/Customer.php @@ -24,7 +24,7 @@ public function getFieldsAttribute($val) public function customerGroups() { - return $this->belongsToMany(CustomerGroup::class); + return $this->belongsToMany(CustomerGroup::class)->withTimestamps(); } public function getFullNameAttribute() diff --git a/src/Core/Drafting/Actions/PublishRoutes.php b/src/Core/Drafting/Actions/PublishRoutes.php index 0992c7fa5..2a33c2487 100644 --- a/src/Core/Drafting/Actions/PublishRoutes.php +++ b/src/Core/Drafting/Actions/PublishRoutes.php @@ -40,7 +40,7 @@ public function handle() // dd($route->publishedParent); if ($route->publishedParent) { $route->publishedParent->update( - $route->only(['default', 'redirect', 'slug', 'locale', 'description', 'path']) + $route->only(['default', 'redirect', 'slug', 'language_id', 'description']) ); $route->forceDelete(); // dd($route); diff --git a/src/Core/Products/Drafting/ProductDrafter.php b/src/Core/Products/Drafting/ProductDrafter.php index 0fb6bb4ad..779b242e9 100644 --- a/src/Core/Products/Drafting/ProductDrafter.php +++ b/src/Core/Products/Drafting/ProductDrafter.php @@ -30,7 +30,8 @@ public function create(Model $parent) $parent = $parent->load([ 'variants', 'categories', - 'routes', + 'routes.publishedParent', + 'routes.draft', 'channels', 'customerGroups', ]); @@ -62,7 +63,8 @@ public function create(Model $parent) return $draft->refresh()->load([ 'variants.publishedParent', 'categories', - 'routes', + 'routes.publishedParent', + 'routes.draft', 'channels', 'customerGroups', ]); diff --git a/src/Core/Products/Services/ProductService.php b/src/Core/Products/Services/ProductService.php index 6acd6ad51..5df8c17d2 100644 --- a/src/Core/Products/Services/ProductService.php +++ b/src/Core/Products/Services/ProductService.php @@ -7,11 +7,13 @@ use GetCandy\Api\Core\Channels\Models\Channel; use GetCandy\Api\Core\Customers\Actions\FetchCustomerGroups; use GetCandy\Api\Core\Customers\Models\CustomerGroup; +use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; use GetCandy\Api\Core\Products\Actions\FetchProductFamily; use GetCandy\Api\Core\Products\Events\ProductCreatedEvent; use GetCandy\Api\Core\Products\Interfaces\ProductInterface; use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\Products\Models\ProductRecommendation; +use GetCandy\Api\Core\Routes\Actions\CreateRoute; use GetCandy\Api\Core\Scaffold\BaseService; use GetCandy\Api\Core\Scopes\CustomerGroupScope; use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; @@ -212,8 +214,15 @@ public function create(array $data) })->toArray()); } - $urls = $this->getUniqueUrl($data['url']); - $product->routes()->createMany($urls); + $language = FetchDefaultLanguage::run(); + CreateRoute::run([ + 'element_type' => Product::class, + 'element_id' => $product->encoded_id, + 'language_id' => $language->encoded_id, + 'slug' => $data['url'], + 'default' => true, + 'redirect' => false, + ]); $sku = $data['sku']; $i = 1; diff --git a/src/Core/RecycleBin/Services/RecycleBinService.php b/src/Core/RecycleBin/Services/RecycleBinService.php index 0350d60d3..b9bffd0b7 100644 --- a/src/Core/RecycleBin/Services/RecycleBinService.php +++ b/src/Core/RecycleBin/Services/RecycleBinService.php @@ -2,12 +2,12 @@ namespace GetCandy\Api\Core\RecycleBin\Services; -use GetCandy\Api\Core\Products\Models\Product; -use GetCandy\Api\Core\RecycleBin\Models\RecycleBin; -use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; use GetCandy\Api\Core\Channels\Actions\FetchCurrentChannel; use GetCandy\Api\Core\Languages\Actions\FetchDefaultLanguage; +use GetCandy\Api\Core\Products\Models\Product; use GetCandy\Api\Core\RecycleBin\Interfaces\RecycleBinServiceInterface; +use GetCandy\Api\Core\RecycleBin\Models\RecycleBin; +use GetCandy\Api\Core\Search\Events\IndexableSavedEvent; class RecycleBinService implements RecycleBinServiceInterface { @@ -25,9 +25,9 @@ public function getItems($paginated = true, $perPage = 25, $term = null, $includ $channel = FetchCurrentChannel::run(); $language = FetchDefaultLanguage::run(); $query = RecycleBin::whereHasMorph('recyclable', [ - Product::class + Product::class, ], function ($query, $type) use ($term, $channel, $language) { - if (!$term) { + if (! $term) { return; } if ($type == Product::class) { diff --git a/src/Core/Routes/Resources/RouteResource.php b/src/Core/Routes/Resources/RouteResource.php index 94d2bf66d..4ba38dec5 100644 --- a/src/Core/Routes/Resources/RouteResource.php +++ b/src/Core/Routes/Resources/RouteResource.php @@ -24,6 +24,8 @@ public function includes() { return [ 'language' => $this->include('language', LanguageResource::class), + 'draft' => $this->include('draft', self::class), + 'published_parent' => $this->include('publishedParent', self::class), 'element' => $this->whenLoaded('element', function () { // Need to guess the element $class = class_basename(get_class($this->element)); diff --git a/src/Http/Controllers/Routes/RouteController.php b/src/Http/Controllers/Routes/RouteController.php index 56ea0f874..968479bfb 100644 --- a/src/Http/Controllers/Routes/RouteController.php +++ b/src/Http/Controllers/Routes/RouteController.php @@ -3,14 +3,14 @@ namespace GetCandy\Api\Http\Controllers\Routes; use GetCandy; -use Illuminate\Http\Request; +use GetCandy\Api\Core\Routes\Resources\RouteCollection; +use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Routes\RouteCriteria; +use GetCandy\Api\Exceptions\MinimumRecordRequiredException; use GetCandy\Api\Http\Controllers\BaseController; use GetCandy\Api\Http\Requests\Routes\UpdateRequest; -use GetCandy\Api\Core\Routes\Resources\RouteResource; -use GetCandy\Api\Core\Routes\Resources\RouteCollection; use Illuminate\Database\Eloquent\ModelNotFoundException; -use GetCandy\Api\Exceptions\MinimumRecordRequiredException; +use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class RouteController extends BaseController diff --git a/src/Http/Resources/Categories/CategoryResource.php b/src/Http/Resources/Categories/CategoryResource.php index 4b1aa9ccc..6ce8690fd 100644 --- a/src/Http/Resources/Categories/CategoryResource.php +++ b/src/Http/Resources/Categories/CategoryResource.php @@ -2,16 +2,16 @@ namespace GetCandy\Api\Http\Resources\Categories; -use GetCandy\Api\Http\Resources\AbstractResource; -use GetCandy\Api\Http\Resources\Assets\AssetResource; +use GetCandy\Api\Core\Channels\Resources\ChannelCollection; +use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; use GetCandy\Api\Core\Routes\Resources\RouteCollection; +use GetCandy\Api\Http\Resources\AbstractResource; use GetCandy\Api\Http\Resources\Assets\AssetCollection; +use GetCandy\Api\Http\Resources\Assets\AssetResource; +use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; use GetCandy\Api\Http\Resources\Layouts\LayoutResource; -use GetCandy\Api\Core\Channels\Resources\ChannelCollection; use GetCandy\Api\Http\Resources\Products\ProductCollection; use GetCandy\Api\Http\Resources\Versioning\VersionCollection; -use GetCandy\Api\Http\Resources\Attributes\AttributeCollection; -use GetCandy\Api\Core\Customers\Resources\CustomerGroupCollection; class CategoryResource extends AbstractResource { diff --git a/src/Http/Resources/RecycleBin/RecycleBinResource.php b/src/Http/Resources/RecycleBin/RecycleBinResource.php index 4382246c5..cceeb5910 100644 --- a/src/Http/Resources/RecycleBin/RecycleBinResource.php +++ b/src/Http/Resources/RecycleBin/RecycleBinResource.php @@ -16,7 +16,7 @@ public function toArray($request) 'thumbnail' => $this->recyclable->getRecycleThumbnail(), 'deleted_at' => $this->recyclable->deleted_at, 'recyclable' => $this->whenLoaded('recyclable', [ - 'data' => new DynamicResource($this->recyclable) + 'data' => new DynamicResource($this->recyclable), ]), ]; } diff --git a/src/Installer/Runners/UserRunner.php b/src/Installer/Runners/UserRunner.php index c18c2f6c3..263575a8f 100644 --- a/src/Installer/Runners/UserRunner.php +++ b/src/Installer/Runners/UserRunner.php @@ -3,6 +3,8 @@ namespace GetCandy\Api\Installer\Runners; use DB; +use GetCandy\Api\Core\Customers\Models\Customer; +use GetCandy\Api\Core\Customers\Models\CustomerGroup; use GetCandy\Api\Installer\Contracts\InstallRunnerContract; use Spatie\Permission\Models\Role; @@ -61,12 +63,18 @@ protected function setUpUser($model) 'email' => $email, ]); - $user->save(); - $user->customer()->updateOrCreate([ + $defaultCustomerGroup = CustomerGroup::whereDefault(true)->first(); + + $customer = Customer::create([ 'firstname' => $nameParts[0], 'lastname' => $nameParts[1] ?? null, ]); + $customer->customerGroups()->attach($defaultCustomerGroup); + + $user->customer_id = $customer->id; + $user->save(); + return $user; } From a67ecfa03ce1bc98913ea5f697de7266174e944c Mon Sep 17 00:00:00 2001 From: Alec Date: Wed, 24 Mar 2021 10:46:10 +0000 Subject: [PATCH 148/152] Add recyclebin and model examples --- openapi/assets/models/Asset.yaml | 16 ++++++++++++++++ openapi/countries/models/Country.yaml | 10 +++++++++- openapi/currencies/models/Currency.yaml | 15 ++++++++++++--- openapi/openapi.yaml | 2 ++ .../paths/recycle-bin.id.restore.yaml | 16 ++++++++++++++++ openapi/settings/models/Setting.yaml | 9 +++++++++ openapi/tags/models/Tag.yaml | 2 ++ 7 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 openapi/recycle-bin/paths/recycle-bin.id.restore.yaml diff --git a/openapi/assets/models/Asset.yaml b/openapi/assets/models/Asset.yaml index 1c4729e48..0c81a3063 100644 --- a/openapi/assets/models/Asset.yaml +++ b/openapi/assets/models/Asset.yaml @@ -3,34 +3,50 @@ type: object properties: id: type: string + example: 1w2ndkgo title: type: string + example: Olive clog boots type: type: string + nullable: true + example: product caption: type: string + example: Olive clog boots kind: type: string + example: image external: type: boolean + example: false position: type: integer + example: 1 primary: type: boolean + example: true url: type: string + example: http:\/\/cdn.test\/storage\/products\/2020\/01\/SBBsDXmM8pg.jpg sub_kind: type: string + example: jpeg extension: type: string + example: 'jpg' original_filename: type: string + example: 'B15Z31__58612_std.jpg' size: type: string + example: 88408 width: type: string + example: 601 height: type: string + example: 1000 transforms: $ref: '../responses/AssetTransformCollection.yaml' tags: diff --git a/openapi/countries/models/Country.yaml b/openapi/countries/models/Country.yaml index 7af878fa9..0b4551034 100644 --- a/openapi/countries/models/Country.yaml +++ b/openapi/countries/models/Country.yaml @@ -3,17 +3,25 @@ type: object properties: id: type: string + example: v8l4q190 name: type: string + example: United Kingdom region: type: string + example: Europe iso_a_2: type: string + example: GB iso_a_3: type: string + example: GBR iso_numeric: type: string + example: 826 preferred: type: boolean + example: true enabled: - type: boolean \ No newline at end of file + type: boolean + example: true \ No newline at end of file diff --git a/openapi/currencies/models/Currency.yaml b/openapi/currencies/models/Currency.yaml index 81c690132..1e1db5adf 100644 --- a/openapi/currencies/models/Currency.yaml +++ b/openapi/currencies/models/Currency.yaml @@ -3,19 +3,28 @@ type: object properties: id: type: string - code: - type: string + example: kdj58jom name: type: string + example: British Pound + code: + type: string + example: GBP enabled: type: boolean + example: true format: type: string + example: £{price} exchange_rate: type: string + example: 1 decimal_point: type: string + example: '.' thousand_point: type: string + example: ',' default: - type: boolean \ No newline at end of file + type: boolean + example: true \ No newline at end of file diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 24db33ad1..500a51a53 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -251,6 +251,8 @@ paths: $ref: './recycle-bin/paths/recycle-bin.yaml' '/recycle-bin/{itemId}': $ref: './recycle-bin/paths/recycle-bin.id.yaml' + '/recycle-bin/{itemId}/restore': + $ref: './recycle-bin/paths/recycle-bin.id.restore.yaml' '/reports/sales': $ref: './reports/paths/reports.sales.yaml' '/reports/customers/spending': diff --git a/openapi/recycle-bin/paths/recycle-bin.id.restore.yaml b/openapi/recycle-bin/paths/recycle-bin.id.restore.yaml new file mode 100644 index 000000000..b8e2ce0e4 --- /dev/null +++ b/openapi/recycle-bin/paths/recycle-bin.id.restore.yaml @@ -0,0 +1,16 @@ +parameters: + - schema: + type: string + name: itemId + description: The ID representing the row in the `recycle_bin` table. + in: path + required: true +post: + summary: Restore an item from the recyle bin + tags: + - Recycle Bin + responses: + '200': + description: No Content + operationId: restore-item + description: Restore an item from the recyle bin \ No newline at end of file diff --git a/openapi/settings/models/Setting.yaml b/openapi/settings/models/Setting.yaml index ce6c10c7d..633d8c7f3 100644 --- a/openapi/settings/models/Setting.yaml +++ b/openapi/settings/models/Setting.yaml @@ -3,12 +3,21 @@ type: object properties: id: type: string + example: v8l4pl01 name: type: string + example: Products handle: type: string + example: products content: type: object + example: { + "transforms": [ + "large_thumbnail" + ], + "asset_source": "products" + } x-examples: products-example: id: v8l4pl01 diff --git a/openapi/tags/models/Tag.yaml b/openapi/tags/models/Tag.yaml index ce482ff10..7c0ef6498 100644 --- a/openapi/tags/models/Tag.yaml +++ b/openapi/tags/models/Tag.yaml @@ -3,5 +3,7 @@ type: object properties: id: type: string + example: 1w2ndkgo name: type: string + example: download From df86513c6282b7c0f1118bf130d3b46726ecf5ca Mon Sep 17 00:00:00 2001 From: Alec Date: Thu, 25 Mar 2021 10:36:22 +0000 Subject: [PATCH 149/152] Update spec --- openapi/categories/models/Category.yaml | 115 +++------------------ openapi/collections/models/Collection.yaml | 12 +-- openapi/openapi.yaml | 38 +++++++ openapi/routes/models/Route.yaml | 28 +++++ 4 files changed, 84 insertions(+), 109 deletions(-) diff --git a/openapi/categories/models/Category.yaml b/openapi/categories/models/Category.yaml index 12a929cf3..bbb88a26f 100644 --- a/openapi/categories/models/Category.yaml +++ b/openapi/categories/models/Category.yaml @@ -1,120 +1,37 @@ title: Category type: object -x-examples: - Standard with mapped attributes: - id: v8l4pl01 - sort: 'min_price:asc' - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - description: Category description - meta_keywords: null - name: Fasteners - page_title: Seo page title - short_description: Short Description - meta_description: Page meta description - layout: [] - primary_asset: [] - Full response: - id: v8l4pl01 - sort: 'min_price:asc' - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - attribute_data: - description: - webstore: - en: Category description - meta_keywords: - webstore: - en: "" - name: - webstore: - en: Category name - page_title: - webstore: - en: Page title - short_description: - webstore: - en: Short description - meta_description: - webstore: - en: Meta description - layout: [] - primary_asset: [] - Full response with child: - id: v8l4pl01 - sort: 'min_price:asc' - products_count: 0 - children_count: 1 - left_pos: 1 - right_pos: 938 - attribute_data: - description: - webstore: - en: Category description - meta_keywords: - webstore: - en: "" - name: - webstore: - en: Category name - page_title: - webstore: - en: Page title - short_description: - webstore: - en: Short description - meta_description: - webstore: - en: Meta description - children: - data: - - id: p09prlrn - sort: 'min_price:asc' - left_pos: 2 - right_pos: 937 - attribute_data: - description: - webstore: - en: '' - meta_keywords: - webstore: - en: '' - name: - webstore: - en: Child Category - page_title: - webstore: - en: Child category SEO title - short_description: - webstore: - en: '' - meta_description: - webstore: - en: Child Category meta description - layout: [] - primary_asset: [] - layout: [] - primary_asset: [] description: '' properties: id: type: string + example: v8l4pl01 sort: type: string + example: min_price:asc products_count: type: integer + example: 778 children_count: type: integer + example: 15 + depth: + type: integer + example: 0 left_pos: type: integer + example: 1 right_pos: type: integer + example: 2 + created_at: + type: string + example: "2020-11-19T10:28:56.000000Z" name: type: string + example: Fashion + description: + type: string + example: "

Discover early autumn fashion. Cotton silk leopard smock dresses. Deep v-neck knitted tank tops. Black contrast stitch denim jeans. Khaki quilted jackets with tortoiseshell toggle buttons. Tobacco gingham cotton dresses. Shop all of fashion below.<\/p>" attribute_data: type: object children: diff --git a/openapi/collections/models/Collection.yaml b/openapi/collections/models/Collection.yaml index e505d0e51..7102c2b0d 100644 --- a/openapi/collections/models/Collection.yaml +++ b/openapi/collections/models/Collection.yaml @@ -1,18 +1,10 @@ title: Collection type: object -description: |- - ### Available includes - - - routes - - layout - - channels - - assets - - attributes - - products - - customerGroups +description: properties: id: type: string + example: v8l4pl01 attribute_data: type: object routes: diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 500a51a53..0a180ab37 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -324,6 +324,43 @@ paths: '/versions/{modelId}/restore': $ref: './versions/paths/versions.id.restore.yaml' tags: + - name: Assets + description: |- + ### Available Includes + | Include | Description | + | ----- | --- | + | `transforms` | Any asset transforms, such as thumbnails | + | `tags` | Any associated tags | + - name: Categories + description: |- + ### Available Includes + | Include | Description | + | ----- | --- | + | `attributes` | The attributes related to the product | + | `assets` | The assets related to the category | + | `children` | Any child categories | + | `draft` | The draft resource for the category (if it exists) | + | `layout` | The associated layout resource | + | `publishedParent` | If this category is a draft, this will be the published (live) version | + | `assets` | Any assets related to the product | + | `routes` | The category's routes | + | `channels` | The channels related to the category | + | `primaryAsset` | Returns the primary asset if it exists | + | `versions` | The past versions of this category | + | `customerGroups` | The associated customer groups | + - name: Collections + description: |- + ### Available Includes + | Include | Description | + | ----- | --- | + | `attributes` | The attributes related to the collection | + | `assets` | The assets related to the collection | + | `routes` | The collection's routes | + | `layout` | The layout assigned to the collection | + | `channels` | The channels related to the collection | + | `assets` | The channels related to the collection | + | `customerGroups` | The associated customer groups | + | `products` | The products that belong to the collection | - name: Routes description: |- ### Available Includes @@ -339,6 +376,7 @@ tags: | Include | Description | | ----- | --- | | `attributes` | The attributes related to the product | + | `assets` | The assets related to the product | | `draft` | The draft resource for the product (if it exists) | | `layout` | The associated layout resource | | `publishedParent` | If this product is a draft, this will be the published (live) version | diff --git a/openapi/routes/models/Route.yaml b/openapi/routes/models/Route.yaml index 632f0bc4d..0b6946cc8 100644 --- a/openapi/routes/models/Route.yaml +++ b/openapi/routes/models/Route.yaml @@ -3,6 +3,7 @@ description: |- properties: id: type: string + example: v8l4pl01 default: type: boolean default: false @@ -11,14 +12,41 @@ properties: default: false slug: type: string + example: fashion description: type: string nullable: true + example: "Fashion clothes" type: type: string + example: category drafted_at: type: string nullable: true + example: "2020-11-19T10:28:56.000000Z" + element: + type: object + example: { + "data": { + "id": "v8l4pl01", + "sort": "min_price:asc", + "drafted_at": null, + "products_count": 0, + "children_count": 0, + "depth": 0, + "has_draft": false, + "left_pos": 1, + "right_pos": 2, + "created_at": "2020-11-19T10:28:56.000000Z", + "updated_at": "2021-02-15T10:15:12.000000Z", + "name": "Fashion", + "description": "

Discover early autumn fashion. Cotton silk leopard smock dresses. Deep v-neck knitted tank tops. Black contrast stitch denim jeans. Khaki quilted jackets with tortoiseshell toggle buttons. Tobacco gingham cotton dresses. Shop all of fashion below.<\/p>", + "draft": [], + "published_parent": [], + "layout": [], + "primary_asset": [] + } + } x-tags: - Routes type: object \ No newline at end of file From e8130fd865d6274add2b4a6923e09545d9020a40 Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 26 Mar 2021 12:51:14 +0000 Subject: [PATCH 150/152] Fix to api spec formatting --- openapi/activity-log/models/ActivityLog.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi/activity-log/models/ActivityLog.yaml b/openapi/activity-log/models/ActivityLog.yaml index c0fdd96fe..7e9618d16 100644 --- a/openapi/activity-log/models/ActivityLog.yaml +++ b/openapi/activity-log/models/ActivityLog.yaml @@ -12,10 +12,10 @@ properties: example: 'status-update' properties: type: object - example: '{ + example: { "previous": "payment-received", "new": "refunded" - }' + } created_at: type: string example: '2020-03-24T10:11:12.000000Z' From 806974c1b25a1453bc16ffeab9d35035f7ab6525 Mon Sep 17 00:00:00 2001 From: Alec Date: Fri, 26 Mar 2021 13:23:56 +0000 Subject: [PATCH 151/152] Update spec for channels --- openapi/channels/paths/channels.id.yaml | 6 +----- openapi/channels/paths/channels.yaml | 1 - .../channels/requests/CreateChannelBody.yaml | 4 ++++ .../channels/requests/UpdateChannelBody.yaml | 6 +++++- openapi/global/models/Pagination.yaml | 18 ++++++++++++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/openapi/channels/paths/channels.id.yaml b/openapi/channels/paths/channels.id.yaml index d9129654f..c9e073561 100644 --- a/openapi/channels/paths/channels.id.yaml +++ b/openapi/channels/paths/channels.id.yaml @@ -6,10 +6,6 @@ parameters: required: true get: summary: Get the channel resource - x-code-samples: - - lang: PHP - source: - $ref: ../examples/code/fetch-channel/laravel.php responses: '200': description: OK @@ -45,7 +41,7 @@ put: operationId: put-channels-channelId requestBody: content: - multipart/form-data: + application/json: schema: $ref: '../requests/UpdateChannelBody.yaml' description: '' diff --git a/openapi/channels/paths/channels.yaml b/openapi/channels/paths/channels.yaml index 18b5485c6..c3637df40 100644 --- a/openapi/channels/paths/channels.yaml +++ b/openapi/channels/paths/channels.yaml @@ -36,7 +36,6 @@ post: application/json: schema: $ref: '../requests/CreateChannelBody.yaml' - examples: {} description: '' tags: - Channels diff --git a/openapi/channels/requests/CreateChannelBody.yaml b/openapi/channels/requests/CreateChannelBody.yaml index 147f7dde6..41de96888 100644 --- a/openapi/channels/requests/CreateChannelBody.yaml +++ b/openapi/channels/requests/CreateChannelBody.yaml @@ -3,14 +3,18 @@ type: object properties: handle: type: string + example: "mobileapp" name: type: string + example: "Mobile App" url: type: string nullable: true + example: "mobileapp:Index" default: type: boolean nullable: true + default: false required: - name - handle \ No newline at end of file diff --git a/openapi/channels/requests/UpdateChannelBody.yaml b/openapi/channels/requests/UpdateChannelBody.yaml index 71b3f4406..b3e19a27c 100644 --- a/openapi/channels/requests/UpdateChannelBody.yaml +++ b/openapi/channels/requests/UpdateChannelBody.yaml @@ -4,12 +4,16 @@ properties: handle: type: string nullable: true + example: "webstore" name: type: string nullable: true + example: "Webstore" url: type: string nullable: true + example: "localhost:8080" default: type: boolean - nullable: true \ No newline at end of file + nullable: true + example: true \ No newline at end of file diff --git a/openapi/global/models/Pagination.yaml b/openapi/global/models/Pagination.yaml index 3b4e3287e..db50c254a 100644 --- a/openapi/global/models/Pagination.yaml +++ b/openapi/global/models/Pagination.yaml @@ -16,6 +16,7 @@ properties: example: 15 path: type: string + example: http:\/\/api.test\/api\/v1\/channels per_page: type: integer example: 1 @@ -26,6 +27,23 @@ properties: type: integer links: type: array + example: [ + { + "url": null, + "label": "« Previous", + "active": false + }, + { + "url": "http:\/\/api.test\/api\/v1\/channels?page=1", + "label": "1", + "active": true + }, + { + "url": null, + "label": "Next »", + "active": false + } + ] items: type: object properties: From 99f32c88042a8a192ecb662e63b67cace56a8179 Mon Sep 17 00:00:00 2001 From: Glenn Jacobs Date: Thu, 10 Jun 2021 12:19:07 +0100 Subject: [PATCH 152/152] Update SearchForRoute.php Random import removed, as it's not needed --- src/Core/Routes/Actions/SearchForRoute.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Routes/Actions/SearchForRoute.php b/src/Core/Routes/Actions/SearchForRoute.php index 4cbf17769..84e54eddf 100644 --- a/src/Core/Routes/Actions/SearchForRoute.php +++ b/src/Core/Routes/Actions/SearchForRoute.php @@ -2,7 +2,6 @@ namespace GetCandy\Api\Core\Routes\Actions; -use GetCandy\Api\Core\Languages\Models\Language; use GetCandy\Api\Core\Routes\Models\Route; use GetCandy\Api\Core\Routes\Resources\RouteResource; use GetCandy\Api\Core\Scaffold\AbstractAction;