Skip to content

Commit

Permalink
Partial POC for MP multi-file uploader to S3
Browse files Browse the repository at this point in the history
  • Loading branch information
mantasmuliar committed Aug 28, 2024
1 parent 5da6831 commit f9193e6
Show file tree
Hide file tree
Showing 95 changed files with 4,239 additions and 4 deletions.
29 changes: 29 additions & 0 deletions config/Shared/config_default.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
use Generated\Shared\Transfer\SearchEndpointRemovedTransfer;
use Generated\Shared\Transfer\SubmitPaymentTaxInvoiceTransfer;
use Monolog\Logger;
use Pyz\Shared\AwsS3\AwsS3Config;
use Pyz\Shared\AwsS3\AwsS3Constants;
use Pyz\Shared\Console\ConsoleConstants;
use Pyz\Shared\Scheduler\SchedulerConfig;
use Pyz\Yves\ShopApplication\YvesBootstrap;
Expand Down Expand Up @@ -927,3 +929,30 @@
$config[PushNotificationWebPushPhpConstants::VAPID_PUBLIC_KEY] = getenv('SPRYKER_PUSH_NOTIFICATION_WEB_PUSH_PHP_VAPID_PUBLIC_KEY');
$config[PushNotificationWebPushPhpConstants::VAPID_PRIVATE_KEY] = getenv('SPRYKER_PUSH_NOTIFICATION_WEB_PUSH_PHP_VAPID_PRIVATE_KEY');
$config[PushNotificationWebPushPhpConstants::VAPID_SUBJECT] = getenv('SPRYKER_PUSH_NOTIFICATION_WEB_PUSH_PHP_VAPID_SUBJECT');

// ----------------------------------------------------------------------------
// ---------------------------- S3 Bucket -------------------------------------
// ----------------------------------------------------------------------------
$config[AwsS3Constants::BUCKETS_CONFIGURATION_MAP] = [
AwsS3Config::OBJECT_TYPE_CVS_IMPORT => [
'region' => getenv('AWS_REGION'),
'Bucket' => getenv('DATA_IMPORT_S3_BUCKET'),
'version' => 'latest',
'credentials' => [
'key' => getenv('DATA_IMPORT_S3_KEY'),
'secret' => getenv('DATA_IMPORT_S3_SECRET'),
],
AwsS3Config::CONFIG_PRE_SIGNED_URL_EXPIRATION => '30 minutes',
],
AwsS3Config::OBJECT_TYPE_ADDITIONAL_MEDIA => [
'region' => getenv('AWS_REGION'),
'Bucket' => getenv('ADDITIONAL_MEDIA_S3_BUCKET'),
'version' => 'latest',
'credentials' => [
'key' => getenv('ADDITIONAL_MEDIA_S3_KEY'),
'secret' => getenv('ADDITIONAL_MEDIA_S3_SECRET'),
],
AwsS3Config::CONFIG_PRE_SIGNED_URL_EXPIRATION => '15 minutes',
AwsS3Config::CONFIG_CDN_HOST => getenv('ADDITIONAL_MEDIA_CDN_URL'),
],
];
8 changes: 8 additions & 0 deletions config/Zed/navigation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -672,4 +672,12 @@
</onboarding>
</pages>
</merchant-portal-payment-settings>
<file-upload-merchant-portal-gui>
<label>Uploaded Files</label>
<title>Uploaded Files</title>
<icon>files</icon>
<bundle>file-upload-merchant-portal-gui</bundle>
<controller>list</controller>
<action>index</action>
</file-upload-merchant-portal-gui>
</config>
21 changes: 18 additions & 3 deletions frontend/merchant-portal/update-config-paths.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');
const { SPRYKER_CORE_DIR, MP_CORE_ENTRY_POINT_FILE, MP_PUBLIC_API_FILE, ROOT_DIR } = require('./mp-paths');
const { getMPEntryPoints, entryPointPathToName } = require('./utils');
const {
SPRYKER_PROJECT_DIR,
SPRYKER_CORE_DIR,
MP_CORE_ENTRY_POINT_FILE,
MP_PUBLIC_API_FILE,
ROOT_DIR,
} = require('./mp-paths');const { getMPEntryPoints, entryPointPathToName } = require('./utils');

const TSCONFIG_FILES = ['tsconfig.mp.json'];

function toPascalCase(str) {
return str
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
}

async function getMPPathsMap() {
const entryPoints = await getMPEntryPoints(SPRYKER_CORE_DIR, MP_CORE_ENTRY_POINT_FILE);

return entryPoints.sort().reduce(
(acc, entryPoint) => ({
...acc,
[`${entryPointPathToName('@mp/', entryPoint)}/project`]: [
path.join(SPRYKER_PROJECT_DIR, toPascalCase(entryPoint.split('/src')[0]), MP_PUBLIC_API_FILE),
],
[entryPointPathToName('@mp/', entryPoint)]: [
path.join(SPRYKER_CORE_DIR, entryPoint.split('/src')[0], MP_PUBLIC_API_FILE),
],
Expand All @@ -28,8 +43,8 @@ async function updateConfigPaths() {
const config = require(path.join(ROOT_DIR, fileName));

config.compilerOptions.paths = {
...mpPaths,
...config.compilerOptions.paths,
...mpPaths,
};

fs.writeFileSync(fileName, JSON.stringify(config));
Expand Down
3 changes: 3 additions & 0 deletions project.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
"vendor/spryker/zed-ui/src/Spryker/Zed/ZedUi/Presentation/Components/styles.less",
"src/Pyz/Zed/ZedUi/Presentation/Components/styles.less"
],
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"scripts": []
},
"configurations": {
Expand Down
37 changes: 37 additions & 0 deletions src/Pyz/Service/AwsS3/AwsS3Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* This file is part of the Spryker Commerce OS.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Pyz\Service\AwsS3;

use Pyz\Shared\AwsS3\AwsS3Constants;
use Spryker\Service\Kernel\AbstractBundleConfig;

class AwsS3Config extends AbstractBundleConfig
{
private const AWS_S3_URL_PATTERN = 's3://%s/%s';

private const CDN_URL_PATTERN = '{schema}://{cdnHost}/{fileName}';

public function getAwsS3UrlPattern(): string
{
return self::AWS_S3_URL_PATTERN;
}

public function getCdnUrlPattern(): string
{
return self::CDN_URL_PATTERN;
}

public function getClientConfigurationForObjectType(string $objectType): array
{
$bucketsConfigurationMap = $this->get(AwsS3Constants::BUCKETS_CONFIGURATION_MAP, []);

return $bucketsConfigurationMap[$objectType] ?? [];
}
}
41 changes: 41 additions & 0 deletions src/Pyz/Service/AwsS3/AwsS3DependencyProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/**
* This file is part of the Spryker Commerce OS.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Pyz\Service\AwsS3;

use Pyz\Service\AwsS3\Dependency\S3ClientProviderPluginInterface;
use Pyz\Service\AwsS3\Plugin\AwsS3\S3ClientProviderPlugin;
use Spryker\Service\Kernel\AbstractBundleDependencyProvider;
use Spryker\Service\Kernel\Container;

/**
* @method \Pyz\Service\AwsS3\AwsS3Config getConfig()
*/
class AwsS3DependencyProvider extends AbstractBundleDependencyProvider
{
public const PLUGIN_S3_CLIENT_PROVIDER = 'PLUGIN_S3_CLIENT_PROVIDER_PLUGIN';

public function provideServiceDependencies(Container $container): Container
{
$container = parent::provideServiceDependencies($container);
$container = $this->addS3ClientProviderPlugin($container);

return $container;
}

private function addS3ClientProviderPlugin(Container $container): Container
{
$container->set(
static::PLUGIN_S3_CLIENT_PROVIDER,
static function (): S3ClientProviderPluginInterface {
return new S3ClientProviderPlugin();
},
);

return $container;
}
}
57 changes: 57 additions & 0 deletions src/Pyz/Service/AwsS3/AwsS3Service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/**
* This file is part of the Spryker Commerce OS.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Pyz\Service\AwsS3;

use Pyz\Shared\AwsS3\AwsS3Config as SharedAwsS3Config;
use Spryker\Service\Kernel\AbstractService;

/**
* @method \Pyz\Service\AwsS3\AwsS3ServiceFactory getFactory()
*/
class AwsS3Service extends AbstractService implements AwsS3ServiceInterface
{
public function getPresignedUrl(string $fileName, string $objectType): string
{
return $this->getFactory()
->createUrlCreator($objectType)
->getPresignedUrl($fileName);
}

public function getS3ObjectUrl(string $fileName, string $objectType): string
{
return $this->getFactory()
->createAwsS3ObjectReader($objectType)
->getS3ObjectUrl($fileName);
}

/**
* @param string $objectType
*
* @return void
*/
public function registerStreamWrapper(string $objectType = SharedAwsS3Config::OBJECT_TYPE_CVS_IMPORT): void
{
$this->getFactory()
->createUtilAws($objectType)
->registerStreamWrapper();
}

public function getCdnUrl(string $fileName, string $objectType): string
{
return $this->getFactory()
->createUrlCreator($objectType)
->getCdnUrl($fileName);
}

public function deleteS3Object(string $objectType, string $path): void
{
$this->getFactory()
->createAwsS3ObjectDeleter($objectType)
->deleteS3Object($path);
}
}
71 changes: 71 additions & 0 deletions src/Pyz/Service/AwsS3/AwsS3ServiceFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/**
* This file is part of the Spryker Commerce OS.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Pyz\Service\AwsS3;

use Aws\S3\S3Client;
use Aws\S3\S3ClientInterface;
use Pyz\Service\AwsS3\Creator\UrlCreator;
use Pyz\Service\AwsS3\Creator\UrlCreatorInterface;
use Pyz\Service\AwsS3\Deleter\AwsS3ObjectDeleter;
use Pyz\Service\AwsS3\Deleter\AwsS3ObjectDeleterInterface;
use Pyz\Service\AwsS3\Dependency\S3ClientProviderPluginInterface;
use Pyz\Service\AwsS3\Reader\AwsS3ObjectReader;
use Pyz\Service\AwsS3\Reader\AwsS3ObjectReaderInterface;
use Pyz\Service\AwsS3\Util\UtilAws;
use Pyz\Service\AwsS3\Util\UtilAwsInterface;
use Spryker\Service\Kernel\AbstractServiceFactory;

/**
* @method \Pyz\Service\AwsS3\AwsS3Config getConfig()
*/
class AwsS3ServiceFactory extends AbstractServiceFactory
{
public function createUrlCreator(string $objectType): UrlCreatorInterface
{
$s3Client = $this
->getS3ClientProviderPlugin()
->provideConfiguredClientForObjectType($objectType);

return new UrlCreator(
$this->getConfig(),
$s3Client,
$objectType,
);
}

public function createAwsS3ObjectReader(string $objectType): AwsS3ObjectReaderInterface
{
return new AwsS3ObjectReader($this->getConfig(), $objectType);
}

public function createAwsS3ObjectDeleter(string $objectType): AwsS3ObjectDeleterInterface
{
return new AwsS3ObjectDeleter(
$this->createUtilAws($objectType),
);
}

public function createUtilAws(string $objectType): UtilAwsInterface
{
$s3Client = $this
->getS3ClientProviderPlugin()
->provideConfiguredClientForObjectType($objectType);

return new UtilAws($s3Client);
}

public function createS3Client(array $configuration): S3ClientInterface
{
return new S3Client($configuration);
}

public function getS3ClientProviderPlugin(): S3ClientProviderPluginInterface
{
return $this->getProvidedDependency(AwsS3DependencyProvider::PLUGIN_S3_CLIENT_PROVIDER);
}
}
75 changes: 75 additions & 0 deletions src/Pyz/Service/AwsS3/AwsS3ServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/**
* This file is part of the Spryker Commerce OS.
* For full license information, please view the LICENSE file that was distributed with this source code.
*/

namespace Pyz\Service\AwsS3;

interface AwsS3ServiceInterface
{
/**
* Specification:
* - Returns the presigned URL for the given file name.
*
* @api
*
* @param string $fileName
* @param string $objectType
*
* @return string
*/
public function getPresignedUrl(string $fileName, string $objectType): string;

/**
* Specification:
* - Returns the S3 object URL for the given file name.
*
* @api
*
* @param string $fileName
* @param string $objectType
*
* @return string
*/
public function getS3ObjectUrl(string $fileName, string $objectType): string;

/**
* Specification:
* - Deletes the S3 object for the given path.
*
* @api
*
* @param string $objectType
* @param string $path
*
* @return void
*/
public function deleteS3Object(string $objectType, string $path): void;

/**
* Specification:
* - Registers the stream wrapper for the S3 client.
*
* @api
*
* @param string $objectType
*
* @return void
*/
public function registerStreamWrapper(string $objectType): void;

/**
* Specification:
* - Returns the CDN URL for the given file name and object type.
*
* @api
*
* @param string $fileName
* @param string $objectType
*
* @return string
*/
public function getCdnUrl(string $fileName, string $objectType): string;
}
Loading

0 comments on commit f9193e6

Please sign in to comment.