Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions app/Http/Controllers/BulkEventUploadController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

namespace App\Http\Controllers;

use App\Imports\GenericEventsImport;
use App\Services\BulkEventImportResult;
use App\Services\BulkEventUploadValidator;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
use Maatwebsite\Excel\Facades\Excel;

class BulkEventUploadController extends Controller
{
private const SESSION_FILE_PATH = 'bulk_upload_file_path';
private const SESSION_DEFAULT_CREATOR = 'bulk_upload_default_creator_email';
private const SESSION_VALIDATION_PASSED = 'bulk_upload_validation_passed';
private const SESSION_VALIDATION_MISSING = 'bulk_upload_validation_missing';

/**
* Show the bulk upload form.
*/
public function index(Request $request): View
{
$validationMissing = $request->session()->get('validation_missing')
?? $request->session()->get(self::SESSION_VALIDATION_MISSING, []);

return view('admin.bulk-upload.index', [
'validationPassed' => $request->session()->get(self::SESSION_VALIDATION_PASSED, false),
'validationMissing' => $validationMissing,
'storedDefaultCreatorEmail' => $request->session()->get(self::SESSION_DEFAULT_CREATOR),
]);
}

/**
* Upload file and validate required columns. Store file in session for import step.
*/
public function validateUpload(Request $request): RedirectResponse
{
$validated = $request->validate([
'file' => [
'required',
'file',
'max:10240',
function ($attribute, $value, $fail) {
if ($value) {
$ext = strtolower($value->getClientOriginalExtension());
$allowed = ['csv', 'xlsx', 'xls'];
if (! in_array($ext, $allowed, true)) {
$filename = strtolower($value->getClientOriginalName());
if (strpos($filename, '.csv') === false && strpos($filename, '.xlsx') === false && strpos($filename, '.xls') === false) {
$fail('The file must be a CSV or Excel file (.csv, .xlsx, or .xls).');
}
}
}
},
],
'default_creator_email' => ['nullable', 'email', 'max:255'],
], [
'file.required' => 'Please select a file to upload.',
'file.max' => 'The file may not be greater than 10 MB.',
]);

$file = $request->file('file');
$extension = strtolower($file->getClientOriginalExtension() ?: '');
if (empty($extension)) {
$filename = strtolower($file->getClientOriginalName());
if (strpos($filename, '.csv') !== false) {
$extension = 'csv';
} elseif (strpos($filename, '.xlsx') !== false) {
$extension = 'xlsx';
} elseif (strpos($filename, '.xls') !== false) {
$extension = 'xls';
}
}
if (! in_array($extension, ['csv', 'xlsx', 'xls'], true)) {
return redirect()->route('admin.bulk-upload.index')
->withErrors(['file' => 'The file must be a CSV or Excel file (.csv, .xlsx, or .xls).'])
->withInput();
}

// Remove any previously stored file
$oldPath = $request->session()->get(self::SESSION_FILE_PATH);
if ($oldPath && Storage::exists($oldPath)) {
Storage::delete($oldPath);
}
$request->session()->forget([self::SESSION_FILE_PATH, self::SESSION_VALIDATION_PASSED, self::SESSION_VALIDATION_MISSING, self::SESSION_DEFAULT_CREATOR]);

$path = $file->storeAs('temp', 'bulk_events_'.time().'.'.$extension);

$headerCheck = BulkEventUploadValidator::validateRequiredColumns($path, 'local');
if (isset($headerCheck['error'])) {
Storage::delete($path);

return redirect()->route('admin.bulk-upload.index')
->withErrors(['file' => 'Could not read file: '.$headerCheck['error']])
->withInput();
}
if (! $headerCheck['valid']) {
Storage::delete($path);

return redirect()->route('admin.bulk-upload.index')
->with('validation_missing', $headerCheck['missing'])
->withErrors(['file' => 'Missing required columns: '.implode(', ', $headerCheck['missing']).'. Please add these column headers to your file.'])
->withInput();
}

$request->session()->put(self::SESSION_FILE_PATH, $path);
$request->session()->put(self::SESSION_DEFAULT_CREATOR, $validated['default_creator_email'] ?? null);
$request->session()->put(self::SESSION_VALIDATION_PASSED, true);
$request->session()->forget(self::SESSION_VALIDATION_MISSING);

return redirect()->route('admin.bulk-upload.index')
->with('success', 'File uploaded. All required columns are present. Click Import to run the import.');
}

/**
* Run the import using the file stored in session (after validate).
*/
public function import(Request $request): View|RedirectResponse
{
$path = $request->session()->get(self::SESSION_FILE_PATH);
if (! $path || ! Storage::exists($path)) {
$request->session()->forget([self::SESSION_FILE_PATH, self::SESSION_DEFAULT_CREATOR, self::SESSION_VALIDATION_PASSED, self::SESSION_VALIDATION_MISSING]);

return redirect()->route('admin.bulk-upload.index')
->withErrors(['import' => 'No validated file found. Please upload and validate a file first.']);
}

$defaultCreatorEmail = $request->session()->get(self::SESSION_DEFAULT_CREATOR);

try {
$result = new BulkEventImportResult;
$import = new GenericEventsImport($defaultCreatorEmail, $result);
Excel::import($import, $path, 'local');

Storage::delete($path);
$request->session()->forget([self::SESSION_FILE_PATH, self::SESSION_DEFAULT_CREATOR, self::SESSION_VALIDATION_PASSED, self::SESSION_VALIDATION_MISSING]);

$this->clearMapCache();

return view('admin.bulk-upload.report', [
'created' => $result->created,
'failures' => $result->failures,
]);
} catch (\Throwable $e) {
if (Storage::exists($path)) {
Storage::delete($path);
}
$request->session()->forget([self::SESSION_FILE_PATH, self::SESSION_DEFAULT_CREATOR, self::SESSION_VALIDATION_PASSED, self::SESSION_VALIDATION_MISSING]);

return redirect()->route('admin.bulk-upload.index')
->withErrors(['import' => 'Import failed: '.$e->getMessage()]);
}
}

/**
* Clear map cache so new/updated events appear on the map immediately.
*/
private function clearMapCache(): void
{
Cache::flush();
}
}
4 changes: 3 additions & 1 deletion app/Http/Controllers/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ protected function transformEventsForMap(EventFilters $filters)
return Cache::remember($composed_key, 300, function () use ($filters) {
$grouped = [];

Event::select('id', 'geoposition', 'country_iso') // Only required fields
Event::select('id', 'geoposition', 'country_iso')
->where('status', 'APPROVED')
->whereNotNull('geoposition')
->where('geoposition', '!=', '')
->filter($filters)
->cursor()
->each(function ($event) use (&$grouped) {
Expand Down
Loading
Loading