This commit is contained in:
DariusIII
2025-08-10 23:47:33 +02:00
parent 88343da88a
commit 3c0df4e8b7
8 changed files with 55 additions and 48 deletions
@@ -171,14 +171,15 @@ class RegisterController extends Controller
$invitedBy = 0;
$invitation = null;
if (!empty($inviteCode)) {
if (! empty($inviteCode)) {
$invitation = Invitation::findValidByToken($inviteCode);
if ($invitation) {
$invitedBy = $invitation->invited_by;
// Validate email matches invitation
if (!empty($invitation->email) && $invitation->email !== $email) {
if (! empty($invitation->email) && $invitation->email !== $email) {
$error = 'Email address does not match the invitation.';
return $this->showRegistrationForm($request, $error);
}
}
@@ -275,7 +276,7 @@ class RegisterController extends Controller
// Pre-fill email if invitation has one
$invitation = Invitation::findValidByToken($inviteCode);
if ($invitation && !empty($invitation->email)) {
if ($invitation && ! empty($invitation->email)) {
app('smarty.view')->assign('email', $invitation->email);
}
} else {
@@ -323,12 +324,12 @@ class RegisterController extends Controller
$invitation = Invitation::findValidByToken($token);
if (!$invitation) {
if (! $invitation) {
return false;
}
// If invitation has specific email, validate it matches
if (!empty($invitation->email) && $invitation->email !== $email) {
if (! empty($invitation->email) && $invitation->email !== $email) {
return false;
}
@@ -345,6 +346,7 @@ class RegisterController extends Controller
}
$invitation = Invitation::findValidByToken($token);
return $invitation !== null;
}
}
@@ -4,9 +4,9 @@ namespace App\Http\Controllers;
use App\Models\Invitation;
use App\Services\InvitationService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class InvitationController extends BasePageController
{
@@ -137,7 +137,7 @@ class InvitationController extends BasePageController
$request->validate([
'email' => 'required|email|unique:users,email',
'expiry_days' => 'sometimes|integer|min:1|max:30',
'role' => 'sometimes|integer|in:' . implode(',', array_keys(config('nntmux.user_roles', []))),
'role' => 'sometimes|integer|in:'.implode(',', array_keys(config('nntmux.user_roles', []))),
]);
try {
@@ -156,7 +156,7 @@ class InvitationController extends BasePageController
);
return redirect()->route('invitations.index')
->with('success', 'Invitation sent successfully to ' . $request->email);
->with('success', 'Invitation sent successfully to '.$request->email);
} catch (\Exception $e) {
return redirect()->back()
@@ -282,6 +282,7 @@ class InvitationController extends BasePageController
public function stats(): JsonResponse
{
$stats = $this->invitationService->getUserInvitationStats(auth()->id());
return response()->json($stats);
}
@@ -291,7 +292,7 @@ class InvitationController extends BasePageController
public function cleanup(): JsonResponse
{
// Check if user is admin
if (!auth()->user()->hasRole('admin')) {
if (! auth()->user()->hasRole('admin')) {
abort(403, 'Unauthorized');
}
@@ -299,7 +300,7 @@ class InvitationController extends BasePageController
return response()->json([
'message' => "Cleaned up {$cleanedCount} expired invitations",
'count' => $cleanedCount
'count' => $cleanedCount,
]);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ class InvitationMail extends Mailable
public function envelope(): Envelope
{
return new Envelope(
subject: 'You\'re invited to join ' . config('app.name'),
subject: 'You\'re invited to join '.config('app.name'),
);
}
+4 -2
View File
@@ -2,7 +2,6 @@
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -37,6 +36,7 @@ use Illuminate\Support\Str;
class Invitation extends Model
{
public const DEFAULT_INVITES = 1;
public const DEFAULT_INVITE_EXPIRY_DAYS = 7;
/**
@@ -154,7 +154,7 @@ class Invitation extends Model
*/
public function isUsed(): bool
{
return !is_null($this->used_at);
return ! is_null($this->used_at);
}
/**
@@ -175,6 +175,7 @@ class Invitation extends Model
public function markAsExpired(): bool
{
$this->is_active = false;
return $this->save();
}
@@ -237,6 +238,7 @@ class Invitation extends Model
if ($invitation) {
return $invitation->delete();
}
return false;
}
+13 -11
View File
@@ -2,9 +2,9 @@
namespace App\Services;
use App\Mail\InvitationMail;
use App\Models\Invitation;
use App\Models\User;
use App\Mail\InvitationMail;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
@@ -22,7 +22,7 @@ class InvitationService
): Invitation {
// Get the user sending the invitation
$user = User::find($invitedBy);
if (!$user) {
if (! $user) {
throw new \Exception('User not found.');
}
@@ -36,12 +36,12 @@ class InvitationService
// Check if user has invites available (total invites - active pending invitations)
$availableInvites = $user->invites - $activeInvitations;
if ($availableInvites <= 0) {
throw new \Exception('You have no invitations available. You have ' . $activeInvitations . ' pending invitation(s). Contact an administrator if you need more invitations.');
throw new \Exception('You have no invitations available. You have '.$activeInvitations.' pending invitation(s). Contact an administrator if you need more invitations.');
}
// Validate email
$validator = Validator::make(['email' => $email], [
'email' => 'required|email|unique:users,email'
'email' => 'required|email|unique:users,email',
]);
if ($validator->fails()) {
@@ -81,7 +81,7 @@ class InvitationService
{
$invitation = Invitation::findOrFail($invitationId);
if (!$invitation->isValid()) {
if (! $invitation->isValid()) {
throw new \Exception('Cannot resend an invalid invitation.');
}
@@ -102,6 +102,7 @@ class InvitationService
}
$invitation->is_active = false;
return $invitation->save();
}
@@ -112,7 +113,7 @@ class InvitationService
{
$invitation = Invitation::findValidByToken($token);
if (!$invitation) {
if (! $invitation) {
throw new \Exception('Invalid or expired invitation token.');
}
@@ -158,7 +159,7 @@ class InvitationService
->with(['usedBy'])
->orderBy('created_at', 'desc');
return match($status) {
return match ($status) {
'valid' => $query->valid()->paginate(15),
'used' => $query->used()->paginate(15),
'expired' => $query->expired()->paginate(15),
@@ -178,13 +179,14 @@ class InvitationService
/**
* Check if a user can send more invitations
*/
public function canUserSendInvitation(int $userId, int $maxInvitations = null): bool
public function canUserSendInvitation(int $userId, ?int $maxInvitations = null): bool
{
if ($maxInvitations === null) {
return true; // No limit set
}
$sentCount = Invitation::where('invited_by', $userId)->count();
return $sentCount < $maxInvitations;
}
@@ -195,7 +197,7 @@ class InvitationService
{
$invitation = Invitation::findByToken($token);
if (!$invitation) {
if (! $invitation) {
return null;
}
@@ -216,7 +218,7 @@ class InvitationService
public function getUserInvitationDetails(int $userId): array
{
$user = User::find($userId);
if (!$user) {
if (! $user) {
return [];
}
@@ -253,7 +255,7 @@ class InvitationService
'expired_invitations' => $expiredInvitations,
'cancelled_invitations' => $cancelledInvitations,
'calculated_available' => $availableInvites,
'can_send_invite' => $availableInvites > 0
'can_send_invite' => $availableInvites > 0,
];
}
}
@@ -2,8 +2,8 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -16,42 +16,42 @@ return new class extends Migration
// Table exists, let's modify it to add missing columns for our custom system
Schema::table('invitations', function (Blueprint $table) {
// Check and add columns that might be missing
if (!Schema::hasColumn('invitations', 'token')) {
if (! Schema::hasColumn('invitations', 'token')) {
$table->string('token', 64)->unique()->after('id');
}
if (!Schema::hasColumn('invitations', 'email')) {
if (! Schema::hasColumn('invitations', 'email')) {
$table->string('email')->after('token');
}
if (!Schema::hasColumn('invitations', 'invited_by')) {
if (! Schema::hasColumn('invitations', 'invited_by')) {
$table->unsignedBigInteger('invited_by')->after('email');
$table->index('invited_by');
}
if (!Schema::hasColumn('invitations', 'expires_at')) {
if (! Schema::hasColumn('invitations', 'expires_at')) {
$table->timestamp('expires_at')->after('invited_by');
}
if (!Schema::hasColumn('invitations', 'used_at')) {
if (! Schema::hasColumn('invitations', 'used_at')) {
$table->timestamp('used_at')->nullable()->after('expires_at');
}
if (!Schema::hasColumn('invitations', 'used_by')) {
if (! Schema::hasColumn('invitations', 'used_by')) {
$table->unsignedBigInteger('used_by')->nullable()->after('used_at');
$table->index('used_by');
}
if (!Schema::hasColumn('invitations', 'is_active')) {
if (! Schema::hasColumn('invitations', 'is_active')) {
$table->boolean('is_active')->default(true)->after('used_by');
}
if (!Schema::hasColumn('invitations', 'metadata')) {
if (! Schema::hasColumn('invitations', 'metadata')) {
$table->json('metadata')->nullable()->after('is_active');
}
// Add timestamps if they don't exist
if (!Schema::hasColumn('invitations', 'created_at')) {
if (! Schema::hasColumn('invitations', 'created_at')) {
$table->timestamps();
}
});
@@ -138,7 +138,7 @@ return new class extends Migration
{
try {
$indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
if (!empty($indexes)) {
if (! empty($indexes)) {
Schema::table($table, function (Blueprint $table) use ($indexName) {
$table->dropIndex($indexName);
});
@@ -2,8 +2,8 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
@@ -14,7 +14,7 @@ return new class extends Migration
{
Schema::table('invitations', function (Blueprint $table) {
// First, check if we have the old column structure
if (Schema::hasColumn('invitations', 'users_id') && !Schema::hasColumn('invitations', 'invited_by')) {
if (Schema::hasColumn('invitations', 'users_id') && ! Schema::hasColumn('invitations', 'invited_by')) {
// Drop the existing foreign key constraint first
try {
$table->dropForeign('FK_users_inv');
@@ -27,37 +27,37 @@ return new class extends Migration
}
// Add missing columns that our system needs
if (!Schema::hasColumn('invitations', 'token')) {
if (! Schema::hasColumn('invitations', 'token')) {
$table->string('token', 64)->unique()->after('id');
}
if (!Schema::hasColumn('invitations', 'email')) {
if (! Schema::hasColumn('invitations', 'email')) {
$table->string('email')->after('token');
}
if (!Schema::hasColumn('invitations', 'expires_at')) {
if (! Schema::hasColumn('invitations', 'expires_at')) {
$table->timestamp('expires_at')->after('invited_by');
}
if (!Schema::hasColumn('invitations', 'used_at')) {
if (! Schema::hasColumn('invitations', 'used_at')) {
$table->timestamp('used_at')->nullable()->after('expires_at');
}
if (!Schema::hasColumn('invitations', 'used_by')) {
if (! Schema::hasColumn('invitations', 'used_by')) {
$table->unsignedBigInteger('used_by')->nullable()->after('used_at');
$table->index('used_by');
}
if (!Schema::hasColumn('invitations', 'is_active')) {
if (! Schema::hasColumn('invitations', 'is_active')) {
$table->boolean('is_active')->default(true)->after('used_by');
}
if (!Schema::hasColumn('invitations', 'metadata')) {
if (! Schema::hasColumn('invitations', 'metadata')) {
$table->json('metadata')->nullable()->after('is_active');
}
// Add timestamps if they don't exist
if (!Schema::hasColumn('invitations', 'created_at')) {
if (! Schema::hasColumn('invitations', 'created_at')) {
$table->timestamps();
}
});
@@ -83,7 +83,7 @@ return new class extends Migration
$this->dropIndexSafely('invitations', 'invitations_used_by_index');
// Rename back to original column name if it was renamed
if (Schema::hasColumn('invitations', 'invited_by') && !Schema::hasColumn('invitations', 'users_id')) {
if (Schema::hasColumn('invitations', 'invited_by') && ! Schema::hasColumn('invitations', 'users_id')) {
$table->renameColumn('invited_by', 'users_id');
}
@@ -121,7 +121,7 @@ return new class extends Migration
{
try {
$indexes = DB::select("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
if (!empty($indexes)) {
if (! empty($indexes)) {
Schema::table($table, function (Blueprint $table) use ($indexName) {
$table->dropIndex($indexName);
});
+1 -1
View File
@@ -58,6 +58,7 @@ use App\Http\Controllers\FailedReleasesController;
use App\Http\Controllers\FileListController;
use App\Http\Controllers\GamesController;
use App\Http\Controllers\GetNzbController;
use App\Http\Controllers\InvitationController;
use App\Http\Controllers\MovieController;
use App\Http\Controllers\MusicController;
use App\Http\Controllers\MyMoviesController;
@@ -70,7 +71,6 @@ use App\Http\Controllers\RssController;
use App\Http\Controllers\SearchController;
use App\Http\Controllers\SeriesController;
use App\Http\Controllers\TermsController;
use App\Http\Controllers\InvitationController;
// Auth::routes();