Add communities form
This commit is contained in:
parent
774ef59e28
commit
214b240423
12 changed files with 350 additions and 41 deletions
80
app/Http/Controllers/CommunitiesController.php
Normal file
80
app/Http/Controllers/CommunitiesController.php
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Community;
|
||||
use App\Models\PlatformInstance;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class CommunitiesController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$communities = Community::with('platformInstance')
|
||||
->orderBy('platform_instance_id')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('pages.communities.index', compact('communities'));
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
$instances = PlatformInstance::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('pages.communities.create', compact('instances'));
|
||||
}
|
||||
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'platform_instance_id' => 'required|exists:platform_instances,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'display_name' => 'required|string|max:255',
|
||||
'community_id' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
Community::create($validated);
|
||||
|
||||
return redirect()->route('communities.index')
|
||||
->with('success', 'Community created successfully!');
|
||||
}
|
||||
|
||||
public function edit(Community $community): View
|
||||
{
|
||||
$instances = PlatformInstance::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('pages.communities.edit', compact('community', 'instances'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Community $community): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'platform_instance_id' => 'required|exists:platform_instances,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'display_name' => 'required|string|max:255',
|
||||
'community_id' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$community->update($validated);
|
||||
|
||||
return redirect()->route('communities.index')
|
||||
->with('success', 'Community updated successfully!');
|
||||
}
|
||||
|
||||
public function destroy(Community $community): RedirectResponse
|
||||
{
|
||||
$community->delete();
|
||||
|
||||
return redirect()->route('communities.index')
|
||||
->with('success', 'Community deleted successfully!');
|
||||
}
|
||||
}
|
||||
|
|
@ -38,10 +38,12 @@ public function handle(): void
|
|||
]);
|
||||
|
||||
try {
|
||||
LemmyPublisher::fromActiveAccount()->publish($this->article, $extractedData);
|
||||
$publications = LemmyPublisher::fromActiveAccount()->publish($this->article, $extractedData);
|
||||
|
||||
logger()->info('Article published successfully', [
|
||||
'article_id' => $this->article->id
|
||||
'article_id' => $this->article->id,
|
||||
'publications_count' => $publications->count(),
|
||||
'communities' => $publications->pluck('community_id')->toArray()
|
||||
]);
|
||||
|
||||
} catch (PublishException $e) {
|
||||
|
|
|
|||
44
app/Models/Community.php
Normal file
44
app/Models/Community.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @method static create(array $validated)
|
||||
* @property PlatformInstance $platformInstance
|
||||
*/
|
||||
class Community extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'platform_instance_id',
|
||||
'name',
|
||||
'display_name',
|
||||
'community_id',
|
||||
'description',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
public function platformInstance(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PlatformInstance::class);
|
||||
}
|
||||
|
||||
public function platformAccounts(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(PlatformAccount::class, 'account_communities')
|
||||
->withPivot(['is_active', 'priority'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function getFullNameAttribute(): string
|
||||
{
|
||||
return $this->platformInstance->url . '/c/' . $this->name;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use App\Enums\PlatformEnum;
|
||||
|
|
@ -21,6 +22,7 @@
|
|||
* @property string $status
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
* @property Collection $activeCommunities
|
||||
* @method static where(string $string, PlatformEnum $platform)
|
||||
* @method static orderBy(string $string)
|
||||
* @method static create(array $validated)
|
||||
|
|
@ -83,4 +85,18 @@ public function setAsActive(): void
|
|||
// Activate this account
|
||||
$this->update(['is_active' => true]);
|
||||
}
|
||||
|
||||
public function communities(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Community::class, 'account_communities')
|
||||
->withPivot(['is_active', 'priority'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function activeCommunities(): BelongsToMany
|
||||
{
|
||||
return $this->communities()
|
||||
->wherePivot('is_active', true)
|
||||
->orderByPivot('priority', 'desc');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
app/Models/PlatformInstance.php
Normal file
44
app/Models/PlatformInstance.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
/**
|
||||
* @method static updateOrCreate(array $array, $instanceData)
|
||||
* @method static where(string $string, mixed $operator)
|
||||
* @property PlatformEnum $platform
|
||||
* @property string $url
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
* @property boolean $is_active
|
||||
*/
|
||||
class PlatformInstance extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'platform',
|
||||
'url',
|
||||
'name',
|
||||
'description',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'platform' => PlatformEnum::class,
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
public function communities(): HasMany
|
||||
{
|
||||
return $this->hasMany(Community::class);
|
||||
}
|
||||
|
||||
public static function findByUrl(PlatformEnum $platform, string $url): ?self
|
||||
{
|
||||
return static::where('platform', $platform)
|
||||
->where('url', $url)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,14 @@
|
|||
namespace App\Modules\Lemmy\Services;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Exceptions\PlatformAuthException;
|
||||
use App\Exceptions\PublishException;
|
||||
use App\Models\Article;
|
||||
use App\Models\ArticlePublication;
|
||||
use App\Models\PlatformAccount;
|
||||
use App\Services\Auth\LemmyAuthService;
|
||||
use Exception;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use RuntimeException;
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ public static function fromActiveAccount(): self
|
|||
$accounts = PlatformAccount::getActive(PlatformEnum::LEMMY);
|
||||
|
||||
if ($accounts->isEmpty()) {
|
||||
throw new RuntimeException('No active Lemmy accounts configured');
|
||||
throw new RuntimeException('No active Lemmy accounts configured'); // TODO Also make this into a PublishException
|
||||
}
|
||||
|
||||
return new self($accounts->first());
|
||||
|
|
@ -37,52 +39,55 @@ public static function fromActiveAccount(): self
|
|||
/**
|
||||
* @throws PublishException
|
||||
*/
|
||||
public function publish(Article $article, array $extractedData): ArticlePublication
|
||||
public function publish(Article $article, array $extractedData): Collection
|
||||
{
|
||||
try {
|
||||
$token = LemmyAuthService::getToken($this->account);
|
||||
$communityId = $this->getCommunityId();
|
||||
$publications = collect();
|
||||
$activeCommunities = $this->account->activeCommunities;
|
||||
|
||||
$languageId = $this->getLanguageIdForSource($article->url);
|
||||
|
||||
$postData = $this->api->createPost(
|
||||
$token,
|
||||
$extractedData['title'] ?? 'Untitled',
|
||||
$extractedData['description'] ?? '',
|
||||
$communityId,
|
||||
$article->url,
|
||||
$extractedData['thumbnail'] ?? null,
|
||||
$languageId
|
||||
);
|
||||
|
||||
return $this->createPublicationRecord($article, $postData, $communityId);
|
||||
} catch (Exception $e) {
|
||||
throw new PublishException($article, PlatformEnum::LEMMY, $e);
|
||||
if ($activeCommunities->isEmpty()) {
|
||||
throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('No active communities configured for account: ' . $this->account->username));
|
||||
}
|
||||
|
||||
$activeCommunities->each(function ($community) use ($article, $extractedData, $publications) {
|
||||
try {
|
||||
$publication = $this->publishToCommunity($article, $extractedData, $community);
|
||||
$publications->push($publication);
|
||||
} catch (Exception $e) {
|
||||
logger()->warning('Failed to publish to community', [
|
||||
'article_id' => $article->id,
|
||||
'community' => $community->name,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
if ($publications->isEmpty()) {
|
||||
throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('Failed to publish to any community'));
|
||||
}
|
||||
|
||||
return $publications;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PlatformAuthException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getCommunityId(): int
|
||||
private function publishToCommunity(Article $article, array $extractedData, $community): ArticlePublication
|
||||
{
|
||||
$community = config('lemmy.community');
|
||||
$token = LemmyAuthService::getToken($this->account);
|
||||
$languageId = $this->getLanguageIdForSource($article->url);
|
||||
|
||||
if (! $community) {
|
||||
throw new RuntimeException('No community configured in config');
|
||||
}
|
||||
$postData = $this->api->createPost(
|
||||
$token,
|
||||
$extractedData['title'] ?? 'Untitled',
|
||||
$extractedData['description'] ?? '',
|
||||
(int) $community->community_id,
|
||||
$article->url,
|
||||
$extractedData['thumbnail'] ?? null,
|
||||
$languageId
|
||||
);
|
||||
|
||||
$cacheKey = "lemmy_community_id_$community";
|
||||
$cachedId = Cache::get($cacheKey);
|
||||
|
||||
if ($cachedId) {
|
||||
return $cachedId;
|
||||
}
|
||||
|
||||
$communityId = $this->api->getCommunityId($community);
|
||||
Cache::put($cacheKey, $communityId, 3600);
|
||||
|
||||
return $communityId;
|
||||
return $this->createPublicationRecord($article, $postData, (int) $community->community_id);
|
||||
}
|
||||
|
||||
private function createPublicationRecord(Article $article, array $postData, int $communityId): ArticlePublication
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?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_instances', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->enum('platform', ['lemmy']);
|
||||
$table->string('url'); // lemmy.world, beehaw.org
|
||||
$table->string('name'); // "Lemmy World", "Beehaw"
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['platform', 'url']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('platform_instances');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?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('communities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('platform_instance_id')->constrained()->onDelete('cascade');
|
||||
$table->string('name'); // "technology"
|
||||
$table->string('display_name'); // "Technology"
|
||||
$table->string('community_id'); // API ID from platform
|
||||
$table->text('description')->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['platform_instance_id', 'name']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('communities');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?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('account_communities', function (Blueprint $table) {
|
||||
$table->foreignId('platform_account_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('community_id')->constrained()->onDelete('cascade');
|
||||
$table->boolean('is_active')->default(false);
|
||||
$table->integer('priority')->default(0); // for ordering
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['platform_account_id', 'community_id']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('account_communities');
|
||||
}
|
||||
};
|
||||
30
database/seeders/PlatformInstanceSeeder.php
Normal file
30
database/seeders/PlatformInstanceSeeder.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Enums\PlatformEnum;
|
||||
use App\Models\PlatformInstance;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class PlatformInstanceSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
collect([
|
||||
[
|
||||
'platform' => PlatformEnum::LEMMY,
|
||||
'url' => 'belgae.social',
|
||||
'name' => 'Belgae Social',
|
||||
'description' => 'A Belgian Lemmy instance on the fediverse',
|
||||
],
|
||||
])->each (fn ($instanceData) =>
|
||||
PlatformInstance::updateOrCreate(
|
||||
[
|
||||
'platform' => $instanceData['platform'],
|
||||
'url' => $instanceData['url'],
|
||||
],
|
||||
$instanceData
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
<!-- Sidebar -->
|
||||
<div class="bg-gray-800 text-white w-64 flex-shrink-0">
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold">Lemmy Poster</h2>
|
||||
<p class="text-gray-400 text-sm">Admin Panel</p>
|
||||
</div>
|
||||
|
||||
|
||||
<nav class="mt-8">
|
||||
<a href="/articles" class="flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 hover:text-white transition-colors {{ request()->is('articles') ? 'bg-gray-700 text-white' : '' }}">
|
||||
<i class="fas fa-newspaper mr-3"></i>
|
||||
|
|
@ -14,6 +13,10 @@
|
|||
<i class="fas fa-share-alt mr-3"></i>
|
||||
Platforms
|
||||
</a>
|
||||
<a href="/communities" class="flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 hover:text-white transition-colors {{ request()->is('communities*') ? 'bg-gray-700 text-white' : '' }}">
|
||||
<i class="fas fa-users mr-3"></i>
|
||||
Communities
|
||||
</a>
|
||||
<a href="/logs" class="flex items-center px-4 py-3 text-gray-300 hover:bg-gray-700 hover:text-white transition-colors {{ request()->is('logs') ? 'bg-gray-700 text-white' : '' }}">
|
||||
<i class="fas fa-list mr-3"></i>
|
||||
Logs
|
||||
|
|
@ -23,4 +26,4 @@
|
|||
Queue Monitor
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,3 +23,5 @@
|
|||
|
||||
Route::resource('platforms', App\Http\Controllers\PlatformAccountsController::class)->names('platforms');
|
||||
Route::post('/platforms/{platformAccount}/set-active', [App\Http\Controllers\PlatformAccountsController::class, 'setActive'])->name('platforms.set-active');
|
||||
|
||||
Route::resource('communities', App\Http\Controllers\CommunitiesController::class)->names('communities');
|
||||
|
|
|
|||
Loading…
Reference in a new issue