Skip to content

Commit 4cdaabb

Browse files
authored
Merge pull request #3610 from codeeu/dev
Dev
2 parents dcf32fe + 9b94ae2 commit 4cdaabb

5 files changed

Lines changed: 425 additions & 77 deletions

File tree

app/Http/Controllers/BulkUserChangesController.php

Lines changed: 163 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Services\BulkUserChanges\BulkUserChangesPlanner;
66
use App\Services\BulkUserChanges\BulkUserChangesReadOptions;
77
use App\Services\BulkUserChanges\BulkUserChangesSheetReader;
8+
use App\Services\BulkUserChanges\BulkUserChangesTextParser;
89
use Illuminate\Http\RedirectResponse;
910
use Illuminate\Http\Request;
1011
use Illuminate\Support\Facades\Cache;
@@ -22,6 +23,94 @@ public function index(): View
2223
}
2324

2425
public function validateUpload(
26+
Request $request,
27+
BulkUserChangesSheetReader $reader,
28+
BulkUserChangesTextParser $textParser,
29+
BulkUserChangesPlanner $planner,
30+
): RedirectResponse {
31+
$hasFile = $request->hasFile('file') && $request->file('file')?->isValid();
32+
$hasPaste = trim((string) $request->input('paste', '')) !== '';
33+
34+
if ($hasFile && $hasPaste) {
35+
return redirect()->route('admin.bulk-user-changes.index')
36+
->withErrors(['file' => 'Upload a file or paste text, not both.'])
37+
->withInput();
38+
}
39+
40+
if (! $hasFile && ! $hasPaste) {
41+
return redirect()->route('admin.bulk-user-changes.index')
42+
->withErrors(['file' => 'Upload a file or paste changes to continue.']);
43+
}
44+
45+
if ($hasPaste) {
46+
return $this->validatePaste($request, $textParser, $planner);
47+
}
48+
49+
return $this->validateFile($request, $reader, $planner);
50+
}
51+
52+
public function preview(Request $request): View|RedirectResponse
53+
{
54+
$payload = $this->payloadFromSession($request);
55+
if ($payload === null) {
56+
return redirect()->route('admin.bulk-user-changes.index')
57+
->withErrors(['file' => 'No validated upload found. Please upload a file or paste changes first.']);
58+
}
59+
60+
return view('admin.bulk-user-changes.preview', [
61+
'parsed' => $payload['parsed'],
62+
'plan' => $payload['plan'],
63+
'token' => $request->session()->get(self::SESSION_TOKEN),
64+
]);
65+
}
66+
67+
public function apply(
68+
Request $request,
69+
BulkUserChangesSheetReader $reader,
70+
BulkUserChangesTextParser $textParser,
71+
BulkUserChangesPlanner $planner,
72+
): RedirectResponse {
73+
$token = (string) $request->input('token', $request->session()->get(self::SESSION_TOKEN, ''));
74+
$payload = $token !== '' ? Cache::get('bulk_user_changes_'.$token) : null;
75+
76+
if (! is_array($payload)) {
77+
return redirect()->route('admin.bulk-user-changes.index')
78+
->withErrors(['file' => 'Session expired. Please upload or paste again.']);
79+
}
80+
81+
try {
82+
$parsed = $this->readPayload($payload, $reader, $textParser);
83+
$result = $planner->apply($parsed['rows']);
84+
} catch (\Throwable $e) {
85+
return redirect()->route('admin.bulk-user-changes.preview')
86+
->withErrors(['apply' => 'Apply failed: '.$e->getMessage()]);
87+
}
88+
89+
$this->cleanupPayload($payload);
90+
Cache::forget('bulk_user_changes_'.$token);
91+
$request->session()->forget(self::SESSION_TOKEN);
92+
$request->session()->put('bulk_user_changes_report', [
93+
'parsed' => $parsed,
94+
'result' => $result,
95+
]);
96+
97+
return redirect()->route('admin.bulk-user-changes.report');
98+
}
99+
100+
public function report(Request $request): View|RedirectResponse
101+
{
102+
$report = $request->session()->get('bulk_user_changes_report');
103+
if (! is_array($report)) {
104+
return redirect()->route('admin.bulk-user-changes.index');
105+
}
106+
107+
return view('admin.bulk-user-changes.report', [
108+
'parsed' => $report['parsed'],
109+
'result' => $report['result'],
110+
]);
111+
}
112+
113+
private function validateFile(
25114
Request $request,
26115
BulkUserChangesSheetReader $reader,
27116
BulkUserChangesPlanner $planner,
@@ -68,89 +157,109 @@ function ($attribute, $value, $fail) {
68157
->withErrors(['file' => 'No actionable rows found after the header row. Check that the Changes sheet has email addresses and actions.']);
69158
}
70159

71-
$plan = $planner->plan($parsed['rows']);
72-
$token = Str::random(64);
73-
74-
Cache::put('bulk_user_changes_'.$token, [
160+
return $this->storePreviewSession($request, $planner, $parsed, [
161+
'source' => 'file',
75162
'path' => $path,
76163
'disk' => $tempDisk,
77-
'parsed' => $parsed,
78-
'plan' => $plan,
79164
'ignore_through_row' => $readOptions->ignoreThroughRow,
80-
], now()->addHours(2));
81-
82-
$request->session()->put(self::SESSION_TOKEN, $token);
83-
84-
return redirect()->route('admin.bulk-user-changes.preview');
165+
]);
85166
}
86167

87-
public function preview(Request $request): View|RedirectResponse
88-
{
89-
$payload = $this->payloadFromSession($request);
90-
if ($payload === null) {
168+
private function validatePaste(
169+
Request $request,
170+
BulkUserChangesTextParser $textParser,
171+
BulkUserChangesPlanner $planner,
172+
): RedirectResponse {
173+
$validated = $request->validate([
174+
'paste' => ['required', 'string', 'min:10', 'max:100000'],
175+
], [
176+
'paste.required' => 'Paste the change list to continue.',
177+
]);
178+
179+
try {
180+
$parsed = $textParser->parse($validated['paste']);
181+
} catch (\Throwable $e) {
91182
return redirect()->route('admin.bulk-user-changes.index')
92-
->withErrors(['file' => 'No validated upload found. Please upload a file first.']);
183+
->withErrors(['paste' => $e->getMessage()])
184+
->withInput();
93185
}
94186

95-
return view('admin.bulk-user-changes.preview', [
96-
'parsed' => $payload['parsed'],
97-
'plan' => $payload['plan'],
98-
'token' => $request->session()->get(self::SESSION_TOKEN),
187+
return $this->storePreviewSession($request, $planner, $parsed, [
188+
'source' => 'paste',
189+
'paste_text' => $validated['paste'],
99190
]);
100191
}
101192

102-
public function apply(
193+
/**
194+
* @param array<string, mixed> $parsed
195+
* @param array<string, mixed> $cacheMeta
196+
*/
197+
private function storePreviewSession(
103198
Request $request,
104-
BulkUserChangesSheetReader $reader,
105199
BulkUserChangesPlanner $planner,
200+
array $parsed,
201+
array $cacheMeta,
106202
): RedirectResponse {
107-
$token = (string) $request->input('token', $request->session()->get(self::SESSION_TOKEN, ''));
108-
$payload = $token !== '' ? Cache::get('bulk_user_changes_'.$token) : null;
203+
$plan = $planner->plan($parsed['rows']);
204+
$token = Str::random(64);
109205

110-
if (! is_array($payload)) {
111-
return redirect()->route('admin.bulk-user-changes.index')
112-
->withErrors(['file' => 'Upload session expired. Please upload the file again.']);
206+
Cache::put('bulk_user_changes_'.$token, [
207+
...$cacheMeta,
208+
'parsed' => $parsed,
209+
'plan' => $plan,
210+
], now()->addHours(2));
211+
212+
$request->session()->put(self::SESSION_TOKEN, $token);
213+
214+
return redirect()->route('admin.bulk-user-changes.preview');
215+
}
216+
217+
/**
218+
* @param array<string, mixed> $payload
219+
* @return array<string, mixed>
220+
*/
221+
private function readPayload(
222+
array $payload,
223+
BulkUserChangesSheetReader $reader,
224+
BulkUserChangesTextParser $textParser,
225+
): array {
226+
if (($payload['source'] ?? 'file') === 'paste') {
227+
$pasteText = (string) ($payload['paste_text'] ?? '');
228+
229+
if ($pasteText === '') {
230+
throw new \RuntimeException('Pasted text no longer available. Please paste again.');
231+
}
232+
233+
return $textParser->parse($pasteText);
113234
}
114235

115236
$path = (string) ($payload['path'] ?? '');
116237
$disk = (string) ($payload['disk'] ?? 'local');
117238

118239
if ($path === '' || ! Storage::disk($disk)->exists($path)) {
119-
return redirect()->route('admin.bulk-user-changes.index')
120-
->withErrors(['file' => 'Uploaded file no longer available. Please upload again.']);
240+
throw new \RuntimeException('Uploaded file no longer available. Please upload again.');
121241
}
122242

123-
try {
124-
$readOptions = BulkUserChangesReadOptions::fromInput($payload['ignore_through_row'] ?? null);
125-
$parsed = $reader->read($path, $disk, $readOptions);
126-
$result = $planner->apply($parsed['rows']);
127-
} catch (\Throwable $e) {
128-
return redirect()->route('admin.bulk-user-changes.preview')
129-
->withErrors(['apply' => 'Apply failed: '.$e->getMessage()]);
130-
}
131-
132-
Storage::disk($disk)->delete($path);
133-
Cache::forget('bulk_user_changes_'.$token);
134-
$request->session()->forget(self::SESSION_TOKEN);
135-
$request->session()->put('bulk_user_changes_report', [
136-
'parsed' => $parsed,
137-
'result' => $result,
138-
]);
243+
$readOptions = BulkUserChangesReadOptions::fromInput($payload['ignore_through_row'] ?? null);
139244

140-
return redirect()->route('admin.bulk-user-changes.report');
245+
return $reader->read($path, $disk, $readOptions);
141246
}
142247

143-
public function report(Request $request): View|RedirectResponse
248+
/**
249+
* @param array<string, mixed> $payload
250+
*/
251+
private function cleanupPayload(array $payload): void
144252
{
145-
$report = $request->session()->get('bulk_user_changes_report');
146-
if (! is_array($report)) {
147-
return redirect()->route('admin.bulk-user-changes.index');
253+
if (($payload['source'] ?? 'file') !== 'file') {
254+
return;
148255
}
149256

150-
return view('admin.bulk-user-changes.report', [
151-
'parsed' => $report['parsed'],
152-
'result' => $report['result'],
153-
]);
257+
$path = (string) ($payload['path'] ?? '');
258+
$disk = (string) ($payload['disk'] ?? 'local');
259+
260+
if ($path !== '' && Storage::disk($disk)->exists($path)) {
261+
Storage::disk($disk)->delete($path);
262+
}
154263
}
155264

156265
/**

0 commit comments

Comments
 (0)