diff --git a/CHANGELOG.md b/CHANGELOG.md index ff6e1fe0a..d58ccfd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## CHANGELOG FOR 0.5.0 + - feature [#115](https://github.com/ergonode/backend/issues/115) Product segment functionality (rprzedzik) + - feature [#118](https://github.com/ergonode/backend/issues/118) Event store history (BastekBielawski) + - feature [#124](https://github.com/ergonode/backend/issues/124) Register events in database (BastekBielawski) + ## CHANGELOG FOR 0.4.0 - feature [#104](https://github.com/ergonode/backend/issues/104) Multiple category trees (wiewiurdp) diff --git a/README.md b/README.md index 84480ee4e..1e7c7a388 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

- Version + Version Code Version @@ -96,7 +96,7 @@ On the front side we've used headless approach with Vue.js application. Thanks t #### Backend Technologies - PHP 7.2 -- Symfony 4.2 +- Symfony 4.3 - Postgres 9.6 (uuid-ossp, ltree) - RabbitMQ (optional) - Redis (optional) diff --git a/composer.json b/composer.json index aebfbede7..31712680a 100644 --- a/composer.json +++ b/composer.json @@ -99,6 +99,7 @@ "Ergonode\\AttributeUnit\\": "module/attribute-unit/src", "Ergonode\\AttributeDate\\": "module/attribute-date/src", "Ergonode\\AttributeImage\\": "module/attribute-image/src", + "Ergonode\\Condition\\": "module/condition/src", "Ergonode\\Segment\\": "module/segment/src", "Ergonode\\Grid\\": "module/grid/src", "Ergonode\\Channel\\": "module/channel/src", @@ -136,6 +137,7 @@ "Ergonode\\AttributeUnit\\Tests\\": "module/attribute-unit/tests", "Ergonode\\AttributeDate\\Tests\\": "module/attribute-date/tests", "Ergonode\\AttributeImage\\Tests\\": "module/attribute-image/tests", + "Ergonode\\Condition\\Tests\\": "module/condition/tests", "Ergonode\\Segment\\Tests\\": "module/segment/tests", "Ergonode\\Grid\\Tests\\": "module/grid/tests", "Ergonode\\Channel\\Tests\\": "module/channel/tests", diff --git a/config/bundles.php b/config/bundles.php index b877aa6b7..5ef63ed0d 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -25,6 +25,8 @@ Ergonode\Importer\ErgonodeImporterBundle::class => ['all' => true], Ergonode\Reader\ErgonodeReaderBundle::class => ['all' => true], Ergonode\Transformer\ErgonodeTransformerBundle::class => ['all' => true], + Ergonode\Condition\ErgonodeConditionBundle::class => ['all' => true], + Ergonode\Segment\ErgonodeSegmentBundle::class => ['all' => true], Ergonode\Category\ErgonodeCategoryBundle::class => ['all' => true], Ergonode\CategoryTree\ErgonodeCategoryTreeBundle::class => ['all' => true], Ergonode\Product\ErgonodeProductBundle::class => ['all' => true], @@ -43,6 +45,6 @@ Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], - Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true], Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], + Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true], ]; diff --git a/config/packages/nelmio_api_doc.yaml b/config/packages/nelmio_api_doc.yaml index bfa6e20a0..6871b2ffb 100644 --- a/config/packages/nelmio_api_doc.yaml +++ b/config/packages/nelmio_api_doc.yaml @@ -98,6 +98,40 @@ nelmio_api_doc: type: boolean required: true example: true + account_upd: + type: object + properties: + firstName: + type: string + required: true + description: User first name + example: Jon + lastName: + type: string + required: true + description: User last name + example: Dove + language: + type: string + required: true + description: User language + example: EN + password: + type: string + required: true + example: 12345678 + passwordRepeat: + type: string + equired: true + example: 12345678 + roleId: + type: string + required: true + example: 86800536-0f2a-4920-9291-f35fdcea3839 + isActive: + type: boolean + required: true + example: true status: type: object properties: @@ -210,6 +244,15 @@ nelmio_api_doc: example: DE: Name DE EN: Name EN + category_upd: + type: object + properties: + name: + type: object + description: category name + example: + DE: Name DE + EN: Name EN product: type: object properties: @@ -228,6 +271,15 @@ nelmio_api_doc: type: string required: false description: Lista id kategorii + product_upd: + type: object + properties: + categortyIds: + type: array + items: + type: string + required: false + description: Lista id kategorii template: type: object properties: @@ -269,7 +321,7 @@ nelmio_api_doc: $ref: "#/definitions/translation" example: key: key_1 - translation: + value: PL: Option PL 1 EN: Option EN 1 attribute_parameters: @@ -281,13 +333,29 @@ nelmio_api_doc: description: Additional format information (If used by attribute) example: YYYY-MM-DDDD segment: - type: object + type: 'object' properties: - name: - type: string + condition_set_id: + type: 'string' required: true - description: segment name - example: segment name + description: 'Condition set ID' + code: + type: 'string' + required: true + description: 'Segment unique code' + name: + $ref: "#/definitions/translation" + description: + $ref: "#/definitions/translation" + example: + condition_set_id: 'd1b9b64a-fef6-440c-9560-cf73daa4b4d6' + code: 'code' + name: + PL: 'Nazwa' + EN: 'Name' + description: + PL: 'Opis' + EN: 'Description' languages_req: type: object properties: @@ -416,7 +484,6 @@ nelmio_api_doc: items: type: 'string' example: 'Error message' - credentials: type: object properties: @@ -451,6 +518,28 @@ nelmio_api_doc: description: List of role privileges example: [ ATTRIBUTE_CREATE, ATTRIBUTE_READ, ATTRIBUTE_UPDATE, ATTRIBUTE_DELETE] + conditionset_create: + type: 'object' + properties: + code: + type: 'string' + required: true + description: 'Condition code' + example: 'CONDITION_CODE' + name: + $ref: '#/definitions/translation' + description: + $ref: '#/definitions/translation' + conditionset_update: + type: 'object' + properties: + name: + $ref: '#/definitions/translation' + description: + $ref: '#/definitions/translation' + conditions: + type: 'array' + example: [] authentication: type: object properties: diff --git a/features/account.feature b/features/account.feature index 734937480..24a75ee59 100644 --- a/features/account.feature +++ b/features/account.feature @@ -27,6 +27,107 @@ Feature: Account module Given I request "/api/v1/EN/roles" using HTTP POST Then unauthorized response is received + Scenario: Create role (without name) + Given current authentication token + Given the request body is: + """ + { + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + + Scenario: Create role (without description) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + + Scenario: Create role (without privileges) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role" + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then created response is received + + Scenario: Create role (wrong parameter - name) + Given current authentication token + Given the request body is: + """ + { + "test": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + + Scenario: Create role (empty name) + Given current authentication token + Given the request body is: + """ + { + "name": "", + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + + Scenario: Create role (empty description) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + + Scenario: Create role (empty privilages) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": [] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then created response is received + + Scenario: Create role (no existing privilages) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": ["test", "test2"] + } + """ + When I request "/api/v1/EN/roles" using HTTP POST + Then validation error response is received + Scenario: Create role for delete Given current authentication token Given the request body is: @@ -56,134 +157,722 @@ Feature: Account module When I request "/api/v1/EN/roles/@@static_uuid@@" using HTTP DELETE Then not found response is received - Scenario: Update role + Scenario: Update role + Given current authentication token + Given the request body is: + """ + { + "name": "Test role 2 (@@random_uuid@@)", + "description": "Test role 2", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then empty response is received + + Scenario: Update role (not authorized) + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then unauthorized response is received + + Scenario: Update role (not found) + Given current authentication token + When I request "/api/v1/EN/roles/@@static_uuid@@" using HTTP PUT + Then not found response is received + + Scenario: Update role (without name) + Given current authentication token + Given the request body is: + """ + { + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (without description) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (without privileges) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role" + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (wrong parameter - name) + Given current authentication token + Given the request body is: + """ + { + "test": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (empty name) + Given current authentication token + Given the request body is: + """ + { + "name": "", + "description": "Test role", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (empty description) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "", + "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_UPDATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (empty privilages) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": [] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Update role (no existing privilages) + Given current authentication token + Given the request body is: + """ + { + "name": "Test role (@@random_uuid@@)", + "description": "Test role", + "privileges": ["test", "test2"] + } + """ + When I request "/api/v1/EN/roles/@role@" using HTTP PUT + Then validation error response is received + + Scenario: Get role + Given current authentication token + When I request "/api/v1/EN/roles/@role@" using HTTP GET + Then the response code is 200 + And the JSON object contains keys "id" + + Scenario: Get role (not authorized) + When I request "/api/v1/EN/roles/@role@" using HTTP GET + Then unauthorized response is received + + Scenario: Get role (not found) + Given current authentication token + When I request "/api/v1/EN/roles/@@static_uuid@@" using HTTP GET + Then not found response is received + + Scenario: Get roles + Given current authentication token + When I request "/api/v1/EN/roles" using HTTP GET + Then grid response is received + + Scenario: Get roles (not authorized) + When I request "/api/v1/EN/roles" using HTTP GET + Then unauthorized response is received + + Scenario: Get roles (order by name) + Given current authentication token + When I request "/api/v1/EN/roles?field=name" using HTTP GET + Then grid response is received + + Scenario: Get roles (order by description) + Given current authentication token + When I request "/api/v1/EN/roles?field=description" using HTTP GET + Then grid response is received + + Scenario: Get roles (order by users_count) + Given current authentication token + When I request "/api/v1/EN/roles?field=users_count" using HTTP GET + Then grid response is received + + Scenario: Get roles (filter by name) + Given current authentication token + When I request "/api/v1/EN/roles?limit=25&offset=0&filter=name%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Get roles (filter by description) + Given current authentication token + When I request "/api/v1/EN/roles?limit=25&offset=0&filter=description%3DManage" using HTTP GET + Then grid response is received + + Scenario: Get roles (filter by user_count) + Given current authentication token + When I request "/api/v1/EN/roles?limit=25&offset=0&filter=users_count%3D1" using HTTP GET + Then grid response is received + + Scenario: Get roles (not authorized) + When I request "/api/v1/EN/roles" using HTTP GET + Then unauthorized response is received + + Scenario: Create user + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then created response is received + And remember response param "id" as "user" + + Scenario: Create user (not authorized) + Given I request "/api/v1/EN/accounts" using HTTP POST + Then unauthorized response is received + + Scenario: Create user (no email) + Given current authentication token + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty email) + Given current authentication token + Given the request body is: + """ + { + "email": "", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no firsName) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty firsName) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no lastName) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty lastName) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no language) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty language) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no password) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty password) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": "", + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no passwordRepeat) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty passwordRepeat) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": "", + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (no roleId) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678 + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (empty roleId) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (not UUID roleID) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "test" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Create user (random UUID roleID) + Given current authentication token + Given the request body is: + """ + { + "email": "@@random_uuid@@@ergonode.com", + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@@random_uuid@@" + } + """ + When I request "/api/v1/EN/accounts" using HTTP POST + Then validation error response is received + + Scenario: Delete role (with conflict) + Given current authentication token + When I request "/api/v1/EN/roles/@role@" using HTTP DELETE + Then the response code is 409 + And the JSON object contains keys "code,message" + + Scenario: Update user + Given current authentication token + Given the request body is: + """ + { + "firstName": "Test (changed)", + "lastName": "Test (changed)", + "language": "EN", + "password": 123456789, + "passwordRepeat": 123456789, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then empty response is received + + Scenario: Update user (not authorized) + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then unauthorized response is received + + Scenario: Update user (not found) + Given current authentication token + When I request "/api/v1/EN/accounts/@@static_uuid@@" using HTTP PUT + Then not found response is received + + Scenario: Update user (no firsName) + Given current authentication token + Given the request body is: + """ + { + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received + + Scenario: Update user (empty firsName) + Given current authentication token + Given the request body is: + """ + { + "firstName": "", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received + + Scenario: Update user (no lastName) + Given current authentication token + Given the request body is: + """ + { + "firstName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received + + Scenario: Update user (empty lastName) Given current authentication token Given the request body is: """ { - "name": "Test role 2 (@@random_uuid@@)", - "description": "Test role 2", - "privileges": ["ATTRIBUTE_CREATE","ATTRIBUTE_READ","ATTRIBUTE_DELETE"] + "firstName": "Test", + "lastName": "", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" } """ - When I request "/api/v1/EN/roles/@role@" using HTTP PUT - Then empty response is received - - Scenario: Update role (not authorized) - When I request "/api/v1/EN/roles/@role@" using HTTP PUT - Then unauthorized response is received - - Scenario: Update role (not found) - Given current authentication token - When I request "/api/v1/EN/roles/@@static_uuid@@" using HTTP PUT - Then not found response is received - - Scenario: Get role - Given current authentication token - When I request "/api/v1/EN/roles/@role@" using HTTP GET - Then the response code is 200 - And the JSON object contains keys "id" - - Scenario: Get role (not authorized) - When I request "/api/v1/EN/roles/@role@" using HTTP GET - Then unauthorized response is received - - Scenario: Get role (not found) - Given current authentication token - When I request "/api/v1/EN/roles/@@static_uuid@@" using HTTP GET - Then not found response is received - - Scenario: Get roles - Given current authentication token - When I request "/api/v1/EN/roles" using HTTP GET - Then grid response is received - - Scenario: Get roles (order by name) - Given current authentication token - When I request "/api/v1/EN/roles?field=name" using HTTP GET - Then grid response is received + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Get roles (order by description) + Scenario: Update user (no language) Given current authentication token - When I request "/api/v1/EN/roles?field=description" using HTTP GET - Then grid response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Get roles (order by users_count) + Scenario: Update user (empty language) Given current authentication token - When I request "/api/v1/EN/roles?field=users_count" using HTTP GET - Then grid response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Get roles (filter by name) + Scenario: Update user (no password) Given current authentication token - When I request "/api/v1/EN/roles?limit=25&offset=0&filter=name%3Dsuper" using HTTP GET - Then grid response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Get roles (filter by description) + Scenario: Update user (empty password) Given current authentication token - When I request "/api/v1/EN/roles?limit=25&offset=0&filter=description%3DManage" using HTTP GET - Then grid response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": "", + "passwordRepeat": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Get roles (filter by user_count) + Scenario: Update user (no passwordRepeat) Given current authentication token - When I request "/api/v1/EN/roles?limit=25&offset=0&filter=users_count%3D1" using HTTP GET - Then grid response is received - - Scenario: Get roles (not authorized) - When I request "/api/v1/EN/roles" using HTTP GET - Then unauthorized response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "roleId": "@role@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Create user + Scenario: Update user (empty passwordRepeat) Given current authentication token Given the request body is: """ { - "email": "@@random_uuid@@@ergonode.com", "firstName": "Test", "lastName": "Test", "language": "EN", "password": 12345678, - "passwordRepeat": 12345678, + "passwordRepeat": "", "roleId": "@role@" } """ - When I request "/api/v1/EN/accounts" using HTTP POST - Then created response is received - And remember response param "id" as "user" - - Scenario: Create user (not authorized) - Given I request "/api/v1/EN/accounts" using HTTP POST - Then unauthorized response is received + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Delete role (with conflict) + Scenario: Update user (no roleId) Given current authentication token - When I request "/api/v1/EN/roles/@role@" using HTTP DELETE - Then the response code is 409 - And the JSON object contains keys "code,message" + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678 + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received - Scenario: Update user + Scenario: Update user (empty roleId) Given current authentication token Given the request body is: """ { - "firstName": "Test (changed)", - "lastName": "Test (changed)", + "firstName": "Test", + "lastName": "Test", "language": "EN", - "password": 123456789, - "passwordRepeat": 123456789, - "roleId": "@role@" + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "" } """ When I request "/api/v1/EN/accounts/@user@" using HTTP PUT - Then empty response is received + Then validation error response is received - Scenario: Update user (not authorized) + Scenario: Update user (not UUID roleID) + Given current authentication token + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "test" + } + """ When I request "/api/v1/EN/accounts/@user@" using HTTP PUT - Then unauthorized response is received + Then validation error response is received - Scenario: Update user (not found) + Scenario: Update user (random UUID roleID) Given current authentication token - When I request "/api/v1/EN/accounts/@@static_uuid@@" using HTTP PUT - Then not found response is received + Given the request body is: + """ + { + "firstName": "Test", + "lastName": "Test", + "language": "EN", + "password": 12345678, + "passwordRepeat": 12345678, + "roleId": "@@random_uuid@@" + } + """ + When I request "/api/v1/EN/accounts/@user@" using HTTP PUT + Then validation error response is received Scenario: Get user Given current authentication token @@ -382,8 +1071,88 @@ Feature: Account module When I request "/api/v1/EN/accounts/log" using HTTP GET Then unauthorized response is received - # TODO Check role create action with all incorrect possibilities - # TODO Check role update action with all incorrect possibilities - # TODO Check user create action with all incorrect possibilities - # TODO Check user update action with all incorrect possibilities + Scenario: Get accounts (order by id) + Given current authentication token + When I request "/api/v1/EN/accounts?field=id" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by email) + Given current authentication token + When I request "/api/v1/EN/accounts?field=email" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by first_name) + Given current authentication token + When I request "/api/v1/EN/accounts?field=first_name" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by last_name) + Given current authentication token + When I request "/api/v1/EN/accounts?field=last_name" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by language) + Given current authentication token + When I request "/api/v1/EN/accounts?field=language" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by role_id) + Given current authentication token + When I request "/api/v1/EN/accounts?field=role_id" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order by is_active) + Given current authentication token + When I request "/api/v1/EN/accounts?field=is_active" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order ASC) + Given current authentication token + When I request "/api/v1/EN/accounts?field=email&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get accounts (order DESC) + Given current authentication token + When I request "/api/v1/EN/accounts?field=email&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by id) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=id%3D1" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by email) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=email%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by first_name) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=first_name%3DCAT" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by last_name) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=last_name%3D1" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by language) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=language%3D1" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by role_id) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=role_id%3Dasd1" using HTTP GET + Then grid response is received + + Scenario: Get accounts (filter by is_active) + Given current authentication token + When I request "/api/v1/EN/accounts?limit=25&offset=0&filter=last_name%3Dasd1" using HTTP GET + Then grid response is received + + Scenario: Get accounts (not authorized) + When I request "/api/v1/EN/accounts" using HTTP GET + Then unauthorized response is received + # TODO Check user avatar change action with correct and incorrect file diff --git a/features/bootstrap/AttributeContext.php b/features/bootstrap/AttributeContext.php index 69e6bdedd..aaef6dd4d 100644 --- a/features/bootstrap/AttributeContext.php +++ b/features/bootstrap/AttributeContext.php @@ -38,11 +38,13 @@ public function gatherContexts(BeforeScenarioScope $scope): void } /** - * @Then remember first attribute group as "attribute_group" + * @param string $key + * + * @Then remember first attribute group as :key */ - public function rememberFirstAttributeGroup(): void + public function rememberFirstAttributeGroup(string $key): void { $response = $this->apiContext->getLastResponseBody(); - $this->storageContext->add('attribute_group', $response[count($response)-1]->id); + $this->storageContext->add($key, $response[key($response)]->id); } } diff --git a/features/category-tree.feature b/features/category-tree.feature index 14ad73a46..6917de759 100644 --- a/features/category-tree.feature +++ b/features/category-tree.feature @@ -5,7 +5,7 @@ Feature: Category tree module Given the request body is: """ { - "code": "TREE_CAT_@@random_code@@", + "code": "TREE_@@random_code@@", "name": { "DE": "Test DE", "EN": "Test EN" @@ -20,7 +20,7 @@ Feature: Category tree module When I request "/api/v1/EN/trees" using HTTP POST Then unauthorized response is received - Scenario: Create category for update + Scenario: Create category for update 1 Given current authentication token Given the request body is: """ @@ -34,20 +34,105 @@ Feature: Category tree module """ When I request "/api/v1/EN/categories" using HTTP POST Then created response is received - And remember response param "id" as "tree_category" + And remember response param "id" as "category_1" - Scenario: Update category tree + Scenario: Create category for update 2 Given current authentication token Given the request body is: - """ + """ { + "code": "TREE_CAT_@@random_code@@", "name": { "DE": "Test DE", "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then created response is received + And remember response param "id" as "category_2" + + Scenario: Create category tree (no Name) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@" + } + """ + When I request "/api/v1/EN/trees" using HTTP POST + Then created response is received + + Scenario: Create category tree (empty Name) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + } + } + """ + When I request "/api/v1/EN/trees" using HTTP POST + Then created response is received + + Scenario: Create category tree (name with language with empty string value) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "DE": "", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/trees" using HTTP POST + Then validation error response is received + + Scenario: Create category tree (name with wrong language code) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "test": "Test DE", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/trees" using HTTP POST + Then validation error response is received + + Scenario: Create category tree (name with no existing language code) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "ZZ": "Test DE", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/trees" using HTTP POST + Then validation error response is received + + Scenario: Update category tree + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" }, "categories": [ { - "category_id": "@tree_category@", + "category_id": "@category_1@", "childrens": [] } ] @@ -65,6 +150,271 @@ Feature: Category tree module When I request "/api/v1/EN/trees/@@static_uuid@@" using HTTP PUT Then not found response is received + Scenario: Update category tree (no name field) + Given current authentication token + Given the request body is: + """ + { + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then empty response is received + + Scenario: Update category tree (empty name) + Given current authentication token + Given the request body is: + """ + { + "name": { + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then empty response is received + + Scenario: Update category tree (wrong parameter) + Given current authentication token + Given the request body is: + """ + { + "test": { + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (wrong language code) + Given current authentication token + Given the request body is: + """ + { + "name": { + "test": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (incorrect language code) + Given current authentication token + Given the request body is: + """ + { + "name": { + "ZZ": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (no categories) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + } + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (incorrect category Id) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "test", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (empty categotry id) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (wrong categories key) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "test": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (with childrens) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [{"category_id":"@category_2@"}] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then empty response is received + + Scenario: Update category tree (no childrens) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@" + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then empty response is received + + Scenario: Update category tree (with empty childrens category Id) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [{"category_id":""}] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (with empty childrens wrong key) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "Test DE (changed)", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [{"test":"@category_2@"}] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + + Scenario: Update category tree (empty translation) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "", + "EN": "Test EN (changed)" + }, + "categories": [ + { + "category_id": "@category_1@", + "childrens": [] + } + ] + } + """ + When I request "/api/v1/EN/trees/@category_tree@" using HTTP PUT + Then validation error response is received + Scenario: Get category tree Given current authentication token When I request "/api/v1/EN/trees/@category_tree@" using HTTP GET @@ -79,6 +429,20 @@ Feature: Category tree module When I request "/api/v1/EN/trees/@@static_uuid@@" using HTTP GET Then not found response is received + Scenario: Delete category tree (not found) + Given current authentication token + When I request "/api/v1/EN/trees/@@static_uuid@@" using HTTP DELETE + Then not found response is received + + Scenario: Delete category tree (not authorized) + When I request "/api/v1/EN/trees/@category_tree@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete category tree + Given current authentication token + When I request "/api/v1/EN/trees/@category_tree@" using HTTP DELETE + Then empty response is received + Scenario: Get category trees (order by name) Given current authentication token When I request "/api/v1/EN/trees?field=name" using HTTP GET @@ -88,6 +452,47 @@ Feature: Category tree module When I request "/api/v1/EN/trees" using HTTP GET Then unauthorized response is received - # TODO Check add category to category tree action - # TODO Check create category tree action with all incorrect possibilities - # TODO Check update category tree action with all incorrect possibilities + Scenario: Get category trees + Given current authentication token + When I request "/api/v1/EN/trees" using HTTP GET + Then grid response is received + + Scenario: Get category trees (order by id) + Given current authentication token + When I request "/api/v1/EN/trees?field=id" using HTTP GET + Then grid response is received + + Scenario: Get category trees (order by code) + Given current authentication token + When I request "/api/v1/EN/trees?field=code" using HTTP GET + Then grid response is received + + Scenario: Get category trees (order by name) + Given current authentication token + When I request "/api/v1/EN/trees?field=name" using HTTP GET + Then grid response is received + + Scenario: Get category trees (order ASC) + Given current authentication token + When I request "/api/v1/EN/trees?field=name&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get category trees (order DESC) + Given current authentication token + When I request "/api/v1/EN/trees?field=name&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get category trees (filter by name) + Given current authentication token + When I request "/api/v1/EN/trees?limit=25&offset=0&filter=name%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get category trees (filter by code) + Given current authentication token + When I request "/api/v1/EN/trees?limit=25&offset=0&filter=code%3DCAT" using HTTP GET + Then grid response is received + + Scenario: Get category trees (filter by name) + Given current authentication token + When I request "/api/v1/EN/trees?limit=25&offset=0&filter=name%3D1" using HTTP GET + Then grid response is received diff --git a/features/category.feature b/features/category.feature index 084b0065c..951ce3e1d 100644 --- a/features/category.feature +++ b/features/category.feature @@ -20,6 +20,75 @@ Feature: Category module When I request "/api/v1/EN/categories" using HTTP POST Then unauthorized response is received + Scenario: Create category (no Name) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@" + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then created response is received + + Scenario: Create category (empty Name) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + } + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then created response is received + + Scenario: Create category (name with language with empty string value) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "DE": "", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then validation error response is received + + Scenario: Create category (name with wrong language code) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "test": "Test DE", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then validation error response is received + + Scenario: Create category (name with no existing language code) + Given current authentication token + Given the request body is: + """ + { + "code": "TREE_CAT_@@random_code@@", + "name": { + "ZZ": "Test DE", + "EN": "Test EN" + } + } + """ + When I request "/api/v1/EN/categories" using HTTP POST + Then validation error response is received + Scenario: Update category Given current authentication token Given the request body is: @@ -43,6 +112,72 @@ Feature: Category module When I request "/api/v1/EN/categories/@@static_uuid@@" using HTTP PUT Then not found response is received + Scenario: Update category (empty name) + Given current authentication token + Given the request body is: + """ + { + "name": { + } + } + """ + When I request "/api/v1/EN/categories/@category@" using HTTP PUT + Then empty response is received + + Scenario: Update category (wrong parameter) + Given current authentication token + Given the request body is: + """ + { + "test": { + } + } + """ + When I request "/api/v1/EN/categories/@category@" using HTTP PUT + Then validation error response is received + + Scenario: Update category (wrong language code) + Given current authentication token + Given the request body is: + """ + { + "name": { + "test": "Test DE (changed)", + "EN": "Test EN (changed)" + } + } + """ + When I request "/api/v1/EN/categories/@category@" using HTTP PUT + Then validation error response is received + + Scenario: Update category (incorrect language code) + Given current authentication token + Given the request body is: + """ + { + "name": { + "ZZ": "Test DE (changed)", + "EN": "Test EN (changed)" + } + } + """ + When I request "/api/v1/EN/categories/@category@" using HTTP PUT + Then validation error response is received + + Scenario: Update category (empty translation) + Given current authentication token + Given the request body is: + """ + { + "name": { + "DE": "", + "EN": "Test EN (changed)" + } + } + """ + When I request "/api/v1/EN/categories/@category@" using HTTP PUT + Then validation error response is received + Scenario: Get category Given current authentication token When I request "/api/v1/EN/categories/@category@" using HTTP GET @@ -77,6 +212,16 @@ Feature: Category module When I request "/api/v1/EN/categories?field=sequence" using HTTP GET Then grid response is received + Scenario: Get categories (order ASC) + Given current authentication token + When I request "/api/v1/EN/categories?field=name&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get categories (order DESC) + Given current authentication token + When I request "/api/v1/EN/categories?field=name&order=DESC" using HTTP GET + Then grid response is received + Scenario: Get categories (filter by sequence) Given current authentication token When I request "/api/v1/EN/categories?limit=25&offset=0&filter=sequence%3D1" using HTTP GET @@ -100,6 +245,3 @@ Feature: Category module Scenario: Get categories (not authorized) When I request "/api/v1/EN/categories" using HTTP GET Then unauthorized response is received - - # TODO Check create category action with all incorrect possibilities - # TODO Check update category action with all incorrect possibilities diff --git a/features/condition.feature b/features/condition.feature new file mode 100644 index 000000000..a019f4c27 --- /dev/null +++ b/features/condition.feature @@ -0,0 +1,733 @@ +Feature: Condition + + Scenario: Get conditions dicitionary (not authorized) + When I request "/api/v1/EN/dictionary/conditions" using HTTP GET + Then unauthorized response is received + + Scenario: Get conditions dicitionary + Given current authentication token + When I request "/api/v1/EN/dictionary/conditions" using HTTP GET + Then the response code is 200 + + Scenario: Get condition (not found) + Given current authentication token + When I request "/api/v1/EN/conditions/asd" using HTTP GET + Then not found response is received + + Scenario: Get numeric condition (not authorized) + When I request "/api/v1/EN/conditions/NUMERIC_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then unauthorized response is received + + Scenario: Get numeric condition + Given current authentication token + When I request "/api/v1/EN/conditions/NUMERIC_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then the response code is 200 + + Scenario: Get option condition (not authorized) + When I request "/api/v1/EN/conditions/OPTION_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then unauthorized response is received + + Scenario: Get option condition + Given current authentication token + When I request "/api/v1/EN/conditions/OPTION_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then the response code is 200 + + Scenario: Get attribute exists condition (not authorized) + When I request "/api/v1/EN/conditions/ATTRIBUTE_EXISTS_CONDITION" using HTTP GET + Then unauthorized response is received + + Scenario: Get attribute exists condition + Given current authentication token + When I request "/api/v1/EN/conditions/ATTRIBUTE_EXISTS_CONDITION" using HTTP GET + Then the response code is 200 + + Scenario: Get text condition (not authorized) + When I request "/api/v1/EN/conditions/TEXT_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then unauthorized response is received + + Scenario: Get text condition + Given current authentication token + When I request "/api/v1/EN/conditions/TEXT_ATTRIBUTE_VALUE_CONDITION" using HTTP GET + Then the response code is 200 + + Scenario: Get attribute groups dictionary + Given current authentication token + When I request "/api/v1/EN/dictionary/attributes/groups" using HTTP GET + Then the response code is 200 + And remember first attribute group as "condition_attribute_group" + + Scenario: Create text attribute + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_@@random_code@@", + "type": "TEXT", + "label": {"PL": "Atrybut tekstowy", "EN": "Text attribute"}, + "groups": ["@condition_attribute_group@"], + "parameters": [] + } + """ + When I request "/api/v1/EN/attributes" using HTTP POST + Then created response is received + And remember response param "id" as "condition_text_attribute" + + Scenario: Create condition set (not authorized) + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then unauthorized response is received + + Scenario: Create condition set + Given current authentication token + Given remember param "condition_set_code" with value "CONDITION_@@random_uuid@@" + Given the request body is: + """ + { + "code": "@condition_set_code@", + "name": { + "PL": "Zbiór warunków", + "EN": "Condition set" + }, + "description": { + "PL": "Opis do zbioru warunków", + "EN": "Condition set description" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then created response is received + And remember response param "id" as "conditionset" + + Scenario: Create condition set (not unique code) + Given current authentication token + Given the request body is: + """ + { + "code": "@condition_set_code@" + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then validation error response is received + + Scenario: Create condition set (without description) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_2_@@random_uuid@@", + "name": { + "PL": "Zbiór warunków", + "EN": "Condition set" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then created response is received + + Scenario: Create condition set (only code) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_3_@@random_uuid@@" + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then created response is received + + Scenario: Create condition set (without code) + Given current authentication token + Given the request body is: + """ + { + "name": { + "PL": "Zbiór warunków", + "EN": "Condition set" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then validation error response is received + + Scenario: Create condition set (short name) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_@@random_uuid@@", + "name": { + "PL": "Z", + "EN": "C" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then validation error response is received + + Scenario: Create condition set (long name) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_@@random_uuid@@", + "name": { + "PL": "ceqvqEO1AsN92sTa0yn6vtYKc4Wkegfw7P5IQO34hhmtNWPYUKZXF8npJg55qGTUG4unmQPlaqRRvAzuaQLST2RP030V9gbqx5gekGPRnRqwVi03Cs0SDvmZe0jmMNm4lOm2w02kyHA1wtMapqgv3GGtQFTsXBegVFFu3aGlpZyfyWRl4TLSm4rTWMSRC89u2A3mxEAWv1AXn64ouBL4AoqwRGomgeU58ewRWiEwPv55BMmMfa0SxQOfiplqksmQ", + "EN": "Condition set" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then validation error response is received + + Scenario: Create condition set (long description) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_@@random_uuid@@", + "description": { + "PL": "Opis do zbioru warunków", + "EN": "ceqvqEO1AsN92sTa0yn6vtYKc4Wkegfw7P5IQO34hhmtNWPYUKZXF8npJg55qGTUG4unmQPlaqRRvAzuaQLST2RP030V9gbqx5gekGPRnRqwVi03Cs0SDvmZe0jmMNm4lOm2w02kyHA1wtMapqgv3GGtQFTsXBegVFFu3aGlpZyfyWRl4TLSm4rTWMSRC89u2A3mxEAWv1AXn64ouBL4AoqwRGomgeU58ewRWiEwPv55BMmMfa0SxQOfiplqksmQ" + } + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then validation error response is received + + Scenario: Update condition set + Given current authentication token + Given the request body is: + """ + { + "name": { + "PL": "Zbiór warunków (changed)", + "EN": "Condition set (changed)" + }, + "description": { + "PL": "Opis do zbioru warunków (changed)", + "EN": "Condition set description (changed)" + }, + "conditions": [ + { + "type": "ATTRIBUTE_EXISTS_CONDITION", + "attribute": "@condition_text_attribute@" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (without conditions) + Given current authentication token + Given the request body is: + """ + { + "name": { + "PL": "Zbiór warunków (changed)", + "EN": "Condition set (changed)" + }, + "description": { + "PL": "Opis do zbioru warunków (changed)", + "EN": "Condition set description (changed)" + } + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (without name) + Given current authentication token + Given the request body is: + """ + { + "description": { + "PL": "Opis do zbioru warunków (changed)", + "EN": "Condition set description (changed)" + }, + "conditions": [ + { + "type": "ATTRIBUTE_EXISTS_CONDITION", + "attribute": "@condition_text_attribute@" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (without description) + Given current authentication token + Given the request body is: + """ + { + "name": { + "PL": "Zbiór warunków (changed)", + "EN": "Condition set (changed)" + }, + "conditions": [ + { + "type": "ATTRIBUTE_EXISTS_CONDITION", + "attribute": "@condition_text_attribute@" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (attribute not exists) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "ATTRIBUTE_EXISTS_CONDITION", + "attribute": "@@static_uuid@@" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (attribute not uuid) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "ATTRIBUTE_EXISTS_CONDITION", + "attribute": "abc" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (numeric attribute with not uuid attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "abc", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute without value) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "=" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute without operator) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute invalid operator) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "123", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute without attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (numeric attribute with not existing attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@@static_uuid@@", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (option attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "OPTION_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (option attribute with not uuid attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "OPTION_ATTRIBUTE_VALUE_CONDITION", + "attribute": "abc", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (option attribute without value) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "OPTION_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (option attribute without attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (option attribute with not existing attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "NUMERIC_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@@static_uuid@@", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then empty response is received + + Scenario: Update condition set (text attribute with not uuid attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "abc", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute without value) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "=" + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute without operator) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute invalid operator) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@condition_text_attribute@", + "operator": "123", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute without attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Update condition set (text attribute with not existing attribute) + Given current authentication token + Given the request body is: + """ + { + "conditions": [ + { + "type": "TEXT_ATTRIBUTE_VALUE_CONDITION", + "attribute": "@@static_uuid@@", + "operator": "=", + "value": 123 + } + ] + } + """ + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP PUT + Then validation error response is received + + Scenario: Get condition set (not authorized) + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP GET + Then unauthorized response is received + + Scenario: Get condition set (not found) + Given current authentication token + Given I request "/api/v1/EN/conditionsets/@@static_uuid@@" using HTTP GET + Then not found response is received + + Scenario: Get condition set + Given current authentication token + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP GET + Then the response code is 200 + + Scenario: Delete condition set (not authorized) + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete condition set (not found) + Given current authentication token + Given I request "/api/v1/EN/conditionsets/@@static_uuid@@" using HTTP DELETE + Then not found response is received + + Scenario: Delete condition set + Given current authentication token + Given I request "/api/v1/EN/conditionsets/@conditionset@" using HTTP DELETE + Then empty response is received + + Scenario: Get condition sets + Given current authentication token + When I request "/api/v1/EN/conditionsets" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (not authorized) + When I request "/api/v1/EN/conditionsets" using HTTP GET + Then unauthorized response is received + + Scenario: Get condition sets (order by code) + Given current authentication token + When I request "/api/v1/EN/conditionsets?field=code" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (order by name) + Given current authentication token + When I request "/api/v1/EN/conditionsets?field=name" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (order by description) + Given current authentication token + When I request "/api/v1/EN/conditionsets?field=description" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (filter by code) + Given current authentication token + When I request "/api/v1/EN/conditionsets?limit=25&offset=0&filter=code%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (filter by name) + Given current authentication token + When I request "/api/v1/EN/conditionsets?limit=25&offset=0&filter=name%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Get condition sets (filter by description) + Given current authentication token + When I request "/api/v1/EN/conditionsets?limit=25&offset=0&filter=description%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Create condition set (for conflict delete) + Given current authentication token + Given the request body is: + """ + { + "code": "CONDITION_DELETE_@@random_uuid@@" + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then created response is received + And remember response param "id" as "conditionset_delete" + + Scenario: Create segment (for relation to condition set conflict delete) + Given current authentication token + Given the request body is: + """ + { + "code": "SEG_REL_@@random_code@@", + "condition_set_id": "@conditionset_delete@" + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then created response is received + + Scenario: Delete condition set (with conflict) + Given current authentication token + Given I request "/api/v1/EN/conditionsets/@conditionset_delete@" using HTTP DELETE + Then conflict response is received diff --git a/features/core.feature b/features/core.feature index c7d47736d..57affe647 100644 --- a/features/core.feature +++ b/features/core.feature @@ -8,3 +8,81 @@ Feature: Core module Scenario: Get languages (not authorized) When I request "/api/v1/EN/dictionary/languages" using HTTP GET Then unauthorized response is received + + Scenario: Get translation language (not authorized) + When I request "/api/v1/EN/languages/EN" using HTTP GET + Then unauthorized response is received + + Scenario: Get translation language + Given current authentication token + When I request "/api/v1/EN/languages/EN" using HTTP GET + Then the response code is 200 + + Scenario: Get translation language (not found) + Given current authentication token + When I request "/api/v1/EN/languages/ZZ" using HTTP GET + Then not found response is received + + Scenario: Update language (not authorized) + When I request "/api/v1/EN/languages" using HTTP PUT + Then unauthorized response is received + + Scenario: Update language + Given current authentication token + Given the request body is: + """ + { + "collection":[ + { + "code":"EN", + "active":true + } + ] + } + """ + When I request "/api/v1/EN/languages" using HTTP PUT + Then the response code is 204 + + Scenario: Update language (wrong active - bad request) + Given current authentication token + Given the request body is: + """ + { + "collection":[ + { + "code":"EN", + "active":"test" + } + ] + } + """ + When I request "/api/v1/EN/languages" using HTTP PUT + Then validation error response is received + + Scenario: Update language (wrong code - bad request) + Given current authentication token + Given the request body is: + """ + { + "collection":[ + { + "code":"ZZ", + "active":true + } + ] + } + """ + When I request "/api/v1/EN/languages" using HTTP PUT + Then validation error response is received + + Scenario: Update language (wrong structure) + Given current authentication token + Given the request body is: + """ + { + "code": "ZZ", + "active": true + } + """ + When I request "/api/v1/EN/languages" using HTTP PUT + Then validation error response is received diff --git a/features/designer.feature b/features/designer.feature index 99f4e84af..a777dcd59 100644 --- a/features/designer.feature +++ b/features/designer.feature @@ -66,6 +66,126 @@ Feature: Designer module When I request "/api/v1/EN/templates" using HTTP POST Then unauthorized response is received + Scenario: Create template (wrong image) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "test", + "elements": [ + { + "position": {"x": 0, "y": 0}, + "size": {"width": 2, "height": 1}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates" using HTTP POST + Then validation error response is received + + Scenario: Create template (wrong position) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": "test", "y": 0}, + "size": {"width": 2, "height": 1}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates" using HTTP POST + Then validation error response is received + + Scenario: Create template (wrong size) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 0, "y": 0}, + "size": {"width": "test", "height": 1}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates" using HTTP POST + Then validation error response is received + + Scenario: Create template (wrong attribute_id) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 0, "y": 0}, + "size": {"width": "test", "height": 1}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "test", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates" using HTTP POST + Then validation error response is received + + Scenario: Create template (wrong required) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 0, "y": 0}, + "size": {"width": 2, "height": 1}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": "test" + } + } + ] + } + """ + When I request "/api/v1/EN/templates" using HTTP POST + Then validation error response is received + Scenario: Update template Given current authentication token Given the request body is: @@ -99,6 +219,126 @@ Feature: Designer module When I request "/api/v1/EN/templates/@@static_uuid@@" using HTTP PUT Then not found response is received + Scenario: Update template (wrong image) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "test", + "elements": [ + { + "position": {"x": 10, "y": 10}, + "size": {"width": 2, "height": 2}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates/@template@" using HTTP PUT + Then validation error response is received + + Scenario: Update template (wrong position) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": "test", "y": 10}, + "size": {"width": 2, "height": 2}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates/@template@" using HTTP PUT + Then validation error response is received + + Scenario: Update template (wrong size) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 10, "y": 10}, + "size": {"width": "test", "height": 2}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates/@template@" using HTTP PUT + Then validation error response is received + + Scenario: Update template (wrong attribute_id) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 10, "y": 10}, + "size": {"width": 2, "height": 2}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "test", + "required": true + } + } + ] + } + """ + When I request "/api/v1/EN/templates/@template@" using HTTP PUT + Then validation error response is received + + Scenario: Update template (wrong required) + Given current authentication token + Given the request body is: + """ + { + "name": "@@random_md5@@", + "image": "@template_image_attribute@", + "elements": [ + { + "position": {"x": 10, "y": 10}, + "size": {"width": "test", "height": 2}, + "variant": "attribute", + "type": "text", + "properties": { + "attribute_id": "@template_text_attribute@", + "required": "test" + } + } + ] + } + """ + When I request "/api/v1/EN/templates/@template@" using HTTP PUT + Then validation error response is received + Scenario: Delete template Given current authentication token When I request "/api/v1/EN/templates/@template@" using HTTP DELETE @@ -127,6 +367,65 @@ Feature: Designer module When I request "/api/v1/EN/templates/@@static_uuid@@" using HTTP GET Then not found response is received + Scenario: Get templates + Given current authentication token + When I request "/api/v1/EN/templates" using HTTP GET + Then grid response is received + + Scenario: Get templates (not authorized) + When I request "/api/v1/EN/templates" using HTTP GET + Then unauthorized response is received + + Scenario: Get templates (order by id) + Given current authentication token + When I request "/api/v1/EN/templates?field=id" using HTTP GET + Then grid response is received + + Scenario: Get templates (order by name) + Given current authentication token + When I request "/api/v1/EN/templates?field=name" using HTTP GET + Then grid response is received + + Scenario: Get templates (order by image_id) + Given current authentication token + When I request "/api/v1/EN/templates?field=image_id" using HTTP GET + Then grid response is received + + Scenario: Get templates (order by group_id) + Given current authentication token + When I request "/api/v1/EN/templates?field=group_id" using HTTP GET + Then grid response is received + + Scenario: Get templates (order ASC) + Given current authentication token + When I request "/api/v1/EN/templates?field=name&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get templates (order DESC) + Given current authentication token + When I request "/api/v1/EN/templates?field=name&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get templates (filter by id) + Given current authentication token + When I request "/api/v1/EN/templates?limit=25&offset=0&filter=id%3D1" using HTTP GET + Then grid response is received + + Scenario: Get templates (filter by name) + Given current authentication token + When I request "/api/v1/EN/templates?limit=25&offset=0&filter=name%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get templates (filter by image_id) + Given current authentication token + When I request "/api/v1/EN/templates?limit=25&offset=0&filter=image_id%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get templates (filter by group_id) + Given current authentication token + When I request "/api/v1/EN/templates?limit=25&offset=0&filter=group_id%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received + Scenario: Get template groups Given current authentication token When I request "/api/v1/EN/templates/groups" using HTTP GET @@ -136,6 +435,46 @@ Feature: Designer module When I request "/api/v1/EN/templates/groups" using HTTP GET Then unauthorized response is received + Scenario: Get templates groups (order by id) + Given current authentication token + When I request "/api/v1/EN/templates/groups?field=id" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (order by name) + Given current authentication token + When I request "/api/v1/EN/templates/groups?field=name" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (order by custom) + Given current authentication token + When I request "/api/v1/EN/templates/groups?field=custom" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (order ASC) + Given current authentication token + When I request "/api/v1/EN/templates/groups?field=name&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (order DESC) + Given current authentication token + When I request "/api/v1/EN/templates/groups?field=name&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (filter by id) + Given current authentication token + When I request "/api/v1/EN/templates/groups?limit=25&offset=0&filter=id%3D1" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (filter by name) + Given current authentication token + When I request "/api/v1/EN/templates/groups?limit=25&offset=0&filter=name%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get templates groups (filter by custom) + Given current authentication token + When I request "/api/v1/EN/templates/groups?limit=25&offset=0&filter=custom%3Dasd" using HTTP GET + Then grid response is received + Scenario: Get template types Given current authentication token When I request "/api/v1/EN/templates/types" using HTTP GET @@ -145,17 +484,89 @@ Feature: Designer module When I request "/api/v1/EN/templates/types" using HTTP GET Then unauthorized response is received - Scenario: Get templates + + Scenario: Get templates types (order by type) Given current authentication token - When I request "/api/v1/EN/templates" using HTTP GET - Then the response code is 200 + When I request "/api/v1/EN/templates/types?field=type" using HTTP GET + Then grid response is received - Scenario: Get templates (not authorized) - When I request "/api/v1/EN/templates" using HTTP GET - Then unauthorized response is received + Scenario: Get templates types (order by variant) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=variant" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order by label) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=label" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order by min_width) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=min_width" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order by min_height) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=min_height" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order by max_width) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=max_width" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order by max_height) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=max_height" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order ASC) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=type&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get templates types (order DESC) + Given current authentication token + When I request "/api/v1/EN/templates/types?field=type&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by id) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=id%3D1" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by type) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=type%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by variant) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=variant%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by label) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=label%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by min_width) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=min_width%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by max_width) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=max_width%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by max_height) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=max_height%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received + + Scenario: Get templates types (filter by min_height) + Given current authentication token + When I request "/api/v1/EN/templates/types?limit=25&offset=0&filter=min_height%3D4fbba5a0-61c7-5dc8-ba1b-3314f398bfa2" using HTTP GET + Then grid response is received - # TODO Check template grid - # TODO Check template group grid - # TODO Check template type grid - # TODO Check create template action with all incorrect possibilities - # TODO Check update template action with all incorrect possibilities diff --git a/features/product.feature b/features/product.feature index 5ddb61bdc..82ac3754c 100644 --- a/features/product.feature +++ b/features/product.feature @@ -94,6 +94,94 @@ Feature: Product module When I request "/api/v1/EN/products" using HTTP POST Then unauthorized response is received + Scenario: Create product (wrong product_template no UUID) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "test", + "categoryIds": ["@product_category@"] + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then validation error response is received + + Scenario: Create product (wrong product_template wrong UUID) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "@@random_uuid@@", + "categoryIds": ["@product_category@"] + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then validation error response is received + + Scenario: Create product (no templateId) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "categoryIds": ["@product_category@"] + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then validation error response is received + + Scenario: Create product (empty categoryIds) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "@product_template@", + "categoryIds": [] + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then created response is received + + Scenario: Create product (no categoryIds) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "@product_template@" + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then created response is received + + Scenario: Create product (categoryIds not UUID) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "@product_template@", + "categoryIds": ["test"] + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then validation error response is received + + Scenario: Create product (no categoryIds) + Given current authentication token + Given the request body is: + """ + { + "sku": "SKU_@@random_code@@", + "templateId": "@product_template@" + } + """ + When I request "/api/v1/EN/products" using HTTP POST + Then created response is received + Scenario: Update product Given current authentication token Given the request body is: @@ -114,6 +202,38 @@ Feature: Product module When I request "/api/v1/EN/products/@@static_uuid@@" using HTTP PUT Then not found response is received + Scenario: Update product (no content) + Given current authentication token + Given the request body is: + """ + { + } + """ + When I request "/api/v1/EN/products/@product@" using HTTP PUT + Then validation error response is received + + Scenario: Update product (categoryID not UUID) + Given current authentication token + Given the request body is: + """ + { + "categoryIds": ["@@random_md5@@"] + } + """ + When I request "/api/v1/EN/products/@product@" using HTTP PUT + Then validation error response is received + + Scenario: Update product (categoryID wrong UUID) + Given current authentication token + Given the request body is: + """ + { + "categoryIds": ["@@random_uuid@@"] + } + """ + When I request "/api/v1/EN/products/@product@" using HTTP PUT + Then validation error response is received + Scenario: Get product Given current authentication token When I request "/api/v1/EN/products/@product@" using HTTP GET @@ -128,6 +248,70 @@ Feature: Product module When I request "/api/v1/EN/products/@@static_uuid@@" using HTTP GET Then not found response is received - # TODO Check product grid - # TODO Check create product action with all incorrect possibilities - # TODO Check update product action with all incorrect possibilities + Scenario: Delete product (not found) + Given current authentication token + When I request "/api/v1/EN/products/@@static_uuid@@" using HTTP DELETE + Then not found response is received + + Scenario: Delete product (not authorized) + When I request "/api/v1/EN/products/@product@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete product + Given current authentication token + When I request "/api/v1/EN/products/@product@" using HTTP DELETE + Then empty response is received + + Scenario: Get products (order by id) + Given current authentication token + When I request "/api/v1/EN/products?field=id" using HTTP GET + Then grid response is received + + Scenario: Get products (order by index) + Given current authentication token + When I request "/api/v1/EN/products?field=index" using HTTP GET + Then grid response is received + + Scenario: Get products (order by sku) + Given current authentication token + When I request "/api/v1/EN/products?field=sku" using HTTP GET + Then grid response is received + + Scenario: Get products (order by template) + Given current authentication token + When I request "/api/v1/EN/products?field=template" using HTTP GET + Then grid response is received + + Scenario: Get products (order ASC) + Given current authentication token + When I request "/api/v1/EN/products?field=index&order=ASC" using HTTP GET + Then grid response is received + + Scenario: Get products (order DESC) + Given current authentication token + When I request "/api/v1/EN/products?field=index&order=DESC" using HTTP GET + Then grid response is received + + Scenario: Get products (filter by template) + Given current authentication token + When I request "/api/v1/EN/products?limit=25&offset=0&filter=template%3D1" using HTTP GET + Then grid response is received + + Scenario: Get products (filter by index) + Given current authentication token + When I request "/api/v1/EN/products?limit=25&offset=0&filter=index%3Dasd" using HTTP GET + Then grid response is received + + Scenario: Get products (filter by id) + Given current authentication token + When I request "/api/v1/EN/products?limit=25&offset=0&filter=id%3DCAT" using HTTP GET + Then grid response is received + + Scenario: Get products (filter by sku) + Given current authentication token + When I request "/api/v1/EN/products?limit=25&offset=0&filter=sku%3D1" using HTTP GET + Then grid response is received + + Scenario: Get products (not authorized) + When I request "/api/v1/EN/products" using HTTP GET + Then unauthorized response is received diff --git a/features/segment.feature b/features/segment.feature new file mode 100644 index 000000000..5decc307b --- /dev/null +++ b/features/segment.feature @@ -0,0 +1,252 @@ +Feature: Segment + + Scenario: Create condition set + Given current authentication token + Given the request body is: + """ + { + "code": "SEGMENT_CONDITION_@@random_uuid@@" + } + """ + Given I request "/api/v1/EN/conditionsets" using HTTP POST + Then created response is received + And remember response param "id" as "segment_conditionset" + + Scenario: Create segment (not authorized) + When I request "/api/v1/EN/segments" using HTTP POST + Then unauthorized response is received + + Scenario: Create segment + Given remember param "segment_code" with value "SEG_1_@@random_code@@" + Given current authentication token + Given the request body is: + """ + { + "code": "@segment_code@", + "condition_set_id": "@segment_conditionset@", + "name": { + "PL": "Segment", + "EN": "Segment" + }, + "description": { + "PL": "Opis segmentu", + "EN": "Segment description" + } + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then created response is received + And remember response param "id" as "segment" + + Scenario: Create segment (not unique code) + Given current authentication token + Given the request body is: + """ + { + "code": "@segment_code@", + "condition_set_id": "@segment_conditionset@", + "description": { + "PL": "Opis segmentu", + "EN": "Segment description" + } + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then validation error response is received + + Scenario: Create segment (without name) + Given current authentication token + Given the request body is: + """ + { + "code": "SEG_2_@@random_code@@", + "condition_set_id": "@segment_conditionset@", + "description": { + "PL": "Opis segmentu", + "EN": "Segment description" + } + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then created response is received + + Scenario: Create segment (without description and name) + Given current authentication token + Given the request body is: + """ + { + "code": "SEG_2_@@random_code@@", + "condition_set_id": "@segment_conditionset@" + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then created response is received + + Scenario: Create segment (without code) + Given current authentication token + Given the request body is: + """ + { + "condition_set_id": "@segment_conditionset@", + "name": { + "PL": "Segment", + "EN": "Segment" + }, + "description": { + "PL": "Opis segmentu", + "EN": "Segment description" + } + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then validation error response is received + + Scenario: Create segment (without condition set) + Given current authentication token + Given the request body is: + """ + { + "code": "SEG_2_@@random_code@@" + } + """ + When I request "/api/v1/EN/segments" using HTTP POST + Then validation error response is received + + Scenario: Update segment (not authorized) + When I request "/api/v1/EN/segments/@segment@" using HTTP PUT + Then unauthorized response is received + + Scenario: Update segment (not found) + Given current authentication token + When I request "/api/v1/EN/segments/@@static_uuid@@" using HTTP PUT + Then not found response is received + + Scenario: Update segment + Given current authentication token + Given the request body is: + """ + { + "condition_set_id": "@segment_conditionset@", + "name": { + "PL": "Segment (changed)", + "EN": "Segment (changed)" + }, + "description": { + "PL": "Opis segmentu (changed)", + "EN": "Segment description (changed)" + } + } + """ + When I request "/api/v1/EN/segments/@segment@" using HTTP PUT + Then empty response is received + + Scenario: Update segment (without name) + Given current authentication token + Given the request body is: + """ + { + "condition_set_id": "@segment_conditionset@", + "description": { + "PL": "Opis segmentu (changed)", + "EN": "Segment description (changed)" + } + } + """ + When I request "/api/v1/EN/segments/@segment@" using HTTP PUT + Then empty response is received + + Scenario: Update segment (without name and description) + Given current authentication token + Given the request body is: + """ + { + "condition_set_id": "@segment_conditionset@" + } + """ + When I request "/api/v1/EN/segments/@segment@" using HTTP PUT + Then empty response is received + + Scenario: Update segment (without condition set) + Given current authentication token + Given the request body is: + """ + { + "name": { + "PL": "Segment (changed)", + "EN": "Segment (changed)" + }, + "description": { + "PL": "Opis segmentu (changed)", + "EN": "Segment description (changed)" + } + } + """ + When I request "/api/v1/EN/segments/@segment@" using HTTP PUT + Then validation error response is received + + Scenario: Get segment (not authorized) + When I request "/api/v1/EN/segments/@segment@" using HTTP GET + Then unauthorized response is received + + Scenario: Get segment (not found) + Given current authentication token + When I request "/api/v1/EN/segments/@@static_uuid@@" using HTTP GET + Then not found response is received + + Scenario: Get segment + Given current authentication token + When I request "/api/v1/EN/segments/@segment@" using HTTP GET + Then the response code is 200 + + Scenario: Get segments + Given current authentication token + When I request "/api/v1/EN/segments" using HTTP GET + Then grid response is received + + Scenario: Get segments (not authorized) + When I request "/api/v1/EN/segments" using HTTP GET + Then unauthorized response is received + + Scenario: Get segments (order by code) + Given current authentication token + When I request "/api/v1/EN/segments?field=code" using HTTP GET + Then grid response is received + + Scenario: Get segments (order by name) + Given current authentication token + When I request "/api/v1/EN/segments?field=name" using HTTP GET + Then grid response is received + + Scenario: Get segments (order by description) + Given current authentication token + When I request "/api/v1/EN/segments?field=description" using HTTP GET + Then grid response is received + + Scenario: Get segments (filter by code) + Given current authentication token + When I request "/api/v1/EN/segments?limit=25&offset=0&filter=code%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Get segments (filter by name) + Given current authentication token + When I request "/api/v1/EN/segments?limit=25&offset=0&filter=name%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Get segments (filter by description) + Given current authentication token + When I request "/api/v1/EN/segments?limit=25&offset=0&filter=description%3Dsuper" using HTTP GET + Then grid response is received + + Scenario: Delete segment (not authorized) + When I request "/api/v1/EN/segments/@segment@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete segment (not found) + Given current authentication token + When I request "/api/v1/EN/segments/@@static_uuid@@" using HTTP DELETE + Then not found response is received + + Scenario: Delete segment + Given current authentication token + When I request "/api/v1/EN/segments/@segment@" using HTTP DELETE + Then empty response is received diff --git a/features/workflow.feature b/features/workflow.feature index 740c12766..ccb9e90b7 100644 --- a/features/workflow.feature +++ b/features/workflow.feature @@ -81,19 +81,18 @@ Feature: Workflow When I request "/api/v1/EN/workflow/default" using HTTP PUT Then empty response is received - Scenario: Delete default status + Scenario: Update default workflow (wrong status) Given current authentication token - When I request "/api/v1/EN/status/@workflow_status@" using HTTP DELETE - Then empty response is received - - Scenario: Delete default status (not authorized) - When I request "/api/v1/EN/status/@workflow_status@" using HTTP DELETE - Then unauthorized response is received - - Scenario: Delete default status (not found) - Given current authentication token - When I request "/api/v1/EN/status/@@static_uuid@@" using HTTP DELETE - Then not found response is received + Given the request body is: + """ + { + "code": "TEST_@@random_code@@", + "statuses": ["test"], + "transitions": [] + } + """ + When I request "/api/v1/EN/workflow/default" using HTTP PUT + Then validation error response is received Scenario: Get default statuses Given current authentication token @@ -118,6 +117,19 @@ Feature: Workflow Then created response is received And remember response param "id" as "workflow" + Scenario: Create workflow (wrong statuses) + Given current authentication token + Given the request body is: + """ + { + "code": "WRK_@@random_code@@", + "statuses": ["test"], + "transitions": [] + } + """ + When I request "/api/v1/EN/workflow" using HTTP POST + Then validation error response is received + Scenario: Create workflow (not authorized) When I request "/api/v1/EN/workflow" using HTTP POST Then unauthorized response is received @@ -135,5 +147,30 @@ Feature: Workflow When I request "/api/v1/EN/workflow/default" using HTTP GET Then unauthorized response is received - # TODO Check create workflow action with all incorrect possibilities - # TODO Check update workflow action with all incorrect possibilities + Scenario: Delete workflow (not found) + Given current authentication token + When I request "/api/v1/EN/workflow/@static_uuid@" using HTTP DELETE + Then not found response is received + + Scenario: Delete workflow (not authorized) + When I request "/api/v1/EN/workflow/@workflow@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete workflow + Given current authentication token + When I request "/api/v1/EN/workflow/@workflow@" using HTTP DELETE + Then empty response is received + + Scenario: Delete default status + Given current authentication token + When I request "/api/v1/EN/status/@workflow_status@" using HTTP DELETE + Then empty response is received + + Scenario: Delete default status (not authorized) + When I request "/api/v1/EN/status/@workflow_status@" using HTTP DELETE + Then unauthorized response is received + + Scenario: Delete default status (not found) + Given current authentication token + When I request "/api/v1/EN/status/@@static_uuid@@" using HTTP DELETE + Then not found response is received diff --git a/module/account/README.md b/module/account/README.md index a3da860f2..8af4522fe 100644 --- a/module/account/README.md +++ b/module/account/README.md @@ -1,4 +1,4 @@ -# Ergonode -Aaccount +# Ergonode - Account ## Documentation diff --git a/module/account/composer.json b/module/account/composer.json index 4446e74f0..b9d5045df 100644 --- a/module/account/composer.json +++ b/module/account/composer.json @@ -7,13 +7,12 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/migration": "^0.4.0", - "ergonode/multimedia": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/migration": "^0.5.0", + "ergonode/multimedia": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/account/migrations/Version20180610062601.php b/module/account/migrations/Version20180610062601.php index a6d98a2a1..3d5ed87b3 100644 --- a/module/account/migrations/Version20180610062601.php +++ b/module/account/migrations/Version20180610062601.php @@ -8,7 +8,6 @@ use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class: */ final class Version20180610062601 extends AbstractErgonodeMigration { @@ -19,7 +18,8 @@ final class Version20180610062601 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql('CREATE TABLE users ( + $this->addSql(' + CREATE TABLE users ( id UUID NOT NULL, first_name VARCHAR(128) NOT NULL, last_name VARCHAR(128) NOT NULL, @@ -28,24 +28,29 @@ public function up(Schema $schema): void password VARCHAR(41) NOT NULL, role_id UUID NOT NULL, language VARCHAR(2) NOT NULL, - PRIMARY KEY(id))'); - + PRIMARY KEY(id) + ) + '); $this->addSql('CREATE UNIQUE INDEX users_username_key ON users (username)'); - $this->addSql('CREATE TABLE privileges ( + $this->addSql(' + CREATE TABLE privileges ( id UUID NOT NULL, code VARCHAR(128) NOT NULL, area VARCHAR(128) NOT NULL, - PRIMARY KEY(id))'); - + PRIMARY KEY(id) + ) + '); $this->addSql('CREATE UNIQUE INDEX privileges_name_key ON privileges (code)'); - $this->addSql('CREATE TABLE roles ( + $this->addSql(' + CREATE TABLE roles ( id UUID NOT NULL, name VARCHAR(100) NOT NULL, description VARCHAR(500) NOT NULL, - PRIMARY KEY(id))'); - + PRIMARY KEY(id) + ) + '); $this->addSql('CREATE UNIQUE INDEX role_name_key ON roles (name)'); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'USER_ROLE_CREATE', 'Role']); @@ -61,5 +66,40 @@ public function up(Schema $schema): void $this->addSql('ALTER TABLE roles ADD privileges json DEFAULT NULL'); $this->addSql('ALTER TABLE users ADD is_active BOOLEAN DEFAULT TRUE NOT NULL'); + + $this->createEventStoreEvents([ + 'Ergonode\Account\Domain\Event\User\UserAvatarChangedEvent' => 'User avatar changed', + 'Ergonode\Account\Domain\Event\User\UserCreatedEvent' => 'User created', + 'Ergonode\Account\Domain\Event\User\UserFirstNameChangedEvent' => 'User first name changed', + 'Ergonode\Account\Domain\Event\User\UserLanguageChangedEvent' => 'User language changed', + 'Ergonode\Account\Domain\Event\User\UserLastNameChangedEvent' => 'User last name changed', + 'Ergonode\Account\Domain\Event\User\UserPasswordChangedEvent' => 'User password changed', + 'Ergonode\Account\Domain\Event\User\UserRoleChangedEvent' => 'User role changed', + 'Ergonode\Account\Domain\Event\User\UserActivatedEvent' => 'User activated', + 'Ergonode\Account\Domain\Event\User\UserDeactivatedEvent' => 'User disabled', + 'Ergonode\Account\Domain\Event\Role\AddPrivilegeToRoleEvent' => 'Privilege added', + 'Ergonode\Account\Domain\Event\Role\RemovePrivilegeFromRoleEvent' => 'Privilege removed', + 'Ergonode\Account\Domain\Event\Role\RoleCreatedEvent' => 'Role created', + 'Ergonode\Account\Domain\Event\Role\RoleNameChangedEvent' => 'Role name changed', + 'Ergonode\Account\Domain\Event\Role\RoleDescriptionChangedEvent' => 'Role description changed', + 'Ergonode\Account\Domain\Event\Role\RolePrivilegesChangedEvent' => 'List of privileges changed', + 'Ergonode\Account\Domain\Event\Role\RoleDeletedEvent' => 'Role deleted', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/account/src/Application/Controller/Api/AccountController.php b/module/account/src/Application/Controller/Api/AccountController.php index 813dac628..e462e5568 100644 --- a/module/account/src/Application/Controller/Api/AccountController.php +++ b/module/account/src/Application/Controller/Api/AccountController.php @@ -305,7 +305,7 @@ public function createUser(Request $request): Response * in="body", * description="Add attribute", * required=true, - * @SWG\Schema(ref="#/definitions/account") + * @SWG\Schema(ref="#/definitions/account_upd") * ) * @SWG\Parameter( * name="language", diff --git a/module/account/src/Application/Form/Model/UpdateUserFormModel.php b/module/account/src/Application/Form/Model/UpdateUserFormModel.php index e82e866c2..b6b739efc 100644 --- a/module/account/src/Application/Form/Model/UpdateUserFormModel.php +++ b/module/account/src/Application/Form/Model/UpdateUserFormModel.php @@ -44,6 +44,7 @@ class UpdateUserFormModel /** * @var Password|null * + * @Assert\NotBlank(message="User password repeat is required") * @Assert\EqualTo(propertyPath="password", message="This value should be same as password") */ public $passwordRepeat; diff --git a/module/account/src/Domain/Entity/Role.php b/module/account/src/Domain/Entity/Role.php index 06b2c246c..7f416cf5f 100644 --- a/module/account/src/Domain/Entity/Role.php +++ b/module/account/src/Domain/Entity/Role.php @@ -15,10 +15,8 @@ use Ergonode\Account\Domain\Event\Role\RoleDescriptionChangedEvent; use Ergonode\Account\Domain\Event\Role\RoleNameChangedEvent; use Ergonode\Account\Domain\Event\Role\RolePrivilegesChangedEvent; -use Ergonode\Account\Domain\Event\Role\RoleRemovedEvent; use Ergonode\Account\Domain\ValueObject\Privilege; use Ergonode\Core\Domain\Entity\AbstractId; -use Ergonode\Core\Domain\ValueObject\State; use Ergonode\EventSourcing\Domain\AbstractAggregateRoot; use JMS\Serializer\Annotation as JMS; use Webmozart\Assert\Assert; @@ -55,13 +53,6 @@ class Role extends AbstractAggregateRoot */ private $privileges; - /** - * @var State - * - * @JMS\Exclude() - */ - private $state; - /** * @param RoleId $id * @param string $name @@ -77,15 +68,6 @@ public function __construct(RoleId $id, string $name, string $description, array $this->apply(new RoleCreatedEvent($id, $name, $description, $privileges)); } - /** - */ - public function remove(): void - { - if ($this->state->getValue() !== State::STATE_DELETED) { - $this->apply(new RoleRemovedEvent()); - } - } - /** * @return RoleId|AbstractId */ @@ -188,14 +170,6 @@ public function removePrivilege(Privilege $privilege): void $this->apply(new RemovePrivilegeFromRoleEvent($privilege)); } - /** - * @return bool - */ - public function isDeleted(): bool - { - return $this->state->getValue() === State::STATE_DELETED; - } - /** * @param RoleCreatedEvent $event */ @@ -205,15 +179,6 @@ protected function applyRoleCreatedEvent(RoleCreatedEvent $event): void $this->name = $event->getName(); $this->description = $event->getDescription(); $this->privileges = $event->getPrivileges(); - $this->state = new State(); - } - - /** - * @param RoleRemovedEvent $event - */ - protected function applyRoleRemovedEvent(RoleRemovedEvent $event): void - { - $this->state = new State(State::STATE_DELETED); } /** diff --git a/module/account/src/Domain/Event/Role/RoleRemovedEvent.php b/module/account/src/Domain/Event/Role/RoleDeletedEvent.php similarity index 64% rename from module/account/src/Domain/Event/Role/RoleRemovedEvent.php rename to module/account/src/Domain/Event/Role/RoleDeletedEvent.php index dbc60514c..a2cbd0a24 100644 --- a/module/account/src/Domain/Event/Role/RoleRemovedEvent.php +++ b/module/account/src/Domain/Event/Role/RoleDeletedEvent.php @@ -9,10 +9,10 @@ namespace Ergonode\Account\Domain\Event\Role; -use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; +use Ergonode\EventSourcing\Infrastructure\AbstractDeleteEvent; /** */ -class RoleRemovedEvent implements DomainEventInterface +class RoleDeletedEvent extends AbstractDeleteEvent { } diff --git a/module/account/src/Domain/Repository/RoleRepositoryInterface.php b/module/account/src/Domain/Repository/RoleRepositoryInterface.php index 68e3790f6..da4627120 100644 --- a/module/account/src/Domain/Repository/RoleRepositoryInterface.php +++ b/module/account/src/Domain/Repository/RoleRepositoryInterface.php @@ -28,4 +28,9 @@ public function load(RoleId $id): ?AbstractAggregateRoot; * @param AbstractAggregateRoot $aggregateRoot */ public function save(AbstractAggregateRoot $aggregateRoot): void; + + /** + * @param AbstractAggregateRoot $aggregateRoot + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void; } diff --git a/module/account/src/Infrastructure/Handler/Role/DeleteRoleCommandHandler.php b/module/account/src/Infrastructure/Handler/Role/DeleteRoleCommandHandler.php index 776df8a4f..4baf41f2d 100644 --- a/module/account/src/Infrastructure/Handler/Role/DeleteRoleCommandHandler.php +++ b/module/account/src/Infrastructure/Handler/Role/DeleteRoleCommandHandler.php @@ -40,8 +40,7 @@ public function __invoke(DeleteRoleCommand $command) { $role = $this->repository->load($command->getId()); Assert::isInstanceOf($role, Role::class, sprintf('Can\'t find Role with id %s', $command->getId())); - $role->remove(); - $this->repository->save($role); + $this->repository->delete($role); } } diff --git a/module/account/src/Persistence/Dbal/Projector/Role/RoleCreatedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/Role/RoleCreatedEventProjector.php index d187b84b5..11bd0b231 100644 --- a/module/account/src/Persistence/Dbal/Projector/Role/RoleCreatedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/Role/RoleCreatedEventProjector.php @@ -15,6 +15,7 @@ use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -28,17 +29,22 @@ class RoleCreatedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,16 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, RoleCreatedEvent::class); } - $this->connection->transactional(function () use ($event) { - $this->connection->insert( - self::TABLE, - [ - 'id' => $event->getId()->getValue(), - 'name' => $event->getName(), - 'description' => $event->getDescription(), - 'privileges' => json_encode($event->getPrivileges()), - ] - ); - }); + $this->connection->insert( + self::TABLE, + [ + 'id' => $event->getId()->getValue(), + 'name' => $event->getName(), + 'description' => $event->getDescription(), + 'privileges' => $this->serializer->serialize($event->getPrivileges(), 'json'), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/Role/RoleRemovedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/Role/RoleDeletedEventProjector.php similarity index 51% rename from module/account/src/Persistence/Dbal/Projector/Role/RoleRemovedEventProjector.php rename to module/account/src/Persistence/Dbal/Projector/Role/RoleDeletedEventProjector.php index ea1ec04c1..713c4b6a1 100644 --- a/module/account/src/Persistence/Dbal/Projector/Role/RoleRemovedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/Role/RoleDeletedEventProjector.php @@ -10,7 +10,7 @@ namespace Ergonode\Account\Persistence\Dbal\Projector\Role; use Doctrine\DBAL\Connection; -use Ergonode\Account\Domain\Event\Role\RoleRemovedEvent; +use Ergonode\Account\Domain\Event\Role\RoleDeletedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; @@ -18,7 +18,7 @@ /** */ -class RoleRemovedEventProjector implements DomainEventProjectorInterface +class RoleDeletedEventProjector implements DomainEventProjectorInterface { private const TABLE = 'roles'; @@ -36,40 +36,27 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { - return $event instanceof RoleRemovedEvent; + return $event instanceof RoleDeletedEvent; } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof RoleRemovedEvent) { - throw new UnsupportedEventException($event, RoleRemovedEvent::class); + if (!$event instanceof RoleDeletedEvent) { + throw new UnsupportedEventException($event, RoleDeletedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->delete( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/Role/RoleDescriptionChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/Role/RoleDescriptionChangedEventProjector.php index 0641556d5..b82ddb348 100644 --- a/module/account/src/Persistence/Dbal/Projector/Role/RoleDescriptionChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/Role/RoleDescriptionChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, RoleDescriptionChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'description' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'description' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/Role/RoleNameChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/Role/RoleNameChangedEventProjector.php index 86456a7aa..b154ca213 100644 --- a/module/account/src/Persistence/Dbal/Projector/Role/RoleNameChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/Role/RoleNameChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, RoleNameChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'name' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'name' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/Role/RolePrivilegesChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/Role/RolePrivilegesChangedEventProjector.php index 847ebd878..0579f3fe5 100644 --- a/module/account/src/Persistence/Dbal/Projector/Role/RolePrivilegesChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/Role/RolePrivilegesChangedEventProjector.php @@ -15,6 +15,7 @@ use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -28,17 +29,22 @@ class RolePrivilegesChangedEventProjector implements DomainEventProjectorInterfa private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,16 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, RolePrivilegesChangedEvent::class); } - $this->connection->transactional(function () use ($event, $aggregateId) { - $this->connection->update( - self::TABLE, - [ - 'privileges' => json_encode($event->getTo()), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - }); + $this->connection->update( + self::TABLE, + [ + 'privileges' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserActivatedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserActivatedEventProjector.php index 8fcdff9f9..06038cf08 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserActivatedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserActivatedEventProjector.php @@ -44,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -56,19 +52,17 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserActivatedEvent::class); } - $this->connection->transactional(function () use ($aggregateId, $event) { - $this->connection->update( - self::TABLE, - [ - 'is_active' => $event->isActive(), - ], - [ - 'id' => $aggregateId->getValue(), - ], - [ - 'is_active' => \PDO::PARAM_BOOL, - ] - ); - }); + $this->connection->update( + self::TABLE, + [ + 'is_active' => $event->isActive(), + ], + [ + 'id' => $aggregateId->getValue(), + ], + [ + 'is_active' => \PDO::PARAM_BOOL, + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserAvatarChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserAvatarChangedEventProjector.php index 0941fd124..d8f0428bc 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserAvatarChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserAvatarChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserAvatarChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'avatar_id' => $event->getAvatarId() ? $event->getAvatarId()->getValue() : null, - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'avatar_id' => $event->getAvatarId() ? $event->getAvatarId()->getValue() : null, + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserCreatedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserCreatedEventProjector.php index 220721c9a..48302e30f 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserCreatedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserCreatedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,23 +52,21 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserCreatedEvent::class); } - $this->connection->transactional(function () use ($event) { - $this->connection->insert( - self::TABLE, - [ - 'id' => $event->getId()->getValue(), - 'first_name' => $event->getFirstName(), - 'last_name' => $event->getLastName(), - 'username' => $event->getEmail(), - 'role_id' => $event->getRoleId()->getValue(), - 'language' => $event->getLanguage()->getCode(), - 'password' => $event->getPassword()->getValue(), - 'is_active' => $event->isActive(), - ], - [ - 'is_active' => \PDO::PARAM_BOOL, - ] - ); - }); + $this->connection->insert( + self::TABLE, + [ + 'id' => $event->getId()->getValue(), + 'first_name' => $event->getFirstName(), + 'last_name' => $event->getLastName(), + 'username' => $event->getEmail(), + 'role_id' => $event->getRoleId()->getValue(), + 'language' => $event->getLanguage()->getCode(), + 'password' => $event->getPassword()->getValue(), + 'is_active' => $event->isActive(), + ], + [ + 'is_active' => \PDO::PARAM_BOOL, + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserDeactivatedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserDeactivatedEventProjector.php index a859b2b16..d08140f13 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserDeactivatedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserDeactivatedEventProjector.php @@ -44,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -56,19 +52,17 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserDeactivatedEvent::class); } - $this->connection->transactional(function () use ($aggregateId, $event) { - $this->connection->update( - self::TABLE, - [ - 'is_active' => $event->isActive(), - ], - [ - 'id' => $aggregateId->getValue(), - ], - [ - 'is_active' => \PDO::PARAM_BOOL, - ] - ); - }); + $this->connection->update( + self::TABLE, + [ + 'is_active' => $event->isActive(), + ], + [ + 'id' => $aggregateId->getValue(), + ], + [ + 'is_active' => \PDO::PARAM_BOOL, + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserFirstNameChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserFirstNameChangedEventProjector.php index f24cb1ac7..c65e0a596 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserFirstNameChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserFirstNameChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserFirstNameChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'first_name' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'first_name' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserLanguageChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserLanguageChangedEventProjector.php index c27bbcf48..5d341e630 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserLanguageChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserLanguageChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserLanguageChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'language' => $event->getTo()->getCode(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'language' => $event->getTo()->getCode(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserLastNameChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserLastNameChangedEventProjector.php index 10c3ef72c..31f180be4 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserLastNameChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserLastNameChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserLastNameChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'last_name' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'last_name' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserPasswordChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserPasswordChangedEventProjector.php index db60ef6c4..c5e158b4a 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserPasswordChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserPasswordChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,11 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,21 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserPasswordChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'password' => $event->getPassword(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'password' => $event->getPassword(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Projector/User/UserRoleChangedEventProjector.php b/module/account/src/Persistence/Dbal/Projector/User/UserRoleChangedEventProjector.php index a6e5fcfe3..7444d8ff7 100644 --- a/module/account/src/Persistence/Dbal/Projector/User/UserRoleChangedEventProjector.php +++ b/module/account/src/Persistence/Dbal/Projector/User/UserRoleChangedEventProjector.php @@ -11,12 +11,10 @@ use Doctrine\DBAL\Connection; use Ergonode\Account\Domain\Event\User\UserRoleChangedEvent; -use Ergonode\Account\Domain\Repository\RoleRepositoryInterface; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; -use Webmozart\Assert\Assert; /** */ @@ -30,24 +28,15 @@ class UserRoleChangedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @var RoleRepositoryInterface + * @param Connection $connection */ - private $repository; - - /** - * @param Connection $connection - * @param RoleRepositoryInterface $repository - */ - public function __construct(Connection $connection, RoleRepositoryInterface $repository) + public function __construct(Connection $connection) { $this->connection = $connection; - $this->repository = $repository; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -55,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -68,20 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, UserRoleChangedEvent::class); } - $role = $this->repository->load($event->getTo()); - - Assert::notNull($role); - - $this->connection->transactional(function () use ($event, $aggregateId) { - $this->connection->update( - self::TABLE, - [ - 'role_id' => $event->getTo()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - }); + $this->connection->update( + self::TABLE, + [ + 'role_id' => $event->getTo()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/account/src/Persistence/Dbal/Query/DbalLogQuery.php b/module/account/src/Persistence/Dbal/Query/DbalLogQuery.php index 5a295435d..96b42e25a 100644 --- a/module/account/src/Persistence/Dbal/Query/DbalLogQuery.php +++ b/module/account/src/Persistence/Dbal/Query/DbalLogQuery.php @@ -59,9 +59,26 @@ public function getDataSet(?UserId $id = null): DataSetInterface */ private function getQuery(): QueryBuilder { + $publicEventStoreBuilder = $this->connection->createQueryBuilder() + ->select('*') + ->from('public.event_store'); + + $importerEventStoreBuilder = $this->connection->createQueryBuilder() + ->select('*') + ->from('importer.event_store'); + + $union = sprintf( + '(%s UNION %s)', + $publicEventStoreBuilder->getSQL(), + $importerEventStoreBuilder->getSQL() + ); + return $this->connection->createQueryBuilder() - ->select('es.id, event, payload, recorded_at, recorded_by AS author_id, coalesce(u.first_name || \' \' || u.last_name, \'System\') AS author') - ->leftJoin('es', 'users', 'u', 'u.id = es.recorded_by') - ->from('event_store', 'es'); + ->select('es.id, es.payload, es.recorded_at, es.recorded_by AS author_id') + ->addSelect('coalesce(u.first_name || \' \' || u.last_name, \'System\') AS author') + ->addSelect('ese.translation_key as event') + ->from($union, 'es') + ->join('es', 'public.event_store_event', 'ese', 'es.event_id = ese.id') + ->leftJoin('es', 'public.users', 'u', 'u.id = es.recorded_by'); } } diff --git a/module/account/src/Persistence/Dbal/Repository/DbalRoleRepository.php b/module/account/src/Persistence/Dbal/Repository/DbalRoleRepository.php index f9e5dc60a..5a0010fc9 100644 --- a/module/account/src/Persistence/Dbal/Repository/DbalRoleRepository.php +++ b/module/account/src/Persistence/Dbal/Repository/DbalRoleRepository.php @@ -9,6 +9,7 @@ use Ergonode\Account\Domain\Entity\Role; use Ergonode\Account\Domain\Entity\RoleId; +use Ergonode\Account\Domain\Event\Role\RoleDeletedEvent; use Ergonode\Account\Domain\Repository\RoleRepositoryInterface; use Ergonode\EventSourcing\Domain\AbstractAggregateRoot; use Ergonode\EventSourcing\Infrastructure\DomainEventDispatcherInterface; @@ -32,8 +33,10 @@ class DbalRoleRepository implements RoleRepositoryInterface * @param DomainEventStoreInterface $eventStore * @param DomainEventDispatcherInterface $eventDispatcher */ - public function __construct(DomainEventStoreInterface $eventStore, DomainEventDispatcherInterface $eventDispatcher) - { + public function __construct( + DomainEventStoreInterface $eventStore, + DomainEventDispatcherInterface $eventDispatcher + ) { $this->eventStore = $eventStore; $this->eventDispatcher = $eventDispatcher; } @@ -59,9 +62,7 @@ public function load(RoleId $id): ?AbstractAggregateRoot $aggregate->initialize($eventStream); - if (!$aggregate->isDeleted()) { - return $aggregate; - } + return $aggregate; } return null; @@ -79,4 +80,17 @@ public function save(AbstractAggregateRoot $aggregateRoot): void $this->eventDispatcher->dispatch($envelope); } } + + /** + * {@inheritDoc} + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new RoleDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } } diff --git a/module/account/src/Resources/translations/log.en.yaml b/module/account/src/Resources/translations/log.en.yaml index 17a742bad..421a10af0 100644 --- a/module/account/src/Resources/translations/log.en.yaml +++ b/module/account/src/Resources/translations/log.en.yaml @@ -1,19 +1,16 @@ -"Ergonode\\Account\\Domain\\Event\\User\\UserAvatarChangedEvent": User avatar changed -"Ergonode\\Account\\Domain\\Event\\User\\UserCreatedEvent": User "%first_name% %last_name%" created -"Ergonode\\Account\\Domain\\Event\\User\\UserFirstNameChangedEvent": User first name changed from "%from% to "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserLanguageChangedEvent": User language changed from "%from% to %to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserLastNameChangedEvent": User last name changed from "%from% to "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserPasswordChangedEvent": User password changed -"Ergonode\\Account\\Domain\\Event\\User\\UserRoleChangedEvent": User role changed from "%from%" to "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserActivatedEvent": User activated -"Ergonode\\Account\\Domain\\Event\\User\\UserDeactivatedEvent": User disabled -"Ergonode\\Account\\Domain\\Event\\Role\\AddPrivilegeToRoleEvent": Provilege "%privilege%" added -"Ergonode\\Account\\Domain\\Event\\Role\\RemovePrivilegeFromRoleEvent": privilege "%privilege%" removed -"Ergonode\\Account\\Domain\\Event\\Role\\RoleCreatedEvent": Role "%name%" created -"Ergonode\\Account\\Domain\\Event\\Role\\RoleNameChangedEvent": Role name changed from "%from%" to "%to%" -"Ergonode\\Account\\Domain\\Event\\Role\\RoleDescriptionChangedEvent": Role description changed from "%from%" to "%to%" -"Ergonode\\Account\\Domain\\Event\\Role\\RolePrivilegesChangedEvent": List of pirivileges changed -"Ergonode\\Account\\Domain\\Event\\Role\\RoleRemovedEvent": Role removed - - - +'User avatar changed': 'User avatar changed' +'User created': 'User "%first_name% %last_name%" created' +'User first name changed': 'User first name changed from "%from% to "%to%"' +'User language changed': 'User language changed from "%from% to %to%"' +'User last name changed': 'User last name changed from "%from% to "%to%"' +'User password changed': 'User password changed' +'User role changed': 'User role changed' +'User activated': 'User activated' +'User disabled': 'User disabled' +'Privilege added': 'Provilege "%privilege%" added' +'Privilege removed': 'Privilege "%privilege%" removed' +'Role created': 'Role "%name%" created' +'Role name changed': 'Role name changed from "%from%" to "%to%"' +'Role description changed': 'Role description changed from "%from%" to "%to%"' +'List of privileges changed': 'List of privileges changed' +'Role deleted': 'Role deleted' diff --git a/module/account/src/Resources/translations/log.pl.yaml b/module/account/src/Resources/translations/log.pl.yaml index 93a70ea15..6360adb94 100644 --- a/module/account/src/Resources/translations/log.pl.yaml +++ b/module/account/src/Resources/translations/log.pl.yaml @@ -1,16 +1,16 @@ -"Ergonode\\Account\\Domain\\Event\\User\\UserAvatarChangedEvent": Awatar użytkownika został zmieniony -"Ergonode\\Account\\Domain\\Event\\User\\UserCreatedEvent": Użytkownik "%first_name% %last_name%" został utworzony -"Ergonode\\Account\\Domain\\Event\\User\\UserFirstNameChangedEvent": Imię użytkownika zostało zmienione z "%from% na "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserLanguageChangedEvent": Język użytkownika został zmieniony z "%from% na %to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserLastNameChangedEvent": Nazwisko użytkownika zostało zmienione z "%from% na "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserPasswordChangedEvent": Hasło użytkownika zostało zmienione -"Ergonode\\Account\\Domain\\Event\\User\\UserRoleChangedEvent": Rola użytkonika została zmieniona z "%from%" na "%to%" -"Ergonode\\Account\\Domain\\Event\\User\\UserActivatedEvent": Użytkownik aktywowany -"Ergonode\\Account\\Domain\\Event\\User\\UserDeactivatedEvent": Użytkownik zablokowany -"Ergonode\\Account\\Domain\\Event\\Role\\AddPrivilegeToRoleEvent": Uprawnienie "%privilege%" zostało dodane -"Ergonode\\Account\\Domain\\Event\\Role\\RemovePrivilegeFromRoleEvent": Uprawnienie "%privilege%" zostało usunięte -"Ergonode\\Account\\Domain\\Event\\Role\\RoleCreatedEvent": Rola "%name%" została utworzona -"Ergonode\\Account\\Domain\\Event\\Role\\RoleDescriptionChangedEvent": Opis roli została zmieniona z "%from%" na "%to%" -"Ergonode\\Account\\Domain\\Event\\Role\\RoleNameChangedEvent": Nazwa roli została zmieniona z "%from%" na "%to%" -"Ergonode\\Account\\Domain\\Event\\Role\\RolePrivilegesChangedEvent": List uprawnień została zmieniona -"Ergonode\\Account\\Domain\\Event\\Role\\RoleRemovedEvent": Rola została usunięta +'User avatar changed': 'Awatar użytkownika został zmieniony' +'User created': 'Użytkownik "%first_name% %last_name%" został utworzony' +'User first name changed': 'Imię użytkownika zostało zmienione z "%from% na "%to%"' +'User language changed': 'Język użytkownika został zmieniony z "%from% na %to%"' +'User last name changed': 'Nazwisko użytkownika zostało zmienione z "%from% na "%to%"' +'User password changed': 'Hasło użytkownika zostało zmienione' +'User role changed': 'Rola użytkownika została zmieniona z "%from%" na "%to%"' +'User activated': 'Użytkownik aktywowany' +'User disabled': 'Użytkownik zablokowany' +'Privilege added': 'Uprawnienie "%privilege%" zostało dodane' +'Privilege removed': 'Uprawnienie "%privilege%" zostało usunięte' +'Role created': 'Rola "%name%" została utworzona' +'Role name changed': 'Opis roli został zmieniony z "%from%" na "%to%"' +'Role description changed': 'Nazwa roli została zmieniona z "%from%" na "%to%"' +'List of privileges changed': 'Lista uprawnień została zmieniona' +'Role deleted': 'Rola została usunięta' diff --git a/module/account/tests/Domain/Entity/RoleTest.php b/module/account/tests/Domain/Entity/RoleTest.php index 0cceb94fb..b51c9387e 100644 --- a/module/account/tests/Domain/Entity/RoleTest.php +++ b/module/account/tests/Domain/Entity/RoleTest.php @@ -130,13 +130,4 @@ public function testChangePrivilegesWithIncorrectType(): void $role = new Role($this->roleId, $this->name, $this->description); $role->changesPrivileges([$privileges]); } - - /** - */ - public function testDelete(): void - { - $role = new Role($this->roleId, $this->name, $this->description); - $role->remove(); - $this->assertTrue($role->isDeleted()); - } } diff --git a/module/api/composer.json b/module/api/composer.json index f1d12f7e6..5b0763394 100644 --- a/module/api/composer.json +++ b/module/api/composer.json @@ -7,7 +7,7 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/core": "^0.4.0", + "ergonode/core": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/api/src/Infrastructure/JMS/Serializer/Handler/ViolationsExceptionHandler.php b/module/api/src/Infrastructure/JMS/Serializer/Handler/ViolationsExceptionHandler.php index 4715f180a..12716b89a 100644 --- a/module/api/src/Infrastructure/JMS/Serializer/Handler/ViolationsExceptionHandler.php +++ b/module/api/src/Infrastructure/JMS/Serializer/Handler/ViolationsExceptionHandler.php @@ -86,13 +86,23 @@ private function mapViolations(ConstraintViolationListInterface $violations): ar $errors = []; /** @var ConstraintViolationInterface $violation */ foreach ($violations as $violation) { - $field = substr($violation->getPropertyPath(), 1, -1); + $field = ltrim(str_replace(['[', ']'], ['.', ''], $violation->getPropertyPath()), '.'); + $path = explode('.', $field); - if (!array_key_exists($field, $errors)) { - $errors[$field] = []; + $pointer = &$errors; + foreach ($path as $key) { + if (ctype_digit($key)) { + $key = 'element-'.$key; + } + + if (!array_key_exists($key, $pointer)) { + $pointer[$key] = []; + } + + $pointer = &$pointer[$key]; } - $errors[$field][] = $violation->getMessage(); + $pointer[] = $violation->getMessage(); } return $errors; diff --git a/module/attribute-date/composer.json b/module/attribute-date/composer.json index 9c133ce0a..20ee6cd51 100644 --- a/module/attribute-date/composer.json +++ b/module/attribute-date/composer.json @@ -6,8 +6,8 @@ "license": "OSL-3.0", "require": { "php": "^7.2", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "symfony/form": "^4.3", diff --git a/module/attribute-date/src/Domain/ValueObject/DateFormat.php b/module/attribute-date/src/Domain/ValueObject/DateFormat.php index f3db75ece..c53c07d3a 100644 --- a/module/attribute-date/src/Domain/ValueObject/DateFormat.php +++ b/module/attribute-date/src/Domain/ValueObject/DateFormat.php @@ -13,15 +13,15 @@ */ class DateFormat { - public const YYYY_MM_DD = 'YYYY-MM-DD'; - public const YY_MM_DD = 'YY-MM-DD'; - public const DD_MM_YY = 'DD.MM.YY'; - public const DD_MM_YYYY = 'DD.MM.YYYY'; - public const MM_DD_YY = 'MM/DD/YY'; - public const MM_DD_YYYY = 'MM/DD/YYYY'; - public const MMMM_DD_YYYY = 'MMMM DD, YYYY'; - public const DD_MMMM_YYYY = 'DD MMMM YYYY'; - public const DD_MMM_YYYY = 'DD MMM YYYY'; + public const YYYY_MM_DD = 'yyyy-MM-dd'; + public const YY_MM_DD = 'yy-MM-dd'; + public const DD_MM_YY = 'dd.MM.yy'; + public const DD_MM_YYYY = 'dd.MM.yyyy'; + public const MM_DD_YY = 'MM/dd/yy'; + public const MM_DD_YYYY = 'MM/dd/yyyy'; + public const MMMM_DD_YYYY = 'MMMM dd, yyyy'; + public const DD_MMMM_YYYY = 'dd MMMM yyyy'; + public const DD_MMM_YYYY = 'dd MMM yyyy'; public const AVAILABLE = [ self::YYYY_MM_DD, diff --git a/module/attribute-image/composer.json b/module/attribute-image/composer.json index add4f831a..50e4484e9 100644 --- a/module/attribute-image/composer.json +++ b/module/attribute-image/composer.json @@ -7,8 +7,8 @@ "require": { "php": "^7.2", "doctrine/collections": "^1.6", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "symfony/http-foundation": "^4.3" diff --git a/module/attribute-image/migrations/Version20180610062602.php b/module/attribute-image/migrations/Version20180610062602.php new file mode 100644 index 000000000..d21222044 --- /dev/null +++ b/module/attribute-image/migrations/Version20180610062602.php @@ -0,0 +1,41 @@ +createEventStoreEvents([ + 'Ergonode\AttributeImage\Domain\Event\AttributeImageFormatAddedEvent' => 'Added format to image attribute', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } +} diff --git a/module/attribute-image/src/Resources/translations/log.en.yaml b/module/attribute-image/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..d3a1d6108 --- /dev/null +++ b/module/attribute-image/src/Resources/translations/log.en.yaml @@ -0,0 +1 @@ +'Added format to image attribute': 'Added format to image attribute' diff --git a/module/attribute-image/src/Resources/translations/log.pl.yaml b/module/attribute-image/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..303411fcd --- /dev/null +++ b/module/attribute-image/src/Resources/translations/log.pl.yaml @@ -0,0 +1 @@ +'Added format to image attribute': 'Dodano nowy format do atrybutu obrazka' diff --git a/module/attribute-price/composer.json b/module/attribute-price/composer.json index 8d5d7d7e4..01eb26d63 100644 --- a/module/attribute-price/composer.json +++ b/module/attribute-price/composer.json @@ -7,10 +7,10 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", "jms/serializer": "^3.1", "moneyphp/money": "^3.2", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/attribute-unit/composer.json b/module/attribute-unit/composer.json index 7ee8cc537..53e58453e 100644 --- a/module/attribute-unit/composer.json +++ b/module/attribute-unit/composer.json @@ -8,8 +8,8 @@ "php": "^7.2", "doctrine/collections": "^1.6", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "symfony/form": "^4.3", diff --git a/module/attribute/composer.json b/module/attribute/composer.json index 3dc20a84d..896c64c31 100644 --- a/module/attribute/composer.json +++ b/module/attribute/composer.json @@ -6,12 +6,12 @@ "license": "OSL-3.0", "require": { "php": "^7.2", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/importer": "^0.4.0", - "ergonode/value": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/importer": "^0.5.0", + "ergonode/value": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/attribute/migrations/Version20180625083834.php b/module/attribute/migrations/Version20180625083834.php index 1197303fc..b846fe8ed 100644 --- a/module/attribute/migrations/Version20180625083834.php +++ b/module/attribute/migrations/Version20180625083834.php @@ -125,6 +125,20 @@ public function up(Schema $schema): void $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'ATTRIBUTE_GROUP_READ', 'Attribute group']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'ATTRIBUTE_GROUP_UPDATE', 'Attribute group']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'ATTRIBUTE_GROUP_DELETE', 'Attribute group']); + + $this->createEventStoreEvents([ + 'Ergonode\Attribute\Domain\Event\Attribute\AttributeCreatedEvent' => 'Attribute added', + 'Ergonode\Attribute\Domain\Event\Attribute\AttributeHintChangedEvent' => 'Attribute hint changed', + 'Ergonode\Attribute\Domain\Event\Attribute\AttributeLabelChangedEvent' => 'Attribute label changed', + 'Ergonode\Attribute\Domain\Event\Attribute\AttributePlaceholderChangedEvent' => 'Attribute placeholder changed', + 'Ergonode\Attribute\Domain\Event\Attribute\AttributeArrayParameterChangeEvent' => 'Attribute parameters changed', + 'Ergonode\Attribute\Domain\Event\Attribute\AttributeParameterChangeEvent' => 'Attribute parameter changed', + 'Ergonode\Attribute\Domain\Event\AttributeGroupAddedEvent' => 'Attribute added to group', + 'Ergonode\Attribute\Domain\Event\AttributeGroupDeletedEvent' => 'Attribute removed from group', + 'Ergonode\Attribute\Domain\Event\AttributeOptionAddedEvent' => 'Attribute option added', + 'Ergonode\Attribute\Domain\Event\AttributeOptionRemovedEvent' => 'Attribute option removed', + 'Ergonode\Attribute\Domain\Event\AttributeOptionChangedEvent' => 'Attribute option changed', + ]); } /** @@ -138,4 +152,20 @@ private function addGroup(string $label, bool $default = false): void $id = AttributeGroupId::generate(); $this->addSql('INSERT INTO attribute_group (id, label, "default") VALUES (?, ?, ?)', [$id, $label, (int) $default], ['default' => \PDO::PARAM_BOOL]); } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } } diff --git a/module/attribute/src/Application/Form/Model/AttributeOptionModel.php b/module/attribute/src/Application/Form/Model/AttributeOptionModel.php index c81e0d040..35b2ec36a 100644 --- a/module/attribute/src/Application/Form/Model/AttributeOptionModel.php +++ b/module/attribute/src/Application/Form/Model/AttributeOptionModel.php @@ -19,7 +19,7 @@ class AttributeOptionModel * @var string * * @Assert\NotBlank(message="Option code is required") - * @Assert\Length(max=128, maxMessage="Option code is to long,. It should have {{ limit }} character or less.") + * @Assert\Length(max=128, maxMessage="Option code is to long. It should have {{ limit }} character or less.") */ public $key; diff --git a/module/attribute/src/Application/Form/Model/AttributeParametersModel.php b/module/attribute/src/Application/Form/Model/AttributeParametersModel.php index 7b67345cc..f36a36917 100644 --- a/module/attribute/src/Application/Form/Model/AttributeParametersModel.php +++ b/module/attribute/src/Application/Form/Model/AttributeParametersModel.php @@ -10,7 +10,6 @@ namespace Ergonode\Attribute\Application\Form\Model; /** - * Class AttributeParametersModel */ class AttributeParametersModel { diff --git a/module/attribute/src/Application/Form/Model/CreateAttributeFormModel.php b/module/attribute/src/Application/Form/Model/CreateAttributeFormModel.php index edbf43dd1..292e43913 100644 --- a/module/attribute/src/Application/Form/Model/CreateAttributeFormModel.php +++ b/module/attribute/src/Application/Form/Model/CreateAttributeFormModel.php @@ -55,7 +55,7 @@ class CreateAttributeFormModel * * @Assert\All({ * @Assert\NotBlank(), - * @Assert\Length(max=4000, maxMessage="Attribute placeholder is to long, It should have {{ limit }} character or less.") + * @Assert\Length(max=4000, maxMessage="Attribute placeholder is to long. It should have {{ limit }} character or less.") * }) */ public $placeholder; @@ -65,7 +65,7 @@ class CreateAttributeFormModel * * @Assert\All({ * @Assert\NotBlank(), - * @Assert\Length(max=4000, maxMessage="Attribute hint is to long,. It should have {{ limit }} character or less.") + * @Assert\Length(max=4000, maxMessage="Attribute hint is to long. It should have {{ limit }} character or less.") * }) */ public $hint; diff --git a/module/attribute/src/Application/Form/Model/UpdateAttributeFormModel.php b/module/attribute/src/Application/Form/Model/UpdateAttributeFormModel.php index 14389e87d..da4d551b3 100644 --- a/module/attribute/src/Application/Form/Model/UpdateAttributeFormModel.php +++ b/module/attribute/src/Application/Form/Model/UpdateAttributeFormModel.php @@ -32,7 +32,7 @@ class UpdateAttributeFormModel * * @Assert\All({ * @Assert\NotBlank(), - * @Assert\Length(max=32, maxMessage="Attribute name is to long, It should have {{ limit }} character or less.") + * @Assert\Length(max=32, maxMessage="Attribute name is to long. It should have {{ limit }} character or less.") * }) */ public $label; @@ -42,7 +42,7 @@ class UpdateAttributeFormModel * * @Assert\All({ * @Assert\NotBlank(), - * @Assert\Length(max=4000, maxMessage="Attribute placeholder is to long, It should have {{ limit }} character or less.") + * @Assert\Length(max=4000, maxMessage="Attribute placeholder is to long. It should have {{ limit }} character or less.") * }) */ public $placeholder; @@ -52,7 +52,7 @@ class UpdateAttributeFormModel * * @Assert\All({ * @Assert\NotBlank(), - * @Assert\Length(max=4000, maxMessage="Attribute hint is to long,. It should have {{ limit }} character or less.") + * @Assert\Length(max=4000, maxMessage="Attribute hint is to long. It should have {{ limit }} character or less.") * }) */ public $hint; diff --git a/module/attribute/src/Domain/Command/DeleteAttributeCommand.php b/module/attribute/src/Domain/Command/DeleteAttributeCommand.php index b5eb61d75..976b0621e 100644 --- a/module/attribute/src/Domain/Command/DeleteAttributeCommand.php +++ b/module/attribute/src/Domain/Command/DeleteAttributeCommand.php @@ -12,7 +12,6 @@ use Ergonode\Attribute\Domain\Entity\AttributeId; /** - * Class DeleteAttributeCommand */ class DeleteAttributeCommand { diff --git a/module/attribute/src/Domain/Entity/AbstractAttribute.php b/module/attribute/src/Domain/Entity/AbstractAttribute.php index 5c61c04fe..b2d6e7617 100644 --- a/module/attribute/src/Domain/Entity/AbstractAttribute.php +++ b/module/attribute/src/Domain/Entity/AbstractAttribute.php @@ -18,7 +18,7 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributePlaceholderChangedEvent; use Ergonode\Attribute\Domain\Event\Attribute\AttributeSystemChangedEvent; use Ergonode\Attribute\Domain\Event\AttributeGroupAddedEvent; -use Ergonode\Attribute\Domain\Event\AttributeGroupRemovedEvent; +use Ergonode\Attribute\Domain\Event\AttributeGroupDeletedEvent; use Ergonode\Attribute\Domain\ValueObject\AttributeCode; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Core\Domain\ValueObject\TranslatableString; @@ -256,7 +256,7 @@ public function addGroup(AttributeGroupId $groupId): void public function removeGroup(AttributeGroupId $groupId): void { if ($this->inGroup($groupId)) { - $this->apply(new AttributeGroupRemovedEvent($groupId)); + $this->apply(new AttributeGroupDeletedEvent($groupId)); } } @@ -322,9 +322,9 @@ protected function applyAttributeGroupAddedEvent(AttributeGroupAddedEvent $event } /** - * @param AttributeGroupRemovedEvent $event + * @param AttributeGroupDeletedEvent $event */ - protected function applyAttributeGroupRemovedEvent(AttributeGroupRemovedEvent $event): void + protected function applyAttributeGroupDeletedEvent(AttributeGroupDeletedEvent $event): void { unset($this->groups[$event->getGroupId()->getValue()]); } diff --git a/module/attribute/src/Domain/Event/AttributeGroupRemovedEvent.php b/module/attribute/src/Domain/Event/AttributeGroupDeletedEvent.php similarity index 92% rename from module/attribute/src/Domain/Event/AttributeGroupRemovedEvent.php rename to module/attribute/src/Domain/Event/AttributeGroupDeletedEvent.php index 7096d875f..46de60f91 100644 --- a/module/attribute/src/Domain/Event/AttributeGroupRemovedEvent.php +++ b/module/attribute/src/Domain/Event/AttributeGroupDeletedEvent.php @@ -15,7 +15,7 @@ /** */ -class AttributeGroupRemovedEvent implements DomainEventInterface +class AttributeGroupDeletedEvent implements DomainEventInterface { /** * @var AttributeGroupId diff --git a/module/attribute/src/Domain/Factory/MultiSelectAttributeFactory.php b/module/attribute/src/Domain/Factory/MultiSelectAttributeFactory.php index 6d36914dd..0706426d7 100644 --- a/module/attribute/src/Domain/Factory/MultiSelectAttributeFactory.php +++ b/module/attribute/src/Domain/Factory/MultiSelectAttributeFactory.php @@ -56,7 +56,8 @@ public function create(CreateAttributeCommand $command): AbstractAttribute $option = new StringOption(''); } } - $attribute->addOption(new OptionKey($key), $option); + + $attribute->addOption(new OptionKey((string) $key), $option); } return $attribute; diff --git a/module/attribute/src/Domain/Factory/SelectAttributeFactory.php b/module/attribute/src/Domain/Factory/SelectAttributeFactory.php index 4245135e6..06d255c81 100644 --- a/module/attribute/src/Domain/Factory/SelectAttributeFactory.php +++ b/module/attribute/src/Domain/Factory/SelectAttributeFactory.php @@ -56,7 +56,8 @@ public function create(CreateAttributeCommand $command): AbstractAttribute $option = new StringOption(''); } } - $attribute->addOption(new OptionKey($key), $option); + + $attribute->addOption(new OptionKey((string) $key), $option); } return $attribute; diff --git a/module/attribute/src/Domain/Query/AttributeQueryInterface.php b/module/attribute/src/Domain/Query/AttributeQueryInterface.php index 9e868046d..4f7f84986 100644 --- a/module/attribute/src/Domain/Query/AttributeQueryInterface.php +++ b/module/attribute/src/Domain/Query/AttributeQueryInterface.php @@ -51,6 +51,20 @@ public function getAttribute(AttributeId $attributeId): ?array; */ public function getAllAttributeCodes(): array; + /** + * @param array $types + * + * @return string[] + */ + public function getDictionary(array $types = []): array; + + /** + * @param array $types + * + * @return array + */ + public function getAttributeCodes(array $types = []): array; + /** * @param AttributeId $attributeId * diff --git a/module/attribute/src/Infrastructure/Validator/AttributeExistsValidator.php b/module/attribute/src/Infrastructure/Validator/AttributeExistsValidator.php index b870685f5..30a1fbddc 100644 --- a/module/attribute/src/Infrastructure/Validator/AttributeExistsValidator.php +++ b/module/attribute/src/Infrastructure/Validator/AttributeExistsValidator.php @@ -54,7 +54,10 @@ public function validate($value, Constraint $constraint): void $value = (string) $value; - $attribute = $this->attributeRepository->load(new AttributeId($value)); + $attribute = false; + if (AttributeId::isValid($value)) { + $attribute = $this->attributeRepository->load(new AttributeId($value)); + } if (!$attribute) { $this->context->buildViolation($constraint->message) diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributeArrayParameterChangeEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributeArrayParameterChangeEventProjector.php index bafc4d86c..f31c0dc4c 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributeArrayParameterChangeEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributeArrayParameterChangeEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributeArrayParameterChangeEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class AttributeArrayParameterChangeEventProjector implements DomainEventProjecto private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,24 +60,17 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeArrayParameterChangeEvent::class); } - try { - $this->connection->beginTransaction(); - if (!empty($event->getTo())) { - $this->connection->update( - self::TABLE_PARAMETER, - [ - 'value' => \json_encode($event->getTo()), - ], - [ - 'attribute_id' => $aggregateId->getValue(), - 'type' => $event->getName(), - ] - ); - } - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); + if (!empty($event->getTo())) { + $this->connection->update( + self::TABLE_PARAMETER, + [ + 'value' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'attribute_id' => $aggregateId->getValue(), + 'type' => $event->getName(), + ] + ); } } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributeCreatedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributeCreatedEventProjector.php index d575f5c97..f0017068a 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributeCreatedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributeCreatedEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributeCreatedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; use Ramsey\Uuid\Uuid; /** @@ -33,17 +33,22 @@ class AttributeCreatedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -51,12 +56,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -64,8 +66,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeCreatedEvent::class); } - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $event) { $labelUuid = Uuid::uuid4(); $placeholderUuid = Uuid::uuid4(); $hintUuid = Uuid::uuid4(); @@ -152,15 +153,11 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) [ 'attribute_id' => $aggregateId->getValue(), 'type' => $name, - 'value' => \json_encode($value), + 'value' => $this->serializer->serialize($value, 'json'), ] ); } } - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributeHintChangedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributeHintChangedEventProjector.php index 1ab535c81..792983352 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributeHintChangedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributeHintChangedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributeHintChangedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ramsey\Uuid\Uuid; @@ -38,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,12 +45,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -61,8 +55,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeHintChangedEvent::class); } - $this->connection->beginTransaction(); - try { + $this->connection->transactional(function () use ($aggregateId, $event) { $from = $event->getFrom()->getTranslations(); $to = $event->getTo()->getTranslations(); @@ -102,11 +95,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ); } } - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributeLabelChangedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributeLabelChangedEventProjector.php index 5bf051356..a4f6c4a5b 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributeLabelChangedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributeLabelChangedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributeLabelChangedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ramsey\Uuid\Uuid; @@ -38,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,12 +45,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -64,8 +58,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) $from = $event->getFrom()->getTranslations(); $to = $event->getTo()->getTranslations(); - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $to, $from) { foreach ($to as $language => $value) { $result = $this->connection->update( self::TABLE, @@ -102,11 +95,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ); } } - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributeParameterChangeEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributeParameterChangeEventProjector.php index 296124ecb..7f2eaaec4 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributeParameterChangeEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributeParameterChangeEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributeParameterChangeEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class AttributeParameterChangeEventProjector implements DomainEventProjectorInte private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,24 +60,17 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeParameterChangeEvent::class); } - try { - $this->connection->beginTransaction(); - if (!empty($event->getTo())) { - $this->connection->update( - self::TABLE_PARAMETER, - [ - 'value' => \json_encode($event->getTo()), - ], - [ - 'attribute_id' => $aggregateId->getValue(), - 'type' => $event->getName(), - ] - ); - } - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); + if (!empty($event->getTo())) { + $this->connection->update( + self::TABLE_PARAMETER, + [ + 'value' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'attribute_id' => $aggregateId->getValue(), + 'type' => $event->getName(), + ] + ); } } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/AttributePlaceholderChangedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/AttributePlaceholderChangedEventProjector.php index fce823842..5a1408c9b 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/AttributePlaceholderChangedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/AttributePlaceholderChangedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Attribute\Domain\Event\Attribute\AttributePlaceholderChangedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ramsey\Uuid\Uuid; @@ -38,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,12 +45,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -61,12 +53,10 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributePlaceholderChangedEvent::class); } - $from = $event->getFrom()->getTranslations(); $to = $event->getTo()->getTranslations(); - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $from, $to) { foreach ($to as $language => $value) { $result = $this->connection->update( self::TABLE, @@ -103,11 +93,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ); } } - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupAddedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupAddedEventProjector.php index a9efd5c43..87f015525 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupAddedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupAddedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Attribute\Domain\Event\AttributeGroupAddedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; @@ -37,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,19 +52,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeGroupAddedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->insert( - self::TABLE, - [ - 'attribute_id' => $aggregateId->getValue(), - 'attribute_group_id' => $event->getGroupId()->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::TABLE, + [ + 'attribute_id' => $aggregateId->getValue(), + 'attribute_group_id' => $event->getGroupId()->getValue(), + ] + ); } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupCreatedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupCreatedEventProjector.php index 9c32f6234..f7d16eef8 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupCreatedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupCreatedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,10 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Exception + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -57,21 +52,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeGroupCreatedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->insert( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - 'label' => $event->getLabel(), + $this->connection->insert( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + 'label' => $event->getLabel(), - ] - ); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - - throw $exception; - } + ] + ); } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupRemovedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupRemovedEventProjector.php index b102ae270..c271488c5 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupRemovedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Group/AttributeGroupRemovedEventProjector.php @@ -10,10 +10,9 @@ namespace Ergonode\Attribute\Persistence\Dbal\Projector\Group; use Doctrine\DBAL\Connection; -use Ergonode\Attribute\Domain\Event\AttributeGroupRemovedEvent; +use Ergonode\Attribute\Domain\Event\AttributeGroupDeletedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; @@ -37,42 +36,28 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { - return $event instanceof AttributeGroupRemovedEvent; + return $event instanceof AttributeGroupDeletedEvent; } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof AttributeGroupRemovedEvent) { - throw new UnsupportedEventException($event, AttributeGroupRemovedEvent::class); + if (!$event instanceof AttributeGroupDeletedEvent) { + throw new UnsupportedEventException($event, AttributeGroupDeletedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->delete( - self::TABLE, - [ - 'attribute_id' => $aggregateId->getValue(), - 'attribute_group_id' => $event->getGroupId()->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->delete( + self::TABLE, + [ + 'attribute_id' => $aggregateId->getValue(), + 'attribute_group_id' => $event->getGroupId()->getValue(), + ] + ); } } diff --git a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionAddedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionAddedEventProjector.php index 43b48b0ba..31388780e 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionAddedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionAddedEventProjector.php @@ -17,7 +17,6 @@ use Ergonode\Attribute\Domain\ValueObject\OptionValue\StringOption; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ramsey\Uuid\Uuid; @@ -43,9 +42,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -53,12 +50,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -66,8 +60,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeOptionAddedEvent::class); } - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $event) { $valueId = Uuid::uuid4()->toString(); $this->connection->insert( @@ -80,11 +73,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ); $this->insertOption($valueId, $event->getOption()); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionChangedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionChangedEventProjector.php index e1ceaee4e..73c2a66c5 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionChangedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionChangedEventProjector.php @@ -17,7 +17,6 @@ use Ergonode\Attribute\Domain\ValueObject\OptionValue\StringOption; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ramsey\Uuid\Uuid; @@ -52,6 +51,8 @@ public function support(DomainEventInterface $event): bool /** * {@inheritDoc} + * + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -59,8 +60,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, AttributeOptionChangedEvent::class); } - $this->connection->beginTransaction(); - try { + $this->connection->transactional(function () use ($aggregateId, $event) { $valueId = Uuid::uuid4()->toString(); $attributeId = $aggregateId->getValue(); @@ -76,12 +76,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ); $this->insertOption($valueId, $event->getTo()); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionRemovedEventProjector.php b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionRemovedEventProjector.php index 088541bb4..520ccf27e 100644 --- a/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionRemovedEventProjector.php +++ b/module/attribute/src/Persistence/Dbal/Projector/Option/AttributeOptionRemovedEventProjector.php @@ -37,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +45,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws DBALException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\Exception\InvalidArgumentException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { diff --git a/module/attribute/src/Persistence/Dbal/Query/DbalAttributeGroupQuery.php b/module/attribute/src/Persistence/Dbal/Query/DbalAttributeGroupQuery.php index f21d11844..fa9524ca3 100644 --- a/module/attribute/src/Persistence/Dbal/Query/DbalAttributeGroupQuery.php +++ b/module/attribute/src/Persistence/Dbal/Query/DbalAttributeGroupQuery.php @@ -54,7 +54,8 @@ public function getAttributeGroups(): array public function getDataSet(Language $language): DataSetInterface { $query = $this->connection->createQueryBuilder(); - $query->select('*') + $query + ->select('*') ->from(sprintf('(%s)', $this->getSQL()), 't'); return new DbalDataSet($query); diff --git a/module/attribute/src/Persistence/Dbal/Query/DbalAttributeQuery.php b/module/attribute/src/Persistence/Dbal/Query/DbalAttributeQuery.php index cd5f5f427..fcae2fb47 100644 --- a/module/attribute/src/Persistence/Dbal/Query/DbalAttributeQuery.php +++ b/module/attribute/src/Persistence/Dbal/Query/DbalAttributeQuery.php @@ -198,12 +198,49 @@ public function checkAttributeExistsByCode(AttributeCode $code): bool */ public function getAllAttributeCodes(): array { - return $this->getQuery() - ->select('code') + return $this->getAttributeCodes(); + } + + /** + * @param array $types + * + * @return string[] + */ + public function getAttributeCodes(array $types = []): array + { + $qb = $this->getQuery() + ->select('code'); + + if ($types) { + $qb->andWhere($qb->expr()->in('type', ':types')) + ->setParameter(':types', $types, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); + } + + return $qb ->execute() ->fetchAll(\PDO::FETCH_COLUMN); } + /** + * @param array $types + * + * @return string[] + */ + public function getDictionary(array $types = []): array + { + $qb = $this->getQuery() + ->select('id, code'); + + if ($types) { + $qb->andWhere($qb->expr()->in('type', ':types')) + ->setParameter(':types', $types, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); + } + + return $qb + ->execute() + ->fetchAll(\PDO::FETCH_KEY_PAIR); + } + /** * @param AttributeId $attributeId * diff --git a/module/attribute/src/Persistence/Dbal/Query/Decorator/CacheAttributeQueryDecorator.php b/module/attribute/src/Persistence/Dbal/Query/Decorator/CacheAttributeQueryDecorator.php index f8fbe4e63..cdfaf6d66 100644 --- a/module/attribute/src/Persistence/Dbal/Query/Decorator/CacheAttributeQueryDecorator.php +++ b/module/attribute/src/Persistence/Dbal/Query/Decorator/CacheAttributeQueryDecorator.php @@ -113,6 +113,16 @@ public function getAllAttributeCodes(): array return $this->attributeQuery->getAllAttributeCodes(); } + /** + * @param array $types + * + * @return array + */ + public function getAttributeCodes(array $types = []): array + { + return $this->attributeQuery->getAttributeCodes($types); + } + /** * @param AttributeId $id * @param OptionKey $key @@ -123,4 +133,14 @@ public function findAttributeOption(AttributeId $id, OptionKey $key): ?OptionInt { return $this->attributeQuery->findAttributeOption($id, $key); } + + /** + * @param array $types + * + * @return array + */ + public function getDictionary(array $types = []): array + { + return $this->attributeQuery->getDictionary($types); + } } diff --git a/module/attribute/src/Resources/translations/log.en.yaml b/module/attribute/src/Resources/translations/log.en.yaml index b4a911951..e78982de6 100644 --- a/module/attribute/src/Resources/translations/log.en.yaml +++ b/module/attribute/src/Resources/translations/log.en.yaml @@ -1,12 +1,12 @@ -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeCreatedEvent": Attribute "%code%" added -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeHintChangedEvent": Attribute hint changed -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeLabelChangedEvent": Attribute label changed -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributePlaceholderChangedEvent": Attribute placeholder changed -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeArrayParameterChangeEvent": Attribute parameters changed -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeParameterChangeEvent": Attribute parameter changed -"Ergonode\\Attribute\\Domain\\Event\\AttributeGroupAddedEvent": Attribute added to group -"Ergonode\\Attribute\\Domain\\Event\\AttributeGroupRemovedEvent": Attribute removed from group -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionAddedEvent": Attribute option "%key%" added -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionRemovedEvent": Attribute option "%key%" removed -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionChangedEvent": Attribute option "%key%" changed +'Attribute added': 'Attribute "%code%" added' +'Attribute hint changed': 'Attribute hint changed' +'Attribute label changed': 'Attribute label changed' +'Attribute placeholder changed': 'Attribute placeholder changed' +'Attribute parameters changed': 'Attribute parameters changed' +'Attribute parameter changed': 'Attribute parameter changed' +'Attribute added to group': 'Attribute added to group' +'Attribute removed from group': 'Attribute removed from group' +'Attribute option added': 'Attribute option "%key%" added' +'Attribute option removed': 'Attribute option "%key%" removed' +'Attribute option changed': 'Attribute option "%key%" changed' diff --git a/module/attribute/src/Resources/translations/log.pl.yaml b/module/attribute/src/Resources/translations/log.pl.yaml index 1327bca5c..a3549da52 100644 --- a/module/attribute/src/Resources/translations/log.pl.yaml +++ b/module/attribute/src/Resources/translations/log.pl.yaml @@ -1,12 +1,12 @@ -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeCreatedEvent": Atrybut "%code%" został dodany -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeHintChangedEvent": Wartość podpowiedzi dla atrybutu została zmieniona -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeLabelChangedEvent": Nazwa atrybutu została zmieniona -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributePlaceholderChangedEvent": Wartość wskazówki dla atrybutu została zmieniona -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeArrayParameterChangeEvent": Ustawienia atrybutu zostały zmienione -"Ergonode\\Attribute\\Domain\\Event\\Attribute\\AttributeParameterChangeEvent": Ustawienia atrybutu zostały zmieniona -"Ergonode\\Attribute\\Domain\\Event\\AttributeGroupAddedEvent": Atrybut dodany do grupy -"Ergonode\\Attribute\\Domain\\Event\\AttributeGroupRemovedEvent": Atrybut usunięty z grupy -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionAddedEvent": Opcja atrybutu "%key%" została dodana -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionRemovedEvent": Opcja atrybutu "%key%" została zmieniona -"Ergonode\\Attribute\\Domain\\Event\\AttributeOptionChangedEvent": Opcja atrybutu "%key%" została usunięta +'Attribute added': 'Atrybut "%code%" został dodany' +'Attribute hint changed': 'Wartość podpowiedzi dla atrybutu została zmieniona' +'Attribute label changed': 'Nazwa atrybutu została zmieniona' +'Attribute placeholder changed': 'Wartość wskazówki dla atrybutu została zmieniona' +'Attribute parameters changed': 'Ustawienia atrybutu zostały zmienione' +'Attribute parameter changed': 'Ustawienia atrybutu zostały zmieniona' +'Attribute added to group': 'Atrybut dodany do grupy' +'Attribute removed from group': 'Atrybut usunięty z grupy' +'Attribute option added': 'Opcja atrybutu "%key%" została dodana' +'Attribute option removed': 'Opcja atrybutu "%key%" została zmieniona' +'Attribute option changed': 'Opcja atrybutu "%key%" została usunięta' diff --git a/module/attribute/tests/Domain/Event/AttributeGroupRemovedEventTest.php b/module/attribute/tests/Domain/Event/AttributeGroupDeletedEventTest.php similarity index 75% rename from module/attribute/tests/Domain/Event/AttributeGroupRemovedEventTest.php rename to module/attribute/tests/Domain/Event/AttributeGroupDeletedEventTest.php index 6fe8915a7..3b224c2ef 100644 --- a/module/attribute/tests/Domain/Event/AttributeGroupRemovedEventTest.php +++ b/module/attribute/tests/Domain/Event/AttributeGroupDeletedEventTest.php @@ -10,12 +10,12 @@ namespace Ergonode\Attribute\Tests\Domain\Event; use Ergonode\Attribute\Domain\Entity\AttributeGroupId; -use Ergonode\Attribute\Domain\Event\AttributeGroupRemovedEvent; +use Ergonode\Attribute\Domain\Event\AttributeGroupDeletedEvent; use PHPUnit\Framework\TestCase; /** */ -class AttributeGroupRemovedEventTest extends TestCase +class AttributeGroupDeletedEventTest extends TestCase { /** */ @@ -23,7 +23,7 @@ public function testEventCreation(): void { /** @var AttributeGroupId $groupId */ $groupId = $this->createMock(AttributeGroupId::class); - $event = new AttributeGroupRemovedEvent($groupId); + $event = new AttributeGroupDeletedEvent($groupId); $this->assertEquals($groupId, $event->getGroupId()); } } diff --git a/module/authentication/composer.json b/module/authentication/composer.json index b4b93bc9f..d513eed49 100644 --- a/module/authentication/composer.json +++ b/module/authentication/composer.json @@ -8,8 +8,8 @@ "php": "^7.2", "doctrine/dbal": "^2.9", "doctrine/orm": "^2.6", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", "friendsofsymfony/rest-bundle": "^2.5", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/category-tree/composer.json b/module/category-tree/composer.json index d925afa33..79898791c 100644 --- a/module/category-tree/composer.json +++ b/module/category-tree/composer.json @@ -7,10 +7,10 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/category": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/migration": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/category": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/migration": "^0.5.0", "friendsofsymfony/rest-bundle": "^2.5", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/category-tree/migrations/Version20180619083700.php b/module/category-tree/migrations/Version20180619083700.php index bbe705848..81801b82d 100644 --- a/module/category-tree/migrations/Version20180619083700.php +++ b/module/category-tree/migrations/Version20180619083700.php @@ -18,17 +18,43 @@ final class Version20180619083700 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql( - 'CREATE TABLE IF NOT EXISTS tree ( - id UUID NOT NULL, - code VARCHAR(64) NOT NULL, - name JSONB NOT NULL, - PRIMARY KEY(id))' - ); + $this->addSql(' + CREATE TABLE IF NOT EXISTS tree ( + id UUID NOT NULL, + code VARCHAR(64) NOT NULL, + name JSONB NOT NULL, + PRIMARY KEY(id) + ) + '); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_TREE_CREATE', 'Category tree']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_TREE_READ', 'Category tree']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_TREE_UPDATE', 'Category tree']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_TREE_DELETE', 'Category tree']); + + $this->createEventStoreEvents([ + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeCategoriesChangedEvent' => 'Categories changed on category tree', + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeCategoryAddedEvent' => 'Category added to category tree', + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeCategoryRemovedEvent' => 'Category removed from category tree', + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeCreatedEvent' => 'Category tree created', + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeNameChangedEvent' => 'Category tree name changed', + 'Ergonode\CategoryTree\Domain\Event\CategoryTreeDeletedEvent' => 'Category tree deleted', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/category-tree/src/Application/Controller/Api/CategoryTreeController.php b/module/category-tree/src/Application/Controller/Api/CategoryTreeController.php index 500dcd21f..ddfd64492 100644 --- a/module/category-tree/src/Application/Controller/Api/CategoryTreeController.php +++ b/module/category-tree/src/Application/Controller/Api/CategoryTreeController.php @@ -13,13 +13,12 @@ use Ergonode\Api\Application\Response\CreatedResponse; use Ergonode\Api\Application\Response\EmptyResponse; use Ergonode\Api\Application\Response\SuccessResponse; -use Ergonode\Category\Domain\Entity\CategoryId; use Ergonode\CategoryTree\Application\Form\CategoryTreeCreateForm; use Ergonode\CategoryTree\Application\Form\CategoryTreeUpdateForm; use Ergonode\CategoryTree\Application\Model\CategoryTreeCreateFormModel; use Ergonode\CategoryTree\Application\Model\CategoryTreeUpdateFormModel; -use Ergonode\CategoryTree\Domain\Command\AddCategoryCommand; use Ergonode\CategoryTree\Domain\Command\CreateTreeCommand; +use Ergonode\CategoryTree\Domain\Command\DeleteTreeCommand; use Ergonode\CategoryTree\Domain\Command\UpdateTreeCommand; use Ergonode\CategoryTree\Domain\Entity\CategoryTree; use Ergonode\CategoryTree\Domain\Entity\CategoryTreeId; @@ -155,9 +154,9 @@ public function getCategories(Language $language, RequestGridConfiguration $conf } /** - * @Route("/trees", methods={"POST"}) + * @Route("/trees/{tree}", methods={"GET"}) * - * @IsGranted("CATEGORY_TREE_CREATE") + * @IsGranted("CATEGORY_TREE_READ") * * @SWG\Tag(name="Tree") * @SWG\Parameter( @@ -168,58 +167,46 @@ public function getCategories(Language $language, RequestGridConfiguration $conf * default="EN", * description="Language Code", * ) + * * @SWG\Parameter( - * name="body", - * in="body", - * description="Category tree body", + * name="tree", + * in="path", + * type="string", * required=true, - * @SWG\Schema(ref="#/definitions/tree_req") + * description="tree ID", + * ) + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * required=true, + * description="Language", * ) * @SWG\Response( - * response=201, - * description="Create category tree", + * response=200, + * description="Returns category tree", * ) * @SWG\Response( - * response=400, - * description="Validation error", - * @SWG\Schema(ref="#/definitions/error_response") + * response=404, + * description="Not found", * ) * - * @param Request $request + * @ParamConverter(class="Ergonode\CategoryTree\Domain\Entity\CategoryTree") * - * @return Response - * @throws \Exception + * @param CategoryTree $tree + * @param Language $language * - * @todo Validation required + * @return Response */ - public function createTree(Request $request): Response + public function getTree(CategoryTree $tree, Language $language): Response { - $model = new CategoryTreeCreateFormModel(); - $form = $this->createForm(CategoryTreeCreateForm::class, $model); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $data = $form->getData(); - $tree = $this->treeRepository->exists(CategoryTreeId::fromKey($data->code)); - - if (!$tree) { - $command = new CreateTreeCommand(new TranslatableString($data->name), $data->code); - $this->messageBus->dispatch($command); - - return new CreatedResponse($command->getId()); - } - - throw new BadRequestHttpException('Tree already exists'); - } - - throw new FormValidationHttpException($form); + return new SuccessResponse($tree); } /** - * @Route("/trees/{tree}/category/{category}/child", methods={"POST"}) + * @Route("/trees", methods={"POST"}) * - * @IsGranted("CATEGORY_CREATE") + * @IsGranted("CATEGORY_TREE_CREATE") * * @SWG\Tag(name="Tree") * @SWG\Parameter( @@ -231,29 +218,15 @@ public function createTree(Request $request): Response * description="Language Code", * ) * @SWG\Parameter( - * name="tree", - * in="path", - * type="string", - * required=true, - * description="Id of category tree", - * ) - * @SWG\Parameter( - * name="category", - * in="path", - * type="string", - * required=true, - * description="Id of category in tree", - * ) - * @SWG\Parameter( - * name="child", - * in="formData", - * type="string", + * name="body", + * in="body", + * description="Category tree body", * required=true, - * description="Id of added child category", + * @SWG\Schema(ref="#/definitions/tree_req") * ) * @SWG\Response( - * response=202, - * description="Action accepted", + * response=201, + * description="Create category tree", * ) * @SWG\Response( * response=400, @@ -261,29 +234,37 @@ public function createTree(Request $request): Response * @SWG\Schema(ref="#/definitions/error_response") * ) * - * @param string $tree - * @param string $category * @param Request $request * * @return Response * @throws \Exception + * + * @todo Validation required */ - public function addCategory(string $tree, string $category, Request $request): Response + public function createTree(Request $request): Response { - $child = $request->request->get('child'); + $model = new CategoryTreeCreateFormModel(); + $form = $this->createForm(CategoryTreeCreateForm::class, $model); + $form->handleRequest($request); - if ($child) { - $command = new AddCategoryCommand(new CategoryTreeId($tree), new CategoryId($category), new CategoryId($child)); - $this->messageBus->dispatch($command); + if ($form->isSubmitted() && $form->isValid()) { + $data = $form->getData(); + $tree = $this->treeRepository->exists(CategoryTreeId::fromKey($data->code)); - return new CreatedResponse($command->getCategoryId()); + if (!$tree) { + $command = new CreateTreeCommand(new TranslatableString($data->name), $data->code); + $this->messageBus->dispatch($command); + + return new CreatedResponse($command->getId()); + } + + throw new BadRequestHttpException('Tree already exists'); } - throw new BadRequestHttpException(); + throw new FormValidationHttpException($form); } - /** - * @Route("/trees/{tree}", methods={"PUT"}) + * @Route("/trees/{tree}", methods={"PUT"}, requirements={"tree"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) * * @IsGranted("CATEGORY_TREE_UPDATE") * @@ -350,9 +331,9 @@ public function updateTree(CategoryTree $tree, Request $request): Response } /** - * @Route("/trees/{tree}", methods={"GET"}) + * @Route("/trees/{tree}", methods={"DELETE"}, requirements={"tree"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) * - * @IsGranted("CATEGORY_TREE_READ") + * @IsGranted("CATEGORY_TREE_DELETE") * * @SWG\Tag(name="Tree") * @SWG\Parameter( @@ -363,39 +344,33 @@ public function updateTree(CategoryTree $tree, Request $request): Response * default="EN", * description="Language Code", * ) - * * @SWG\Parameter( * name="tree", * in="path", - * type="string", * required=true, - * description="tree ID", - * ) - * @SWG\Parameter( - * name="language", - * in="path", * type="string", - * required=true, - * description="Language", + * description="Tree ID", * ) * @SWG\Response( - * response=200, - * description="Returns import", + * response=204, + * description="Success" * ) * @SWG\Response( * response=404, - * description="Not found", + * description="Not found" * ) * * @ParamConverter(class="Ergonode\CategoryTree\Domain\Entity\CategoryTree") * * @param CategoryTree $tree - * @param Language $language * * @return Response */ - public function getTree(CategoryTree $tree, Language $language): Response + public function deleteTree(CategoryTree $tree): Response { - return new SuccessResponse($tree); + $command = new DeleteTreeCommand($tree->getId()); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); } } diff --git a/module/category-tree/src/Application/Model/CategoryTreeUpdateFormModel.php b/module/category-tree/src/Application/Model/CategoryTreeUpdateFormModel.php index ea335bfc9..8a6741a41 100644 --- a/module/category-tree/src/Application/Model/CategoryTreeUpdateFormModel.php +++ b/module/category-tree/src/Application/Model/CategoryTreeUpdateFormModel.php @@ -28,6 +28,7 @@ class CategoryTreeUpdateFormModel /** * @var TreeNodeFormModel[] * + * @Assert\NotBlank() * @Assert\Valid() */ public $categories; diff --git a/module/category-tree/src/Domain/Command/AddCategoryCommand.php b/module/category-tree/src/Domain/Command/AddCategoryCommand.php deleted file mode 100644 index 25b5c279f..000000000 --- a/module/category-tree/src/Domain/Command/AddCategoryCommand.php +++ /dev/null @@ -1,78 +0,0 @@ -treeId = $treeId; - $this->categoryId = $categoryId; - $this->parentId = $parentId; - } - - /** - * @return CategoryTreeId - */ - public function getTreeId(): CategoryTreeId - { - return $this->treeId; - } - - /** - * @return CategoryId - */ - public function getCategoryId(): CategoryId - { - return $this->categoryId; - } - - /** - * @return CategoryId - */ - public function getParentId(): CategoryId - { - return $this->parentId; - } -} diff --git a/module/category-tree/src/Domain/Command/DeleteTreeCommand.php b/module/category-tree/src/Domain/Command/DeleteTreeCommand.php new file mode 100644 index 000000000..a8236a0b9 --- /dev/null +++ b/module/category-tree/src/Domain/Command/DeleteTreeCommand.php @@ -0,0 +1,41 @@ +id = $id; + } + + /** + * @return CategoryTreeId + */ + public function getId(): CategoryTreeId + { + return $this->id; + } +} diff --git a/module/category-tree/src/Domain/Event/CategoryTreeDeletedEvent.php b/module/category-tree/src/Domain/Event/CategoryTreeDeletedEvent.php new file mode 100644 index 000000000..cac665864 --- /dev/null +++ b/module/category-tree/src/Domain/Event/CategoryTreeDeletedEvent.php @@ -0,0 +1,18 @@ +repository->load($command->getTreeId()); + $categoryTree = $this->repository->load($command->getId()); + Assert::isInstanceOf($categoryTree, CategoryTree::class, sprintf('Can\'t find category tree with id "%s"', $command->getId())); - Assert::notNull($tree); - $tree->addCategory($command->getCategoryId(), $command->getParentId()); - - $this->repository->save($tree); + $this->repository->delete($categoryTree); } } diff --git a/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeCreatedEventProjector.php b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeCreatedEventProjector.php index 03c1de2f0..c390e584a 100644 --- a/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeCreatedEventProjector.php +++ b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeCreatedEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\CategoryTree\Domain\Event\CategoryTreeCreatedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class CategoryTreeCreatedEventProjector implements DomainEventProjectorInterface protected $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,20 +60,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, CategoryTreeCreatedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->insert( - self::TABLE, - [ - 'id' => $event->getId(), - 'code' => $event->getCode(), - 'name' => json_encode($event->getName()->getTranslations()), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::TABLE, + [ + 'id' => $event->getId(), + 'code' => $event->getCode(), + 'name' => $this->serializer->serialize($event->getName()->getTranslations(), 'json'), + ] + ); } } diff --git a/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeDeletedEventProjector.php b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeDeletedEventProjector.php new file mode 100644 index 000000000..b920013a9 --- /dev/null +++ b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeDeletedEventProjector.php @@ -0,0 +1,62 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof CategoryTreeDeletedEvent; + } + + /** + * {@inheritDoc} + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof CategoryTreeDeletedEvent) { + throw new UnsupportedEventException($event, CategoryTreeDeletedEvent::class); + } + + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeNameChangedEventProjector.php b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeNameChangedEventProjector.php index 7dfcdc3a8..373663744 100644 --- a/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeNameChangedEventProjector.php +++ b/module/category-tree/src/Persistence/Dbal/Projector/CategoryTreeNameChangedEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\CategoryTree\Domain\Event\CategoryTreeNameChangedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class CategoryTreeNameChangedEventProjector implements DomainEventProjectorInter private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,21 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, CategoryTreeNameChangedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->update( - self::TABLE, - [ - 'name' => json_encode($event->getTo()->getTranslations()), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'name' => $this->serializer->serialize($event->getTo()->getTranslations(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/category-tree/src/Persistence/Dbal/Repository/DbalTreeRepository.php b/module/category-tree/src/Persistence/Dbal/Repository/DbalTreeRepository.php index eebc0104d..1a1b45bd3 100644 --- a/module/category-tree/src/Persistence/Dbal/Repository/DbalTreeRepository.php +++ b/module/category-tree/src/Persistence/Dbal/Repository/DbalTreeRepository.php @@ -11,6 +11,7 @@ use Ergonode\CategoryTree\Domain\Entity\CategoryTree; use Ergonode\CategoryTree\Domain\Entity\CategoryTreeId; +use Ergonode\CategoryTree\Domain\Event\CategoryTreeDeletedEvent; use Ergonode\CategoryTree\Domain\Repository\TreeRepositoryInterface; use Ergonode\EventSourcing\Domain\AbstractAggregateRoot; use Ergonode\EventSourcing\Infrastructure\DomainEventDispatcherInterface; @@ -41,9 +42,7 @@ public function __construct(DomainEventStoreInterface $eventStore, DomainEventDi } /** - * @param CategoryTreeId $id - * - * @return CategoryTree|null + * {@inheritDoc} * * @throws \ReflectionException */ @@ -68,9 +67,7 @@ public function load(CategoryTreeId $id): ?AbstractAggregateRoot } /** - * @param CategoryTreeId $id - * - * @return bool + * {@inheritDoc} */ public function exists(CategoryTreeId $id) : bool { @@ -80,7 +77,7 @@ public function exists(CategoryTreeId $id) : bool } /** - * @param AbstractAggregateRoot $aggregateRoot + * {@inheritDoc} */ public function save(AbstractAggregateRoot $aggregateRoot): void { @@ -91,4 +88,17 @@ public function save(AbstractAggregateRoot $aggregateRoot): void $this->eventDispatcher->dispatch($envelope); } } + + /** + * {@inheritDoc} + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new CategoryTreeDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } } diff --git a/module/category-tree/src/Resources/translations/log.en.yaml b/module/category-tree/src/Resources/translations/log.en.yaml index 86d4a3ef5..9bccc0439 100644 --- a/module/category-tree/src/Resources/translations/log.en.yaml +++ b/module/category-tree/src/Resources/translations/log.en.yaml @@ -1,4 +1,6 @@ -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoriesChangedEvent": Categories changed -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoryAddedEvent": Category added -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoryRemovedEvent": Category removed -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCreatedEvent": Category Tree "%name%" created +'Categories changed on category tree': 'Categories changed on category tree' +'Category added to category tree': 'Category added to category tree' +'Category removed from category tree': 'Category removed from category tree' +'Category tree created': 'Category tree created' +'Category tree name changed': 'Category tree name changed' +'Category tree deleted': 'Category tree "%id%" deleted' diff --git a/module/category-tree/src/Resources/translations/log.pl.yaml b/module/category-tree/src/Resources/translations/log.pl.yaml index 2824e0efe..976dea321 100644 --- a/module/category-tree/src/Resources/translations/log.pl.yaml +++ b/module/category-tree/src/Resources/translations/log.pl.yaml @@ -1,4 +1,6 @@ -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoriesChangedEvent": Zamiana wielu kategorii -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoryAddedEvent": Dodanie kategorii -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCategoryRemovedEvent": Usunięcie kategorii -"Ergonode\\CategoryTree\\Domain\\Event\\CategoryTreeCreatedEvent": Stworzenie drzewa kategorii +'Categories changed on category tree': 'Zamiana wielu kategorii na drzewie kategorii' +'Category added to category tree': 'Dodanie kategorii do drzewa kategorii' +'Category removed from category tree': 'Usunięcie kategorii z drzewa kategorii' +'Category tree created': 'Stworzenie drzewa kategorii' +'Category tree name changed': 'Zmieniono nazwę drzewa kategorii' +'Category tree deleted': 'Drzewo kategorii "%id%" zostało usunięte' diff --git a/module/category/composer.json b/module/category/composer.json index 91bd8dbce..3ba439d6f 100644 --- a/module/category/composer.json +++ b/module/category/composer.json @@ -7,12 +7,12 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/value": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/value": "^0.5.0", "friendsofsymfony/rest-bundle": "^2.5", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/category/migrations/Version20180619083800.php b/module/category/migrations/Version20180619083800.php index 915035e59..1e59bc377 100644 --- a/module/category/migrations/Version20180619083800.php +++ b/module/category/migrations/Version20180619083800.php @@ -18,19 +18,40 @@ final class Version20180619083800 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql( - 'CREATE TABLE IF NOT EXISTS category ( - id UUID NOT NULL, - name JSONB NOT NULL, - code VARCHAR(255) DEFAULT NULL, - sequence SERIAL, - PRIMARY KEY(id) - )' - ); + $this->addSql(' + CREATE TABLE IF NOT EXISTS category ( + id UUID NOT NULL, + name JSONB NOT NULL, + code VARCHAR(255) DEFAULT NULL, + sequence SERIAL, + PRIMARY KEY(id) + ) + '); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_CREATE', 'Category']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_READ', 'Category']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_UPDATE', 'Category']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CATEGORY_DELETE', 'Category']); + + $this->createEventStoreEvents([ + 'Ergonode\Category\Domain\Event\CategoryCreatedEvent' => 'Category created', + 'Ergonode\Category\Domain\Event\CategoryNameChangedEvent' => 'Category name changed', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/category/src/Application/Controller/Api/CategoryController.php b/module/category/src/Application/Controller/Api/CategoryController.php index 26e32dc3f..4eb6d19f1 100644 --- a/module/category/src/Application/Controller/Api/CategoryController.php +++ b/module/category/src/Application/Controller/Api/CategoryController.php @@ -270,7 +270,7 @@ public function createCategory(Request $request): Response * in="body", * description="Category body", * required=true, - * @SWG\Schema(ref="#/definitions/category") + * @SWG\Schema(ref="#/definitions/category_upd") * ) * @SWG\Response( * response=204, diff --git a/module/category/src/Persistence/Dbal/Projector/CategoryCreatedEventProjector.php b/module/category/src/Persistence/Dbal/Projector/CategoryCreatedEventProjector.php index d12ddcff3..878fd0008 100644 --- a/module/category/src/Persistence/Dbal/Projector/CategoryCreatedEventProjector.php +++ b/module/category/src/Persistence/Dbal/Projector/CategoryCreatedEventProjector.php @@ -15,7 +15,6 @@ use Ergonode\Category\Domain\Event\CategoryCreatedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use JMS\Serializer\SerializerInterface; @@ -51,9 +50,7 @@ public function __construct(Connection $connection, SerializerInterface $seriali } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -61,12 +58,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -74,13 +68,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, CategoryCreatedEvent::class); } - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $event) { $this->connection->insert( self::TABLE, [ 'id' => $aggregateId->getValue(), - 'name' => json_encode($event->getName()->getTranslations()), + 'name' => $this->serializer->serialize($event->getName()->getTranslations(), 'json'), 'code' => $event->getCode()->getValue(), ] ); @@ -116,10 +109,6 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) ] ); } - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } } diff --git a/module/category/src/Persistence/Dbal/Projector/CategoryNameChangedEventProjector.php b/module/category/src/Persistence/Dbal/Projector/CategoryNameChangedEventProjector.php index 76067415f..b6e429b10 100644 --- a/module/category/src/Persistence/Dbal/Projector/CategoryNameChangedEventProjector.php +++ b/module/category/src/Persistence/Dbal/Projector/CategoryNameChangedEventProjector.php @@ -13,9 +13,9 @@ use Ergonode\Category\Domain\Event\CategoryNameChangedEvent; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class CategoryNameChangedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,21 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, CategoryNameChangedEvent::class); } - try { - $this->connection->beginTransaction(); - $this->connection->update( - self::TABLE, - [ - 'name' => json_encode($event->getTo()->getTranslations()), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'name' => $this->serializer->serialize($event->getTo()->getTranslations(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/category/src/Resources/translations/log.en.yaml b/module/category/src/Resources/translations/log.en.yaml index f60cec2e8..0cf9f891f 100644 --- a/module/category/src/Resources/translations/log.en.yaml +++ b/module/category/src/Resources/translations/log.en.yaml @@ -1,2 +1,2 @@ -"Ergonode\\Category\\Domain\\Event\\CategoryCreatedEvent": Category "%code%" created -"Ergonode\\Category\\Domain\\Event\\CategoryNameChangedEvent": Category name changed +'Category created': 'Category "%code%" created' +'Category name changed': 'Category name changed' diff --git a/module/category/src/Resources/translations/log.pl.yaml b/module/category/src/Resources/translations/log.pl.yaml index 8bb9dc04a..6961c8a64 100644 --- a/module/category/src/Resources/translations/log.pl.yaml +++ b/module/category/src/Resources/translations/log.pl.yaml @@ -1,2 +1,2 @@ -"Ergonode\\Category\\Domain\\Event\\CategoryCreatedEvent": Stworzono kategorię "%code%" -"Ergonode\\Category\\Domain\\Event\\CategoryNameChangedEvent": Nazwa kategori została zmieniona +'Category created': 'Stworzono kategorię "%code%"' +'Category name changed': 'Nazwa kategori została zmieniona' diff --git a/module/completeness/composer.json b/module/completeness/composer.json index 43c70657c..af6c05a8f 100644 --- a/module/completeness/composer.json +++ b/module/completeness/composer.json @@ -7,10 +7,10 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/editor": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/editor": "^0.5.0", "friendsofsymfony/rest-bundle": "^2.5", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/condition/LICENSE.txt b/module/condition/LICENSE.txt new file mode 100644 index 000000000..996dcc641 --- /dev/null +++ b/module/condition/LICENSE.txt @@ -0,0 +1,26 @@ +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + a) to reproduce the Original Work in copies, either alone or as part of a collective work; + b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + d) to perform the Original Work publicly; and + e) to display the Original Work publicly. + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/module/condition/README.md b/module/condition/README.md new file mode 100644 index 000000000..8e93bc73a --- /dev/null +++ b/module/condition/README.md @@ -0,0 +1,19 @@ +# Ergonode - Condition + +## Documentation + +* Follow link to [**Ergonode Documentation**](https://docs.ergonode.com), + +## Community + +* Get Ergonode support on **Stack Overflow**, [**Slack**](https://ergonode.slack.com) and [**email**](team@ergonode.com). +* Follow us on [**GitHub**](https://github.com/ergonode), [**Twitter**](https://twitter.com/ergonode) and [**Facebook**](https://www.facebook.com/ergonode), + +## Contributing + +Ergonode is a Open Source. Join us as a [**contributor**](https://ergonode.com/contribution). + +## About Us + +Ergonode development is sponsored by Bold Brand Commerce Sp. z o.o., lead by **Eronode Core Team** and supported by Ergonode contributors. + diff --git a/module/condition/composer.json b/module/condition/composer.json new file mode 100644 index 000000000..e300606f1 --- /dev/null +++ b/module/condition/composer.json @@ -0,0 +1,30 @@ +{ + "name": "ergonode/condition", + "type": "ergonode-module", + "description": "Ergonode - Condition", + "homepage": "https://ergonode.com", + "license": "OSL-3.0", + "require": { + "php": "^7.2", + "doctrine/dbal": "^2.9", + "ergonode/attribute": "^0.5.0", + "ergonode/product": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/value": "^0.5.0", + "jms/serializer": "^3.1", + "nelmio/api-doc-bundle": "^3.4", + "ramsey/uuid": "^3.8", + "sensio/framework-extra-bundle": "^5.4", + "symfony/form": "^4.3", + "symfony/messenger": "^4.3", + "symfony/translation": "^4.3", + "symfony/validator": "^4.3" + }, + "autoload": { + "psr-4": { + "Ergonode\\Condition\\": "src/" + } + } +} diff --git a/module/condition/migrations/Version20190910151314.php b/module/condition/migrations/Version20190910151314.php new file mode 100644 index 000000000..925974eab --- /dev/null +++ b/module/condition/migrations/Version20190910151314.php @@ -0,0 +1,67 @@ +addSql(' + CREATE TABLE condition_set ( + id UUID NOT NULL, + code VARCHAR(100) NOT NULL, + name JSON NOT NULL, + description JSON NOT NULL, + conditions JSONB NOT NULL, + PRIMARY KEY(id) + ) + '); + $this->addSql('CREATE UNIQUE index condition_set_code_uindex ON condition_set (code)'); + + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CONDITION_CREATE', 'Condition']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CONDITION_READ', 'Condition']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CONDITION_UPDATE', 'Condition']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'CONDITION_DELETE', 'Condition']); + + $this->createEventStoreEvents([ + 'Ergonode\Condition\Domain\Event\ConditionSetCreatedEvent' => 'Condition set created', + 'Ergonode\Condition\Domain\Event\ConditionSetDeletedEvent' => 'Condition set deleted', + 'Ergonode\Condition\Domain\Event\ConditionSetDescriptionChangedEvent' => 'Condition set description changed', + 'Ergonode\Condition\Domain\Event\ConditionSetNameChangedEvent' => 'Condition set name changed', + 'Ergonode\Condition\Domain\Event\ConditionSetConditionsChangedEvent' => 'Condition set conditions changed', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } +} diff --git a/module/condition/src/Application/Controller/Api/ConditionController.php b/module/condition/src/Application/Controller/Api/ConditionController.php new file mode 100644 index 000000000..0b1df8a49 --- /dev/null +++ b/module/condition/src/Application/Controller/Api/ConditionController.php @@ -0,0 +1,83 @@ +provider = $provider; + } + + /** + * @Route("/conditions/{condition}", methods={"GET"}) + * + * @IsGranted("CONDITION_READ") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="condition", + * in="path", + * type="string", + * description="Condition ID" + * ) + * @SWG\Response( + * response=200, + * description="Returns condition" + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * + * @param Language $language + * @param string $condition + * + * @return Response + */ + public function getCondition(Language $language, string $condition): Response + { + try { + $configuration = $this->provider->getConfiguration($language, $condition); + } catch (ConditionStrategyNotFoundException $exception) { + throw new NotFoundHttpException($exception->getMessage()); + } + + return new SuccessResponse($configuration); + } +} diff --git a/module/condition/src/Application/Controller/Api/ConditionSetController.php b/module/condition/src/Application/Controller/Api/ConditionSetController.php new file mode 100644 index 000000000..88101edd3 --- /dev/null +++ b/module/condition/src/Application/Controller/Api/ConditionSetController.php @@ -0,0 +1,402 @@ +validator = $validator; + $this->messageBus = $messageBus; + $this->serializer = $serializer; + $this->conditionSetGrid = $conditionSetGrid; + $this->conditionSetQuery = $conditionSetQuery; + $this->createConditionSetValidatorBuilder = $createConditionSetValidatorBuilder; + $this->updateConditionSetValidatorBuilder = $updateConditionSetValidatorBuilder; + $this->segmentQuery = $segmentQuery; + } + + /** + * @Route("/conditionsets", methods={"GET"}) + * + * @IsGranted("CONDITION_READ") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="limit", + * in="query", + * type="integer", + * required=true, + * default="50", + * description="Number of returned lines", + * ) + * @SWG\Parameter( + * name="offset", + * in="query", + * type="integer", + * required=true, + * default="0", + * description="Number of start line", + * ) + * @SWG\Parameter( + * name="field", + * in="query", + * required=false, + * type="string", + * enum={"id", "code", "name", "description"}, + * description="Order field", + * ) + * @SWG\Parameter( + * name="order", + * in="query", + * required=false, + * type="string", + * enum={"ASC", "DESC"}, + * description="Order", + * ) + * @SWG\Parameter( + * name="filter", + * in="query", + * required=false, + * type="string", + * description="Filter" + * ) + * @SWG\Parameter( + * name="show", + * in="query", + * required=false, + * type="string", + * enum={"COLUMN", "DATA"}, + * description="Specify what response should containts" + * ) + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * required=true, + * default="EN", + * description="Language Code", + * ) + * @SWG\Response( + * response=200, + * description="Returns condition set collection", + * ) + * + * @ParamConverter(class="Ergonode\Grid\RequestGridConfiguration") + * + * @param Language $language + * @param RequestGridConfiguration $configuration + * + * @return Response + */ + public function getConditionSets(Language $language, RequestGridConfiguration $configuration): Response + { + return new GridResponse( + $this->conditionSetGrid, + $configuration, + $this->conditionSetQuery->getDataSet($language), + $language + ); + } + + /** + * @Route("/conditionsets/{conditionSet}", methods={"GET"}, requirements={"conditionSet"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("CONDITION_READ") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="conditionSet", + * in="path", + * type="string", + * description="Conditionset ID" + * ) + * @SWG\Response( + * response=200, + * description="Returns conditionset" + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * + * @ParamConverter(class="Ergonode\Condition\Domain\Entity\ConditionSet") + * + * @param ConditionSet $conditionSet + * + * @return Response + */ + public function getConditionSet(ConditionSet $conditionSet): Response + { + return new SuccessResponse($conditionSet); + } + + /** + * @Route("/conditionsets", methods={"POST"}) + * + * @IsGranted("CONDITION_CREATE") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * description="Create condition set", + * required=true, + * @SWG\Schema(ref="#/definitions/conditionset_create") + * ) + * @SWG\Response( + * response=201, + * description="Returns condition ID" + * ) + * @SWG\Response( + * response=400, + * description="Validation error", + * @SWG\Schema(ref="#/definitions/validation_error_response") + * ) + * + * @param Request $request + * + * @return Response + */ + public function createConditionSet(Request $request): Response + { + $data = $request->request->all(); + + $violations = $this->validator->validate($data, $this->createConditionSetValidatorBuilder->build($data)); + if (0 === $violations->count()) { + $data['id'] = ConditionSetId::fromCode(new ConditionSetCode($data['code']))->getValue(); + $data['name'] = $data['name'] ?? []; + $data['description'] = $data['description'] ?? []; + + /** @var CreateConditionSetCommand $command */ + $command = $this->serializer->fromArray($data, CreateConditionSetCommand::class); + $this->messageBus->dispatch($command); + + return new CreatedResponse($command->getId()); + } + + throw new ViolationsHttpException($violations); + } + + /** + * @Route("/conditionsets/{conditionSet}", methods={"PUT"}, requirements={"conditionSet"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("CONDITION_UPDATE") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * description="Update condition set", + * required=true, + * @SWG\Schema(ref="#/definitions/conditionset_update") + * ) + * @SWG\Response( + * response=204, + * description="Success" + * ) + * @SWG\Response( + * response=400, + * description="Validation error", + * @SWG\Schema(ref="#/definitions/validation_error_response") + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * + * @ParamConverter(class="Ergonode\Condition\Domain\Entity\ConditionSet") + * + * @param ConditionSet $conditionSet + * @param Request $request + * + * @return Response + */ + public function updateConditionSet(ConditionSet $conditionSet, Request $request): Response + { + $data = $request->request->all(); + + $violations = $this->validator->validate($data, $this->updateConditionSetValidatorBuilder->build($data)); + if (0 === $violations->count()) { + $data['id'] = $conditionSet->getId()->getValue(); + + /** @var UpdateConditionSetCommand $command */ + $command = $this->serializer->fromArray($data, UpdateConditionSetCommand::class); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } + + throw new ViolationsHttpException($violations); + } + + /** + * @Route("/conditionsets/{conditionSet}", methods={"DELETE"}, requirements={"conditionSet"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("CONDITION_DELETE") + * + * @SWG\Tag(name="Condition") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="conditionSet", + * in="path", + * required=true, + * type="string", + * description="Condition set ID", + * ) + * @SWG\Response( + * response=204, + * description="Success" + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * @SWG\Response( + * response=409, + * description="Existing relations" + * ) + * + * @ParamConverter(class="Ergonode\Condition\Domain\Entity\ConditionSet") + * + * @param ConditionSet $conditionSet + * + * @return Response + */ + public function deleteConditionSet(ConditionSet $conditionSet): Response + { + $segments = $this->segmentQuery->findIdByConditionSetId($conditionSet->getId()); + if (0 !== count($segments)) { + throw new ConflictHttpException('Cannot delete condition set. Segments are assigned to it'); + } + + $command = new DeleteConditionSetCommand($conditionSet->getId()); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } +} diff --git a/module/condition/src/Application/Controller/Api/DictionaryController.php b/module/condition/src/Application/Controller/Api/DictionaryController.php new file mode 100644 index 000000000..31b076e07 --- /dev/null +++ b/module/condition/src/Application/Controller/Api/DictionaryController.php @@ -0,0 +1,68 @@ +provider = $provider; + } + + /** + * @Route("conditions", methods={"GET"}) + * + * @SWG\Tag(name="Dictionary") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * required=true, + * default="EN", + * description="Language Code" + * ) + * @SWG\Response( + * response=200, + * description="Returns dictionary of available conditions" + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * + * @param Language $language + * + * @return Response + */ + public function getDictionary(Language $language): Response + { + $dictionary = $this->provider->getDictionary($language); + + return new SuccessResponse($dictionary); + } +} diff --git a/module/condition/src/Application/DependencyInjection/CompilerPass/ConditionConfiguratorCompilerPass.php b/module/condition/src/Application/DependencyInjection/CompilerPass/ConditionConfiguratorCompilerPass.php new file mode 100644 index 000000000..36bcb17a4 --- /dev/null +++ b/module/condition/src/Application/DependencyInjection/CompilerPass/ConditionConfiguratorCompilerPass.php @@ -0,0 +1,49 @@ +has(ConditionConfigurationProvider::class)) { + $this->processTransformers($container); + } + } + + /** + * @param ContainerBuilder $container + */ + private function processTransformers(ContainerBuilder $container): void + { + $arguments = []; + $definition = $container->findDefinition(ConditionConfigurationProvider::class); + $strategies = $container->findTaggedServiceIds(self::TAG); + + foreach ($strategies as $id => $strategy) { + $arguments[] = new Reference($id); + } + + $definition->setArguments($arguments); + } +} diff --git a/module/condition/src/Application/DependencyInjection/ErgonodeConditionExtension.php b/module/condition/src/Application/DependencyInjection/ErgonodeConditionExtension.php new file mode 100644 index 000000000..ffef94940 --- /dev/null +++ b/module/condition/src/Application/DependencyInjection/ErgonodeConditionExtension.php @@ -0,0 +1,42 @@ +registerForAutoconfiguration(ConditionConfigurationStrategyInteface::class) + ->addTag(ConditionConfiguratorCompilerPass::TAG); + + $loader->load('services.yml'); + } +} diff --git a/module/condition/src/Application/Request/ParamConverter/ConditionSetParamConverter.php b/module/condition/src/Application/Request/ParamConverter/ConditionSetParamConverter.php new file mode 100644 index 000000000..1a1b54430 --- /dev/null +++ b/module/condition/src/Application/Request/ParamConverter/ConditionSetParamConverter.php @@ -0,0 +1,69 @@ +conditionSetRepository = $conditionSetRepository; + } + + /** + * {@inheritDoc} + */ + public function apply(Request $request, ParamConverter $configuration): void + { + $parameter = $request->get('conditionSet'); + + if (null === $parameter) { + throw new BadRequestHttpException('Route parameter "conditionSet" is missing'); + } + + if (!ConditionSetId::isValid($parameter)) { + throw new BadRequestHttpException('Invalid condition set ID format'); + } + + $entity = $this->conditionSetRepository->load(new ConditionSetId($parameter)); + + if (null === $entity) { + throw new NotFoundHttpException(sprintf('Condition set by ID "%s" not found', $parameter)); + } + + $request->attributes->set($configuration->getName(), $entity); + } + + /** + * {@inheritDoc} + */ + public function supports(ParamConverter $configuration): bool + { + return ConditionSet::class === $configuration->getClass(); + } +} diff --git a/module/condition/src/Application/Validator/ConstraintAttributeExistsCondition.php b/module/condition/src/Application/Validator/ConstraintAttributeExistsCondition.php new file mode 100644 index 000000000..ddb0869f2 --- /dev/null +++ b/module/condition/src/Application/Validator/ConstraintAttributeExistsCondition.php @@ -0,0 +1,25 @@ +attributeQuery = $attributeQuery; + } + + /** + * {@inheritDoc} + */ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof ConstraintAttributeExistsCondition) { + throw new UnexpectedTypeException($constraint, ConstraintAttributeExistsCondition::class); + } + + if (!is_array($value)) { + throw new UnexpectedValueException($value, 'array'); + } + + if (!array_key_exists('code', $value)) { + $this->context + ->buildViolation('Attribute code not set') + ->addViolation(); + } + + $code = new AttributeCode($value['code']); + if (null === $this->attributeQuery->findAttributeByCode($code)) { + $this->context + ->buildViolation('Attribute code "value" not found') + ->setParameter('value', $value['code']) + ->atPath('code') + ->addViolation(); + } + } +} diff --git a/module/condition/src/Domain/Command/CreateConditionSetCommand.php b/module/condition/src/Domain/Command/CreateConditionSetCommand.php new file mode 100644 index 000000000..e5e5ba4ca --- /dev/null +++ b/module/condition/src/Domain/Command/CreateConditionSetCommand.php @@ -0,0 +1,93 @@ +id = ConditionSetId::fromCode($code); + $this->code = $code; + $this->name = $name; + $this->description = $description; + } + + /** + * @return ConditionSetId + */ + public function getId(): ConditionSetId + { + return $this->id; + } + + /** + * @return ConditionSetCode + */ + public function getCode(): ConditionSetCode + { + return $this->code; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } +} diff --git a/module/condition/src/Domain/Command/DeleteConditionSetCommand.php b/module/condition/src/Domain/Command/DeleteConditionSetCommand.php new file mode 100644 index 000000000..3db9ef693 --- /dev/null +++ b/module/condition/src/Domain/Command/DeleteConditionSetCommand.php @@ -0,0 +1,41 @@ +id = $id; + } + + /** + * @return ConditionSetId + */ + public function getId(): ConditionSetId + { + return $this->id; + } +} diff --git a/module/condition/src/Domain/Command/UpdateConditionSetCommand.php b/module/condition/src/Domain/Command/UpdateConditionSetCommand.php new file mode 100644 index 000000000..09c1f3e6a --- /dev/null +++ b/module/condition/src/Domain/Command/UpdateConditionSetCommand.php @@ -0,0 +1,117 @@ +") + */ + private $conditions; + + /** + * @param ConditionSetId $id + * @param array $conditions + * @param TranslatableString|null $name + * @param TranslatableString|null $description + */ + public function __construct( + ConditionSetId $id, + array $conditions, + ?TranslatableString $name = null, + ?TranslatableString $description = null + ) { + Assert::allIsInstanceOf($conditions, ConditionInterface::class); + + $this->id = $id; + $this->name = $name; + $this->description = $description; + $this->conditions = $conditions; + } + + /** + * @return ConditionSetId + */ + public function getId(): ConditionSetId + { + return $this->id; + } + + /** + * @return bool + */ + public function hasName(): bool + { + return $this->name instanceof TranslatableString; + } + + /** + * @return TranslatableString|null + */ + public function getName(): ?TranslatableString + { + return $this->name; + } + + /** + * @return bool + */ + public function hasDescription(): bool + { + return $this->description instanceof TranslatableString; + } + + /** + * @return TranslatableString|null + */ + public function getDescription(): ?TranslatableString + { + return $this->description; + } + + /** + * @return ConditionInterface[] + */ + public function getConditions(): array + { + return $this->conditions; + } +} diff --git a/module/condition/src/Domain/Condition/AttributeExistsCondition.php b/module/condition/src/Domain/Condition/AttributeExistsCondition.php new file mode 100644 index 000000000..a2cebc3cc --- /dev/null +++ b/module/condition/src/Domain/Condition/AttributeExistsCondition.php @@ -0,0 +1,54 @@ +attribute = $attribute; + } + + /** + * {@inheritDoc} + * + * @JMS\VirtualProperty() + */ + public function getType(): string + { + return self::TYPE; + } + + /** + * @return AttributeId + */ + public function getAttribute(): AttributeId + { + return $this->attribute; + } +} diff --git a/module/condition/src/Domain/Condition/ConditionInterface.php b/module/condition/src/Domain/Condition/ConditionInterface.php new file mode 100644 index 000000000..172f005ba --- /dev/null +++ b/module/condition/src/Domain/Condition/ConditionInterface.php @@ -0,0 +1,20 @@ +attribute = $attribute; + $this->operator = $operator; + $this->value = $value; + } + + /** + * {@inheritDoc} + * + * @JMS\VirtualProperty() + */ + public function getType(): string + { + return self::TYPE; + } + + /** + * @return AttributeId + */ + public function getAttribute(): AttributeId + { + return $this->attribute; + } + + /** + * @return string + */ + public function getOption(): string + { + return $this->operator; + } + + /** + * @return float + */ + public function getValue(): float + { + return $this->value; + } +} diff --git a/module/condition/src/Domain/Condition/OptionAttributeValueCondition.php b/module/condition/src/Domain/Condition/OptionAttributeValueCondition.php new file mode 100644 index 000000000..a87cc9b9b --- /dev/null +++ b/module/condition/src/Domain/Condition/OptionAttributeValueCondition.php @@ -0,0 +1,71 @@ +attribute = $attribute; + $this->value = $value; + } + + /** + * {@inheritDoc} + * + * @JMS\VirtualProperty() + */ + public function getType(): string + { + return self::TYPE; + } + + /** + * @return AttributeId + */ + public function getAttribute(): AttributeId + { + return $this->attribute; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/module/condition/src/Domain/Condition/TextAttributeValueCondition.php b/module/condition/src/Domain/Condition/TextAttributeValueCondition.php new file mode 100644 index 000000000..0181a58d2 --- /dev/null +++ b/module/condition/src/Domain/Condition/TextAttributeValueCondition.php @@ -0,0 +1,89 @@ +attribute = $attribute; + $this->operator = $operator; + $this->value = $value; + } + + /** + * {@inheritDoc} + * + * @JMS\VirtualProperty() + */ + public function getType(): string + { + return self::TYPE; + } + + + /** + * @return AttributeId + */ + public function getAttribute(): AttributeId + { + return $this->attribute; + } + + /** + * @return string + */ + public function getOption(): string + { + return $this->operator; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } +} diff --git a/module/condition/src/Domain/Entity/ConditionSet.php b/module/condition/src/Domain/Entity/ConditionSet.php new file mode 100644 index 000000000..e4c246dff --- /dev/null +++ b/module/condition/src/Domain/Entity/ConditionSet.php @@ -0,0 +1,201 @@ +") + * @JMS\Expose() + */ + private $conditions; + + /** + * @param ConditionSetId $id + * @param ConditionSetCode $code + * @param TranslatableString $name + * @param TranslatableString $description + * @param array $conditions + * + * @throws \Exception + */ + public function __construct( + ConditionSetId $id, + ConditionSetCode $code, + TranslatableString $name, + TranslatableString $description, + array $conditions = [] + ) { + Assert::allIsInstanceOf($conditions, ConditionInterface::class); + + $this->apply(new ConditionSetCreatedEvent($id, $code, $name, $description, $conditions)); + } + + /** + * @return ConditionSetId|AbstractId + */ + public function getId(): AbstractId + { + return $this->id; + } + + /** + * @return ConditionSetCode + */ + public function getCode(): ConditionSetCode + { + return $this->code; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } + + /** + * @return ConditionInterface[] + */ + public function getConditions(): array + { + return $this->conditions; + } + + /** + * @param TranslatableString $name + * + * @throws \Exception + */ + public function changeName(TranslatableString $name): void + { + if (!$name->isEqual($this->name)) { + $this->apply(new ConditionSetNameChangedEvent($this->name, $name)); + } + } + + /** + * @param TranslatableString $description + * + * @throws \Exception + */ + public function changeDescription(TranslatableString $description): void + { + if (!$description->isEqual($this->description)) { + $this->apply(new ConditionSetDescriptionChangedEvent($this->description, $description)); + } + } + + /** + * @param array $conditions + * + * @throws \Exception + */ + public function changeConditons(array $conditions): void + { + if (sha1(serialize($this->conditions)) !== sha1(serialize($conditions))) { + $this->apply(new ConditionSetConditionsChangedEvent($this->conditions, $conditions)); + } + } + + /** + * @param ConditionSetCreatedEvent $event + */ + protected function applyConditionSetCreatedEvent(ConditionSetCreatedEvent $event): void + { + $this->id = $event->getId(); + $this->code = $event->getCode(); + $this->name = $event->getName(); + $this->description = $event->getDescription(); + $this->conditions = $event->getConditions(); + } + + /** + * @param ConditionSetNameChangedEvent $event + */ + protected function applyConditionSetNameChangedEvent(ConditionSetNameChangedEvent $event): void + { + $this->name = $event->getTo(); + } + + /** + * @param ConditionSetDescriptionChangedEvent $event + */ + protected function applyConditionSetDescriptionChangedEvent(ConditionSetDescriptionChangedEvent $event): void + { + $this->description = $event->getTo(); + } + + /** + * @param ConditionSetConditionsChangedEvent $event + */ + protected function applyConditionSetConditionsChangedEvent(ConditionSetConditionsChangedEvent $event): void + { + $this->conditions = $event->getTo(); + } +} diff --git a/module/condition/src/Domain/Entity/ConditionSetId.php b/module/condition/src/Domain/Entity/ConditionSetId.php new file mode 100644 index 000000000..df8ca94f3 --- /dev/null +++ b/module/condition/src/Domain/Entity/ConditionSetId.php @@ -0,0 +1,32 @@ +getValue())->toString()); + } +} diff --git a/module/condition/src/Domain/Event/ConditionSetConditionsChangedEvent.php b/module/condition/src/Domain/Event/ConditionSetConditionsChangedEvent.php new file mode 100644 index 000000000..cea8cf729 --- /dev/null +++ b/module/condition/src/Domain/Event/ConditionSetConditionsChangedEvent.php @@ -0,0 +1,63 @@ +") + */ + private $from; + + /** + * @var ConditionInterface[] + * + * @JMS\Type("array") + */ + private $to; + + /** + * @param array $from + * @param array $to + */ + public function __construct(array $from, array $to) + { + Assert::allIsInstanceOf($from, ConditionInterface::class); + Assert::allIsInstanceOf($to, ConditionInterface::class); + + $this->from = $from; + $this->to = $to; + } + + /** + * @return ConditionInterface[] + */ + public function getFrom(): array + { + return $this->from; + } + + /** + * @return ConditionInterface[] + */ + public function getTo(): array + { + return $this->to; + } +} diff --git a/module/condition/src/Domain/Event/ConditionSetCreatedEvent.php b/module/condition/src/Domain/Event/ConditionSetCreatedEvent.php new file mode 100644 index 000000000..b2577c397 --- /dev/null +++ b/module/condition/src/Domain/Event/ConditionSetCreatedEvent.php @@ -0,0 +1,117 @@ +") + */ + private $conditions = []; + + /** + * @param ConditionSetId $id + * @param ConditionSetCode $code + * @param TranslatableString $name + * @param TranslatableString $description + * @param array $conditions + */ + public function __construct( + ConditionSetId $id, + ConditionSetCode $code, + TranslatableString $name, + TranslatableString $description, + array $conditions = [] + ) { + $this->id = $id; + $this->code = $code; + $this->name = $name; + $this->description = $description; + $this->conditions = $conditions; + } + + /** + * @return ConditionSetId + */ + public function getId(): ConditionSetId + { + return $this->id; + } + + /** + * @return ConditionSetCode + */ + public function getCode(): ConditionSetCode + { + return $this->code; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } + + /** + * @return array + */ + public function getConditions(): array + { + return $this->conditions; + } +} diff --git a/module/condition/src/Domain/Event/ConditionSetDeletedEvent.php b/module/condition/src/Domain/Event/ConditionSetDeletedEvent.php new file mode 100644 index 000000000..f4364f38b --- /dev/null +++ b/module/condition/src/Domain/Event/ConditionSetDeletedEvent.php @@ -0,0 +1,18 @@ +strategies = $strategies; + } + + /** + * @param Language $language + * @param string $type + * + * @return array + * + * @throws ConditionStrategyNotFoundException + */ + public function getConfiguration(Language $language, string $type): array + { + foreach ($this->strategies as $strategy) { + if ($strategy->isSupportedBy($type)) { + return $strategy->getConfiguration($language); + } + } + + throw new ConditionStrategyNotFoundException($type); + } +} diff --git a/module/condition/src/Domain/Provider/ConditionDictionaryProvider.php b/module/condition/src/Domain/Provider/ConditionDictionaryProvider.php new file mode 100644 index 000000000..a79543c6f --- /dev/null +++ b/module/condition/src/Domain/Provider/ConditionDictionaryProvider.php @@ -0,0 +1,50 @@ +translator = $translator; + } + + /** + * @param Language $language + * + * @return array + */ + public function getDictionary(Language $language): array + { + return [ + AttributeExistsCondition::TYPE => $this->translator->trans(AttributeExistsCondition::TYPE, [], 'condition', $language->getCode()), + TextAttributeValueCondition::TYPE => $this->translator->trans(TextAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + OptionAttributeValueCondition::TYPE => $this->translator->trans(OptionAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + NumericAttributeValueCondition::TYPE => $this->translator->trans(NumericAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + ]; + } +} diff --git a/module/condition/src/Domain/Query/ConditionSetQueryInterface.php b/module/condition/src/Domain/Query/ConditionSetQueryInterface.php new file mode 100644 index 000000000..33a5c914f --- /dev/null +++ b/module/condition/src/Domain/Query/ConditionSetQueryInterface.php @@ -0,0 +1,33 @@ +strategies = $strategies; + } + + /** + * @param ConditionInterface $condition + * @param Language $language + * + * @return array + */ + public function getConfiguration(ConditionInterface $condition, Language $language): array + { + foreach ($this->strategies as $strategy) { + if ($strategy->isSupportedBy($condition->getType())) { + return $strategy->getConfiguration($language); + } + } + + throw new \RuntimeException(sprintf('Can\'t find strategy for "%s" condition', get_class($condition))); + } +} diff --git a/module/condition/src/Domain/Service/ConditionVerifier.php b/module/condition/src/Domain/Service/ConditionVerifier.php new file mode 100644 index 000000000..89a513958 --- /dev/null +++ b/module/condition/src/Domain/Service/ConditionVerifier.php @@ -0,0 +1,66 @@ +strategies = $strategies; + } + + /** + * @param ConditionSet $conditionSet + * @param AbstractProduct $product + * + * @return bool + */ + public function verify(ConditionSet $conditionSet, AbstractProduct $product): bool + { + foreach ($conditionSet->getConditions() as $condition) { + $strategy = $this->getStrategy($condition); + if (!$strategy->verify($product, $condition)) { + return false; + } + } + + return true; + } + + /** + * @param ConditionInterface $condition + * + * @return ConditionVerifierStrategyInterface + */ + private function getStrategy(ConditionInterface $condition): ConditionVerifierStrategyInterface + { + foreach ($this->strategies as $strategy) { + if ($strategy->isSupportedBy($condition->getType())) { + return $strategy; + } + } + + throw new \RuntimeException(sprintf('Can\'t find strategy for "%s" condition', get_class($condition))); + } +} diff --git a/module/condition/src/Domain/Service/ConditionVerifierStrategyInterface.php b/module/condition/src/Domain/Service/ConditionVerifierStrategyInterface.php new file mode 100644 index 000000000..b17fcdb5e --- /dev/null +++ b/module/condition/src/Domain/Service/ConditionVerifierStrategyInterface.php @@ -0,0 +1,33 @@ +query = $query; + $this->translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function isSupportedBy(string $type): bool + { + return AttributeExistsCondition::TYPE === $type; + } + + /** + * {@inheritDoc} + */ + public function getConfiguration(Language $language): array + { + $codes = $this->query->getDictionary(); + + return [ + 'type' => AttributeExistsCondition::TYPE, + 'name' => $this->translator->trans(AttributeExistsCondition::TYPE, [], 'condition', $language->getCode()), + 'phrase' => $this->translator->trans(AttributeExistsCondition::PHRASE, [], 'condition', $language->getCode()), + 'parameters' => [ + [ + 'name' => 'attribute', + 'type' => 'SELECT', + 'options' => $codes, + ], + ], + ]; + } +} diff --git a/module/condition/src/Domain/Service/Strategy/AttributeExistsConditionVerifierStrategy.php b/module/condition/src/Domain/Service/Strategy/AttributeExistsConditionVerifierStrategy.php new file mode 100644 index 000000000..a44a6cc4a --- /dev/null +++ b/module/condition/src/Domain/Service/Strategy/AttributeExistsConditionVerifierStrategy.php @@ -0,0 +1,36 @@ +hasAttribute($configuration->getCode()); + } +} diff --git a/module/condition/src/Domain/Service/Strategy/NumericAttributeValueConditionConfigurationStrategy.php b/module/condition/src/Domain/Service/Strategy/NumericAttributeValueConditionConfigurationStrategy.php new file mode 100644 index 000000000..ecb805283 --- /dev/null +++ b/module/condition/src/Domain/Service/Strategy/NumericAttributeValueConditionConfigurationStrategy.php @@ -0,0 +1,87 @@ +query = $query; + $this->translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function isSupportedBy(string $type): bool + { + return NumericAttributeValueCondition::TYPE === $type; + } + + /** + * {@inheritDoc} + */ + public function getConfiguration(Language $language): array + { + $codes = $this->query->getDictionary([NumericAttribute::TYPE]); + + return [ + 'type' => NumericAttributeValueCondition::TYPE, + 'name' => $this->translator->trans(NumericAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + 'phrase' => $this->translator->trans(NumericAttributeValueCondition::PHRASE, [], 'condition', $language->getCode()), + 'parameters' => [ + [ + 'name' => 'attribute', + 'type' => 'SELECT', + 'options' => $codes, + ], + [ + 'name' => 'operator', + 'type' => 'SELECT', + 'options' => [ + '=' => '=', + '<>' => '<>', + '>' => '>', + '<' => '<', + '>=' => '>=', + '<=' => '<=', + ], + ], + [ + 'name' => 'value', + 'type' => 'TEXT', + ], + ], + ]; + } +} diff --git a/module/condition/src/Domain/Service/Strategy/OptionAttributeValueConditionConfigurationStrategy.php b/module/condition/src/Domain/Service/Strategy/OptionAttributeValueConditionConfigurationStrategy.php new file mode 100644 index 000000000..004af2ff4 --- /dev/null +++ b/module/condition/src/Domain/Service/Strategy/OptionAttributeValueConditionConfigurationStrategy.php @@ -0,0 +1,76 @@ +query = $query; + $this->translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function isSupportedBy(string $type): bool + { + return OptionAttributeValueCondition::TYPE === $type; + } + + /** + * {@inheritDoc} + */ + public function getConfiguration(Language $language): array + { + $codes = $this->query->getDictionary([SelectAttribute::TYPE, MultiSelectAttribute::TYPE]); + + return [ + 'type' => OptionAttributeValueCondition::TYPE, + 'name' => $this->translator->trans(OptionAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + 'phrase' => $this->translator->trans(OptionAttributeValueCondition::PHRASE, [], 'condition', $language->getCode()), + 'parameters' => [ + [ + 'name' => 'attribute', + 'type' => 'SELECT', + 'options' => $codes, + ], + [ + 'name' => 'value', + 'type' => 'TEXT', + ], + ], + ]; + } +} diff --git a/module/condition/src/Domain/Service/Strategy/TextAttributeValueConditionConfigurationStrategy.php b/module/condition/src/Domain/Service/Strategy/TextAttributeValueConditionConfigurationStrategy.php new file mode 100644 index 000000000..d1749e1a0 --- /dev/null +++ b/module/condition/src/Domain/Service/Strategy/TextAttributeValueConditionConfigurationStrategy.php @@ -0,0 +1,84 @@ +query = $query; + $this->translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function isSupportedBy(string $type): bool + { + return TextAttributeValueCondition::TYPE === $type; + } + + /** + * {@inheritDoc} + */ + public function getConfiguration(Language $language): array + { + $codes = $this->query->getDictionary([TextAttribute::TYPE, TextareaAttribute::TYPE]); + + return [ + 'type' => TextAttributeValueCondition::TYPE, + 'name' => $this->translator->trans(TextAttributeValueCondition::TYPE, [], 'condition', $language->getCode()), + 'phrase' => $this->translator->trans(TextAttributeValueCondition::PHRASE, [], 'condition', $language->getCode()), + 'parameters' => [ + [ + 'name' => 'attribute', + 'type' => 'SELECT', + 'options' => $codes, + ], + [ + 'name' => 'operator', + 'type' => 'SELECT', + 'options' => [ + '=' => $this->translator->trans('Is equal', [], 'condition', $language->getCode()), + '~' => $this->translator->trans('Has', [], 'condition', $language->getCode()), + ], + ], + [ + 'name' => 'value', + 'type' => 'TEXT', + ], + ], + ]; + } +} diff --git a/module/condition/src/Domain/ValueObject/ConditionSetCode.php b/module/condition/src/Domain/ValueObject/ConditionSetCode.php new file mode 100644 index 000000000..1360a8ed1 --- /dev/null +++ b/module/condition/src/Domain/ValueObject/ConditionSetCode.php @@ -0,0 +1,58 @@ +value = $value; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * @param string $value + * + * @return bool + */ + public static function isValid(string $value): bool + { + return strlen($value) <= 100; + } +} diff --git a/module/condition/src/ErgonodeConditionBundle.php b/module/condition/src/ErgonodeConditionBundle.php new file mode 100644 index 000000000..1dc4bf6f6 --- /dev/null +++ b/module/condition/src/ErgonodeConditionBundle.php @@ -0,0 +1,29 @@ +addCompilerPass(new ConditionConfiguratorCompilerPass()); + } +} diff --git a/module/condition/src/Infrastructure/Builder/Condition/AttributeExistsConditionValidatorBuilder.php b/module/condition/src/Infrastructure/Builder/Condition/AttributeExistsConditionValidatorBuilder.php new file mode 100644 index 000000000..da8ee0aaf --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/Condition/AttributeExistsConditionValidatorBuilder.php @@ -0,0 +1,38 @@ + [ + new NotBlank(), + new AttributeExists(), + ], + ] + ); + } +} diff --git a/module/condition/src/Infrastructure/Builder/Condition/NumericAttributeValueConditionValidatorBuilder.php b/module/condition/src/Infrastructure/Builder/Condition/NumericAttributeValueConditionValidatorBuilder.php new file mode 100644 index 000000000..9c623b13f --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/Condition/NumericAttributeValueConditionValidatorBuilder.php @@ -0,0 +1,48 @@ + [ + new NotBlank(), + new AttributeExists(), + ], + 'operator' => [ + new NotBlank(), + new Choice(['=', '<>', '>', '<', '>=', '<=']), + ], + 'value' => [ + new NotBlank(), + new Type('numeric'), + ], + ] + ); + } +} diff --git a/module/condition/src/Infrastructure/Builder/Condition/OptionAttributeValueConditionValidatorBuilder.php b/module/condition/src/Infrastructure/Builder/Condition/OptionAttributeValueConditionValidatorBuilder.php new file mode 100644 index 000000000..0e8e1e700 --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/Condition/OptionAttributeValueConditionValidatorBuilder.php @@ -0,0 +1,41 @@ + [ + new NotBlank(), + new AttributeExists(), + ], + 'value' => [ + new NotBlank(), + ], + ] + ); + } +} diff --git a/module/condition/src/Infrastructure/Builder/Condition/TextAttributeValueConditionValidatorBuilder.php b/module/condition/src/Infrastructure/Builder/Condition/TextAttributeValueConditionValidatorBuilder.php new file mode 100644 index 000000000..a31da2603 --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/Condition/TextAttributeValueConditionValidatorBuilder.php @@ -0,0 +1,46 @@ + [ + new NotBlank(), + new AttributeExists(), + ], + 'operator' => [ + new NotBlank(), + new Choice(['=', '~']), + ], + 'value' => [ + new NotBlank(), + ], + ] + ); + } +} diff --git a/module/condition/src/Infrastructure/Builder/ConditionValidatorBuilderInterface.php b/module/condition/src/Infrastructure/Builder/ConditionValidatorBuilderInterface.php new file mode 100644 index 000000000..cb3679c0a --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/ConditionValidatorBuilderInterface.php @@ -0,0 +1,24 @@ + [ + 'code' => [ + new NotBlank(), + new Length(['min' => 2, 'max' => 100]), + new UniqueConditionSetCode(), + ], + 'name' => [ + new Optional([ + new All([ + new Length(['min' => 2, 'max' => 255]), + ]), + ]), + ], + 'description' => [ + new Optional([ + new All([ + new Length(['max' => 255]), + ]), + ]), + ], + ], + ]); + } +} diff --git a/module/condition/src/Infrastructure/Builder/UpdateConditionSetValidatorBuilder.php b/module/condition/src/Infrastructure/Builder/UpdateConditionSetValidatorBuilder.php new file mode 100644 index 000000000..168cd1ace --- /dev/null +++ b/module/condition/src/Infrastructure/Builder/UpdateConditionSetValidatorBuilder.php @@ -0,0 +1,97 @@ +conditionConstraintResolver = $conditionConstraintResolver; + } + + /** + * @param array $data + * + * @return Constraint + */ + public function build(array $data): Constraint + { + $resolver = function ($data, ExecutionContextInterface $context, $payload) { + foreach ($data as $index => $condition) { + if (!is_array($condition)) { + throw new \InvalidArgumentException('Condition in condition set must be array type'); + } + + if (!array_key_exists('type', $condition)) { + throw new \InvalidArgumentException('Type not found in condition'); + } + + $constraint = $this->conditionConstraintResolver->resolve($condition['type'])->build($condition); + unset($condition['type']); + $violations = $context->getValidator()->validate($condition, $constraint); + if (0 !== $violations->count()) { + /** @var ConstraintViolation $violation */ + foreach ($violations as $violation) { + $path = sprintf('[%d]%s', $index, $violation->getPropertyPath()); + $context + ->buildViolation($violation->getMessage(), $violation->getParameters()) + ->atPath($path) + ->addViolation(); + } + } + } + }; + + return new Collection([ + 'fields' => [ + 'name' => [ + new Optional([ + new NotBlank(), + new All([ + new Length(['min' => 2, 'max' => 255]), + ]), + ]), + ], + 'description' => [ + new Optional([ + new NotBlank(), + new All([ + new Length(['max' => 255]), + ]), + ]), + ], + 'conditions' => [ + new Callback(['callback' => $resolver]), + ], + ], + ]); + } +} diff --git a/module/condition/src/Infrastructure/Grid/ConditionSetGrid.php b/module/condition/src/Infrastructure/Grid/ConditionSetGrid.php new file mode 100644 index 000000000..79d09936d --- /dev/null +++ b/module/condition/src/Infrastructure/Grid/ConditionSetGrid.php @@ -0,0 +1,64 @@ +translator = $translator; + } + + /** + * {@inheritDoc} + */ + public function init(GridConfigurationInterface $configuration, Language $language): void + { + $filters = $configuration->getFilters(); + + $id = new TextColumn('id', $this->trans('Id')); + $id->setVisible(false); + $this->addColumn('id', $id); + $this->addColumn('code', new TextColumn('code', $this->trans('Code'), new TextFilter($filters->getString('code')))); + $this->addColumn('name', new TextColumn('name', $this->trans('Name'), new TextFilter($filters->getString('name')))); + $this->addColumn('description', new TextColumn('description', $this->trans('Description'), new TextFilter($filters->getString('description')))); + $this->addColumn('edit', new ActionColumn('edit')); + $this->setConfiguration(self::PARAMETER_ALLOW_COLUMN_RESIZE, true); + } + + /** + * @param string $id + * @param array $parameters + * + * @return string + */ + private function trans(string $id, array $parameters = []): string + { + return $this->translator->trans($id, $parameters, 'grid'); + } +} diff --git a/module/condition/src/Infrastructure/Handler/CreateConditionSetCommandHandler.php b/module/condition/src/Infrastructure/Handler/CreateConditionSetCommandHandler.php new file mode 100644 index 000000000..5543f1c7d --- /dev/null +++ b/module/condition/src/Infrastructure/Handler/CreateConditionSetCommandHandler.php @@ -0,0 +1,49 @@ +repository = $repository; + } + + /** + * @param CreateConditionSetCommand $command + * + * @throws \Exception + */ + public function __invoke(CreateConditionSetCommand $command) + { + $segment = new ConditionSet( + $command->getId(), + $command->getCode(), + $command->getName(), + $command->getDescription() + ); + + $this->repository->save($segment); + } +} diff --git a/module/condition/src/Infrastructure/Handler/DeleteConditionSetCommandHandler.php b/module/condition/src/Infrastructure/Handler/DeleteConditionSetCommandHandler.php new file mode 100644 index 000000000..767f2fa91 --- /dev/null +++ b/module/condition/src/Infrastructure/Handler/DeleteConditionSetCommandHandler.php @@ -0,0 +1,45 @@ +repository = $repository; + } + + /** + * @param DeleteConditionSetCommand $command + * + * @throws \Exception + */ + public function __invoke(DeleteConditionSetCommand $command) + { + $conditionSet = $this->repository->load($command->getId()); + Assert::notNull($conditionSet); + + $this->repository->delete($conditionSet); + } +} diff --git a/module/condition/src/Infrastructure/Handler/UpdateConditionSetCommandHandler.php b/module/condition/src/Infrastructure/Handler/UpdateConditionSetCommandHandler.php new file mode 100644 index 000000000..9264aeec2 --- /dev/null +++ b/module/condition/src/Infrastructure/Handler/UpdateConditionSetCommandHandler.php @@ -0,0 +1,55 @@ +repository = $repository; + } + + /** + * @param UpdateConditionSetCommand $command + * + * @throws \Exception + */ + public function __invoke(UpdateConditionSetCommand $command) + { + $conditionSet = $this->repository->load($command->getId()); + Assert::notNull($conditionSet); + + $conditionSet->changeConditons($command->getConditions()); + + if ($command->hasName()) { + $conditionSet->changeName($command->getName()); + } + + if ($command->hasDescription()) { + $conditionSet->changeDescription($command->getDescription()); + } + + $this->repository->save($conditionSet); + } +} diff --git a/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionInterfaceHandler.php b/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionInterfaceHandler.php new file mode 100644 index 000000000..549c37fb1 --- /dev/null +++ b/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionInterfaceHandler.php @@ -0,0 +1,26 @@ + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'type' => ConditionSetCode::class, + 'format' => $format, + 'method' => 'serialize', + ]; + + $methods[] = [ + 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, + 'type' => ConditionSetCode::class, + 'format' => $format, + 'method' => 'deserialize', + ]; + } + + return $methods; + } + + /** + * @param SerializationVisitorInterface $visitor + * @param ConditionSetCode $status + * @param array $type + * @param Context $context + * + * @return string + */ + public function serialize(SerializationVisitorInterface $visitor, ConditionSetCode $status, array $type, Context $context): string + { + return (string) $status; + } + + /** + * @param DeserializationVisitorInterface $visitor + * @param mixed $data + * @param array $type + * @param Context $context + * + * @return ConditionSetCode + */ + public function deserialize(DeserializationVisitorInterface $visitor, $data, array $type, Context $context): ConditionSetCode + { + return new ConditionSetCode($data); + } +} diff --git a/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionSetIdHandler.php b/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionSetIdHandler.php new file mode 100644 index 000000000..fc687db07 --- /dev/null +++ b/module/condition/src/Infrastructure/JMS/Serializer/Handler/ConditionSetIdHandler.php @@ -0,0 +1,75 @@ + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'type' => ConditionSetId::class, + 'format' => $format, + 'method' => 'serialize', + ]; + + $methods[] = [ + 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, + 'type' => ConditionSetId::class, + 'format' => $format, + 'method' => 'deserialize', + ]; + } + + return $methods; + } + + /** + * @param SerializationVisitorInterface $visitor + * @param ConditionSetId $id + * @param array $type + * @param Context $context + * + * @return string + */ + public function serialize(SerializationVisitorInterface $visitor, ConditionSetId $id, array $type, Context $context): string + { + return $id->getValue(); + } + + /** + * @param DeserializationVisitorInterface $visitor + * @param mixed $data + * @param array $type + * @param Context $context + * + * @return ConditionSetId + */ + public function deserialize(DeserializationVisitorInterface $visitor, $data, array $type, Context $context): ConditionSetId + { + return new ConditionSetId($data); + } +} diff --git a/module/condition/src/Infrastructure/Resolver/ConditionConstraintResolver.php b/module/condition/src/Infrastructure/Resolver/ConditionConstraintResolver.php new file mode 100644 index 000000000..32d539949 --- /dev/null +++ b/module/condition/src/Infrastructure/Resolver/ConditionConstraintResolver.php @@ -0,0 +1,48 @@ +constraints[$type] = $constraintClass; + } + + /** + * @param string $type + * + * @return ConditionValidatorBuilderInterface + * + * @throws \OutOfBoundsException + */ + public function resolve(string $type): ConditionValidatorBuilderInterface + { + if (!array_key_exists($type, $this->constraints)) { + throw new \OutOfBoundsException(sprintf('Constraint by condition type "%s" not found', $type)); + } + + return $this->constraints[$type]; + } +} diff --git a/module/condition/src/Infrastructure/Validator/UniqueConditionSetCode.php b/module/condition/src/Infrastructure/Validator/UniqueConditionSetCode.php new file mode 100644 index 000000000..8e0f5d07d --- /dev/null +++ b/module/condition/src/Infrastructure/Validator/UniqueConditionSetCode.php @@ -0,0 +1,28 @@ +query = $query; + } + + /** + * @param mixed $value + * @param UniqueConditionSetCode|Constraint $constraint + */ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof UniqueConditionSetCode) { + throw new UnexpectedTypeException($constraint, UniqueConditionSetCode::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + + if (!ConditionSetCode::isValid($value)) { + $this->context->buildViolation($constraint->validMessage) + ->setParameter('{{ value }}', $value) + ->addViolation(); + + return; + } + + if ($this->query->isExistsByCode(new ConditionSetCode($value))) { + $this->context->buildViolation($constraint->uniqueMessage) + ->addViolation(); + } + } +} diff --git a/module/condition/src/Persistence/Dbal/Projector/ConditionSetConditionsChangedEventProjector.php b/module/condition/src/Persistence/Dbal/Projector/ConditionSetConditionsChangedEventProjector.php new file mode 100644 index 000000000..e0b6bceba --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Projector/ConditionSetConditionsChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ConditionSetConditionsChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ConditionSetConditionsChangedEvent) { + throw new UnsupportedEventException($event, ConditionSetConditionsChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'conditions' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/condition/src/Persistence/Dbal/Projector/ConditionSetCreatedEventProjector.php b/module/condition/src/Persistence/Dbal/Projector/ConditionSetCreatedEventProjector.php new file mode 100644 index 000000000..825e28613 --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Projector/ConditionSetCreatedEventProjector.php @@ -0,0 +1,79 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ConditionSetCreatedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ConditionSetCreatedEvent) { + throw new UnsupportedEventException($event, ConditionSetCreatedEvent::class); + } + + $this->connection->insert( + self::TABLE, + [ + 'id' => $event->getId()->getValue(), + 'code' => $event->getCode(), + 'name' => $this->serializer->serialize($event->getName(), 'json'), + 'description' => $this->serializer->serialize($event->getDescription(), 'json'), + 'conditions' => $this->serializer->serialize($event->getConditions(), 'json'), + ] + ); + } +} diff --git a/module/condition/src/Persistence/Dbal/Projector/ConditionSetDeletedEventProjector.php b/module/condition/src/Persistence/Dbal/Projector/ConditionSetDeletedEventProjector.php new file mode 100644 index 000000000..ea869432a --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Projector/ConditionSetDeletedEventProjector.php @@ -0,0 +1,62 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ConditionSetDeletedEvent; + } + + /** + * {@inheritDoc} + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ConditionSetDeletedEvent) { + throw new UnsupportedEventException($event, ConditionSetDeletedEvent::class); + } + + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/condition/src/Persistence/Dbal/Projector/ConditionSetDescriptionChangedEventProjector.php b/module/condition/src/Persistence/Dbal/Projector/ConditionSetDescriptionChangedEventProjector.php new file mode 100644 index 000000000..44a17e5b8 --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Projector/ConditionSetDescriptionChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ConditionSetDescriptionChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ConditionSetDescriptionChangedEvent) { + throw new UnsupportedEventException($event, ConditionSetDescriptionChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'description' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/condition/src/Persistence/Dbal/Projector/ConditionSetNameChangedEventProjector.php b/module/condition/src/Persistence/Dbal/Projector/ConditionSetNameChangedEventProjector.php new file mode 100644 index 000000000..769425c7b --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Projector/ConditionSetNameChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ConditionSetNameChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ConditionSetNameChangedEvent) { + throw new UnsupportedEventException($event, ConditionSetNameChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'name' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/condition/src/Persistence/Dbal/Query/DbalConditionSetQuery.php b/module/condition/src/Persistence/Dbal/Query/DbalConditionSetQuery.php new file mode 100644 index 000000000..14db6e155 --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Query/DbalConditionSetQuery.php @@ -0,0 +1,84 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function getDataSet(Language $language): DbalDataSet + { + $query = $this->getQuery(); + $query->addSelect(sprintf('(name->>\'%s\') AS name', $language->getCode())); + $query->addSelect(sprintf('(description->>\'%s\') AS description', $language->getCode())); + + $result = $this->connection->createQueryBuilder(); + $result->select('*'); + $result->from(sprintf('(%s)', $query->getSQL()), 't'); + + return new DbalDataSet($result); + } + + /** + * {@inheritDoc} + */ + public function isExistsByCode(ConditionSetCode $conditionSetCode): bool + { + $queryBuilder = $this->connection->createQueryBuilder() + ->select('id') + ->from(self::TABLE) + ->where('code = :code') + ->setParameter('code', $conditionSetCode->getValue()) + ->setMaxResults(1); + + $result = $queryBuilder->execute()->fetchColumn(); + + return !empty($result); + } + + /** + * @return QueryBuilder + */ + private function getQuery(): QueryBuilder + { + return $this->connection->createQueryBuilder() + ->select(self::FIELDS) + ->from(self::TABLE, 't'); + } +} diff --git a/module/condition/src/Persistence/Dbal/Repository/DbalConditionSetRepository.php b/module/condition/src/Persistence/Dbal/Repository/DbalConditionSetRepository.php new file mode 100644 index 000000000..117dbbaa2 --- /dev/null +++ b/module/condition/src/Persistence/Dbal/Repository/DbalConditionSetRepository.php @@ -0,0 +1,108 @@ +eventStore = $eventStore; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @param ConditionSetId $id + * + * @return AbstractAggregateRoot|null + * + * @throws \ReflectionException + */ + public function load(ConditionSetId $id): ?AbstractAggregateRoot + { + $eventStream = $this->eventStore->load($id); + + if (\count($eventStream) > 0) { + $class = new \ReflectionClass(ConditionSet::class); + /** @var AbstractAggregateRoot $aggregate */ + $aggregate = $class->newInstanceWithoutConstructor(); + if (!$aggregate instanceof AbstractAggregateRoot) { + throw new \LogicException(sprintf('Impossible to initialize "%s"', $class)); + } + + $aggregate->initialize($eventStream); + + return $aggregate; + } + + return null; + } + + /** + * @param AbstractAggregateRoot $aggregateRoot + */ + public function save(AbstractAggregateRoot $aggregateRoot): void + { + $events = $aggregateRoot->popEvents(); + + $this->eventStore->append($aggregateRoot->getId(), $events); + foreach ($events as $envelope) { + $this->eventDispatcher->dispatch($envelope); + } + } + + /** + * @param ConditionSetId $id + * + * @return bool + */ + public function exists(ConditionSetId $id): bool + { + $eventStream = $this->eventStore->load($id); + + return \count($eventStream) > 0; + } + + /** + * @param AbstractAggregateRoot $aggregateRoot + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new ConditionSetDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } +} diff --git a/module/condition/src/Resources/config/routes.yml b/module/condition/src/Resources/config/routes.yml new file mode 100644 index 000000000..bd2e16589 --- /dev/null +++ b/module/condition/src/Resources/config/routes.yml @@ -0,0 +1,14 @@ +ergonode_condition_dictionary: + resource: '../../Application/Controller/Api/Dictionary*' + type: 'annotation' + prefix: '/api/v1/{language}/dictionary' + +ergonode_condition_api: + resource: '../../Application/Controller/Api/Condition*' + type: 'annotation' + prefix: '/api/v1/{language}' + +ergonode_condition_set_api: + resource: '../../Application/Controller/Api/ConditionSet*' + type: 'annotation' + prefix: '/api/v1/{language}' diff --git a/module/condition/src/Resources/config/services.yml b/module/condition/src/Resources/config/services.yml new file mode 100644 index 000000000..6c0ce0884 --- /dev/null +++ b/module/condition/src/Resources/config/services.yml @@ -0,0 +1,47 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + _instanceof: + Ergonode\Condition\Domain\Service\ConfigurationStrategyInterface: + tags: ['component.condition.condition_set.configurator_interface'] + + Ergonode\Condition\Application\: + resource: '../../Application/*' + + Ergonode\Condition\Persistence\: + resource: '../../Persistence/*' + + Ergonode\Condition\Domain\: + resource: '../../Domain/*' + exclude: '../../Domain/{Entity,ValueObject,Condition}' + + Ergonode\Condition\Infrastructure\: + resource: '../../Infrastructure/*' + + Ergonode\Condition\Infrastructure\Handler\: + resource: '../../Infrastructure/Handler/*' + exclude: '../../Infrastructure/Handler/{Strategy}' + tags: ['messenger.message_handler'] + + Ergonode\Condition\Infrastructure\JMS\Serializer\Handler\: + resource: '../../Infrastructure/JMS/Serializer/Handler/*' + tags: ['jms_serializer.subscribing_handler'] + + Ergonode\Condition\Infrastructure\Resolver\ConditionConstraintResolver: + calls: + - ['set', ['ATTRIBUTE_EXISTS_CONDITION', '@Ergonode\Condition\Infrastructure\Builder\Condition\AttributeExistsConditionValidatorBuilder']] + - ['set', ['TEXT_ATTRIBUTE_VALUE_CONDITION', '@Ergonode\Condition\Infrastructure\Builder\Condition\TextAttributeValueConditionValidatorBuilder']] + - ['set', ['OPTION_ATTRIBUTE_VALUE_CONDITION', '@Ergonode\Condition\Infrastructure\Builder\Condition\OptionAttributeValueConditionValidatorBuilder']] + - ['set', ['NUMERIC_ATTRIBUTE_VALUE_CONDITION', '@Ergonode\Condition\Infrastructure\Builder\Condition\NumericAttributeValueConditionValidatorBuilder']] + + Ergonode\Condition\Infrastructure\JMS\Serializer\Handler\ConditionInterfaceHandler: + calls: + - ['set', ['Ergonode\Condition\Domain\Condition\AttributeExistsCondition']] + - ['set', ['Ergonode\Condition\Domain\Condition\NumericAttributeValueCondition']] + - ['set', ['Ergonode\Condition\Domain\Condition\OptionAttributeValueCondition']] + - ['set', ['Ergonode\Condition\Domain\Condition\TextAttributeValueCondition']] + + Ergonode\Condition\Domain\Query\ConditionSetQueryInterface: '@Ergonode\Condition\Persistence\Dbal\Query\DbalConditionSetQuery' + Ergonode\Condition\Domain\Repository\ConditionSetRepositoryInterface: '@Ergonode\Condition\Persistence\Dbal\Repository\DbalConditionSetRepository' diff --git a/module/condition/src/Resources/translations/condition.en.yaml b/module/condition/src/Resources/translations/condition.en.yaml new file mode 100644 index 000000000..c2966f2b5 --- /dev/null +++ b/module/condition/src/Resources/translations/condition.en.yaml @@ -0,0 +1,8 @@ +ATTRIBUTE_EXISTS_CONDITION: Attribute exists +ATTRIBUTE_EXISTS_CONDITION_PHRASE: Attribute [attribute] exists +NUMERIC_ATTRIBUTE_VALUE_CONDITION: Numeric attribute has value +NUMERIC_ATTRIBUTE_VALUE_CONDITION_PHRASE: Attribute [attribute] has value [operator] [value] +OPTION_ATTRIBUTE_VALUE_CONDITION: Option attribute has value +OPTION_ATTRIBUTE_VALUE_CONDITION_PHRASE: Attribute [attribute] has option [value] +TEXT_ATTRIBUTE_VALUE_CONDITION: Text attribute has value +TEXT_ATTRIBUTE_VALUE_CONDITION_PHRASE: Attribute [attribute] has value [operator] [value] diff --git a/module/condition/src/Resources/translations/condition.pl.yaml b/module/condition/src/Resources/translations/condition.pl.yaml new file mode 100644 index 000000000..ef6a249b4 --- /dev/null +++ b/module/condition/src/Resources/translations/condition.pl.yaml @@ -0,0 +1,8 @@ +ATTRIBUTE_EXISTS_CONDITION: Posiada attrybut +ATTRIBUTE_EXISTS_CONDITION_PHRASE: Posiada attrybut [attribute] +NUMERIC_ATTRIBUTE_VALUE_CONDITION: Atrybut liczbowy ma wartość +NUMERIC_ATTRIBUTE_VALUE_CONDITION_PHRASE: Atrybut [attribute] ma wartość [operator] [value] +OPTION_ATTRIBUTE_VALUE_CONDITION: Atrybut posiada opcję o wartości +OPTION_ATTRIBUTE_VALUE_CONDITION_PHRASE: Atrybut [attribute] ma opcję o wartości [value] +TEXT_ATTRIBUTE_VALUE_CONDITION: Atrybut textowy ma wartość +TEXT_ATTRIBUTE_VALUE_CONDITION_PHRASE: Atrybut [attribute] ma wartość [operator] [value] diff --git a/module/condition/src/Resources/translations/grid.en.yaml b/module/condition/src/Resources/translations/grid.en.yaml new file mode 100644 index 000000000..6d06acbd8 --- /dev/null +++ b/module/condition/src/Resources/translations/grid.en.yaml @@ -0,0 +1,5 @@ +Code: Code +Name: Name +Description: Description +Edit: Edit +Status: Status diff --git a/module/condition/src/Resources/translations/grid.pl.yaml b/module/condition/src/Resources/translations/grid.pl.yaml new file mode 100644 index 000000000..d52851ca9 --- /dev/null +++ b/module/condition/src/Resources/translations/grid.pl.yaml @@ -0,0 +1,5 @@ +Code: Kod +Name: Nazwa +Description: Opis +Edit: Edycja +Status: Status diff --git a/module/condition/src/Resources/translations/log.en.yaml b/module/condition/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..b549e0b41 --- /dev/null +++ b/module/condition/src/Resources/translations/log.en.yaml @@ -0,0 +1,5 @@ +'Condition set created': 'Condition set "%code%" created' +'Condition set deleted': 'Condition set "%id%" deleted' +'Condition set description changed': 'Condition set description changed' +'Condition set name changed': 'Condition set name changed' +'Condition set conditions changed': 'Condition set conditions changed' diff --git a/module/condition/src/Resources/translations/log.pl.yaml b/module/condition/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..aade6db69 --- /dev/null +++ b/module/condition/src/Resources/translations/log.pl.yaml @@ -0,0 +1,5 @@ +'Condition set created': 'Zbiór warunków "%code%" utworzony' +'Condition set deleted': 'Zbiór warunków "%id%" usunięty' +'Condition set description changed': 'Zmieniono opis w zbiorze warunków' +'Condition set name changed': 'Zmieniono nazwe w zbiorze warunków' +'Condition set conditions changed': 'Zmineniono warunki w zbiorze warunków' diff --git a/module/condition/src/Resources/translations/privilege.en.yaml b/module/condition/src/Resources/translations/privilege.en.yaml new file mode 100644 index 000000000..f78984bce --- /dev/null +++ b/module/condition/src/Resources/translations/privilege.en.yaml @@ -0,0 +1 @@ +Condition: Conditions diff --git a/module/condition/src/Resources/translations/privilege.pl.yaml b/module/condition/src/Resources/translations/privilege.pl.yaml new file mode 100644 index 000000000..7a93c6bad --- /dev/null +++ b/module/condition/src/Resources/translations/privilege.pl.yaml @@ -0,0 +1 @@ +Condition: Warunki diff --git a/module/core/composer.json b/module/core/composer.json index b2d6fc112..1064a2e21 100644 --- a/module/core/composer.json +++ b/module/core/composer.json @@ -7,9 +7,8 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/migration": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/migration": "^0.5.0", "jms/serializer": "^3.1", "moneyphp/money": "^3.2", "nelmio/api-doc-bundle": "^3.4", diff --git a/module/core/migrations/Version20180610093112.php b/module/core/migrations/Version20180610093112.php index 5cdeda1d0..d814a9580 100644 --- a/module/core/migrations/Version20180610093112.php +++ b/module/core/migrations/Version20180610093112.php @@ -36,20 +36,6 @@ public function up(Schema $schema): void ); $this->addSql('CREATE TABLE translation (translation_id UUID NOT NULL, language VARCHAR(2) NOT NULL, phrase VARCHAR(255), PRIMARY KEY(translation_id, language))'); - $this->addSql( - 'CREATE TABLE event_store ( - id BIGSERIAL NOT NULL, - aggregate_id uuid NOT NULL, - sequence int, - event character varying(255) NOT NULL, - payload jsonb NOT NULL, - recorded_by uuid default NULL, - recorded_at timestamp without time zone NOT NULL, - CONSTRAINT event_store_pkey PRIMARY KEY (id) - )' - ); - $this->addSql('CREATE UNIQUE INDEX event_store_aggregate_id_sequence_key ON event_store USING btree (aggregate_id, sequence)'); - foreach ($this->getLanguages() as $iso => $name) { $this->addSql('INSERT INTO language (id, iso, name) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), $iso, $name]); } diff --git a/module/core/src/Application/Form/DataTransformer/BooleanDataTransformer.php b/module/core/src/Application/Form/DataTransformer/BooleanDataTransformer.php new file mode 100644 index 000000000..2df5dc48a --- /dev/null +++ b/module/core/src/Application/Form/DataTransformer/BooleanDataTransformer.php @@ -0,0 +1,49 @@ +addViewTransformer(new BooleanDataTransformer()); + } /** * @param OptionsResolver $resolver */ diff --git a/module/core/src/Application/Form/Type/LanguageConfigurationFormType.php b/module/core/src/Application/Form/Type/LanguageConfigurationFormType.php index abe9ea3b6..cfb7fa6ce 100644 --- a/module/core/src/Application/Form/Type/LanguageConfigurationFormType.php +++ b/module/core/src/Application/Form/Type/LanguageConfigurationFormType.php @@ -11,7 +11,6 @@ use Ergonode\Core\Application\Model\Type\LanguageConfigurationFormTypeModel; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -33,7 +32,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ) ->add( 'active', - CheckboxType::class + BooleanType::class ); } diff --git a/module/core/src/Infrastructure/JMS/Serializer/Handler/AbstractInterfaceHandler.php b/module/core/src/Infrastructure/JMS/Serializer/Handler/AbstractInterfaceHandler.php index 3ac9bc23e..c456cc95d 100644 --- a/module/core/src/Infrastructure/JMS/Serializer/Handler/AbstractInterfaceHandler.php +++ b/module/core/src/Infrastructure/JMS/Serializer/Handler/AbstractInterfaceHandler.php @@ -86,6 +86,8 @@ public function deserialize(DeserializationVisitorInterface $visitor, array $dat { $typeField = strtolower($this->constant); + $data = $this->prepareData($data); + if (!array_key_exists($data[$typeField], $this->map)) { throw new \OutOfBoundsException(sprintf('Value type "%s" not mapped', $data[$typeField])); } @@ -127,4 +129,14 @@ public function deserialize(DeserializationVisitorInterface $visitor, array $dat return $object; } + + /** + * @param array $data + * + * @return array + */ + protected function prepareData(array $data): array + { + return $data; + } } diff --git a/module/core/tests/Application/Form/DataTransformer/BooleanDataTransformerTest.php b/module/core/tests/Application/Form/DataTransformer/BooleanDataTransformerTest.php new file mode 100644 index 000000000..834610155 --- /dev/null +++ b/module/core/tests/Application/Form/DataTransformer/BooleanDataTransformerTest.php @@ -0,0 +1,103 @@ +assertEquals($expected, $transformer->transform($value)); + } + + /** + * @dataProvider reverseDataProvider + * + * @param mixed $value + * @param string $expected + */ + public function testReverseTransform($value, string $expected): void + { + $transformer = new BooleanDataTransformer(); + $this->assertEquals($expected, $transformer->reverseTransform($value)); + } + + /** + */ + public function testReverseTransformException(): void + { + $transformer = new BooleanDataTransformer(); + $this->expectExceptionMessage('Expect boolean'); + $this->assertEquals('false', $transformer->reverseTransform('fadwwalse')); + } + + + /** + * @return array + */ + public function reverseDataProvider(): array + { + return [ + [ + 'value' => 'true', + 'expected' => 'true', + ], + [ + 'value' => true, + 'expected' => 'true', + ], + [ + 'value' => 1, + 'expected' => 'true', + ], + [ + 'value' => 0, + 'expected' => 'false', + ], + [ + 'value' => 'false', + 'expected' => 'false', + ], + [ + 'value' => false, + 'expected' => 'false', + ], + ]; + } + + /** + * @return array + */ + public function transformDataProvider(): array + { + return [ + [ + 'value' => 'true', + 'expected' => 1, + ], + [ + 'value' => 'false', + 'expected' => false, + ], + ]; + } +} diff --git a/module/designer/composer.json b/module/designer/composer.json index 3a0cfa757..08a449b23 100644 --- a/module/designer/composer.json +++ b/module/designer/composer.json @@ -7,12 +7,11 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/multimedia": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/multimedia": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/designer/migrations/Version20180719132703.php b/module/designer/migrations/Version20180719132703.php index bc0a17479..2aba1943c 100644 --- a/module/designer/migrations/Version20180719132703.php +++ b/module/designer/migrations/Version20180719132703.php @@ -9,7 +9,6 @@ use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class: */ final class Version20180719132703 extends AbstractErgonodeMigration { @@ -22,49 +21,49 @@ public function up(Schema $schema): void { $this->addSql('CREATE SCHEMA designer'); - $this->addSql( - 'CREATE TABLE designer.template ( - id UUID NOT NULL, - name VARCHAR(255) NOT NULL, - image_id UUID DEFAULT NULL, - template_group_id UUID NOT NULL, - PRIMARY KEY(id) - )' - ); - - $this->addSql( - 'CREATE TABLE designer.template_element ( - template_id UUID NOT NULL, - x INTEGER NOT NULL, - y INTEGER NOT NULL, - width INTEGER NOT NULL, - height INTEGER NOT NULL, - properties JSONB NOT NULL, - PRIMARY KEY(template_id, x, y) - )' - ); - - $this->addSql( - 'CREATE TABLE designer.template_group ( - id UUID NOT NULL, - name VARCHAR(32) NOT NULL, - custom boolean DEFAULT FALSE, - PRIMARY KEY(id) - )' - ); - - $this->addSql( - 'CREATE TABLE designer.element_type ( - type VARCHAR(32) NOT NULL, - variant VARCHAR(32) NOT NULL, - label VARCHAR(32) NOT NULL, - min_width INTEGER NOT NULL, - min_height INTEGER NOT NULL, - max_width INTEGER NOT NULL, - max_height INTEGER NOT NULL, - PRIMARY KEY(type) - )' - ); + $this->addSql(' + CREATE TABLE designer.template ( + id UUID NOT NULL, + name VARCHAR(255) NOT NULL, + image_id UUID DEFAULT NULL, + template_group_id UUID NOT NULL, + PRIMARY KEY(id) + ) + '); + + $this->addSql(' + CREATE TABLE designer.template_element ( + template_id UUID NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL, + width INTEGER NOT NULL, + height INTEGER NOT NULL, + properties JSONB NOT NULL, + PRIMARY KEY(template_id, x, y) + ) + '); + + $this->addSql(' + CREATE TABLE designer.template_group ( + id UUID NOT NULL, + name VARCHAR(32) NOT NULL, + custom boolean DEFAULT FALSE, + PRIMARY KEY(id) + ) + '); + + $this->addSql(' + CREATE TABLE designer.element_type ( + type VARCHAR(32) NOT NULL, + variant VARCHAR(32) NOT NULL, + label VARCHAR(32) NOT NULL, + min_width INTEGER NOT NULL, + min_height INTEGER NOT NULL, + max_width INTEGER NOT NULL, + max_height INTEGER NOT NULL, + PRIMARY KEY(type) + ) + '); $this->addType('TEXT', 'attribute', 'Text'); $this->addType('NUMERIC', 'attribute', 'Numeric'); @@ -86,6 +85,19 @@ public function up(Schema $schema): void $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'TEMPLATE_DESIGNER_READ', 'Template designer']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'TEMPLATE_DESIGNER_UPDATE', 'Template designer']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'TEMPLATE_DESIGNER_DELETE', 'Template designer']); + + $this->createEventStoreEvents([ + 'Ergonode\Designer\Domain\Event\TemplateCreatedEvent' => 'Template created', + 'Ergonode\Designer\Domain\Event\TemplateElementAddedEvent' => 'Template element added to template', + 'Ergonode\Designer\Domain\Event\TemplateElementChangedEvent' => 'Template element changed', + 'Ergonode\Designer\Domain\Event\TemplateElementRemovedEvent' => 'Template element removed', + 'Ergonode\Designer\Domain\Event\TemplateGroupChangedEvent' => 'Template group removed', + 'Ergonode\Designer\Domain\Event\TemplateImageAddedEvent' => 'Template image added', + 'Ergonode\Designer\Domain\Event\TemplateImageChangedEvent' => 'Template image changed', + 'Ergonode\Designer\Domain\Event\TemplateImageRemovedEvent' => 'Template image removed', + 'Ergonode\Designer\Domain\Event\TemplateNameChangedEvent' => 'Template name changed', + 'Ergonode\Designer\Domain\Event\TemplateRemovedEvent' => 'Template removed', + ]); } /** @@ -123,4 +135,20 @@ private function addType( ): void { $this->addSql(\sprintf('INSERT INTO designer.element_type (type, variant, label, min_width, min_height, max_width, max_height) VALUES (\'%s\', \'%s\', \'%s\', %d, %d, %d, %d)', $code, $variant, $label, $minWidth, $minHeight, $maxWidth, $maxHeight)); } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } } diff --git a/module/designer/src/Application/Controller/Api/TemplateController.php b/module/designer/src/Application/Controller/Api/TemplateController.php index 222ab6ed4..441eae8d8 100644 --- a/module/designer/src/Application/Controller/Api/TemplateController.php +++ b/module/designer/src/Application/Controller/Api/TemplateController.php @@ -354,11 +354,11 @@ public function getTemplate(Template $template): Response * description="Can't remove Template, it has relations to products" * ) * + * @ParamConverter(class="Ergonode\Designer\Domain\Entity\Template") + * * @param Template $template * * @return Response - * - * @ParamConverter(class="Ergonode\Designer\Domain\Entity\Template") */ public function deleteTemplate(Template $template): Response { @@ -366,6 +366,7 @@ public function deleteTemplate(Template $template): Response throw new ConflictHttpException('Can\'t remove Template, it has relations to products'); } + // @todo Handler not found $command = new DeleteTemplateCommand($template->getId()); $this->messageBus->dispatch($command); diff --git a/module/designer/src/Application/Form/Transformer/PositionFormDataTransformer.php b/module/designer/src/Application/Form/Transformer/PositionFormDataTransformer.php index 410bd6ccc..6cddfac7b 100644 --- a/module/designer/src/Application/Form/Transformer/PositionFormDataTransformer.php +++ b/module/designer/src/Application/Form/Transformer/PositionFormDataTransformer.php @@ -42,9 +42,9 @@ public function transform($value): ?array */ public function reverseTransform($value): ?Position { - if (is_array($value)) { + if (isset($value['x'], $value['y'])) { try { - return new Position((int) $value['x'], (int) $value['y']); + return new Position($value['x'], $value['y']); } catch (\InvalidArgumentException $e) { throw new TransformationFailedException(sprintf('invalid size %s value', implode(',', $value))); } diff --git a/module/designer/src/Application/Form/Transformer/SizeFormDataTransformer.php b/module/designer/src/Application/Form/Transformer/SizeFormDataTransformer.php index 79a6d7573..882334be3 100644 --- a/module/designer/src/Application/Form/Transformer/SizeFormDataTransformer.php +++ b/module/designer/src/Application/Form/Transformer/SizeFormDataTransformer.php @@ -42,9 +42,9 @@ public function transform($value): ?array */ public function reverseTransform($value): ?Size { - if (is_array($value)) { + if (isset($value['width'], $value['height'])) { try { - return new Size((int) $value['width'], (int) $value['height']); + return new Size($value['width'], $value['height']); } catch (\InvalidArgumentException $e) { throw new TransformationFailedException(sprintf('invalid size %s value', implode(',', $value))); } diff --git a/module/designer/src/Application/Form/Type/PositionFormType.php b/module/designer/src/Application/Form/Type/PositionFormType.php index 81823227a..eb5e8c9a4 100644 --- a/module/designer/src/Application/Form/Type/PositionFormType.php +++ b/module/designer/src/Application/Form/Type/PositionFormType.php @@ -11,7 +11,7 @@ use Ergonode\Designer\Application\Form\Transformer\PositionFormDataTransformer; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -28,10 +28,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder ->add( 'x', - TextType::class + IntegerType::class )->add( 'y', - TextType::class + IntegerType::class ); $builder->addModelTransformer(new PositionFormDataTransformer()); } diff --git a/module/designer/src/Application/Form/Type/Properties/AttributeElementPropertyType.php b/module/designer/src/Application/Form/Type/Properties/AttributeElementPropertyType.php index 1e498bba9..d03dd94b8 100644 --- a/module/designer/src/Application/Form/Type/Properties/AttributeElementPropertyType.php +++ b/module/designer/src/Application/Form/Type/Properties/AttributeElementPropertyType.php @@ -9,9 +9,9 @@ namespace Ergonode\Designer\Application\Form\Type\Properties; +use Ergonode\Core\Application\Form\Type\BooleanType; use Ergonode\Designer\Application\Model\Form\Type\Property\AttributeElementPropertyTypeModel; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -36,16 +36,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ) ->add( 'required', - CheckboxType::class, - [ - 'false_values' => [ - '0', - 'false', - '', - false, - ], - 'empty_data' => false, - ] + BooleanType::class ); } diff --git a/module/designer/src/Application/Form/Type/SizeFormType.php b/module/designer/src/Application/Form/Type/SizeFormType.php index d57e7bd13..c7c3df408 100644 --- a/module/designer/src/Application/Form/Type/SizeFormType.php +++ b/module/designer/src/Application/Form/Type/SizeFormType.php @@ -11,7 +11,7 @@ use Ergonode\Designer\Application\Form\Transformer\SizeFormDataTransformer; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -28,10 +28,10 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder ->add( 'width', - TextType::class + IntegerType::class )->add( 'height', - TextType::class + IntegerType::class ); $builder->addModelTransformer(new SizeFormDataTransformer()); } diff --git a/module/designer/src/Infrastructure/Validator/TemplateExists.php b/module/designer/src/Infrastructure/Validator/TemplateExists.php new file mode 100644 index 000000000..28ea6f867 --- /dev/null +++ b/module/designer/src/Infrastructure/Validator/TemplateExists.php @@ -0,0 +1,23 @@ +templateRepository = $templateRepository; + } + + /** + * @param mixed $value + * @param Constraint $constraint + */ + public function validate($value, Constraint $constraint) + { + if (!$constraint instanceof TemplateExists) { + throw new UnexpectedTypeException($constraint, TemplateExists::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + $template = false; + + if (TemplateId::isValid($value)) { + $template = $this->templateRepository->load(new TemplateId($value)); + } + + if (!$template) { + $this->context->buildViolation($constraint->message) + ->setParameter('{{ value }}', $value) + ->addViolation(); + } + } +} diff --git a/module/designer/src/Persistence/Dbal/Projector/Group/TemplateGroupCreatedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/Group/TemplateGroupCreatedEventProjector.php index ee01d896b..c6927d2eb 100644 --- a/module/designer/src/Persistence/Dbal/Projector/Group/TemplateGroupCreatedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/Group/TemplateGroupCreatedEventProjector.php @@ -28,8 +28,6 @@ class TemplateGroupCreatedEventProjector implements DomainEventProjectorInterfac private $connection; /** - * TemplateCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -38,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,33 +44,20 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateGroupCreatedEvent) { throw new UnsupportedEventException($event, TemplateGroupCreatedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->insert( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - 'name' => $event->getName(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->insert( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + 'name' => $event->getName(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateCreatedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateCreatedEventProjector.php index 140b3ff0b..e0458012f 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateCreatedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateCreatedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,35 +44,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateCreatedEvent) { throw new UnsupportedEventException($event, TemplateCreatedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->insert( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - 'name' => $event->getName(), - 'image_id' => $event->getImageId() ? $event->getImageId()->getValue() : null, - 'template_group_id' => $event->getGroupId()->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->insert( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + 'name' => $event->getName(), + 'image_id' => $event->getImageId() ? $event->getImageId()->getValue() : null, + 'template_group_id' => $event->getGroupId()->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateElementAddedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateElementAddedEventProjector.php index 9db238910..0c477fc37 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateElementAddedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateElementAddedEventProjector.php @@ -44,9 +44,7 @@ public function __construct(Connection $connection, SerializerInterface $seriali } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -54,38 +52,25 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateElementAddedEvent) { throw new UnsupportedEventException($event, TemplateElementAddedEvent::class); } - $this->connection->beginTransaction(); - try { - $element = $event->getElement(); - $this->connection->insert( - self::ELEMENT_TABLE, - [ - 'template_id' => $aggregateId->getValue(), - 'x' => $element->getPosition()->getX(), - 'y' => $element->getPosition()->getY(), - 'width' => $element->getSize()->getWidth(), - 'height' => $element->getSize()->getHeight(), - 'properties' => $this->serializer->serialize($element->getProperties(), 'json'), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $element = $event->getElement(); + $this->connection->insert( + self::ELEMENT_TABLE, + [ + 'template_id' => $aggregateId->getValue(), + 'x' => $element->getPosition()->getX(), + 'y' => $element->getPosition()->getY(), + 'width' => $element->getSize()->getWidth(), + 'height' => $element->getSize()->getHeight(), + 'properties' => $this->serializer->serialize($element->getProperties(), 'json'), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateElementChangedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateElementChangedEventProjector.php index d80d87ae6..94498fa64 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateElementChangedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateElementChangedEventProjector.php @@ -44,9 +44,7 @@ public function __construct(Connection $connection, SerializerInterface $seriali } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -54,40 +52,27 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateElementChangedEvent) { throw new UnsupportedEventException($event, TemplateElementChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $element = $event->getElement(); - $this->connection->update( - self::ELEMENT_TABLE, - [ - 'width' => $element->getSize()->getWidth(), - 'height' => $element->getSize()->getHeight(), - 'properties' => $this->serializer->serialize($element->getProperties(), 'json'), - ], - [ - 'template_id' => $aggregateId->getValue(), - 'x' => $element->getPosition()->getX(), - 'y' => $element->getPosition()->getY(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $element = $event->getElement(); + $this->connection->update( + self::ELEMENT_TABLE, + [ + 'width' => $element->getSize()->getWidth(), + 'height' => $element->getSize()->getHeight(), + 'properties' => $this->serializer->serialize($element->getProperties(), 'json'), + ], + [ + 'template_id' => $aggregateId->getValue(), + 'x' => $element->getPosition()->getX(), + 'y' => $element->getPosition()->getY(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateElementRemovedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateElementRemovedEventProjector.php index 6c60c16d7..056eb2c7c 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateElementRemovedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateElementRemovedEventProjector.php @@ -28,8 +28,6 @@ class TemplateElementRemovedEventProjector implements DomainEventProjectorInterf private $connection; /** - * TemplateCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -38,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,34 +44,21 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateElementRemovedEvent) { throw new UnsupportedEventException($event, TemplateElementRemovedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->delete( - self::ELEMENT_TABLE, - [ - 'template_id' => $aggregateId->getValue(), - 'x' => $event->getPosition()->getX(), - 'y' => $event->getPosition()->getY(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->delete( + self::ELEMENT_TABLE, + [ + 'template_id' => $aggregateId->getValue(), + 'x' => $event->getPosition()->getX(), + 'y' => $event->getPosition()->getY(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateGroupChangedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateGroupChangedEventProjector.php index 12fb788b7..5281f4a2e 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateGroupChangedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateGroupChangedEventProjector.php @@ -28,8 +28,6 @@ class TemplateGroupChangedEventProjector implements DomainEventProjectorInterfac private $connection; /** - * TemplateCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -38,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,35 +44,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateGroupChangedEvent) { throw new UnsupportedEventException($event, TemplateGroupChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'template_group_id' => $event->getNew()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'template_group_id' => $event->getNew()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateImageAddedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateImageAddedEventProjector.php index 6a32cad71..75267a177 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateImageAddedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateImageAddedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,35 +44,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateImageAddedEvent) { throw new UnsupportedEventException($event, TemplateImageAddedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'image_id' => $event->getImageId()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'image_id' => $event->getImageId()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateImageChangedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateImageChangedEventProjector.php index 2b45d54b1..687f8262d 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateImageChangedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateImageChangedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,35 +44,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateImageChangedEvent) { throw new UnsupportedEventException($event, TemplateImageChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'image_id' => $event->getTo()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'image_id' => $event->getTo()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateImageRemovedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateImageRemovedEventProjector.php index d99c4e2de..5761d49b0 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateImageRemovedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateImageRemovedEventProjector.php @@ -36,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -46,35 +44,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateImageRemovedEvent) { throw new UnsupportedEventException($event, TemplateImageRemovedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'image_id' => null, - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'image_id' => null, + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateNameChangedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateNameChangedEventProjector.php index 56b90e775..9bf54e331 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateNameChangedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateNameChangedEventProjector.php @@ -38,9 +38,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,35 +46,22 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof TemplateNameChangedEvent) { throw new UnsupportedEventException($event, TemplateNameChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'name' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE, + [ + 'name' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/designer/src/Persistence/Dbal/Projector/TemplateRemovedEventProjector.php b/module/designer/src/Persistence/Dbal/Projector/TemplateRemovedEventProjector.php index bc77e329b..fc3507493 100644 --- a/module/designer/src/Persistence/Dbal/Projector/TemplateRemovedEventProjector.php +++ b/module/designer/src/Persistence/Dbal/Projector/TemplateRemovedEventProjector.php @@ -37,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,11 +45,8 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void @@ -60,24 +55,20 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, TemplateRemovedEvent::class); } - $this->connection->beginTransaction(); - try { + $this->connection->transactional(function () use ($aggregateId) { $this->connection->delete( self::ELEMENT_TABLE, [ 'template_id' => $aggregateId->getValue(), ] ); + $this->connection->delete( self::TABLE, [ 'id' => $aggregateId->getValue(), ] ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + }); } } diff --git a/module/designer/src/Resources/translations/log.en.yaml b/module/designer/src/Resources/translations/log.en.yaml index 95552984e..428f8af7c 100644 --- a/module/designer/src/Resources/translations/log.en.yaml +++ b/module/designer/src/Resources/translations/log.en.yaml @@ -1,10 +1,10 @@ -"Ergonode\\Designer\\Domain\\Event\\TemplateCreatedEvent": Template "%name%" created -"Ergonode\\Designer\\Domain\\Event\\TemplateElementAddedEvent": Template element added to template -"Ergonode\\Designer\\Domain\\Event\\TemplateElementChangedEvent": Template element changed -"Ergonode\\Designer\\Domain\\Event\\TemplateElementRemovedEvent": Template element removed -"Ergonode\\Designer\\Domain\\Event\\TemplateGroupChangedEvent": Template group removed -"Ergonode\\Designer\\Domain\\Event\\TemplateImageAddedEvent": Template image added -"Ergonode\\Designer\\Domain\\Event\\TemplateImageChangedEvent": Template image changed -"Ergonode\\Designer\\Domain\\Event\\TemplateImageRemovedEvent": Template image removed -"Ergonode\\Designer\\Domain\\Event\\TemplateNameChangedEvent": Template name changed from "%from%" to "%to%" -"Ergonode\\Designer\\Domain\\Event\\TemplateRemovedEvent": Template removed +'Template created': 'Template "%name%" created' +'Template element added to template': 'Template element added to template' +'Template element changed': 'Template element changed' +'Template element removed': 'Template element removed' +'Template group removed': 'Template group removed' +'Template image added': 'Template image added' +'Template image changed': 'Template image changed' +'Template image removed': 'Template image removed' +'Template name changed': 'Template name changed from "%from%" to "%to%"' +'Template removed': 'Template removed' diff --git a/module/designer/src/Resources/translations/log.pl.yaml b/module/designer/src/Resources/translations/log.pl.yaml index 815df19ed..a94069253 100644 --- a/module/designer/src/Resources/translations/log.pl.yaml +++ b/module/designer/src/Resources/translations/log.pl.yaml @@ -1,10 +1,10 @@ -"Ergonode\\Designer\\Domain\\Event\\TemplateCreatedEvent": Szablon "%name%" dodany -"Ergonode\\Designer\\Domain\\Event\\TemplateElementAddedEvent": Element dodany do szablonu -"Ergonode\\Designer\\Domain\\Event\\TemplateElementChangedEvent": Element szablonu zmieniony -"Ergonode\\Designer\\Domain\\Event\\TemplateElementRemovedEvent": Element szablonu usunięty -"Ergonode\\Designer\\Domain\\Event\\TemplateGroupChangedEvent": Szablon usunięty z grupy -"Ergonode\\Designer\\Domain\\Event\\TemplateImageAddedEvent": Zdjęcie dodane do szablonu -"Ergonode\\Designer\\Domain\\Event\\TemplateImageChangedEvent": Zmieniono zdjęcie szablonu -"Ergonode\\Designer\\Domain\\Event\\TemplateImageRemovedEvent": Usunieto zdjęcie szablonu -"Ergonode\\Designer\\Domain\\Event\\TemplateNameChangedEvent": Nazwa szablonu zmieniona z "%from%" na "%to%" -"Ergonode\\Designer\\Domain\\Event\\TemplateRemovedEvent": Szablon usunieto +'Template created': 'Szablon "%name%" dodany' +'Template element added to template': 'Element dodany do szablonu' +'Template element changed': 'Element szablonu zmieniony' +'Template element removed': 'Element szablonu usunięty' +'Template group removed': 'Szablon usunięty z grupy' +'Template image added': 'Zdjęcie dodane do szablonu' +'Template image changed': 'Zmieniono zdjęcie szablonu' +'Template image removed': 'Usunieto zdjęcie szablonu' +'Template name changed': 'Nazwa szablonu zmieniona z "%from%" na "%to%"' +'Template removed': 'Szablon usunieto' diff --git a/module/editor/composer.json b/module/editor/composer.json index afa367ca6..bb698aa66 100644 --- a/module/editor/composer.json +++ b/module/editor/composer.json @@ -7,14 +7,13 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/attribute": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/product": "^0.4.0", - "ergonode/value": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/attribute": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/product": "^0.5.0", + "ergonode/value": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/editor/migrations/Version20180731143300.php b/module/editor/migrations/Version20180731143300.php index 8f07847f5..0b4755572 100644 --- a/module/editor/migrations/Version20180731143300.php +++ b/module/editor/migrations/Version20180731143300.php @@ -5,9 +5,9 @@ namespace Ergonode\Migration; use Doctrine\DBAL\Schema\Schema; +use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class: */ final class Version20180731143300 extends AbstractErgonodeMigration { @@ -18,36 +18,60 @@ final class Version20180731143300 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql( - 'CREATE TABLE designer.product ( - product_id UUID NOT NULL, - template_id UUID NOT NULL, - PRIMARY KEY(product_id, template_id) - )' - ); - - $this->addSql( - 'CREATE TABLE designer.draft ( - id UUID NOT NULL, - sku VARCHAR(255) DEFAULT NULL, - type VARCHAR(16) NOT NULL DEFAULT \'NEW\', - product_id UUID DEFAULT NULL, - applied boolean NOT NULL DEFAULT FALSE, - PRIMARY KEY(id) - )' - ); - - $this->addSql( - 'CREATE TABLE designer.draft_value ( - id UUID NOT NULL, - draft_id UUID DEFAULT NULL, - element_id UUID NOT NULL, - language VARCHAR(2) DEFAULT NULL, - value text NOT NULL, - PRIMARY KEY(id) - )' - ); + $this->addSql(' + CREATE TABLE designer.product ( + product_id UUID NOT NULL, + template_id UUID NOT NULL, + PRIMARY KEY(product_id, template_id) + ) + '); + + $this->addSql(' + CREATE TABLE designer.draft ( + id UUID NOT NULL, + sku VARCHAR(255) DEFAULT NULL, + type VARCHAR(16) NOT NULL DEFAULT \'NEW\', + product_id UUID DEFAULT NULL, + applied boolean NOT NULL DEFAULT FALSE, + PRIMARY KEY(id) + ) + '); + + $this->addSql(' + CREATE TABLE designer.draft_value ( + id UUID NOT NULL, + draft_id UUID DEFAULT NULL, + element_id UUID NOT NULL, + language VARCHAR(2) DEFAULT NULL, + value text NOT NULL, + PRIMARY KEY(id) + ) + '); $this->addSql('ALTER TABLE designer.product ADD CONSTRAINT product_template_id_fk FOREIGN KEY (template_id) REFERENCES designer.template (id) ON DELETE RESTRICT'); + + $this->createEventStoreEvents([ + 'Ergonode\Editor\Domain\Event\ProductDraftApplied' => 'Applied product draft', + 'Ergonode\Editor\Domain\Event\ProductDraftCreated' => 'Product draft created', + 'Ergonode\Editor\Domain\Event\ProductDraftValueAdded' => 'Value added to product draft', + 'Ergonode\Editor\Domain\Event\ProductDraftValueChanged' => 'Product draft value changed', + 'Ergonode\Editor\Domain\Event\ProductDraftValueRemoved' => 'Product draft value removed', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/editor/src/Persistence/Projector/ProductCreatedEventProjector.php b/module/editor/src/Persistence/Projector/ProductCreatedEventProjector.php index 2c0573e2b..4974b292a 100644 --- a/module/editor/src/Persistence/Projector/ProductCreatedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductCreatedEventProjector.php @@ -12,7 +12,6 @@ use Doctrine\DBAL\Connection; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Product\Domain\Event\ProductCreated; @@ -37,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,19 +52,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductCreated::class); } - try { - $this->connection->beginTransaction(); - $this->connection->insert( - self::TABLE, - [ - 'product_id' => $aggregateId->getValue(), - 'template_id' => $event->getTemplateId()->getValue(), - ] - ); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::TABLE, + [ + 'product_id' => $aggregateId->getValue(), + 'template_id' => $event->getTemplateId()->getValue(), + ] + ); } } diff --git a/module/editor/src/Persistence/Projector/ProductDraftAppliedEventProjector.php b/module/editor/src/Persistence/Projector/ProductDraftAppliedEventProjector.php index 7eb88e6f3..5e2f26a86 100644 --- a/module/editor/src/Persistence/Projector/ProductDraftAppliedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductDraftAppliedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Editor\Domain\Event\ProductDraftApplied; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; @@ -37,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,24 +52,17 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductDraftApplied::class); } - try { - $this->connection->beginTransaction(); - $this->connection->update( - self::DRAFT_TABLE, - [ - 'applied' => true, - ], - [ - 'id' => $aggregateId->getValue(), - ], - [ - 'applied' => \PDO::PARAM_BOOL, - ] - ); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::DRAFT_TABLE, + [ + 'applied' => true, + ], + [ + 'id' => $aggregateId->getValue(), + ], + [ + 'applied' => \PDO::PARAM_BOOL, + ] + ); } } diff --git a/module/editor/src/Persistence/Projector/ProductDraftCreatedEventProjector.php b/module/editor/src/Persistence/Projector/ProductDraftCreatedEventProjector.php index 583d3c5e7..8c9d76d9a 100644 --- a/module/editor/src/Persistence/Projector/ProductDraftCreatedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductDraftCreatedEventProjector.php @@ -13,7 +13,6 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Editor\Domain\Event\ProductDraftCreated; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; @@ -37,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,20 +52,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductDraftCreated::class); } - try { - $this->connection->beginTransaction(); - $this->connection->insert( - self::DRAFT_TABLE, - [ - 'id' => $aggregateId->getValue(), - 'product_id' => $event->getProductId() ? $event->getProductId()->getValue() : null, - 'type' => $event->getProductId() ? 'EDITED': 'NEW', - ] - ); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::DRAFT_TABLE, + [ + 'id' => $aggregateId->getValue(), + 'product_id' => $event->getProductId() ? $event->getProductId()->getValue() : null, + 'type' => $event->getProductId() ? 'EDITED': 'NEW', + ] + ); } } diff --git a/module/editor/src/Persistence/Projector/ProductDraftValueAddedEventProjector.php b/module/editor/src/Persistence/Projector/ProductDraftValueAddedEventProjector.php index 0ea834301..aec1d9152 100644 --- a/module/editor/src/Persistence/Projector/ProductDraftValueAddedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductDraftValueAddedEventProjector.php @@ -15,7 +15,6 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Editor\Domain\Event\ProductDraftValueAdded; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Value\Domain\ValueObject\StringCollectionValue; @@ -45,9 +44,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -55,12 +52,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -68,19 +62,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductDraftValueAdded::class); } - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $event) { $draftId = $aggregateId->getValue(); $elementId = AttributeId::fromKey($event->getAttributeCode())->getValue(); - $value = $event->getTo(); $this->insertValue($draftId, $elementId, $value); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/editor/src/Persistence/Projector/ProductDraftValueChangedEventProjector.php b/module/editor/src/Persistence/Projector/ProductDraftValueChangedEventProjector.php index a570342e1..f99b2d9b5 100644 --- a/module/editor/src/Persistence/Projector/ProductDraftValueChangedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductDraftValueChangedEventProjector.php @@ -15,7 +15,6 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Editor\Domain\Event\ProductDraftValueChanged; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Value\Domain\ValueObject\StringCollectionValue; @@ -45,9 +44,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -55,12 +52,9 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -68,8 +62,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductDraftValueChanged::class); } - try { - $this->connection->beginTransaction(); + $this->connection->transactional(function () use ($aggregateId, $event) { $draftId = $aggregateId->getValue(); $elementId = AttributeId::fromKey($event->getAttributeCode())->getValue(); @@ -77,11 +70,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) $this->delete($draftId, $elementId); $this->insertValue($draftId, $elementId, $value); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + }); } /** diff --git a/module/editor/src/Persistence/Projector/ProductDraftValueRemovedEventProjector.php b/module/editor/src/Persistence/Projector/ProductDraftValueRemovedEventProjector.php index 098781159..aea26699f 100644 --- a/module/editor/src/Persistence/Projector/ProductDraftValueRemovedEventProjector.php +++ b/module/editor/src/Persistence/Projector/ProductDraftValueRemovedEventProjector.php @@ -15,7 +15,6 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Editor\Domain\Event\ProductDraftValueRemoved; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; @@ -39,9 +38,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -49,12 +46,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -62,17 +54,10 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductDraftValueRemoved::class); } - try { - $this->connection->beginTransaction(); - $draftId = $aggregateId->getValue(); - $elementId = AttributeId::fromKey($event->getAttributeCode())->getValue(); + $draftId = $aggregateId->getValue(); + $elementId = AttributeId::fromKey($event->getAttributeCode())->getValue(); - $this->delete($draftId, $elementId); - $this->connection->commit(); - } catch (\Exception $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->delete($draftId, $elementId); } /** diff --git a/module/editor/src/Resources/translations/log.en.yaml b/module/editor/src/Resources/translations/log.en.yaml index b233410eb..e1e9a07b4 100644 --- a/module/editor/src/Resources/translations/log.en.yaml +++ b/module/editor/src/Resources/translations/log.en.yaml @@ -1,6 +1,6 @@ -"Ergonode\\Editor\\Domain\\Event\\ProductDraftApplied": Appliening product draft -"Ergonode\\Editor\\Domain\\Event\\ProductDraftCreated": Product draft created -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueAdded": Value added to draft -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueChanged": Draft value changed -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueRemoved": Draft value removed +'Applied product draft': 'Applied product draft' +'Product draft created': 'Product draft created' +'Value added to product draft': 'Value added to product draft' +'Product draft value changed': 'Product draft value changed' +'Product draft value removed': 'Product draft value removed' diff --git a/module/editor/src/Resources/translations/log.pl.yaml b/module/editor/src/Resources/translations/log.pl.yaml index b648bfe7d..6b53cb374 100644 --- a/module/editor/src/Resources/translations/log.pl.yaml +++ b/module/editor/src/Resources/translations/log.pl.yaml @@ -1,6 +1,6 @@ -"Ergonode\\Editor\\Domain\\Event\\ProductDraftApplied": Zastosowano roboczą wersję produktu -"Ergonode\\Editor\\Domain\\Event\\ProductDraftCreated": Utworzono roboczą wersję produktu -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueAdded": Dodano wartość do roboczej wersji produktu -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueChanged": Zmieniono wartość w roboczej wersji produktu -"Ergonode\\Editor\\Domain\\Event\\ProductDraftValueRemoved": Usunieto wartość z roboczej wersji produktu +'Applied product draft': 'Zastosowano roboczą wersję produktu' +'Product draft created': 'Utworzono roboczą wersję produktu' +'Value added to product draft': 'Dodano wartość do roboczej wersji produktu' +'Product draft value changed': 'Zmieniono wartość w roboczej wersji produktu' +'Product draft value removed': 'Usunieto wartość z roboczej wersji produktu' diff --git a/module/event-sourcing/composer.json b/module/event-sourcing/composer.json index 54c24ea15..e5e410561 100644 --- a/module/event-sourcing/composer.json +++ b/module/event-sourcing/composer.json @@ -7,8 +7,8 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", "jms/serializer": "^3.1", "symfony/cache": "^4.3", "symfony/config": "^4.3", diff --git a/module/event-sourcing/migrations/Version20180101000000.php b/module/event-sourcing/migrations/Version20180101000000.php new file mode 100644 index 000000000..96e29a570 --- /dev/null +++ b/module/event-sourcing/migrations/Version20180101000000.php @@ -0,0 +1,62 @@ +addSql(' + CREATE TABLE event_store ( + id BIGSERIAL NOT NULL, + aggregate_id uuid NOT NULL, + sequence int NOT NULL, + event_id UUID NOT NULL, + payload jsonb NOT NULL, + recorded_by uuid default NULL, + recorded_at timestamp without time zone NOT NULL, + CONSTRAINT event_store_pkey PRIMARY KEY (id) + ) + '); + $this->addSql('CREATE UNIQUE INDEX event_store_unique_key ON event_store USING btree (aggregate_id, sequence)'); + + $this->addSql(' + CREATE TABLE event_store_history ( + id BIGSERIAL NOT NULL, + aggregate_id uuid NOT NULL, + sequence int NOT NULL, + variant int NOT NULL DEFAULT 1, + event_id UUID NOT NULL, + payload jsonb NOT NULL, + recorded_by uuid default NULL, + recorded_at timestamp without time zone NOT NULL, + CONSTRAINT event_store_history_pkey PRIMARY KEY (id) + ) + '); + $this->addSql('CREATE UNIQUE INDEX event_store_history_unique_key ON event_store_history USING btree (aggregate_id, sequence, variant)'); + + $this->addSql(' + CREATE TABLE event_store_event ( + id UUID NOT NULL, + event_class character varying(255) NOT NULL, + translation_key text NOT NULL, + CONSTRAINT event_store_event_pkey PRIMARY KEY (id) + ) + '); + $this->addSql('CREATE UNIQUE INDEX event_store_event_unique_key ON event_store_event USING btree (event_class)'); + } +} diff --git a/module/event-sourcing/src/Domain/AbstractAggregateRoot.php b/module/event-sourcing/src/Domain/AbstractAggregateRoot.php index 2b5921f63..0f3f776f5 100644 --- a/module/event-sourcing/src/Domain/AbstractAggregateRoot.php +++ b/module/event-sourcing/src/Domain/AbstractAggregateRoot.php @@ -10,6 +10,7 @@ namespace Ergonode\EventSourcing\Domain; use Ergonode\Core\Domain\Entity\AbstractId; +use Ergonode\EventSourcing\Infrastructure\AbstractDeleteEvent; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; use Ergonode\EventSourcing\Infrastructure\Envelope\DomainEventEnvelope; use Ergonode\EventSourcing\Infrastructure\Stream\DomainEventStream; @@ -98,14 +99,16 @@ public function getSequence(): int private function handle(DomainEventInterface $event, \DateTime $recordedAt): void { $this->editedAt = $recordedAt; - $classArray = explode('\\', get_class($event)); - $class = end($classArray); - $method = sprintf('apply%s', $class); - if (!method_exists($this, $method)) { - throw new \RuntimeException(sprintf('Can\'t find method %s for event in aggregate %s', $method, get_class($this))); - } + if (!$event instanceof AbstractDeleteEvent) { + $classArray = explode('\\', get_class($event)); + $class = end($classArray); + $method = sprintf('apply%s', $class); + if (!method_exists($this, $method)) { + throw new \RuntimeException(sprintf('Can\'t find method %s for event in aggregate %s', $method, get_class($this))); + } - $this->$method($event); + $this->$method($event); + } } } diff --git a/module/event-sourcing/src/Infrastructure/AbstractDeleteEvent.php b/module/event-sourcing/src/Infrastructure/AbstractDeleteEvent.php new file mode 100644 index 000000000..ca1840f40 --- /dev/null +++ b/module/event-sourcing/src/Infrastructure/AbstractDeleteEvent.php @@ -0,0 +1,17 @@ +event); + return get_class($this->event); } /** diff --git a/module/event-sourcing/src/Infrastructure/EventSubscriber/DomainEventEnvelopeSubscriber.php b/module/event-sourcing/src/Infrastructure/EventSubscriber/DomainEventEnvelopeSubscriber.php index f92287fbe..5b0a6bd7a 100644 --- a/module/event-sourcing/src/Infrastructure/EventSubscriber/DomainEventEnvelopeSubscriber.php +++ b/module/event-sourcing/src/Infrastructure/EventSubscriber/DomainEventEnvelopeSubscriber.php @@ -10,6 +10,7 @@ namespace Ergonode\EventSourcing\Infrastructure\EventSubscriber; use Ergonode\EventSourcing\Infrastructure\Envelope\DomainEventEnvelope; +use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjector; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -47,6 +48,10 @@ public static function getSubscribedEvents(): array */ public function projection(DomainEventEnvelope $envelope): void { - $this->projector->projection($envelope); + try { + $this->projector->projection($envelope); + } catch (\Throwable $exception) { + throw new ProjectorException($envelope->getEvent(), $exception); + } } } diff --git a/module/event-sourcing/src/Infrastructure/Exception/ProjectorException.php b/module/event-sourcing/src/Infrastructure/Exception/ProjectorException.php index f96afe006..93d24ec1a 100644 --- a/module/event-sourcing/src/Infrastructure/Exception/ProjectorException.php +++ b/module/event-sourcing/src/Infrastructure/Exception/ProjectorException.php @@ -15,7 +15,7 @@ */ class ProjectorException extends \Exception { - public const MESSAGE = 'Can\'t project event %s'; + public const MESSAGE = 'Can\'t project event "%s"'; /** * @param DomainEventInterface $event @@ -23,6 +23,6 @@ class ProjectorException extends \Exception */ public function __construct(DomainEventInterface $event, \Throwable $previous = null) { - parent::__construct(sprintf(self::MESSAGE, \get_class($event)), 0, $previous); + parent::__construct(sprintf(self::MESSAGE, get_class($event)), 0, $previous); } } diff --git a/module/event-sourcing/src/Infrastructure/Factory/SimpleDomainEventFactory.php b/module/event-sourcing/src/Infrastructure/Factory/SimpleDomainEventFactory.php index ff797e1a6..5b9db07c5 100644 --- a/module/event-sourcing/src/Infrastructure/Factory/SimpleDomainEventFactory.php +++ b/module/event-sourcing/src/Infrastructure/Factory/SimpleDomainEventFactory.php @@ -33,10 +33,7 @@ public function __construct(SerializerInterface $serializer) } /** - * @param AbstractId $id - * @param array $records - * - * @return DomainEventEnvelope[] + * {@inheritDoc} */ public function create(AbstractId $id, array $records): array { diff --git a/module/event-sourcing/src/Infrastructure/Provider/DomainEventProvider.php b/module/event-sourcing/src/Infrastructure/Provider/DomainEventProvider.php new file mode 100644 index 000000000..ad48a1ebc --- /dev/null +++ b/module/event-sourcing/src/Infrastructure/Provider/DomainEventProvider.php @@ -0,0 +1,78 @@ +connection = $connection; + $this->cache = $cache; + } + + /** + * {@inheritDoc} + * + * @throws \Psr\Cache\InvalidArgumentException + * @throws \RuntimeException + */ + public function provideEventId(string $eventClass): string + { + $cacheItem = $this->cache->getItem(sha1($eventClass)); + $eventId = $cacheItem->isHit() ? $cacheItem->get() : $this->fetchFromDatabase($eventClass); + + return (string) $eventId; + } + + /** + * @param string $eventClass + * + * @return string + * + * @throws \RuntimeException + */ + private function fetchFromDatabase(string $eventClass): string + { + $queryBuilder = $this->connection->createQueryBuilder() + ->from('event_store_event') + ->select('id') + ->where('event_class = :class') + ->setParameter('class', $eventClass); + $eventId = $queryBuilder->execute()->fetchColumn(); + + if (empty($eventId)) { + throw new \RuntimeException(sprintf( + 'Event class "%s" not found. Check event definition in "event_store_event" table', + $eventClass + )); + } + + return (string) $eventId; + } +} diff --git a/module/event-sourcing/src/Infrastructure/Provider/DomainEventProviderInterface.php b/module/event-sourcing/src/Infrastructure/Provider/DomainEventProviderInterface.php new file mode 100644 index 000000000..4959f132c --- /dev/null +++ b/module/event-sourcing/src/Infrastructure/Provider/DomainEventProviderInterface.php @@ -0,0 +1,22 @@ +tokenStorage = $tokenStorage; $this->connection = $connection; $this->serializer = $serializer; $this->domainEventFactory = $domainEventFactory; $this->cache = $cache; + $this->domainEventProvider = $domainEventProvider; } /** - * @param AbstractId $id - * - * @param string $table - * - * @return DomainEventStream + * {@inheritDoc} * * @throws \Psr\Cache\InvalidArgumentException */ @@ -88,7 +98,7 @@ public function load(AbstractId $id, ?string $table = null): DomainEventStream $item = $this->cache->getItem($key); if ($item->isHit()) { - $result = $item->get(); + $result = $item->get(); $sequence = count($result); } else { $result = []; @@ -98,8 +108,10 @@ public function load(AbstractId $id, ?string $table = null): DomainEventStream $qb = $this->connection->createQueryBuilder(); $records = $qb - ->select('*') - ->from($table) + ->select('es.id, es.aggregate_id, es.sequence, es.payload, es.recorded_by, es.recorded_at') + ->addSelect('ese.event_class as event') + ->from($table, 'es') + ->join('es', 'event_store_event', 'ese', 'es.event_id = ese.id') ->where($qb->expr()->eq('aggregate_id', ':aggregateId')) ->andWhere($qb->expr()->gt('sequence', ':sequence')) ->setParameter('aggregateId', $id->getValue()) @@ -119,19 +131,16 @@ public function load(AbstractId $id, ?string $table = null): DomainEventStream } /** - * @param AbstractId $id - * @param DomainEventStream $stream - * - * @param string $table + * {@inheritDoc} * - * @throws \Doctrine\DBAL\ConnectionException * @throws \Throwable */ public function append(AbstractId $id, DomainEventStream $stream, ?string $table = null): void { $table = $table ?: self::TABLE; - $this->connection->beginTransaction(); - try { + + $this->connection->transactional(function () use ($id, $stream, $table) { + $userId = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser()->getId()->getValue() : null; foreach ($stream as $envelope) { $payload = $this->serializer->serialize($envelope->getEvent(), 'json'); $this->connection->insert( @@ -139,17 +148,56 @@ public function append(AbstractId $id, DomainEventStream $stream, ?string $table [ 'aggregate_id' => $id->getValue(), 'sequence' => $envelope->getSequence(), - 'event' => $envelope->getType(), + 'event_id' => $this->domainEventProvider->provideEventId($envelope->getType()), 'payload' => $payload, 'recorded_at' => $envelope->getRecordedAt()->format('Y-m-d H:i:s'), - 'recorded_by' => $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser()->getId()->getValue() : null, + 'recorded_by' => $userId, ] ); } - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + }); + } + + /** + * {@inheritDoc} + * + * @throws \Throwable + * @throws \Psr\Cache\InvalidArgumentException + */ + public function delete(AbstractId $id, ?string $table = null): void + { + $dataTable = $table ?? self::TABLE; + $historyTable = sprintf('%s_history', $dataTable); + + $this->connection->transactional(function () use ($id, $dataTable, $historyTable) { + $queryBuilder = $this->connection->createQueryBuilder() + ->from($historyTable) + ->select('variant') + ->where('aggregate_id = :id') + ->orderBy('variant', 'DESC') + ->setMaxResults(1) + ->setParameter('id', $id->getValue()); + $version = $queryBuilder->execute()->fetchColumn(); + + if (empty($version)) { + $version = 1; + } + + $this->connection->executeQuery( + sprintf( + 'INSERT INTO %s (aggregate_id, sequence, event_id, payload, recorded_by, recorded_at, variant) + SELECT aggregate_id, sequence, event_id, payload, recorded_by, recorded_at, %d FROM %s WHERE aggregate_id = ?', + $historyTable, + $version, + $dataTable + ), + [$id->getValue()] + ); + + $this->connection->delete($dataTable, ['aggregate_id' => $id->getValue()]); + }); + + $key = sprintf(self::KEY, $id->getValue()); + $this->cache->deleteItem($key); } } diff --git a/module/event-sourcing/src/Resources/config/services.yml b/module/event-sourcing/src/Resources/config/services.yml index 1e7980cc7..fab7f6db1 100644 --- a/module/event-sourcing/src/Resources/config/services.yml +++ b/module/event-sourcing/src/Resources/config/services.yml @@ -9,3 +9,4 @@ services: Ergonode\EventSourcing\Infrastructure\DomainEventStoreInterface: '@Ergonode\EventSourcing\Infrastructure\Store\DbalDomainEventStore' Ergonode\EventSourcing\Infrastructure\DomainEventDispatcherInterface: '@Ergonode\EventSourcing\Infrastructure\Dispatcher\SymfonyDomainEventDispatcher' + Ergonode\EventSourcing\Infrastructure\Provider\DomainEventProviderInterface: '@Ergonode\EventSourcing\Infrastructure\Provider\DomainEventProvider' diff --git a/module/fixture/composer.json b/module/fixture/composer.json index 991430429..d7e4bb297 100644 --- a/module/fixture/composer.json +++ b/module/fixture/composer.json @@ -6,7 +6,7 @@ "license": "OSL-3.0", "require": { "php": "^7.2", - "ergonode/es": "^0.4.0", + "ergonode/es": "^0.5.0", "nelmio/alice": "^3.5", "symfony/config": "^4.3", "symfony/dependency-injection": "^4.3", diff --git a/module/fixture/src/Infrastructure/Faker/ConditionSetCodeFaker.php b/module/fixture/src/Infrastructure/Faker/ConditionSetCodeFaker.php new file mode 100644 index 000000000..004fe7547 --- /dev/null +++ b/module/fixture/src/Infrastructure/Faker/ConditionSetCodeFaker.php @@ -0,0 +1,34 @@ +loader = $loader; $this->generator = $generator; $this->manager = $manager; + $this->connection = $connection; } /** * @param string|null $group * * @throws FixtureException + * @throws ConnectionException */ public function process(?string $group = null): void { try { + $this->connection->beginTransaction(); $files = $this->loader->load($group); $loader = new NativeLoader($this->generator); @@ -65,7 +78,9 @@ public function process(?string $group = null): void $this->manager->persist($object); } } + $this->connection->commit(); } catch (\Throwable $exception) { + $this->connection->rollBack(); throw new FixtureException('Cant process fixtures', 0, $exception); } } diff --git a/module/fixture/src/Resources/fixtures/develop/conditions.yaml b/module/fixture/src/Resources/fixtures/develop/conditions.yaml new file mode 100644 index 000000000..240805750 --- /dev/null +++ b/module/fixture/src/Resources/fixtures/develop/conditions.yaml @@ -0,0 +1,25 @@ +Ergonode\Condition\Domain\Condition\AttributeExistsCondition: + condition_attribute_exists: + __construct: + - '' + +Ergonode\Core\Domain\ValueObject\TranslatableString: + condition_set_name: + __construct: + - + 'PL': 'Zbiór warunków' + 'EN': 'Condition set' + condition_set_description: + __construct: + - + 'PL': 'Opis do zbioru warunków' + 'EN': 'Description of condition set' + +Ergonode\Condition\Domain\Entity\ConditionSet: + condition_set: + __construct: + - '' + - '' + - '@condition_set_name' + - '@condition_set_description' + - ['@condition_attribute_exists'] diff --git a/module/fixture/src/Resources/fixtures/develop/fixture.yaml b/module/fixture/src/Resources/fixtures/develop/fixture.yaml index dc09520ef..91fd91f12 100644 --- a/module/fixture/src/Resources/fixtures/develop/fixture.yaml +++ b/module/fixture/src/Resources/fixtures/develop/fixture.yaml @@ -8,3 +8,5 @@ include: - 'templates.yaml' - 'categories.yaml' - 'products.yaml' + - 'segments.yaml' + - 'conditions.yaml' diff --git a/module/fixture/src/Resources/fixtures/develop/roles.yaml b/module/fixture/src/Resources/fixtures/develop/roles.yaml index fed4da07c..e28e4db8a 100644 --- a/module/fixture/src/Resources/fixtures/develop/roles.yaml +++ b/module/fixture/src/Resources/fixtures/develop/roles.yaml @@ -59,6 +59,18 @@ Ergonode\Account\Domain\Entity\Role: - '' - '' - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' admin_role: __construct: - '@admin_role_id' @@ -105,6 +117,18 @@ Ergonode\Account\Domain\Entity\Role: - '' - '' - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' data_inputer_role: __construct: - '@data_inputer_role_id' @@ -174,4 +198,4 @@ Ergonode\Account\Domain\Entity\Role: - '' - '' - '' - - '' \ No newline at end of file + - '' diff --git a/module/fixture/src/Resources/fixtures/develop/segments.yaml b/module/fixture/src/Resources/fixtures/develop/segments.yaml new file mode 100644 index 000000000..d9913674e --- /dev/null +++ b/module/fixture/src/Resources/fixtures/develop/segments.yaml @@ -0,0 +1,22 @@ +Ergonode\Core\Domain\ValueObject\TranslatableString: + segment_name: + __construct: + - + 'PL': 'Polish_segment_name' + 'EN': 'English_segment_name' + 'ZH': 'Chinese_segment_name' + segment_description: + __construct: + - + 'PL': 'Polish_segment_name' + 'EN': 'English_segment_name' + 'ZH': 'Chinese_segment_name' + +Ergonode\Segment\Domain\Entity\Segment: + test_segment: + __construct: + - '' + - '' + - '' + - '@segment_name' + - '@segment_description' diff --git a/module/fixture/src/Resources/fixtures/fixture.yaml b/module/fixture/src/Resources/fixtures/fixture.yaml index c675ca6a9..35929a1a0 100644 --- a/module/fixture/src/Resources/fixtures/fixture.yaml +++ b/module/fixture/src/Resources/fixtures/fixture.yaml @@ -75,6 +75,14 @@ Ergonode\Account\Domain\Entity\Role: - '' - '' - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' admin_role: __construct: - '@admin_role_id' @@ -121,6 +129,18 @@ Ergonode\Account\Domain\Entity\Role: - '' - '' - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' data_inputer_role: __construct: - '@data_inputer_role_id' diff --git a/module/grid/composer.json b/module/grid/composer.json index eb9261df2..d4ed73060 100644 --- a/module/grid/composer.json +++ b/module/grid/composer.json @@ -9,8 +9,8 @@ "ext-json": "*", "doctrine/collections": "^1.6", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", "symfony/http-foundation": "^4.3" }, "autoload": { diff --git a/module/grid/src/Renderer/GridRenderer.php b/module/grid/src/Renderer/GridRenderer.php index 616a58151..05cddbda5 100644 --- a/module/grid/src/Renderer/GridRenderer.php +++ b/module/grid/src/Renderer/GridRenderer.php @@ -53,11 +53,27 @@ public function render(AbstractGrid $grid, GridConfigurationInterface $configura $field = $configuration->getField(); $order = $configuration->getOrder(); $records = $dataSet->getItems($grid->getColumns(), $configuration->getLimit(), $configuration->getOffset(), $field, $order); - $result = []; - $result['configuration'] = $grid->getConfiguration(); - $result['columns'] = $this->columnRenderer->render($grid, []); - $result['collection'] = []; + $result = [ + 'configuration' => $grid->getConfiguration(), + 'columns' => $this->columnRenderer->render($grid, []), + 'collection' => [], + ]; + + // @todo HAX for column ordering (we need to refactor whole gird) + if (!empty($configuration->getColumns())) { + $columnsOrdered = []; + foreach (array_keys($configuration->getColumns()) as $name) { + foreach ($result['columns'] as $key => $column) { + if ($name === $column['id']) { + $columnsOrdered[] = $result['columns'][$key]; + break; + } + } + } + + $result['columns'] = $columnsOrdered; + } foreach ($records as $row) { $result['collection'][] = $this->rowRenderer->render($grid, $row); diff --git a/module/grid/src/RequestGridConfiguration.php b/module/grid/src/RequestGridConfiguration.php index 519cf7aca..c17eddb74 100644 --- a/module/grid/src/RequestGridConfiguration.php +++ b/module/grid/src/RequestGridConfiguration.php @@ -83,10 +83,15 @@ public function __construct(Request $request) foreach ($columns as $column) { $data = explode(':', $column); $key = $data[0]; + if (empty($key)) { + continue; + } + $language = null; if (isset($data[1])) { $language = new Language($data[1]); } + $this->columns[$column] = new RequestColumn($key, $language); } } diff --git a/module/importer/composer.json b/module/importer/composer.json index 9b0748bea..8cdd3ba12 100644 --- a/module/importer/composer.json +++ b/module/importer/composer.json @@ -7,12 +7,11 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/reader": "^0.4.0", - "ergonode/transformer": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/reader": "^0.5.0", + "ergonode/transformer": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/importer/migrations/Version20180618134343.php b/module/importer/migrations/Version20180618134343.php index cd07b086a..80e257c96 100644 --- a/module/importer/migrations/Version20180618134343.php +++ b/module/importer/migrations/Version20180618134343.php @@ -5,59 +5,76 @@ namespace Ergonode\Migration; use Doctrine\DBAL\Schema\Schema; -use Ergonode\Migration\AbstractErgonodeMigration; use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class */ final class Version20180618134343 extends AbstractErgonodeMigration { /** * @param Schema $schema + * + * @throws \Exception */ public function up(Schema $schema): void { $this->addSql('CREATE SCHEMA IF NOT EXISTS importer'); - $this->addSql( - 'CREATE TABLE importer.import ( - id UUID NOT NULL, - name VARCHAR(128) NOT NULL, - type VARCHAR(255) NOT NULL, - status VARCHAR(16) NOT NULL, - options JSON NOT NULL, - reason TEXT DEFAULT NULL, - created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - started_at TIMESTAMP WITHOUT TIME ZONE, - ended_at TIMESTAMP WITHOUT TIME ZONE, - PRIMARY KEY(id) - )' - ); - $this->addSql( - 'CREATE TABLE importer.import_line ( - id UUID NOT NULL, - lp BIGSERIAL, - import_id UUID NOT NULL, - line JSON NOT NULL, - created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - PRIMARY KEY(id) - )' - ); - $this->addSql( - 'CREATE TABLE importer.event_store ( - id BIGSERIAL NOT NULL, - aggregate_id uuid NOT NULL, - sequence int, - event character varying(255) NOT NULL, - payload jsonb NOT NULL, - recorded_by uuid default NULL, - recorded_at timestamp without time zone NOT NULL, - CONSTRAINT event_store_pkey PRIMARY KEY (id) - )' - ); + $this->addSql(' + CREATE TABLE importer.import ( + id UUID NOT NULL, + name VARCHAR(128) NOT NULL, + type VARCHAR(255) NOT NULL, + status VARCHAR(16) NOT NULL, + options JSON NOT NULL, + reason TEXT DEFAULT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + started_at TIMESTAMP WITHOUT TIME ZONE, + ended_at TIMESTAMP WITHOUT TIME ZONE, + PRIMARY KEY(id) + ) + '); + + $this->addSql(' + CREATE TABLE importer.import_line ( + id UUID NOT NULL, + lp BIGSERIAL, + import_id UUID NOT NULL, + line JSON NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + PRIMARY KEY(id) + ) + '); + + $this->addSql(' + CREATE TABLE importer.event_store ( + id BIGSERIAL NOT NULL, + aggregate_id uuid NOT NULL, + sequence int, + event_id UUID NOT NULL, + payload jsonb NOT NULL, + recorded_by uuid default NULL, + recorded_at timestamp without time zone NOT NULL, + CONSTRAINT event_store_pkey PRIMARY KEY (id) + ) + '); + + $this->addSql(' + CREATE TABLE importer.event_store_history ( + id BIGSERIAL NOT NULL, + aggregate_id uuid NOT NULL, + sequence int NOT NULL, + variant int NOT NULL DEFAULT 1, + event_id UUID NOT NULL, + payload jsonb NOT NULL, + recorded_by uuid default NULL, + recorded_at timestamp without time zone NOT NULL, + CONSTRAINT event_store_history_pkey PRIMARY KEY (id) + ) + '); + $this->addSql('CREATE UNIQUE INDEX importer_event_store_history_unique_key ON importer.event_store_history USING btree (aggregate_id, sequence, variant)'); $this->addSql('CREATE INDEX import_line_import_id_idx ON importer.import_line USING btree (import_id)'); $this->addSql('ALTER TABLE importer.import_line ADD CONSTRAINT import_line_import_id_fk FOREIGN KEY (import_id) REFERENCES importer.import (id) ON DELETE CASCADE'); diff --git a/module/multimedia/composer.json b/module/multimedia/composer.json index 22cce29bb..036428e51 100644 --- a/module/multimedia/composer.json +++ b/module/multimedia/composer.json @@ -7,10 +7,9 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/multimedia/src/Application/Controller/Api/MultimediaController.php b/module/multimedia/src/Application/Controller/Api/MultimediaController.php index 43c336180..0caf8a282 100644 --- a/module/multimedia/src/Application/Controller/Api/MultimediaController.php +++ b/module/multimedia/src/Application/Controller/Api/MultimediaController.php @@ -81,7 +81,7 @@ public function uploadMultimedia(Request $request): Response $form = $this->createForm(MultimediaUploadForm::class, $uploadModel); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $command = new UploadMultimediaCommand('TestName', $uploadModel->upload); + $command = new UploadMultimediaCommand('Default', $uploadModel->upload); $this->messageBus->dispatch($command); $response = new CreatedResponse($command->getId()); diff --git a/module/multimedia/src/Domain/Command/UploadMultimediaCommand.php b/module/multimedia/src/Domain/Command/UploadMultimediaCommand.php index f097a71ef..6c8d4b77a 100644 --- a/module/multimedia/src/Domain/Command/UploadMultimediaCommand.php +++ b/module/multimedia/src/Domain/Command/UploadMultimediaCommand.php @@ -39,7 +39,7 @@ class UploadMultimediaCommand */ public function __construct(string $name, UploadedFile $file) { - $this->id = MultimediaId::generate(); + $this->id = MultimediaId::createFromFile($file); $this->name = $name; $this->file = $file; } diff --git a/module/multimedia/src/Domain/Entity/MultimediaId.php b/module/multimedia/src/Domain/Entity/MultimediaId.php index 47d1e92a9..d2d97af67 100644 --- a/module/multimedia/src/Domain/Entity/MultimediaId.php +++ b/module/multimedia/src/Domain/Entity/MultimediaId.php @@ -25,6 +25,6 @@ class MultimediaId extends AbstractId */ public static function createFromFile(\SplFileInfo $file): self { - return new self(Uuid::uuid5(self::NAMESPACE, file_get_contents($file->getRealPath()))->toString()); + return new self(Uuid::uuid5(self::NAMESPACE, sha1_file($file->getRealPath()))->toString()); } } diff --git a/module/product-simple/composer.json b/module/product-simple/composer.json index 59d7fa8ab..f4779ad50 100644 --- a/module/product-simple/composer.json +++ b/module/product-simple/composer.json @@ -13,7 +13,7 @@ }, "require": { "php": "^7.2", - "ergonode/product": "^0.4.0" + "ergonode/product": "^0.5.0" }, "autoload": { "psr-4": { diff --git a/module/product/composer.json b/module/product/composer.json index 8906ad156..c671e5dbf 100644 --- a/module/product/composer.json +++ b/module/product/composer.json @@ -7,15 +7,14 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/category": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/migration": "^0.4.0", - "ergonode/workflow": "^0.4.0", - "ergonode/value": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/category": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/migration": "^0.5.0", + "ergonode/workflow": "^0.5.0", + "ergonode/value": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/product/migrations/Version20180619083830.php b/module/product/migrations/Version20180619083830.php index 5bedc2ce8..9dab127ad 100644 --- a/module/product/migrations/Version20180619083830.php +++ b/module/product/migrations/Version20180619083830.php @@ -18,22 +18,25 @@ final class Version20180619083830 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql( - 'CREATE TABLE IF NOT EXISTS product ( - id UUID NOT NULL, - index SERIAL, - template_id UUID NOT NULL, - sku VARCHAR(128) NOT NULL, - status VARCHAR(32) NOT NULL, - version INT NOT NULL DEFAULT 0, - attributes JSONB NOT NULL DEFAULT \'{}\'::JSONB, - PRIMARY KEY(id) - )' - ); - + $this->addSql(' + CREATE TABLE IF NOT EXISTS product ( + id UUID NOT NULL, + index SERIAL, + template_id UUID NOT NULL, + sku VARCHAR(128) NOT NULL, + status VARCHAR(32) NOT NULL, + version INT NOT NULL DEFAULT 0, + attributes JSONB NOT NULL DEFAULT \'{}\'::JSONB, + PRIMARY KEY(id) + ) + '); $this->addSql('CREATE UNIQUE INDEX product_sku_key ON product USING btree(sku)'); + $this->addSql('CREATE TABLE product_value (product_id UUID NOT NULL, attribute_id UUID NOT NULL, value_id UUID NOT NULL, PRIMARY KEY(product_id, attribute_id, value_id))'); + $this->addSql('ALTER TABLE product_value ADD CONSTRAINT product_value_product_id_fk FOREIGN KEY (product_id) references product on update cascade on delete cascade;'); + $this->addSql('CREATE TABLE product_category_product (category_id UUID NOT NULL, product_id UUID NOT NULL, PRIMARY KEY(category_id, product_id))'); + $this->addSql('ALTER TABLE product_category_product ADD CONSTRAINT product_category_product_product_id_fk FOREIGN KEY (product_id) references product on update cascade on delete cascade'); $this->addSql('CREATE TABLE product_status (code VARCHAR(32), name VARCHAR(64), PRIMARY KEY(code))'); $this->addSql('INSERT INTO product_status (code, name) VALUES(?, ?)', ['DRAFT', 'Draft']); @@ -45,5 +48,31 @@ public function up(Schema $schema): void $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'PRODUCT_READ', 'Product']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'PRODUCT_UPDATE', 'Product']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'PRODUCT_DELETE', 'Product']); + + $this->createEventStoreEvents([ + 'Ergonode\Product\Domain\Event\ProductAddedToCategory' => 'Product added to category', + 'Ergonode\Product\Domain\Event\ProductCreated' => 'Product created', + 'Ergonode\Product\Domain\Event\ProductRemovedFromCategory' => 'Product removed from category', + 'Ergonode\Product\Domain\Event\ProductValueAdded' => 'Product attribute value added', + 'Ergonode\Product\Domain\Event\ProductValueChanged' => 'Product attribute value changed', + 'Ergonode\Product\Domain\Event\ProductValueRemoved' => 'Product attribute value removed', + 'Ergonode\Product\Domain\Event\ProductDeletedEvent' => 'Product deleted', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/product/src/Application/Controller/Api/ProductController.php b/module/product/src/Application/Controller/Api/ProductController.php index bb5f86dc1..c7a39b713 100644 --- a/module/product/src/Application/Controller/Api/ProductController.php +++ b/module/product/src/Application/Controller/Api/ProductController.php @@ -22,6 +22,7 @@ use Ergonode\Product\Application\Model\ProductCreateFormModel; use Ergonode\Product\Application\Model\ProductUpdateFormModel; use Ergonode\Product\Domain\Command\CreateProductCommand; +use Ergonode\Product\Domain\Command\DeleteProductCommand; use Ergonode\Product\Domain\Command\UpdateProductCommand; use Ergonode\Product\Domain\Entity\AbstractProduct; use Ergonode\Product\Domain\ValueObject\Sku; @@ -269,7 +270,7 @@ public function createProduct(Request $request): Response * in="body", * description="Add product", * required=true, - * @SWG\Schema(ref="#/definitions/product") + * @SWG\Schema(ref="#/definitions/product_upd") * ) * @SWG\Response( * response=204, @@ -305,4 +306,47 @@ public function updateProduct(AbstractProduct $product, Request $request): Respo throw new FormValidationHttpException($form); } + + /** + * @Route("/products/{product}", methods={"DELETE"}, requirements={"product"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("PRODUCT_DELETE") + * + * @SWG\Tag(name="Product") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="product", + * in="path", + * required=true, + * type="string", + * description="Product ID", + * ) + * @SWG\Response( + * response=204, + * description="Success" + * ) + * @SWG\Response( + * response=404, + * description="Not found", + * ) + * + * @ParamConverter(class="Ergonode\Product\Domain\Entity\AbstractProduct") + * + * @param AbstractProduct $product + * + * @return Response + */ + public function deleteProduct(AbstractProduct $product): Response + { + $command = new DeleteProductCommand($product->getId()); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } } diff --git a/module/product/src/Application/Model/ProductCreateFormModel.php b/module/product/src/Application/Model/ProductCreateFormModel.php index 799585119..de62ce853 100644 --- a/module/product/src/Application/Model/ProductCreateFormModel.php +++ b/module/product/src/Application/Model/ProductCreateFormModel.php @@ -9,6 +9,7 @@ namespace Ergonode\Product\Application\Model; +use Ergonode\Designer\Infrastructure\Validator\TemplateExists; use Ergonode\Product\Infrastructure\Validator\Sku; use Ergonode\Product\Infrastructure\Validator\SkuExists; use Symfony\Component\Validator\Constraints as Assert; @@ -36,6 +37,7 @@ class ProductCreateFormModel * * @Assert\NotBlank(message="Template is required") * @Assert\Uuid() + * @TemplateExists() */ public $template; diff --git a/module/product/src/Domain/Command/DeleteProductCommand.php b/module/product/src/Domain/Command/DeleteProductCommand.php new file mode 100644 index 000000000..1dc13b9bf --- /dev/null +++ b/module/product/src/Domain/Command/DeleteProductCommand.php @@ -0,0 +1,41 @@ +id = $id; + } + + /** + * @return ProductId + */ + public function getId(): ProductId + { + return $this->id; + } +} diff --git a/module/product/src/Domain/Event/ProductDeletedEvent.php b/module/product/src/Domain/Event/ProductDeletedEvent.php new file mode 100644 index 000000000..2574ebde8 --- /dev/null +++ b/module/product/src/Domain/Event/ProductDeletedEvent.php @@ -0,0 +1,19 @@ +repository = $repository; + } + + /** + * @param DeleteProductCommand $command + * + * @throws \Exception + */ + public function __invoke(DeleteProductCommand $command) + { + $product = $this->repository->load($command->getId()); + Assert::isInstanceOf($product, AbstractProduct::class, sprintf('Can\'t find product with id "%s"', $command->getId())); + + $this->repository->delete($product); + } +} diff --git a/module/product/src/Infrastructure/Handler/UpdateProductCommandHandler.php b/module/product/src/Infrastructure/Handler/UpdateProductCommandHandler.php index 902730d89..0d263207e 100644 --- a/module/product/src/Infrastructure/Handler/UpdateProductCommandHandler.php +++ b/module/product/src/Infrastructure/Handler/UpdateProductCommandHandler.php @@ -16,7 +16,6 @@ use Webmozart\Assert\Assert; /** - * Class UpdateProductCommandHandler */ class UpdateProductCommandHandler { @@ -42,6 +41,8 @@ public function __construct(ProductRepositoryInterface $productRepository, Categ /** * @param UpdateProductCommand $command + * + * @throws \Exception */ public function __invoke(UpdateProductCommand $command) { diff --git a/module/product/src/Persistence/Dbal/Projector/ProductAddedToCategoryEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductAddedToCategoryEventProjector.php index 7eebc6923..e38cf346a 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductAddedToCategoryEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductAddedToCategoryEventProjector.php @@ -39,9 +39,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -49,11 +47,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -61,20 +55,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductAddedToCategory::class); } - $this->connection->beginTransaction(); - - try { - $this->connection->insert( - self::TABLE_PRODUCT_CATEGORY, - [ - 'product_id' => $aggregateId->getValue(), - 'category_id' => CategoryId::fromCode($event->getCategoryCode()), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->insert( + self::TABLE_PRODUCT_CATEGORY, + [ + 'product_id' => $aggregateId->getValue(), + 'category_id' => CategoryId::fromCode($event->getCategoryCode()), + ] + ); } } diff --git a/module/product/src/Persistence/Dbal/Projector/ProductCreateEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductCreateEventProjector.php index e83b934a4..f77ee11d9 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductCreateEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductCreateEventProjector.php @@ -46,8 +46,6 @@ class ProductCreateEventProjector implements DomainEventProjectorInterface private $cache = []; /** - * ProductCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -56,9 +54,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -66,11 +62,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -78,9 +70,9 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductCreated::class); } - try { + $this->connection->transactional(function () use ($aggregateId, $event) { $productId = $aggregateId->getValue(); - $this->connection->beginTransaction(); + $this->connection->insert( self::TABLE_PRODUCT, [ @@ -105,12 +97,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) $attributeId = AttributeId::fromKey(new AttributeCode($code))->getValue(); $this->insertValue($productId, $attributeId, $value); } - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + }); } /** diff --git a/module/product/src/Persistence/Dbal/Projector/ProductDeletedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductDeletedEventProjector.php new file mode 100644 index 000000000..dfb8cef29 --- /dev/null +++ b/module/product/src/Persistence/Dbal/Projector/ProductDeletedEventProjector.php @@ -0,0 +1,62 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof ProductDeletedEvent; + } + + /** + * {@inheritDoc} + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof ProductDeletedEvent) { + throw new UnsupportedEventException($event, ProductDeletedEvent::class); + } + + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/product/src/Persistence/Dbal/Projector/ProductRemovedFromCategoryEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductRemovedFromCategoryEventProjector.php index 9525eef4e..3d7511cfe 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductRemovedFromCategoryEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductRemovedFromCategoryEventProjector.php @@ -29,8 +29,6 @@ class ProductRemovedFromCategoryEventProjector implements DomainEventProjectorIn private $connection; /** - * ProductCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -39,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -49,11 +45,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -61,20 +53,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductRemovedFromCategory::class); } - $this->connection->beginTransaction(); - - try { - $this->connection->delete( - self::TABLE_PRODUCT_CATEGORY, - [ - 'product_id' => $aggregateId->getValue(), - 'category_id' => CategoryId::fromCode($event->getCategoryCode()), - ] - ); - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->delete( + self::TABLE_PRODUCT_CATEGORY, + [ + 'product_id' => $aggregateId->getValue(), + 'category_id' => CategoryId::fromCode($event->getCategoryCode()), + ] + ); } } diff --git a/module/product/src/Persistence/Dbal/Projector/ProductStatusChangedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductStatusChangedEventProjector.php deleted file mode 100644 index 438b71d26..000000000 --- a/module/product/src/Persistence/Dbal/Projector/ProductStatusChangedEventProjector.php +++ /dev/null @@ -1,79 +0,0 @@ -connection = $connection; - } - - /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable - */ - public function projection(AbstractId $aggregateId, DomainEventInterface $event): void - { - if (!$event instanceof ProductStatusChanged) { - throw new UnsupportedEventException($event, ProductStatusChanged::class); - } - - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE_PRODUCT, - [ - 'status' => $event->getTo()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } - } -} diff --git a/module/product/src/Persistence/Dbal/Projector/ProductValueAddedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductValueAddedEventProjector.php index ff2b0ba3e..8f51745e7 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductValueAddedEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductValueAddedEventProjector.php @@ -37,8 +37,6 @@ class ProductValueAddedEventProjector implements DomainEventProjectorInterface private $connection; /** - * ProductCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -47,9 +45,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -57,10 +53,8 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event + * {@inheritDoc} * - * @throws \Doctrine\DBAL\ConnectionException * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void @@ -69,19 +63,12 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductValueAdded::class); } - $this->connection->beginTransaction(); - - try { + $this->connection->transactional(function () use ($aggregateId, $event) { $productId = $aggregateId->getValue(); $attributeId = AttributeId::fromKey($event->getAttributeCode())->getValue(); $this->insertValue($productId, $attributeId, $event->getValue()); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + }); } /** diff --git a/module/product/src/Persistence/Dbal/Projector/ProductValueChangedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductValueChangedEventProjector.php index 7f937e1c3..acfa0938d 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductValueChangedEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductValueChangedEventProjector.php @@ -37,8 +37,6 @@ class ProductValueChangedEventProjector implements DomainEventProjectorInterface private $connection; /** - * ProductCreateEventProjector constructor. - * * @param Connection $connection */ public function __construct(Connection $connection) @@ -47,9 +45,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -57,11 +53,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -69,19 +61,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductValueChanged::class); } - $this->connection->beginTransaction(); - try { + $this->connection->transactional(function () use ($aggregateId, $event) { $productId = $aggregateId->getValue(); $attributeId = AttributeId::fromKey($event->getAttributeCode())->getValue(); $this->delete($productId, $attributeId); $this->insertValue($productId, $attributeId, $event->getTo()); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + }); } /** diff --git a/module/product/src/Persistence/Dbal/Projector/ProductValueRemovedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductValueRemovedEventProjector.php index 632070b61..08a853778 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductValueRemovedEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductValueRemovedEventProjector.php @@ -37,9 +37,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,11 +45,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -59,15 +53,7 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductValueRemoved::class); } - $this->connection->beginTransaction(); - try { - $this->delete($aggregateId->getValue(), AttributeId::fromKey($event->getAttributeCode())->getValue()); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->delete($aggregateId->getValue(), AttributeId::fromKey($event->getAttributeCode())->getValue()); } /** diff --git a/module/product/src/Persistence/Dbal/Projector/ProductVersionIncreasedEventProjector.php b/module/product/src/Persistence/Dbal/Projector/ProductVersionIncreasedEventProjector.php index 3a313c1a3..59acfaf84 100644 --- a/module/product/src/Persistence/Dbal/Projector/ProductVersionIncreasedEventProjector.php +++ b/module/product/src/Persistence/Dbal/Projector/ProductVersionIncreasedEventProjector.php @@ -28,29 +28,23 @@ class ProductVersionIncreasedEventProjector implements DomainEventProjectorInter private $connection; /** - * @param DomainEventInterface $event - * - * @return bool + * @param Connection $connection */ - public function support(DomainEventInterface $event): bool + public function __construct(Connection $connection) { - return $event instanceof ProductVersionIncreased; + $this->connection = $connection; } /** - * @param Connection $connection + * {@inheritDoc} */ - public function __construct(Connection $connection) + public function support(DomainEventInterface $event): bool { - $this->connection = $connection; + return $event instanceof ProductVersionIncreased; } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws \Doctrine\DBAL\ConnectionException - * @throws \Throwable + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -58,22 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ProductVersionIncreased::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE_PRODUCT, - [ - 'version' => $event->getTo(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw $exception; - } + $this->connection->update( + self::TABLE_PRODUCT, + [ + 'version' => $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/product/src/Persistence/Dbal/Query/DbalProductQuery.php b/module/product/src/Persistence/Dbal/Query/DbalProductQuery.php index eec9298be..cbabc6d61 100644 --- a/module/product/src/Persistence/Dbal/Query/DbalProductQuery.php +++ b/module/product/src/Persistence/Dbal/Query/DbalProductQuery.php @@ -16,6 +16,7 @@ use Ergonode\Product\Domain\Entity\ProductId; use Ergonode\Product\Domain\Query\ProductQueryInterface; use Ergonode\Product\Domain\ValueObject\Sku; +use Ergonode\Workflow\Domain\Entity\StatusId; /** */ @@ -110,6 +111,42 @@ public function findProductIdByTemplateId(TemplateId $templateId): array ->fetchAll(\PDO::FETCH_COLUMN); } + /** + * {@inheritDoc} + */ + public function findProductIdByStatusId(StatusId $statusId): array + { + $statusCode = $this->connection->createQueryBuilder() + ->select('s.code') + ->from('public.status', 's') + ->where('s.id = :id') + ->setParameter('id', $statusId->getValue()) + ->setMaxResults(1) + ->execute()->fetchColumn(); + + // @todo This is unacceptable! + $attributeId = $this->connection->createQueryBuilder() + ->select('a.id') + ->from('public.attribute', 'a') + ->where('a.code = \'esa_status\'') + ->setMaxResults(1) + ->execute()->fetchColumn(); + + // @todo That's too! + $queryBuilder = $this->connection->createQueryBuilder(); + $queryBuilder + ->select('p.id') + ->from(self::PRODUCT_TABLE, 'p') + ->join('p', 'designer.draft', 'd', 'p.id = d.product_id') + ->join('d', 'designer.draft_value', 'dv', 'd.id = dv.draft_id') + ->where('dv.value = :statusCode') + ->andWhere('dv.element_id = :attributeId') + ->setParameter('statusCode', $statusCode) + ->setParameter('attributeId', $attributeId); + + return $queryBuilder->execute()->fetchAll(\PDO::FETCH_COLUMN); + } + /** * @return QueryBuilder */ diff --git a/module/product/src/Persistence/Dbal/Query/Decorator/CacheProductQueryDecorator.php b/module/product/src/Persistence/Dbal/Query/Decorator/CacheProductQueryDecorator.php index 1b596a975..2ff04e298 100644 --- a/module/product/src/Persistence/Dbal/Query/Decorator/CacheProductQueryDecorator.php +++ b/module/product/src/Persistence/Dbal/Query/Decorator/CacheProductQueryDecorator.php @@ -14,6 +14,7 @@ use Ergonode\Product\Domain\Entity\ProductId; use Ergonode\Product\Domain\Query\ProductQueryInterface; use Ergonode\Product\Domain\ValueObject\Sku; +use Ergonode\Workflow\Domain\Entity\StatusId; /** */ @@ -79,4 +80,14 @@ public function findProductIdByTemplateId(TemplateId $templateId): array { return $this->query->findProductIdByTemplateId($templateId); } + + /** + * @param StatusId $statusId + * + * @return array + */ + public function findProductIdByStatusId(StatusId $statusId): array + { + return $this->query->findProductIdByStatusId($statusId); + } } diff --git a/module/product/src/Persistence/Dbal/Repository/DbalProductRepository.php b/module/product/src/Persistence/Dbal/Repository/DbalProductRepository.php index 830babfcc..4abb1282e 100644 --- a/module/product/src/Persistence/Dbal/Repository/DbalProductRepository.php +++ b/module/product/src/Persistence/Dbal/Repository/DbalProductRepository.php @@ -13,6 +13,7 @@ use Ergonode\EventSourcing\Infrastructure\DomainEventDispatcherInterface; use Ergonode\EventSourcing\Infrastructure\DomainEventStoreInterface; use Ergonode\Product\Domain\Entity\ProductId; +use Ergonode\Product\Domain\Event\ProductDeletedEvent; use Ergonode\Product\Domain\Repository\ProductRepositoryInterface; use Ergonode\ProductSimple\Domain\Entity\SimpleProduct; @@ -41,9 +42,7 @@ public function __construct(DomainEventStoreInterface $eventStore, DomainEventDi } /** - * @param ProductId $id - * - * @return AbstractAggregateRoot|null + * {@inheritDoc} * * @throws \ReflectionException */ @@ -69,7 +68,7 @@ public function load(ProductId $id): ?AbstractAggregateRoot } /** - * @param AbstractAggregateRoot $aggregateRoot + * {@inheritDoc} */ public function save(AbstractAggregateRoot $aggregateRoot): void { @@ -80,4 +79,17 @@ public function save(AbstractAggregateRoot $aggregateRoot): void $this->eventDispatcher->dispatch($envelope); } } + + /** + * {@inheritDoc} + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new ProductDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } } diff --git a/module/product/src/Resources/translations/log.en.yaml b/module/product/src/Resources/translations/log.en.yaml index 56bc9f7c3..470593f7b 100644 --- a/module/product/src/Resources/translations/log.en.yaml +++ b/module/product/src/Resources/translations/log.en.yaml @@ -1,6 +1,7 @@ -"Ergonode\\Product\\Domain\\Event\\ProductAddedToCategory": Product added to category "%categoryCode%" -"Ergonode\\Product\\Domain\\Event\\ProductCreated": Product "%sku%" created -"Ergonode\\Product\\Domain\\Event\\ProductRemovedFromCategory": Product removed from category "%categoryCode%" -"Ergonode\\Product\\Domain\\Event\\ProductValueAdded": Product value of attribute "%code%" added -"Ergonode\\Product\\Domain\\Event\\ProductValueChanged": Product value of attribute "%code%" changed -"Ergonode\\Product\\Domain\\Event\\ProductValueRemoved": Product value of attribute "%code%" removed +'Product added to category': 'Product added to category "%categoryCode%"' +'Product created': 'Product "%sku%" created' +'Product removed from category': 'Product removed from category "%categoryCode%"' +'Product attribute value added': 'Product value of attribute "%code%" added' +'Product attribute value changed': 'Product value of attribute "%code%" changed' +'Product attribute value removed': 'Product value of attribute "%code%" removed' +'Product deleted': 'Product "%id%" deleted' diff --git a/module/product/src/Resources/translations/log.pl.yaml b/module/product/src/Resources/translations/log.pl.yaml index e2d4430a4..c10ca02ab 100644 --- a/module/product/src/Resources/translations/log.pl.yaml +++ b/module/product/src/Resources/translations/log.pl.yaml @@ -1,6 +1,7 @@ -"Ergonode\\Product\\Domain\\Event\\ProductAddedToCategory": Produkt dodany do kategorii "%categoryCode%" -"Ergonode\\Product\\Domain\\Event\\ProductCreated": Produkt "%sku%" utworzony -"Ergonode\\Product\\Domain\\Event\\ProductRemovedFromCategory": Produkt usunięty z kategorii "%categoryCode%" -"Ergonode\\Product\\Domain\\Event\\ProductValueAdded": Dodano wartość atrybutu "%code%" -"Ergonode\\Product\\Domain\\Event\\ProductValueChanged": Zmieniono wartość atrybutu "%code%" -"Ergonode\\Product\\Domain\\Event\\ProductValueRemoved": Usunięto wartość atrybutu "%code%" +'Product added to category': 'Produkt dodany do kategorii "%categoryCode%"' +'Product created': 'Produkt "%sku%" utworzony' +'Product removed from category': 'Produkt usunięty z kategorii "%categoryCode%"' +'Product attribute value added': 'Dodano wartość atrybutu "%code%"' +'Product attribute value changed': 'Zmieniono wartość atrybutu "%code%"' +'Product attribute value removed': 'Usunięto wartość atrybutu "%code%"' +'Product deleted': 'Produkt "%id%" usunięto' diff --git a/module/reader/composer.json b/module/reader/composer.json index 973829b89..a1e4f71dd 100644 --- a/module/reader/composer.json +++ b/module/reader/composer.json @@ -7,12 +7,11 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", - "ergonode/migration": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/migration": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/reader/migrations/Version20180619100000.php b/module/reader/migrations/Version20180619100000.php index df5f4b19c..0bea82014 100644 --- a/module/reader/migrations/Version20180619100000.php +++ b/module/reader/migrations/Version20180619100000.php @@ -5,10 +5,9 @@ namespace Ergonode\Migration; use Doctrine\DBAL\Schema\Schema; -use Ergonode\Migration\AbstractErgonodeMigration; +use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class */ final class Version20180619100000 extends AbstractErgonodeMigration { @@ -19,13 +18,33 @@ public function up(Schema $schema): void { $this->addSql('CREATE SCHEMA IF NOT EXISTS importer'); - $this->addSql( - 'CREATE TABLE IF NOT EXISTS importer.reader ( - id UUID NOT NULL, - name VARCHAR(64) NOT NULL, - type VARCHAR(32) NOT NULL, - PRIMARY KEY(id) - )' - ); + $this->addSql(' + CREATE TABLE IF NOT EXISTS importer.reader ( + id UUID NOT NULL, + name VARCHAR(64) NOT NULL, + type VARCHAR(32) NOT NULL, + PRIMARY KEY(id) + ) + '); + + $this->createEventStoreEvents([ + 'Ergonode\Reader\Domain\Event\ReaderCreatedEvent' => 'Reader created', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/reader/src/Persistence/Dbal/Projector/ReaderCreatedEventProjector.php b/module/reader/src/Persistence/Dbal/Projector/ReaderCreatedEventProjector.php index da348aac4..98cd1ea3d 100644 --- a/module/reader/src/Persistence/Dbal/Projector/ReaderCreatedEventProjector.php +++ b/module/reader/src/Persistence/Dbal/Projector/ReaderCreatedEventProjector.php @@ -12,7 +12,6 @@ use Doctrine\DBAL\Connection; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Reader\Domain\Event\ReaderCreatedEvent; @@ -37,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws \Doctrine\DBAL\ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -60,21 +52,13 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, ReaderCreatedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->insert( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - 'name' => $event->getName(), - 'type' => $event->getType(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + 'name' => $event->getName(), + 'type' => $event->getType(), + ] + ); } } diff --git a/module/reader/src/Resources/translations/log.en.yaml b/module/reader/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..d478bbaf4 --- /dev/null +++ b/module/reader/src/Resources/translations/log.en.yaml @@ -0,0 +1 @@ +'Reader created': 'Reader "%name%" created' diff --git a/module/reader/src/Resources/translations/log.pl.yaml b/module/reader/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..1abfa5ed3 --- /dev/null +++ b/module/reader/src/Resources/translations/log.pl.yaml @@ -0,0 +1 @@ +'Reader created': 'Reader "%name%" utworzony' diff --git a/module/segment/LICENSE.txt b/module/segment/LICENSE.txt new file mode 100644 index 000000000..996dcc641 --- /dev/null +++ b/module/segment/LICENSE.txt @@ -0,0 +1,26 @@ +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + a) to reproduce the Original Work in copies, either alone or as part of a collective work; + b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + d) to perform the Original Work publicly; and + e) to display the Original Work publicly. + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/module/segment/README.md b/module/segment/README.md new file mode 100644 index 000000000..b44db384c --- /dev/null +++ b/module/segment/README.md @@ -0,0 +1,19 @@ +# Ergonode - Segment + +## Documentation + +* Follow link to [**Ergonode Documentation**](https://docs.ergonode.com), + +## Community + +* Get Ergonode support on **Stack Overflow**, [**Slack**](https://ergonode.slack.com) and [**email**](team@ergonode.com). +* Follow us on [**GitHub**](https://github.com/ergonode), [**Twitter**](https://twitter.com/ergonode) and [**Facebook**](https://www.facebook.com/ergonode), + +## Contributing + +Ergonode is a Open Source. Join us as a [**contributor**](https://ergonode.com/contribution). + +## About Us + +Ergonode development is sponsored by Bold Brand Commerce Sp. z o.o., lead by **Eronode Core Team** and supported by Ergonode contributors. + diff --git a/module/segment/composer.json b/module/segment/composer.json new file mode 100644 index 000000000..600eea0a8 --- /dev/null +++ b/module/segment/composer.json @@ -0,0 +1,30 @@ +{ + "name": "ergonode/segment", + "type": "ergonode-module", + "description": "Ergonode - Segment", + "homepage": "https://ergonode.com", + "license": "OSL-3.0", + "require": { + "php": "^7.2", + "doctrine/dbal": "^2.9", + "ergonode/attribute": "^0.5.0", + "ergonode/product": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", + "ergonode/value": "^0.5.0", + "jms/serializer": "^3.1", + "nelmio/api-doc-bundle": "^3.4", + "ramsey/uuid": "^3.8", + "sensio/framework-extra-bundle": "^5.4", + "symfony/form": "^4.3", + "symfony/messenger": "^4.3", + "symfony/translation": "^4.3", + "symfony/validator": "^4.3" + }, + "autoload": { + "psr-4": { + "Ergonode\\Segment\\": "src/" + } + } +} diff --git a/module/segment/migrations/Version20190130104000.php b/module/segment/migrations/Version20190130104000.php new file mode 100644 index 000000000..c0a97c8af --- /dev/null +++ b/module/segment/migrations/Version20190130104000.php @@ -0,0 +1,70 @@ +addSql(' + CREATE TABLE segment ( + id UUID NOT NULL, + code VARCHAR(100) NOT NULL, + name JSON NOT NULL, + description JSON NOT NULL, + status VARCHAR(32) NOT NULL, + condition_set_id UUID DEFAULT NULL, + PRIMARY KEY(id) + ) + '); + $this->addSql('CREATE UNIQUE index segment_code_uindex ON segment (code)'); + + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'SEGMENT_CREATE', 'Segment']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'SEGMENT_READ', 'Segment']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'SEGMENT_UPDATE', 'Segment']); + $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'SEGMENT_DELETE', 'Segment']); + + $this->createEventStoreEvents([ + 'Ergonode\Segment\Domain\Event\SegmentCreatedEvent' => 'Segment created', + 'Ergonode\Segment\Domain\Event\SegmentDescriptionChangedEvent' => 'Segment description changed', + 'Ergonode\Segment\Domain\Event\SegmentNameChangedEvent' => 'Segment name changed', + 'Ergonode\Segment\Domain\Event\SegmentSpecificationAddedEvent' => 'Segment specification added', + 'Ergonode\Segment\Domain\Event\SegmentStatusChangedEvent' => 'Segment status changed', + 'Ergonode\Segment\Domain\Event\SegmentDeletedEvent' => 'Segment deleted', + 'Ergonode\Segment\Domain\Event\SegmentConditionSetChangedEvent' => 'Segment condition set changed', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } +} diff --git a/module/segment/src/Application/Controller/Api/SegmentController.php b/module/segment/src/Application/Controller/Api/SegmentController.php new file mode 100644 index 000000000..2dbf2c871 --- /dev/null +++ b/module/segment/src/Application/Controller/Api/SegmentController.php @@ -0,0 +1,357 @@ +grid = $grid; + $this->query = $query; + $this->messageBus = $messageBus; + } + + /** + * @Route("segments", methods={"GET"}) + * + * @IsGranted("SEGMENT_READ") + * + * @SWG\Tag(name="Segment") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * required=true, + * default="EN", + * description="Language Code", + * ) + * @SWG\Parameter( + * name="limit", + * in="query", + * type="integer", + * required=true, + * default="50", + * description="Number of returned lines", + * ) + * @SWG\Parameter( + * name="offset", + * in="query", + * type="integer", + * required=true, + * default="0", + * description="Number of start line", + * ) + * @SWG\Parameter( + * name="field", + * in="query", + * required=false, + * type="string", + * description="Order field", + * ) + * @SWG\Parameter( + * name="order", + * in="query", + * required=false, + * type="string", + * enum={"ASC","DESC"}, + * description="Order", + * ) + * @SWG\Parameter( + * name="filter", + * in="query", + * required=false, + * type="string", + * description="Filter" + * ) + * @SWG\Parameter( + * name="show", + * in="query", + * required=false, + * type="string", + * enum={"COLUMN","DATA"}, + * description="Specify what response should containts" + * ) + * @SWG\Response( + * response=200, + * description="Returns imported data collection", + * ) + * + * @ParamConverter(class="Ergonode\Grid\RequestGridConfiguration") + * + * @param Language $language + * @param RequestGridConfiguration $configuration + * + * @return Response + */ + public function getSegments(Language $language, RequestGridConfiguration $configuration): Response + { + return new GridResponse($this->grid, $configuration, $this->query->getDataSet($language), $language); + } + + /** + * @Route("/segments/{segment}", methods={"GET"}, requirements={"segment" = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("SEGMENT_READ") + * + * @SWG\Tag(name="Segment") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="segment", + * in="path", + * type="string", + * description="Segment ID", + * ) + * @SWG\Response( + * response=200, + * description="Returns segment", + * ) + * @SWG\Response( + * response=404, + * description="Not found", + * ) + * + * @ParamConverter(class="Ergonode\Segment\Domain\Entity\Segment") + * + * @param Segment $segment + * + * @return Response + */ + public function getSegment(Segment $segment): Response + { + return new SuccessResponse($segment); + } + + /** + * @Route("/segments", methods={"POST"}) + * + * @IsGranted("SEGMENT_CREATE") + * + * @SWG\Tag(name="Segment") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * description="Add segment", + * required=true, + * @SWG\Schema(ref="#/definitions/segment") + * ) + * @SWG\Response( + * response=200, + * description="Returns segment", + * ) + * @SWG\Response( + * response=404, + * description="Not found", + * ) + * + * @param Request $request + * + * @return Response + * + * @throws \Exception + */ + public function createSegment(Request $request): Response + { + $form = $this->createForm(CreateSegmentForm::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var CreateSegmentFormModel $data */ + $data = $form->getData(); + + $command = new CreateSegmentCommand( + new SegmentCode($data->code), + new ConditionSetId($data->conditionSetId), + new TranslatableString($data->name), + new TranslatableString($data->description) + ); + $this->messageBus->dispatch($command); + + return new CreatedResponse($command->getId()); + } + + throw new FormValidationHttpException($form); + } + + /** + * @Route("/segments/{segment}", methods={"PUT"}, requirements={"segment"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("SEGMENT_UPDATE") + * + * @SWG\Tag(name="Segment") + * @SWG\Parameter( + * name="segment", + * in="path", + * type="string", + * description="Segment id", + * ) + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="body", + * in="body", + * description="Add segment", + * required=true, + * @SWG\Schema(ref="#/definitions/segment") + * ) + * @SWG\Response( + * response=200, + * description="Returns segment", + * ) + * @SWG\Response( + * response=404, + * description="Not found", + * ) + * + * @ParamConverter(class="Ergonode\Segment\Domain\Entity\Segment") + * + * @param Segment $segment + * @param Request $request + * + * @return Response + */ + public function updateSegment(Segment $segment, Request $request): Response + { + $model = new UpdateSegmentFormModel(); + $form = $this->createForm(UpdateSegmentForm::class, $model, ['method' => Request::METHOD_PUT]); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var CreateSegmentFormModel $data */ + $data = $form->getData(); + + $command = new UpdateSegmentCommand( + $segment->getId(), + new TranslatableString($data->name), + new TranslatableString($data->description), + new ConditionSetId($data->conditionSetId) + ); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } + + throw new FormValidationHttpException($form); + } + + /** + * @Route("/segments/{segment}", methods={"DELETE"}, requirements={"segment"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("CONDITION_DELETE") + * + * @SWG\Tag(name="Segment") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * description="Language code", + * default="EN" + * ) + * @SWG\Parameter( + * name="segment", + * in="path", + * required=true, + * type="string", + * description="Segment ID", + * ) + * @SWG\Response( + * response=204, + * description="Success" + * ) + * @SWG\Response( + * response=404, + * description="Not found" + * ) + * + * @ParamConverter(class="Ergonode\Segment\Domain\Entity\Segment") + * + * @param Segment $segment + * + * @return Response + */ + public function deleteSegment(Segment $segment): Response + { + $command = new DeleteSegmentCommand($segment->getId()); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } +} diff --git a/module/segment/src/Application/DependencyInjection/CompilerPass/SegmentGeneratorCompilerPass.php b/module/segment/src/Application/DependencyInjection/CompilerPass/SegmentGeneratorCompilerPass.php new file mode 100644 index 000000000..be3c830ce --- /dev/null +++ b/module/segment/src/Application/DependencyInjection/CompilerPass/SegmentGeneratorCompilerPass.php @@ -0,0 +1,49 @@ +has(SegmentGeneratorProvider::class)) { + $this->processTransformers($container); + } + } + + /** + * @param ContainerBuilder $container + */ + private function processTransformers(ContainerBuilder $container): void + { + $arguments = []; + $definition = $container->findDefinition(SegmentGeneratorProvider::class); + $strategies = $container->findTaggedServiceIds(self::TAG); + + foreach ($strategies as $id => $strategy) { + $arguments[] = new Reference($id); + } + + $definition->setArguments($arguments); + } +} diff --git a/module/segment/src/Application/DependencyInjection/ErgonodeSegmentExtension.php b/module/segment/src/Application/DependencyInjection/ErgonodeSegmentExtension.php new file mode 100644 index 000000000..2b3011db3 --- /dev/null +++ b/module/segment/src/Application/DependencyInjection/ErgonodeSegmentExtension.php @@ -0,0 +1,44 @@ +registerForAutoconfiguration(SegmentGeneratorInterface::class) + ->addTag(SegmentGeneratorCompilerPass::TAG); + + $loader->load('services.yml'); + } +} diff --git a/module/segment/src/Application/Form/CreateSegmentForm.php b/module/segment/src/Application/Form/CreateSegmentForm.php new file mode 100644 index 000000000..4351f1d97 --- /dev/null +++ b/module/segment/src/Application/Form/CreateSegmentForm.php @@ -0,0 +1,70 @@ +add( + 'condition_set_id', + TextType::class, + [ + 'property_path' => 'conditionSetId', + ] + ) + ->add( + 'code', + TextType::class + ) + ->add( + 'name', + TranslationType::class + ) + ->add( + 'description', + TranslationType::class + ); + } + + /** + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => CreateSegmentFormModel::class, + 'translation_domain' => 'segment', + 'empty_data' => new CreateSegmentFormModel(), + ]); + } + + /** + * @return null|string + */ + public function getBlockPrefix(): ?string + { + return null; + } +} diff --git a/module/segment/src/Application/Form/Model/CreateSegmentFormModel.php b/module/segment/src/Application/Form/Model/CreateSegmentFormModel.php new file mode 100644 index 000000000..98813c4ce --- /dev/null +++ b/module/segment/src/Application/Form/Model/CreateSegmentFormModel.php @@ -0,0 +1,53 @@ +name = []; + $this->description = []; + } +} diff --git a/module/segment/src/Application/Form/Model/UpdateSegmentFormModel.php b/module/segment/src/Application/Form/Model/UpdateSegmentFormModel.php new file mode 100644 index 000000000..b4967fa1c --- /dev/null +++ b/module/segment/src/Application/Form/Model/UpdateSegmentFormModel.php @@ -0,0 +1,43 @@ +name = []; + $this->description = []; + } +} diff --git a/module/segment/src/Application/Form/UpdateSegmentForm.php b/module/segment/src/Application/Form/UpdateSegmentForm.php new file mode 100644 index 000000000..ef52c35ed --- /dev/null +++ b/module/segment/src/Application/Form/UpdateSegmentForm.php @@ -0,0 +1,65 @@ +add( + 'condition_set_id', + TextType::class, + [ + 'property_path' => 'conditionSetId', + ] + ) + ->add( + 'name', + TranslationType::class + ) + ->add( + 'description', + TranslationType::class + ); + } + + /** + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => UpdateSegmentFormModel::class, + 'translation_domain' => 'segment', + ]); + } + + /** + * @return null|string + */ + public function getBlockPrefix(): ?string + { + return null; + } +} diff --git a/module/segment/src/Application/Request/ParamConverter/SegmentParamConverter.php b/module/segment/src/Application/Request/ParamConverter/SegmentParamConverter.php new file mode 100644 index 000000000..62ddf14e1 --- /dev/null +++ b/module/segment/src/Application/Request/ParamConverter/SegmentParamConverter.php @@ -0,0 +1,69 @@ +repository = $repository; + } + + /** + * {@inheritDoc} + */ + public function apply(Request $request, ParamConverter $configuration): void + { + $parameter = $request->get('segment'); + + if (null === $parameter) { + throw new BadRequestHttpException('Route parameter "segment" is missing'); + } + + if (!SegmentId::isValid($parameter)) { + throw new BadRequestHttpException('Invalid segment ID format'); + } + + $entity = $this->repository->load(new SegmentId($parameter)); + + if (null === $entity) { + throw new NotFoundHttpException(sprintf('Segment by ID "%s" not found', $parameter)); + } + + $request->attributes->set($configuration->getName(), $entity); + } + + /** + * {@inheritDoc} + */ + public function supports(ParamConverter $configuration): bool + { + return Segment::class === $configuration->getClass(); + } +} diff --git a/module/segment/src/Domain/Command/CreateSegmentCommand.php b/module/segment/src/Domain/Command/CreateSegmentCommand.php new file mode 100644 index 000000000..e3b814070 --- /dev/null +++ b/module/segment/src/Domain/Command/CreateSegmentCommand.php @@ -0,0 +1,111 @@ +id = SegmentId::fromCode($code); + $this->conditionSetId = $conditionSetId; + $this->code = $code; + $this->name = $name; + $this->description = $description; + } + + /** + * @return SegmentId + */ + public function getId(): SegmentId + { + return $this->id; + } + + /** + * @return ConditionSetId + */ + public function getConditionSetId(): ConditionSetId + { + return $this->conditionSetId; + } + + /** + * @return SegmentCode + */ + public function getCode(): SegmentCode + { + return $this->code; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } +} diff --git a/module/segment/src/Domain/Command/DeleteSegmentCommand.php b/module/segment/src/Domain/Command/DeleteSegmentCommand.php new file mode 100644 index 000000000..d8c39f50d --- /dev/null +++ b/module/segment/src/Domain/Command/DeleteSegmentCommand.php @@ -0,0 +1,40 @@ +id = $id; + } + + /** + * @return SegmentId + */ + public function getId(): SegmentId + { + return $this->id; + } +} diff --git a/module/segment/src/Domain/Command/GenerateSegmentCommand.php b/module/segment/src/Domain/Command/GenerateSegmentCommand.php new file mode 100644 index 000000000..9c95eea6a --- /dev/null +++ b/module/segment/src/Domain/Command/GenerateSegmentCommand.php @@ -0,0 +1,76 @@ +id = SegmentId::generate(); + $this->code = $code; + $this->type = $type; + } + + /** + * @return SegmentId + */ + public function getId(): SegmentId + { + return $this->id; + } + + /** + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } +} diff --git a/module/segment/src/Domain/Command/UpdateSegmentCommand.php b/module/segment/src/Domain/Command/UpdateSegmentCommand.php new file mode 100644 index 000000000..fe7a7b185 --- /dev/null +++ b/module/segment/src/Domain/Command/UpdateSegmentCommand.php @@ -0,0 +1,96 @@ +id = $id; + $this->name = $name; + $this->description = $description; + $this->conditionSetId = $conditionSetId; + } + + /** + * @return SegmentId + */ + public function getId(): SegmentId + { + return $this->id; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } + + /** + * @return ConditionSetId + */ + public function getConditionSetId(): ConditionSetId + { + return $this->conditionSetId; + } +} diff --git a/module/segment/src/Domain/Entity/Segment.php b/module/segment/src/Domain/Entity/Segment.php new file mode 100644 index 000000000..089c9642c --- /dev/null +++ b/module/segment/src/Domain/Entity/Segment.php @@ -0,0 +1,238 @@ +status = new SegmentStatus(SegmentStatus::NEW); + $this->apply(new SegmentCreatedEvent($id, $code, $conditionSetId, $name, $description, $this->status)); + } + + /** + * @return SegmentId|AbstractId + */ + public function getId(): AbstractId + { + return $this->id; + } + + /** + * @return SegmentCode + */ + public function getCode(): SegmentCode + { + return $this->code; + } + + /** + * @return ConditionSetId + */ + public function getConditionSetId(): ConditionSetId + { + return $this->conditionSetId; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } + + /** + * @return SegmentStatus + */ + public function getStatus(): SegmentStatus + { + return $this->status; + } + + /** + * @param SegmentStatus $status + * + * @throws \Exception + */ + public function changeStatus(SegmentStatus $status): void + { + if (!$status->isEqual($this->status)) { + $this->apply(new SegmentStatusChangedEvent($this->status, $status)); + } + } + + /** + * @param TranslatableString $name + * + * @throws \Exception + */ + public function changeName(TranslatableString $name): void + { + if (!$name->isEqual($this->name)) { + $this->apply(new SegmentNameChangedEvent($this->name, $name)); + } + } + + /** + * @param TranslatableString $description + * + * @throws \Exception + */ + public function changeDescription(TranslatableString $description): void + { + if (!$description->isEqual($this->description)) { + $this->apply(new SegmentDescriptionChangedEvent($this->description, $description)); + } + } + + /** + * @param ConditionSetId $conditionSetId + * + * @throws \Exception + */ + public function changeConditionSet(ConditionSetId $conditionSetId): void + { + if (!$conditionSetId->isEqual($this->conditionSetId)) { + $this->apply(new SegmentConditionSetChangedEvent($this->conditionSetId, $conditionSetId)); + } + } + + /** + * @param SegmentCreatedEvent $event + */ + protected function applySegmentCreatedEvent(SegmentCreatedEvent $event): void + { + $this->id = $event->getId(); + $this->code = $event->getCode(); + $this->conditionSetId = $event->getConditionSetId(); + $this->name = $event->getName(); + $this->description = $event->getDescription(); + $this->status = $event->getStatus(); + } + + /** + * @param SegmentStatusChangedEvent $event + */ + protected function applySegmentStatusChangedEvent(SegmentStatusChangedEvent $event): void + { + $this->status = $event->getTo(); + } + + /** + * @param SegmentNameChangedEvent $event + */ + protected function applySegmentNameChangedEvent(SegmentNameChangedEvent $event): void + { + $this->name = $event->getTo(); + } + + /** + * @param SegmentDescriptionChangedEvent $event + */ + protected function applySegmentDescriptionChangedEvent(SegmentDescriptionChangedEvent $event): void + { + $this->description = $event->getTo(); + } + + /** + * @param SegmentConditionSetChangedEvent $event + */ + protected function applySegmentConditionSetChangedEvent(SegmentConditionSetChangedEvent $event): void + { + $this->conditionSetId = $event->getTo(); + } +} diff --git a/module/segment/src/Domain/Entity/SegmentId.php b/module/segment/src/Domain/Entity/SegmentId.php new file mode 100644 index 000000000..6275b9147 --- /dev/null +++ b/module/segment/src/Domain/Entity/SegmentId.php @@ -0,0 +1,32 @@ +getValue())->toString()); + } +} diff --git a/module/segment/src/Domain/Event/SegmentConditionSetChangedEvent.php b/module/segment/src/Domain/Event/SegmentConditionSetChangedEvent.php new file mode 100644 index 000000000..1f9ce772d --- /dev/null +++ b/module/segment/src/Domain/Event/SegmentConditionSetChangedEvent.php @@ -0,0 +1,59 @@ +from = $from; + $this->to = $to; + } + + /** + * @return ConditionSetId + */ + public function getFrom(): ConditionSetId + { + return $this->from; + } + + /** + * @return ConditionSetId + */ + public function getTo(): ConditionSetId + { + return $this->to; + } +} diff --git a/module/segment/src/Domain/Event/SegmentCreatedEvent.php b/module/segment/src/Domain/Event/SegmentCreatedEvent.php new file mode 100644 index 000000000..f91c46e03 --- /dev/null +++ b/module/segment/src/Domain/Event/SegmentCreatedEvent.php @@ -0,0 +1,137 @@ +id = $id; + $this->code = $code; + $this->conditionSetId = $conditionSetId; + $this->name = $name; + $this->description = $description; + $this->status = $status; + } + + /** + * @return SegmentId + */ + public function getId(): SegmentId + { + return $this->id; + } + + /** + * @return SegmentCode + */ + public function getCode(): SegmentCode + { + return $this->code; + } + + /** + * @return ConditionSetId + */ + public function getConditionSetId(): ConditionSetId + { + return $this->conditionSetId; + } + + /** + * @return TranslatableString + */ + public function getName(): TranslatableString + { + return $this->name; + } + + /** + * @return TranslatableString + */ + public function getDescription(): TranslatableString + { + return $this->description; + } + + /** + * @return SegmentStatus + */ + public function getStatus(): SegmentStatus + { + return $this->status; + } +} diff --git a/module/segment/src/Domain/Event/SegmentDeletedEvent.php b/module/segment/src/Domain/Event/SegmentDeletedEvent.php new file mode 100644 index 000000000..be3a8625b --- /dev/null +++ b/module/segment/src/Domain/Event/SegmentDeletedEvent.php @@ -0,0 +1,18 @@ +from = $from; + $this->to = $to; + } + + /** + * @return SegmentStatus + */ + public function getFrom(): SegmentStatus + { + return $this->from; + } + + /** + * @return SegmentStatus + */ + public function getTo(): SegmentStatus + { + return $this->to; + } +} diff --git a/module/segment/src/Domain/Query/SegmentQueryInterface.php b/module/segment/src/Domain/Query/SegmentQueryInterface.php new file mode 100644 index 000000000..3704a0dff --- /dev/null +++ b/module/segment/src/Domain/Query/SegmentQueryInterface.php @@ -0,0 +1,41 @@ +value = $value; + } + + /** + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * @param string $value + * + * @return bool + */ + public static function isValid(string $value): bool + { + return strlen($value) <= 100; + } +} diff --git a/module/segment/src/Domain/ValueObject/SegmentStatus.php b/module/segment/src/Domain/ValueObject/SegmentStatus.php new file mode 100644 index 000000000..6fe887a81 --- /dev/null +++ b/module/segment/src/Domain/ValueObject/SegmentStatus.php @@ -0,0 +1,112 @@ +value = $value; + } + + /** + * @param string $value + * + * @return bool + */ + public static function isValid(string $value): bool + { + return in_array(strtoupper($value), self::AVAILABLE, true); + } + + /** + * @return bool + */ + public function isNew(): bool + { + return self::NEW === $this->value; + } + + /** + * @return bool + */ + public function isProcessed(): bool + { + return self::PROCESSED === $this->value; + } + + /** + * @return bool + */ + public function isCalculated(): bool + { + return self::CALCULATED === $this->value; + } + + /** + * @return bool + */ + public function isOutdated(): bool + { + return self::OUTDATED === $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } + + /** + * @param SegmentStatus $status + * + * @return bool + */ + public function isEqual(self $status): bool + { + return (string) $status === $this->value; + } +} diff --git a/module/segment/src/ErgonodeSegmentBundle.php b/module/segment/src/ErgonodeSegmentBundle.php new file mode 100644 index 000000000..060a43676 --- /dev/null +++ b/module/segment/src/ErgonodeSegmentBundle.php @@ -0,0 +1,30 @@ +addCompilerPass(new SegmentGeneratorCompilerPass()); + } +} diff --git a/module/segment/src/Infrastructure/Exception/SegmentException.php b/module/segment/src/Infrastructure/Exception/SegmentException.php new file mode 100644 index 000000000..4e2866179 --- /dev/null +++ b/module/segment/src/Infrastructure/Exception/SegmentException.php @@ -0,0 +1,16 @@ +translator = $translator; + } + + /** + * @param GridConfigurationInterface $configuration + * @param Language $language + */ + public function init(GridConfigurationInterface $configuration, Language $language): void + { + $filters = $configuration->getFilters(); + + $statuses = array_combine(SegmentStatus::AVAILABLE, SegmentStatus::AVAILABLE); + + $id = new TextColumn('id', $this->trans('Id'), new TextFilter()); + $id->setVisible(false); + $this->addColumn('id', $id); + $this->addColumn('code', new TextColumn('name', $this->trans('Code'), new TextFilter($filters->getString('code')))); + $this->addColumn('status', new TextColumn('status', $this->trans('Status'), new SelectFilter($statuses, $filters->getString('status')))); + $this->addColumn('name', new TextColumn('name', $this->trans('Name'), new TextFilter($filters->getString('name')))); + $this->addColumn('description', new TextColumn('description', $this->trans('Description'), new TextFilter($filters->getString('description')))); + $this->addColumn('edit', new ActionColumn('edit')); + $this->setConfiguration(self::PARAMETER_ALLOW_COLUMN_RESIZE, true); + $this->orderBy('id', 'DESC'); + } + + /** + * @param string $id + * @param array $parameters + * + * @return string + */ + private function trans(string $id, array $parameters = []): string + { + return $this->translator->trans($id, $parameters, 'grid'); + } +} diff --git a/module/segment/src/Infrastructure/Handler/CreateSegmentCommandHandler.php b/module/segment/src/Infrastructure/Handler/CreateSegmentCommandHandler.php new file mode 100644 index 000000000..011eeab58 --- /dev/null +++ b/module/segment/src/Infrastructure/Handler/CreateSegmentCommandHandler.php @@ -0,0 +1,50 @@ +repository = $repository; + } + + /** + * @param CreateSegmentCommand $command + * + * @throws \Exception + */ + public function __invoke(CreateSegmentCommand $command) + { + $segment = new Segment( + $command->getId(), + $command->getCode(), + $command->getConditionSetId(), + $command->getName(), + $command->getDescription() + ); + + $this->repository->save($segment); + } +} diff --git a/module/segment/src/Infrastructure/Handler/DeleteSegmentCommandHandler.php b/module/segment/src/Infrastructure/Handler/DeleteSegmentCommandHandler.php new file mode 100644 index 000000000..1738b25ca --- /dev/null +++ b/module/segment/src/Infrastructure/Handler/DeleteSegmentCommandHandler.php @@ -0,0 +1,45 @@ +repository = $repository; + } + + /** + * @param DeleteSegmentCommand $command + * + * @throws \Exception + */ + public function __invoke(DeleteSegmentCommand $command) + { + $conditionSet = $this->repository->load($command->getId()); + Assert::notNull($conditionSet); + + $this->repository->delete($conditionSet); + } +} diff --git a/module/segment/src/Infrastructure/Handler/GenerateSegmentCommandHandler.php b/module/segment/src/Infrastructure/Handler/GenerateSegmentCommandHandler.php new file mode 100644 index 000000000..9c4be6f77 --- /dev/null +++ b/module/segment/src/Infrastructure/Handler/GenerateSegmentCommandHandler.php @@ -0,0 +1,54 @@ +repository = $repository; + $this->generator = $generator; + } + + /** + * @param GenerateSegmentCommand $command + * + * @throws SegmentGeneratorProviderException + */ + public function __invoke(GenerateSegmentCommand $command) + { + $generator = $this->generator->provide($command->getType()); + + $segment = $generator->generate($command->getId(), $command->getCode()); + + $this->repository->save($segment); + } +} diff --git a/module/segment/src/Infrastructure/Handler/UpdateSegmentCommandHandler.php b/module/segment/src/Infrastructure/Handler/UpdateSegmentCommandHandler.php new file mode 100644 index 000000000..b60bf3974 --- /dev/null +++ b/module/segment/src/Infrastructure/Handler/UpdateSegmentCommandHandler.php @@ -0,0 +1,50 @@ +repository = $repository; + } + + /** + * @param UpdateSegmentCommand $command + * + * @throws \Exception + */ + public function __invoke(UpdateSegmentCommand $command) + { + $segment = $this->repository->load($command->getId()); + + Assert::notNull($segment); + + $segment->changeName($command->getName()); + $segment->changeDescription($command->getDescription()); + $segment->changeConditionSet($command->getConditionSetId()); + + $this->repository->save($segment); + } +} diff --git a/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandler.php b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandler.php new file mode 100644 index 000000000..666519b78 --- /dev/null +++ b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandler.php @@ -0,0 +1,75 @@ + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'type' => SegmentCode::class, + 'format' => $format, + 'method' => 'serialize', + ]; + + $methods[] = [ + 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, + 'type' => SegmentCode::class, + 'format' => $format, + 'method' => 'deserialize', + ]; + } + + return $methods; + } + + /** + * @param SerializationVisitorInterface $visitor + * @param SegmentCode $status + * @param array $type + * @param Context $context + * + * @return string + */ + public function serialize(SerializationVisitorInterface $visitor, SegmentCode $status, array $type, Context $context): string + { + return (string) $status; + } + + /** + * @param DeserializationVisitorInterface $visitor + * @param mixed $data + * @param array $type + * @param Context $context + * + * @return SegmentCode + */ + public function deserialize(DeserializationVisitorInterface $visitor, $data, array $type, Context $context): SegmentCode + { + return new SegmentCode($data); + } +} diff --git a/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentIdHandler.php b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentIdHandler.php new file mode 100644 index 000000000..683161a9e --- /dev/null +++ b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentIdHandler.php @@ -0,0 +1,75 @@ + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'type' => SegmentId::class, + 'format' => $format, + 'method' => 'serialize', + ]; + + $methods[] = [ + 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, + 'type' => SegmentId::class, + 'format' => $format, + 'method' => 'deserialize', + ]; + } + + return $methods; + } + + /** + * @param SerializationVisitorInterface $visitor + * @param SegmentId $id + * @param array $type + * @param Context $context + * + * @return string + */ + public function serialize(SerializationVisitorInterface $visitor, SegmentId $id, array $type, Context $context): string + { + return $id->getValue(); + } + + /** + * @param DeserializationVisitorInterface $visitor + * @param mixed $data + * @param array $type + * @param Context $context + * + * @return SegmentId + */ + public function deserialize(DeserializationVisitorInterface $visitor, $data, array $type, Context $context): SegmentId + { + return new SegmentId($data); + } +} diff --git a/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandler.php b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandler.php new file mode 100644 index 000000000..06991371f --- /dev/null +++ b/module/segment/src/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandler.php @@ -0,0 +1,75 @@ + GraphNavigatorInterface::DIRECTION_SERIALIZATION, + 'type' => SegmentStatus::class, + 'format' => $format, + 'method' => 'serialize', + ]; + + $methods[] = [ + 'direction' => GraphNavigatorInterface::DIRECTION_DESERIALIZATION, + 'type' => SegmentStatus::class, + 'format' => $format, + 'method' => 'deserialize', + ]; + } + + return $methods; + } + + /** + * @param SerializationVisitorInterface $visitor + * @param SegmentStatus $status + * @param array $type + * @param Context $context + * + * @return string + */ + public function serialize(SerializationVisitorInterface $visitor, SegmentStatus $status, array $type, Context $context): string + { + return (string) $status; + } + + /** + * @param DeserializationVisitorInterface $visitor + * @param mixed $data + * @param array $type + * @param Context $context + * + * @return SegmentStatus + */ + public function deserialize(DeserializationVisitorInterface $visitor, $data, array $type, Context $context): SegmentStatus + { + return new SegmentStatus($data); + } +} diff --git a/module/segment/src/Infrastructure/Provider/SegmentGeneratorProvider.php b/module/segment/src/Infrastructure/Provider/SegmentGeneratorProvider.php new file mode 100644 index 000000000..efa71de1c --- /dev/null +++ b/module/segment/src/Infrastructure/Provider/SegmentGeneratorProvider.php @@ -0,0 +1,49 @@ +generators = $generators; + } + + /** + * @param string $type + * + * @return SegmentGeneratorInterface + * @throws SegmentGeneratorProviderException + */ + public function provide(string $type): SegmentGeneratorInterface + { + foreach ($this->generators as $generator) { + if (strtoupper($type) === $generator->getType()) { + return $generator; + } + } + + throw new SegmentGeneratorProviderException(sprintf('Can\'t find segment %s generator ', $type)); + } +} diff --git a/module/segment/src/Infrastructure/Provider/SegmentProvider.php b/module/segment/src/Infrastructure/Provider/SegmentProvider.php new file mode 100644 index 000000000..3c9b4995f --- /dev/null +++ b/module/segment/src/Infrastructure/Provider/SegmentProvider.php @@ -0,0 +1,61 @@ +repository = $repository; + $this->provider = $provider; + } + + /** + * @param SegmentCode $code + * + * @return Segment + * + * @throws SegmentGeneratorProviderException + */ + public function provide(SegmentCode $code): Segment + { + $segmentId = SegmentId::fromCode($code); + $segment = $this->repository->load($segmentId); + if (null === $segment) { + $generator = $this->provider->provide($code->getValue()); + $segment = $generator->generate($segmentId, $code->getValue()); + $this->repository->save($segment); + } + + return $segment; + } +} diff --git a/module/segment/src/Infrastructure/Validator/UniqueSegmentCode.php b/module/segment/src/Infrastructure/Validator/UniqueSegmentCode.php new file mode 100644 index 000000000..18c8ec34b --- /dev/null +++ b/module/segment/src/Infrastructure/Validator/UniqueSegmentCode.php @@ -0,0 +1,28 @@ +query = $query; + } + + /** + * @param mixed $value + * @param UniqueSegmentCode|Constraint $constraint + */ + public function validate($value, Constraint $constraint): void + { + if (!$constraint instanceof UniqueSegmentCode) { + throw new UnexpectedTypeException($constraint, UniqueSegmentCode::class); + } + + if (null === $value || '' === $value) { + return; + } + + if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { + throw new UnexpectedTypeException($value, 'string'); + } + + $value = (string) $value; + + if (!SegmentCode::isValid($value)) { + $this->context->buildViolation($constraint->validMessage) + ->setParameter('{{ value }}', $value) + ->addViolation(); + + return; + } + + if ($this->query->isExistsByCode(new SegmentCode($value))) { + $this->context->buildViolation($constraint->uniqueMessage) + ->addViolation(); + } + } +} diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusRemovedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentConditionSetChangedEventProjector.php similarity index 55% rename from module/workflow/src/Persistence/Dbal/Projector/StatusRemovedEventProjector.php rename to module/segment/src/Persistence/Dbal/Projector/SegmentConditionSetChangedEventProjector.php index 36ef8def5..6b1ddb5f7 100644 --- a/module/workflow/src/Persistence/Dbal/Projector/StatusRemovedEventProjector.php +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentConditionSetChangedEventProjector.php @@ -7,21 +7,21 @@ declare(strict_types = 1); -namespace Ergonode\Workflow\Persistence\Dbal\Projector; +namespace Ergonode\Segment\Persistence\Dbal\Projector; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\DBALException; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; -use Ergonode\Workflow\Domain\Event\Status\StatusRemovedEvent; +use Ergonode\Segment\Domain\Event\SegmentConditionSetChangedEvent; /** */ -class StatusRemovedEventProjector implements DomainEventProjectorInterface +class SegmentConditionSetChangedEventProjector implements DomainEventProjectorInterface { - private const TABLE = 'status'; + private const TABLE = 'segment'; /** * @var Connection @@ -43,34 +43,30 @@ public function __construct(Connection $connection) */ public function support(DomainEventInterface $event): bool { - return $event instanceof StatusRemovedEvent; + return $event instanceof SegmentConditionSetChangedEvent; } /** * @param AbstractId $aggregateId * @param DomainEventInterface $event * - * @throws ProjectorException * @throws UnsupportedEventException + * @throws DBALException */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { - if (!$event instanceof StatusRemovedEvent) { - throw new UnsupportedEventException($event, StatusRemovedEvent::class); + if (!$this->support($event)) { + throw new UnsupportedEventException($event, SegmentConditionSetChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->delete( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'condition_set_id' => $event->getTo()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/segment/src/Persistence/Dbal/Projector/SegmentCreatedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentCreatedEventProjector.php new file mode 100644 index 000000000..9d4e44ec1 --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentCreatedEventProjector.php @@ -0,0 +1,83 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof SegmentCreatedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof SegmentCreatedEvent) { + throw new UnsupportedEventException($event, SegmentCreatedEvent::class); + } + + $this->connection->insert( + self::TABLE, + [ + 'id' => $event->getId()->getValue(), + 'code' => $event->getCode(), + 'name' => $this->serializer->serialize($event->getName(), 'json'), + 'description' => $this->serializer->serialize($event->getDescription(), 'json'), + 'status' => SegmentStatus::NEW, + 'condition_set_id' => $event->getConditionSetId()->getValue(), + ] + ); + } +} diff --git a/module/segment/src/Persistence/Dbal/Projector/SegmentDeletedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentDeletedEventProjector.php new file mode 100644 index 000000000..f3fff888c --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentDeletedEventProjector.php @@ -0,0 +1,62 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof SegmentDeletedEvent; + } + + /** + * {@inheritDoc} + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$this->support($event)) { + throw new UnsupportedEventException($event, SegmentDeletedEvent::class); + } + + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/segment/src/Persistence/Dbal/Projector/SegmentDescriptionChangedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentDescriptionChangedEventProjector.php new file mode 100644 index 000000000..6900d412d --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentDescriptionChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof SegmentDescriptionChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof SegmentDescriptionChangedEvent) { + throw new UnsupportedEventException($event, SegmentDescriptionChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'description' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/segment/src/Persistence/Dbal/Projector/SegmentNameChangedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentNameChangedEventProjector.php new file mode 100644 index 000000000..52736982d --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentNameChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof SegmentNameChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof SegmentNameChangedEvent) { + throw new UnsupportedEventException($event, SegmentNameChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'name' => $this->serializer->serialize($event->getTo(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/segment/src/Persistence/Dbal/Projector/SegmentStatusChangedEventProjector.php b/module/segment/src/Persistence/Dbal/Projector/SegmentStatusChangedEventProjector.php new file mode 100644 index 000000000..ef87d4fa2 --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Projector/SegmentStatusChangedEventProjector.php @@ -0,0 +1,80 @@ +connection = $connection; + $this->serializer = $serializer; + } + + /** + * @param DomainEventInterface $event + * + * @return bool + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof SegmentStatusChangedEvent; + } + + /** + * @param AbstractId $aggregateId + * @param DomainEventInterface $event + * + * @throws UnsupportedEventException + * @throws DBALException + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof SegmentStatusChangedEvent) { + throw new UnsupportedEventException($event, SegmentStatusChangedEvent::class); + } + + $this->connection->update( + self::TABLE, + [ + 'status' => (string) $event->getTo(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/segment/src/Persistence/Dbal/Query/DbalSegmentQuery.php b/module/segment/src/Persistence/Dbal/Query/DbalSegmentQuery.php new file mode 100644 index 000000000..74645e7e6 --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Query/DbalSegmentQuery.php @@ -0,0 +1,106 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function getDataSet(Language $language): DbalDataSet + { + $query = $this->getQuery(); + $query->addSelect(sprintf('(name->>\'%s\') AS name', $language->getCode())); + $query->addSelect(sprintf('(description->>\'%s\') AS description', $language->getCode())); + + $result = $this->connection->createQueryBuilder(); + $result->select('*'); + $result->from(sprintf('(%s)', $query->getSQL()), 't'); + + return new DbalDataSet($result); + } + + /** + * {@inheritDoc} + */ + public function findIdByConditionSetId(ConditionSetId $conditionSetId): array + { + $queryBuilder = $this->connection->createQueryBuilder(); + $queryBuilder + ->select('id') + ->from(self::TABLE) + ->where('condition_set_id = :id') + ->setParameter('id', $conditionSetId->getValue()); + + $result = $queryBuilder->execute()->fetchAll(\PDO::FETCH_COLUMN); + if (false === $result) { + $result = []; + } + + return $result; + } + + /** + * {@inheritDoc} + */ + public function isExistsByCode(SegmentCode $segmentCode): bool + { + $queryBuilder = $this->connection->createQueryBuilder() + ->select('id') + ->from(self::TABLE) + ->where('code = :code') + ->setParameter('code', $segmentCode->getValue()) + ->setMaxResults(1); + + $result = $queryBuilder->execute()->fetchColumn(); + + return !empty($result); + } + + /** + * @return QueryBuilder + */ + private function getQuery(): QueryBuilder + { + return $this->connection->createQueryBuilder() + ->select(self::FIELDS) + ->from(self::TABLE, 't'); + } +} diff --git a/module/segment/src/Persistence/Dbal/Repository/DbalSegmentRepository.php b/module/segment/src/Persistence/Dbal/Repository/DbalSegmentRepository.php new file mode 100644 index 000000000..9fd4437cb --- /dev/null +++ b/module/segment/src/Persistence/Dbal/Repository/DbalSegmentRepository.php @@ -0,0 +1,104 @@ +eventStore = $eventStore; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * {@inheritDoc} + * + * @throws \ReflectionException + */ + public function load(SegmentId $id): ?AbstractAggregateRoot + { + $eventStream = $this->eventStore->load($id); + + if (\count($eventStream) > 0) { + $class = new \ReflectionClass(Segment::class); + /** @var AbstractAggregateRoot $aggregate */ + $aggregate = $class->newInstanceWithoutConstructor(); + if (!$aggregate instanceof AbstractAggregateRoot) { + throw new \LogicException(sprintf('Impossible to initialize "%s"', $class)); + } + + $aggregate->initialize($eventStream); + + return $aggregate; + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function save(AbstractAggregateRoot $aggregateRoot): void + { + $events = $aggregateRoot->popEvents(); + + $this->eventStore->append($aggregateRoot->getId(), $events); + foreach ($events as $envelope) { + $this->eventDispatcher->dispatch($envelope); + } + } + + /** + * {@inheritDoc} + */ + public function exists(SegmentId $id): bool + { + $eventStream = $this->eventStore->load($id); + + return \count($eventStream) > 0; + } + + /** + * {@inheritDoc} + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new SegmentDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } +} diff --git a/module/segment/src/Resources/config/routes.yml b/module/segment/src/Resources/config/routes.yml new file mode 100644 index 000000000..6087d5e64 --- /dev/null +++ b/module/segment/src/Resources/config/routes.yml @@ -0,0 +1,4 @@ +ergonode_segment_api: + resource: '../../Application/Controller/Api/Segment*' + type: annotation + prefix: /api/v1/{language} \ No newline at end of file diff --git a/module/segment/src/Resources/config/services.yml b/module/segment/src/Resources/config/services.yml new file mode 100644 index 000000000..331c79dc4 --- /dev/null +++ b/module/segment/src/Resources/config/services.yml @@ -0,0 +1,31 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Ergonode\Segment\Application\: + resource: '../../Application/*' + + Ergonode\Segment\Persistence\: + resource: '../../Persistence/*' + + Ergonode\Segment\Domain\: + resource: '../../Domain/*' + exclude: '../../Domain/{Entity,ValueObject,Type,Condition}' + + Ergonode\Segment\Infrastructure\: + resource: '../../Infrastructure/*' + exclude: '../../Infrastructure/{Specification}' + + Ergonode\Segment\Infrastructure\Handler\: + resource: '../../Infrastructure/Handler/*' + exclude: '../../Infrastructure/Handler/{Strategy}' + tags: ['messenger.message_handler'] + + Ergonode\Segment\Infrastructure\JMS\Serializer\Handler\: + resource: '../../Infrastructure/JMS/Serializer/Handler/*' + tags: ['jms_serializer.subscribing_handler'] + + Ergonode\Segment\Domain\Repository\SegmentRepositoryInterface: '@Ergonode\Segment\Persistence\Dbal\Repository\DbalSegmentRepository' + Ergonode\Segment\Domain\Query\SegmentQueryInterface: '@Ergonode\Segment\Persistence\Dbal\Query\DbalSegmentQuery' diff --git a/module/segment/src/Resources/translations/grid.en.yaml b/module/segment/src/Resources/translations/grid.en.yaml new file mode 100644 index 000000000..6d06acbd8 --- /dev/null +++ b/module/segment/src/Resources/translations/grid.en.yaml @@ -0,0 +1,5 @@ +Code: Code +Name: Name +Description: Description +Edit: Edit +Status: Status diff --git a/module/segment/src/Resources/translations/grid.pl.yaml b/module/segment/src/Resources/translations/grid.pl.yaml new file mode 100644 index 000000000..d52851ca9 --- /dev/null +++ b/module/segment/src/Resources/translations/grid.pl.yaml @@ -0,0 +1,5 @@ +Code: Kod +Name: Nazwa +Description: Opis +Edit: Edycja +Status: Status diff --git a/module/segment/src/Resources/translations/log.en.yaml b/module/segment/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..5c198c19a --- /dev/null +++ b/module/segment/src/Resources/translations/log.en.yaml @@ -0,0 +1,7 @@ +'Segment created': 'Segment "%code%" created' +'Segment description changed': 'Segment description changed' +'Segment name changed': 'Segment name changed' +'Segment specification added': 'Segment specification added' +'Segment status changed': 'Segment status changed' +'Segment deleted': 'Segment "%id%" deleted' +'Segment condition set changed': 'Segment condition set changed' diff --git a/module/segment/src/Resources/translations/log.pl.yaml b/module/segment/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..d29a99762 --- /dev/null +++ b/module/segment/src/Resources/translations/log.pl.yaml @@ -0,0 +1,7 @@ +'Segment created': 'Segment "%code%" utworzono' +'Segment description changed': 'Zmieniono opis segmentu' +'Segment name changed': 'Zmieniono nazwę segmentu' +'Segment specification added': 'Dodano specyfikację do segmentu' +'Segment status changed': 'Zmieniono status segmentu' +'Segment deleted': 'Segment "%id%" został usunięty' +'Segment condition set changed': 'Zmieniono przypisanie do zbioru warunków w segmencie' diff --git a/module/segment/src/Resources/translations/privilege.en.yaml b/module/segment/src/Resources/translations/privilege.en.yaml new file mode 100644 index 000000000..8d2cd6f43 --- /dev/null +++ b/module/segment/src/Resources/translations/privilege.en.yaml @@ -0,0 +1 @@ +Segment: Segments diff --git a/module/segment/src/Resources/translations/privilege.pl.yaml b/module/segment/src/Resources/translations/privilege.pl.yaml new file mode 100644 index 000000000..fbf341d29 --- /dev/null +++ b/module/segment/src/Resources/translations/privilege.pl.yaml @@ -0,0 +1 @@ +Segment: Segmenty diff --git a/module/segment/src/Resources/translations/segment.en.yaml b/module/segment/src/Resources/translations/segment.en.yaml new file mode 100644 index 000000000..b1c8e8813 --- /dev/null +++ b/module/segment/src/Resources/translations/segment.en.yaml @@ -0,0 +1,2 @@ +ATTRIBUTE_EXISTS_CONDITION: Attribute exists +ATTRIBUTE_EXISTS_CONDITION_PHRASE: Attribute [attribute] exists diff --git a/module/segment/src/Resources/translations/segment.pl.yaml b/module/segment/src/Resources/translations/segment.pl.yaml new file mode 100644 index 000000000..345fca8d6 --- /dev/null +++ b/module/segment/src/Resources/translations/segment.pl.yaml @@ -0,0 +1,2 @@ +ATTRIBUTE_EXISTS_CONDITION: Posiada attrybut +ATTRIBUTE_EXISTS_CONDITION_PHRASE: Posiada attrybut [attribute] diff --git a/module/segment/tests/Domain/Command/CreateSegmentCommandTest.php b/module/segment/tests/Domain/Command/CreateSegmentCommandTest.php new file mode 100644 index 000000000..ee0530852 --- /dev/null +++ b/module/segment/tests/Domain/Command/CreateSegmentCommandTest.php @@ -0,0 +1,43 @@ +createMock(ConditionSetId::class); + /** @var SegmentCode|MockObject $code */ + $code = $this->createMock(SegmentCode::class); + /** @var TranslatableString $name */ + $name = $this->createMock(TranslatableString::class); + /** @var TranslatableString $description */ + $description = $this->createMock(TranslatableString::class); + + $command = new CreateSegmentCommand($code, $conditionSetId, $name, $description); + $this->assertEquals($code, $command->getCode()); + $this->assertEquals($name, $command->getName()); + $this->assertEquals($description, $command->getDescription()); + $this->assertEquals($conditionSetId, $command->getConditionSetId()); + } +} diff --git a/module/segment/tests/Domain/Command/UpdateSegmentCommandTest.php b/module/segment/tests/Domain/Command/UpdateSegmentCommandTest.php new file mode 100644 index 000000000..aa8fc3fe7 --- /dev/null +++ b/module/segment/tests/Domain/Command/UpdateSegmentCommandTest.php @@ -0,0 +1,41 @@ +createMock(SegmentId::class); + /** @var TranslatableString $name */ + $name = $this->createMock(TranslatableString::class); + /** @var TranslatableString $description */ + $description = $this->createMock(TranslatableString::class); + /** @var ConditionSetId $conditionSetId */ + $conditionSetId = $this->createMock(ConditionSetId::class); + + $command = new UpdateSegmentCommand($id, $name, $description, $conditionSetId); + $this->assertEquals($id, $command->getId()); + $this->assertEquals($name, $command->getName()); + $this->assertEquals($description, $command->getDescription()); + $this->assertEquals($conditionSetId, $command->getConditionSetId()); + } +} diff --git a/module/segment/tests/Domain/Entity/SegmentTest.php b/module/segment/tests/Domain/Entity/SegmentTest.php new file mode 100644 index 000000000..277469564 --- /dev/null +++ b/module/segment/tests/Domain/Entity/SegmentTest.php @@ -0,0 +1,108 @@ +id = $this->createMock(SegmentId::class); + $this->code = $this->createMock(SegmentCode::class); + $this->name = $this->createMock(TranslatableString::class); + $this->description = $this->createMock(TranslatableString::class); + $this->conditionSetId = $this->createMock(ConditionSetId::class); + } + + /** + * @throws \Exception + */ + public function testSegmentCreation():void + { + $segment = new Segment($this->id, $this->code, $this->conditionSetId, $this->name, $this->description); + + $this->assertEquals($this->id, $segment->getId()); + $this->assertEquals($this->code, $segment->getCode()); + $this->assertEquals($this->name, $segment->getName()); + $this->assertEquals($this->description, $segment->getDescription()); + $this->assertEquals($this->conditionSetId, $segment->getConditionSetId()); + $this->assertEquals(new SegmentStatus(SegmentStatus::NEW), $segment->getStatus()); + } + + /** + * @throws \Exception + */ + public function testSegmentManipulation():void + { + /** @var TranslatableString|MockObject $name */ + $name = $this->createMock(TranslatableString::class); + $name->method('isEqual')->willReturn(false); + + /** @var TranslatableString|MockObject $description */ + $description = $this->createMock(TranslatableString::class); + $description->method('isEqual')->willReturn(false); + + /** @var SegmentStatus|MockObject $status */ + $status = $this->createMock(SegmentStatus::class); + $status->method('isEqual')->willReturn(false); + + /** @var ConditionSetId|MockObject $conditionSet */ + $conditionSetId = $this->createMock(ConditionSetId::class); + $conditionSetId->method('isEqual')->willReturn(false); + + $segment = new Segment($this->id, $this->code, $this->conditionSetId, $this->name, $this->description); + $segment->changeStatus($status); + $segment->changeName($name); + $segment->changeDescription($description); + $segment->changeConditionSet($conditionSetId); + + $this->assertSame($name, $segment->getName()); + $this->assertSame($description, $segment->getDescription()); + $this->assertSame($status, $segment->getStatus()); + $this->assertSame($conditionSetId, $segment->getConditionSetId()); + } +} diff --git a/module/segment/tests/Domain/Event/SegmentCreatedEventTest.php b/module/segment/tests/Domain/Event/SegmentCreatedEventTest.php new file mode 100644 index 000000000..ab4b3ec26 --- /dev/null +++ b/module/segment/tests/Domain/Event/SegmentCreatedEventTest.php @@ -0,0 +1,51 @@ +createMock(SegmentId::class); + /** @var TranslatableString $name */ + $name = $this->createMock(TranslatableString::class); + /** @var TranslatableString $description */ + $description = $this->createMock(TranslatableString::class); + /** @var SegmentCode|MockObject $code */ + $code = $this->createMock(SegmentCode::class); + /** @var ConditionSetId|MockObject $conditionSetId */ + $conditionSetId = $this->createMock(ConditionSetId::class); + /** @var SegmentStatus|MockObject $status */ + $status = $this->createMock(SegmentStatus::class); + + $event = new SegmentCreatedEvent($id, $code, $conditionSetId, $name, $description, $status); + + $this->assertSame($id, $event->getId()); + $this->assertSame($code, $event->getCode()); + $this->assertSame($name, $event->getName()); + $this->assertSame($description, $event->getDescription()); + $this->assertSame($conditionSetId, $event->getConditionSetId()); + $this->assertSame($status, $event->getStatus()); + } +} diff --git a/module/segment/tests/Domain/Event/SegmentDescriptionChangedEventTest.php b/module/segment/tests/Domain/Event/SegmentDescriptionChangedEventTest.php new file mode 100644 index 000000000..291a38bce --- /dev/null +++ b/module/segment/tests/Domain/Event/SegmentDescriptionChangedEventTest.php @@ -0,0 +1,26 @@ +createMock(TranslatableString::class); + /** @var TranslatableString $to */ + $to = $this->createMock(TranslatableString::class); + + $event = new SegmentDescriptionChangedEvent($from, $to); + $this->assertSame($from, $event->getFrom()); + $this->assertSame($to, $event->getTo()); + } +} diff --git a/module/segment/tests/Domain/Event/SegmentNameChangedEventTest.php b/module/segment/tests/Domain/Event/SegmentNameChangedEventTest.php new file mode 100644 index 000000000..1c23bc813 --- /dev/null +++ b/module/segment/tests/Domain/Event/SegmentNameChangedEventTest.php @@ -0,0 +1,26 @@ +createMock(TranslatableString::class); + /** @var TranslatableString $to */ + $to = $this->createMock(TranslatableString::class); + + $event = new SegmentNameChangedEvent($from, $to); + $this->assertSame($from, $event->getFrom()); + $this->assertSame($to, $event->getTo()); + } +} diff --git a/module/segment/tests/Domain/Event/SegmentStatusChangedEventTest.php b/module/segment/tests/Domain/Event/SegmentStatusChangedEventTest.php new file mode 100644 index 000000000..7820f259f --- /dev/null +++ b/module/segment/tests/Domain/Event/SegmentStatusChangedEventTest.php @@ -0,0 +1,25 @@ +createMock(SegmentStatus::class); + /** @var SegmentStatus $to */ + $to = $this->createMock(SegmentStatus::class); + + $event = new SegmentStatusChangedEvent($from, $to); + $this->assertSame($from, $event->getFrom()); + $this->assertSame($to, $event->getTo()); + } +} diff --git a/module/segment/tests/Domain/ValueObject/SegmentStatusTest.php b/module/segment/tests/Domain/ValueObject/SegmentStatusTest.php new file mode 100644 index 000000000..81b80b761 --- /dev/null +++ b/module/segment/tests/Domain/ValueObject/SegmentStatusTest.php @@ -0,0 +1,90 @@ +assertEquals(strtoupper($status), (string) $status); + $this->assertTrue(SegmentStatus::isValid($status)); + $this->assertEquals($new, $status->isNew()); + $this->assertEquals($processed, $status->isProcessed()); + $this->assertEquals($calculated, $status->isCalculated()); + $this->assertEquals($outdated, $status->isOutdated()); + } + + /** + * @param string $status + * + * @dataProvider validDataProvider + */ + public function testPositiveValidation(string $status): void + { + $this->assertTrue(SegmentStatus::isValid($status)); + } + + /** + * @param string $status + * + * @dataProvider inValidDataProvider + */ + public function testNegativeValidation(string $status): void + { + $this->assertFalse(SegmentStatus::isValid($status)); + } + + /** + * @param string $status + * + * @dataProvider inValidDataProvider + * + * @expectedException \InvalidArgumentException + */ + public function testInvalidData(string $status): void + { + new SegmentStatus($status); + } + + /** + * @return array + */ + public function validDataProvider(): array + { + return [ + [SegmentStatus::NEW, true, false, false, false], + [SegmentStatus::PROCESSED, false, true, false, false], + [SegmentStatus::CALCULATED, false,false, true, false], + [SegmentStatus::OUTDATED, false, false, false , true], + ]; + } + + /** + * @return array + */ + public function inValidDataProvider(): array + { + return [ + [''], + ['not exists status'], + [123], + ['!@#)(*&(^^*^('], + ]; + } +} diff --git a/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandlerTest.php b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandlerTest.php new file mode 100644 index 000000000..6db400015 --- /dev/null +++ b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentCodeHandlerTest.php @@ -0,0 +1,77 @@ +handler = new SegmentCodeHandler(); + $this->serializerVisitor = $this->createMock(SerializationVisitorInterface::class); + $this->deserializerVisitor = $this->createMock(DeserializationVisitorInterface::class); + $this->context = $this->createMock(Context::class); + } + + /** + */ + public function testConfiguration(): void + { + $configurations = SegmentCodeHandler::getSubscribingMethods(); + foreach ($configurations as $configuration) { + $this->assertArrayHasKey('direction', $configuration); + $this->assertArrayHasKey('type', $configuration); + $this->assertArrayHasKey('format', $configuration); + $this->assertArrayHasKey('method', $configuration); + } + } + + /** + */ + public function testSerialize(): void + { + $testValue = 'code'; + $code = new SegmentCode($testValue); + $result = $this->handler->serialize($this->serializerVisitor, $code, [], $this->context); + + $this->assertEquals($testValue, $result); + } + + /** + */ + public function testDeserialize(): void + { + $testValue = 'code'; + $result = $this->handler->deserialize($this->deserializerVisitor, $testValue, [], $this->context); + + $this->assertEquals($testValue, (string) $result); + } +} diff --git a/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentIdHandlerTest.php b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentIdHandlerTest.php new file mode 100644 index 000000000..f835eb0b2 --- /dev/null +++ b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentIdHandlerTest.php @@ -0,0 +1,78 @@ +handler = new SegmentIdHandler(); + $this->serializerVisitor = $this->createMock(SerializationVisitorInterface::class); + $this->deserializerVisitor = $this->createMock(DeserializationVisitorInterface::class); + $this->context = $this->createMock(Context::class); + } + + /** + */ + public function testConfiguration(): void + { + $configurations = SegmentIdHandler::getSubscribingMethods(); + foreach ($configurations as $configuration) { + $this->assertArrayHasKey('direction', $configuration); + $this->assertArrayHasKey('type', $configuration); + $this->assertArrayHasKey('format', $configuration); + $this->assertArrayHasKey('method', $configuration); + } + } + + /** + */ + public function testSerialize(): void + { + $testValue = Uuid::uuid4()->toString(); + $code = new SegmentId($testValue); + $result = $this->handler->serialize($this->serializerVisitor, $code, [], $this->context); + + $this->assertEquals($testValue, $result); + } + + /** + */ + public function testDeserialize(): void + { + $testValue = Uuid::uuid4()->toString();; + $result = $this->handler->deserialize($this->deserializerVisitor, $testValue, [], $this->context); + + $this->assertEquals($testValue, (string) $result); + } +} diff --git a/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandlerTest.php b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandlerTest.php new file mode 100644 index 000000000..2aceb1a92 --- /dev/null +++ b/module/segment/tests/Infrastructure/JMS/Serializer/Handler/SegmentStatusHandlerTest.php @@ -0,0 +1,77 @@ +handler = new SegmentStatusHandler(); + $this->serializerVisitor = $this->createMock(SerializationVisitorInterface::class); + $this->deserializerVisitor = $this->createMock(DeserializationVisitorInterface::class); + $this->context = $this->createMock(Context::class); + } + + /** + */ + public function testConfiguration(): void + { + $configurations = SegmentStatusHandler::getSubscribingMethods(); + foreach ($configurations as $configuration) { + $this->assertArrayHasKey('direction', $configuration); + $this->assertArrayHasKey('type', $configuration); + $this->assertArrayHasKey('format', $configuration); + $this->assertArrayHasKey('method', $configuration); + } + } + + /** + */ + public function testSerialize(): void + { + $testValue = SegmentStatus::OUTDATED; + $code = new SegmentStatus($testValue); + $result = $this->handler->serialize($this->serializerVisitor, $code, [], $this->context); + + $this->assertEquals($testValue, $result); + } + + /** + */ + public function testDeserialize(): void + { + $testValue = SegmentStatus::OUTDATED; + $result = $this->handler->deserialize($this->deserializerVisitor, $testValue, [], $this->context); + + $this->assertEquals($testValue, (string) $result); + } +} diff --git a/module/transformer/composer.json b/module/transformer/composer.json index b987a61f7..ced41e10e 100644 --- a/module/transformer/composer.json +++ b/module/transformer/composer.json @@ -7,11 +7,10 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/value": "^0.4.0", - "friendsofsymfony/rest-bundle": "^2.5", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/value": "^0.5.0", "jms/serializer": "^3.1", "nelmio/api-doc-bundle": "^3.4", "ramsey/uuid": "^3.8", diff --git a/module/transformer/migrations/Version20180619083829.php b/module/transformer/migrations/Version20180619083829.php index 852e214b8..ddb1d4af7 100644 --- a/module/transformer/migrations/Version20180619083829.php +++ b/module/transformer/migrations/Version20180619083829.php @@ -10,9 +10,9 @@ namespace Ergonode\Migration; use Doctrine\DBAL\Schema\Schema; +use Ramsey\Uuid\Uuid; /** - * Auto-generated Ergonode Migration Class: */ final class Version20180619083829 extends AbstractErgonodeMigration { @@ -22,28 +22,53 @@ final class Version20180619083829 extends AbstractErgonodeMigration public function up(Schema $schema): void { $this->addSql('CREATE SCHEMA IF NOT EXISTS importer'); - $this->addSql( - 'CREATE TABLE importer.transformer ( - id UUID NOT NULL, - name VARCHAR(128) NOT NULL, - key VARCHAR(128) NOT NULL, - PRIMARY KEY(id) - )' - ); + + $this->addSql(' + CREATE TABLE importer.transformer ( + id UUID NOT NULL, + name VARCHAR(128) NOT NULL, + key VARCHAR(128) NOT NULL, + PRIMARY KEY(id) + ) + '); $this->addSql('CREATE TABLE importer.transformer_converter (id UUID NOT NULL, transformer_id UUID NOT NULL, field VARCHAR(64) NOT NULL, type VARCHAR(255) NOT NULL, options JSON NOT NULL, PRIMARY KEY(id))'); - $this->addSql( - 'CREATE TABLE importer.processor ( - id UUID NOT NULL, - import_id UUID NOT NULL, - transformer_Id UUID NOT NULL, - action VARCHAR(64) NOT NULL, - created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, - started_at TIMESTAMP WITHOUT TIME ZONE, - ended_at TIMESTAMP WITHOUT TIME ZONE, - status character varying(32) NOT NULL, - PRIMARY KEY(id) - )' - ); + + $this->addSql(' + CREATE TABLE importer.processor ( + id UUID NOT NULL, + import_id UUID NOT NULL, + transformer_Id UUID NOT NULL, + action VARCHAR(64) NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL, + started_at TIMESTAMP WITHOUT TIME ZONE, + ended_at TIMESTAMP WITHOUT TIME ZONE, + status character varying(32) NOT NULL, + PRIMARY KEY(id) + ) + '); + + $this->createEventStoreEvents([ + 'Ergonode\Transformer\Domain\Event\ProcessorCreatedEvent' => 'Transformer processor created', + 'Ergonode\Transformer\Domain\Event\ProcessorStatusChangedEvent' => 'Transformer processor status changed', + 'Ergonode\Transformer\Domain\Event\TransformerConverterAddedEvent' => 'Transformer converter added', + 'Ergonode\Transformer\Domain\Event\TransformerCreatedEvent' => 'Transformer created', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/transformer/src/Persistence/Dbal/Projector/ProcessorCreatedEventProjector.php b/module/transformer/src/Persistence/Dbal/Projector/ProcessorCreatedEventProjector.php index d2faf6f31..4aa1e5d34 100644 --- a/module/transformer/src/Persistence/Dbal/Projector/ProcessorCreatedEventProjector.php +++ b/module/transformer/src/Persistence/Dbal/Projector/ProcessorCreatedEventProjector.php @@ -44,6 +44,8 @@ public function support(DomainEventInterface $event): bool /** * {@inheritDoc} + * + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { diff --git a/module/transformer/src/Persistence/Dbal/Projector/ProcessorStatusChangedEventProjector.php b/module/transformer/src/Persistence/Dbal/Projector/ProcessorStatusChangedEventProjector.php index 3e8fe2fd5..b3a278128 100644 --- a/module/transformer/src/Persistence/Dbal/Projector/ProcessorStatusChangedEventProjector.php +++ b/module/transformer/src/Persistence/Dbal/Projector/ProcessorStatusChangedEventProjector.php @@ -44,6 +44,8 @@ public function support(DomainEventInterface $event): bool /** * {@inheritDoc} + * + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { diff --git a/module/transformer/src/Persistence/Dbal/Projector/TransformerCreatedEventProjector.php b/module/transformer/src/Persistence/Dbal/Projector/TransformerCreatedEventProjector.php index 590220336..ac2963e8a 100644 --- a/module/transformer/src/Persistence/Dbal/Projector/TransformerCreatedEventProjector.php +++ b/module/transformer/src/Persistence/Dbal/Projector/TransformerCreatedEventProjector.php @@ -43,6 +43,8 @@ public function support(DomainEventInterface $event): bool /** * {@inheritDoc} + * + * @throws \Throwable */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { diff --git a/module/transformer/src/Resources/translations/log.en.yaml b/module/transformer/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..f794e83a1 --- /dev/null +++ b/module/transformer/src/Resources/translations/log.en.yaml @@ -0,0 +1,4 @@ +'Transformer created': 'Transformer "%key%" created' +'Transformer converter added': 'Transformer converter added' +'Transformer processor status changed': 'Transformer processor status changed' +'Transformer processor created': 'Transformer processor "%id%" created' diff --git a/module/transformer/src/Resources/translations/log.pl.yaml b/module/transformer/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..b444f2e32 --- /dev/null +++ b/module/transformer/src/Resources/translations/log.pl.yaml @@ -0,0 +1,4 @@ +'Transformer created': 'Transformer "%key%" został utworzony' +'Transformer converter added': 'Dodano konwerter do transformera' +'Transformer processor status changed': 'Zmieniono status procesora transformera' +'Transformer processor created': 'Procesor transformera "%id%" został utworzony' diff --git a/module/translation-deepl/README.md b/module/translation-deepl/README.md index 59cdabdbc..916de1004 100644 --- a/module/translation-deepl/README.md +++ b/module/translation-deepl/README.md @@ -1,4 +1,4 @@ -# Ergonode - transformer +# Ergonode - Deepl translation ## Documentation diff --git a/module/translation-deepl/composer.json b/module/translation-deepl/composer.json index 0c405be44..b64d76e10 100644 --- a/module/translation-deepl/composer.json +++ b/module/translation-deepl/composer.json @@ -9,8 +9,8 @@ "ext-json": "*", "doctrine/collections": "^1.6", "doctrine/dbal": "^2.9", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", "symfony/http-foundation": "^4.3", "scn/deepl-api-connector": "^2.0" }, diff --git a/module/value/composer.json b/module/value/composer.json index bb1b563e7..3268c59c5 100644 --- a/module/value/composer.json +++ b/module/value/composer.json @@ -7,9 +7,9 @@ "require": { "php": "^7.2", "doctrine/dbal": "^2.9", - "ergonode/attribute": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", + "ergonode/attribute": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", "jms/serializer": "^3.1", "ramsey/uuid": "^3.8" }, diff --git a/module/value/migrations/Version20180619083831.php b/module/value/migrations/Version20180619083831.php new file mode 100644 index 000000000..4d2e3e26e --- /dev/null +++ b/module/value/migrations/Version20180619083831.php @@ -0,0 +1,43 @@ +createEventStoreEvents([ + 'Ergonode\Value\Domain\Event\ValueAddedEvent' => 'Value created', + 'Ergonode\Value\Domain\Event\ValueChangedEvent' => 'Value changed', + 'Ergonode\Value\Domain\Event\ValueRemovedEvent' => 'Value deleted', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } + } +} diff --git a/module/value/src/Resources/translations/log.en.yaml b/module/value/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..d50562d0c --- /dev/null +++ b/module/value/src/Resources/translations/log.en.yaml @@ -0,0 +1,3 @@ +'Value created': 'Value "%code%" created' +'Value changed': 'Value changed from "%from%" to "%to%"' +'Value deleted': 'Value "%code%" deleted' diff --git a/module/value/src/Resources/translations/log.pl.yaml b/module/value/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..f481daaaf --- /dev/null +++ b/module/value/src/Resources/translations/log.pl.yaml @@ -0,0 +1,3 @@ +'Value created': 'Wartość "%code%" została utworzona' +'Value changed': 'Zmieniono wartość z "%from%" na "%to%"' +'Value deleted': 'Wartość "%code%" została usunięta' diff --git a/module/workflow/composer.json b/module/workflow/composer.json index 1a6eeb76a..656dd4bd1 100644 --- a/module/workflow/composer.json +++ b/module/workflow/composer.json @@ -6,10 +6,10 @@ "license": "OSL-3.0", "require": { "php": "^7.2", - "ergonode/api": "^0.4.0", - "ergonode/core": "^0.4.0", - "ergonode/es": "^0.4.0", - "ergonode/grid": "^0.4.0", + "ergonode/api": "^0.5.0", + "ergonode/core": "^0.5.0", + "ergonode/es": "^0.5.0", + "ergonode/grid": "^0.5.0", "doctrine/dbal": "^2.9", "jms/serializer": "^3.1", "ramsey/uuid": "^3.8" diff --git a/module/workflow/migrations/Version20190818160000.php b/module/workflow/migrations/Version20190818160000.php index b73535554..74abb13eb 100644 --- a/module/workflow/migrations/Version20190818160000.php +++ b/module/workflow/migrations/Version20190818160000.php @@ -18,20 +18,50 @@ final class Version20190818160000 extends AbstractErgonodeMigration */ public function up(Schema $schema): void { - $this->addSql( - 'CREATE TABLE IF NOT EXISTS status ( - id UUID NOT NULL, - code VARCHAR(128) NOT NULL, - color VARCHAR(7) NOT NULL, - name JSONB NOT NULL DEFAULT \'{}\', - description JSONB NOT NULL DEFAULT \'{}\', - PRIMARY KEY(id) - )' - ); + $this->addSql(' + CREATE TABLE IF NOT EXISTS status ( + id UUID NOT NULL, + code VARCHAR(128) NOT NULL, + color VARCHAR(7) NOT NULL, + name JSONB NOT NULL DEFAULT \'{}\', + description JSONB NOT NULL DEFAULT \'{}\', + PRIMARY KEY(id) + ) + '); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'WORKFLOW_CREATE', 'Workflow']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'WORKFLOW_READ', 'Workflow']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'WORKFLOW_UPDATE', 'Workflow']); $this->addSql('INSERT INTO privileges (id, code, area) VALUES (?, ?, ?)', [Uuid::uuid4()->toString(), 'WORKFLOW_DELETE', 'Workflow']); + + $this->createEventStoreEvents([ + 'Ergonode\Workflow\Domain\Event\Status\StatusColorChangedEvent' => 'Status color changed', + 'Ergonode\Workflow\Domain\Event\Status\StatusCreatedEvent' => 'Status created', + 'Ergonode\Workflow\Domain\Event\Status\StatusDeletedEvent' => 'Status deleted', + 'Ergonode\Workflow\Domain\Event\Status\StatusDescriptionChangedEvent' => 'Status description changed', + 'Ergonode\Workflow\Domain\Event\Status\StatusNameChangedEvent' => 'Status name changed', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowCreatedEvent' => 'Workflow created', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowStatusAddedEvent' => 'Added status to workflow', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowStatusRemovedEvent' => 'Deleted status from workflow', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowTransitionAddedEvent' => 'Added transition to workflow', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowTransitionRemovedEvent' => 'Deleted transition from workflow', + 'Ergonode\Workflow\Domain\Event\Workflow\WorkflowDeletedEvent' => 'Workflow deleted', + ]); + } + + /** + * @param array $collection + * + * @throws \Doctrine\DBAL\DBALException + */ + private function createEventStoreEvents(array $collection): void + { + foreach ($collection as $class => $translation) { + $this->connection->insert('event_store_event', [ + 'id' => Uuid::uuid4()->toString(), + 'event_class' => $class, + 'translation_key' => $translation, + ]); + } } } diff --git a/module/workflow/src/Application/Controller/Api/StatusController.php b/module/workflow/src/Application/Controller/Api/StatusController.php index 58e17a9f7..8b01053c6 100644 --- a/module/workflow/src/Application/Controller/Api/StatusController.php +++ b/module/workflow/src/Application/Controller/Api/StatusController.php @@ -17,6 +17,7 @@ use Ergonode\Core\Domain\ValueObject\TranslatableString; use Ergonode\Grid\RequestGridConfiguration; use Ergonode\Grid\Response\GridResponse; +use Ergonode\Product\Domain\Query\ProductQueryInterface; use Ergonode\Workflow\Application\Form\Model\StatusFormModel; use Ergonode\Workflow\Application\Form\StatusForm; use Ergonode\Workflow\Domain\Command\Status\CreateStatusCommand; @@ -32,6 +33,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\PropertyAccess\Exception\InvalidPropertyPathException; use Symfony\Component\Routing\Annotation\Route; @@ -56,18 +58,26 @@ class StatusController extends AbstractController private $grid; /** - * @param MessageBusInterface $messageBus - * @param StatusQueryInterface $query - * @param StatusGrid $grid + * @var ProductQueryInterface + */ + private $productQuery; + + /** + * @param MessageBusInterface $messageBus + * @param StatusQueryInterface $query + * @param StatusGrid $grid + * @param ProductQueryInterface $productQuery */ public function __construct( MessageBusInterface $messageBus, StatusQueryInterface $query, - StatusGrid $grid + StatusGrid $grid, + ProductQueryInterface $productQuery ) { $this->messageBus = $messageBus; $this->query = $query; $this->grid = $grid; + $this->productQuery = $productQuery; } /** @@ -82,7 +92,7 @@ public function __construct( * type="string", * required=true, * default="EN", - * description="Language Code", + * description="Language Code" * ) * @SWG\Parameter( * name="limit", @@ -90,7 +100,7 @@ public function __construct( * type="integer", * required=true, * default="50", - * description="Number of returned lines", + * description="Number of returned lines" * ) * @SWG\Parameter( * name="offset", @@ -98,14 +108,14 @@ public function __construct( * type="integer", * required=true, * default="0", - * description="Number of start line", + * description="Number of start line" * ) * @SWG\Parameter( * name="field", * in="query", * required=false, * type="string", - * description="Order field", + * description="Order field" * ) * @SWG\Parameter( * name="order", @@ -113,7 +123,7 @@ public function __construct( * required=false, * type="string", * enum={"ASC","DESC"}, - * description="Order", + * description="Order" * ) * @SWG\Parameter( * name="filter", @@ -132,7 +142,7 @@ public function __construct( * ) * @SWG\Response( * response=200, - * description="Returns statuses collection", + * description="Returns statuses collection" * ) * * @ParamConverter(class="Ergonode\Grid\RequestGridConfiguration") @@ -158,7 +168,7 @@ public function getStatuses(Language $language, RequestGridConfiguration $config * in="path", * type="string", * required=true, - * description="Status id", + * description="Status id" * ) * @SWG\Parameter( * name="language", @@ -166,15 +176,15 @@ public function getStatuses(Language $language, RequestGridConfiguration $config * type="string", * required=true, * default="EN", - * description="Language Code", + * description="Language Code" * ) * @SWG\Response( * response=200, - * description="Returns status", + * description="Returns status" * ) * @SWG\Response( * response=404, - * description="Not found", + * description="Not found" * ) * * @ParamConverter(class="Ergonode\Workflow\Domain\Entity\Status") @@ -210,7 +220,7 @@ public function getStatus(Status $status): Response * ) * @SWG\Response( * response=201, - * description="Returns status ID", + * description="Returns status ID" * ) * @SWG\Response( * response=400, @@ -264,7 +274,7 @@ public function createStatus(Request $request): Response * in="path", * type="string", * required=true, - * description="Status code", + * description="Status code" * ) * @SWG\Parameter( * name="language", @@ -338,7 +348,7 @@ public function updateStatus(Status $status, Request $request): Response * in="path", * type="string", * required=true, - * description="Status code", + * description="Status code" * ) * @SWG\Parameter( * name="language", @@ -359,6 +369,10 @@ public function updateStatus(Status $status, Request $request): Response * response=404, * description="Status not found" * ) + * @SWG\Response( + * response=409, + * description="Can't remove" + * ) * * @ParamConverter(class="Ergonode\Workflow\Domain\Entity\Status") * @@ -370,6 +384,11 @@ public function updateStatus(Status $status, Request $request): Response */ public function deleteStatus(Status $status): Response { + $relationships = $this->productQuery->findProductIdByStatusId($status->getId()); + if (0 !== count($relationships)) { + throw new ConflictHttpException('Can\'t remove status, it has relations to products'); + } + $command = new DeleteStatusCommand($status->getId()); $this->messageBus->dispatch($command); diff --git a/module/workflow/src/Application/Controller/Api/WorkflowController.php b/module/workflow/src/Application/Controller/Api/WorkflowController.php index 6bc59860c..565fb37d6 100644 --- a/module/workflow/src/Application/Controller/Api/WorkflowController.php +++ b/module/workflow/src/Application/Controller/Api/WorkflowController.php @@ -14,13 +14,16 @@ use Ergonode\Api\Application\Response\EmptyResponse; use Ergonode\Api\Application\Response\SuccessResponse; use Ergonode\Workflow\Domain\Command\Workflow\CreateWorkflowCommand; +use Ergonode\Workflow\Domain\Command\Workflow\DeleteWorkflowCommand; use Ergonode\Workflow\Domain\Command\Workflow\UpdateWorkflowCommand; +use Ergonode\Workflow\Domain\Entity\Workflow; use Ergonode\Workflow\Domain\Entity\WorkflowId; use Ergonode\Workflow\Domain\Provider\WorkflowProvider; use Ergonode\Workflow\Infrastructure\Builder\WorkflowValidatorBuilder; use JMS\Serializer\Serializer; use JMS\Serializer\SerializerInterface; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Swagger\Annotations as SWG; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -139,8 +142,8 @@ public function getWorkflow(): Response * @SWG\Schema(ref="#/definitions/workflow") * ) * @SWG\Response( - * response=200, - * description="Returns attribute", + * response=201, + * description="Returns workflow ID", * ) * @SWG\Response( * response=400, @@ -158,7 +161,7 @@ public function createWorkflow(Request $request): Response { $data = $request->request->all(); - $violations = $this->validator->validate($data, $this->builder->build($data), [WorkflowValidatorBuilder::UNIQUE_WORKFLOW]); + $violations = $this->validator->validate($data, $this->builder->build($data), ['Default', WorkflowValidatorBuilder::UNIQUE_WORKFLOW]); if (0 === $violations->count()) { $data['id'] = WorkflowId::fromCode($data['code'])->getValue(); @@ -215,7 +218,6 @@ public function updateWorkflow(Request $request): Response $workflow = $this->provider->provide(); $violations = $this->validator->validate($data, $this->builder->build($data)); - if (0 === $violations->count()) { $data['id'] = $workflow->getId()->getValue(); $command = $this->serializer->fromArray($data, UpdateWorkflowCommand::class); @@ -227,4 +229,48 @@ public function updateWorkflow(Request $request): Response throw new ViolationsHttpException($violations); } + + /** + * @Route("/workflow/{workflow}", methods={"DELETE"}, requirements={"workflow"="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"}) + * + * @IsGranted("WORKFLOW_DELETE") + * + * @SWG\Tag(name="Workflow") + * @SWG\Parameter( + * name="language", + * in="path", + * type="string", + * required=true, + * default="EN", + * description="Language Code", + * ) + * @SWG\Parameter( + * name="workflow", + * in="path", + * required=true, + * type="string", + * description="Workflow ID", + * ) + * @SWG\Response( + * response=204, + * description="Success" + * ) + * @SWG\Response( + * response=404, + * description="Not found", + * ) + * + * @ParamConverter(class="Ergonode\Workflow\Domain\Entity\Workflow") + * + * @param Workflow $workflow + * + * @return Response + */ + public function deleteWorkflow(Workflow $workflow): Response + { + $command = new DeleteWorkflowCommand($workflow->getId()); + $this->messageBus->dispatch($command); + + return new EmptyResponse(); + } } diff --git a/module/workflow/src/Application/Request/ParamConverter/StatusParamConverter.php b/module/workflow/src/Application/Request/ParamConverter/StatusParamConverter.php index 749a3899a..c0e068f59 100644 --- a/module/workflow/src/Application/Request/ParamConverter/StatusParamConverter.php +++ b/module/workflow/src/Application/Request/ParamConverter/StatusParamConverter.php @@ -28,18 +28,15 @@ class StatusParamConverter implements ParamConverterInterface private $repository; /** - * @param StatusRepositoryInterface $parameterRepository + * @param StatusRepositoryInterface $repository */ - public function __construct(StatusRepositoryInterface $parameterRepository) + public function __construct(StatusRepositoryInterface $repository) { - $this->repository = $parameterRepository; + $this->repository = $repository; } /** - * @param Request $request - * @param ParamConverter $configuration - * - * @return void + * {@inheritDoc} * * @throws \ReflectionException */ @@ -48,7 +45,7 @@ public function apply(Request $request, ParamConverter $configuration): void $parameter = $request->get('status'); if (null === $parameter) { - throw new BadRequestHttpException('Route parameter status is missing'); + throw new BadRequestHttpException('Route parameter "status" is missing'); } if (!StatusId::isValid($parameter)) { @@ -65,9 +62,7 @@ public function apply(Request $request, ParamConverter $configuration): void } /** - * @param ParamConverter $configuration - * - * @return bool + * {@inheritDoc} */ public function supports(ParamConverter $configuration): bool { diff --git a/module/workflow/src/Application/Request/ParamConverter/WorkflowParamConverter.php b/module/workflow/src/Application/Request/ParamConverter/WorkflowParamConverter.php new file mode 100644 index 000000000..9b9c09a22 --- /dev/null +++ b/module/workflow/src/Application/Request/ParamConverter/WorkflowParamConverter.php @@ -0,0 +1,71 @@ +repository = $repository; + } + + /** + * {@inheritDoc} + * + * @throws \ReflectionException + */ + public function apply(Request $request, ParamConverter $configuration): void + { + $parameter = $request->get('workflow'); + + if (null === $parameter) { + throw new BadRequestHttpException('Route parameter "workflow" is missing'); + } + + if (!WorkflowId::isValid($parameter)) { + throw new BadRequestHttpException('Invalid workflow ID format'); + } + + $entity = $this->repository->load(new WorkflowId($parameter)); + + if (null === $entity) { + throw new NotFoundHttpException(sprintf('Workflow by id "%s" not found', $parameter)); + } + + $request->attributes->set($configuration->getName(), $entity); + } + + /** + * {@inheritDoc} + */ + public function supports(ParamConverter $configuration): bool + { + return Workflow::class === $configuration->getClass(); + } +} diff --git a/module/workflow/src/Domain/Command/Workflow/CreateWorkflowCommand.php b/module/workflow/src/Domain/Command/Workflow/CreateWorkflowCommand.php index bac66d768..676ca47b8 100644 --- a/module/workflow/src/Domain/Command/Workflow/CreateWorkflowCommand.php +++ b/module/workflow/src/Domain/Command/Workflow/CreateWorkflowCommand.php @@ -11,7 +11,6 @@ use Ergonode\Workflow\Domain\Entity\StatusId; use Ergonode\Workflow\Domain\Entity\WorkflowId; -use Ergonode\Workflow\Domain\ValueObject\Status; use Ergonode\Workflow\Domain\ValueObject\Transition; use JMS\Serializer\Annotation as JMS; use Webmozart\Assert\Assert; diff --git a/module/workflow/src/Domain/Command/Workflow/UpdateWorkflowCommand.php b/module/workflow/src/Domain/Command/Workflow/UpdateWorkflowCommand.php index 10c301d02..e3c442e04 100644 --- a/module/workflow/src/Domain/Command/Workflow/UpdateWorkflowCommand.php +++ b/module/workflow/src/Domain/Command/Workflow/UpdateWorkflowCommand.php @@ -11,7 +11,6 @@ use Ergonode\Workflow\Domain\Entity\StatusId; use Ergonode\Workflow\Domain\Entity\WorkflowId; -use Ergonode\Workflow\Domain\ValueObject\Status; use Ergonode\Workflow\Domain\ValueObject\Transition; use JMS\Serializer\Annotation as JMS; use Webmozart\Assert\Assert; diff --git a/module/workflow/src/Domain/Entity/Status.php b/module/workflow/src/Domain/Entity/Status.php index 47ae496c0..c8d655012 100644 --- a/module/workflow/src/Domain/Entity/Status.php +++ b/module/workflow/src/Domain/Entity/Status.php @@ -11,14 +11,12 @@ use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\Core\Domain\ValueObject\Color; -use Ergonode\Core\Domain\ValueObject\State; use Ergonode\Core\Domain\ValueObject\TranslatableString; use Ergonode\EventSourcing\Domain\AbstractAggregateRoot; use Ergonode\Workflow\Domain\Event\Status\StatusColorChangedEvent; use Ergonode\Workflow\Domain\Event\Status\StatusCreatedEvent; use Ergonode\Workflow\Domain\Event\Status\StatusDescriptionChangedEvent; use Ergonode\Workflow\Domain\Event\Status\StatusNameChangedEvent; -use Ergonode\Workflow\Domain\Event\Status\StatusRemovedEvent; use JMS\Serializer\Annotation as JMS; /** @@ -60,13 +58,6 @@ class Status extends AbstractAggregateRoot */ private $description; - /** - * @var State - * - * @JMS\Exclude() - */ - private $state; - /** * @param StatusId $id * @param string $code @@ -89,23 +80,6 @@ public function getId(): AbstractId return $this->id; } - /** - */ - public function remove(): void - { - if ($this->state->getValue() !== State::STATE_DELETED) { - $this->apply(new StatusRemovedEvent($this->id)); - } - } - - /** - * @return bool - */ - public function isDeleted(): bool - { - return $this->state->getValue() === State::STATE_DELETED; - } - /** * @return string */ @@ -184,7 +158,6 @@ protected function applyStatusCreatedEvent(StatusCreatedEvent $event): void $this->color = $event->getColor(); $this->name = $event->getName(); $this->description = $event->getDescription(); - $this->state = new State(); } /** @@ -210,12 +183,4 @@ protected function applyStatusColorChangedEvent(StatusColorChangedEvent $event): { $this->color = $event->getTo(); } - - /** - * @param StatusRemovedEvent $event - */ - protected function applyStatusRemovedEvent(StatusRemovedEvent $event): void - { - $this->state = new State(State::STATE_DELETED); - } } diff --git a/module/workflow/src/Domain/Event/Status/StatusDeletedEvent.php b/module/workflow/src/Domain/Event/Status/StatusDeletedEvent.php new file mode 100644 index 000000000..c0877941c --- /dev/null +++ b/module/workflow/src/Domain/Event/Status/StatusDeletedEvent.php @@ -0,0 +1,18 @@ +id = $id; - } - - /** - * @return StatusId - */ - public function getId(): StatusId - { - return $this->id; - } -} diff --git a/module/workflow/src/Domain/Event/Workflow/WorkflowDeletedEvent.php b/module/workflow/src/Domain/Event/Workflow/WorkflowDeletedEvent.php new file mode 100644 index 000000000..d84e4b8d3 --- /dev/null +++ b/module/workflow/src/Domain/Event/Workflow/WorkflowDeletedEvent.php @@ -0,0 +1,19 @@ +repository->load($id); + $workflow = $this->repository->load($id); if (null === $workflow) { $workflow = $this->factory->create($id, $code); $this->repository->save($workflow); diff --git a/module/workflow/src/Domain/Repository/StatusRepositoryInterface.php b/module/workflow/src/Domain/Repository/StatusRepositoryInterface.php index 6d89713bd..f196b957a 100644 --- a/module/workflow/src/Domain/Repository/StatusRepositoryInterface.php +++ b/module/workflow/src/Domain/Repository/StatusRepositoryInterface.php @@ -26,15 +26,20 @@ interface StatusRepositoryInterface */ public function load(StatusId $id): ?AbstractAggregateRoot; + /** + * @param StatusId $id + * + * @return bool + */ + public function exists(StatusId $id): bool; + /** * @param AbstractAggregateRoot $aggregateRoot */ public function save(AbstractAggregateRoot $aggregateRoot): void; /** - * @param StatusId $id - * - * @return bool + * @param AbstractAggregateRoot $aggregateRoot */ - public function exists(StatusId $id): bool; + public function delete(AbstractAggregateRoot $aggregateRoot): void; } diff --git a/module/workflow/src/Domain/Repository/WorkflowRepositoryInterface.php b/module/workflow/src/Domain/Repository/WorkflowRepositoryInterface.php index 9631958f4..06b59f5dd 100644 --- a/module/workflow/src/Domain/Repository/WorkflowRepositoryInterface.php +++ b/module/workflow/src/Domain/Repository/WorkflowRepositoryInterface.php @@ -21,8 +21,6 @@ interface WorkflowRepositoryInterface * @param WorkflowId $id * * @return null|Workflow - * - * @throws \ReflectionException */ public function load(WorkflowId $id): ?AbstractAggregateRoot; @@ -30,4 +28,9 @@ public function load(WorkflowId $id): ?AbstractAggregateRoot; * @param AbstractAggregateRoot $aggregateRoot */ public function save(AbstractAggregateRoot $aggregateRoot): void; + + /** + * @param AbstractAggregateRoot $aggregateRoot + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void; } diff --git a/module/workflow/src/Infrastructure/Builder/WorkflowValidatorBuilder.php b/module/workflow/src/Infrastructure/Builder/WorkflowValidatorBuilder.php index bb2dfd779..e601a6d0d 100644 --- a/module/workflow/src/Infrastructure/Builder/WorkflowValidatorBuilder.php +++ b/module/workflow/src/Infrastructure/Builder/WorkflowValidatorBuilder.php @@ -68,7 +68,6 @@ public function build(array $data): Constraint }; $uniqueTransition = static function ($data, ExecutionContextInterface $context, $payload) { - $result = []; foreach ($data as $transition) { $key = $transition['source'].$transition['destination']; diff --git a/module/workflow/src/Infrastructure/Handler/Status/DeleteStatusCommandHandler.php b/module/workflow/src/Infrastructure/Handler/Status/DeleteStatusCommandHandler.php index ed70d91bd..1ecb49301 100644 --- a/module/workflow/src/Infrastructure/Handler/Status/DeleteStatusCommandHandler.php +++ b/module/workflow/src/Infrastructure/Handler/Status/DeleteStatusCommandHandler.php @@ -9,13 +9,11 @@ namespace Ergonode\Workflow\Infrastructure\Handler\Status; -use Ergonode\Core\Application\Exception\NotImplementedException; use Ergonode\Workflow\Domain\Command\Status\DeleteStatusCommand; use Ergonode\Workflow\Domain\Repository\StatusRepositoryInterface; use Webmozart\Assert\Assert; /** - * @todo Implement workflow status delete */ class DeleteStatusCommandHandler { @@ -41,7 +39,7 @@ public function __invoke(DeleteStatusCommand $command) { $status = $this->repository->load($command->getId()); Assert::notNull($status); - $status->remove(); - $this->repository->save($status); + + $this->repository->delete($status); } } diff --git a/module/workflow/src/Infrastructure/Handler/Workflow/DeleteWorkflowCommandHandler.php b/module/workflow/src/Infrastructure/Handler/Workflow/DeleteWorkflowCommandHandler.php new file mode 100644 index 000000000..3b7c88c5a --- /dev/null +++ b/module/workflow/src/Infrastructure/Handler/Workflow/DeleteWorkflowCommandHandler.php @@ -0,0 +1,43 @@ +repository = $repository; + } + + /** + * @param DeleteWorkflowCommand $command + */ + public function __invoke(DeleteWorkflowCommand $command) + { + $workflow = $this->repository->load($command->getId()); + Assert::notNull($workflow); + + $this->repository->delete($workflow); + } +} diff --git a/module/workflow/src/Infrastructure/Validator/StatusNotExists.php b/module/workflow/src/Infrastructure/Validator/StatusNotExists.php index 37e9b5609..f5b8f9790 100644 --- a/module/workflow/src/Infrastructure/Validator/StatusNotExists.php +++ b/module/workflow/src/Infrastructure/Validator/StatusNotExists.php @@ -19,5 +19,5 @@ class StatusNotExists extends Constraint /** * @var string */ - public $message = 'Status {{ value }} not exists.'; + public $message = 'Status {{ value }} not exists'; } diff --git a/module/workflow/src/Infrastructure/Validator/StatusNotExistsValidator.php b/module/workflow/src/Infrastructure/Validator/StatusNotExistsValidator.php index e2994c836..f5ae8e4e1 100644 --- a/module/workflow/src/Infrastructure/Validator/StatusNotExistsValidator.php +++ b/module/workflow/src/Infrastructure/Validator/StatusNotExistsValidator.php @@ -54,7 +54,7 @@ public function validate($value, Constraint $constraint): void $value = (string) $value; - $workflow = null; + $workflow = false; if (StatusId::isValid($value)) { $workflow = $this->repository->load(new StatusId($value)); } diff --git a/module/workflow/src/Infrastructure/Validator/WorkflowExistsValidator.php b/module/workflow/src/Infrastructure/Validator/WorkflowExistsValidator.php index 471add847..7495c2b77 100644 --- a/module/workflow/src/Infrastructure/Validator/WorkflowExistsValidator.php +++ b/module/workflow/src/Infrastructure/Validator/WorkflowExistsValidator.php @@ -33,10 +33,9 @@ public function __construct(WorkflowRepositoryInterface $repository) } /** - * @param mixed $value - * @param WorkflowExists|Constraint $constraint + * {@inheritDoc} * - * @throws \ReflectionException + * @throws \Exception */ public function validate($value, Constraint $constraint): void { diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusColorChangedEventProjector.php b/module/workflow/src/Persistence/Dbal/Projector/StatusColorChangedEventProjector.php index 67026aee7..37965d02d 100644 --- a/module/workflow/src/Persistence/Dbal/Projector/StatusColorChangedEventProjector.php +++ b/module/workflow/src/Persistence/Dbal/Projector/StatusColorChangedEventProjector.php @@ -10,10 +10,8 @@ namespace Ergonode\Workflow\Persistence\Dbal\Projector; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\ConnectionException; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Workflow\Domain\Event\Status\StatusColorChangedEvent; @@ -38,9 +36,7 @@ public function __construct(Connection $connection) } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -48,12 +44,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException - * @throws ConnectionException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -61,22 +52,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, StatusColorChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'color' => $event->getTo()->getValue(), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - $this->connection->rollBack(); - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'color' => $event->getTo()->getValue(), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusCreatedEventProjector.php b/module/workflow/src/Persistence/Dbal/Projector/StatusCreatedEventProjector.php index addba2a95..eff17738c 100644 --- a/module/workflow/src/Persistence/Dbal/Projector/StatusCreatedEventProjector.php +++ b/module/workflow/src/Persistence/Dbal/Projector/StatusCreatedEventProjector.php @@ -12,10 +12,10 @@ use Doctrine\DBAL\Connection; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Workflow\Domain\Event\Status\StatusCreatedEvent; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class StatusCreatedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,11 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -59,22 +60,15 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, StatusCreatedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->insert( - self::TABLE, - [ - 'id' => $aggregateId->getValue(), - 'code' => $event->getCode(), - 'name' => json_encode($event->getName()->getTranslations()), - 'description' => json_encode($event->getDescription()->getTranslations()), - 'color' => $event->getColor()->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - throw new ProjectorException($event, $exception); - } + $this->connection->insert( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + 'code' => $event->getCode(), + 'name' => $this->serializer->serialize($event->getName()->getTranslations(), 'json'), + 'description' => $this->serializer->serialize($event->getDescription()->getTranslations(), 'json'), + 'color' => $event->getColor()->getValue(), + ] + ); } } diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusDeletedEventProjector.php b/module/workflow/src/Persistence/Dbal/Projector/StatusDeletedEventProjector.php new file mode 100644 index 000000000..94b899576 --- /dev/null +++ b/module/workflow/src/Persistence/Dbal/Projector/StatusDeletedEventProjector.php @@ -0,0 +1,62 @@ +connection = $connection; + } + + /** + * {@inheritDoc} + */ + public function support(DomainEventInterface $event): bool + { + return $event instanceof StatusDeletedEvent; + } + + /** + * {@inheritDoc} + */ + public function projection(AbstractId $aggregateId, DomainEventInterface $event): void + { + if (!$event instanceof StatusDeletedEvent) { + throw new UnsupportedEventException($event, StatusDeletedEvent::class); + } + + $this->connection->delete( + self::TABLE, + [ + 'id' => $aggregateId->getValue(), + ] + ); + } +} diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusDescriptionChangedEventProjector.php b/module/workflow/src/Persistence/Dbal/Projector/StatusDescriptionChangedEventProjector.php index be099324d..49a39d9f8 100644 --- a/module/workflow/src/Persistence/Dbal/Projector/StatusDescriptionChangedEventProjector.php +++ b/module/workflow/src/Persistence/Dbal/Projector/StatusDescriptionChangedEventProjector.php @@ -12,10 +12,10 @@ use Doctrine\DBAL\Connection; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Workflow\Domain\Event\Status\StatusDescriptionChangedEvent; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class StatusDescriptionChangedEventProjector implements DomainEventProjectorInte private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,11 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -59,21 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, StatusDescriptionChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'description' => json_encode($event->getTo()->getTranslations()), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'description' => $this->serializer->serialize($event->getTo()->getTranslations(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/workflow/src/Persistence/Dbal/Projector/StatusNameChangedEventProjector.php b/module/workflow/src/Persistence/Dbal/Projector/StatusNameChangedEventProjector.php index c325130cc..5e47e7c2e 100644 --- a/module/workflow/src/Persistence/Dbal/Projector/StatusNameChangedEventProjector.php +++ b/module/workflow/src/Persistence/Dbal/Projector/StatusNameChangedEventProjector.php @@ -12,10 +12,10 @@ use Doctrine\DBAL\Connection; use Ergonode\Core\Domain\Entity\AbstractId; use Ergonode\EventSourcing\Infrastructure\DomainEventInterface; -use Ergonode\EventSourcing\Infrastructure\Exception\ProjectorException; use Ergonode\EventSourcing\Infrastructure\Exception\UnsupportedEventException; use Ergonode\EventSourcing\Infrastructure\Projector\DomainEventProjectorInterface; use Ergonode\Workflow\Domain\Event\Status\StatusNameChangedEvent; +use JMS\Serializer\SerializerInterface; /** */ @@ -29,17 +29,22 @@ class StatusNameChangedEventProjector implements DomainEventProjectorInterface private $connection; /** - * @param Connection $connection + * @var SerializerInterface */ - public function __construct(Connection $connection) + private $serializer; + + /** + * @param Connection $connection + * @param SerializerInterface $serializer + */ + public function __construct(Connection $connection, SerializerInterface $serializer) { $this->connection = $connection; + $this->serializer = $serializer; } /** - * @param DomainEventInterface $event - * - * @return bool + * {@inheritDoc} */ public function support(DomainEventInterface $event): bool { @@ -47,11 +52,7 @@ public function support(DomainEventInterface $event): bool } /** - * @param AbstractId $aggregateId - * @param DomainEventInterface $event - * - * @throws ProjectorException - * @throws UnsupportedEventException + * {@inheritDoc} */ public function projection(AbstractId $aggregateId, DomainEventInterface $event): void { @@ -59,21 +60,14 @@ public function projection(AbstractId $aggregateId, DomainEventInterface $event) throw new UnsupportedEventException($event, StatusNameChangedEvent::class); } - $this->connection->beginTransaction(); - try { - $this->connection->update( - self::TABLE, - [ - 'name' => json_encode($event->getTo()->getTranslations()), - ], - [ - 'id' => $aggregateId->getValue(), - ] - ); - - $this->connection->commit(); - } catch (\Throwable $exception) { - throw new ProjectorException($event, $exception); - } + $this->connection->update( + self::TABLE, + [ + 'name' => $this->serializer->serialize($event->getTo()->getTranslations(), 'json'), + ], + [ + 'id' => $aggregateId->getValue(), + ] + ); } } diff --git a/module/workflow/src/Persistence/Dbal/Repository/DbalStatusRepository.php b/module/workflow/src/Persistence/Dbal/Repository/DbalStatusRepository.php index fe26d9e36..8c1ae2b12 100644 --- a/module/workflow/src/Persistence/Dbal/Repository/DbalStatusRepository.php +++ b/module/workflow/src/Persistence/Dbal/Repository/DbalStatusRepository.php @@ -14,6 +14,7 @@ use Ergonode\EventSourcing\Infrastructure\DomainEventStoreInterface; use Ergonode\Workflow\Domain\Entity\Status; use Ergonode\Workflow\Domain\Entity\StatusId; +use Ergonode\Workflow\Domain\Event\Status\StatusDeletedEvent; use Ergonode\Workflow\Domain\Repository\StatusRepositoryInterface; /** @@ -34,8 +35,10 @@ class DbalStatusRepository implements StatusRepositoryInterface * @param DomainEventStoreInterface $eventStore * @param DomainEventDispatcherInterface $eventDispatcher */ - public function __construct(DomainEventStoreInterface $eventStore, DomainEventDispatcherInterface $eventDispatcher) - { + public function __construct( + DomainEventStoreInterface $eventStore, + DomainEventDispatcherInterface $eventDispatcher + ) { $this->eventStore = $eventStore; $this->eventDispatcher = $eventDispatcher; } @@ -50,7 +53,7 @@ public function __construct(DomainEventStoreInterface $eventStore, DomainEventDi public function load(StatusId $id): ?AbstractAggregateRoot { $eventStream = $this->eventStore->load($id); - if (\count($eventStream) > 0) { + if (count($eventStream) > 0) { $class = new \ReflectionClass(Status::class); /** @var Status $aggregate */ $aggregate = $class->newInstanceWithoutConstructor(); @@ -60,14 +63,22 @@ public function load(StatusId $id): ?AbstractAggregateRoot $aggregate->initialize($eventStream); - if (!$aggregate->isDeleted()) { - return $aggregate; - } + return $aggregate; } return null; } + /** + * @param StatusId $id + * + * @return bool + */ + public function exists(StatusId $id) : bool + { + return $this->eventStore->load($id)->count() > 0; + } + /** * @param AbstractAggregateRoot $aggregateRoot */ @@ -82,12 +93,15 @@ public function save(AbstractAggregateRoot $aggregateRoot): void } /** - * @param StatusId $id + * {@inheritDoc} * - * @return bool + * @throws \Exception */ - public function exists(StatusId $id) : bool + public function delete(AbstractAggregateRoot $aggregateRoot): void { - return $this->eventStore->load($id)->count() > 0; + $aggregateRoot->apply(new StatusDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); } } diff --git a/module/workflow/src/Persistence/Dbal/Repository/DbalWorkflowRepository.php b/module/workflow/src/Persistence/Dbal/Repository/DbalWorkflowRepository.php index 7ddb648d3..bd4d54d5c 100644 --- a/module/workflow/src/Persistence/Dbal/Repository/DbalWorkflowRepository.php +++ b/module/workflow/src/Persistence/Dbal/Repository/DbalWorkflowRepository.php @@ -14,6 +14,7 @@ use Ergonode\EventSourcing\Infrastructure\DomainEventStoreInterface; use Ergonode\Workflow\Domain\Entity\Workflow; use Ergonode\Workflow\Domain\Entity\WorkflowId; +use Ergonode\Workflow\Domain\Event\Workflow\WorkflowDeletedEvent; use Ergonode\Workflow\Domain\Repository\WorkflowRepositoryInterface; /** @@ -41,16 +42,14 @@ public function __construct(DomainEventStoreInterface $eventStore, DomainEventDi } /** - * @param WorkflowId $id - * - * @return Workflow|null + * {@inheritDoc} * * @throws \ReflectionException */ public function load(WorkflowId $id): ?AbstractAggregateRoot { $eventStream = $this->eventStore->load($id); - if (\count($eventStream) > 0) { + if (count($eventStream) > 0) { $class = new \ReflectionClass(Workflow::class); /** @var AbstractAggregateRoot $aggregate */ $aggregate = $class->newInstanceWithoutConstructor(); @@ -67,7 +66,7 @@ public function load(WorkflowId $id): ?AbstractAggregateRoot } /** - * @param AbstractAggregateRoot $aggregateRoot + * {@inheritDoc} */ public function save(AbstractAggregateRoot $aggregateRoot): void { @@ -78,4 +77,17 @@ public function save(AbstractAggregateRoot $aggregateRoot): void $this->eventDispatcher->dispatch($envelope); } } + + /** + * {@inheritDoc} + * + * @throws \Exception + */ + public function delete(AbstractAggregateRoot $aggregateRoot): void + { + $aggregateRoot->apply(new WorkflowDeletedEvent()); + $this->save($aggregateRoot); + + $this->eventStore->delete($aggregateRoot->getId()); + } } diff --git a/module/workflow/src/Resources/translations/log.en.yaml b/module/workflow/src/Resources/translations/log.en.yaml new file mode 100644 index 000000000..3840853ce --- /dev/null +++ b/module/workflow/src/Resources/translations/log.en.yaml @@ -0,0 +1,11 @@ +'Status color changed': 'Status color changed from "%from%" to "%to%"' +'Status created': 'Status "%code%" created' +'Status deleted': 'Status deleted' +'Status description changed': 'Status description changed' +'Status name changed': 'Status name changed' +'Workflow created': 'Workflow "%code%" created' +'Added status to workflow': 'Added status to workflow' +'Deleted status from workflow': 'Deleted status from workflow' +'Added transition to workflow': 'Added transition to workflow' +'Deleted transition from workflow': 'Deleted transition from workflow' +'Workflow deleted': 'Workflow "%id%" deleted' diff --git a/module/workflow/src/Resources/translations/log.pl.yaml b/module/workflow/src/Resources/translations/log.pl.yaml new file mode 100644 index 000000000..d99fa9bb1 --- /dev/null +++ b/module/workflow/src/Resources/translations/log.pl.yaml @@ -0,0 +1,11 @@ +'Status color changed': 'Zmieniono kolor statusu z "%from%" na "%to%"' +'Status created': 'Status "%code%" został utworzony' +'Status deleted': 'Usunięto status' +'Status description changed': 'Zmieniono opis statusu' +'Status name changed': 'Zmieniono nazwę statusu' +'Workflow created': 'Przepływ pracy "%code%" utworzony' +'Added status to workflow': 'Dodano status do przepływu pracy' +'Deleted status from workflow': 'Usunięto status z przepływu pracy' +'Added transition to workflow': 'Dodano transformację do przepływu pracy' +'Deleted transition from workflow': 'Usunięto transformację z przepływu pracy' +'Workflow deleted': 'Przepływ pracy "%id%" usunięto' diff --git a/symfony.lock b/symfony.lock index 04fbaa9a3..df4ccedfe 100644 --- a/symfony.lock +++ b/symfony.lock @@ -104,15 +104,6 @@ "firebase/php-jwt": { "version": "v5.0.0" }, - "friendsofsymfony/rest-bundle": { - "version": "2.2", - "recipe": { - "repo": "github.com/symfony/recipes-contrib", - "branch": "master", - "version": "2.2", - "ref": "258300d52be6ad59b32a888d5ddafbf9638540ff" - } - }, "fzaninotto/faker": { "version": "v1.7.1" },