diff --git a/README.md b/README.md index 46764f1b0..c0a465ca5 100644 --- a/README.md +++ b/README.md @@ -41,29 +41,33 @@ Todo: ## Initial Setup -1. Use the ``docker compose up --build -d`` to bring up the containers.Make sure to have Docker Desktop active and have no other containers running that may cause conflict with this project's containers(There may be conflicts port wise if more than one container uses the same ports). +1. Use the ``docker compose up --build -d`` to bring up the containers. Make sure to have Docker Desktop active and have no other containers running that may cause conflict with this project's containers(There may be conflicts port wise if more than one container uses the same ports). 2. Check the status of containers using the command ```docker-compose ps```. Make sure they are running and services are healthy. -3. Get inside the php container using ```docker exec -it php bash```. +3. Get inside the database container using ```docker exec -it mysqlLaravel /bin/bash```. Then, create 4 databases: `inventory`, `social`, `crm`, `workflow`. -4. Create 4 databases `inventory`, `social`, `crm`, `workflow` update your .env with the connection info +4. Set up your .env: You can start by copying the `.env.example setup`. Next, update it with the database and Redis connection info, making sure that the host values match your container's name. -5. Check the .env and setup correctly the `REDIS` parameters and your database connections before running the setup-ecosystem +5. Get inside the php container using ```docker exec -it phpLaravel bash```. -6. Use the command ```php artisan kanvas:setup-ecosystem``` to run the kanvas setup +6. Generate app keys with `php artisan key:generate`. +**Note:** Confirm that your app key is correctly registered in the `apps` table within the `kanvas_laravel` database. -7. If you're presenting some errors after running the command from before, drop all the tables from the schema `kanvas_laravel` and run it again +7. Update the app variables in your .env `APP_JWT_TOKEN`, `APP_KEY`, `KANVAS_APP_ID` before running the setup-ecosystem. +**Note:** You can use the default values provided in `tests.yml`. -8. Generate app keys `php artisan key:generate` +8. Use the command ```php artisan kanvas:setup-ecosystem``` to run the kanvas setup. -9. To check if the API is working just make a GET request to ```http://localhost:80/v1/``` and see if the response returns ```"Woot Kanvas"``` +9. If you're presenting some errors after running the command from before, drop all the tables from the schema `kanvas_laravel` and run it again. + +10. To check if the API is working just make a GET request to ```http://localhost:80/v1/``` and see if the response returns ```"Woot Kanvas"```. ### Setup Inventory 1. composer migrate-inventory 2. Set env var in .env ``` -DB_INVENTORY_HOST=mysql +DB_INVENTORY_HOST=mysqlLaravel DB_INVENTORY_PORT=3306 DB_INVENTORY_DATABASE=inventory DB_INVENTORY_USERNAME=root @@ -76,7 +80,7 @@ DB_INVENTORY_PASSWORD=password 1. composer migrate-social 2. Set env var in .env ``` -DB_SOCIAL_HOST=mysql +DB_SOCIAL_HOST=mysqlLaravel DB_SOCIAL_PORT=3306 DB_SOCIAL_DATABASE=social DB_SOCIAL_USERNAME=root @@ -89,7 +93,7 @@ DB_SOCIAL_PASSWORD=password 1. composer migrate-crm 2. Set env var in .env ``` -DB_CRM_HOST=mysql +DB_CRM_HOST=mysqlLaravel DB_CRM_PORT=3306 DB_CRM_DATABASE=cr DB_CRM_USERNAME=root diff --git a/app/Console/Commands/Connectors/Shopify/ShopifyUploadCategoryCommand.php b/app/Console/Commands/Connectors/Shopify/ShopifyUploadCategoryCommand.php new file mode 100644 index 000000000..2941f171d --- /dev/null +++ b/app/Console/Commands/Connectors/Shopify/ShopifyUploadCategoryCommand.php @@ -0,0 +1,27 @@ +argument('app_id')); + $categories = Categories::getById((int) $this->argument('categories_id'), $app); + $warehouses = Warehouses::getById((int) $this->argument('warehouse_id'), $app); + $shopifyId = $this->argument('shopify_id'); + (new UploadCategoriesToCollectionAction($categories, $app, $warehouses, $shopifyId))->execute(); + } +} diff --git a/app/Console/Commands/Guild/GuildDailyReportCommand.php b/app/Console/Commands/Guild/GuildDailyReportCommand.php new file mode 100644 index 000000000..15e13f623 --- /dev/null +++ b/app/Console/Commands/Guild/GuildDailyReportCommand.php @@ -0,0 +1,44 @@ +argument('app_id')); + $company = Companies::getById($this->argument('company_id')); + $this->overwriteAppService($app); + + //for now just apollo, but this should be for sending all the different reports + $this->info('Sending Apollo Daily Report - ' . date('Y-m-d')); + $apolloDailyReport = new DailyUsageReportAction($app, $company); + $result = $apolloDailyReport->execute(); + $this->info('Total report send ' . count($result)); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 6e10e03e5..77014a276 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -30,7 +30,7 @@ protected function schedule(Schedule $schedule) if (getenv('CADDIE_APP_KEY')) { $schedule->command(MailCaddieLabCommand::class, [getenv('CADDIE_APP_KEY')]) ->dailyAt('13:00') - ->timezone('America/Santo_Domingo'); + ->timezone('America/New_York'); } } diff --git a/app/GraphQL/Ecosystem/Mutations/ImporterTemplate/ImporterTemplateManagementMutation.php b/app/GraphQL/Ecosystem/Mutations/ImporterTemplate/ImporterTemplateManagementMutation.php new file mode 100644 index 000000000..25d56519a --- /dev/null +++ b/app/GraphQL/Ecosystem/Mutations/ImporterTemplate/ImporterTemplateManagementMutation.php @@ -0,0 +1,27 @@ +user(), + companies: auth()->user()->getCurrentCompany(), + apps: app(Apps::class), + name: $req['name'], + attributes: $req['attributes'], + description: $req['description'] ?? null, + ); + return (new CreateMapperImporterTemplateAction($dto))->execute(); + } +} diff --git a/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php b/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php index 67865ebe0..6fe6747f1 100644 --- a/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php +++ b/app/GraphQL/Social/Mutations/Messages/MessageManagementMutation.php @@ -60,6 +60,7 @@ public function create(mixed $root, array $request): Message $systemModuleId = $messageData['system_modules_id'] ?? null; $systemModule = $systemModuleId ? SystemModules::getById((int)$systemModuleId, $app) : null; + $messageData['ip_address'] = request()->ip(); $data = MessageInput::fromArray( $messageData, $user, diff --git a/composer.lock b/composer.lock index 9fa32d421..49c3c445d 100644 --- a/composer.lock +++ b/composer.lock @@ -1195,16 +1195,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.319.4", + "version": "3.320.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b39a56786bef9e922c8bdd0e47c73ba828cc512e" + "reference": "702b9955160d2dacdf2cdf4d4476fcf95eae1aaf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b39a56786bef9e922c8bdd0e47c73ba828cc512e", - "reference": "b39a56786bef9e922c8bdd0e47c73ba828cc512e", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/702b9955160d2dacdf2cdf4d4476fcf95eae1aaf", + "reference": "702b9955160d2dacdf2cdf4d4476fcf95eae1aaf", "shasum": "" }, "require": { @@ -1287,9 +1287,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.319.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.320.7" }, - "time": "2024-08-13T18:04:50+00:00" + "time": "2024-08-23T18:13:50+00:00" }, { "name": "berkayk/onesignal-laravel", @@ -2440,16 +2440,16 @@ }, { "name": "google/apiclient-services", - "version": "v0.368.0", + "version": "v0.370.0", "source": { "type": "git", "url": "https://github.com/googleapis/google-api-php-client-services.git", - "reference": "edc08087aa3ca63d3b74f24d59f1d2caab39b5d9" + "reference": "25ad8515701dd832313d0f5f0a828670d60e541a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/edc08087aa3ca63d3b74f24d59f1d2caab39b5d9", - "reference": "edc08087aa3ca63d3b74f24d59f1d2caab39b5d9", + "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/25ad8515701dd832313d0f5f0a828670d60e541a", + "reference": "25ad8515701dd832313d0f5f0a828670d60e541a", "shasum": "" }, "require": { @@ -2478,9 +2478,9 @@ ], "support": { "issues": "https://github.com/googleapis/google-api-php-client-services/issues", - "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.368.0" + "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.370.0" }, - "time": "2024-07-11T01:08:44+00:00" + "time": "2024-08-26T01:04:18+00:00" }, { "name": "google/auth", @@ -2719,16 +2719,16 @@ }, { "name": "google/gax", - "version": "v1.34.0", + "version": "v1.34.1", "source": { "type": "git", "url": "https://github.com/googleapis/gax-php.git", - "reference": "28aa3e95969a75b278606a88448992a6396a119e" + "reference": "173f0a97323284f91fd453c4ed7ed8317ecf6cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/googleapis/gax-php/zipball/28aa3e95969a75b278606a88448992a6396a119e", - "reference": "28aa3e95969a75b278606a88448992a6396a119e", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/173f0a97323284f91fd453c4ed7ed8317ecf6cfa", + "reference": "173f0a97323284f91fd453c4ed7ed8317ecf6cfa", "shasum": "" }, "require": { @@ -2770,9 +2770,9 @@ ], "support": { "issues": "https://github.com/googleapis/gax-php/issues", - "source": "https://github.com/googleapis/gax-php/tree/v1.34.0" + "source": "https://github.com/googleapis/gax-php/tree/v1.34.1" }, - "time": "2024-05-30T00:35:13+00:00" + "time": "2024-08-15T18:00:58+00:00" }, { "name": "google/grpc-gcp", @@ -3843,16 +3843,16 @@ }, { "name": "laravel/framework", - "version": "v11.20.0", + "version": "v11.21.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "3cd7593dd9b67002fc416b46616f4d4d1da3e571" + "reference": "9d9d36708d56665b12185493f684abce38ad2d30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/3cd7593dd9b67002fc416b46616f4d4d1da3e571", - "reference": "3cd7593dd9b67002fc416b46616f4d4d1da3e571", + "url": "https://api.github.com/repos/laravel/framework/zipball/9d9d36708d56665b12185493f684abce38ad2d30", + "reference": "9d9d36708d56665b12185493f684abce38ad2d30", "shasum": "" }, "require": { @@ -4045,20 +4045,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-08-06T14:39:21+00:00" + "time": "2024-08-20T15:00:52+00:00" }, { "name": "laravel/octane", - "version": "v2.5.3", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/laravel/octane.git", - "reference": "2b5b4d08982cb33a692d467ca96c5bb2f0fcd9ae" + "reference": "d7b8991270eb57eef83be7de62ba04c1289dd65b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/octane/zipball/2b5b4d08982cb33a692d467ca96c5bb2f0fcd9ae", - "reference": "2b5b4d08982cb33a692d467ca96c5bb2f0fcd9ae", + "url": "https://api.github.com/repos/laravel/octane/zipball/d7b8991270eb57eef83be7de62ba04c1289dd65b", + "reference": "d7b8991270eb57eef83be7de62ba04c1289dd65b", "shasum": "" }, "require": { @@ -4135,20 +4135,20 @@ "issues": "https://github.com/laravel/octane/issues", "source": "https://github.com/laravel/octane" }, - "time": "2024-08-05T13:53:57+00:00" + "time": "2024-08-09T12:25:04+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.24", + "version": "v0.1.25", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "409b0b4305273472f3754826e68f4edbd0150149" + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", - "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", "shasum": "" }, "require": { @@ -4191,9 +4191,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.24" + "source": "https://github.com/laravel/prompts/tree/v0.1.25" }, - "time": "2024-06-17T13:58:22+00:00" + "time": "2024-08-12T22:06:33+00:00" }, { "name": "laravel/sanctum", @@ -4611,16 +4611,16 @@ }, { "name": "league/commonmark", - "version": "2.5.2", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "df09d5b6a4188f8f3c3ab2e43a109076a5eeb767" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/df09d5b6a4188f8f3c3ab2e43a109076a5eeb767", - "reference": "df09d5b6a4188f8f3c3ab2e43a109076a5eeb767", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -4633,8 +4633,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.31.0", - "commonmark/commonmark.js": "0.31.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -4713,7 +4713,7 @@ "type": "tidelift" } ], - "time": "2024-08-14T10:56:57+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -5858,16 +5858,16 @@ }, { "name": "nesbot/carbon", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139" + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cb4374784c87d0a0294e8513a52eb63c0aff3139", - "reference": "cb4374784c87d0a0294e8513a52eb63c0aff3139", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f", "shasum": "" }, "require": { @@ -5960,7 +5960,7 @@ "type": "tidelift" } ], - "time": "2024-07-16T22:29:20+00:00" + "time": "2024-08-19T06:22:39+00:00" }, { "name": "nette/schema", @@ -6320,16 +6320,16 @@ }, { "name": "nuwave/lighthouse", - "version": "v6.42.2", + "version": "v6.43.1", "source": { "type": "git", "url": "https://github.com/nuwave/lighthouse.git", - "reference": "b7ba38c959e8f1200f679ddb55ec6192bd1f6223" + "reference": "39f86b2efdab9808eed4aa42a9955f3da1a1260e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nuwave/lighthouse/zipball/b7ba38c959e8f1200f679ddb55ec6192bd1f6223", - "reference": "b7ba38c959e8f1200f679ddb55ec6192bd1f6223", + "url": "https://api.github.com/repos/nuwave/lighthouse/zipball/39f86b2efdab9808eed4aa42a9955f3da1a1260e", + "reference": "39f86b2efdab9808eed4aa42a9955f3da1a1260e", "shasum": "" }, "require": { @@ -6350,7 +6350,7 @@ "webonyx/graphql-php": "^15" }, "require-dev": { - "algolia/algoliasearch-client-php": "^3 || ^4", + "algolia/algoliasearch-client-php": "^3", "bensampo/laravel-enum": "^5 || ^6", "dms/phpunit-arraysubset-asserts": "^0.4 || ^0.5", "ergebnis/composer-normalize": "^2.2.2", @@ -6446,7 +6446,7 @@ "type": "patreon" } ], - "time": "2024-08-05T17:35:49+00:00" + "time": "2024-08-23T09:11:14+00:00" }, { "name": "nyholm/psr7", @@ -7822,16 +7822,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "79dff0b268932c640297f5208d6298f71855c03e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", "shasum": "" }, "require": { @@ -7866,9 +7866,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.1" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-08-21T13:31:24+00:00" }, { "name": "psr/simple-cache", @@ -14135,16 +14135,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.10", + "version": "1.11.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f" + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/640410b32995914bde3eed26fa89552f9c2c082f", - "reference": "640410b32995914bde3eed26fa89552f9c2c082f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", "shasum": "" }, "require": { @@ -14189,36 +14189,36 @@ "type": "github" } ], - "time": "2024-08-08T09:02:50+00:00" + "time": "2024-08-19T14:37:29+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.5", + "version": "11.0.6", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861" + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/19b6365ab8b59a64438c0c3f4241feeb480c9861", - "reference": "19b6365ab8b59a64438c0c3f4241feeb480c9861", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.0", + "nikic/php-parser": "^5.1.0", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0", - "phpunit/php-text-template": "^4.0", - "sebastian/code-unit-reverse-lookup": "^4.0", - "sebastian/complexity": "^4.0", - "sebastian/environment": "^7.0", - "sebastian/lines-of-code": "^3.0", - "sebastian/version": "^5.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^11.0" @@ -14230,7 +14230,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.0-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -14259,7 +14259,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.5" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6" }, "funding": [ { @@ -14267,7 +14267,7 @@ "type": "github" } ], - "time": "2024-07-03T05:05:37+00:00" + "time": "2024-08-22T04:37:56+00:00" }, { "name": "phpunit/php-file-iterator", diff --git a/database/migrations/2024_08_08_063713_importers_templates_table.php b/database/migrations/2024_08_08_063713_importers_templates_table.php new file mode 100644 index 000000000..746378a23 --- /dev/null +++ b/database/migrations/2024_08_08_063713_importers_templates_table.php @@ -0,0 +1,32 @@ +id(); + $table->bigInteger('apps_id')->unsigned(); + $table->bigInteger('users_id')->unsigned(); + $table->bigInteger('companies_id')->unsigned(); + $table->string('name'); + $table->string('description')->nullable(); + $table->integer('is_deleted')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('importers_templates'); + } +}; diff --git a/database/migrations/2024_08_08_072854_attributes_importes_templates.php b/database/migrations/2024_08_08_072854_attributes_importes_templates.php new file mode 100644 index 000000000..471a48043 --- /dev/null +++ b/database/migrations/2024_08_08_072854_attributes_importes_templates.php @@ -0,0 +1,31 @@ +id(); + $table->bigInteger('importers_templates_id')->unsigned(); + $table->bigInteger('parent_id')->unsigned(); // This is the parent attribute id from the attributes_importers_templates table + $table->string('name'); + $table->string('mapping_field'); + $table->integer('is_deleted')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('attributes_importers_templates'); + } +}; diff --git a/database/migrations/2024_08_20_130755_update_app_settings_value.php b/database/migrations/2024_08_20_130755_update_app_settings_value.php new file mode 100644 index 000000000..f8effaa3a --- /dev/null +++ b/database/migrations/2024_08_20_130755_update_app_settings_value.php @@ -0,0 +1,23 @@ +text('value') + ->nullable() + ->change(); + }); + } + + public function down() + { + Schema::table('apps_settings', function (Blueprint $table) { + $table->string('value', 255)->change(); + }); + } +}; diff --git a/database/migrations/2024_08_21_063913_add_path_attribute_mapper.php b/database/migrations/2024_08_21_063913_add_path_attribute_mapper.php new file mode 100644 index 000000000..7956a64f0 --- /dev/null +++ b/database/migrations/2024_08_21_063913_add_path_attribute_mapper.php @@ -0,0 +1,39 @@ +bigInteger('parent_id')->unsigned()->nullable()->change(); + }); + + Schema::table('attributes_mappers_importers_templates', function (Blueprint $table) { + // + $table->string('path')->nullable()->index()->after('parent_id'); + }); + Schema::table('attributes_mappers_importers_templates', function (Blueprint $table) { + $table->foreign('parent_id') + ->references('id') + ->on('attributes_mappers_importers_templates') + ->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('attributes_mappers_importers_templates', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/Social/2024_08_20_173143_add_is_public_messages_column.php b/database/migrations/Social/2024_08_20_173143_add_is_public_messages_column.php new file mode 100644 index 000000000..8e4409a7f --- /dev/null +++ b/database/migrations/Social/2024_08_20_173143_add_is_public_messages_column.php @@ -0,0 +1,27 @@ +integer('is_public')->after('total_disliked')->nullable()->default(1)->index('is_public'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('messages', function (Blueprint $table) { + $table->dropColumn('is_public'); + }); + } +}; diff --git a/database/migrations/Social/2024_08_20_203537_add_dislike_to_user_message.php b/database/migrations/Social/2024_08_20_203537_add_dislike_to_user_message.php new file mode 100644 index 000000000..ca27986c5 --- /dev/null +++ b/database/migrations/Social/2024_08_20_203537_add_dislike_to_user_message.php @@ -0,0 +1,27 @@ +boolean('is_disliked')->after('is_liked')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('user_messages', function (Blueprint $table) { + $table->dropColumn('is_disliked'); + }); + } +}; diff --git a/database/migrations/Social/2024_08_24_033240_add_message_ip.php b/database/migrations/Social/2024_08_24_033240_add_message_ip.php new file mode 100644 index 000000000..faaff9c6e --- /dev/null +++ b/database/migrations/Social/2024_08_24_033240_add_message_ip.php @@ -0,0 +1,27 @@ +string('ip_address', 39)->after('total_shared')->index()->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('messages', function (Blueprint $table) { + $table->dropColumn('ip_address'); + }); + } +}; diff --git a/docker/php.ini b/docker/php.ini index 1a4b6953c..9285baf71 100644 --- a/docker/php.ini +++ b/docker/php.ini @@ -5,3 +5,4 @@ post_max_size = 100M display_startup_errors=1 error_log=/var/log/php/errors.log memory_limit=512M +expose_php = off diff --git a/graphql/schemas/Ecosystem/importerTemplate.graphql b/graphql/schemas/Ecosystem/importerTemplate.graphql new file mode 100644 index 000000000..cc03ba97a --- /dev/null +++ b/graphql/schemas/Ecosystem/importerTemplate.graphql @@ -0,0 +1,50 @@ +type ImporterTemplate { + id: ID! + apps: App + users: User! + company: Company! + name: String! + description: String + attributes: [AttributeImporterTemplate!] @belongsTo(method: "attributes") +} + +type AttributeImporterTemplate { + id: ID! + parent_id: ID + name: String! + mapping_field: String! + importersTemplates: [ImporterTemplate!]! @belongsTo + parent: AttributeImporterTemplate + children: [AttributeImporterTemplate!] +} + +input ImporterTemplateInput { + name: String! + description: String + attributes: [AttributeImporterTemplateInput!] +} + +input AttributeImporterTemplateInput { + name: String + mapping_field: String + children: [AttributeImporterTemplateInput!] +} + +extend type Mutation @guard { + createMapperImporterTemplate(input: ImporterTemplateInput!): ImporterTemplate! + @field( + resolver: "App\\GraphQL\\Ecosystem\\Mutations\\ImporterTemplate\\ImporterTemplateManagementMutation@create" + ) +} + +extend type Query @guard { + mapperImportersTemplates( + where: _ @whereConditions(columns: ["id", "name", "description"]) + hasAttributes: _ @whereHasConditions(columns: ["id", "parent_id", "name", "value"]) + ): [ImporterTemplate!] + @paginate( + model: "Kanvas\\MappersImportersTemplates\\Models\\MapperImporterTemplate" + defaultCount: 25 + scopes: ["fromApp", "fromCompany", "notDeleted"] + ) +} diff --git a/graphql/schemas/Guild/contact.graphql b/graphql/schemas/Guild/contact.graphql index 617a46845..9d150b0c6 100644 --- a/graphql/schemas/Guild/contact.graphql +++ b/graphql/schemas/Guild/contact.graphql @@ -44,4 +44,16 @@ input AddressInput { country: String country_id: ID is_default: Boolean -} \ No newline at end of file +} + +extend type Query @guard { + contactType( + where: _ @whereConditions(columns: ["name"]) + orderBy: _ @orderBy(columns: ["name", "created_at"]) + ): [ContactType!]! + @paginate( + model: "Kanvas\\Guild\\Customers\\Models\\ContactType" + defaultCount: 25 + scopes: ["notDeleted"] + ) +} diff --git a/graphql/schemas/Inventory/attributes.graphql b/graphql/schemas/Inventory/attributes.graphql index 92d4e94ad..67e846d70 100644 --- a/graphql/schemas/Inventory/attributes.graphql +++ b/graphql/schemas/Inventory/attributes.graphql @@ -44,12 +44,14 @@ type AttributesValue { type ProductAttribute { id: ID! name: String! + slug: String! value: Mixed } type VariantsAttributes { id: ID! name: String! + slug: String! value: Mixed } diff --git a/graphql/schemas/Social/message.graphql b/graphql/schemas/Social/message.graphql index bbd003884..792748712 100644 --- a/graphql/schemas/Social/message.graphql +++ b/graphql/schemas/Social/message.graphql @@ -12,6 +12,7 @@ type Message { comment_count: Int total_liked: Int total_disliked: Int + is_public: Int total_saved: Int total_shared: Int total_view: Int @@ -23,6 +24,7 @@ type Message { myInteraction: myInteraction @method(name: "getMyInteraction") comments: [MessageComments!]! @hasMany(type: PAGINATOR) additional_field: Mixed + created_at: DateTime! custom_fields: [CustomField!]! @cacheRedis @paginate( @@ -45,6 +47,7 @@ type AppModuleMessage { type myInteraction { is_liked: Boolean + is_disliked: Boolean is_saved: Boolean is_shared: Boolean is_reported: Boolean @@ -152,8 +155,11 @@ extend type Query @guard { "reactions_count" "comments_count" "total_liked" + "total_disliked" "total_saved" + "total_view" "total_shared" + "is_public" ] ) hasUser: _ @@ -162,6 +168,8 @@ extend type Query @guard { columns: ["id", "displayname"] ) hasTags: _ @whereHasConditions(relation: "tags", columns: ["name"]) + hasType: _ + @whereHasConditions(relation: "messageType", columns: ["name"]) hasAppModuleMessage: _ @whereHasConditions(columns: ["entity_id", "system_modules"]) orderBy: _ @orderBy(columns: ["created_at", "updated_at", "id"]) diff --git a/graphql/schemas/Social/tags.graphql b/graphql/schemas/Social/tags.graphql index edd811b51..8c97e7db1 100644 --- a/graphql/schemas/Social/tags.graphql +++ b/graphql/schemas/Social/tags.graphql @@ -56,8 +56,10 @@ extend type Mutation @guard { extend type Query @guard { tags( + search: String @search where: _ @whereConditions(columns: ["id", "name", "slug", "weight"]) - orderBy: _ @orderBy(columns: ["id"]) + orderBy: _ + @orderBy(columns: ["id", "name", "slug", "weight", "created_at"]) ): [Tag!]! @paginate( model: "Kanvas\\Social\\Tags\\Models\\Tag" diff --git a/src/Domains/Connectors/Apollo/Actions/DailyUsageReportAction.php b/src/Domains/Connectors/Apollo/Actions/DailyUsageReportAction.php new file mode 100644 index 000000000..4e6e23efa --- /dev/null +++ b/src/Domains/Connectors/Apollo/Actions/DailyUsageReportAction.php @@ -0,0 +1,66 @@ +company->get(ConfigurationEnum::APOLLO_COMPANY_REPORTS->value) ?? []; + $usersWhoWantReport = $this->company->get(ConfigurationEnum::APOLLO_COMPANY_REPORTS_USERS->value) ?? []; + $todayInEST = $date ?? Carbon::now('America/New_York')->format('Y-m-d'); + + if (empty($report) || ! isset($report[$todayInEST]) || empty($usersWhoWantReport)) { + return []; + } + + return $this->sendReport($todayInEST, $report[$todayInEST], $usersWhoWantReport); + } + + private function sendReport(string $date, array $report, array $users): array + { + $totalUsersSent = []; + foreach ($users as $userId) { + try { + $user = Users::getById($userId); + UsersRepository::belongsToThisApp($user, $this->app, $this->company); + + $user->notify( + new Blank( + 'apollo-daily-report', + [ + 'report' => $report, + 'today' => $date, + 'subject' => 'Apollo Daily Enrichment Report - ' . $date, + ], + ['mail'], + $user + ) + ); + + $totalUsersSent[] = $user->id; + } catch (ModelNotFoundException $e) { + continue; + } + } + + return $totalUsersSent; + } +} diff --git a/src/Domains/Connectors/Apollo/Enums/ConfigurationEnum.php b/src/Domains/Connectors/Apollo/Enums/ConfigurationEnum.php index 5e0ed97d3..f4f0cf594 100644 --- a/src/Domains/Connectors/Apollo/Enums/ConfigurationEnum.php +++ b/src/Domains/Connectors/Apollo/Enums/ConfigurationEnum.php @@ -8,4 +8,7 @@ enum ConfigurationEnum: string { case APOLLO_API_KEY = 'APOLLO_API_KEY'; case APOLLO_JOB_SEGMENTS = 'APOLLO_JOB_SEGMENTS'; + case APOLLO_DATA_ENRICHMENT_CUSTOM_FIELDS = 'APOLLO_DATA_ENRICHMENT_CUSTOM_FIELDS'; + case APOLLO_COMPANY_REPORTS = 'APOLLO_COMPANY_REPORTS'; + case APOLLO_COMPANY_REPORTS_USERS = 'APOLLO_COMPANY_REPORTS_USERS'; } diff --git a/src/Domains/Connectors/Apollo/Workflows/Activities/ScreeningPeopleActivity.php b/src/Domains/Connectors/Apollo/Workflows/Activities/ScreeningPeopleActivity.php index 61398b0fd..aadf4c6e2 100644 --- a/src/Domains/Connectors/Apollo/Workflows/Activities/ScreeningPeopleActivity.php +++ b/src/Domains/Connectors/Apollo/Workflows/Activities/ScreeningPeopleActivity.php @@ -30,11 +30,48 @@ class ScreeningPeopleActivity extends Activity public function execute(Model $people, AppInterface $app, array $params): array { + if ($this->hasReachedLimit($people)) { + return $this->limitReachedResponse($people); + } + + if ($this->hasBeenScreenedRecently($people)) { + return $this->alreadyScreenedResponse($people); + } + $peopleData = (new ScreeningAction($people, $app))->execute(); + $this->processPeopleData($people, $app, $peopleData); + + return $this->successResponse($people, $peopleData); + } + + private function hasReachedLimit(Model $people): bool + { + $todayReport = $this->getTodayReport($people); + + return $todayReport[date('Y-m-d')]['total'] >= 2000; + } + + private function hasBeenScreenedRecently(Model $people): bool + { + $key = ConfigurationEnum::APOLLO_DATA_ENRICHMENT_CUSTOM_FIELDS->value; + + return $people->get($key) && $people->get($key) > strtotime('-30 days'); + } + + private function processPeopleData(Model $people, AppInterface $app, array $peopleData): void + { $contacts = $this->buildContacts($peopleData); $address = $this->buildAddress($peopleData); + $peopleDto = $this->buildPeopleDto($people, $app, $peopleData, $contacts, $address); - $peopleDto = People::from([ + (new UpdatePeopleAction($people, $peopleDto))->execute(); + $this->updateEmploymentHistory($people, $app, $peopleData['employment_history']); + $this->updateTodayReport($people, ! empty($peopleData['employment_history'])); + } + + private function buildPeopleDto(Model $people, AppInterface $app, array $peopleData, array $contacts, array $address): People + { + return People::from([ 'app' => $app, 'branch' => $people->company->defaultBranch, 'user' => $people->user, @@ -54,72 +91,23 @@ public function execute(Model $people, AppInterface $app, array $params): array 'country' => $address[0]['countries_id'] ?? null, ], ]); - - (new UpdatePeopleAction($people, $peopleDto))->execute(); - $this->updateEmploymentHistory($people, $app, $peopleData['employment_history']); - - return [ - 'status' => 'success', - 'message' => 'People screened successfully', - 'people_id' => $people->id, - 'data' => $peopleData, - ]; } - private function buildContacts(array $peopleData): array + private function updateTodayReport(Model $people, bool $successExtraction): void { - $linkedinId = ContactType::getByName('LinkedIn')->getId(); - $contacts = [ - [ - 'contacts_types_id' => $linkedinId, - 'value' => $peopleData['linkedin_url'], - 'weight' => 0, - ], - [ - 'contacts_types_id' => ContactTypeEnum::EMAIL->value, - 'value' => $peopleData['email'], - 'weight' => 1, - ], - ]; - - if (! empty($peopleData['phone_numbers'][0])) { - $contacts[] = [ - 'contacts_types_id' => ContactTypeEnum::PHONE->value, - 'value' => $peopleData['phone_numbers'][0]['sanitized_number'], - 'weight' => 2, - ]; - } - - return array_values(array_filter($contacts, fn ($contact) => ! empty($contact['value']))); - } + $company = $people->company; + $todayReport = $this->getTodayReport($people); + $today = date('Y-m-d'); - private function buildAddress(array $peopleData): array - { - if (empty($peopleData['country']) || empty($peopleData['state']) || empty($peopleData['city'])) { - return []; - } - - try { - $state = States::getByName($peopleData['state']); - $country = Countries::getByName($peopleData['country']); + $todayReport[$today] = [ + 'total' => $todayReport[$today]['total'] + 1 ?? 1, + 'success' => $successExtraction ? ($todayReport[$today]['success'] + 1 ?? 1) : ($todayReport[$today]['success'] ?? 0), + 'processed' => $todayReport[$today]['processed'] + 1 ?? 1, + 'failed' => ! $successExtraction ? ($todayReport[$today]['failed'] + 1 ?? 1) : ($todayReport[$today]['failed'] ?? 0), + ]; - return [ - [ - 'address' => '', - 'address_2' => '', - 'city' => $peopleData['city'], - 'state' => $state ? $state->code : null, - 'county' => '', - 'zip' => '', - 'city_id' => null, - 'state_id' => $state ? $state->id : null, - 'countries_id' => $country->getId(), - 'country' => $country->name, - ], - ]; - } catch (Exception $e) { - return []; - } + $company->set(ConfigurationEnum::APOLLO_COMPANY_REPORTS->value, $todayReport); + $people->set(ConfigurationEnum::APOLLO_DATA_ENRICHMENT_CUSTOM_FIELDS->value, time()); } private function updateEmploymentHistory(Model $people, AppInterface $app, array $employmentHistory): void @@ -128,7 +116,8 @@ private function updateEmploymentHistory(Model $people, AppInterface $app, array if (empty($employment['organization_name'])) { continue; } - $organization = new CreateOrganizationAction( + + $organization = (new CreateOrganizationAction( new Organization( $people->company, $people->user, @@ -136,7 +125,7 @@ private function updateEmploymentHistory(Model $people, AppInterface $app, array $employment['organization_name'], $employment['raw_address'] ) - ); + ))->execute(); PeopleEmploymentHistory::firstOrCreate([ 'status' => (int)$employment['current'], @@ -145,7 +134,7 @@ private function updateEmploymentHistory(Model $people, AppInterface $app, array 'position' => $employment['title'], 'apps_id' => $app->getId(), 'peoples_id' => $people->id, - 'organizations_id' => $organization->execute()->getId(), + 'organizations_id' => $organization->getId(), ]); $this->assignAudienceSegment($people, $app, $employment['title']); @@ -155,22 +144,131 @@ private function updateEmploymentHistory(Model $people, AppInterface $app, array private function assignAudienceSegment(Model $people, AppInterface $app, string $jobTitle): void { $segments = $app->get(ConfigurationEnum::APOLLO_JOB_SEGMENTS->value); - if (empty($segments)) { return; } + $tags = $this->getMatchingSegments($segments, $jobTitle); + $people->addTags($tags); + } + + private function getMatchingSegments(array $segments, string $jobTitle): array + { $jobTitle = strtolower($jobTitle); $tags = []; + foreach ($segments as $segment => $data) { foreach ($data['keywords'] as $keyword) { if (Str::contains($jobTitle, strtolower($keyword))) { - $segmentSlug = strtolower($segment); - $tags[$segmentSlug] = $segmentSlug; + $tags[strtolower($segment)] = strtolower($segment); } } } - $people->addTags($tags); + return $tags; + } + + private function buildContacts(array $peopleData): array + { + $linkedinId = ContactType::getByName('LinkedIn')->getId(); + $contacts = [ + $this->createContact($linkedinId, $peopleData['linkedin_url'], 0), + $this->createContact(ContactTypeEnum::EMAIL->value, $peopleData['email'], 1), + ]; + + if (! empty($peopleData['phone_numbers'][0])) { + $contacts[] = $this->createContact(ContactTypeEnum::PHONE->value, $peopleData['phone_numbers'][0]['sanitized_number'], 2); + } + + return $this->filterEmptyContacts($contacts); + } + + private function createContact(int $typeId, ?string $value, int $weight): array + { + return [ + 'contacts_types_id' => $typeId, + 'value' => $value, + 'weight' => $weight, + ]; + } + + private function filterEmptyContacts(array $contacts): array + { + return array_values(array_filter($contacts, fn ($contact) => ! empty($contact['value']))); + } + + private function buildAddress(array $peopleData): array + { + if (empty($peopleData['country']) || empty($peopleData['state']) || empty($peopleData['city'])) { + return []; + } + + try { + $state = States::getByName($peopleData['state']); + $country = Countries::getByName($peopleData['country']); + + return [ + $this->createAddress($peopleData, $state, $country), + ]; + } catch (Exception $e) { + return []; + } + } + + private function createAddress(array $peopleData, ?States $state, Countries $country): array + { + return [ + 'address' => '', + 'address_2' => '', + 'city' => $peopleData['city'], + 'state' => $state?->code, + 'county' => '', + 'zip' => '', + 'city_id' => null, + 'state_id' => $state?->id, + 'countries_id' => $country->getId(), + 'country' => $country->name, + ]; + } + + private function getTodayReport(Model $people): array + { + $report = $people->company->get(ConfigurationEnum::APOLLO_COMPANY_REPORTS->value) ?? []; + + if (! isset($report[date('Y-m-d')])) { + $report[date('Y-m-d')] = ['total' => 0, 'success' => 0, 'processed' => 0, 'failed' => 0]; + } + + return $report; + } + + private function limitReachedResponse(Model $people): array + { + return [ + 'status' => 'failed', + 'message' => 'Limit reached', + 'people_id' => $people->id, + 'data' => [], + ]; + } + + private function alreadyScreenedResponse(Model $people): array + { + return [ + 'status' => 'success', + 'message' => 'People already screened', + 'people_id' => $people->id, + 'data' => [], + ]; + } + + private function successResponse(Model $people, array $peopleData): array + { + return [ + 'status' => 'success', + 'message' => 'People screened successfully', + 'people_id' => $people->id, + 'data' => $peopleData, + ]; } } diff --git a/src/Domains/Connectors/Shopify/Actions/UploadCategoriesToCollectionAction.php b/src/Domains/Connectors/Shopify/Actions/UploadCategoriesToCollectionAction.php new file mode 100644 index 000000000..f83431fda --- /dev/null +++ b/src/Domains/Connectors/Shopify/Actions/UploadCategoriesToCollectionAction.php @@ -0,0 +1,38 @@ +shopifyService = new ShopifyInventoryService($app, $categories->company, $warehouses); + } + + public function execute() + { + $products = $this->categories->products()->join('products_warehouses', 'products.id', '=', 'products_warehouses.products_id') + ->where('products_warehouses.warehouses_id', $this->warehouses->id) + ->get(); + foreach ($products as $product) { + $collection = $this->collectionId ?? $this->categories->get(CustomFieldEnum::SHOPIFY_COLLECTION_ID->value); + $this->shopifyService->attachToCollection($product, $collection); + } + } +} diff --git a/src/Domains/Connectors/Shopify/Enums/CustomFieldEnum.php b/src/Domains/Connectors/Shopify/Enums/CustomFieldEnum.php index 91d0ef542..74602efdf 100644 --- a/src/Domains/Connectors/Shopify/Enums/CustomFieldEnum.php +++ b/src/Domains/Connectors/Shopify/Enums/CustomFieldEnum.php @@ -19,4 +19,8 @@ enum CustomFieldEnum: string case SHOPIFY_ORDER_ID = 'SHOPIFY_ORDER_ID'; case USER_SHOPIFY_ID = 'shopify_id'; case SHOPIFY_MANUEL_ORDER_NOTIFICATION_MSG = 'SHOPIFY_MANUEL_ORDER_NOTIFICATION_MSG'; + + case SHOPIFY_COLLECTION_ID = 'shopify_collection_id'; + + case SHOPIFY_COLLECTION_IS_SYNCED = 'shopify_collection_is_synced'; } diff --git a/src/Domains/Connectors/Shopify/Services/ShopifyInventoryService.php b/src/Domains/Connectors/Shopify/Services/ShopifyInventoryService.php index 50cb9e823..0253519af 100644 --- a/src/Domains/Connectors/Shopify/Services/ShopifyInventoryService.php +++ b/src/Domains/Connectors/Shopify/Services/ShopifyInventoryService.php @@ -211,4 +211,24 @@ public function publishProduct(Products $product): array { return $this->changeProductStatus($product, StatusEnum::ACTIVE); } + + public function attachToCollection(Products $product, string $collectionId): void + { + $shopifyProductId = $product->getShopifyId($this->warehouses->regions); + + $collectData = [ + 'collection_id' => $collectionId, + 'product_id' => $shopifyProductId + ]; + $collects = $this->shopifySdk->Collect->get([ + 'collection_id' => $collectionId, + 'product_id' => $shopifyProductId, + 'limit' => 1 + ]); + if ($collects) { + return; + } + + $response = $this->shopifySdk->Collect->post($collectData); + } } diff --git a/src/Domains/Guild/Customers/Models/People.php b/src/Domains/Guild/Customers/Models/People.php index 4c4eecbe0..6bdd74826 100644 --- a/src/Domains/Guild/Customers/Models/People.php +++ b/src/Domains/Guild/Customers/Models/People.php @@ -206,6 +206,13 @@ public function shouldBeSearchable(): bool return $this->is_deleted == 0; } + public function searchableAs(): string + { + $customIndex = $this->app ? $this->app->get('app_custom_people_index') : null; + + return config('scout.prefix') . ($customIndex ?? 'peoples'); + } + public function toSearchableArray(): array { $people = [ @@ -219,6 +226,8 @@ public function toSearchableArray(): array 'dob' => $this->dob, 'apps_id' => $this->apps_id, 'users_id' => $this->users_id, + 'created_at' => $this->created_at, + 'updated_at' => $this->updated_at, 'files' => $this->getFiles()->take(5)->map(function ($files) { //for now limit return [ 'uuid' => $files->uuid, diff --git a/src/Domains/Inventory/Categories/Models/Categories.php b/src/Domains/Inventory/Categories/Models/Categories.php index 080946650..af5db9296 100644 --- a/src/Domains/Inventory/Categories/Models/Categories.php +++ b/src/Domains/Inventory/Categories/Models/Categories.php @@ -14,6 +14,8 @@ use Kanvas\Inventory\Products\Models\ProductsCategories; use Baka\Traits\DatabaseSearchableTrait; use Kanvas\Inventory\Traits\ScopesTrait; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Kanvas\Inventory\Products\Models\Products; class Categories extends BaseModel { @@ -49,6 +51,11 @@ public function productsCategories(): HasMany return $this->hasMany(ProductsCategories::class, 'categories_id'); } + public function products(): BelongsToMany + { + return $this->belongsToMany(Products::class, 'products_categories', 'categories_id', 'products_id'); + } + /** * Get the total amount of products of a product type. * diff --git a/src/Domains/Social/Messages/Actions/CreateMessageAction.php b/src/Domains/Social/Messages/Actions/CreateMessageAction.php index 5f8ea7d7b..49875b661 100644 --- a/src/Domains/Social/Messages/Actions/CreateMessageAction.php +++ b/src/Domains/Social/Messages/Actions/CreateMessageAction.php @@ -36,6 +36,7 @@ public function execute(): Message 'total_disliked' => $this->messageInput->total_disliked, 'total_saved' => $this->messageInput->total_saved, 'total_shared' => $this->messageInput->total_shared, + 'ip_address' => $this->messageInput->ip_address, ]; $validator = Validator::make($data, [ diff --git a/src/Domains/Social/Messages/Actions/CreateUserMessageAction.php b/src/Domains/Social/Messages/Actions/CreateUserMessageAction.php index e4fb505f9..1353f01e5 100644 --- a/src/Domains/Social/Messages/Actions/CreateUserMessageAction.php +++ b/src/Domains/Social/Messages/Actions/CreateUserMessageAction.php @@ -30,18 +30,21 @@ public function __construct( public function execute(): UserMessage { $userMessage = UserMessage::firstOrCreate([ - 'messages_id' => $this->message->id, - 'users_id' => $this->user->id, - ]); - $userMessageActivity = UserMessageActivity::firstOrCreate([ - 'user_messages_id' => $userMessage->id, - 'from_entity_id' => $this->message->appModuleMessage->entity_id, - 'entity_namespace' => $this->activity['entity_namespace'], - 'username' => $this->activity['username'], - 'type' => $this->activity['type'], - 'text' => $this->activity['text'], + 'messages_id' => $this->message->getId(), + 'users_id' => $this->user->getId(), ]); + if ($this->message->appModuleMessage && ! empty($this->activity)) { + UserMessageActivity::firstOrCreate([ + 'user_messages_id' => $userMessage->id, + 'from_entity_id' => $this->message->appModuleMessage->entity_id, + 'entity_namespace' => $this->activity['entity_namespace'] ?? null, + 'username' => $this->activity['username'] ?? null, + 'type' => $this->activity['type'] ?? null, + 'text' => $this->activity['text'] ?? null, + ]); + } + return $userMessage; } } diff --git a/src/Domains/Social/Messages/DataTransferObject/MessageInput.php b/src/Domains/Social/Messages/DataTransferObject/MessageInput.php index 92c88a795..b8059d190 100644 --- a/src/Domains/Social/Messages/DataTransferObject/MessageInput.php +++ b/src/Domains/Social/Messages/DataTransferObject/MessageInput.php @@ -35,6 +35,7 @@ public function __construct( public ?int $total_saved = 0, public ?int $total_shared = 0, public ?string $parent_unique_id = null, + public ?string $ip_address = null, public array $tags = [] ) { } @@ -74,6 +75,7 @@ public static function fromArray( $data['total_saved'] ?? 0, $data['total_shared'] ?? 0, $parent ? $parent->uuid : null, + $data['ip_address'] ?? null, $data['tags'] ?? [] ); } diff --git a/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php b/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php index a8afb90b1..f8677a2fd 100644 --- a/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php +++ b/src/Domains/Social/Messages/Jobs/CreateMessageFromReceiverJob.php @@ -41,6 +41,7 @@ public function execute(): array } $user = $this->receiver->user; + $payload['message']['ip_address'] = $this->webhookRequest->headers['x-real-ip'] ?? null; $createMessage = new CreateMessageAction( new MessageInput( app: $this->receiver->app, diff --git a/src/Domains/Social/Messages/Models/Message.php b/src/Domains/Social/Messages/Models/Message.php index 858788558..a58103738 100644 --- a/src/Domains/Social/Messages/Models/Message.php +++ b/src/Domains/Social/Messages/Models/Message.php @@ -45,9 +45,11 @@ * @property int $comments_count * @property int $total_liked * @property int $total_disliked + * @property int $is_public * @property int $total_view * @property int $total_saved * @property int $total_shared + * @property string|null ip_address */ // Company, User and App Relationship is defined in KanvasModelTrait, class Message extends BaseModel @@ -131,6 +133,7 @@ public function getMyInteraction(): array return [ 'is_liked' => (int) ($userMessage?->is_liked), + 'is_disliked' => (int) ($userMessage?->is_disliked), 'is_saved' => (int) ($userMessage?->is_saved), 'is_shared' => (int) ($userMessage?->is_shared), 'is_reported' => (int) ($userMessage?->is_reported), diff --git a/src/Domains/Social/Messages/Models/UserMessage.php b/src/Domains/Social/Messages/Models/UserMessage.php index 122fa046a..9fc791a43 100644 --- a/src/Domains/Social/Messages/Models/UserMessage.php +++ b/src/Domains/Social/Messages/Models/UserMessage.php @@ -9,6 +9,20 @@ use Kanvas\Social\Models\BaseModel; use Kanvas\Users\Models\Users; +/** + * Class UserMessage + * @property int $message_id + * @property int $users_id + * @property int $is_liked + * @property int $is_disliked + * @property int $is_saved + * @property int $is_shared + * @property int $is_reported + * @property string $notes + * @property string $reactions + * @property string $saved_lists + * @property string $activities + */ class UserMessage extends BaseModel { protected $table = 'user_messages'; diff --git a/src/Domains/Social/Messages/Services/MessageInteractionService.php b/src/Domains/Social/Messages/Services/MessageInteractionService.php index d750690a4..d315fd44e 100644 --- a/src/Domains/Social/Messages/Services/MessageInteractionService.php +++ b/src/Domains/Social/Messages/Services/MessageInteractionService.php @@ -11,7 +11,9 @@ use Kanvas\Social\Interactions\DataTransferObject\UserInteraction; use Kanvas\Social\Interactions\Models\Interactions; use Kanvas\Social\Interactions\Models\UsersInteractions; +use Kanvas\Social\Messages\Actions\CreateUserMessageAction; use Kanvas\Social\Messages\Models\Message; +use Kanvas\Social\Messages\Models\UserMessage; class MessageInteractionService { @@ -25,6 +27,10 @@ public function share(UserInterface $who, ?UserInterface $to = null): string $this->incrementInteractionCount('total_shared'); $this->createInteraction($who, InteractionEnum::SHARE->getValue()); + $userMessage = $this->addToUserMessage($who); + $userMessage->is_shared = 1; + $userMessage->saveOrFail(); + $shareUrl = $this->message->app->get(AppEnum::SHAREABLE_LINK->value) ?? $this->message->app->url; if ($this->message->app->get(AppEnum::SHAREABLE_LINK_WITH_USERNAME->value)) { @@ -45,14 +51,28 @@ public function like(UserInterface $who): UsersInteractions { $this->incrementInteractionCount('total_liked'); - return $this->createInteraction($who, InteractionEnum::LIKE->getValue()); + $userInteraction = $this->createInteraction($who, InteractionEnum::LIKE->getValue()); + + $userMessage = $this->addToUserMessage($who); + $userMessage->is_liked = 1; + $userMessage->is_disliked = 0; + $userMessage->saveOrFail(); + + return $userInteraction; } public function dislike(UserInterface $who): UsersInteractions { $this->incrementInteractionCount('total_disliked'); - return $this->createInteraction($who, InteractionEnum::DISLIKE->getValue()); + $userInteraction = $this->createInteraction($who, InteractionEnum::DISLIKE->getValue()); + + $userMessage = $this->addToUserMessage($who); + $userMessage->is_disliked = 1; + $userMessage->is_liked = 0; + $userMessage->saveOrFail(); + + return $userInteraction; } protected function incrementInteractionCount(string $interactionType): void @@ -76,4 +96,16 @@ protected function createInteraction(UserInterface $who, string $interactionType return $createUserInteraction->execute(); } + + protected function addToUserMessage(UserInterface $user): UserMessage + { + $createUserMessage = new CreateUserMessageAction( + $this->message, + $user, + [ + ] + ); + + return $createUserMessage->execute(); + } } diff --git a/src/Domains/Social/Tags/Models/Tag.php b/src/Domains/Social/Tags/Models/Tag.php index 50baab46c..3f4460902 100644 --- a/src/Domains/Social/Tags/Models/Tag.php +++ b/src/Domains/Social/Tags/Models/Tag.php @@ -5,8 +5,11 @@ namespace Kanvas\Social\Tags\Models; use Baka\Traits\SlugTrait; +use Baka\Users\Contracts\UserInterface; use Illuminate\Support\Facades\DB; +use Kanvas\Apps\Models\Apps; use Kanvas\Social\Models\BaseModel; +use Laravel\Scout\Searchable; /** * @property int id @@ -21,6 +24,9 @@ class Tag extends BaseModel { use SlugTrait; + use Searchable { + search as public traitSearch; + } protected $guarded = []; protected $table = 'tags'; @@ -40,6 +46,53 @@ public function entities() public function getTable() { $databaseName = DB::connection($this->connection)->getDatabaseName(); + return $databaseName . '.tags'; } + + public function shouldBeSearchable(): bool + { + return $this->is_deleted == 0; + } + + public function searchableAs(): string + { + $customIndex = $this->app ? $this->app->get('app_custom_tag_index') : null; + + return config('scout.prefix') . ($customIndex ?? 'tag_index'); + } + + public static function search($query = '', $callback = null) + { + $query = self::traitSearch($query, $callback)->where('apps_id', app(Apps::class)->getId()); + $user = auth()->user(); + if ($user instanceof UserInterface && ! auth()->user()->isAppOwner()) { + $query->where('company.id', auth()->user()->getCurrentCompany()->getId()); + } + + return $query; + } + + public function toSearchableArray(): array + { + return [ + 'objectID' => $this->id, + 'id' => $this->id, + 'name' => $this->name, + 'company' => [ + 'id' => $this->companies_id, + 'name' => $this->company->name, + ], + 'user' => [ + 'firstname' => $this?->company?->user?->firstname, + 'lastname' => $this?->company?->user?->lastname, + ], + 'slug' => $this->slug, + 'apps_id' => $this->apps_id, + 'weight' => $this->weight, + 'status' => $this->status, + 'is_featured' => $this->is_feature, + 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + ]; + } } diff --git a/src/Kanvas/Auth/Services/AuthenticationService.php b/src/Kanvas/Auth/Services/AuthenticationService.php index c8e722a01..47f684652 100644 --- a/src/Kanvas/Auth/Services/AuthenticationService.php +++ b/src/Kanvas/Auth/Services/AuthenticationService.php @@ -80,15 +80,24 @@ public function login( Password::rehash($loginInput->getPassword(), $authentically); $this->resetLoginTries($authentically); + $company = $user->getCurrentCompany(); + if (! $company->isActive()) { + $authMessage = $this->app->get(AppSettingsEnums::INACTIVE_COMPANY_ACCOUNT_ERROR_MESSAGE->getValue()) ?? 'Company is not active, please contact support.'; + + throw new AuthenticationException($authMessage); + } + $user->fireWorkflow( WorkflowEnum::USER_LOGIN->value, true, - ['company' => $user->getCurrentCompany()] + ['company' => $company] ); return $user; } elseif (! $authentically->isActive()) { - throw new AuthenticationException('User is not active, please contact support.'); + $authMessage = $this->app->get(AppSettingsEnums::INACTIVE_ACCOUNT_ERROR_MESSAGE->getValue()) ?? 'User is not active, please contact support.'; + + throw new AuthenticationException($authMessage); } elseif ($authentically->isBanned()) { throw new AuthenticationException('User has been banned, please contact support.'); } else { diff --git a/src/Kanvas/Auth/Services/ForgotPassword.php b/src/Kanvas/Auth/Services/ForgotPassword.php index cf26710e3..69d3c3ef3 100644 --- a/src/Kanvas/Auth/Services/ForgotPassword.php +++ b/src/Kanvas/Auth/Services/ForgotPassword.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Kanvas\Apps\Models\Apps; use Kanvas\Enums\AppEnums; +use Kanvas\Enums\AppSettingsEnums; use Kanvas\Exceptions\ModelNotFoundException as ExceptionsModelNotFoundException; use Kanvas\Notifications\Templates\ResetPassword; use Kanvas\Users\Models\Users; @@ -30,10 +31,12 @@ public function forgot(string $email): Users $recoverUser->generateForgotHash($this->app); try { + $resetPasswordTitle = $this->app->get((string) AppSettingsEnums::RESET_PASSWORD_EMAIL_SUBJECT->getValue()) ?? $this->app->name . ' - Reset your password'; + $recoverUser->notify(new ResetPassword( $recoverUser, [ - 'subject' => $this->app->name . ' - Reset your password', + 'subject' => $resetPasswordTitle, 'app' => $this->app, ] )); diff --git a/src/Kanvas/Companies/Models/Companies.php b/src/Kanvas/Companies/Models/Companies.php index 3dfd98145..0ad329e74 100644 --- a/src/Kanvas/Companies/Models/Companies.php +++ b/src/Kanvas/Companies/Models/Companies.php @@ -22,8 +22,10 @@ use Kanvas\Companies\Factories\CompaniesFactory; use Kanvas\Companies\Repositories\CompaniesRepository; use Kanvas\Currencies\Models\Currencies; +use Kanvas\Enums\AppSettingsEnums; use Kanvas\Enums\StateEnums; use Kanvas\Filesystem\Models\FilesystemEntities; +use Kanvas\Filesystem\Repositories\FilesystemEntitiesRepository; use Kanvas\Filesystem\Traits\HasFilesystemTrait; use Kanvas\Inventory\Regions\Models\Regions; use Kanvas\Models\BaseModel; @@ -282,6 +284,11 @@ public function isOwner(Users $user): bool return $this->users_id === $user->getKey(); } + public function isActive(): bool + { + return (bool) $this->is_active; + } + /** * Not deleted scope. */ @@ -339,6 +346,9 @@ public function toSearchableArray() public function getPhoto(): ?FilesystemEntities { - return $this->getFileByName('photo'); + $app = app(Apps::class); + $defaultAvatarId = $app->get(AppSettingsEnums::DEFAULT_COMPANY_AVATAR->getValue()); + + return $this->getFileByName('photo') ?: ($defaultAvatarId ? FilesystemEntitiesRepository::getFileFromEntityById($defaultAvatarId) : null); } } diff --git a/src/Kanvas/Enums/AppSettingsEnums.php b/src/Kanvas/Enums/AppSettingsEnums.php index 10a948cb2..0a9cbc853 100644 --- a/src/Kanvas/Enums/AppSettingsEnums.php +++ b/src/Kanvas/Enums/AppSettingsEnums.php @@ -28,6 +28,11 @@ enum AppSettingsEnums implements EnumsInterface case SOCIALITE_PROVIDER_FACEBOOK; case SOCIALITE_PROVIDER_GOOGLE; case SOCIALITE_PROVIDER_APPLE; + case DEFAULT_USER_AVATAR; + case DEFAULT_COMPANY_AVATAR; + case INACTIVE_ACCOUNT_ERROR_MESSAGE; + case INACTIVE_COMPANY_ACCOUNT_ERROR_MESSAGE; + case RESET_PASSWORD_EMAIL_SUBJECT; /** * Get value. @@ -55,6 +60,11 @@ public function getValue(): mixed self::SOCIALITE_PROVIDER_FACEBOOK => 'facebook_social_config', self::SOCIALITE_PROVIDER_GOOGLE => 'google_social_config', self::SOCIALITE_PROVIDER_APPLE => 'apple_social_config', + self::DEFAULT_USER_AVATAR => 'default_user_avatar', + self::DEFAULT_COMPANY_AVATAR => 'default_company_avatar', + self::INACTIVE_ACCOUNT_ERROR_MESSAGE => 'inactive_account_error_message', + self::INACTIVE_COMPANY_ACCOUNT_ERROR_MESSAGE => 'inactive_company_account_error_message', + self::RESET_PASSWORD_EMAIL_SUBJECT => 'reset_password_email_subject', }; } } diff --git a/src/Kanvas/Filesystem/Repositories/FilesystemEntitiesRepository.php b/src/Kanvas/Filesystem/Repositories/FilesystemEntitiesRepository.php index 9590e2a7b..9a4293164 100644 --- a/src/Kanvas/Filesystem/Repositories/FilesystemEntitiesRepository.php +++ b/src/Kanvas/Filesystem/Repositories/FilesystemEntitiesRepository.php @@ -94,6 +94,25 @@ public static function getFileFromEntityByName(Model $entity, string $name): ?Fi ->first(); } + public static function getFileFromEntityById(int $id): ?FilesystemEntities + { + return FilesystemEntities::join('filesystem', 'filesystem.id', '=', 'filesystem_entities.filesystem_id') + ->where('filesystem_entities.id', '=', $id) + ->where('filesystem_entities.is_deleted', '=', StateEnums::NO->getValue()) + ->where('filesystem.is_deleted', '=', StateEnums::NO->getValue()) + ->select( + 'filesystem_entities.*', + 'filesystem.url', + 'filesystem.path', + 'filesystem.name', + 'filesystem.apps_id', + 'filesystem.users_id', + 'filesystem.size', + 'filesystem.file_type' + ) + ->first(); + } + /** * Given the entity delete all related files. * @psalm-suppress MixedReturnStatement diff --git a/src/Kanvas/MappersImportersTemplates/Actions/CreateMapperImporterTemplateAction.php b/src/Kanvas/MappersImportersTemplates/Actions/CreateMapperImporterTemplateAction.php new file mode 100644 index 000000000..69160d4d3 --- /dev/null +++ b/src/Kanvas/MappersImportersTemplates/Actions/CreateMapperImporterTemplateAction.php @@ -0,0 +1,51 @@ + $this->data->apps->getId(), + 'users_id' => $this->data->users->getId(), + 'companies_id' => $this->data->companies->getId(), + 'name' => $this->data->name, + 'description' => $this->data->description + ]); + $this->createAttributes($importersTemplates, $this->data->attributes); + return $importersTemplates; + } + + protected function createAttributes(MapperImporterTemplate $importersTemplates, array $attributes): void + { + foreach ($attributes as $attribute) { + $root = new AttributeMapperImporterTemplate(); + $root->importers_templates_id = $importersTemplates->id; + $root->name = $attribute['name']; + $root->mapping_field = $attribute['mapping_field']; + $root->save(); + + if (isset($attribute['children'])) { + foreach ($attribute['children'] as $child) { + $childModel = new AttributeMapperImporterTemplate(); + $childModel->importers_templates_id = $importersTemplates->id; + $childModel->name = $child['name']; + $childModel->mapping_field = $child['mapping_field']; + $childModel->parent()->associate($root); + $childModel->save(); + } + } + } + } +} diff --git a/src/Kanvas/MappersImportersTemplates/DataTransferObject/MapperImporterTemplate.php b/src/Kanvas/MappersImportersTemplates/DataTransferObject/MapperImporterTemplate.php new file mode 100644 index 000000000..a78df4faf --- /dev/null +++ b/src/Kanvas/MappersImportersTemplates/DataTransferObject/MapperImporterTemplate.php @@ -0,0 +1,23 @@ +belongsTo(self::class, 'parent_id'); + } + + public function children(): HasMany + { + return $this->hasMany(self::class, 'parent_id'); + } + + public function importersTemplates(): BelongsTo + { + return $this->belongsTo(MapperImporterTemplate::class, 'importers_templates_id'); + } +} diff --git a/src/Kanvas/MappersImportersTemplates/Models/MapperImporterTemplate.php b/src/Kanvas/MappersImportersTemplates/Models/MapperImporterTemplate.php new file mode 100644 index 000000000..0a386d0d4 --- /dev/null +++ b/src/Kanvas/MappersImportersTemplates/Models/MapperImporterTemplate.php @@ -0,0 +1,29 @@ +hasMany(AttributeMapperImporterTemplate::class, 'importers_templates_id')->whereNull("parent_id"); + } +} diff --git a/src/Kanvas/Users/Models/Users.php b/src/Kanvas/Users/Models/Users.php index 186d170b2..ca734613a 100644 --- a/src/Kanvas/Users/Models/Users.php +++ b/src/Kanvas/Users/Models/Users.php @@ -35,11 +35,13 @@ use Kanvas\Companies\Models\Companies; use Kanvas\Companies\Models\CompaniesBranches; use Kanvas\Enums\AppEnums; +use Kanvas\Enums\AppSettingsEnums; use Kanvas\Enums\StateEnums; use Kanvas\Exceptions\InternalServerErrorException; use Kanvas\Exceptions\ModelNotFoundException; use Kanvas\Exceptions\ModelNotFoundException as ExceptionsModelNotFoundException; use Kanvas\Filesystem\Models\FilesystemEntities; +use Kanvas\Filesystem\Repositories\FilesystemEntitiesRepository; use Kanvas\Filesystem\Traits\HasFilesystemTrait; use Kanvas\Locations\Models\Cities; use Kanvas\Locations\Models\Countries; @@ -718,7 +720,10 @@ public function runVerifyTwoFactorAuth(?AppInterface $app = null): bool public function getPhoto(): ?FilesystemEntities { - return $this->getFileByName('photo'); + $app = app(Apps::class); + $defaultAvatarId = $app->get(AppSettingsEnums::DEFAULT_USER_AVATAR->getValue()); + + return $this->getFileByName('photo') ?: ($defaultAvatarId ? FilesystemEntitiesRepository::getFileFromEntityById($defaultAvatarId) : null); } public function getSocialInfo(): array diff --git a/tests/GraphQL/Ecosystem/Mapper/MapperImporterTemplateTest.php b/tests/GraphQL/Ecosystem/Mapper/MapperImporterTemplateTest.php new file mode 100644 index 000000000..0897bbf1a --- /dev/null +++ b/tests/GraphQL/Ecosystem/Mapper/MapperImporterTemplateTest.php @@ -0,0 +1,79 @@ + fake()->name, + 'description' => fake()->sentence, + 'attributes' => [ + [ + 'name' => fake()->name, + 'mapping_field' => fake()->sentence, + 'children' => [ + [ + 'name' => fake()->name, + 'mapping_field' => fake()->sentence, + ], + ], + ], + ], + ]; + $response = $this->graphQL(/** @lang GraphQL */ ' + mutation( + $input: ImporterTemplateInput! + ){ + createMapperImporterTemplate(input: $input) { + name, + description, + attributes { + name, + mapping_field , + } + } + } + ', + [ + 'input' => $mapperImporterTemplate, + ], + ); + $response->assertJson([ + 'data' => [ + 'createMapperImporterTemplate' => [ + 'name' => $mapperImporterTemplate['name'], + 'description' => $mapperImporterTemplate['description'], + 'attributes' => [ + [ + 'name' => $mapperImporterTemplate['attributes'][0]['name'], + 'mapping_field' => $mapperImporterTemplate['attributes'][0]['mapping_field'], + ], + ], + ], + ], + ]); + + $response->assertJsonStructure([ + 'data' => [ + 'createMapperImporterTemplate' => [ + 'name', + 'attributes' => [ + [ + 'name', + 'mapping_field', + ], + ], + ], + ], + ]); + } +}