Add share-video-by-email feature

New POST /videos/{video}/share/email route (auth + throttled) handled by
VideoController@shareByEmail, sending the VideoShared mailable rendered from
emails/video-shared.blade.php. Wired into the share modal and video-actions.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-05-24 14:12:01 +03:00
1 parent a4384113c2
commit d9959c4452
6 files changed
+274 -5

No files matched your search

+41
View File
@@ -2176,6 +2176,47 @@ class VideoController extends Controller
return response()->json(['url' => route('share.access', $token)]);
}
/**
* Email a friend a properly-formatted share email, version-aware: the link and the
* email's title reflect the language track the sender chose. The URL is built
* server-side so outgoing mail can never carry an attacker-supplied link.
*/
public function shareByEmail(Request $request, Video $video)
{
if (! $video->isShareable() || ! $video->canView(Auth::user())) {
return response()->json(['error' => 'This video cannot be shared.'], 403);
}
$data = $request->validate([
'email' => 'required|email|max:255',
'message' => 'nullable|string|max:500',
'track' => 'nullable|integer',
]);
$trackId = (int) ($data['track'] ?? 0);
$shareTitle = $video->title;
$shareUrl = $video->share_url;
if ($trackId && ($track = $video->audioTracks->firstWhere('id', $trackId))) {
if (! empty($track->title)) $shareTitle = $track->title;
$shareUrl .= (str_contains($shareUrl, '?') ? '&' : '?') . 'track=' . $trackId;
}
try {
\Mail::to($data['email'])->send(new \App\Mail\VideoShared(
$video,
$shareUrl,
Auth::user(),
$data['message'] ?? null,
$shareTitle,
));
} catch (\Throwable $e) {
\Log::error('Share-by-email failed: ' . $e->getMessage(), ['video_id' => $video->id]);
return response()->json(['error' => 'Could not send the email right now. Please try again.'], 500);
}
return response()->json(['success' => true]);
}
public function accessShare(Request $request, string $token)
{
$share = \DB::table('video_shares')->where('token', $token)->first();
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Mail;
use App\Models\User;
use App\Models\Video;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class VideoShared extends Mailable
{
use Queueable, SerializesModels;
public string $shareTitle;
public function __construct(
public Video $video,
public string $shareUrl,
public User $sender,
public ?string $personalMessage = null,
?string $shareTitle = null,
) {
// Version-aware title (e.g. the English track's title) with a primary fallback.
$this->shareTitle = $shareTitle ?: $video->title;
}
public function envelope(): Envelope
{
return new Envelope(
subject: $this->sender->name . ' shared "' . $this->shareTitle . '" with you on ' . config('app.name'),
// Let the recipient reply straight to the friend who sent it.
replyTo: $this->sender->email
? [new Address($this->sender->email, $this->sender->name)]
: [],
);
}
public function content(): Content
{
return new Content(view: 'emails.video-shared');
}
}