From 51d43df6f9895ad559e76b1da68ce03e4a48ea61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20Ioni=C8=9B=C4=83?= Date: Thu, 24 Oct 2024 14:05:46 +0100 Subject: [PATCH] wip --- Dockerfile | 5 + app/Enums/Cron.php | 52 ++++++ app/Exceptions/InvalidSourceUrlException.php | 12 ++ app/Exceptions/MissingSourceUrlException.php | 12 ++ app/Filament/Admin/Pages/ElectionSettings.php | 24 +++ .../ElectionResource/Pages/CreateElection.php | 13 -- .../ElectionResource/Pages/EditElection.php | 21 --- .../ScheduledJobRelationManager.php | 105 ++++++++++++ app/Imports/Siruta/CountiesImport.php | 50 +++++- app/Jobs/DummyJob.php | 20 +++ .../Turnout/FetchTurnoutDataJob.php | 83 ++++++++++ .../Turnout/ImportCountyTurnoutJob.php | 95 +++++++++++ app/Jobs/SchedulableJob.php | 45 ++++++ app/Models/County.php | 8 +- app/Models/Election.php | 12 ++ app/Models/ScheduledJob.php | 131 +++++++++++++++ app/Models/Turnout.php | 12 +- app/Models/User.php | 2 + app/Policies/ScheduledJobPolicy.php | 67 ++++++++ app/Providers/AppServiceProvider.php | 20 +++ app/Providers/Filament/AdminPanelProvider.php | 8 + composer.json | 6 +- composer.lock | 149 +++++++++--------- config/horizon.php | 1 + config/import.php | 11 ++ config/queue.php | 2 +- config/session.php | 2 +- database/data/siruta.sql | 86 +++++----- database/factories/ElectionFactory.php | 7 + database/factories/ScheduledJobFactory.php | 39 +++++ .../0001_01_01_000000_create_users_table.php | 9 -- ...05_create_personal_access_tokens_table.php | 27 ++++ ...> 0001_01_01_000010_create_jobs_table.php} | 0 ...1_000100_create_breezy_sessions_table.php} | 0 ... 0001_01_01_001000_create_media_table.php} | 0 ...0001_01_02_000000_create_siruta_tables.php | 4 +- ..._02_000005_create_scheduled_jobs_table.php | 37 +++++ ...001_01_10_000000_create_turnouts_table.php | 33 +++- database/seeders/DatabaseSeeder.php | 4 +- lang/ro/admin.php | 7 + lang/ro/app.php | 26 +++ 41 files changed, 1070 insertions(+), 177 deletions(-) create mode 100644 app/Enums/Cron.php create mode 100644 app/Exceptions/InvalidSourceUrlException.php create mode 100644 app/Exceptions/MissingSourceUrlException.php create mode 100644 app/Filament/Admin/Pages/ElectionSettings.php delete mode 100644 app/Filament/Admin/Resources/ElectionResource/Pages/CreateElection.php delete mode 100644 app/Filament/Admin/Resources/ElectionResource/Pages/EditElection.php create mode 100644 app/Filament/Admin/Resources/ElectionResource/RelationManagers/ScheduledJobRelationManager.php create mode 100644 app/Jobs/DummyJob.php create mode 100644 app/Jobs/Europarl240609/Turnout/FetchTurnoutDataJob.php create mode 100644 app/Jobs/Europarl240609/Turnout/ImportCountyTurnoutJob.php create mode 100644 app/Jobs/SchedulableJob.php create mode 100644 app/Models/ScheduledJob.php create mode 100644 app/Policies/ScheduledJobPolicy.php create mode 100644 config/import.php create mode 100644 database/factories/ScheduledJobFactory.php create mode 100644 database/migrations/0001_01_01_000005_create_personal_access_tokens_table.php rename database/migrations/{0001_01_01_000002_create_jobs_table.php => 0001_01_01_000010_create_jobs_table.php} (100%) rename database/migrations/{0001_01_01_000003_create_breezy_sessions_table.php => 0001_01_01_000100_create_breezy_sessions_table.php} (100%) rename database/migrations/{0001_01_01_000004_create_media_table.php => 0001_01_01_001000_create_media_table.php} (100%) create mode 100644 database/migrations/0001_01_02_000005_create_scheduled_jobs_table.php diff --git a/Dockerfile b/Dockerfile index d59252d..f94010c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,6 +38,11 @@ RUN set -ex; \ FROM vendor +# Needed for splitting CSVs +RUN set -ex; \ + apk add --no-cache \ + gawk; + COPY docker/s6-rc.d /etc/s6-overlay/s6-rc.d COPY --from=assets --chown=www-data:www-data /build/public/build /var/www/public/build diff --git a/app/Enums/Cron.php b/app/Enums/Cron.php new file mode 100644 index 0000000..83d9393 --- /dev/null +++ b/app/Enums/Cron.php @@ -0,0 +1,52 @@ + __('app.cron.every_minute'), + self::EVERY_2_MINUTES => __('app.cron.every_2_minutes'), + self::EVERY_3_MINUTES => __('app.cron.every_3_minutes'), + self::EVERY_4_MINUTES => __('app.cron.every_4_minutes'), + self::EVERY_5_MINUTES => __('app.cron.every_5_minutes'), + self::EVERY_10_MINUTES => __('app.cron.every_10_minutes'), + self::EVERY_5_1_MINUTES => __('app.cron.every_5_1_minutes'), + self::EVERY_5_2_MINUTES => __('app.cron.every_5_2_minutes'), + self::EVERY_5_3_MINUTES => __('app.cron.every_5_3_minutes'), + self::EVERY_5_4_MINUTES => __('app.cron.every_5_4_minutes'), + self::EVERY_10_5_MINUTES => __('app.cron.every_10_5_minutes'), + self::EVERY_10_6_MINUTES => __('app.cron.every_10_6_minutes'), + self::EVERY_10_7_MINUTES => __('app.cron.every_10_7_minutes'), + self::EVERY_10_8_MINUTES => __('app.cron.every_10_8_minutes'), + self::EVERY_10_9_MINUTES => __('app.cron.every_10_9_minutes'), + }; + } +} diff --git a/app/Exceptions/InvalidSourceUrlException.php b/app/Exceptions/InvalidSourceUrlException.php new file mode 100644 index 0000000..0b3707c --- /dev/null +++ b/app/Exceptions/InvalidSourceUrlException.php @@ -0,0 +1,12 @@ +schema([ + Select::make('job') + ->label(__('admin.field.job')) + ->options(function () { + ClassFinder::disablePSR4Vendors(); + + $classes = ClassFinder::getClassesInNamespace('App\Jobs', ClassFinder::RECURSIVE_MODE); + + return collect($classes) + ->filter(fn (string $class) => is_subclass_of($class, SchedulableJob::class)) + ->mapWithKeys(fn (string $job) => [ + $job => $job::name(), + ]); + }), + + Select::make('cron') + ->label(__('admin.field.cron')) + ->options(Cron::options()) + ->enum(Cron::class) + ->required(), + + Fieldset::make('source') + ->label('Source') + ->schema([ + TextInput::make('source_url') + ->label(__('admin.field.source_url')) + ->columnSpanFull(), + + TextInput::make('source_part') + ->label(__('admin.field.source_part')), + + TextInput::make('source_username') + ->label(__('admin.field.source_username')), + + TextInput::make('source_password') + ->label(__('admin.field.source_password')) + ->password(), + ]), + ]); + } + + public function table(Table $table): Table + { + return $table + ->recordTitleAttribute('job') + ->columns([ + ToggleColumn::make('is_enabled') + ->label(__('admin.field.is_enabled')) + ->shrink(), + + TextColumn::make('job') + ->label(__('admin.field.job')) + ->description(fn (string $state) => $state, 'above') + ->formatStateUsing(fn (string $state) => $state::name()), + + TextColumn::make('cron') + ->label(__('admin.field.cron')), + + TextColumn::make('last_run_at') + ->label(__('admin.field.last_run_at')) + ->since(), + ]) + ->filters([ + // + ]) + ->headerActions([ + Tables\Actions\CreateAction::make(), + ]) + ->actions([ + Tables\Actions\EditAction::make(), + Tables\Actions\DeleteAction::make(), + ]); + } + + public function isReadOnly(): bool + { + return false; + } +} diff --git a/app/Imports/Siruta/CountiesImport.php b/app/Imports/Siruta/CountiesImport.php index 9c05f01..913c366 100644 --- a/app/Imports/Siruta/CountiesImport.php +++ b/app/Imports/Siruta/CountiesImport.php @@ -29,12 +29,12 @@ public function model(array $row): ?Model return new County([ 'id' => $row['siruta'], + 'code' => $this->getCountyCode($row['siruta']), 'name' => Str::of($row['denloc']) ->replace(['Ţ', 'Ş'], ['Ț', 'Ș']) ->title() ->remove(['Județul', 'Municipiul']) ->trim(), - ]); } @@ -42,4 +42,52 @@ public function batchSize(): int { return 50; } + + protected function getCountyCode(int $siruta): string + { + return [ + 10 => 'AB', + 29 => 'AR', + 38 => 'AG', + 47 => 'BC', + 56 => 'BH', + 65 => 'BN', + 74 => 'BT', + 83 => 'BV', + 92 => 'BR', + 109 => 'BZ', + 118 => 'CS', + 127 => 'CJ', + 136 => 'CT', + 145 => 'CV', + 154 => 'DB', + 163 => 'DJ', + 172 => 'GL', + 181 => 'GJ', + 190 => 'HR', + 207 => 'HD', + 216 => 'IL', + 225 => 'IS', + 234 => 'IF', + 243 => 'MM', + 252 => 'MH', + 261 => 'MS', + 270 => 'NT', + 289 => 'OT', + 298 => 'PH', + 305 => 'SM', + 314 => 'SJ', + 323 => 'SB', + 332 => 'SV', + 341 => 'TR', + 350 => 'TM', + 369 => 'TL', + 378 => 'VS', + 387 => 'VL', + 396 => 'VN', + 403 => 'B', + 519 => 'CL', + 528 => 'GR', + ][$siruta]; + } } diff --git a/app/Jobs/DummyJob.php b/app/Jobs/DummyJob.php new file mode 100644 index 0000000..1915dd0 --- /dev/null +++ b/app/Jobs/DummyJob.php @@ -0,0 +1,20 @@ +info('Dummy job executed', [ + 'job' => $this->scheduledJob, + ]); + } +} diff --git a/app/Jobs/Europarl240609/Turnout/FetchTurnoutDataJob.php b/app/Jobs/Europarl240609/Turnout/FetchTurnoutDataJob.php new file mode 100644 index 0000000..9ef42a7 --- /dev/null +++ b/app/Jobs/Europarl240609/Turnout/FetchTurnoutDataJob.php @@ -0,0 +1,83 @@ +deleteWhenDestroyed(); + + $cwd = $temporaryDirectory->path(); + + $tmpDisk = Storage::build([ + 'driver' => 'local', + 'root' => $cwd, + ]); + + $tmpDisk->put('turnout.csv', $this->scheduledJob->fetchSource()->resource()); + + // Split the CSV by county + Process::path($cwd) + ->run([ + config('import.awk_path'), + '-F,', + 'FNR==1 {header = $0; next} !seen[$1]++ {print header > $1".csv"} {print > $1".csv"}', + 'turnout.csv', + ]); + + $tmpDisk->delete('turnout.csv'); + + logger()->info('DispatchTurnoutJob', [ + 'path' => $cwd, + 'files' => $tmpDisk->allFiles(), + ]); + + collect($tmpDisk->allFiles()) + ->each(function (string $file) use ($tmpDisk) { + $this->scheduledJob->disk() + ->writeStream( + $this->scheduledJob->getSourcePath($file), + $tmpDisk->readStream($file) + ); + }); + + Bus::batch( + County::all() + ->map(fn (County $county) => new ImportCountyTurnoutJob($this->scheduledJob, $county)) + )->dispatch(); + } + + /** + * Get the tags that should be assigned to the job. + * + * @return array + */ + public function tags(): array + { + return [ + 'import', + 'turnout', + 'scheduled_job:' . $this->scheduledJob->id, + 'election:' . $this->scheduledJob->election_id, + static::name(), + ]; + } +} diff --git a/app/Jobs/Europarl240609/Turnout/ImportCountyTurnoutJob.php b/app/Jobs/Europarl240609/Turnout/ImportCountyTurnoutJob.php new file mode 100644 index 0000000..4de3f3c --- /dev/null +++ b/app/Jobs/Europarl240609/Turnout/ImportCountyTurnoutJob.php @@ -0,0 +1,95 @@ +scheduledJob = $scheduledJob; + $this->county = $county; + } + + public function handle(): void + { + $disk = $this->scheduledJob->disk(); + $path = $this->scheduledJob->getSourcePath("{$this->county->code}.csv"); + + if (! $disk->exists($path)) { + throw new Exception('Missing source file for county ' . $this->county->code); + } + + $reader = Reader::createFromStream($disk->readStream($path)); + $reader->setHeaderOffset(0); + + logger()->info('ImportCountyTurnoutJob', [ + 'county' => $this->county->code, + 'first' => $reader->count(), + ]); + + $values = collect(); + + $records = $reader->getRecords(); + foreach ($records as $record) { + $values->push([ + 'election_id' => $this->scheduledJob->election_id, + 'county_id' => $this->county->id, + 'locality_id' => $record['Siruta'], + 'section' => $record['Nr sectie de votare'], + + 'initial_permanent' => $record['Înscriși pe liste permanente'], + 'initial_complement' => 0, + 'permanent' => $record['LP'], + 'complement' => $record['LC'], + 'supplement' => $record['LS'], + 'mobile' => $record['UM'], + ]); + } + + Turnout::upsert( + $values->all(), + ['election_id', 'county_id', 'section'], + ['initial_permanent', 'initial_complement', 'permanent', 'complement', 'supplement', 'mobile'] + ); + } + + /** + * Get the tags that should be assigned to the job. + * + * @return array + */ + public function tags(): array + { + return [ + 'import', + 'turnout', + 'scheduled_job:' . $this->scheduledJob->id, + 'election:' . $this->scheduledJob->election_id, + 'county:' . $this->county->code, + ]; + } +} diff --git a/app/Jobs/SchedulableJob.php b/app/Jobs/SchedulableJob.php new file mode 100644 index 0000000..0c483e3 --- /dev/null +++ b/app/Jobs/SchedulableJob.php @@ -0,0 +1,45 @@ +scheduledJob = $scheduledJob; + } + + abstract public function execute(): void; + + abstract public static function name(): string; + + /** + * Execute the job. + */ + final public function handle(): void + { + $this->execute(); + + $this->scheduledJob->touch('last_run_at'); + } +} diff --git a/app/Models/County.php b/app/Models/County.php index b72e02f..891dbeb 100644 --- a/app/Models/County.php +++ b/app/Models/County.php @@ -17,6 +17,7 @@ class County extends Model protected $fillable = [ 'id', + 'code', 'name', ]; @@ -39,6 +40,7 @@ public function toSearchableArray(): array { return [ 'id' => (string) $this->id, + 'code' => $this->code, 'name' => $this->name, ]; } @@ -52,6 +54,10 @@ public static function getTypesenseModelSettings(): array 'name' => 'id', 'type' => 'string', ], + [ + 'name' => 'code', + 'type' => 'string', + ], [ 'name' => 'name', 'type' => 'string', @@ -59,7 +65,7 @@ public static function getTypesenseModelSettings(): array ], ], 'search-parameters' => [ - 'query_by' => 'name', + 'query_by' => 'name,code,id', ], ]; } diff --git a/app/Models/Election.php b/app/Models/Election.php index a7f1230..9a709db 100644 --- a/app/Models/Election.php +++ b/app/Models/Election.php @@ -7,9 +7,11 @@ use Database\Factories\ElectionFactory; use Filament\Models\Contracts\HasAvatar; use Filament\Models\Contracts\HasName; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class Election extends Model implements HasName, HasAvatar { @@ -39,6 +41,16 @@ public function type(): BelongsTo return $this->belongsTo(ElectionType::class); } + public function scheduledJobs(): HasMany + { + return $this->hasMany(ScheduledJob::class); + } + + public function scopeWhereLive(Builder $query): Builder + { + return $query->where('is_live', true); + } + public function getFilamentName(): string { return \sprintf('%s: %s', $this->year, $this->title); diff --git a/app/Models/ScheduledJob.php b/app/Models/ScheduledJob.php new file mode 100644 index 0000000..1d39a14 --- /dev/null +++ b/app/Models/ScheduledJob.php @@ -0,0 +1,131 @@ + */ + use HasFactory; + + protected static string $factory = ScheduledJobFactory::class; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'job', + 'cron', + 'is_enabled', + 'source_part', + 'source_url', + 'source_username', + 'source_password', + 'election_id', + 'last_run_at', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'source_username', + 'source_password', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'cron' => Cron::class, + 'is_enabled' => 'boolean', + 'source_username' => 'encrypted', + 'source_password' => 'encrypted', + 'last_run_at' => 'datetime', + ]; + } + + public function election(): BelongsTo + { + return $this->belongsTo(Election::class); + } + + public function requiresAuthentication(): bool + { + return filled($this->source_username) && filled($this->source_password); + } + + /** + * @param array $map + * @return string + * @throws MissingSourceUrlException + * @throws InvalidSourceUrlException + */ + public function getPreparedSourceUrl(array $map = []): string + { + if (blank($this->source_url)) { + throw new MissingSourceUrlException(); + } + + $map = array_merge($map, [ + '{{part}}' => $this->source_part, + ]); + + $search = array_keys($map); + $replace = array_values($map); + + $url = filter_var( + Str::replace($search, $replace, $this->source_url, caseSensitive: false), + \FILTER_SANITIZE_URL + ); + + if (filter_var($url, \FILTER_VALIDATE_URL) === false) { + throw new InvalidSourceUrlException(); + } + + return $url; + } + + public function fetchSource(array $map = []): Response + { + return Http::createPendingRequest() + ->when($this->requiresAuthentication(), function (PendingRequest $request) { + return $request->withBasicAuth($this->source_username, $this->source_password); + }) + ->get($this->getPreparedSourceUrl($map)) + ->throw(); + } + + public function disk(): Filesystem + { + return Storage::disk(config('import.disk')); + } + + public function getSourcePath(string $filename): string + { + return \sprintf('source/%s/%s', $this->election_id, $filename); + } +} diff --git a/app/Models/Turnout.php b/app/Models/Turnout.php index 5063120..da61eb2 100644 --- a/app/Models/Turnout.php +++ b/app/Models/Turnout.php @@ -8,12 +8,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Znck\Eloquent\Relations\BelongsToThrough; -use Znck\Eloquent\Traits\BelongsToThrough as BelongsToThroughTrait; class Turnout extends Model { - use BelongsToThroughTrait; /** @use HasFactory */ use HasFactory; @@ -34,6 +31,11 @@ class Turnout extends Model 'supplement', 'mobile', 'has_issues', + 'country_id', + 'county_id', + 'locality_id', + 'election_id', + 'section', ]; /** @@ -64,9 +66,9 @@ public function country(): BelongsTo return $this->belongsTo(Country::class); } - public function county(): BelongsToThrough + public function county(): BelongsTo { - return $this->belongsToThrough(County::class, Locality::class); + return $this->belongsTo(County::class); } public function locality(): BelongsTo diff --git a/app/Models/User.php b/app/Models/User.php index 0c6cf06..354c991 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -20,12 +20,14 @@ use Illuminate\Support\Collection; use Illuminate\Support\Str; use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable; +use Laravel\Sanctum\HasApiTokens; use Spatie\Image\Enums\Fit; use Spatie\MediaLibrary\HasMedia; use Spatie\MediaLibrary\InteractsWithMedia; class User extends Authenticatable implements FilamentUser, HasAvatar, HasName, HasMedia, HasTenants { + use HasApiTokens; use HasRole; /** @use HasFactory */ use HasFactory; diff --git a/app/Policies/ScheduledJobPolicy.php b/app/Policies/ScheduledJobPolicy.php new file mode 100644 index 0000000..d3f1210 --- /dev/null +++ b/app/Policies/ScheduledJobPolicy.php @@ -0,0 +1,67 @@ +isAdmin(); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, ScheduledJob $scheduledJob): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, ScheduledJob $scheduledJob): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, ScheduledJob $scheduledJob): bool + { + return $user->isAdmin(); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, ScheduledJob $scheduledJob): bool + { + return $this->delete($user, $scheduledJob); + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, ScheduledJob $scheduledJob): bool + { + return $this->delete($user, $scheduledJob); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 5cc877e..43c2db7 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,6 +4,8 @@ namespace App\Providers; +use App\Models\ScheduledJob; +use Illuminate\Console\Scheduling\Schedule; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Str; @@ -33,6 +35,8 @@ public function boot(): void Model::preventAccessingMissingAttributes($shouldBeEnabled); }); + $this->resolveSchedule(); + $this->setSeoDefaults(); } @@ -57,4 +61,20 @@ protected function setSeoDefaults(): void ->favicon() ->twitter(); } + + protected function resolveSchedule(): void + { + $this->app->resolving(Schedule::class, function (Schedule $schedule) { + ScheduledJob::query() + ->with('election') + ->where('is_enabled', true) + ->each( + fn (ScheduledJob $job) => $schedule + ->job(new $job->job($job)) + ->cron($job->cron->value) + ->withoutOverlapping() + ->onOneServer() + ); + }); + } } diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index d212820..4ed732d 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -5,13 +5,16 @@ namespace App\Providers\Filament; use App\Filament\Admin\Pages\Auth\Login; +use App\Filament\Admin\Resources\ElectionResource; use App\Models\Election; +use Filament\Facades\Filament; use Filament\Forms\Components\DateTimePicker; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Infolist; +use Filament\Navigation\NavigationItem; use Filament\Pages; use Filament\Panel; use Filament\PanelProvider; @@ -78,6 +81,11 @@ public function panel(Panel $panel): Panel Authenticate::class, ]) ->sidebarCollapsibleOnDesktop() + ->navigationItems([ + NavigationItem::make('Settings') + ->icon('heroicon-o-cog') + ->url(fn () => ElectionResource::getUrl('view', ['record' => Filament::getTenant()])), + ]) ->collapsibleNavigationGroups(false); } diff --git a/composer.json b/composer.json index a891c2c..c7c5bdd 100644 --- a/composer.json +++ b/composer.json @@ -14,16 +14,18 @@ "filament/spatie-laravel-media-library-plugin": "^3.2", "haydenpierce/class-finder": "^0.5.3", "jeffgreco13/filament-breezy": "^2.4", - "laravel/framework": "^11.28", + "laravel/framework": "^11.29", "laravel/horizon": "^5.29", "laravel/sanctum": "^4.0", "laravel/scout": "^10.11", "laravel/tinker": "^2.10", + "league/csv": "^9.18", "league/flysystem-aws-s3-v3": "^3.29", "league/flysystem-read-only": "^3.28", "livewire/livewire": "^3.5", "maatwebsite/excel": "^3.1", "sentry/sentry-laravel": "^4.9", + "spatie/temporary-directory": "^2.2", "staudenmeir/belongs-to-through": "^2.16", "tpetry/laravel-query-expressions": "^1.4", "typesense/typesense-php": "^4.9" @@ -35,7 +37,7 @@ "friendsofphp/php-cs-fixer": "^3.64", "larastan/larastan": "^2.9", "laravel/pint": "^1.18", - "laravel/sail": "^1.36", + "laravel/sail": "^1.37", "laravel/telescope": "^5.2", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.5", diff --git a/composer.lock b/composer.lock index a455ef0..83cc6e5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7d03ee803920b9dd848bf37fab385378", + "content-hash": "fd641d859ab7f60839b6d6bdd715f19b", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -132,16 +132,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.6", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "a63485b65b6b3367039306496d49737cf1995408" + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", - "reference": "a63485b65b6b3367039306496d49737cf1995408", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { @@ -180,22 +180,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" }, - "time": "2024-06-13T17:21:28+00:00" + "time": "2024-10-18T22:15:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.324.6", + "version": "3.324.9", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7412a44da62fd607efbaac4084e69d6621f29de1" + "reference": "e6cb2597e6365c420f42d218a3a4e82a20df700d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7412a44da62fd607efbaac4084e69d6621f29de1", - "reference": "7412a44da62fd607efbaac4084e69d6621f29de1", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/e6cb2597e6365c420f42d218a3a4e82a20df700d", + "reference": "e6cb2597e6365c420f42d218a3a4e82a20df700d", "shasum": "" }, "require": { @@ -278,9 +278,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.324.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.324.9" }, - "time": "2024-10-18T18:06:33+00:00" + "time": "2024-10-23T18:06:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1510,16 +1510,16 @@ }, { "name": "filament/actions", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/actions.git", - "reference": "addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8" + "reference": "3b525a00a5ff3641825a5bacc00bb74a8a3ad370" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/actions/zipball/addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8", - "reference": "addd6a6f5e8e250a34c7c12bcb4060cc5ecbb9e8", + "url": "https://api.github.com/repos/filamentphp/actions/zipball/3b525a00a5ff3641825a5bacc00bb74a8a3ad370", + "reference": "3b525a00a5ff3641825a5bacc00bb74a8a3ad370", "shasum": "" }, "require": { @@ -1559,20 +1559,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-16T12:07:31+00:00" + "time": "2024-10-23T07:36:06+00:00" }, { "name": "filament/filament", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/panels.git", - "reference": "6ab7628f7a0c164d694a540dfe69b8d79484fd7d" + "reference": "1d17d655e0ee8c276ec51c0522346c78e592f9ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/panels/zipball/6ab7628f7a0c164d694a540dfe69b8d79484fd7d", - "reference": "6ab7628f7a0c164d694a540dfe69b8d79484fd7d", + "url": "https://api.github.com/repos/filamentphp/panels/zipball/1d17d655e0ee8c276ec51c0522346c78e592f9ac", + "reference": "1d17d655e0ee8c276ec51c0522346c78e592f9ac", "shasum": "" }, "require": { @@ -1624,20 +1624,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-17T09:39:37+00:00" + "time": "2024-10-23T07:36:20+00:00" }, { "name": "filament/forms", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/forms.git", - "reference": "9eaf88275da18777b1738514db6083e34b1700d4" + "reference": "562f5d78c2cb22cdea027496c92bd69a649b626e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/forms/zipball/9eaf88275da18777b1738514db6083e34b1700d4", - "reference": "9eaf88275da18777b1738514db6083e34b1700d4", + "url": "https://api.github.com/repos/filamentphp/forms/zipball/562f5d78c2cb22cdea027496c92bd69a649b626e", + "reference": "562f5d78c2cb22cdea027496c92bd69a649b626e", "shasum": "" }, "require": { @@ -1680,11 +1680,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-16T12:07:33+00:00" + "time": "2024-10-23T07:36:11+00:00" }, { "name": "filament/infolists", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/infolists.git", @@ -1735,16 +1735,16 @@ }, { "name": "filament/notifications", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/notifications.git", - "reference": "b56b155efd4290459d944a1b3d515b5aaf54846d" + "reference": "c19df07c801c5550de0d30957c5a316f53019533" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/notifications/zipball/b56b155efd4290459d944a1b3d515b5aaf54846d", - "reference": "b56b155efd4290459d944a1b3d515b5aaf54846d", + "url": "https://api.github.com/repos/filamentphp/notifications/zipball/c19df07c801c5550de0d30957c5a316f53019533", + "reference": "c19df07c801c5550de0d30957c5a316f53019533", "shasum": "" }, "require": { @@ -1783,20 +1783,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-16T12:07:28+00:00" + "time": "2024-10-23T07:36:14+00:00" }, { "name": "filament/spatie-laravel-media-library-plugin", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git", - "reference": "ee19b53a92b37fa28c8745cbc4f18f38d5cdeabb" + "reference": "35004964449944b98c15a698a1be842f23f6f7e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/spatie-laravel-media-library-plugin/zipball/ee19b53a92b37fa28c8745cbc4f18f38d5cdeabb", - "reference": "ee19b53a92b37fa28c8745cbc4f18f38d5cdeabb", + "url": "https://api.github.com/repos/filamentphp/spatie-laravel-media-library-plugin/zipball/35004964449944b98c15a698a1be842f23f6f7e6", + "reference": "35004964449944b98c15a698a1be842f23f6f7e6", "shasum": "" }, "require": { @@ -1820,20 +1820,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-07-24T12:10:24+00:00" + "time": "2024-10-23T07:36:33+00:00" }, { "name": "filament/support", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/support.git", - "reference": "85d83838ec0290fffba2be3f99a8b0840da1dc01" + "reference": "1b63dfd79ea37e940efad438717935081a61ba24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/support/zipball/85d83838ec0290fffba2be3f99a8b0840da1dc01", - "reference": "85d83838ec0290fffba2be3f99a8b0840da1dc01", + "url": "https://api.github.com/repos/filamentphp/support/zipball/1b63dfd79ea37e940efad438717935081a61ba24", + "reference": "1b63dfd79ea37e940efad438717935081a61ba24", "shasum": "" }, "require": { @@ -1879,20 +1879,20 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-16T12:07:44+00:00" + "time": "2024-10-23T07:36:38+00:00" }, { "name": "filament/tables", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/tables.git", - "reference": "fac7e8e9a787aef94f45938fd47b50ea3f9d3bff" + "reference": "69ff3ae5eaf4efc3fd92236f5b286147169b319d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filamentphp/tables/zipball/fac7e8e9a787aef94f45938fd47b50ea3f9d3bff", - "reference": "fac7e8e9a787aef94f45938fd47b50ea3f9d3bff", + "url": "https://api.github.com/repos/filamentphp/tables/zipball/69ff3ae5eaf4efc3fd92236f5b286147169b319d", + "reference": "69ff3ae5eaf4efc3fd92236f5b286147169b319d", "shasum": "" }, "require": { @@ -1931,11 +1931,11 @@ "issues": "https://github.com/filamentphp/filament/issues", "source": "https://github.com/filamentphp/filament" }, - "time": "2024-10-16T12:07:46+00:00" + "time": "2024-10-23T07:36:38+00:00" }, { "name": "filament/widgets", - "version": "v3.2.119", + "version": "v3.2.120", "source": { "type": "git", "url": "https://github.com/filamentphp/widgets.git", @@ -2763,16 +2763,16 @@ }, { "name": "laravel/framework", - "version": "v11.28.1", + "version": "v11.29.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c" + "reference": "425054512c362835ba9c0307561973c8eeac7385" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c", - "reference": "3ef5c8a85b4c598d5ffaf98afd72f6a5d6a0be2c", + "url": "https://api.github.com/repos/laravel/framework/zipball/425054512c362835ba9c0307561973c8eeac7385", + "reference": "425054512c362835ba9c0307561973c8eeac7385", "shasum": "" }, "require": { @@ -2968,20 +2968,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-16T16:32:21+00:00" + "time": "2024-10-22T14:13:31+00:00" }, { "name": "laravel/horizon", - "version": "v5.29.1", + "version": "v5.29.2", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "9f482f21c23ed01c2366d1157843165165579c23" + "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/9f482f21c23ed01c2366d1157843165165579c23", - "reference": "9f482f21c23ed01c2366d1157843165165579c23", + "url": "https://api.github.com/repos/laravel/horizon/zipball/d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", + "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", "shasum": "" }, "require": { @@ -2996,6 +2996,7 @@ "ramsey/uuid": "^4.0", "symfony/console": "^6.0|^7.0", "symfony/error-handler": "^6.0|^7.0", + "symfony/polyfill-php83": "^1.28", "symfony/process": "^6.0|^7.0" }, "require-dev": { @@ -3045,9 +3046,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.29.1" + "source": "https://github.com/laravel/horizon/tree/v5.29.2" }, - "time": "2024-10-08T18:23:02+00:00" + "time": "2024-10-16T21:36:57+00:00" }, { "name": "laravel/prompts", @@ -10481,16 +10482,16 @@ }, { "name": "barryvdh/reflection-docblock", - "version": "v2.1.2", + "version": "v2.1.3", "source": { "type": "git", "url": "https://github.com/barryvdh/ReflectionDocBlock.git", - "reference": "bba116ba9d5794fbf12e03ed40f10804e51acf76" + "reference": "c6fad15f7c878be21650c51e1f841bca7e49752e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/bba116ba9d5794fbf12e03ed40f10804e51acf76", - "reference": "bba116ba9d5794fbf12e03ed40f10804e51acf76", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/c6fad15f7c878be21650c51e1f841bca7e49752e", + "reference": "c6fad15f7c878be21650c51e1f841bca7e49752e", "shasum": "" }, "require": { @@ -10527,9 +10528,9 @@ } ], "support": { - "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.1.2" + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.1.3" }, - "time": "2024-10-16T11:06:28+00:00" + "time": "2024-10-23T11:41:03+00:00" }, { "name": "clue/ndjson-react", @@ -11380,16 +11381,16 @@ }, { "name": "laravel/sail", - "version": "v1.36.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "f184d3d687155d06bc8cb9ff6dc48596a138460c" + "reference": "5d385f2e698f0f774cdead82aff5d989fb95309b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/f184d3d687155d06bc8cb9ff6dc48596a138460c", - "reference": "f184d3d687155d06bc8cb9ff6dc48596a138460c", + "url": "https://api.github.com/repos/laravel/sail/zipball/5d385f2e698f0f774cdead82aff5d989fb95309b", + "reference": "5d385f2e698f0f774cdead82aff5d989fb95309b", "shasum": "" }, "require": { @@ -11439,7 +11440,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-10-10T13:26:02+00:00" + "time": "2024-10-21T17:13:38+00:00" }, { "name": "laravel/telescope", @@ -14823,12 +14824,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/horizon.php b/config/horizon.php index 46477d8..51fd188 100644 --- a/config/horizon.php +++ b/config/horizon.php @@ -203,6 +203,7 @@ '*' => [ 'supervisor-1' => [ + 'minProcesses' => 1, 'maxProcesses' => 3, ], ], diff --git a/config/import.php b/config/import.php new file mode 100644 index 0000000..4a051b4 --- /dev/null +++ b/config/import.php @@ -0,0 +1,11 @@ + env('AWK_PATH', '/usr/bin/awk'), + + 'disk' => env('IMPORT_DISK', 'local'), + +]; diff --git a/config/queue.php b/config/queue.php index 8829481..5ebbfcb 100644 --- a/config/queue.php +++ b/config/queue.php @@ -15,7 +15,7 @@ | */ - 'default' => env('QUEUE_CONNECTION', 'database'), + 'default' => env('QUEUE_CONNECTION', 'redis'), /* |-------------------------------------------------------------------------- diff --git a/config/session.php b/config/session.php index da4942c..661810e 100644 --- a/config/session.php +++ b/config/session.php @@ -18,7 +18,7 @@ | */ - 'driver' => env('SESSION_DRIVER', 'database'), + 'driver' => env('SESSION_DRIVER', 'redis'), /* |-------------------------------------------------------------------------- diff --git a/database/data/siruta.sql b/database/data/siruta.sql index b688406..818db3e 100644 --- a/database/data/siruta.sql +++ b/database/data/siruta.sql @@ -8,49 +8,49 @@ /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -INSERT INTO `counties` (`id`, `name`) VALUES -(10, 'Alba'), -(29, 'Arad'), -(38, 'Argeș'), -(47, 'Bacău'), -(56, 'Bihor'), -(65, 'Bistrița-Năsăud'), -(74, 'Botoșani'), -(83, 'Brașov'), -(92, 'Brăila'), -(109, 'Buzău'), -(118, 'Caraș-Severin'), -(127, 'Cluj'), -(136, 'Constanța'), -(145, 'Covasna'), -(154, 'Dâmbovița'), -(163, 'Dolj'), -(172, 'Galați'), -(181, 'Gorj'), -(190, 'Harghita'), -(207, 'Hunedoara'), -(216, 'Ialomița'), -(225, 'Iași'), -(234, 'Ilfov'), -(243, 'Maramureș'), -(252, 'Mehedinți'), -(261, 'Mureș'), -(270, 'Neamț'), -(289, 'Olt'), -(298, 'Prahova'), -(305, 'Satu Mare'), -(314, 'Sălaj'), -(323, 'Sibiu'), -(332, 'Suceava'), -(341, 'Teleorman'), -(350, 'Timiș'), -(369, 'Tulcea'), -(378, 'Vaslui'), -(387, 'Vâlcea'), -(396, 'Vrancea'), -(403, 'București'), -(519, 'Călărași'), -(528, 'Giurgiu'); +INSERT INTO `counties` (`id`, `code`, `name`) VALUES +(10, 'AB', 'Alba'), +(29, 'AR', 'Arad'), +(38, 'AG', 'Argeș'), +(47, 'BC', 'Bacău'), +(56, 'BH', 'Bihor'), +(65, 'BN', 'Bistrița-Năsăud'), +(74, 'BT', 'Botoșani'), +(83, 'BV', 'Brașov'), +(92, 'BR', 'Brăila'), +(109, 'BZ', 'Buzău'), +(118, 'CS', 'Caraș-Severin'), +(127, 'CJ', 'Cluj'), +(136, 'CT', 'Constanța'), +(145, 'CV', 'Covasna'), +(154, 'DB', 'Dâmbovița'), +(163, 'DJ', 'Dolj'), +(172, 'GL', 'Galați'), +(181, 'GJ', 'Gorj'), +(190, 'HR', 'Harghita'), +(207, 'HD', 'Hunedoara'), +(216, 'IL', 'Ialomița'), +(225, 'IS', 'Iași'), +(234, 'IF', 'Ilfov'), +(243, 'MM', 'Maramureș'), +(252, 'MH', 'Mehedinți'), +(261, 'MS', 'Mureș'), +(270, 'NT', 'Neamț'), +(289, 'OT', 'Olt'), +(298, 'PH', 'Prahova'), +(305, 'SM', 'Satu Mare'), +(314, 'SJ', 'Sălaj'), +(323, 'SB', 'Sibiu'), +(332, 'SV', 'Suceava'), +(341, 'TR', 'Teleorman'), +(350, 'TM', 'Timiș'), +(369, 'TL', 'Tulcea'), +(378, 'VS', 'Vaslui'), +(387, 'VL', 'Vâlcea'), +(396, 'VN', 'Vrancea'), +(403, 'B', 'București'), +(519, 'CL', 'Călărași'), +(528, 'GR', 'Giurgiu'); INSERT INTO `localities` (`id`, `county_id`, `level`, `type`, `parent_id`, `name`) VALUES (1017, 10, 2, 1, NULL, 'Municipiul Alba Iulia'), diff --git a/database/factories/ElectionFactory.php b/database/factories/ElectionFactory.php index abf8f88..f14a9b8 100644 --- a/database/factories/ElectionFactory.php +++ b/database/factories/ElectionFactory.php @@ -9,6 +9,7 @@ use App\Models\ElectionType; use App\Models\Locality; use App\Models\Party; +use App\Models\ScheduledJob; use App\Models\Turnout; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Collection; @@ -49,6 +50,12 @@ public function configure(): static ->for($election) ->count(rand(25, 50)) ->create(); + + ScheduledJob::factory() + ->for($election) + ->enabled($election->is_live) + ->count(2) + ->create(); }); } diff --git a/database/factories/ScheduledJobFactory.php b/database/factories/ScheduledJobFactory.php new file mode 100644 index 0000000..1da3389 --- /dev/null +++ b/database/factories/ScheduledJobFactory.php @@ -0,0 +1,39 @@ + + */ +class ScheduledJobFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'job' => DummyJob::class, + 'cron' => fake()->randomElement(Cron::values()), + + 'is_enabled' => fake()->boolean(), + 'election_id' => Election::factory(), + ]; + } + + public function enabled(bool $condition = true): static + { + return $this->state([ + 'is_enabled' => $condition, + ]); + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index e9f84f8..a28c738 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -26,14 +26,5 @@ public function up(): void $table->string('token'); $table->timestamp('created_at')->nullable(); }); - - Schema::create('sessions', function (Blueprint $table) { - $table->string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); - $table->string('ip_address', 45)->nullable(); - $table->text('user_agent')->nullable(); - $table->longText('payload'); - $table->integer('last_activity')->index(); - }); } }; diff --git a/database/migrations/0001_01_01_000005_create_personal_access_tokens_table.php b/database/migrations/0001_01_01_000005_create_personal_access_tokens_table.php new file mode 100644 index 0000000..55fff76 --- /dev/null +++ b/database/migrations/0001_01_01_000005_create_personal_access_tokens_table.php @@ -0,0 +1,27 @@ +id(); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000010_create_jobs_table.php similarity index 100% rename from database/migrations/0001_01_01_000002_create_jobs_table.php rename to database/migrations/0001_01_01_000010_create_jobs_table.php diff --git a/database/migrations/0001_01_01_000003_create_breezy_sessions_table.php b/database/migrations/0001_01_01_000100_create_breezy_sessions_table.php similarity index 100% rename from database/migrations/0001_01_01_000003_create_breezy_sessions_table.php rename to database/migrations/0001_01_01_000100_create_breezy_sessions_table.php diff --git a/database/migrations/0001_01_01_000004_create_media_table.php b/database/migrations/0001_01_01_001000_create_media_table.php similarity index 100% rename from database/migrations/0001_01_01_000004_create_media_table.php rename to database/migrations/0001_01_01_001000_create_media_table.php diff --git a/database/migrations/0001_01_02_000000_create_siruta_tables.php b/database/migrations/0001_01_02_000000_create_siruta_tables.php index dd63d3b..ed82be8 100644 --- a/database/migrations/0001_01_02_000000_create_siruta_tables.php +++ b/database/migrations/0001_01_02_000000_create_siruta_tables.php @@ -7,7 +7,6 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Schema; use Maatwebsite\Excel\Facades\Excel; @@ -17,6 +16,7 @@ public function up(): void { Schema::create('counties', function (Blueprint $table) { $table->smallInteger('id')->unsigned()->primary(); + $table->string('code', 2)->unique(); $table->string('name'); }); @@ -45,7 +45,7 @@ public function up(): void Schema::withoutForeignKeyConstraints(function () { DB::unprepared( - File::get(database_path('data/siruta.sql')) + Storage::disk('seed-data')->get('siruta.sql') ); }); } diff --git a/database/migrations/0001_01_02_000005_create_scheduled_jobs_table.php b/database/migrations/0001_01_02_000005_create_scheduled_jobs_table.php new file mode 100644 index 0000000..7d93b50 --- /dev/null +++ b/database/migrations/0001_01_02_000005_create_scheduled_jobs_table.php @@ -0,0 +1,37 @@ +id(); + + $table->string('job'); + $table->string('cron'); + $table->boolean('is_enabled')->default(false); + + $table->string('source_part')->nullable(); + $table->string('source_url')->nullable(); + $table->string('source_username')->nullable(); + $table->string('source_password')->nullable(); + + $table->foreignIdFor(Election::class) + ->constrained() + ->cascadeOnDelete(); + + $table->timestamps(); + $table->timestamp('last_run_at')->nullable(); + }); + } +}; diff --git a/database/migrations/0001_01_10_000000_create_turnouts_table.php b/database/migrations/0001_01_10_000000_create_turnouts_table.php index f83989b..68b5cfa 100644 --- a/database/migrations/0001_01_10_000000_create_turnouts_table.php +++ b/database/migrations/0001_01_10_000000_create_turnouts_table.php @@ -29,6 +29,14 @@ public function up(): void ->constrained() ->cascadeOnDelete(); + $table->smallInteger('county_id') + ->unsigned() + ->nullable(); + + $table->foreign('county_id') + ->references('id') + ->on('counties'); + $table->mediumInteger('locality_id') ->unsigned() ->nullable(); @@ -37,6 +45,8 @@ public function up(): void ->references('id') ->on('localities'); + $table->string('section'); + $table->mediumInteger('initial_permanent')->unsigned(); $table->mediumInteger('initial_complement')->unsigned(); $table->mediumInteger('permanent')->unsigned(); @@ -62,8 +72,27 @@ public function up(): void ROUND(total / initial_total * 100, 2) SQL); - $table->unique(['election_id', 'locality_id']); - $table->unique(['election_id', 'country_id']); + // $table->unique(['election_id', 'locality_id']); + $table->unique(['election_id', 'county_id', 'section']); + // $table->unique(['election_id', 'country_id']); + + // $table->integer('eligible_voters')->unsigned(); + // $table->mediumInteger('total_votes')->unsigned(); + // $table->mediumInteger('null_votes')->unsigned(); + // $table->mediumInteger('votes_by_mail')->unsigned(); + // $table->mediumInteger('valid_votes')->unsigned(); + // $table->mediumInteger('total_seats')->unsigned(); // TODO: check if we need it. it's currently empty in the old database + // $table->mediumInteger('coefficient')->unsigned(); + // $table->mediumInteger('threshold')->unsigned(); + // $table->mediumInteger('circumscription')->unsigned(); + // $table->mediumInteger('min_votes')->unsigned(); + + // $table->mediumInteger('division')->unsigned(); + // $table->mediumInteger('mandates')->unsigned(); + // $table->mediumInteger('correspondence_votes')->unsigned(); + // $table->mediumInteger('permanent_lists_votes')->unsigned(); + // $table->mediumInteger('special_lists_votes')->unsigned(); + // $table->mediumInteger('supplementary_votes')->unsigned(); }); } }; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 3428187..38c99d6 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -43,8 +43,8 @@ public function run(): void Election::factory() ->live() - ->withLocalTurnout() - ->withAbroadTurnout() + // ->withLocalTurnout() + // ->withAbroadTurnout() ->recycle($electionTypes) ->create(); } diff --git a/lang/ro/admin.php b/lang/ro/admin.php index 0d6566d..180484b 100644 --- a/lang/ro/admin.php +++ b/lang/ro/admin.php @@ -36,6 +36,13 @@ 'voters_supplement' => 'Votanți pe liste suplimentare', 'voters_total' => 'Total votanți', 'year' => 'An', + 'job' => 'Job', + 'cron' => 'Interval', + 'source_url' => 'URL sursă', + 'source_part' => 'Parte', + 'source_username' => 'Utilizator', + 'source_password' => 'Parolă', + 'is_enabled' => 'Activ', ], ]; diff --git a/lang/ro/app.php b/lang/ro/app.php index c5aa890..ba1dd86 100644 --- a/lang/ro/app.php +++ b/lang/ro/app.php @@ -9,6 +9,8 @@ 'singular' => 'alegere', 'plural' => 'alegeri', ], + + 'settings' => 'Setări rundă electorală', ], 'election_type' => [ @@ -29,6 +31,13 @@ 'label' => 'prezență', ], + 'scheduled_job' => [ + 'label' => [ + 'singular' => 'job programat', + 'plural' => 'joburi programate', + ], + ], + 'user' => [ 'label' => [ 'singular' => 'utilizator', @@ -42,4 +51,21 @@ ], ], + 'cron' => [ + 'every_minute' => '1 minut', + 'every_2_minutes' => '2 minute', + 'every_3_minutes' => '3 minute', + 'every_4_minutes' => '4 minute', + 'every_5_minutes' => '5 minute', + 'every_10_minutes' => '10 minute', + 'every_5_1_minutes' => '5+1 minute', + 'every_5_2_minutes' => '5+2 minute', + 'every_5_3_minutes' => '5+3 minute', + 'every_5_4_minutes' => '5+4 minute', + 'every_10_5_minutes' => '10+5 minute', + 'every_10_6_minutes' => '10+6 minute', + 'every_10_7_minutes' => '10+7 minute', + 'every_10_8_minutes' => '10+8 minute', + 'every_10_9_minutes' => '10+9 minute', + ], ];