پس از تلاشهای فراوان **معین** بالاخره به سرزمین رویاهایش [**ایرانسرور** *(IranServer)*](https://www.iranserver.com/) رسید! از آنجایی که ایرانسرور قصد دارد به زودی **سرویسهای ابری جدیدی** را به کاربرانش ارائه دهد، معین قرار است همراه با سایر *ایرانسروریون* به توسعه پنل کاربری جدیدی با عنوان __"ایرانگیت"__ مشغول شود که بر پایه [**لاراول ۱۲**](https://laravel.com/docs/12.x/releases#laravel-12) است که به تازگی معرفی شده است!
از آنجایی که *ایرانگیت* به زودی قرار است به کاربران معرفی شود، در این سوال شما نیز قرار است تا به کمک معین و *ایرانسروریون* بروید تا با استفاده از [**لاراول ۱۲**](https://laravel.com/docs/12.x/releases#laravel-12) و [**استارتر کیت Jetstream**](https://jetstream.laravel.com/introduction.html) بخشهایی از پنل کاربری ایرانگیت را پیادهسازی کنید.

# جزئیات پروژه
**پروژهی اولیه** را از [این لینک](/contest/assignments/81560/download_problem_initial_project/279971/) دانلود کنید.
<details class="yellow">
<summary>
**ساختار فایلها**
</summary>
```
irangate
├── app
│ ├── Http
│ │ ├── Middleware
│ │ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">CheckFeatureAccess.php</mark>
│ ├── Jobs
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">DowngradePendingInvoicesJob.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">ExpireSubscriptionsJob.php</mark>
│ ├── Models
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Coupon.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Feature.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Invoice.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Log.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Plan.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Referral.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">Subscription.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionLog.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">User.php</mark>
│ ├── Observers
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionObserver.php</mark>
│ ├── Policies
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">FeaturePolicy.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionPolicy.php</mark>
│ ├── Providers
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">AppServiceProvider.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">AuthServiceProvider.php</mark>
├── bootstrap
│ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">app.php</mark>
│ └── <mark class="orange" title="این فایل باید پیاده سازی شود">providers.php</mark>
├── database
│ ├── factories
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">CouponFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">FeatureFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">InvoiceFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">LogFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">PlanFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">ReferralFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionFactory.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionLogFactory.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">UserFactory.php</mark>
│ ├── migrations
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164235_create_plans_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164344_create_features_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164408_create_subscriptions_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164432_create_subscription_logs_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164501_create_referrals_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164538_create_coupons_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164610_create_invoices_table.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_164639_create_logs_table.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">2025_02_26_173343_create_plan_feature_table.php</mark>
│ ├── seeders
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">CouponSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">DatabaseSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">FeatureSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">InvoiceSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">LogSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">PlanSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">ReferralSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionLogSeeder.php</mark>
│ │ ├── <mark class="orange" title="این فایل باید پیاده سازی شود">SubscriptionSeeder.php</mark>
│ │ └── <mark class="orange" title="این فایل باید پیاده سازی شود">UserSeeder.php</mark>
├── routes
│ └── <mark class="orange" title="این فایل باید پیاده سازی شود">console.php</mark>
├── tests
├── README.md
├── artisan
├── composer.json
├── composer.lock
├── package.json
├── phpunit.xml
├── server.php
└── webpack.mix.js
```
</details>
<details class="grey">
<summary>
**راهاندازی پروژه**
</summary>
**برای اجرای پروژه، باید `php`، `composer` و `npm` را از قبل نصب کرده باشید.**
+ ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
+ دستور `composer install` را در **پوشهی اصلی پروژه** برای نصب نیازمندیها اجرا کنید.
+ دستور `npm install` را در **پوشهی اصلی پروژه** برای نصب نیازمندیها اجرا کنید. _(توجه کنید که این پروژه از_ [_Tailwindcss_](https://tailwindcss.com/) _استفاده میکند)_
+ دستور `npm run dev` را در **مسیر پوشه اصلی پروژه** اجرا کنید. در صورتی که این دستور را اجرا **نکنید،** **نمیتوانید** ویوهای ساخته شده با _Tailwindcss_ را مشاهده کنید.
+ برای **اجرای مایگریشنها** از دستور `php artisan migrate` استفاده کنید.
+ برای **اجرای تستهای نمونه،** میتوانید از دستور `php artisan test` استفاده کنید.
</details>
شما در این سوال قرار است تا در **پیادهسازی یک سیستم پنل کاربری** بر پایه *Jetstream* که **امکانات شخصیسازی شدهای** دارد به معین و ایرانسروریون کمک کنید. **توجه داشته باشید که برخی مواردی که در این سوال از شما خواسته شده تا پیادهسازی کنید ممکن است تا در این سوال به صورت عملیاتی مورد استفاده قرار نگیرند، اما در داوری سوال مورد ارزیابی قرار خواهند گرفت.** توضیح مواردی که باید پیادهسازی شوند به شکل زیر میباشد:

## **معرفی کلی پروژه ایرانگیت** *(IranGate)* **و موارد پیادهسازی شده**
در این بخش به معرفی پروژه **ایرانگیت** *(IranGate)* خواهیم پرداخت. بخشهایی از ایرانگیت در این پروژه اولیه این سوال از قبل پیادهسازی شدهاند و نیازی به پیادهسازی دوباره ندارند. پایه ایرانگیت همانطور که پیشتر گفته شد بر اساس [**استارتر کیت Jetstream**](https://jetstream.laravel.com/introduction.html) میباشد و امکانات پیشفرضی که این استارتر کیت در اختیار توسعهدهندگان قرار میدهد را شامل میشود. موارد پیادهسازی شده در پروژه به شکل زیر هستند:
<details class="yellow">
<summary>
**معرفی امکانات پیشفرض بر پایه** *Jetstream*
</summary>
این پروژه **بر اساس امکانات پیشفرض** پیادهسازی شده در *Jetstream* از **ویوها، کامپوننتها، روتها و ...** این استارتر کیت **ارثبری** میکند. **صفحه پیشفرض پروژه،** صفحه پیشفرض **لاراول ۱۲** میباشد که به شما برای شروع حل این سوال خوشآمد میگوید:

بخشی از **روتهای** *(Routes)* پیادهسازی شده در پروژه نیز به مانند روتهای پیشفرض *Jetstream* به شکل زیر تعریف شدهاند:
```php routes/web.php laravel
<?php
use App\Http\Controllers\FeatureController;
use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\PlanController;
use App\Http\Controllers\SubscriptionLogController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified',
])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
```
شما میتوانید همانند تصاویر زیر از **امکانات پیشفرض پیادهسازی شده** در پروژه برای **ورود، عضویت، فراموشی رمز عبور و پنل کاربری پیشفرض** استفاده کنید:



</details>
<details class="yellow">
<summary>
**معرفی کنترلر** `FeatureController`
</summary>
این کنترلر برای **مدیریت دسترسی به ویژگیهای مختلف** استفاده میشود. **میدلور** `CheckFeatureAccess` که قرار است توسط شما پیادهسازی شود، **بررسی میکند که کاربر دسترسی به یک ویژگی خاص با عنوان** `featureSlug` را دارد یا نه و پیامی مبنی بر مجوز دسترسی به آن ویژگی برمیگرداند. **کنترلر** و **روت** *(Route)* مربوط به آن به شکل زیر از قبل پیادهسازی شدهاند:
```php app/Http/Controllers/FeatureController.php laravel
<?php
namespace App\Http\Controllers;
class FeatureController extends Controller
{
public function access($featureSlug)
{
return response('Access granted to feature: ' . $featureSlug, 200);
}
}
```
```php routes/web.php laravel
<?php
use App\Http\Controllers\FeatureController;
use App\Http\Controllers\InvoiceController;
use App\Http\Controllers\PlanController;
use App\Http\Controllers\SubscriptionLogController;
use Illuminate\Support\Facades\Route;
Route::get('/feature/{featureSlug}', [FeatureController::class, 'access'])->middleware('feature.access')->name('feature');
```
</details>
<details class="yellow">
<summary>
**معرفی کنترلر** `InvoiceController` **و ویوی** `invoices.index`
</summary>

این **کنترلر** برای نمایش **صورتحسابهای کاربران** است. متد `index` تمام فاکتورها را از طریق ارتباط با مدل `Invoice` و برای کاربر وارد شده (`auth()->user()`) بدست میآورد و در ویوی `invoices.index` به نمایش میگذارد. **کنترلر** مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
```php app/Http/Controllers/InvoiceController.php laravel
<?php
namespace App\Http\Controllers;
class InvoiceController extends Controller
{
public function index()
{
$invoices = auth()->user()->invoices()->latest()->get();
return view('invoices.index', compact('invoices'));
}
}
```
</details>
<details class="yellow">
<summary>
**معرفی کنترلر** `PlanController` **و ویوی** `profile.plans`
</summary>

این **کنترلر** برای **مدیریت اشتراکهای کاربری و تغییرات آنها** استفاده میشود. **متد** `index` **تمام اشتراکهای کاربری موجود را نمایش میدهد** و **متد** `switch` به کاربر این امکان را میدهد که اشتراک کاربری خود را تغییر دهد. **اگر اشتراک جدید هزینه داشته باشد، یک صورتحساب برای پرداخت ایجاد میشود** که شما باید در بخشی از پیادهسازیهای خود آنها را مدیریت کنید. **کنترلر** مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
```php app/Http/Controllers/PlanController.php laravel
<?php
namespace App\Http\Controllers;
use App\Models\Plan;
use Illuminate\Support\Facades\Auth;
class PlanController extends Controller
{
public function index()
{
$plans = Plan::all();
return view('profile.plans', compact('plans'));
}
public function switch(Plan $plan)
{
$user = Auth::user();
$subscription = $user->subscription;
if (!$subscription) {
$subscription = $user->subscription()->create([
'plan_id' => $plan->id,
'expires_at' => now()->addDays($plan->duration),
]);
} else {
$subscription->plan_id = $plan->id;
$subscription->expires_at = now()->addDays($plan->duration);
}
if ($plan->price > 0) {
$invoice = $user->invoices()->create([
'plan_id' => $plan->id,
'amount' => $plan->price,
'status' => 'pending',
]);
$subscription->expires_at = now()->addDays($plan->duration);
$subscription->save();
return redirect()->route('plans.index')->with('success', 'اشتراک کاربری شما با موفقیت بروزرسانی شد. لطفا نسبت به پرداخت صورت حساب آن اقدام کنید.');
} else {
$subscription->save();
return redirect()->route('plans.index')->with('success', 'اشتراک کاربری شما با موفقیت بروزرسانی شد.');
}
}
}
```
**با تغییر اشتراک کاربری،** پیام موفقیت در تغییر اشتراک کاربری به نمایش در میآید و شما میتوانید در **منوی کاربری بالای صفحه امکانات مختلفی که برای آن اشتراک کاربری تعریف شده** است را ببینید و به آنها **دسترسی** پیدا کنید.


</details>
<details class="yellow">
<summary>
**معرفی کنترلر** `SubscriptionLogController` **و ویوی** `subscription-logs.index`
</summary>

این **کنترلر** برای نمایش **سوابق اشتراکهای کاربری** است. متد `index` **تمام سوابق اشتراکی مربوط به کاربر فعلی** را از طریق ارتباط با مدل `SubscriptionLog` بدست میآورد و در ویوی `subscription-logs.index` به نمایش میگذارد. **کنترلر** مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
```php app/Http/Controllers/SubscriptionLogController.php laravel
<?php
namespace App\Http\Controllers;
class SubscriptionLogController extends Controller
{
public function index()
{
$subscriptionLogs = auth()->user()->subscriptionLogs()->latest()->get();
return view('subscription-logs.index', compact('subscriptionLogs'));
}
}
```
</details>
## **پیادهسازی جداول و نوشتن مایگریشنها** *(Migrations)*
در این بخش، مایگریشنهای مربوط به ایجاد جداول `users`، `plans`، `subscriptions`، `invoices`، `logs`، `coupons`، `features` و سایر جداول مرتبط باید پیادهسازی شوند. این جداول برای مدیریت **کاربران، اشتراکها، پرداختها، ویژگیهای پلنها و گزارشهای سیستم** طراحی شدهاند. ساختار مربوط به هر کدام از جداول در بخش زیر مشخص است:
<details class="blue">
<summary>
**ساختار جدول** `users` **و پیادهسازی مایگریشن** `create_users_table`
</summary>
## **ساختار جدول** `users`**:**
| نام ستون | نوع داده | توضیحات |
| ---------------- | ----------- | ---------------------- |
| `id` | `bigint` | کلید اصلی |
| `name` | `string` | نام کاربر |
| `email` | `string` | ایمیل (منحصربهفرد) |
| `password` | `string` | رمز عبور |
| `remember_token` | `string` | توکن احراز هویت |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `plans` **و پیادهسازی مایگریشن** `create_plans_table`
</summary>
## **ساختار جدول** `plans`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ----------------------------- |
| `id` | `bigint` | کلید اصلی |
| `name` | `string` | نام اشتراک کاربری |
| `slug` | `string` | عنوان یکتای اشتراک کاربری (منحصربهفرد) |
| `price` | `decimal` | قیمت اشتراک کاربری |
| `duration` | `integer` | مدت زمان اشتراک کاربری (به روز) |
| `is_active` | `boolean` | وضعیت فعال بودن اشتراک کاربری |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `subscriptions` **و پیادهسازی مایگریشن** `create_subscriptions_table`
</summary>
## **ساختار جدول** `subscriptions`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ---------------------- |
| `id` | `bigint` | کلید اصلی |
| `user_id` | `bigint` | کلید خارجی به `users` |
| `plan_id` | `bigint` | کلید خارجی به `plans` |
| `expires_at` | `timestamp` | تاریخ انقضای اشتراک کاربری |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `invoices` **و پیادهسازی مایگریشن** `create_invoices_table`
</summary>
## **ساختار جدول** `invoices`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ---------------------------------- |
| `id` | `bigint` | کلید اصلی |
| `user_id` | `bigint` | کلید خارجی به `users` |
| `plan_id` | `bigint` | کلید خارجی به `plans` |
| `payment_id` | `string` | یک رشته که میتواند `null` باشد |
| `amount` | `decimal` | مبلغ پرداختی |
| `status` | `string` | وضعیت پرداخت (`paid` یا `pending`) |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `logs` **و پیادهسازی مایگریشن** `create_logs_table`
</summary>
## **ساختار جدول** `logs`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ---------------------- |
| `id` | `bigint` | کلید اصلی |
| `user_id` | `bigint` | کلید خارجی به `users` |
| `action` | `string` | نوع فعالیت انجامشده |
| `details` | `text` | جزئیات فعالیت |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `coupons` **و پیادهسازی مایگریشن** `create_coupons_table`
</summary>
## **ساختار جدول** `coupons`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ---------------------- |
| `id` | `bigint` | کلید اصلی |
| `code` | `string` | کد تخفیف (منحصربهفرد) |
| `discount` | `decimal` | مقدار تخفیف |
| `max_uses` | `integer` | حداکثر دفعات استفاده |
| `expires_at` | `timestamp` | تاریخ انقضای کد |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `features` **و پیادهسازی مایگریشن** `create_features_table`
</summary>
## **ساختار جدول** `features`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ---------------------- |
| `id` | `bigint` | کلید اصلی |
| `name` | `string` | نام ویژگی |
| `slug` | `string` | شناسه یکتای ویژگی |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `plan_feature` **و پیادهسازی مایگریشن** `create_plan_feature_table`
</summary>
## **ساختار جدول** `plan_feature`**:**
| نام ستون | نوع داده | توضیحات |
| ------------ | ----------- | ------------------------ |
| `id` | `bigint` | کلید اصلی |
| `plan_id` | `bigint` | کلید خارجی به `plans` |
| `feature_id` | `bigint` | کلید خارجی به `features` |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `referrals` **و پیادهسازی مایگریشن** `create_referrals_table`
</summary>
## **ساختار جدول** `referrals`**:**
| نام ستون | نوع داده | توضیحات |
| ------------- | ----------- | ---------------------------------- |
| `id` | `bigint` | کلید اصلی |
| `referrer_id` | `bigint` | کاربری که معرف شخص دیگر است |
| `referred_id` | `bigint` | کاربری که معرفی شده است |
| `code` | `string` | کد ارجاع (منحصربهفرد) |
| `reward` | `decimal` | پاداش دادهشده به معرف |
| `is_used` | `boolean` | وضعیت استفاده از کد (`true/false`) |
| `created_at` | `timestamp` | زمان ایجاد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی |
</details>
<details class="blue">
<summary>
**ساختار جدول** `subscription_logs` **و پیادهسازی مایگریشن** `create_subscription_logs_table`
</summary>
## **ساختار جدول** `subscription_logs`**:**
| نام ستون | نوع داده | توضیحات |
| ------------------------ | ----------- | -------------------------------- |
| `id` | `bigint` | کلید اصلی |
| `user_id` | `bigint` | کلید خارجی به جدول کاربران |
| `old_subscription_level` | `string` | سطح قبلی اشتراک کاربر (nullable) |
| `new_subscription_level` | `string` | سطح جدید اشتراک کاربر (nullable) |
| `created_at` | `timestamp` | زمان ایجاد رکورد |
| `updated_at` | `timestamp` | زمان آخرین بهروزرسانی رکورد |
</details>
## **پیادهسازی مدلها**
لیست برخی از مدلهای پیادهسازی شده در سیستم به شرح زیر است که شما باید آنها را همراه با روابط بینشان در این بخش پیادهسازی کنید:
+ **مدل `User`**: مدیریت کاربران سیستم، شامل اطلاعات کاربری و روابط مرتبط.
+ **مدل `Plan`**: نمایشدهندهی اشتراکهای کاربری، شامل ویژگیها و مشخصات هر پلن.
+ **مدل `Subscription`**: ثبت اطلاعات اشتراک کاربران در اشتراکهای مختلف.
+ **مدل `Invoice`**: صورتحسابهای مربوط به خرید اشتراک و پرداختهای کاربران.
+ **مدل `Feature`**: ویژگیهایی که برای هر اشتراک کاربری قابل ارائه هستند.
+ **مدل `Coupon`**: کدهای تخفیف که کاربران میتوانند برای کاهش هزینه خرید اشتراک کاربری استفاده کنند.
+ **مدل `Referral`**: ثبت سیستم ارجاع کاربران و پاداشهای مربوط به معرفی کاربران جدید.
+ **مدل `Log`**: ثبت اقدامات کاربران در سیستم جهت نظارت و بررسی تاریخچه فعالیتها.
+ **مدل `SubscriptionLog`**: مدیریت تغییرات اشتراک کاربران و تاریخچه ارتقا یا کاهش پلن.
## **پیادهسازی روابط بین مدلها**
+ هر **کاربر (User)** میتواند دارای یک **اشتراک کاربری (Subscription)** باشد.
+ هر **اشتراک کاربری (Subscription)** به یک **اشتراک (Plan)** متصل است.
+ هر **اشتراک (Plan)** میتواند چندین **ویژگی (Feature)** داشته باشد.
+ هر **ویژگی (Feature)** میتواند به چندین **اشتراک (Plan)** مرتبط باشد.
+ هر **کاربر** میتواند چندین **صورتحساب (Invoice)** برای خرید پلنهای مختلف داشته باشد.
+ هر **صورتحساب (Invoice)** به دقیقا یک **اشتراک (Plan)** و دقیقا یک **کاربر (User)** مرتبط است.
+ هر **کاربر (User)** میتواند از **کدهای تخفیف (Coupon)** استفاده کند.
+ هر **کاربر (User)** میتواند از طریق سیستم ارجاع، کاربران جدید را معرفی کرده و پاداش دریافت کند.
+ هر **کاربر (User)** میتواند **چندین لاگ (Log)** از فعالیتهای خود داشته باشد. همچنین هر لاگ مربوط به دقیقا یک **کاربر (User)** است.
+ هر **کاربر (User)** میتواند چندین **تاریخچه تغییرات اشتراک (SubscriptionLogs)** داشته باشد.
## **پیادهسازی فکتوریها** *(Factories)*
در این بخش، فکتوریهای تعریف شده برای تولید دادههای تصادفی جهت تست برنامه معرفی شدهاند. این فکتوریها به منظور تولید دادههای آزمایشی برای جداول مرتبط در پایگاه داده استفاده میشوند:
<details class="red">
<summary>
**ساختار فکتوری** `CouponFactory`
</summary>
## **فکتوری** `CouponFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ---------- | ---------------------- | ------------------------------ |
| `code` | کد تخفیف (منحصربهفرد) | یک UUID تصادفی |
| `discount` | مقدار تخفیف | عدد اعشاری بین ۵ تا ۵۰ |
| `max_uses` | حداکثر دفعات استفاده | عدد صحیح بین ۱ تا ۱۰۰ |
| `expires_at` | تاریخ انقضا | تاریخی بین حالا تا ۱ سال آینده |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `FeatureFactory`
</summary>
### **فکتوری**`FeatureFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| -------- | ----------------- | ------------------ |
| `name` | نام ویژگی | یک کلمه تصادفی |
| `slug` | شناسه یکتا (slug) | مقدار یکتای تصادفی |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `InvoiceFactory`
</summary>
### **فکتوری** `InvoiceFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ---------- | -------------------------- | ------------------------ |
| `user_id` | کلید خارجی به جدول `users` | مقدار یک `user_id` معتبر |
| `plan_id` | کلید خارجی به جدول `plans` | مقدار یک `plan_id` معتبر |
| `payment_id` | شناسه پرداخت | یک UUID تصادفی |
| `amount` | مبلغ صورتحساب | عدد اعشاری بین ۱۰ تا ۱۰۰ |
| `status` | وضعیت پرداخت | `paid` یا `failed` |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `LogFactory`
</summary>
### **فکتوری** `LogFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| -------- | -------------------------- | ------------------------ |
| `action` | نوع فعالیت | یک کلمه تصادفی |
| `details` | جزئیات فعالیت | یک آرایه JSON |
| `user_id` | کلید خارجی به جدول `users` | مقدار یک `user_id` معتبر |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `PlanFactory`
</summary>
### **فکتوری** `PlanFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| --------- | --------------------------- | ------------------------ |
| `name` | نام اشتراک | یک کلمه تصادفی |
| `slug` | شناسه یکتا (slug) | مقدار یکتای تصادفی |
| `price` | قیمت | عدد اعشاری بین ۱۰ تا ۱۰۰ |
| `duration` | مدت زمان (روز) | عدد صحیح بین ۳۰ تا ۳۶۵ |
| `is_active` | وضعیت فعال/غیرفعال بودن اشتراک | مقدار `true` یا `false` |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `ReferralFactory`
</summary>
### **فکتوری** `ReferralFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ----------- | -------------------------- | ------------------------ |
| `referrer_id` | کلید خارجی به جدول `users` | مقدار یک `user_id` معتبر |
| `referred_id` | کلید خارجی به جدول `users` | مقدار یک `user_id` معتبر |
| `code` | کد ارجاع (منحصربهفرد) | یک UUID تصادفی |
| `reward` | مقدار جایزه ارجاع | عدد اعشاری بین ۰ تا ۱۰۰ |
| `is_used` | وضعیت استفاده از کد | مقدار `true` یا `false` |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `UserFactory`
</summary>
### **فکتوری** `UserFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ----------------- | ------------------- | ----------------------- |
| `name` | نام کاربر | یک نام تصادفی |
| `email` | ایمیل کاربر (یونیک) | ایمیل تصادفی معتبر |
| `email_verified_at` | زمان تأیید ایمیل | `now()` یا `null` |
| `password` | رمز عبور | `password` هششده |
| `remember_token` | توکن یادآوری | رشته ۱۰ کاراکتری تصادفی |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `SubscriptionFactory`
</summary>
### **فکتوری** `SubscriptionFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ------------ | ---------------------------- | ---------------------------------- |
| `user_id` | شناسه کاربر مرتبط | مقدار تصادفی از `UserFactory` |
| `plan_id` | شناسه پلن مرتبط | مقدار تصادفی از `PlanFactory` |
| `expires_at` | تاریخ انقضای اشتراک | تاریخ تصادفی بین `now` و `+1 year` |
| `created_at` | زمان ایجاد رکورد | `now()` |
| `updated_at` | زمان آخرین بهروزرسانی رکورد | `now()` |
</details>
<details class="red">
<summary>
**ساختار فکتوری** `SubscriptionLogFactory`
</summary>
### **فکتوری** `SubscriptionLogFactory`
| نام ستون | توضیحات | مقادیر ممکن |
| ------------------------ | ---------------------------- | ----------------------------- |
| `user_id` | شناسه کاربر مرتبط | مقدار تصادفی از `UserFactory` |
| `old_subscription_level` | سطح قبلی اشتراک کاربر | یک کلمه تصادفی |
| `new_subscription_level` | سطح جدید اشتراک کاربر | یک کلمه تصادفی |
| `created_at` | زمان ایجاد رکورد | `now()` |
| `updated_at` | زمان آخرین بهروزرسانی رکورد | `now()` |
</details>
## **پیادهسازی سیدر** *(Seeder)*
درون پروژه اولیه تعدادی **سیدر** *(Seeder)* وجود دارد که باید به شکل زیر پیادهسازی شوند، در نهایت قرار است تا این سیدرها با ترتیب درستی در سیدر `DatabaseSeeder` مورد استفاده قرار بگیرند. سیدرها باید به شکل زیر ایجاد شوند:
1. **دقیقا ۵۰ کد تخفیف** *(Coupon)* با استفاده از فکتوری `Coupon` ساخته شود.
2. **دقیقا ۲۰ قابلیت** *(Feature)* با استفاده از فکتوری `Feature` ساخته شود. همچنین یک اشتراک کاربری با **عنوان** *(Slug)* `vip` که توسط سیدر مربوطه ساخته شده **باید به تمام قابلیتهای ساخته شده توسط این سیدر** دسترسی داشته باشد.
3. **دقیقا ۱۰۰ صورت حساب** *(Invoice)* با استفاده از فکتوری `Invoice` ساخته شود.
4. **دقیقا ۲۰۰ لاگ** *(Log)* با استفاده از فکتوری `Log` ساخته شود.
5. **دقیقا ۱۰ اشتراک** *(Plan)* با استفاده از فکتوری `Plan` ساخته شود. همچنین باید سه اشتراک کاربری دیگر توسط این سیدر به صورت زیر ساخته شود:
- **اشتراک** پایه با **نام** *(Name)* `Basic`، **عنوان** *(Slug)* `basic`، **قیمت** *(Price)* `0` و **مدتزمان** *(Duration)* `30` روز
- **اشتراک** پریمیوم با **نام** *(Name)* `Premium`، **عنوان** *(Slug)* `premium`، **قیمت** *(Price)* `100` و **مدتزمان** *(Duration)* `30` روز
- **اشتراک** ویژه با **نام** *(Name)* `Vip`، **عنوان** *(Slug)* `vip`، **قیمت** *(Price)* `200` و **مدتزمان** *(Duration)* `30` روز
6. **دقیقا ۵۰ ارجاع** *(Referral)* با استفاده از فکتوری `Referral` ساخته شود.
7. **دقیقا ۱۵۰ گزارش اشتراک** *(SubscriptionLog)* با استفاده از فکتوری `SubscriptionLog` ساخته شود.
8. **دقیقا ۱۰۰ اشتراک کاربری** *(Subscription)* با استفاده از فکتوری `Subscription` ساخته شود.
9. **دقیقا ۵۰ کاربر** *(User)* با استفاده از فکتوری `User` ساخته شود. **تمامی این ۵۰ کاربر** باید در **اشتراک کاربری** `basic` که قبلا توسط سیدر مربوطه ساخته شده است، **مشترک** شوند.
## **پیادهسازی پالیسیها** *(Policies)*
در این سوال شما باید **دو پالیسی** `FeaturePolicy` و `SubscriptionPolicy` را برای **مدیریت دسترسی به ویژگیهای خاص** و **اشتراکهای کاربری خاص** به صورت زیر پیادهسازی کنید:
<details class="green">
<summary>
**ساختار پالیسی** `FeaturePolicy`
</summary>
**پالیسی** `FeaturePolicy` به صورت زیر در مسیر `app/Policies/FeaturePolicy.php` پیادهسازی شده است که با استفاده از تابعی با نام `accessFeature` و دریافت **دو مقدار** `$user` و `$feature` **به صورت پویا** مشخص میکند که آیا کاربر `$user` در **اشتراک کاربری فعلیاش** میتواند میتواند به **قابلیت** `$feature` **دسترسی داشته باشد یا نه.**
```php app/Policies/FeaturePolicy.php laravel
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Feature;
class FeaturePolicy
{
public function accessFeature(User $user, Feature $feature)
{
// TODO
}
}
```
- از این پالیسی برای مدیریت دسترسی بر اساس **قابلیتها** *(Features)* استفاده خواهد شد.
</details>
<details class="green">
<summary>
**ساختار پالیسی** `SubscriptionPolicy`
</summary>
**پالیسی** `SubscriptionPolicy` به صورت زیر در مسیر `app/Policies/SubscriptionPolicy.php` پیادهسازی شده است که برای **مدیریت دسترسیهای کاربران با اشتراکهای کاربری پیشفرض** `basic`، `premium` و `vip` به بخشهای مختلف پنلکاربری مورد استفاده قرار میگیرد. این پالیسی با استفاده از هر کدام از سه تابع `accessVIP`، `accessPremium` و `accessBasic` بررسی میکند که آیا **اشتراک کاربری** `$subscription` که متعلق به **کاربر** `$user` میباشد به ترتیب از نوع `vip`، `premium` و `basic` **میباشد یا نه.**
```php app/Policies/FeaturePolicy.php laravel
<?php
namespace App\Policies;
use App\Models\Subscription;
use App\Models\User;
use App\Models\Plan;
class SubscriptionPolicy
{
public function accessVip(User $user, Subscription $subscription)
{
// TODO
}
public function accessPremium(User $user, Subscription $subscription)
{
// TODO
}
public function accessBasic(User $user, Subscription $subscription)
{
// TODO
}
}
```
- از این پالیسی برای مدیریت دسترسی بر اساس **اشتراکهای کاربری** *(Subscriptions)* استفاده خواهد شد.
</details>
## **پیادهسازی** *Observer* `SubscriptionObserver`
در این پروژه شما باید یک *Observer* را برای مدل `Subscription` به صورتی پیادهسازی کنید که هنگام **بروزرسانی** *(Update)* شناسه اشتراک کاربری (`plan_id`) برای یک کاربر، یک `SubscriptionLog` **جدید** ایجاد کند که مقدار `user_id` آن برابر با **شناسه کاربری است که اشتراک کاربری آن تغییر کرده** است، مقدار `old_subscription_level` آن برابر با مقدار **عنوان** *(Slug)* **اشتراک کاربری قبلی کاربر** و مقدار `new_subscription_level` آن برابر با مقدار **عنوان** *(Slug)* **اشتراک جدید کاربر** میباشد.
## پیادهسازی **وظایف** *(Jobs)* و **اسکجول** *(Schedule)* **کردن**
در این پروژه شما باید **دو جاب** `ExpireSubscriptionsJob` **و** `DowngradePendingInvoicesJob` را برای انجام بخشی از پردازشهای مربوط به پنل کاربری به صورت زیر پیادهسازی کنید:
<details class="purple">
<summary>
**ساختار جاب** `DowngradePendingInvoicesJob`
</summary>
**جاب** `DowngradePendingInvoicesJob` را به صورت زیر در مسیر `app/Jobs/DowngradePendingInvoicesJob.php` پیادهسازی کنید تا اشتراک کاربری **تمام کاربرانی** را حداقل یک **صورتحساب** *(Invoice)* با **وضعیت** *(Status)* **باز** (`pending`) دارند که **بیشتر مساوی یک هفته** از صادر شدنشان گذشته است، به **اشتراک کاربری** `basic` تغییر دهد **که زمان انقضایی هم نداشته باشد.**
```php app/Jobs/DowngradePendingInvoicesJob.php laravel
<?php
namespace App\Jobs;
use App\Models\User;
use App\Models\Plan;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class DowngradePendingInvoicesJob implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
// TODO
}
}
```
- از این پالیسی برای مدیریت دسترسی بر اساس **قابلیتها** *(Features)* استفاده خواهد شد.
</details>
<details class="purple">
<summary>
**ساختار جاب** `ExpireSubscriptionsJob`
</summary>
**جاب** `ExpireSubscriptionsJob` را به صورت زیر در مسیر `app/Jobs/ExpireSubscriptionsJob.php` پیادهسازی کنید تا اشتراک کاربری **تمام کاربرانی** را که **تاریخ انقضای اشتراک کاربریشان** (`expires_at`) به پایان رسیده است *(تاریخ انقضای اشتراک کاربریشان کوچکتر مساوی زمان حال است)،* به **اشتراک کاربری** `basic` تغییر دهد **که زمان انقضایی هم نداشته باشد.**
```php app/Jobs/ExpireSubscriptionsJob.php laravel
<?php
namespace App\Jobs;
use App\Models\Subscription;
use App\Models\Plan;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ExpireSubscriptionsJob implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
public function handle()
{
// TODO
}
}
```
- از این پالیسی برای مدیریت دسترسی بر اساس **اشتراکهای کاربری** *(Subscriptions)* استفاده خواهد شد.
</details>
در نهایت پس از تعریف **وظایف** *(Jobs)* های مشخص شده، شما باید این دو **وظیفه** *(Job)* را طوری در لاراول **اسکجول** *(Schedule)* کنید تا **به صورت خودکار** و **هر دقیقه یک بار** پردازش شوند.
## **پیادهسازی میدلور** *(Middleware)*
در نهایت شما قرار است تا **میدلور** *(Middleware)* `CheckFeatureAccess` را برای پردازش دسترسی در **درخواست** *(Request)* کاربر به صورت زیر پیادهسازی کنید:
```php app/Http/Middleware/CheckFeatureAccess.php laravel
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckFeatureAccess
{
public function handle(Request $request, Closure $next): Response
{
// TODO
}
}
```
این **میدلور** با استفاده از **پالیسیهای تعریف شده** در مراحل قبل بررسی میکند که آیا **کاربر فعلی** میتواند به **مسیر مشخص شده** که از این **میدلور** استفاده میکند و مشخص کننده مسیر **یکی از قابلیتهای موجود در برنامه** است دسترسی دارد یا نه. در صورتی که کاربر در تلاش بود تا به مسیر مربوط به قابلیتی دسترسی پیدا کند که **در برنامه به کل وجود ندارد** باید **خطای** `404` به همراه پیام **"این صفحه یافت نشد."** و در صورتی که در تلاش بود تا به مسیر مربوط به قابلیتی دسترسی پیدا کند که اشتراک کاربریاش **اجازه دسترسی به آن را ندارد،** باید **خطای** `403` به همراه پیام **"شما دسترسی به این بخش را ندارید."** نمایش داده شود. **در غیر این صورت ادامه پردازش درخواست کاربر انجام میشود.**
# آنچه باید آپلود کنید
+ **توجه**: پس از پیادهسازی موارد خواسته شده، کل فایلهای پروژه بهجز پوشهی `vendor` را زیپ کرده و ارسال کنید.
+ **توجه**: شما مجاز به افزودن فایل جدیدی در این ساختار **نیستید** و تنها باید تغییرات را در فایلهای موجود اعمال کنید.
+ **توجه**: که نام فایل _Zip_ اهمیتی **ندارد**.
ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.