Skip to content

Commit

Permalink
Unsplash trigger download Job
Browse files Browse the repository at this point in the history
  • Loading branch information
lao9s committed Dec 2, 2023
1 parent c772110 commit d457a61
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 39 deletions.
2 changes: 1 addition & 1 deletion config/mixpost.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
* The media component is integrated with third-party services Unsplash.com and Tenor.com
* Defines the default terms for displaying media resources
*/
'external_media_terms' => ['young', 'social', 'mix', 'content', 'viral', 'trend', 'test', 'light', 'true', 'false', 'marketing', 'self-hosted', 'ambient', 'writer', 'technology'],
'external_media_terms' => ['social', 'mix', 'content', 'popular', 'viral', 'trend', 'light', 'marketing', 'self-hosted', 'ambient', 'writer', 'technology'],

/*
* Options for each social network
Expand Down
5 changes: 4 additions & 1 deletion resources/js/Components/Media/AddMedia.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ const insert = () => {
if (toDownload) {
// Download external media files
downloadExternal(selectedItems.value, (response) => {
downloadExternal(selectedItems.value.map((item) => {
const {id, url, download_data} = item;
return {id, url, download_data};
}), (response) => {
emit('insert', response.data);
close();
})
Expand Down
3 changes: 2 additions & 1 deletion resources/js/Composables/useMedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ const useMedia = (routeName = 'mixpost.media.fetchUploads', routeParams = {}) =>
NProgress.start();

axios.post(route('mixpost.media.download', routeParams), {
items
items,
from: activeTab.value,
}).then((response) => {
callback(response);
}).catch(() => {
Expand Down
5 changes: 4 additions & 1 deletion resources/js/Pages/Media.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ const use = () => {
const toDownload = activeTab.value !== 'uploads';
if (toDownload) {
downloadExternal(selectedItems.value, (response) => {
downloadExternal(selectedItems.value.map((item) => {
const {id, url, download_data} = item;
return {id, url, download_data};
}), (response) => {
createPost(response.data);
});
}
Expand Down
1 change: 1 addition & 0 deletions src/Http/Controllers/MediaFetchGifsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function __invoke(Request $request): AnonymousResourceCollection
]);

$media->setAttribute('id', $item['id']);
$media->setAttribute('download_data', 'false');

return $media;
});
Expand Down
25 changes: 7 additions & 18 deletions src/Http/Controllers/MediaFetchStockController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,19 @@
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Routing\Controller;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Inovector\Mixpost\Facades\Services;
use Inovector\Mixpost\Http\Resources\MediaResource;
use Inovector\Mixpost\Integrations\Unsplash\Unsplash;
use Inovector\Mixpost\Models\Media;
use Symfony\Component\HttpFoundation\Response;

class MediaFetchStockController extends Controller
{
public function __invoke(Request $request): AnonymousResourceCollection
{
$clientId = Services::get('unsplash', 'client_id');
$unsplash = new Unsplash();

if (!$clientId) {
abort(Response::HTTP_FORBIDDEN);
}
$items = $unsplash->photos($request->query('keyword', ''), $request->query('page', 1));

$terms = config('mixpost.external_media_terms');

$items = Http::get("https://api.unsplash.com/search/photos", [
'client_id' => $clientId,
'query' => $request->query('keyword', Arr::random($terms)),
'page' => $request->query('page', 1),
'per_page' => 30,
]);

$media = collect($items->json('results', []))->map(function ($item) {
$media = collect($items)->map(function ($item) {
$media = new Media([
'name' => $item['user']['name'],
'mime_type' => 'image/jpeg',
Expand All @@ -48,6 +34,9 @@ public function __invoke(Request $request): AnonymousResourceCollection

$media->setAttribute('id', $item['id']);
$media->setAttribute('credit_url', $item['user']['links']['html']);
$media->setAttribute('download_data', [
'download_location' => $item['links']['download_location']
]);

return $media;
});
Expand Down
55 changes: 45 additions & 10 deletions src/Http/Requests/MediaDownloadExternal.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Inovector\Mixpost\Integrations\Unsplash\Jobs\TriggerDownloadJob;
use Inovector\Mixpost\MediaConversions\MediaImageResizeConversion;
use Inovector\Mixpost\Support\File;
use Illuminate\Support\Facades\Http;
Expand All @@ -15,36 +17,69 @@ class MediaDownloadExternal extends FormRequest
public function rules(): array
{
return [
'from' => ['required', 'string', 'in:stock,gifs'],
'items' => [
'required',
'array',
function ($attribute, $value, $fail) {
$containsNonPublicDomain = collect($value)->some(function ($item) {
return !Util::isPublicDomainUrl($item['url']);
});
foreach ($value as $item) {
$validKeys = ['id', 'url', 'download_data'];

if ($containsNonPublicDomain) {
$fail('The ' . $attribute . ' contains non-public domain URLs.');
$extraKeys = array_diff(array_keys($item), $validKeys);

if (!empty($extraKeys)) {
$fail('The ' . $attribute . ' item contains invalid keys: ' . implode(', ', $extraKeys));
break;
}

foreach ($validKeys as $key) {
if (empty($item[$key])) {
$fail('The ' . $attribute . ' item must have a non-empty "' . $key . '" key.');
break 2;
}
}

if (!Util::isPublicDomainUrl($item['url'])) {
$fail('The ' . $attribute . ' contains non-public domain URLs.');
}
}
},
]
],
];
}

public function handle(): Collection
{
return collect($this->input('items'))->filter(function ($item) {
return Util::isPublicDomainUrl($item['url']);
})->map(function ($item) {
return collect($this->input('items'))->map(function ($item) {
$result = Http::get($item['url']);

$now = now()->format('m-Y');

$file = File::fromBase64(base64_encode($result->body()));

return MediaUploader::fromFile($file)->path("mixpost/$now")->conversions([
$media = MediaUploader::fromFile($file)->path("mixpost/$now")->conversions([
MediaImageResizeConversion::name('thumb')->width(430),
])->uploadAndInsert();

$method = 'downloadAction' . Str::studly($this->input('from'));

$this->$method($item);

return $media;
});
}

protected function downloadActionStock(array $item): void
{
if (empty($item['download_data']['download_location'])) {
return;
}

TriggerDownloadJob::dispatch($item['download_data']['download_location']);
}

protected function downloadActionGifs(array $item): void
{

}
}
3 changes: 2 additions & 1 deletion src/Http/Resources/MediaResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ public function toArray($request)
'url' => $this->getUrl(),
'thumb_url' => $this->isImageGif() ? $this->getUrl() : $this->getThumbUrl(),
'is_video' => $this->isVideo(),
'credit_url' => $this->credit_url ?? null
'credit_url' => $this->credit_url ?? null,
'download_data' => $this->download_data ?? null,
];
}
}
27 changes: 27 additions & 0 deletions src/Integrations/Unsplash/Jobs/TriggerDownloadJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Inovector\Mixpost\Integrations\Unsplash\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Inovector\Mixpost\Integrations\Unsplash\Unsplash;

class TriggerDownloadJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;

public function __construct(public readonly string $downloadLocation)
{
}

public function handle(Unsplash $unsplash): void
{
$unsplash->downloadPhoto($this->downloadLocation);
}
}
45 changes: 45 additions & 0 deletions src/Integrations/Unsplash/Unsplash.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Inovector\Mixpost\Integrations\Unsplash;

use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Inovector\Mixpost\Facades\Services;
use Inovector\Mixpost\Util;

class Unsplash
{
protected string $clientId;
protected string $endpointUrl = 'https://api.unsplash.com';

public function __construct()
{
$clientId = Services::get('unsplash', 'client_id');

if (!$clientId) {
throw new \Exception('Unsplash is not configured.');
}

$this->clientId = $clientId;
}

public function photos(string $query = '', int $page = 1): array
{
return Http::get("$this->endpointUrl/search/photos", [
'client_id' => $this->clientId,
'query' => $query ?: Arr::random(Util::config('external_media_terms')),
'page' => $page,
'per_page' => 30,
])->json('results', []);
}

public function downloadPhoto(string $downloadLocation)
{
$download_path = parse_url($downloadLocation, PHP_URL_PATH);
$download_query = parse_url($downloadLocation, PHP_URL_QUERY);

return Http::get("$this->endpointUrl$download_path?$download_query", [
'client_id' => $this->clientId,
])->json();
}
}
6 changes: 6 additions & 0 deletions src/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
use DateTimeZone;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Config;
use Inovector\Mixpost\Facades\Settings;

class Util
{
public static function config(string $key, mixed $default = null)
{
return Config::get("mixpost.$key", $default);
}

public static function isMixpostRequest(Request $request): bool
{
$path = 'mixpost';
Expand Down
36 changes: 30 additions & 6 deletions tests/Http/Controllers/MediaDownloadExternalControllerTest.php
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Queue;
use Inovector\Mixpost\Integrations\Unsplash\Jobs\TriggerDownloadJob;
use Inovector\Mixpost\Models\User;

beforeEach(function () {
test()->user = User::factory()->create();
});

it("requires items", function () {

it("shows validation error", function () {
$this->actingAs(test()->user);

$this->postJson(route('mixpost.media.download'))->assertUnprocessable();
$this->postJson(route('mixpost.media.download'), [
'items' => [[]]
])->assertUnprocessable()->assertJsonValidationErrors([
'items',
'from'
]);
});

it("will not download media from internal url", function () {
$this->actingAs(test()->user);

$this->postJson(route('mixpost.media.download'), [
'from' => 'stock',
'items' => [
[
'url' => 'http://localhost:8000'
'id' => '123',
'url' => 'http://localhost:8000',
'download_data' => [
'download_location' => 'https://publicdomain.example/image.jpg?download=1'
]
]
]
])->assertUnprocessable();
Expand All @@ -28,19 +41,30 @@
it("will start to download media file", function () {
$this->actingAs(test()->user);

$url = 'https://publicdomain.example/image.jpg';

Queue::fake();
Http::fake();

$url = 'https://publicdomain.example/image.jpg';
$downloadLocation = 'https://publicdomain.example/image.jpg?download=1';

$this->postJson(route('mixpost.media.download'), [
'from' => 'stock',
'items' => [
[
'url' => $url
'id' => '123',
'url' => $url,
'download_data' => [
'download_location' => $downloadLocation
]
]
]
])->assertOk();

Http::assertSent(function ($request) use ($url) {
return $request->url() == $url;
});

Queue::assertPushed(TriggerDownloadJob::class, function ($job) use ($downloadLocation) {
return $job->downloadLocation == $downloadLocation;
});
});

0 comments on commit d457a61

Please sign in to comment.