Files
landing-api/database/seeders/DatabaseSeeder.php
Josep Piguillem 3897113313 Initial commit: Pireon Admin — headless admin panel and public API
Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer
collective, backed by the Pireon OAuth identity server. The public site is a
separate, server-rendered application consuming /api/public/v1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 17:55:27 +02:00

183 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\Call;
use App\Models\Category;
use App\Models\Client;
use App\Models\Member;
use App\Models\Page;
use App\Models\Project;
use App\Models\Repository;
use App\Models\Service;
use App\Models\Talk;
use App\Models\Technology;
use App\Models\Testimonial;
use Illuminate\Database\Seeder;
use Illuminate\Support\Collection;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Always required: the app cannot authorise anyone without its roles
// and permissions in place.
$this->call(RolePermissionSeeder::class);
// Sample content only outside production. Real user accounts are
// never seeded — they are created by the OAuth login flow.
if (! app()->environment('production')) {
$this->seedDemoContent();
}
}
protected function seedDemoContent(): void
{
if (Page::query()->exists()) {
return;
}
$categories = Category::factory()->count(4)->create();
Page::factory()
->count(12)
->published()
->recycle($categories)
->create();
Page::factory()->count(4)->draft()->recycle($categories)->create();
// A few pages deliberately left translated in the default locale only,
// so the translation filters and dashboard stat have something to show.
Page::factory()->count(3)->untranslated()->recycle($categories)->create();
$this->seedCollective();
$this->command?->info('Seeded demo content for the collective.');
}
/**
* The collective's own content: who we are, what we build, what we use and
* what we need.
*/
protected function seedCollective(): void
{
$technologies = Technology::factory()->count(12)->create();
$members = Member::factory()->count(8)->create();
Member::factory()->count(2)->founder()->create();
Member::factory()->count(2)->past()->create();
Member::all()->each(
fn (Member $member) => $member->technologies()->sync(
$technologies->random(rand(2, 5))->pluck('id'),
),
);
$projects = Project::factory()
->count(6)
->published()
->create()
->each(function (Project $project) use ($technologies, $members): void {
$project->technologies()->sync($technologies->random(rand(2, 5))->pluck('id'));
$project->members()->sync(
$members->random(rand(1, 3))->mapWithKeys(fn (Member $m) => [
$m->id => ['role' => fake()->randomElement(['dev', 'diseño', 'infra'])],
]),
);
$project->update(['lead_member_id' => $members->random()->id]);
Repository::factory()->for($project)->create();
});
// One archived project with a write-up, and one unpublished, so the
// public API's filtering has something real to exclude.
Project::factory()->withPostmortem()->published()->create();
Project::factory()->draft()->create();
$this->seedShowcase($projects, $members, $technologies);
// Open calls, some attached to a project and some standalone.
Call::factory()
->count(4)
->create(['project_id' => $projects->random()->id])
->each(fn (Call $call) => $call->technologies()->sync(
$technologies->random(rand(1, 3))->pluck('id'),
));
Call::factory()->count(2)->create(['project_id' => null]);
Call::factory()->closed()->create(['project_id' => null]);
// Mirrored issues, so the "good first issues" endpoint is not empty
// before anyone has configured a real git token.
Repository::all()->each(function (Repository $repository): void {
foreach (range(1, rand(1, 4)) as $number) {
$repository->issues()->create([
'number' => $number,
'title' => fake()->sentence(6),
'html_url' => "{$repository->html_url}/issues/{$number}",
'labels' => ['good first issue'],
'is_good_first_issue' => true,
'is_help_wanted' => fake()->boolean(),
'author' => 'maintainer',
'assignee' => fake()->boolean(25) ? 'someone' : null,
'comments' => rand(0, 8),
'issue_created_at' => now()->subDays(rand(1, 90)),
'issue_updated_at' => now()->subDays(rand(0, 10)),
]);
}
});
}
/**
* What the landing page is built from.
*
* Without these a fresh install has projects and members but nothing to
* compose a front page out of, which makes it impossible to tell whether
* the frontend works.
*
* @param Collection<int, Project> $projects
* @param Collection<int, Member> $members
* @param Collection<int, Technology> $technologies
*/
protected function seedShowcase($projects, $members, $technologies): void
{
// Organisations the work was for, each with at least one project so
// the wall of logos is not empty.
$clients = Client::factory()->count(4)->create();
$projects->each(fn (Project $project) => $project->update([
'client_id' => $clients->random()->id,
]));
$services = Service::factory()->count(4)->create();
$services->each(function (Service $service) use ($projects, $technologies): void {
$service->technologies()->sync($technologies->random(rand(2, 4))->pluck('id'));
$service->projects()->sync($projects->random(rand(1, 3))->pluck('id'));
});
// One published service with nothing behind it, so the health check
// that calls that out has something real to find.
Service::factory()->create(['is_featured' => true]);
Service::factory()->draft()->create();
Testimonial::factory()->count(3)->create([
'client_id' => $clients->random()->id,
]);
Testimonial::factory()->create(['service_id' => $services->random()->id]);
Testimonial::factory()->create(['project_id' => $projects->random()->id]);
Talk::factory()->count(5)->create([
'member_id' => $members->random()->id,
]);
// And one nobody is credited with, for the same reason.
Talk::factory()->create(['member_id' => null]);
}
}