Check community history for same urls
This commit is contained in:
parent
800df655bf
commit
18d7a2dbce
10 changed files with 312 additions and 3 deletions
60
app/Console/Commands/SyncChannelPostsCommand.php
Normal file
60
app/Console/Commands/SyncChannelPostsCommand.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Jobs\SyncChannelPostsJob;
|
||||
use App\Modules\Lemmy\Services\LemmyApiService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncChannelPostsCommand extends Command
|
||||
{
|
||||
protected $signature = 'channel:sync {platform=lemmy}';
|
||||
|
||||
protected $description = 'Manually sync channel posts for a platform';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$platform = $this->argument('platform');
|
||||
|
||||
if ($platform === 'lemmy') {
|
||||
return $this->syncLemmy();
|
||||
}
|
||||
|
||||
$this->error("Unsupported platform: {$platform}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
private function syncLemmy(): int
|
||||
{
|
||||
$communityName = config('lemmy.community');
|
||||
|
||||
if (!$communityName) {
|
||||
$this->error('Missing Lemmy community configuration (lemmy.community)');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->info("Getting community ID for: {$communityName}");
|
||||
|
||||
$api = new LemmyApiService(config('lemmy.instance'));
|
||||
$communityId = $api->getCommunityId($communityName);
|
||||
|
||||
$this->info("Running sync job for Lemmy community: {$communityName} (ID: {$communityId})");
|
||||
|
||||
SyncChannelPostsJob::dispatchSync(
|
||||
PlatformEnum::LEMMY,
|
||||
(string) $communityId,
|
||||
$communityName
|
||||
);
|
||||
|
||||
$this->info('Channel posts synced successfully');
|
||||
|
||||
return self::SUCCESS;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error("Failed to sync channel posts: {$e->getMessage()}");
|
||||
return self::FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
app/Jobs/SyncChannelPostsJob.php
Normal file
76
app/Jobs/SyncChannelPostsJob.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Exceptions\PlatformAuthException;
|
||||
use App\Modules\Lemmy\Services\LemmyApiService;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SyncChannelPostsJob implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public function __construct(
|
||||
private readonly PlatformEnum $platform,
|
||||
private readonly string $channelId,
|
||||
private readonly string $channelName
|
||||
) {
|
||||
$this->onQueue('lemmy-posts');
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
if ($this->platform === PlatformEnum::LEMMY) {
|
||||
$this->syncLemmyChannelPosts();
|
||||
}
|
||||
}
|
||||
|
||||
private function syncLemmyChannelPosts(): void
|
||||
{
|
||||
try {
|
||||
$api = new LemmyApiService(config('lemmy.instance'));
|
||||
$token = $this->getAuthToken($api);
|
||||
|
||||
$api->syncChannelPosts($token, (int) $this->channelId, $this->channelName);
|
||||
|
||||
logger()->info('Channel posts synced successfully', [
|
||||
'platform' => $this->platform->value,
|
||||
'channel_id' => $this->channelId,
|
||||
'channel_name' => $this->channelName
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
logger()->error('Failed to sync channel posts', [
|
||||
'platform' => $this->platform->value,
|
||||
'channel_id' => $this->channelId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function getAuthToken(LemmyApiService $api): string
|
||||
{
|
||||
return Cache::remember('lemmy_jwt_token', 3600, function () use ($api) {
|
||||
$username = config('lemmy.username');
|
||||
$password = config('lemmy.password');
|
||||
|
||||
if (!$username || !$password) {
|
||||
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Missing credentials');
|
||||
}
|
||||
|
||||
$token = $api->login($username, $password);
|
||||
|
||||
if (!$token) {
|
||||
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Login failed');
|
||||
}
|
||||
|
||||
return $token;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ class Article extends Model
|
|||
'title',
|
||||
'description',
|
||||
'is_valid',
|
||||
'is_duplicate',
|
||||
'fetched_at',
|
||||
'validated_at',
|
||||
];
|
||||
|
|
@ -37,6 +38,8 @@ class Article extends Model
|
|||
public function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_valid' => 'boolean',
|
||||
'is_duplicate' => 'boolean',
|
||||
'fetched_at' => 'datetime',
|
||||
'validated_at' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
|
|
|
|||
|
|
@ -5,14 +5,21 @@
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property integer $article_id
|
||||
* @property integer $community_id
|
||||
* @property integer $post_id
|
||||
*
|
||||
* @method static create(array $array)
|
||||
*/
|
||||
class ArticlePublication extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'article_id',
|
||||
'published_at',
|
||||
'community_id',
|
||||
'published_by',
|
||||
'post_id',
|
||||
'published_at',
|
||||
'published_by',
|
||||
'platform',
|
||||
'publication_data',
|
||||
];
|
||||
|
|
|
|||
56
app/Models/PlatformChannelPost.php
Normal file
56
app/Models/PlatformChannelPost.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @method static where(string $string, PlatformEnum $platform)
|
||||
* @method static updateOrCreate(array $array, array $array1)
|
||||
*/
|
||||
class PlatformChannelPost extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'platform',
|
||||
'channel_id',
|
||||
'channel_name',
|
||||
'post_id',
|
||||
'url',
|
||||
'title',
|
||||
'posted_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'posted_at' => 'datetime',
|
||||
'platform' => PlatformEnum::class,
|
||||
];
|
||||
}
|
||||
|
||||
public static function urlExists(PlatformEnum $platform, string $channelId, string $url): bool
|
||||
{
|
||||
return self::where('platform', $platform)
|
||||
->where('channel_id', $channelId)
|
||||
->where('url', $url)
|
||||
->exists();
|
||||
}
|
||||
|
||||
public static function storePost(PlatformEnum $platform, string $channelId, ?string $channelName, string $postId, ?string $url, ?string $title, ?\DateTime $postedAt = null): self
|
||||
{
|
||||
return self::updateOrCreate(
|
||||
[
|
||||
'platform' => $platform,
|
||||
'channel_id' => $channelId,
|
||||
'post_id' => $postId,
|
||||
],
|
||||
[
|
||||
'channel_name' => $channelName,
|
||||
'url' => $url,
|
||||
'title' => $title,
|
||||
'posted_at' => $postedAt ?? now(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace App\Modules\Lemmy\Services;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Models\PlatformChannelPost;
|
||||
use App\Modules\Lemmy\LemmyRequest;
|
||||
use Exception;
|
||||
|
||||
|
|
@ -57,6 +59,54 @@ public function getCommunityId(string $communityName): int
|
|||
}
|
||||
}
|
||||
|
||||
public function syncChannelPosts(string $token, int $communityId, string $communityName): void
|
||||
{
|
||||
try {
|
||||
$request = new LemmyRequest($this->instance, $token);
|
||||
$response = $request->get('post/list', [
|
||||
'community_id' => $communityId,
|
||||
'limit' => 50,
|
||||
'sort' => 'New'
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
logger()->warning('Failed to sync channel posts', [
|
||||
'status' => $response->status(),
|
||||
'community_id' => $communityId
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$posts = $data['posts'] ?? [];
|
||||
|
||||
foreach ($posts as $postData) {
|
||||
$post = $postData['post'];
|
||||
|
||||
PlatformChannelPost::storePost(
|
||||
PlatformEnum::LEMMY,
|
||||
(string) $communityId,
|
||||
$communityName,
|
||||
(string) $post['id'],
|
||||
$post['url'] ?? null,
|
||||
$post['name'] ?? null,
|
||||
isset($post['published']) ? new \DateTime($post['published']) : null
|
||||
);
|
||||
}
|
||||
|
||||
logger()->info('Synced channel posts', [
|
||||
'community_id' => $communityId,
|
||||
'posts_count' => count($posts)
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
logger()->error('Exception while syncing channel posts', [
|
||||
'error' => $e->getMessage(),
|
||||
'community_id' => $communityId
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function createPost(string $token, string $title, string $body, int $communityId, ?string $url = null, ?string $thumbnail = null): array
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ private function getAuthToken(): string
|
|||
}
|
||||
|
||||
$token = $this->api->login($username, $password);
|
||||
|
||||
|
||||
if (!$token) {
|
||||
throw new PlatformAuthException(PlatformEnum::LEMMY, 'Login failed');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ public function up(): void
|
|||
$table->string('title')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_valid')->nullable();
|
||||
$table->boolean('is_duplicate')->default(false);
|
||||
$table->timestamp('validated_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('platform_channel_posts', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('platform');
|
||||
$table->string('channel_id');
|
||||
$table->string('channel_name')->nullable();
|
||||
$table->string('post_id');
|
||||
$table->longText('url')->nullable();
|
||||
$table->string('title')->nullable();
|
||||
$table->timestamp('posted_at');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['platform', 'channel_id']);
|
||||
$table->index(['platform', 'channel_id', 'posted_at']);
|
||||
// Will add URL index with prefix after table creation
|
||||
$table->unique(['platform', 'channel_id', 'post_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('platform_channel_posts');
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
|
||||
use App\Console\Commands\FetchNewArticlesCommand;
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Jobs\SyncChannelPostsJob;
|
||||
use App\Models\Article;
|
||||
use App\Modules\Lemmy\Services\LemmyPublisher;
|
||||
use App\Services\Article\ArticleFetcher;
|
||||
|
|
@ -36,3 +38,24 @@
|
|||
logger()->debug('No unpublished valid articles found for Lemmy publishing');
|
||||
}
|
||||
})->everyFifteenMinutes()->name('publish-to-lemmy');
|
||||
|
||||
Schedule::call(function () {
|
||||
$communityId = config('lemmy.community_id');
|
||||
$communityName = config('lemmy.community');
|
||||
|
||||
if ($communityId && $communityName) {
|
||||
SyncChannelPostsJob::dispatch(
|
||||
PlatformEnum::LEMMY,
|
||||
$communityId,
|
||||
$communityName
|
||||
);
|
||||
|
||||
logger()->info('Dispatched channel posts sync job', [
|
||||
'platform' => 'lemmy',
|
||||
'community_id' => $communityId,
|
||||
'community_name' => $communityName
|
||||
]);
|
||||
} else {
|
||||
logger()->warning('Missing Lemmy community configuration for sync job');
|
||||
}
|
||||
})->everyTenMinutes()->name('sync-lemmy-channel-posts');
|
||||
|
|
|
|||
Loading…
Reference in a new issue