Link::all()]); } /** * Show the form for creating a new resource. */ public function create() { return view('links.create'); } /** * Store a newly created resource in storage. */ public function store(StoreLinkRequest $req) { $validated = $req->validated(); $link = Link::create([ 'label' => $validated['label'], 'url' => $validated['url'], 'description' => $validated['description'] ?? '', 'user_id' => Auth::id(), ]); $redirect = $req->input('_redirect', route('l.index')); return redirect($redirect)->with('success', 'Link added'); } /** * Display the specified resource. */ public function show($id) { $link = Link::findOrFail($id); return view('links.show', [ 'link' => $link ]); } /** * Show the form for editing the specified resource. */ public function edit(Link $link) { // } /** * Update the specified resource in storage. */ public function update(UpdateLinkRequest $request, Link $link) { $this->authorize('update', $link); $validated = $request->validated(); $link->update($validated); $redirect = $request->input('_redirect', route('l.index')); return redirect($redirect)->with('success', 'Link updated'); } /** * Remove the specified resource from storage. */ public function destroy(Link $link) { $this->authorize('delete', $link); $link->delete(); $redirect = request()->input('_redirect', route('l.index')); return redirect($redirect)->with('success', 'Link deleted'); } /** * Import links from an uploaded JSON file. Admin only. */ public function import(Request $request) { $request->validate([ 'json_file' => 'required|file|max:2048', ]); $file = $request->file('json_file'); if (!in_array($file->getClientOriginalExtension(), ['json', 'txt'])) { return back()->withErrors(['json_file' => 'File must be a .json or .txt file.']); } $contents = file_get_contents($file->getRealPath()); $entries = json_decode($contents, true); if (json_last_error() !== JSON_ERROR_NONE) { return back()->withErrors(['json_file' => 'Invalid JSON: ' . json_last_error_msg()]); } if (!is_array($entries)) { return back()->withErrors(['json_file' => 'JSON must contain an array of link objects.']); } if (count($entries) > 500) { return back()->withErrors(['json_file' => 'Too many entries. Maximum is 500 per upload.']); } $created = 0; $skipped = 0; $errors = []; DB::transaction(function () use ($entries, &$created, &$skipped, &$errors) { foreach ($entries as $i => $entry) { if (empty($entry['url'])) { $errors[] = "Entry #{$i}: missing url, skipped."; $skipped++; continue; } $entryValidator = Validator::make($entry, [ 'url' => 'required|string|url|max:255', 'label' => 'nullable|string|min:2|max:255', 'description' => 'nullable|string|max:255', 'tags' => 'nullable|array|max:20', 'tags.*' => 'string|max:50', ]); if ($entryValidator->fails()) { $reasons = implode(', ', $entryValidator->errors()->all()); $errors[] = "Entry #{$i} ({$entry['url']}): {$reasons}"; $skipped++; continue; } $validated = $entryValidator->validated(); $existing = Link::where('url', $validated['url'])->first(); if ($existing) { $skipped++; continue; } $link = Link::create([ 'url' => $validated['url'], 'label' => $validated['label'] ?? $validated['url'], 'description' => $validated['description'] ?? '', 'user_id' => Auth::id(), ]); // Attach tags if present if (!empty($validated['tags']) && is_array($validated['tags'])) { $tagIds = []; foreach ($validated['tags'] as $tagName) { if (is_string($tagName) && trim($tagName) !== '') { $tag = Tag::firstOrCreate(['label' => trim($tagName)]); $tagIds[] = $tag->id; } } if (!empty($tagIds)) { $link->tags()->syncWithoutDetaching($tagIds); } } $created++; } }); $message = "{$created} link(s) imported, {$skipped} skipped (duplicates or invalid)."; if (!empty($errors)) { $message .= ' Warnings: ' . implode(' ', array_slice($errors, 0, 5)); } return redirect()->route('l.index')->with('success', $message); } }