2026-04-15 13:20:37 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
|
|
|
|
class UnsplashService
|
|
|
|
|
{
|
|
|
|
|
private string $baseUrl = 'https://api.unsplash.com';
|
|
|
|
|
|
|
|
|
|
private string $accessKey;
|
|
|
|
|
|
|
|
|
|
public function __construct()
|
|
|
|
|
{
|
2026-07-01 15:16:15 +00:00
|
|
|
$this->accessKey = (string) config('services.unsplash.access_key', '');
|
2026-04-15 13:20:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array{results: array<int, array<string, mixed>>, total: int, total_pages: int}
|
|
|
|
|
*/
|
2026-04-15 14:02:10 +00:00
|
|
|
public function search(string $query, int $page = 1): array
|
2026-04-15 13:20:37 +00:00
|
|
|
{
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
if (empty($this->accessKey)) {
|
|
|
|
|
return ['results' => [], 'total' => 0, 'total_pages' => 0];
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 13:20:37 +00:00
|
|
|
$response = Http::timeout(10)
|
|
|
|
|
->withHeaders(['Authorization' => "Client-ID {$this->accessKey}"])
|
|
|
|
|
->get("{$this->baseUrl}/search/photos", [
|
|
|
|
|
'query' => $query,
|
|
|
|
|
'page' => $page,
|
2026-04-15 14:02:10 +00:00
|
|
|
'per_page' => config('app.pagination.default'),
|
2026-04-15 13:20:37 +00:00
|
|
|
'orientation' => 'landscape',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($response->failed()) {
|
|
|
|
|
Log::warning('Unsplash search failed', ['body' => $response->body()]);
|
|
|
|
|
|
|
|
|
|
return ['results' => [], 'total' => 0, 'total_pages' => 0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $response->json();
|
|
|
|
|
|
|
|
|
|
return [
|
2026-04-15 14:02:10 +00:00
|
|
|
'results' => collect(data_get($data, 'results', []))->map(fn (array $photo) => $this->formatPhoto($photo))->all(),
|
2026-04-15 13:20:37 +00:00
|
|
|
'total' => data_get($data, 'total', 0),
|
|
|
|
|
'total_pages' => data_get($data, 'total_pages', 0),
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:02:10 +00:00
|
|
|
/**
|
|
|
|
|
* @return array<int, array<string, mixed>>
|
|
|
|
|
*/
|
|
|
|
|
public function trending(int $page = 1): array
|
|
|
|
|
{
|
feat: complete AI assistant with custom services and brand config
Baseline snapshot of custom AI implementation before Laravel AI SDK migration.
Includes:
- Custom services: GeminiTextGenerationService, TextGenerationService (OpenAI), ImageGenerationService, AudioGenerationService, VideoGenerationService
- IntentDetector for content moderation via keyword matching
- AI enums: Intent, Orientation, UsageType
- Blade prompt templates: system.blade.php, image.blade.php, video.blade.php
- AiMessage with content_html accessor (markdown rendering)
- AiUsageLog for monthly quota tracking per account
- PostAssistantController with regex-based [GENERATE_*] parsing
- WritingAssistantTab with markdown rendering, add-to-post, attachments
- Workspace brand fields (name, description, tone, voice_notes) in system prompt
- Session state block injected into prompts (thread counts, quota remaining)
- AttachmentCollector pattern will replace the regex approach in Phase 2
- Post comments with replies, emoji reactions, real-time via Echo
- Assets page with Unsplash + Giphy integrations
2026-04-16 12:25:08 +00:00
|
|
|
if (empty($this->accessKey)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 14:02:10 +00:00
|
|
|
$response = Http::timeout(10)
|
|
|
|
|
->withHeaders(['Authorization' => "Client-ID {$this->accessKey}"])
|
|
|
|
|
->get("{$this->baseUrl}/photos", [
|
|
|
|
|
'page' => $page,
|
|
|
|
|
'per_page' => config('app.pagination.default'),
|
|
|
|
|
'order_by' => 'popular',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if ($response->failed()) {
|
|
|
|
|
Log::warning('Unsplash trending fetch failed', ['body' => $response->body()]);
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return collect($response->json())->map(fn (array $photo) => $this->formatPhoto($photo))->all();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 13:20:37 +00:00
|
|
|
public function trackDownload(string $downloadLocation): void
|
|
|
|
|
{
|
|
|
|
|
Http::timeout(5)
|
|
|
|
|
->withHeaders(['Authorization' => "Client-ID {$this->accessKey}"])
|
|
|
|
|
->get($downloadLocation);
|
|
|
|
|
}
|
2026-04-15 14:02:10 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return array<string, mixed>
|
|
|
|
|
*/
|
|
|
|
|
private function formatPhoto(array $photo): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'id' => data_get($photo, 'id'),
|
|
|
|
|
'url_small' => data_get($photo, 'urls.small'),
|
|
|
|
|
'url_regular' => data_get($photo, 'urls.regular'),
|
|
|
|
|
'url_full' => data_get($photo, 'urls.full'),
|
|
|
|
|
'download_location' => data_get($photo, 'links.download_location'),
|
|
|
|
|
'description' => data_get($photo, 'alt_description'),
|
|
|
|
|
'width' => data_get($photo, 'width'),
|
|
|
|
|
'height' => data_get($photo, 'height'),
|
|
|
|
|
'author' => [
|
|
|
|
|
'name' => data_get($photo, 'user.name'),
|
|
|
|
|
'url' => data_get($photo, 'user.links.html'),
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
}
|
2026-04-15 13:20:37 +00:00
|
|
|
}
|