Add NIP-05 handle management: Introduce migration, API route, and Livewire updates to support NIP-05 handle verification.

 Enhance Nostr fetcher: Refactor profile data merging logic for improved efficiency and accuracy.
🛠
This commit is contained in:
HolgerHatGarKeineNode
2026-01-20 13:56:50 +01:00
parent a857e54d61
commit 34f8d949d5
11 changed files with 669 additions and 106 deletions

View File

@@ -0,0 +1,164 @@
<?php
use App\Enums\AssociationStatus;
use App\Models\EinundzwanzigPleb;
use App\Models\PaymentEvent;
beforeEach(function () {
PaymentEvent::query()->delete();
EinundzwanzigPleb::query()->delete();
});
test('returns paid members for a specific year', function () {
$member1 = EinundzwanzigPleb::factory()->create([
'npub' => 'npub1abc',
'pubkey' => 'pubkey1',
'nip05_handle' => 'user1@example.com',
'association_status' => AssociationStatus::ACTIVE,
]);
$member2 = EinundzwanzigPleb::factory()->create([
'npub' => 'npub2def',
'pubkey' => 'pubkey2',
'nip05_handle' => 'user2@example.com',
'association_status' => AssociationStatus::ACTIVE,
]);
$member3 = EinundzwanzigPleb::factory()->create([
'npub' => 'npub3ghi',
'pubkey' => 'pubkey3',
'association_status' => AssociationStatus::ACTIVE,
]);
$year = 2024;
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member1->id,
'year' => $year,
'paid' => true,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member2->id,
'year' => $year,
'paid' => true,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member3->id,
'year' => $year,
'paid' => false,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member1->id,
'year' => 2023,
'paid' => true,
]);
$response = $this->getJson("/api/members/{$year}");
$response->assertStatus(200);
$response->assertJsonCount(2);
$response->assertJsonFragment([
'npub' => 'npub1abc',
'pubkey' => 'pubkey1',
'nip05_handle' => 'user1@example.com',
]);
$response->assertJsonFragment([
'npub' => 'npub2def',
'pubkey' => 'pubkey2',
'nip05_handle' => 'user2@example.com',
]);
$response->assertJsonMissing([
'npub' => 'npub3ghi',
]);
});
test('returns empty array when no members paid for year', function () {
$year = 2024;
$response = $this->getJson("/api/members/{$year}");
$response->assertStatus(200);
$response->assertJson([]);
});
test('only returns npub, pubkey, and nip05_handle fields', function () {
$member = EinundzwanzigPleb::factory()->create([
'npub' => 'npub1abc',
'pubkey' => 'pubkey1',
'nip05_handle' => 'user1@example.com',
'association_status' => AssociationStatus::ACTIVE,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member->id,
'year' => 2024,
'paid' => true,
]);
$response = $this->getJson('/api/members/2024');
$response->assertStatus(200);
$json = $response->json();
expect($json[0])->toHaveKeys(['id', 'npub', 'pubkey', 'nip05_handle']);
expect($json[0])->not->toHaveKeys([
'email',
'association_status',
'no_email',
'application_for',
]);
});
test('includes nip05_handle in response when available', function () {
$member = EinundzwanzigPleb::factory()->create([
'npub' => 'npub1abc',
'pubkey' => 'pubkey1',
'nip05_handle' => 'verified@example.com',
'association_status' => AssociationStatus::ACTIVE,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member->id,
'year' => 2024,
'paid' => true,
]);
$response = $this->getJson('/api/members/2024');
$response->assertStatus(200);
$response->assertJsonFragment([
'nip05_handle' => 'verified@example.com',
]);
});
test('nip05_handle is null in response when not set', function () {
$member = EinundzwanzigPleb::factory()->create([
'npub' => 'npub1abc',
'pubkey' => 'pubkey1',
'nip05_handle' => null,
'association_status' => AssociationStatus::ACTIVE,
]);
PaymentEvent::factory()->create([
'einundzwanzig_pleb_id' => $member->id,
'year' => 2024,
'paid' => true,
]);
$response = $this->getJson('/api/members/2024');
$response->assertStatus(200);
$json = $response->json();
expect($json[0]['nip05_handle'])->toBeNull();
});

View File

@@ -49,6 +49,60 @@ it('validates email format', function () {
->assertHasErrors(['email']);
});
it('can save nip05 handle', function () {
$pleb = EinundzwanzigPleb::factory()->active()->create();
NostrAuth::login($pleb->pubkey);
Livewire::test('association.profile')
->set('nip05Handle', 'user@example.com')
->call('saveNip05Handle')
->assertHasNoErrors();
expect($pleb->fresh()->nip05_handle)->toBe('user@example.com');
});
it('validates nip05 handle format', function () {
$pleb = EinundzwanzigPleb::factory()->active()->create();
NostrAuth::login($pleb->pubkey);
Livewire::test('association.profile')
->set('nip05Handle', 'not-an-email')
->call('saveNip05Handle')
->assertHasErrors(['nip05Handle']);
});
it('validates nip05 handle uniqueness', function () {
$pleb1 = EinundzwanzigPleb::factory()->active()->create([
'nip05_handle' => 'taken@example.com',
]);
$pleb2 = EinundzwanzigPleb::factory()->active()->create();
NostrAuth::login($pleb2->pubkey);
Livewire::test('association.profile')
->set('nip05Handle', 'taken@example.com')
->call('saveNip05Handle')
->assertHasErrors(['nip05Handle']);
});
it('can save null nip05 handle', function () {
$pleb = EinundzwanzigPleb::factory()->active()->create([
'nip05_handle' => 'old@example.com',
]);
NostrAuth::login($pleb->pubkey);
Livewire::test('association.profile')
->set('nip05Handle', null)
->call('saveNip05Handle')
->assertHasNoErrors();
expect($pleb->fresh()->nip05_handle)->toBeNull();
});
it('can update no email preference', function () {
$pleb = EinundzwanzigPleb::factory()->create();