Commit c41d3271 authored by Emma's avatar Emma 🏳🌈

support S3 for storing of submission images/thumbs

parent ec43b620
Pipeline #59239476 passed with stage
in 1 minute and 36 seconds
......@@ -19,6 +19,23 @@ APP_ENABLE_WEBHOOKS=0
# No whitelisting by default
RATELIMIT_WHITELIST=
# Configure how uploads (i.e. downloaded submission images/thumbnails) are
# handled. By default, they're stored in Postmill's public/ directory, and are
# served from its web root.
UPLOAD_DSN="file://%kernel.project_dir%/public"
UPLOAD_ROOT="%router.request_context.base_url%"
UPLOAD_HOST=
# Example using AWS S3 for storing uploads
#UPLOAD_DSN="s3://your-key:your-secret@your-region/bucket-name"
#UPLOAD_ROOT=https://bucket-name.s3.amazonaws.com/
#UPLOAD_HOST=bucket-name.s3.amazonaws.com
# Example using a Minio instance for storing uploads
#UPLOAD_DSN="s3://your-key:your-secret@foo/bucket-name?endpoint=https://minio.example:9000&bucket_endpoint=0&use_path_style_endpoint=1"
#UPLOAD_ROOT=https://minio.example/bucket-name
#UPLOAD_HOST=minio.example
APP_FONTS=%kernel.project_dir%/assets/fonts.json
APP_THEMES=%kernel.project_dir%/assets/themes.json
......
This diff is collapsed.
......@@ -7,7 +7,6 @@ return [
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['test' => true],
FOS\JsRoutingBundle\FOSJsRoutingBundle::class => ['all' => true],
Liip\ImagineBundle\LiipImagineBundle::class => ['all' => true],
Oneup\FlysystemBundle\OneupFlysystemBundle::class => ['all' => true],
Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
......
# todo: use flysystem for thumbnails too
liip_imagine:
loaders:
submission_images:
flysystem:
filesystem_service: oneup_flysystem.submission_images_filesystem
# fixme: we change the root here because things break otherwise
# this loader is unused
default:
filesystem:
data_root: "%kernel.project_dir%/public"
services:
postmill.submission_images:
class: League\Flysystem\FilesystemInterface
factory: [App\Flysystem\DsnAwareFilesystemFactory, createFilesystem]
arguments:
$dsn: "%env(resolve:UPLOAD_DSN)%"
$options: { prefix: submission_images/, visibility: public }
filter_sets:
# deprecated: legacy filter
submission_thumbnail:
data_loader: submission_images
filters:
thumbnail: { size: [200, 200], mode: outbound }
quality: 70
postmill.submission_thumbnails:
class: League\Flysystem\FilesystemInterface
factory: [App\Flysystem\DsnAwareFilesystemFactory, createFilesystem]
arguments:
$dsn: "%env(resolve:UPLOAD_DSN)%"
# https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html#create-thumbnails
liip_imagine:
filter_sets:
submission_thumbnail_1x:
data_loader: submission_images
filters:
auto_rotate: ~
strip: ~
thumbnail: { size: [70, 70], mode: outbound, allow_upscale: true }
quality: 60
submission_thumbnail_2x:
data_loader: submission_images
filters:
auto_rotate: ~
strip: ~
thumbnail: { size: [140, 140], mode: outbound, allow_upscale: true }
quality: 60
resolvers:
loaders:
default:
web_path:
web_root: "%kernel.project_dir%/public"
flysystem:
filesystem_service: postmill.submission_images
#liip_imagine:
# # valid drivers options include "gd" or "gmagick" or "imagick"
# driver: "gd"
#
# # define your filter sets under this option
# filter_sets:
#
# # an example thumbnail transformation definition
# # https://symfony.com/doc/current/bundles/LiipImagineBundle/basic-usage.html#create-thumbnails
# squared_thumbnail:
#
# # set your image quality defaults
# jpeg_quality: 85
# png_compression_level: 8
#
# # setup the filter steps to apply for this transformation
# filters:
#
# # auto rotate the image using EXIF metadata
# auto_rotate: ~
#
# # strip the image of all metadata
# strip: ~
#
# # scale and square the image to the given dimensions
# thumbnail:
# size: [253, 253]
# mode: outbound
# allow_upscale: true
#
# # create border by placing image on larger black background
# background:
# size: [256, 256]
# position: center
# color: '#fff'
resolvers:
default:
flysystem:
filesystem_service: postmill.submission_thumbnails
root_url: "%env(resolve:UPLOAD_ROOT)%"
cache_prefix: media/cache
visibility: public
......@@ -23,6 +23,7 @@ nelmio_security:
img-src:
- self
- 'data:'
- "%env(resolve:UPLOAD_HOST)%"
external_redirects:
abort: true
oneup_flysystem:
adapters:
submission_images:
local:
directory: "%kernel.project_dir%/public/submission_images"
filesystems:
submission_images: { adapter: submission_images }
......@@ -26,6 +26,7 @@ services:
$themesConfig: "%themes_config%"
$secret: "%env(APP_SECRET)%"
$siteName: "%env(SITE_NAME)%"
$uploadRoot: "%env(resolve:UPLOAD_ROOT)%"
public: false
# makes classes in src/ available to be used as services
......@@ -59,7 +60,7 @@ services:
App\EventListener\SubmissionImageListener:
$client: "@eight_points_guzzle.client.submission_image_client"
$filesystem: "@oneup_flysystem.submission_images_filesystem"
$filesystem: "@postmill.submission_images"
App\EventListener\WebhookListener:
$client: "@eight_points_guzzle.client.webhook_client"
......
version: '3'
volumes:
minio_data:
driver: local
postgres_data:
driver: local
......@@ -24,6 +26,7 @@ services:
- DATABASE_URL=pgsql://postgres@db/postgres?serverVersion=11
links:
- db
- s3
volumes:
- ./.git:/app/.git:ro
- ./:/app
......@@ -33,3 +36,13 @@ services:
volumes:
- ./.git:/app/.git:ro
- ./:/app:rw
s3:
command: server /data/minio
image: minio/minio
ports:
- 9000:9000
volumes:
- minio_data:/data
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: minio123
<?php
namespace App\Flysystem;
use Aws\S3\S3Client;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Adapter\Local;
use League\Flysystem\AwsS3v3\AwsS3Adapter;
use League\Flysystem\Filesystem;
final class DsnAwareFilesystemFactory {
public static function createFilesystem(string $dsn, array $options = []): Filesystem {
$parts = \parse_url($dsn);
$parts += [
'scheme' => null,
'user' => null,
'pass' => null,
'path' => null,
'query' => null,
];
\parse_str($parts['query'], $query);
switch ($parts['scheme']) {
case 'local':
case 'file':
$path = \rtrim($parts['path'], '/');
if (!empty($options['prefix'])) {
$path .= '/'.$options['prefix'];
}
$adapter = new Local($path);
break;
case 's3':
$client = new S3Client([
'credentials' => [
'key' => $parts['user'],
'secret' => $parts['pass'],
],
'endpoint' => $query['endpoint'] ?? null,
'region' => $parts['host'],
'version' => $query['version'] ?? 'latest',
'bucket_endpoint' => (bool) ($query['bucket_endpoint'] ?? true),
'use_path_style_endpoint' => (bool) ($query['use_path_style_endpoint'] ?? false),
]);
$bucket = \ltrim($parts['path'], '/');
$prefix = $options['prefix'] ?? '';
$adapter = new AwsS3Adapter($client, $bucket, $prefix, []);
break;
default:
throw new \InvalidArgumentException(
"Unknown filesystem '{$parts['scheme']}'"
);
}
return new Filesystem($adapter, $options);
}
}
......@@ -2,6 +2,7 @@
namespace App\Twig;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
......@@ -10,6 +11,11 @@ use Twig\TwigFunction;
* functions.
*/
final class AppExtension extends AbstractExtension {
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var string
*/
......@@ -37,20 +43,29 @@ final class AppExtension extends AbstractExtension {
private $themesConfig;
/**
* @var string
*/
private $uploadRoot;
public function __construct(
RequestStack $requestStack,
string $siteName,
?string $branch,
?string $version,
bool $enableWebhooks,
array $fontsConfig,
array $themesConfig
array $themesConfig,
string $uploadRoot
) {
$this->requestStack = $requestStack;
$this->siteName = $siteName;
$this->branch = $branch;
$this->version = $version;
$this->enableWebhooks = $enableWebhooks;
$this->fontsConfig = $fontsConfig;
$this->themesConfig = $themesConfig;
$this->uploadRoot = $uploadRoot;
}
public function getFunctions(): array {
......@@ -92,6 +107,17 @@ final class AppExtension extends AbstractExtension {
return $config['entrypoint'][$nightMode ? 'night' : 'day'] ?? $config['entrypoint'];
}),
new TwigFunction('upload_url', function (string $path) {
$path = \rtrim($this->uploadRoot, '/').'/'.$path;
if (\strpos($path, '//') === false) {
$request = $this->requestStack->getCurrentRequest();
$path = $request->getSchemeAndHttpHost().$path;
}
return $path;
}),
];
}
}
{
"aws/aws-sdk-php": {
"version": "3.92.5"
},
"composer/ca-bundle": {
"version": "1.1.0"
},
......@@ -164,6 +167,9 @@
"league/flysystem": {
"version": "1.0.41"
},
"league/flysystem-aws-s3-v3": {
"version": "1.0.22"
},
"liip/imagine-bundle": {
"version": "1.8",
"recipe": {
......@@ -176,6 +182,9 @@
"monolog/monolog": {
"version": "1.23.0"
},
"mtdowling/jmespath.php": {
"version": "2.4.0"
},
"nelmio/security-bundle": {
"version": "2.4",
"recipe": {
......@@ -194,9 +203,6 @@
"ocramius/proxy-manager": {
"version": "2.1.1"
},
"oneup/flysystem-bundle": {
"version": "3.0.0"
},
"pagerfanta/pagerfanta": {
"version": "v1.0.5"
},
......
......@@ -10,7 +10,7 @@
<meta property="og:title" content="{{ submission.title }}">
<meta property="og:type" content="article">
<meta property="og:url" content="{{ url('submission', {forum_name: submission.forum.name, submission_id: submission.id, slug: submission.title|slugify}) }}">
<meta property="og:image" content="{{ absolute_url(asset(submission.image ? 'submission_images/%s'|format(submission.image) : 'apple-touch-icon-precomposed.png')) }}">
<meta property="og:image" content="{{ submission.image ? upload_url('submission_images/'~submission.image) : absolute_url(asset('apple-touch-icon-precomposed.png')) }}">
<meta property="og:article:published_time" content="{{ submission.timestamp|date('c') }}">
<meta property="og:article:author" content="{{ submission.user.username }}">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment