Added quiz new features
This commit is contained in:
85
app/Http/Controllers/Admin/DailyQuestController.php
Normal file
85
app/Http/Controllers/Admin/DailyQuestController.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DailyQuest;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DailyQuestController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$quests = \App\Models\DailyQuest::latest()->paginate(15);
|
||||
|
||||
// Upgraded Bank of Smart Suggestions (Hardcore Focused)
|
||||
$suggestions = [
|
||||
// --- EXTREME CHALLENGES ---
|
||||
['title' => 'اسطوره لیگ: پیروزی در ۵۰ دوئل', 'quest_type' => 'duel_win', 'required_amount' => 50, 'reward_xp' => 4000],
|
||||
['title' => 'ماراتن تستزنی: پاسخ به ۵۰۰ تست', 'quest_type' => 'answer_tests', 'required_amount' => 500, 'reward_xp' => 3500],
|
||||
['title' => 'کمالگرا: کسب نمره کامل در ۵ آزمون', 'quest_type' => 'perfect_score', 'required_amount' => 5, 'reward_xp' => 2500],
|
||||
|
||||
// --- HARD CHALLENGES ---
|
||||
['title' => 'مبارز خستگیناپذیر: پیروزی در ۲۰ دوئل', 'quest_type' => 'duel_win', 'required_amount' => 20, 'reward_xp' => 1500],
|
||||
['title' => 'چالش حقوقدان: پاسخ به ۲۵۰ تست', 'quest_type' => 'answer_tests', 'required_amount' => 250, 'reward_xp' => 1200],
|
||||
['title' => 'دقت بالا: کسب نمره کامل در ۳ آزمون', 'quest_type' => 'perfect_score', 'required_amount' => 3, 'reward_xp' => 1200],
|
||||
['title' => 'پیروزی در ۱۰ دوئل متوالی (کاپ نقره)', 'quest_type' => 'duel_win', 'required_amount' => 10, 'reward_xp' => 700],
|
||||
|
||||
// --- MEDIUM CHALLENGES (For balance) ---
|
||||
['title' => 'گرمکردن: پیروزی در ۵ دوئل', 'quest_type' => 'duel_win', 'required_amount' => 5, 'reward_xp' => 300],
|
||||
['title' => 'مرور روزانه: پاسخ به ۵۰ تست', 'quest_type' => 'answer_tests', 'required_amount' => 50, 'reward_xp' => 250],
|
||||
['title' => 'کسب نمره کامل در ۱ آزمون', 'quest_type' => 'perfect_score', 'required_amount' => 1, 'reward_xp' => 350],
|
||||
];
|
||||
|
||||
return view('admin.daily-quests.index', compact('quests', 'suggestions'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.daily-quests.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'quest_type' => 'required|string|in:duel_win,answer_tests,perfect_score',
|
||||
'required_amount' => 'required|integer|min:1',
|
||||
'reward_xp' => 'required|integer|min:10',
|
||||
]);
|
||||
|
||||
$validated['is_active'] = $request->has('is_active'); // <-- Catch the switch
|
||||
|
||||
DailyQuest::create($validated);
|
||||
|
||||
return redirect()->route('daily-quests.index')->with('success', 'چالش روزانه با موفقیت ایجاد شد.');
|
||||
}
|
||||
|
||||
public function update(Request $request, DailyQuest $daily_quest)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'quest_type' => 'required|string|in:duel_win,answer_tests,perfect_score',
|
||||
'required_amount' => 'required|integer|min:1',
|
||||
'reward_xp' => 'required|integer|min:10',
|
||||
]);
|
||||
|
||||
$validated['is_active'] = $request->has('is_active'); // <-- Catch the switch
|
||||
|
||||
$daily_quest->update($validated);
|
||||
|
||||
return redirect()->route('daily-quests.index')->with('success', 'چالش روزانه با موفقیت ویرایش شد.');
|
||||
}
|
||||
|
||||
|
||||
public function edit(DailyQuest $daily_quest)
|
||||
{
|
||||
return view('admin.daily-quests.update', compact('daily_quest'));
|
||||
}
|
||||
|
||||
public function destroy(DailyQuest $daily_quest)
|
||||
{
|
||||
$daily_quest->delete();
|
||||
return redirect()->route('daily-quests.index')->with('success', 'چالش روزانه حذف شد.');
|
||||
}
|
||||
}
|
||||
139
app/Http/Controllers/api/LeagueController.php
Normal file
139
app/Http/Controllers/api/LeagueController.php
Normal file
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\UserQuest;
|
||||
use App\Traits\BaseApiResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LeagueController extends Controller
|
||||
{
|
||||
use BaseApiResponse;
|
||||
|
||||
/**
|
||||
* API 1: User League Stats
|
||||
*/
|
||||
public function stats(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Dynamic Level Calculation (Every 500 XP = 1 Level)
|
||||
$level = floor($user->xp / 500) + 1;
|
||||
$xpToNext = 500 - ($user->xp % 500);
|
||||
$progress = ($user->xp % 500) / 500;
|
||||
|
||||
$winRate = $user->duels_played > 0
|
||||
? round(($user->duels_won / $user->duels_played) * 100)
|
||||
: 0;
|
||||
|
||||
// Calculate Global Rank Position
|
||||
$rankPosition = User::where('xp', '>', $user->xp)->count() + 1;
|
||||
|
||||
$data = [
|
||||
'user_id' => $user->id,
|
||||
'name' => $user->name ?? 'کاربر',
|
||||
'current_level' => $level,
|
||||
'rank_name' => $this->getRankName($level),
|
||||
'total_xp' => $user->xp,
|
||||
'xp_to_next_level' => $xpToNext,
|
||||
'progress_percent' => round($progress, 2),
|
||||
'win_rate_percent' => $winRate,
|
||||
'total_duels' => $user->duels_played,
|
||||
'global_rank_position' => $rankPosition
|
||||
];
|
||||
|
||||
return $this->success($data, 'موفق', 'اطلاعات لیگ کاربر دریافت شد');
|
||||
}
|
||||
|
||||
/**
|
||||
* API 2: Daily Quests
|
||||
*/
|
||||
public function quests(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
// Fetch active quests for today using the new relationship
|
||||
$userQuests = $user->activeQuests()->with('quest')->get();
|
||||
|
||||
$formattedQuests = $userQuests->map(function ($uq) {
|
||||
return [
|
||||
'id' => $uq->id,
|
||||
'title' => $uq->quest->title,
|
||||
'current_progress' => $uq->current_progress,
|
||||
'required_amount' => $uq->quest->required_amount,
|
||||
'reward_text' => '+' . $uq->quest->reward_xp . ' XP',
|
||||
'is_completed' => $uq->is_completed
|
||||
];
|
||||
});
|
||||
|
||||
$expiresIn = now()->endOfDay()->diffInSeconds(now());
|
||||
|
||||
return $this->success([
|
||||
'expires_in_seconds' => $expiresIn,
|
||||
'quests' => $formattedQuests
|
||||
], 'موفق', 'چالشهای روزانه دریافت شد');
|
||||
}
|
||||
|
||||
/**
|
||||
* API 3: League Leaderboard
|
||||
*/
|
||||
public function leaderboard(Request $request)
|
||||
{
|
||||
$currentUser = $request->user();
|
||||
|
||||
// Get Top 50 Users
|
||||
$topUsers = User::orderByDesc('xp')->limit(50)->get(['id', 'name', 'xp']);
|
||||
|
||||
$leaderboard = $topUsers->map(function ($u, $index) use ($currentUser) {
|
||||
return [
|
||||
'rank' => $index + 1,
|
||||
'user_id' => $u->id,
|
||||
'name' => $u->name ?? 'کاربر ناشناس',
|
||||
'xp' => $u->xp,
|
||||
'is_me' => $u->id === $currentUser->id
|
||||
];
|
||||
});
|
||||
|
||||
// Current User Data
|
||||
$myRank = User::where('xp', '>', $currentUser->xp)->count() + 1;
|
||||
|
||||
$myStats = [
|
||||
'rank' => $myRank,
|
||||
'user_id' => $currentUser->id,
|
||||
'name' => $currentUser->name ?? 'شما',
|
||||
'xp' => $currentUser->xp,
|
||||
'is_me' => true
|
||||
];
|
||||
|
||||
return $this->success([
|
||||
'leaderboard' => $leaderboard,
|
||||
'current_user' => $myStats
|
||||
], 'موفق', 'جدول ردهبندی دریافت شد');
|
||||
}
|
||||
|
||||
/**
|
||||
* API 4: PvP Matchmaking Entry
|
||||
*/
|
||||
public function matchmake(Request $request)
|
||||
{
|
||||
$ticketId = 'req_' . Str::random(12);
|
||||
|
||||
return $this->success([
|
||||
'matchmaking_ticket_id' => $ticketId,
|
||||
'status' => 'searching',
|
||||
'websocket_channel' => 'private-matchmaking.' . $ticketId,
|
||||
'estimated_wait_seconds' => 15
|
||||
], 'در حال جستجو', 'به صف انتظار دوئل اضافه شدید');
|
||||
}
|
||||
|
||||
private function getRankName($level)
|
||||
{
|
||||
if ($level >= 50) return 'الماس';
|
||||
if ($level >= 30) return 'طلا';
|
||||
if ($level >= 10) return 'نقرهای';
|
||||
return 'برنز';
|
||||
}
|
||||
}
|
||||
28
app/Models/DailyQuest.php
Normal file
28
app/Models/DailyQuest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DailyQuest extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'required_amount',
|
||||
'reward_xp',
|
||||
'quest_type',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
public function userQuests()
|
||||
{
|
||||
return $this->hasMany(UserQuest::class, 'daily_quest_id');
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ class User extends Authenticatable
|
||||
'is_admin',
|
||||
'email',
|
||||
'password',
|
||||
'xp',
|
||||
'duels_played',
|
||||
'duels_won',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -67,4 +70,25 @@ class User extends Authenticatable
|
||||
->withPivot('read_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// --- NEW: Gamification Relationships ---
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get all quest records for the user.
|
||||
*/
|
||||
public function userQuests()
|
||||
{
|
||||
return $this->hasMany(UserQuest::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper relationship to quickly get ONLY active, unexpired quests.
|
||||
*/
|
||||
public function activeQuests()
|
||||
{
|
||||
return $this->hasMany(UserQuest::class)
|
||||
->where('expires_at', '>', now());
|
||||
}
|
||||
}
|
||||
|
||||
35
app/Models/UserQuest.php
Normal file
35
app/Models/UserQuest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserQuest extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'daily_quest_id',
|
||||
'current_progress',
|
||||
'is_completed',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
// Casts ensure Laravel automatically converts these to the right data types
|
||||
protected $casts = [
|
||||
'is_completed' => 'boolean',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function quest()
|
||||
{
|
||||
return $this->belongsTo(DailyQuest::class, 'daily_quest_id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user