feat: integrate LinkedIn API for automated social media post publishing via service and console command

This commit is contained in:
Ümit Tunç
2026-08-07 23:43:38 +03:00
parent a51af8208d
commit 25cdfda897
11 changed files with 682 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
<?php
namespace App\Services;
use App\Models\Setting;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class LinkedInService
{
protected string $clientId;
protected string $clientSecret;
protected string $organizationId;
protected string $redirectUri;
public function __construct()
{
$this->clientId = config('linkedin.client_id', '');
$this->clientSecret = config('linkedin.client_secret', '');
$this->organizationId = config('linkedin.organization_id', '35611757');
$this->redirectUri = config('linkedin.redirect_uri', url('/admin/linkedin/callback'));
}
/**
* Generate OAuth 2.0 Authorization URL for admin redirect
*/
public function getAuthorizationUrl(): string
{
$scopes = implode(' ', config('linkedin.scopes', [
'openid',
'profile',
'email',
'w_member_social',
'w_organization_social',
'r_organization_admin',
'rw_organization_admin',
]));
$state = Str::random(32);
session(['linkedin_oauth_state' => $state]);
$queryParams = http_build_query([
'response_type' => 'code',
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUri,
'state' => $state,
'scope' => $scopes,
]);
return "https://www.linkedin.com/oauth/v2/authorization?" . $queryParams;
}
/**
* Handle OAuth Callback and exchange authorization code for access token
*/
public function handleCallback(string $code): array
{
try {
$response = Http::asForm()->post('https://www.linkedin.com/oauth/v2/accessToken', [
'grant_type' => 'authorization_code',
'code' => $code,
'redirect_uri' => $this->redirectUri,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
]);
if ($response->failed()) {
Log::error('LinkedIn OAuth Token Exchange Failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return [
'success' => false,
'message' => 'LinkedIn Access Token alınamadı: ' . ($response->json('error_description') ?? $response->body()),
];
}
$data = $response->json();
$accessToken = $data['access_token'] ?? null;
$expiresIn = $data['expires_in'] ?? 5184000; // Default 60 days in seconds
if (!$accessToken) {
return [
'success' => false,
'message' => 'LinkedIn Access Token yanıt içinde bulunamadı.',
];
}
$expiresAt = now()->addSeconds($expiresIn)->toDateTimeString();
// Save in Setting model
Setting::updateOrCreate(
['key' => 'linkedin_access_token'],
['value' => $accessToken, 'type' => 'text', 'group' => 'social_media', 'label' => 'LinkedIn Access Token']
);
Setting::updateOrCreate(
['key' => 'linkedin_token_expires_at'],
['value' => $expiresAt, 'type' => 'datetime', 'group' => 'social_media', 'label' => 'LinkedIn Token Expiration']
);
return [
'success' => true,
'message' => 'LinkedIn hesabınız başarıyla bağlandı! Access Token kaydedildi.',
'expires_at' => $expiresAt,
];
} catch (\Throwable $e) {
Log::error('LinkedIn Callback Error: ' . $e->getMessage());
return [
'success' => false,
'message' => 'Hata oluştu: ' . $e->getMessage(),
];
}
}
/**
* Check if valid LinkedIn Access Token exists
*/
public function isAuthorized(): bool
{
$token = Setting::get('linkedin_access_token');
$expiresAt = Setting::get('linkedin_token_expires_at');
if (!$token) {
return false;
}
if ($expiresAt && now()->greaterThanOrEqualTo($expiresAt)) {
return false;
}
return true;
}
/**
* Get remaining days of Access Token
*/
public function getTokenExpirationStatus(): array
{
$expiresAt = Setting::get('linkedin_token_expires_at');
if (!$expiresAt) {
return [
'is_valid' => false,
'message' => 'Yetkilendirme yapılmadı.',
];
}
$date = \Carbon\Carbon::parse($expiresAt);
$diff = now()->diffInDays($date, false);
if ($diff <= 0) {
return [
'is_valid' => false,
'message' => 'Erişim anahtarının süresi doldu (' . $date->format('d.m.Y H:i') . '). Yeniden yetkilendirin.',
];
}
return [
'is_valid' => true,
'days_left' => $diff,
'expires_at' => $date->format('d.m.Y H:i'),
'message' => "Erişim anahtarı aktif. Kalan süre: {$diff} gün ({$date->format('d.m.Y H:i')}).",
];
}
/**
* Share a post to LinkedIn Company Page
*/
public function sharePost(string $title, string $text, ?string $url = null, ?string $imageUrl = null): array
{
if (!$this->isAuthorized()) {
return [
'success' => false,
'message' => 'LinkedIn yetkilendirmesi bulunamadı veya süresi dolmuş.',
];
}
$token = Setting::get('linkedin_access_token');
$authorUrn = "urn:li:organization:{$this->organizationId}";
// Prepare UGC Post payload
$shareCommentary = trim($title . "\n\n" . $text);
if ($url) {
$shareCommentary .= "\n\n" . $url;
}
$mediaContent = [];
if ($url) {
$mediaItem = [
'status' => 'READY',
'originalUrl' => $url,
'title' => [
'text' => Str::limit($title, 200),
],
'description' => [
'text' => Str::limit(strip_tags($text), 250),
],
];
$mediaContent[] = $mediaItem;
}
$shareContent = [
'shareCommentary' => [
'text' => $shareCommentary,
],
'shareMediaCategory' => !empty($mediaContent) ? 'ARTICLE' : 'NONE',
];
if (!empty($mediaContent)) {
$shareContent['media'] = $mediaContent;
}
$payload = [
'author' => $authorUrn,
'lifecycleState' => 'PUBLISHED',
'specificContent' => [
'com.linkedin.ugc.ShareContent' => $shareContent,
],
'visibility' => [
'com.linkedin.ugc.ShareProductVisibility' => 'PUBLIC',
],
];
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'X-Restli-Protocol-Version' => '2.0.0',
'Content-Type' => 'application/json',
])->post('https://api.linkedin.com/v2/ugcPosts', $payload);
if ($response->successful()) {
$postId = $response->header('x-restli-id') ?? $response->json('id');
Log::info('LinkedIn post published successfully', [
'organization_id' => $this->organizationId,
'post_id' => $postId,
]);
return [
'success' => true,
'message' => 'LinkedIn gönderisi başarıyla paylaşıldı!',
'post_id' => $postId,
];
}
Log::error('LinkedIn Share API Failed', [
'status' => $response->status(),
'body' => $response->body(),
]);
return [
'success' => false,
'message' => 'LinkedIn gönderisi paylaşılamadı: ' . ($response->json('message') ?? $response->body()),
];
} catch (\Throwable $e) {
Log::error('LinkedIn Share Post Exception: ' . $e->getMessage());
return [
'success' => false,
'message' => 'Gönderi paylaşılırken bir hata oluştu: ' . $e->getMessage(),
];
}
}
}