سلام دوست عزیز😃👋
به «مسابقه استخدامی ایرانسرور» خوش آمدی!
هرگونه ارتباط با سایر شرکتکنندگان و یا استفاده از ابزارهای تولید کد، مثل ChatGPT
و... در مسابقات کوئرا ممنوع است و بعد از شناسایی از لیست شرکتکنندگان مسابقه حذف میشوید.
لینکهای مفید برای شرکت در مسابقه:
سوالات و مشکلات خودتان را میتوانید از طریق قسمت «سوال بپرسید» با ما در میان بگذارید.
سوالات «چالشهای نام ایرانسرور»، «میزگرد بزرگ» و «معین سخنور» مربوط به تکنولوژی PHP
و سوالات «میعین فیراری» و «ایرانگیت» مربوط به تکنولوژیLaravel
است.
موفق باشید و بهتون خوش بگذره 😉✌
لیست سوالات را میتوانید از نوار سمت راست این صفحه مشاهده کنید.
پس از تلاشهای فراوان معین بالاخره به سرزمین رویاهایش ایرانسرور (IranServer) رسید! از آنجایی که ایرانسرور قصد دارد به زودی سرویسهای ابری جدیدی را به کاربرانش ارائه دهد، معین قرار است همراه با سایر ایرانسروریون به توسعه پنل کاربری جدیدی با عنوان "ایرانگیت" مشغول شود که بر پایه لاراول ۱۲ است که به تازگی معرفی شده است!
از آنجایی که ایرانگیت به زودی قرار است به کاربران معرفی شود، در این سوال شما نیز قرار است تا به کمک معین و ایرانسروریون بروید تا با استفاده از لاراول ۱۲ و استارتر کیت Jetstream بخشهایی از پنل کاربری ایرانگیت را پیادهسازی کنید.
پروژهی اولیه را از این لینک دانلود کنید.
برای اجرای پروژه، باید php
، composer
و npm
را از قبل نصب کرده باشید.
composer install
را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید.npm install
را در پوشهی اصلی پروژه برای نصب نیازمندیها اجرا کنید. (توجه کنید که این پروژه از Tailwindcss استفاده میکند)npm run dev
را در مسیر پوشه اصلی پروژه اجرا کنید. در صورتی که این دستور را اجرا نکنید، نمیتوانید ویوهای ساخته شده با Tailwindcss را مشاهده کنید.php artisan migrate
استفاده کنید.php artisan test
استفاده کنید.شما در این سوال قرار است تا در پیادهسازی یک سیستم پنل کاربری بر پایه Jetstream که امکانات شخصیسازی شدهای دارد به معین و ایرانسروریون کمک کنید. توجه داشته باشید که برخی مواردی که در این سوال از شما خواسته شده تا پیادهسازی کنید ممکن است تا در این سوال به صورت عملیاتی مورد استفاده قرار نگیرند، اما در داوری سوال مورد ارزیابی قرار خواهند گرفت. توضیح مواردی که باید پیادهسازی شوند به شکل زیر میباشد:
در این بخش به معرفی پروژه ایرانگیت (IranGate) خواهیم پرداخت. بخشهایی از ایرانگیت در این پروژه اولیه این سوال از قبل پیادهسازی شدهاند و نیازی به پیادهسازی دوباره ندارند. پایه ایرانگیت همانطور که پیشتر گفته شد بر اساس استارتر کیت Jetstream میباشد و امکانات پیشفرضی که این استارتر کیت در اختیار توسعهدهندگان قرار میدهد را شامل میشود. موارد پیادهسازی شده در پروژه به شکل زیر هستند:
این پروژه بر اساس امکانات پیشفرض پیادهسازی شده در Jetstream از ویوها، کامپوننتها، روتها و ... این استارتر کیت ارثبری میکند. صفحه پیشفرض پروژه، صفحه پیشفرض لاراول ۱۲ میباشد که به شما برای شروع حل این سوال خوشآمد میگوید:
بخشی از روتهای (Routes) پیادهسازی شده در پروژه نیز به مانند روتهای پیشفرض Jetstream به شکل زیر تعریف شدهاند:
شما میتوانید همانند تصاویر زیر از امکانات پیشفرض پیادهسازی شده در پروژه برای ورود، عضویت، فراموشی رمز عبور و پنل کاربری پیشفرض استفاده کنید:
FeatureController
این کنترلر برای مدیریت دسترسی به ویژگیهای مختلف استفاده میشود. میدلور CheckFeatureAccess
که قرار است توسط شما پیادهسازی شود، بررسی میکند که کاربر دسترسی به یک ویژگی خاص با عنوان featureSlug
را دارد یا نه و پیامی مبنی بر مجوز دسترسی به آن ویژگی برمیگرداند. کنترلر و روت (Route) مربوط به آن به شکل زیر از قبل پیادهسازی شدهاند:
InvoiceController
و ویوی invoices.index
این کنترلر برای نمایش صورتحسابهای کاربران است. متد index
تمام فاکتورها را از طریق ارتباط با مدل Invoice
و برای کاربر وارد شده (auth()->user()
) بدست میآورد و در ویوی invoices.index
به نمایش میگذارد. کنترلر مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
PlanController
و ویوی profile.plans
این کنترلر برای مدیریت اشتراکهای کاربری و تغییرات آنها استفاده میشود. متد index
تمام اشتراکهای کاربری موجود را نمایش میدهد و متد switch
به کاربر این امکان را میدهد که اشتراک کاربری خود را تغییر دهد. اگر اشتراک جدید هزینه داشته باشد، یک صورتحساب برای پرداخت ایجاد میشود که شما باید در بخشی از پیادهسازیهای خود آنها را مدیریت کنید. کنترلر مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
با تغییر اشتراک کاربری، پیام موفقیت در تغییر اشتراک کاربری به نمایش در میآید و شما میتوانید در منوی کاربری بالای صفحه امکانات مختلفی که برای آن اشتراک کاربری تعریف شده است را ببینید و به آنها دسترسی پیدا کنید.
SubscriptionLogController
و ویوی subscription-logs.index
این کنترلر برای نمایش سوابق اشتراکهای کاربری است. متد index
تمام سوابق اشتراکی مربوط به کاربر فعلی را از طریق ارتباط با مدل SubscriptionLog
بدست میآورد و در ویوی subscription-logs.index
به نمایش میگذارد. کنترلر مربوط به آن به شکل زیر از قبل پیادهسازی شده است:
در این بخش، مایگریشنهای مربوط به ایجاد جداول users
، plans
، subscriptions
، invoices
، logs
، coupons
، features
و سایر جداول مرتبط باید پیادهسازی شوند. این جداول برای مدیریت کاربران، اشتراکها، پرداختها، ویژگیهای پلنها و گزارشهای سیستم طراحی شدهاند. ساختار مربوط به هر کدام از جداول در بخش زیر مشخص است:
users
و پیادهسازی مایگریشن create_users_table
users
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
name |
string |
نام کاربر |
email |
string |
ایمیل (منحصربهفرد) |
password |
string |
رمز عبور |
remember_token |
string |
توکن احراز هویت |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
plans
و پیادهسازی مایگریشن create_plans_table
plans
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
name |
string |
نام اشتراک کاربری |
slug |
string |
عنوان یکتای اشتراک کاربری (منحصربهفرد) |
price |
decimal |
قیمت اشتراک کاربری |
duration |
integer |
مدت زمان اشتراک کاربری (به روز) |
is_active |
boolean |
وضعیت فعال بودن اشتراک کاربری |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
subscriptions
و پیادهسازی مایگریشن create_subscriptions_table
subscriptions
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
user_id |
bigint |
کلید خارجی به users |
plan_id |
bigint |
کلید خارجی به plans |
expires_at |
timestamp |
تاریخ انقضای اشتراک کاربری |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
invoices
و پیادهسازی مایگریشن create_invoices_table
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 |
زمان آخرین بهروزرسانی |
logs
و پیادهسازی مایگریشن create_logs_table
logs
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
user_id |
bigint |
کلید خارجی به users |
action |
string |
نوع فعالیت انجامشده |
details |
text |
جزئیات فعالیت |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
coupons
و پیادهسازی مایگریشن create_coupons_table
coupons
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
code |
string |
کد تخفیف (منحصربهفرد) |
discount |
decimal |
مقدار تخفیف |
max_uses |
integer |
حداکثر دفعات استفاده |
expires_at |
timestamp |
تاریخ انقضای کد |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
features
و پیادهسازی مایگریشن create_features_table
features
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
name |
string |
نام ویژگی |
slug |
string |
شناسه یکتای ویژگی |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
plan_feature
و پیادهسازی مایگریشن create_plan_feature_table
plan_feature
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
plan_id |
bigint |
کلید خارجی به plans |
feature_id |
bigint |
کلید خارجی به features |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
referrals
و پیادهسازی مایگریشن create_referrals_table
referrals
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
referrer_id |
bigint |
کاربری که معرف شخص دیگر است |
referred_id |
bigint |
کاربری که معرفی شده است |
code |
string |
کد ارجاع (منحصربهفرد) |
reward |
decimal |
پاداش دادهشده به معرف |
is_used |
boolean |
وضعیت استفاده از کد (true/false ) |
created_at |
timestamp |
زمان ایجاد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی |
subscription_logs
و پیادهسازی مایگریشن create_subscription_logs_table
subscription_logs
:🔗نام ستون | نوع داده | توضیحات |
---|---|---|
id |
bigint |
کلید اصلی |
user_id |
bigint |
کلید خارجی به جدول کاربران |
old_subscription_level |
string |
سطح قبلی اشتراک کاربر (nullable) |
new_subscription_level |
string |
سطح جدید اشتراک کاربر (nullable) |
created_at |
timestamp |
زمان ایجاد رکورد |
updated_at |
timestamp |
زمان آخرین بهروزرسانی رکورد |
لیست برخی از مدلهای پیادهسازی شده در سیستم به شرح زیر است که شما باید آنها را همراه با روابط بینشان در این بخش پیادهسازی کنید:
User
: مدیریت کاربران سیستم، شامل اطلاعات کاربری و روابط مرتبط.Plan
: نمایشدهندهی اشتراکهای کاربری، شامل ویژگیها و مشخصات هر پلن.Subscription
: ثبت اطلاعات اشتراک کاربران در اشتراکهای مختلف.Invoice
: صورتحسابهای مربوط به خرید اشتراک و پرداختهای کاربران.Feature
: ویژگیهایی که برای هر اشتراک کاربری قابل ارائه هستند.Coupon
: کدهای تخفیف که کاربران میتوانند برای کاهش هزینه خرید اشتراک کاربری استفاده کنند.Referral
: ثبت سیستم ارجاع کاربران و پاداشهای مربوط به معرفی کاربران جدید.Log
: ثبت اقدامات کاربران در سیستم جهت نظارت و بررسی تاریخچه فعالیتها.SubscriptionLog
: مدیریت تغییرات اشتراک کاربران و تاریخچه ارتقا یا کاهش پلن.در این بخش، فکتوریهای تعریف شده برای تولید دادههای تصادفی جهت تست برنامه معرفی شدهاند. این فکتوریها به منظور تولید دادههای آزمایشی برای جداول مرتبط در پایگاه داده استفاده میشوند:
CouponFactory
CouponFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
code |
کد تخفیف (منحصربهفرد) | یک UUID تصادفی |
discount |
مقدار تخفیف | عدد اعشاری بین ۵ تا ۵۰ |
max_uses |
حداکثر دفعات استفاده | عدد صحیح بین ۱ تا ۱۰۰ |
expires_at |
تاریخ انقضا | تاریخی بین حالا تا ۱ سال آینده |
FeatureFactory
FeatureFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
name |
نام ویژگی | یک کلمه تصادفی |
slug |
شناسه یکتا (slug) | مقدار یکتای تصادفی |
InvoiceFactory
InvoiceFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
user_id |
کلید خارجی به جدول users |
مقدار یک user_id معتبر |
plan_id |
کلید خارجی به جدول plans |
مقدار یک plan_id معتبر |
payment_id |
شناسه پرداخت | یک UUID تصادفی |
amount |
مبلغ صورتحساب | عدد اعشاری بین ۱۰ تا ۱۰۰ |
status |
وضعیت پرداخت | paid یا failed |
LogFactory
LogFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
action |
نوع فعالیت | یک کلمه تصادفی |
details |
جزئیات فعالیت | یک آرایه JSON |
user_id |
کلید خارجی به جدول users |
مقدار یک user_id معتبر |
PlanFactory
PlanFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
name |
نام اشتراک | یک کلمه تصادفی |
slug |
شناسه یکتا (slug) | مقدار یکتای تصادفی |
price |
قیمت | عدد اعشاری بین ۱۰ تا ۱۰۰ |
duration |
مدت زمان (روز) | عدد صحیح بین ۳۰ تا ۳۶۵ |
is_active |
وضعیت فعال/غیرفعال بودن اشتراک | مقدار true یا false |
ReferralFactory
ReferralFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
referrer_id |
کلید خارجی به جدول users |
مقدار یک user_id معتبر |
referred_id |
کلید خارجی به جدول users |
مقدار یک user_id معتبر |
code |
کد ارجاع (منحصربهفرد) | یک UUID تصادفی |
reward |
مقدار جایزه ارجاع | عدد اعشاری بین ۰ تا ۱۰۰ |
is_used |
وضعیت استفاده از کد | مقدار true یا false |
UserFactory
UserFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
name |
نام کاربر | یک نام تصادفی |
email |
ایمیل کاربر (یونیک) | ایمیل تصادفی معتبر |
email_verified_at |
زمان تأیید ایمیل | now() یا null |
password |
رمز عبور | password هششده |
remember_token |
توکن یادآوری | رشته ۱۰ کاراکتری تصادفی |
SubscriptionFactory
SubscriptionFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
user_id |
شناسه کاربر مرتبط | مقدار تصادفی از UserFactory |
plan_id |
شناسه پلن مرتبط | مقدار تصادفی از PlanFactory |
expires_at |
تاریخ انقضای اشتراک | تاریخ تصادفی بین now و +1 year |
created_at |
زمان ایجاد رکورد | now() |
updated_at |
زمان آخرین بهروزرسانی رکورد | now() |
SubscriptionLogFactory
SubscriptionLogFactory
🔗نام ستون | توضیحات | مقادیر ممکن |
---|---|---|
user_id |
شناسه کاربر مرتبط | مقدار تصادفی از UserFactory |
old_subscription_level |
سطح قبلی اشتراک کاربر | یک کلمه تصادفی |
new_subscription_level |
سطح جدید اشتراک کاربر | یک کلمه تصادفی |
created_at |
زمان ایجاد رکورد | now() |
updated_at |
زمان آخرین بهروزرسانی رکورد | now() |
درون پروژه اولیه تعدادی سیدر (Seeder) وجود دارد که باید به شکل زیر پیادهسازی شوند، در نهایت قرار است تا این سیدرها با ترتیب درستی در سیدر DatabaseSeeder
مورد استفاده قرار بگیرند. سیدرها باید به شکل زیر ایجاد شوند:
Coupon
ساخته شود.Feature
ساخته شود. همچنین یک اشتراک کاربری با عنوان (Slug) vip
که توسط سیدر مربوطه ساخته شده باید به تمام قابلیتهای ساخته شده توسط این سیدر دسترسی داشته باشد.Invoice
ساخته شود.Log
ساخته شود.Plan
ساخته شود. همچنین باید سه اشتراک کاربری دیگر توسط این سیدر به صورت زیر ساخته شود:Basic
، عنوان (Slug) basic
، قیمت (Price) 0
و مدتزمان (Duration) 30
روزPremium
، عنوان (Slug) premium
، قیمت (Price) 100
و مدتزمان (Duration) 30
روزVip
، عنوان (Slug) vip
، قیمت (Price) 200
و مدتزمان (Duration) 30
روزReferral
ساخته شود.SubscriptionLog
ساخته شود.Subscription
ساخته شود.User
ساخته شود. تمامی این ۵۰ کاربر باید در اشتراک کاربری basic
که قبلا توسط سیدر مربوطه ساخته شده است، مشترک شوند.در این سوال شما باید دو پالیسی FeaturePolicy
و SubscriptionPolicy
را برای مدیریت دسترسی به ویژگیهای خاص و اشتراکهای کاربری خاص به صورت زیر پیادهسازی کنید:
FeaturePolicy
پالیسی FeaturePolicy
به صورت زیر در مسیر app/Policies/FeaturePolicy.php
پیادهسازی شده است که با استفاده از تابعی با نام accessFeature
و دریافت دو مقدار $user
و $feature
به صورت پویا مشخص میکند که آیا کاربر $user
در اشتراک کاربری فعلیاش میتواند میتواند به قابلیت $feature
دسترسی داشته باشد یا نه.
SubscriptionPolicy
پالیسی SubscriptionPolicy
به صورت زیر در مسیر app/Policies/SubscriptionPolicy.php
پیادهسازی شده است که برای مدیریت دسترسیهای کاربران با اشتراکهای کاربری پیشفرض basic
، premium
و vip
به بخشهای مختلف پنلکاربری مورد استفاده قرار میگیرد. این پالیسی با استفاده از هر کدام از سه تابع accessVIP
، accessPremium
و accessBasic
بررسی میکند که آیا اشتراک کاربری $subscription
که متعلق به کاربر $user
میباشد به ترتیب از نوع vip
، premium
و basic
میباشد یا نه.
SubscriptionObserver
🔗در این پروژه شما باید یک Observer را برای مدل Subscription
به صورتی پیادهسازی کنید که هنگام بروزرسانی (Update) شناسه اشتراک کاربری (plan_id
) برای یک کاربر، یک SubscriptionLog
جدید ایجاد کند که مقدار user_id
آن برابر با شناسه کاربری است که اشتراک کاربری آن تغییر کرده است، مقدار old_subscription_level
آن برابر با مقدار عنوان (Slug) اشتراک کاربری قبلی کاربر و مقدار new_subscription_level
آن برابر با مقدار عنوان (Slug) اشتراک جدید کاربر میباشد.
در این پروژه شما باید دو جاب ExpireSubscriptionsJob
و DowngradePendingInvoicesJob
را برای انجام بخشی از پردازشهای مربوط به پنل کاربری به صورت زیر پیادهسازی کنید:
DowngradePendingInvoicesJob
جاب DowngradePendingInvoicesJob
را به صورت زیر در مسیر app/Jobs/DowngradePendingInvoicesJob.php
پیادهسازی کنید تا اشتراک کاربری تمام کاربرانی را حداقل یک صورتحساب (Invoice) با وضعیت (Status) باز (pending
) دارند که بیشتر مساوی یک هفته از صادر شدنشان گذشته است، به اشتراک کاربری basic
تغییر دهد که زمان انقضایی هم نداشته باشد.
ExpireSubscriptionsJob
جاب ExpireSubscriptionsJob
را به صورت زیر در مسیر app/Jobs/ExpireSubscriptionsJob.php
پیادهسازی کنید تا اشتراک کاربری تمام کاربرانی را که تاریخ انقضای اشتراک کاربریشان (expires_at
) به پایان رسیده است (تاریخ انقضای اشتراک کاربریشان کوچکتر مساوی زمان حال است)، به اشتراک کاربری basic
تغییر دهد که زمان انقضایی هم نداشته باشد.
در نهایت پس از تعریف وظایف (Jobs) های مشخص شده، شما باید این دو وظیفه (Job) را طوری در لاراول اسکجول (Schedule) کنید تا به صورت خودکار و هر دقیقه یک بار پردازش شوند.
در نهایت شما قرار است تا میدلور (Middleware) CheckFeatureAccess
را برای پردازش دسترسی در درخواست (Request) کاربر به صورت زیر پیادهسازی کنید:
این میدلور با استفاده از پالیسیهای تعریف شده در مراحل قبل بررسی میکند که آیا کاربر فعلی میتواند به مسیر مشخص شده که از این میدلور استفاده میکند و مشخص کننده مسیر یکی از قابلیتهای موجود در برنامه است دسترسی دارد یا نه. در صورتی که کاربر در تلاش بود تا به مسیر مربوط به قابلیتی دسترسی پیدا کند که در برنامه به کل وجود ندارد باید خطای 404
به همراه پیام "این صفحه یافت نشد." و در صورتی که در تلاش بود تا به مسیر مربوط به قابلیتی دسترسی پیدا کند که اشتراک کاربریاش اجازه دسترسی به آن را ندارد، باید خطای 403
به همراه پیام "شما دسترسی به این بخش را ندارید." نمایش داده شود. در غیر این صورت ادامه پردازش درخواست کاربر انجام میشود.
vendor
را زیپ کرده و ارسال کنید.