- Refactor edit.blade.php to handle admin-specific fields (accepted and sats_paid) through conditional logic.

- 📦 Upgrade Laravel framework, Livewire, and dependencies to ensure compatibility with version `13.1.1`.
This commit is contained in:
HolgerHatGarKeineNode
2026-03-23 17:50:17 +00:00
parent 347082bbc8
commit 7a992cec3f
31 changed files with 496 additions and 525 deletions

View File

@@ -2,6 +2,7 @@
namespace App\Auth;
use App\Models\EinundzwanzigPleb;
use Illuminate\Contracts\Auth\Authenticatable;
class NostrUser implements Authenticatable
@@ -13,7 +14,7 @@ class NostrUser implements Authenticatable
public function __construct(string $pubkey)
{
$this->pubkey = $pubkey;
$this->pleb = \App\Models\EinundzwanzigPleb::query()
$this->pleb = EinundzwanzigPleb::query()
->where('pubkey', $pubkey)
->first();
}

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands\Nostr;
use App\Models\EinundzwanzigPleb;
use App\Models\Event;
use App\Traits\NostrEventRendererTrait;
use Illuminate\Console\Command;
@@ -35,7 +36,7 @@ class FetchEvents extends Command
*/
public function handle()
{
$plebs = \App\Models\EinundzwanzigPleb::query()
$plebs = EinundzwanzigPleb::query()
->get();
$subscription = new Subscription;

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands\Nostr;
use App\Models\Event;
use App\Traits\NostrEventRendererTrait;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Broadcast;
@@ -29,7 +30,7 @@ class RenderAllEvents extends Command
*/
public function handle()
{
$events = \App\Models\Event::query()
$events = Event::query()
->get();
foreach ($events as $event) {

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Traits;
use App\Models\EinundzwanzigPleb;
use App\Support\NostrAuth;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Attributes\On;
@@ -32,7 +33,7 @@ trait WithNostrAuth
NostrAuth::login($pubkey);
$this->currentPubkey = $pubkey;
$this->currentPleb = \App\Models\EinundzwanzigPleb::query()
$this->currentPleb = EinundzwanzigPleb::query()
->where('pubkey', $pubkey)
->first();

View File

@@ -22,13 +22,10 @@ class ProjectProposal extends Model implements HasMedia
/** @var list<string> */
protected $fillable = [
'einundzwanzig_pleb_id',
'name',
'description',
'support_in_sats',
'website',
'accepted',
'sats_paid',
];
/**

View File

@@ -3,6 +3,7 @@
namespace App\Policies;
use App\Auth\NostrUser;
use App\Models\EinundzwanzigPleb;
use App\Models\ProjectProposal;
class ProjectProposalPolicy
@@ -87,7 +88,7 @@ class ProjectProposalPolicy
}
/**
* @param \App\Models\EinundzwanzigPleb $pleb
* @param EinundzwanzigPleb $pleb
*/
private function isBoardMember(object $pleb): bool
{

View File

@@ -3,6 +3,7 @@
namespace App\Traits;
use App\Models\Profile;
use Illuminate\Support\Facades\Http;
use swentel\nostr\Filter\Filter;
use swentel\nostr\Key\Key;
use swentel\nostr\Message\RequestMessage;
@@ -21,7 +22,7 @@ trait NostrFetcherTrait
public function getNip05HandlesForPubkey(string $pubkey): array
{
try {
$response = \Illuminate\Support\Facades\Http::get(
$response = Http::get(
'https://einundzwanzig.space/.well-known/nostr.json',
);
$data = $response->json();

View File

@@ -4,6 +4,7 @@ use App\Services\SecurityMonitor;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Sentry\Laravel\Integration;
return Application::configure(basePath: dirname(__DIR__))
@@ -16,7 +17,7 @@ return Application::configure(basePath: dirname(__DIR__))
)
->withMiddleware(function (Middleware $middleware) {
$middleware->api(prepend: [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
ThrottleRequests::class.':api',
]);
})
->withExceptions(function (Exceptions $exceptions) {

View File

@@ -1,7 +1,10 @@
<?php
use App\Providers\AppServiceProvider;
use App\Providers\NostrAuthServiceProvider;
return [
App\Providers\AppServiceProvider::class,
AppServiceProvider::class,
// App\Providers\FolioServiceProvider::class, // Disabled - laravel/folio package removed during Laravel 12 upgrade
App\Providers\NostrAuthServiceProvider::class,
NostrAuthServiceProvider::class,
];

View File

@@ -12,10 +12,10 @@
"akuechler/laravel-geoly": "^1.0",
"archtechx/enums": "^1.1",
"calebporzio/sushi": "^2.5",
"laravel/framework": "^12.0",
"laravel/framework": "^13.0",
"laravel/nightwatch": "^1.22",
"laravel/reverb": "^1.0",
"laravel/tinker": "^2.9",
"laravel/tinker": "^3.0",
"livewire/flux": "^2.10",
"livewire/flux-pro": "^2.10",
"livewire/livewire": "^4.0",
@@ -27,7 +27,7 @@
"sentry/sentry-laravel": "^4.9",
"simplesoftwareio/simple-qrcode": "^4.2",
"spatie/image": "^3.7",
"spatie/laravel-backup": "^9.1",
"spatie/laravel-backup": "^10.0",
"spatie/laravel-ciphersweet": "^1.6",
"spatie/laravel-google-fonts": "^1.4",
"spatie/laravel-markdown": "^2.5",
@@ -39,7 +39,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/boost": "^1.8",
"laravel/boost": "^2.0",
"laravel/pail": "^1.2",
"laravel/pint": "^1.13",
"mockery/mockery": "^1.6",

737
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
<?php
use App\Models\User;
return [
/*
@@ -66,7 +68,7 @@ return [
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class),
'model' => env('AUTH_MODEL', User::class),
],
'nostr' => [
'driver' => 'nostr',

View File

@@ -1,5 +1,16 @@
<?php
use Spatie\Backup\Notifications\Notifiable;
use Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification;
use Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification;
use Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification;
use Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification;
use Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification;
use Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification;
use Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays;
use Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes;
return [
'backup' => [
@@ -196,19 +207,19 @@ return [
*/
'notifications' => [
'notifications' => [
\Spatie\Backup\Notifications\Notifications\BackupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\UnhealthyBackupWasFoundNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\CleanupHasFailedNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\BackupWasSuccessfulNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\HealthyBackupWasFoundNotification::class => ['mail'],
\Spatie\Backup\Notifications\Notifications\CleanupWasSuccessfulNotification::class => ['mail'],
BackupHasFailedNotification::class => ['mail'],
UnhealthyBackupWasFoundNotification::class => ['mail'],
CleanupHasFailedNotification::class => ['mail'],
BackupWasSuccessfulNotification::class => ['mail'],
HealthyBackupWasFoundNotification::class => ['mail'],
CleanupWasSuccessfulNotification::class => ['mail'],
],
/*
* Here you can specify the notifiable to which the notifications should be sent. The default
* notifiable will use the variables specified in this config file.
*/
'notifiable' => \Spatie\Backup\Notifications\Notifiable::class,
'notifiable' => Notifiable::class,
'mail' => [
'to' => 'your@example.com',
@@ -257,8 +268,8 @@ return [
'name' => env('APP_NAME', 'laravel-backup'),
'disks' => ['backups'],
'health_checks' => [
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1,
\Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000,
MaximumAgeInDays::class => 1,
MaximumStorageInMegabytes::class => 5000,
],
],
@@ -284,7 +295,7 @@ return [
* No matter how you configure it the default strategy will never
* delete the newest backup.
*/
'strategy' => \Spatie\Backup\Tasks\Cleanup\Strategies\DefaultStrategy::class,
'strategy' => DefaultStrategy::class,
'default_strategy' => [
/*

View File

@@ -105,4 +105,17 @@ return [
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
/*
|--------------------------------------------------------------------------
| Serializable Classes
|--------------------------------------------------------------------------
|
| This option allows you to specify which classes may be unserialized from
| the cache to help prevent PHP deserialization gadget chain attacks.
| Set to false to allow no classes to be unserialized from cache.
|
*/
'serializable_classes' => false,
];

View File

@@ -1,5 +1,9 @@
<?php
use PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v4\ExportToCsv;
use PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v4\ExportToXLS;
use PowerComponents\LivewirePowerGrid\Themes\Tailwind;
return [
/*
@@ -11,7 +15,7 @@ return [
| Configure here the theme of your choice.
*/
'theme' => \PowerComponents\LivewirePowerGrid\Themes\Tailwind::class,
'theme' => Tailwind::class,
// 'theme' => \PowerComponents\LivewirePowerGrid\Themes\Bootstrap5::class,
/*
@@ -130,12 +134,12 @@ return [
'exportable' => [
'default' => 'openspout_v4',
'openspout_v4' => [
'xlsx' => \PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v4\ExportToXLS::class,
'csv' => \PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v4\ExportToCsv::class,
'xlsx' => ExportToXLS::class,
'csv' => ExportToCsv::class,
],
'openspout_v3' => [
'xlsx' => \PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v3\ExportToXLS::class,
'csv' => \PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v3\ExportToCsv::class,
'xlsx' => PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v3\ExportToXLS::class,
'csv' => PowerComponents\LivewirePowerGrid\Components\Exports\OpenSpout\v3\ExportToCsv::class,
],
],

View File

@@ -1,5 +1,7 @@
<?php
use Spatie\LaravelMarkdown\MarkdownRenderer;
return [
'code_highlighting' => [
/*
@@ -64,7 +66,7 @@ return [
*
* More info: https://spatie.be/docs/laravel-markdown/v1/advanced-usage/customizing-the-rendering-process
*/
'renderer_class' => Spatie\LaravelMarkdown\MarkdownRenderer::class,
'renderer_class' => MarkdownRenderer::class,
/*
* These extensions should be added to the markdown environment. A valid

View File

@@ -1,5 +1,30 @@
<?php
use Spatie\ImageOptimizer\Optimizers\Avifenc;
use Spatie\ImageOptimizer\Optimizers\Cwebp;
use Spatie\ImageOptimizer\Optimizers\Gifsicle;
use Spatie\ImageOptimizer\Optimizers\Jpegoptim;
use Spatie\ImageOptimizer\Optimizers\Optipng;
use Spatie\ImageOptimizer\Optimizers\Pngquant;
use Spatie\ImageOptimizer\Optimizers\Svgo;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Avif;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Image;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Svg;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Video;
use Spatie\MediaLibrary\Conversions\ImageGenerators\Webp;
use Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob;
use Spatie\MediaLibrary\Downloaders\DefaultDownloader;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob;
use Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred;
use Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator;
use Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer;
use Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover;
use Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator;
use Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator;
use Spatie\MediaLibraryPro\Models\TemporaryUpload;
return [
/*
@@ -39,7 +64,7 @@ return [
/*
* The fully qualified class name of the media model.
*/
'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
'media_model' => Media::class,
/*
* When enabled, media collections will be serialised using the default
@@ -54,7 +79,7 @@ return [
*
* This model is only used in Media Library Pro (https://medialibrary.pro)
*/
'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class,
'temporary_upload_model' => TemporaryUpload::class,
/*
* When enabled, Media Library Pro will only process temporary uploads that were uploaded
@@ -71,17 +96,17 @@ return [
/*
* This is the class that is responsible for naming generated files.
*/
'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
'file_namer' => DefaultFileNamer::class,
/*
* The class that contains the strategy for determining a media file's path.
*/
'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class,
'path_generator' => DefaultPathGenerator::class,
/*
* The class that contains the strategy for determining how to remove files.
*/
'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class,
'file_remover_class' => DefaultFileRemover::class,
/*
* Here you can specify which path generator should be used for the given class.
@@ -96,7 +121,7 @@ return [
* When urls to files get generated, this class will be called. Use the default
* if your files are stored locally above the site root or on s3.
*/
'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class,
'url_generator' => DefaultUrlGenerator::class,
/*
* Moves media on updating to keep path consistent. Enable it only with a custom
@@ -116,34 +141,34 @@ return [
* the optimizers that will be used by default.
*/
'image_optimizers' => [
Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [
Jpegoptim::class => [
'-m85', // set maximum quality to 85%
'--force', // ensure that progressive generation is always done also if a little bigger
'--strip-all', // this strips out all text information such as comments and EXIF data
'--all-progressive', // this will make sure the resulting image is a progressive one
],
Spatie\ImageOptimizer\Optimizers\Pngquant::class => [
Pngquant::class => [
'--force', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Optipng::class => [
Optipng::class => [
'-i0', // this will result in a non-interlaced, progressive scanned image
'-o2', // this set the optimization level to two (multiple IDAT compression trials)
'-quiet', // required parameter for this package
],
Spatie\ImageOptimizer\Optimizers\Svgo::class => [
Svgo::class => [
'--disable=cleanupIDs', // disabling because it is known to cause troubles
],
Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [
Gifsicle::class => [
'-b', // required parameter for this package
'-O3', // this produces the slowest but best results
],
Spatie\ImageOptimizer\Optimizers\Cwebp::class => [
Cwebp::class => [
'-m 6', // for the slowest compression method in order to get the best compression.
'-pass 10', // for maximizing the amount of analysis pass.
'-mt', // multithreading for some speed improvements.
'-q 90', // quality factor that brings the least noticeable changes.
],
Spatie\ImageOptimizer\Optimizers\Avifenc::class => [
Avifenc::class => [
'-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63).
'-j all', // number of jobs (worker threads, "all" uses all available cores).
'--min 0', // min quantizer for color (0-63).
@@ -159,12 +184,12 @@ return [
* These generators will be used to create an image of media files.
*/
'image_generators' => [
Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class,
Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class,
Image::class,
Webp::class,
Avif::class,
Pdf::class,
Svg::class,
Video::class,
],
/*
@@ -192,8 +217,8 @@ return [
* your custom jobs extend the ones provided by the package.
*/
'jobs' => [
'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class,
'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class,
'perform_conversions' => PerformConversionsJob::class,
'generate_responsive_images' => GenerateResponsiveImagesJob::class,
],
/*
@@ -201,7 +226,7 @@ return [
* This is particularly useful when the url of the image is behind a firewall and
* need to add additional flags, possibly using curl.
*/
'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class,
'media_downloader' => DefaultDownloader::class,
/*
* When using the addMediaFromUrl method the SSL is verified by default.
@@ -232,7 +257,7 @@ return [
*
* https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images
*/
'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class,
'width_calculator' => FileSizeOptimizedWidthCalculator::class,
/*
* By default rendering media to a responsive image will add some javascript and a tiny placeholder.
@@ -245,7 +270,7 @@ return [
* This class will generate the tiny placeholder used for progressive image loading. By default
* the media library will use a tiny blurred jpg image.
*/
'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class,
'tiny_placeholder_generator' => Blurred::class,
],
/*

View File

@@ -2,10 +2,12 @@
namespace Database\Factories;
use App\Enums\AssociationStatus;
use App\Models\EinundzwanzigPleb;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\EinundzwanzigPleb>
* @extends Factory<EinundzwanzigPleb>
*/
class EinundzwanzigPlebFactory extends Factory
{
@@ -20,14 +22,14 @@ class EinundzwanzigPlebFactory extends Factory
'pubkey' => $this->faker->sha256(),
'npub' => $this->faker->word(),
'email' => $this->faker->safeEmail(),
'association_status' => \App\Enums\AssociationStatus::DEFAULT,
'association_status' => AssociationStatus::DEFAULT,
];
}
public function active(): static
{
return $this->state(fn (array $attributes) => [
'association_status' => \App\Enums\AssociationStatus::ACTIVE,
'association_status' => AssociationStatus::ACTIVE,
]);
}
@@ -35,13 +37,13 @@ class EinundzwanzigPlebFactory extends Factory
{
return $this->state(fn (array $attributes) => [
'npub' => config('einundzwanzig.config.current_board')[0],
'association_status' => \App\Enums\AssociationStatus::HONORARY,
'association_status' => AssociationStatus::HONORARY,
]);
}
public function withPaidCurrentYear(): static
{
return $this->afterCreating(function (\App\Models\EinundzwanzigPleb $pleb) {
return $this->afterCreating(function (EinundzwanzigPleb $pleb) {
$pleb->paymentEvents()->create([
'year' => date('Y'),
'amount' => 21000,

View File

@@ -2,10 +2,11 @@
namespace Database\Factories;
use App\Models\Election;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Election>
* @extends Factory<Election>
*/
class ElectionFactory extends Factory
{

View File

@@ -2,10 +2,13 @@
namespace Database\Factories;
use App\Enums\NewsCategory;
use App\Models\EinundzwanzigPleb;
use App\Models\Notification;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Notification>
* @extends Factory<Notification>
*/
class NotificationFactory extends Factory
{
@@ -19,8 +22,8 @@ class NotificationFactory extends Factory
return [
'name' => $this->faker->sentence(3),
'description' => $this->faker->paragraph(),
'category' => $this->faker->randomElement(\App\Enums\NewsCategory::cases()),
'einundzwanzig_pleb_id' => \App\Models\EinundzwanzigPleb::factory(),
'category' => $this->faker->randomElement(NewsCategory::cases()),
'einundzwanzig_pleb_id' => EinundzwanzigPleb::factory(),
];
}
}

View File

@@ -2,17 +2,19 @@
namespace Database\Factories;
use App\Models\EinundzwanzigPleb;
use App\Models\PaymentEvent;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\PaymentEvent>
* @extends Factory<PaymentEvent>
*/
class PaymentEventFactory extends Factory
{
public function definition(): array
{
return [
'einundzwanzig_pleb_id' => \App\Models\EinundzwanzigPleb::factory(),
'einundzwanzig_pleb_id' => EinundzwanzigPleb::factory(),
'year' => fake()->year(),
'event_id' => fake()->uuid(),
'amount' => 21000,

View File

@@ -2,10 +2,12 @@
namespace Database\Factories;
use App\Models\EinundzwanzigPleb;
use App\Models\ProjectProposal;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ProjectProposal>
* @extends Factory<ProjectProposal>
*/
class ProjectProposalFactory extends Factory
{
@@ -17,7 +19,7 @@ class ProjectProposalFactory extends Factory
public function definition(): array
{
return [
'einundzwanzig_pleb_id' => \App\Models\EinundzwanzigPleb::factory(),
'einundzwanzig_pleb_id' => EinundzwanzigPleb::factory(),
'name' => $this->faker->sentence(3),
'description' => $this->faker->paragraph(),
'support_in_sats' => $this->faker->numberBetween(10000, 1000000),

View File

@@ -2,12 +2,13 @@
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
* @extends Factory<User>
*/
class UserFactory extends Factory
{

View File

@@ -107,10 +107,15 @@ class extends Component
'description' => $this->form['description'],
'support_in_sats' => (int) $this->form['support_in_sats'],
'website' => $this->form['website'],
'accepted' => $canAccept ? (bool) $this->form['accepted'] : $this->project->accepted,
'sats_paid' => $canAccept ? $this->form['sats_paid'] : $this->project->sats_paid,
]);
// Update admin-only fields directly if user has permission
if ($canAccept) {
$this->project->accepted = (bool) $this->form['accepted'];
$this->project->sats_paid = $this->form['sats_paid'];
$this->project->save();
}
if ($this->file) {
$this->project->addMedia($this->file)->toMediaCollection('main');
}

View File

@@ -1,5 +1,6 @@
<?php
use App\Support\NostrAuth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Session;
@@ -41,7 +42,7 @@ Route::get('media/{media}', function (Media $media, Request $request) {
->middleware('signed');
Route::post('logout', function () {
\App\Support\NostrAuth::logout();
NostrAuth::logout();
Session::flush();
return redirect('/');

View File

@@ -22,15 +22,15 @@ beforeEach(function () {
'event_id' => 'test_event_'.Str::random(40),
]);
$this->project = ProjectProposal::query()->create([
'einundzwanzig_pleb_id' => $this->pleb->id,
'name' => 'Original Project',
'description' => 'Original Description',
'support_in_sats' => 21000,
'website' => 'https://original.example.com',
'accepted' => false,
'sats_paid' => 0,
]);
$this->project = new ProjectProposal;
$this->project->einundzwanzig_pleb_id = $this->pleb->id;
$this->project->name = 'Original Project';
$this->project->description = 'Original Description';
$this->project->support_in_sats = 21000;
$this->project->website = 'https://original.example.com';
$this->project->accepted = false;
$this->project->sats_paid = 0;
$this->project->save();
// Get board member pubkeys from config
$this->boardMember = EinundzwanzigPleb::query()->create([

View File

@@ -1,6 +1,7 @@
<?php
use App\Models\ProjectProposal;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
it('serves original media via signed route', function () {
@@ -9,7 +10,7 @@ it('serves original media via signed route', function () {
$project = ProjectProposal::factory()->create();
$project->addMedia(
\Illuminate\Http\UploadedFile::fake()->image('test.jpg', 100, 100)
UploadedFile::fake()->image('test.jpg', 100, 100)
)->toMediaCollection('main');
$media = $project->getFirstMedia('main');
@@ -25,7 +26,7 @@ it('serves conversion media via signed route when conversion parameter is provid
$project = ProjectProposal::factory()->create();
$project->addMedia(
\Illuminate\Http\UploadedFile::fake()->image('test.jpg', 500, 500)
UploadedFile::fake()->image('test.jpg', 500, 500)
)->toMediaCollection('main');
$media = $project->getFirstMedia('main');
@@ -44,7 +45,7 @@ it('falls back to original when conversion does not exist', function () {
$project = ProjectProposal::factory()->create();
$project->addMedia(
\Illuminate\Http\UploadedFile::fake()->image('test.jpg', 100, 100)
UploadedFile::fake()->image('test.jpg', 100, 100)
)->toMediaCollection('main');
$media = $project->getFirstMedia('main');
@@ -63,7 +64,7 @@ it('rejects unsigned media requests', function () {
$project = ProjectProposal::factory()->create();
$project->addMedia(
\Illuminate\Http\UploadedFile::fake()->image('test.jpg', 100, 100)
UploadedFile::fake()->image('test.jpg', 100, 100)
)->toMediaCollection('main');
$media = $project->getFirstMedia('main');
@@ -77,7 +78,7 @@ it('generates signed url with conversion parameter', function () {
$project = ProjectProposal::factory()->create();
$project->addMedia(
\Illuminate\Http\UploadedFile::fake()->image('test.jpg', 500, 500)
UploadedFile::fake()->image('test.jpg', 500, 500)
)->toMediaCollection('main');
$urlWithoutConversion = $project->getSignedMediaUrl('main');

View File

@@ -1,6 +1,7 @@
<?php
use App\Auth\NostrUser;
use App\Enums\AssociationStatus;
use App\Models\EinundzwanzigPleb;
use App\Models\Election;
use Illuminate\Support\Facades\Gate;
@@ -77,7 +78,7 @@ it('allows active member to vote in an election', function () {
it('allows honorary member to vote in an election', function () {
$pleb = EinundzwanzigPleb::factory()->create([
'association_status' => \App\Enums\AssociationStatus::HONORARY,
'association_status' => AssociationStatus::HONORARY,
]);
$election = Election::factory()->create();
$nostrUser = new NostrUser($pleb->pubkey);
@@ -87,7 +88,7 @@ it('allows honorary member to vote in an election', function () {
it('denies passive member from voting in an election', function () {
$pleb = EinundzwanzigPleb::factory()->create([
'association_status' => \App\Enums\AssociationStatus::PASSIVE,
'association_status' => AssociationStatus::PASSIVE,
]);
$election = Election::factory()->create();
$nostrUser = new NostrUser($pleb->pubkey);

View File

@@ -1,6 +1,7 @@
<?php
use App\Auth\NostrUser;
use App\Enums\AssociationStatus;
use App\Models\EinundzwanzigPleb;
use App\Models\ProjectProposal;
use Illuminate\Support\Facades\Gate;
@@ -48,7 +49,7 @@ it('denies creation for active member without paid membership', function () {
it('denies creation for passive member without paid membership', function () {
$pleb = EinundzwanzigPleb::factory()->create([
'association_status' => \App\Enums\AssociationStatus::PASSIVE,
'association_status' => AssociationStatus::PASSIVE,
]);
$nostrUser = new NostrUser($pleb->pubkey);
@@ -57,7 +58,7 @@ it('denies creation for passive member without paid membership', function () {
it('allows passive member with paid membership to create project proposals', function () {
$pleb = EinundzwanzigPleb::factory()->withPaidCurrentYear()->create([
'association_status' => \App\Enums\AssociationStatus::PASSIVE,
'association_status' => AssociationStatus::PASSIVE,
]);
$nostrUser = new NostrUser($pleb->pubkey);

View File

@@ -2,6 +2,7 @@
use App\Models\EinundzwanzigPleb;
use App\Models\ProjectProposal;
use App\Models\Vote;
use App\Support\NostrAuth;
use Illuminate\Support\Facades\RateLimiter;
use Livewire\Livewire;
@@ -46,7 +47,7 @@ test('voting actions are rate limited after 10 attempts', function () {
RateLimiter::attempt('voting:127.0.0.1', 10, function () {});
}
Livewire::test('association.project-support.show', ['projectProposal' => $project->slug])
Livewire::test('association.project-support.show', ['projectProposal' => $project])
->call('handleApprove')
->assertStatus(429);
});
@@ -93,7 +94,7 @@ test('project proposal update is rate limited after 5 attempts', function () {
RateLimiter::attempt('project-proposal-update:127.0.0.1', 5, function () {});
}
Livewire::test('association.project-support.form.edit', ['projectProposal' => $project->slug])
Livewire::test('association.project-support.form.edit', ['projectProposal' => $project])
->set('form.name', 'Updated Name')
->call('update')
->assertStatus(429);
@@ -105,11 +106,11 @@ test('voting works within rate limit', function () {
NostrAuth::login($pleb->pubkey);
Livewire::test('association.project-support.show', ['projectProposal' => $project->slug])
Livewire::test('association.project-support.show', ['projectProposal' => $project])
->call('handleApprove')
->assertHasNoErrors();
$vote = \App\Models\Vote::query()
$vote = Vote::query()
->where('project_proposal_id', $project->id)
->where('einundzwanzig_pleb_id', $pleb->id)
->first();

View File

@@ -1,5 +1,8 @@
<?php
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/*
|--------------------------------------------------------------------------
| Test Case
@@ -12,8 +15,8 @@
*/
uses(
Tests\TestCase::class,
Illuminate\Foundation\Testing\RefreshDatabase::class,
TestCase::class,
RefreshDatabase::class,
)->in('Feature');
/*