trypost/app/Services/Social/MastodonPublisher.php
Paulo Castellano 56b8c92e72 refactor: settings redesign, Spanish translations, language system, strict_types
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals

Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController

Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props

All 710 tests passing.
2026-03-30 00:20:43 -03:00

127 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Social;
use App\Exceptions\TokenExpiredException;
use App\Models\PostPlatform;
use App\Models\SocialAccount;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class MastodonPublisher
{
public function publish(PostPlatform $postPlatform): array
{
$account = $postPlatform->socialAccount;
$instance = $account->meta['instance'] ?? 'https://mastodon.social';
$medias = $postPlatform->media;
$mediaIds = [];
// Upload media first (max 4)
foreach ($medias->take(4) as $media) {
$mediaId = $this->uploadMedia($account, $instance, $media->url, $media->original_filename);
if ($mediaId) {
$mediaIds[] = $mediaId;
}
}
// Create status
$payload = [
'status' => $postPlatform->content ?? '',
'visibility' => 'public',
];
if (! empty($mediaIds)) {
$payload['media_ids'] = $mediaIds;
}
Log::info('Mastodon publishing status', [
'instance' => $instance,
'user_id' => $account->platform_user_id,
'has_media' => count($mediaIds) > 0,
]);
$response = Http::withToken($account->access_token)
->post("{$instance}/api/v1/statuses", $payload);
if ($response->failed()) {
Log::error('Mastodon post failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
$this->handleApiError($response);
}
$data = $response->json();
Log::info('Mastodon post created', [
'id' => $data['id'],
'url' => $data['url'],
]);
return [
'id' => $data['id'],
'url' => $data['url'],
];
}
private function uploadMedia(SocialAccount $account, string $instance, string $url, ?string $filename): ?string
{
try {
$fileContent = file_get_contents($url);
if ($fileContent === false) {
Log::error('Mastodon failed to read media', ['url' => $url]);
return null;
}
// Determine filename from URL if not provided
$name = $filename ?? basename(parse_url($url, PHP_URL_PATH));
if (empty($name)) {
$name = 'media';
}
$response = Http::withToken($account->access_token)
->attach('file', $fileContent, $name)
->post("{$instance}/api/v1/media");
if ($response->failed()) {
Log::error('Mastodon media upload failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return null;
}
$data = $response->json();
Log::info('Mastodon media uploaded', ['id' => $data['id']]);
return $data['id'];
} catch (\Exception $e) {
Log::error('Mastodon media upload error', [
'error' => $e->getMessage(),
'url' => $url,
]);
return null;
}
}
private function handleApiError(Response $response): void
{
$body = $response->json() ?? [];
$error = $body['error'] ?? $response->body();
if ($response->status() === 401 || $response->status() === 403) {
throw new TokenExpiredException("Mastodon: {$error}");
}
throw new \Exception("Mastodon API error: {$error}");
}
}