55use App \Services \BulkUserChanges \BulkUserChangesPlanner ;
66use App \Services \BulkUserChanges \BulkUserChangesReadOptions ;
77use App \Services \BulkUserChanges \BulkUserChangesSheetReader ;
8+ use App \Services \BulkUserChanges \BulkUserChangesTextParser ;
89use Illuminate \Http \RedirectResponse ;
910use Illuminate \Http \Request ;
1011use 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