🔥 Add initial database migrations, seeders, and factories

🎨 Refactor `Lecturer` model to include new fields and factory usage
🔧 Update `DatabaseSeeder` to handle default seeds
🛠️ Enhance `einundzwanzig` database configuration for SQLite compatibility
This commit is contained in:
BT
2026-05-02 17:17:13 +01:00
parent 04abf231bd
commit cb61d9d543
54 changed files with 1975 additions and 417 deletions
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace Database\Factories;
use App\Models\Category;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Category>
*/
class CategoryFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->unique()->randomElement([
'Grundlagen',
'Self Custody',
'Lightning',
'Nostr',
'Privacy',
'Mining',
'Wirtschaft',
'Recht',
'Technik',
]),
];
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace Database\Factories;
use App\Models\City;
use App\Models\Country;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<City>
*/
class CityFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'country_id' => Country::factory(),
'name' => fake()->city(),
'latitude' => fake()->latitude(47, 55),
'longitude' => fake()->longitude(6, 16),
'osm_relation' => null,
'simplified_geojson' => null,
];
}
public function vienna(): static
{
return $this->state(fn () => [
'name' => 'Wien',
'latitude' => 48.2082,
'longitude' => 16.3738,
]);
}
public function berlin(): static
{
return $this->state(fn () => [
'name' => 'Berlin',
'latitude' => 52.5200,
'longitude' => 13.4050,
]);
}
public function munich(): static
{
return $this->state(fn () => [
'name' => 'München',
'latitude' => 48.1351,
'longitude' => 11.5820,
]);
}
public function zurich(): static
{
return $this->state(fn () => [
'name' => 'Zürich',
'latitude' => 47.3769,
'longitude' => 8.5417,
]);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php
namespace Database\Factories;
use App\Models\Country;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Country>
*/
class CountryFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->unique()->country(),
'code' => strtoupper(fake()->unique()->lexify('??')),
'language_codes' => ['de'],
];
}
public function germany(): static
{
return $this->state(fn () => [
'name' => 'Deutschland',
'code' => 'DE',
'language_codes' => ['de'],
]);
}
public function austria(): static
{
return $this->state(fn () => [
'name' => 'Österreich',
'code' => 'AT',
'language_codes' => ['de'],
]);
}
public function switzerland(): static
{
return $this->state(fn () => [
'name' => 'Schweiz',
'code' => 'CH',
'language_codes' => ['de', 'fr', 'it'],
]);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace Database\Factories;
use App\Models\Course;
use App\Models\CourseEvent;
use App\Models\Venue;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<CourseEvent>
*/
class CourseEventFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$from = fake()->dateTimeBetween('+1 day', '+90 days');
$to = (clone $from)->modify('+2 hours');
return [
'course_id' => Course::factory(),
'venue_id' => Venue::factory(),
'from' => $from,
'to' => $to,
];
}
public function past(): static
{
return $this->state(function () {
$from = fake()->dateTimeBetween('-90 days', '-1 day');
return [
'from' => $from,
'to' => (clone $from)->modify('+2 hours'),
];
});
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace Database\Factories;
use App\Models\Course;
use App\Models\Lecturer;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Course>
*/
class CourseFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$titles = [
'Bitcoin Basics',
'Self Custody und Hardware Wallets',
'Lightning Network 101',
'Nostr Einführung',
'Bitcoin für Unternehmer',
'Privacy & Coinjoin',
'Running Your Own Node',
];
return [
'lecturer_id' => Lecturer::factory(),
'name' => fake()->randomElement($titles),
'description' => fake()->paragraphs(2, true),
'duration_minutes' => fake()->randomElement([60, 90, 120, 180]),
];
}
public function bitcoinBasics(): static
{
return $this->state(fn () => [
'name' => 'Bitcoin Basics',
'description' => 'Eine umfassende Einführung in Bitcoin: Was ist Bitcoin, wie funktioniert die Blockchain, warum ist es revolutionär. Perfekt für Einsteiger.',
'duration_minutes' => 90,
]);
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace Database\Factories;
use App\Models\Event;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Event>
*/
class EventFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$eventId = bin2hex(random_bytes(32));
$pubkey = bin2hex(random_bytes(32));
$createdAt = fake()->dateTimeBetween('-1 year')->getTimestamp();
return [
'event_id' => $eventId,
'parent_event_id' => null,
'pubkey' => $pubkey,
'type' => fake()->randomElement(['note', 'long-form', 'reaction']),
'json' => json_encode([
'id' => $eventId,
'pubkey' => $pubkey,
'created_at' => $createdAt,
'kind' => 1,
'tags' => [],
'content' => fake()->paragraph(),
'sig' => bin2hex(random_bytes(64)),
], JSON_THROW_ON_ERROR),
];
}
public function fromMarkusTurm(): static
{
return $this->state(function () {
$eventId = bin2hex(random_bytes(32));
$pubkey = 'f240be2b684f85cc81566f2081386af81d7427ea86250c8bde6b7a8500c761ba';
$createdAt = fake()->dateTimeBetween('-30 days')->getTimestamp();
return [
'event_id' => $eventId,
'pubkey' => $pubkey,
'type' => 'note',
'json' => json_encode([
'id' => $eventId,
'pubkey' => $pubkey,
'created_at' => $createdAt,
'kind' => 1,
'tags' => [['t', 'bitcoin'], ['t', 'einundzwanzig']],
'content' => 'Bitcoin fixes this. #bitcoin #einundzwanzig',
'sig' => bin2hex(random_bytes(64)),
], JSON_THROW_ON_ERROR),
];
});
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace Database\Factories;
use App\Models\Lecturer;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Lecturer>
*/
class LecturerFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'bio' => fake()->paragraph(),
'pubkey' => bin2hex(random_bytes(32)),
'website' => fake()->url(),
'active' => true,
];
}
public function markusTurm(): static
{
return $this->state(fn () => [
'name' => 'Markus Turm',
'bio' => 'Hobby Hedge Fund Manager. Bitcoin, Austrian Economics, Laissez-Faire Radical.',
'npub' => 'npub17fqtu2mgf7zueq2kdusgzwr2lqwhgfl2scjsez77ddag2qx8vxaq3vnr8y',
'pubkey' => 'f240be2b684f85cc81566f2081386af81d7427ea86250c8bde6b7a8500c761ba',
'website' => 'https://einundzwanzig.space',
'active' => true,
]);
}
public function inactive(): static
{
return $this->state(fn () => ['active' => false]);
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace Database\Factories;
use App\Models\Meetup;
use App\Models\MeetupEvent;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<MeetupEvent>
*/
class MeetupEventFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'meetup_id' => Meetup::factory(),
'start' => fake()->dateTimeBetween('+1 day', '+90 days'),
'location' => fake()->company().', '.fake()->streetAddress(),
'description' => fake()->paragraph(),
'link' => fake()->url(),
'attendees' => [
'f240be2b684f85cc81566f2081386af81d7427ea86250c8bde6b7a8500c761ba',
bin2hex(random_bytes(32)),
],
'might_attendees' => [bin2hex(random_bytes(32))],
'nostr_status' => 'Sent event '.bin2hex(random_bytes(8)).' to wss://simple-relay.codingarena.top',
];
}
public function past(): static
{
return $this->state(fn () => [
'start' => fake()->dateTimeBetween('-90 days', '-1 day'),
]);
}
public function upcoming(): static
{
return $this->state(fn () => [
'start' => fake()->dateTimeBetween('+1 day', '+30 days'),
]);
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Database\Factories;
use App\Models\City;
use App\Models\Meetup;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Meetup>
*/
class MeetupFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$name = 'Einundzwanzig '.fake()->city();
return [
'city_id' => City::factory(),
'name' => $name,
'description' => fake()->paragraph(),
'website' => 'https://einundzwanzig.space/meetups/'.str()->slug($name),
'nostr_pubkey' => bin2hex(random_bytes(32)),
'github_data' => [
'repo' => 'einundzwanzig-portal',
'path' => 'meetups/'.str()->slug($name).'.json',
],
'simplified_geojson' => null,
];
}
public function vienna(): static
{
return $this->state(fn () => [
'name' => 'Einundzwanzig Wien',
'description' => 'Das Bitcoin-only Meetup in Wien. Jeden ersten Donnerstag im Monat.',
'website' => 'https://einundzwanzig.space/meetups/wien',
'nostr_pubkey' => 'f240be2b684f85cc81566f2081386af81d7427ea86250c8bde6b7a8500c761ba',
]);
}
public function berlin(): static
{
return $this->state(fn () => [
'name' => 'Einundzwanzig Berlin',
'description' => 'Bitcoin Meetup in der Hauptstadt. Plebs willkommen.',
'website' => 'https://einundzwanzig.space/meetups/berlin',
]);
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Database\Factories;
use App\Models\Profile;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Profile>
*/
class ProfileFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
$name = fake()->userName();
return [
'pubkey' => bin2hex(random_bytes(32)),
'name' => $name,
'display_name' => fake()->name(),
'picture' => 'https://image.nostr.build/'.fake()->uuid().'.jpg',
'banner' => null,
'website' => fake()->url(),
'about' => fake()->sentence(12),
'nip05' => $name.'@einundzwanzig.space',
'lud16' => $name.'@walletofsatoshi.com',
'lud06' => null,
'deleted' => false,
];
}
public function markusTurm(): static
{
return $this->state(fn (array $attributes) => [
'pubkey' => 'f240be2b684f85cc81566f2081386af81d7427ea86250c8bde6b7a8500c761ba',
'name' => 'markusturm',
'display_name' => 'Markus Turm',
'picture' => 'https://m.primal.net/HQqf.jpg',
'banner' => 'https://m.primal.net/HQqg.jpg',
'website' => 'https://einundzwanzig.space',
'about' => '#Bitcoin | Austrian Economics | Laissez-Faire Radical | @_einundzwanzig_',
'nip05' => 'markusturm@einundzwanzig.space',
'lud16' => 'markusturm@walletofsatoshi.com',
'deleted' => false,
]);
}
}
@@ -0,0 +1,26 @@
<?php
namespace Database\Factories;
use App\Models\Event;
use App\Models\RenderedEvent;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<RenderedEvent>
*/
class RenderedEventFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'event_id' => Event::factory()->create()->event_id,
'html' => '<p>'.fake()->paragraph().'</p>',
'profile_image' => 'https://m.primal.net/'.fake()->uuid().'.jpg',
'profile_name' => fake()->userName(),
];
}
}
@@ -0,0 +1,49 @@
<?php
namespace Database\Factories;
use App\Models\SecurityAttempt;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Validation\ValidationException;
/**
* @extends Factory<SecurityAttempt>
*/
class SecurityAttemptFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'method' => fake()->randomElement(['GET', 'POST']),
'url' => fake()->url(),
'route_name' => fake()->randomElement(['association.profile', 'association.elections', 'association.projectSupport']),
'exception_class' => fake()->randomElement([
ValidationException::class,
AuthenticationException::class,
AuthorizationException::class,
]),
'exception_message' => fake()->sentence(),
'component_name' => fake()->randomElement(['association.profile', 'association.election.show']),
'target_property' => fake()->randomElement(['name', 'email', 'npub']),
'payload' => ['attempt' => fake()->word()],
'severity' => fake()->randomElement(['low', 'medium', 'high', 'critical']),
];
}
public function high(): static
{
return $this->state(fn () => ['severity' => 'high']);
}
public function critical(): static
{
return $this->state(fn () => ['severity' => 'critical']);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Database\Factories;
use App\Models\City;
use App\Models\Venue;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Venue>
*/
class VenueFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'city_id' => City::factory(),
'name' => fake()->randomElement(['Bitcoin Bar', 'Plebsfreundlicher Coworking', 'Hodler Hangout', 'Lightning Lounge']).' '.fake()->lastName(),
'description' => fake()->paragraph(),
'address' => fake()->streetAddress(),
'website' => fake()->url(),
];
}
public function bitcoinBarVienna(): static
{
return $this->state(fn () => [
'name' => 'Bitcoin Bar Wien',
'description' => 'Die erste Bitcoin-only Bar im 7. Bezirk.',
'address' => 'Neubaugasse 21, 1070 Wien',
'website' => 'https://bitcoin-bar.at',
]);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace Database\Factories;
use App\Models\EinundzwanzigPleb;
use App\Models\ProjectProposal;
use App\Models\Vote;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Vote>
*/
class VoteFactory extends Factory
{
/**
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'einundzwanzig_pleb_id' => EinundzwanzigPleb::factory(),
'project_proposal_id' => ProjectProposal::factory(),
'value' => fake()->boolean(70),
'reason' => fake()->optional(0.3)->sentence(),
];
}
public function approve(): static
{
return $this->state(fn () => ['value' => true]);
}
public function reject(): static
{
return $this->state(fn () => ['value' => false]);
}
}