سلام ورودی


سلام!

به مسابقه بَله کمپ دوآپس خوش آمدید.

به عنوان تمرین اول از شما می‌خواهیم تا اسکریپتی بنویسید که به تمام ورودی‌های دریافتی سلام کند! یعنی پس از اجرا صرفا عبارت زیر را چاپ کند.

extensionFromNameTerminal
$ salam.sh ahali quera
Salam ahali quera
Terminal

به علامت‌های نگارشی و بزرگی و کوچکی توجه کنید. خروجی اسکریپت شما باید دقیقا برابر مقدار خواسته شده باشد.

مثال
extensionFromNameTerminal
$ ./salam.sh a b c d e f g
Salam a b c d e f g
$ ./salam.sh ali
Salam ali
Terminal

پروژه اولیه🔗

برای دریافت پروژه اولیه این لینک را دانلود کنید. درون لینک ساختار فایلی زیر را مشاهده می‌کنید:

Salam
└── salam.sh
Plain text

راه‌حل خود را درون فایل salam.sh پیاده‌سازی کنید.

توجه کنید🔗

  • فراموش نکنید که اسکریپتی کامل را در پاسخ‌تان تحویل دهید.
  • فراموش نکنید که اسکریپت خود را درون فایلی با نام salam.sh وارد نمایید.
  • برای ارسال پاسخ خود کافی‌ست فایل salam.sh را آپلود کنید.

نقشه گنج بَله


سلیب فارغ از دنیا و آخرت در حال خوش‌گذرانی بود که ناگهان امین پیش او آمد و گفت که در پیام‌رسان بله خبر از گنجی به او رسیده. سلیب هم که برای مالی بادآورده جان می‌دهد، قبول کرد تا به دنبال گنج بگردد. برای این کار نیاز بود تا خود را با کشتی به جزیره‌ای برساند که در آن‌جا نقشه‌ی گنج دفن شده بود.

سلیب مطمئن بود که سایتی برای خرید بلیط کشتی به ارزان‌ترین حالت ممکن را قبلاً دیده و آن را بوک‌مارک کرده، اما نمی‌دانست که سایت را در کجای کدام مرورگر خود ذخیره کرده‌است. او که به انجام ندادن کارهای خود عادت کرده، از شما خواسته تا اسکریپتی به زبان bash برای او بنویسید تا تمامی بوک‌مارک‌های تمامی مرورگر‌های سلیب را به صورت markdown درون فایلی ذخیره کند. در ادامه به بیان جزئیات بیشتر مسئله می‌پردازیم.

جزئیات پروژه🔗

برای دانلود پروژه‌ی اولیه روی این لینک کلیک کنید.

در لپ‌تاپ شخصی سلیب مرورگر‌های chrome ،opera و operagx موجود هستند.

تمامی مرورگر‌ها بوک‌مارک‌های خود را در ساختاری مختص به خود درون فایل json ذخیره می‌کنند. سلیب جهت آسان‌تر کردن کار شما، آدرس فایل‌های مربوط به هر مرورگر را در فایلی با نام addresses.txt در کنار اسکریپت شما ذخیره کرده‌است. نمونه محتوای این فایل به شکل زیر است:

extensionFromNameaddresses.txt
operagx:./samples/operagx.json
opera:./samples/opera.json
chrome:./samples/chrome.json
Plain text

توجه داشته باشید ممکن است در آدرس کاراکتر‌های اسپیس ()، نقطه (.) و تیلدا (~) موجود باشد، همچنین ترتیب مرورگرها درون فایل همواره ثابت نیست ولی همواره تمامی مرورگر‌ها درون فایل موجود هستند.

اسکریپت شما باید آدرس و تایتل تمامی سایت‌های بوک‌مارک موجود در هر یک از این مرورگرها را بیابد و سپس موارد تکراری موجود را حذف کند و در نهایت مطابق شکل زیر، موارد را درون فایلی با نام unique_bookmarks.md ذخیره کند.

extensionFromNameunique_bookmarks.md
# Unique bookmarks:

| Name | URL |
|------|-----|
| {NAME} | {URL} |
| {NAME} | {URL} |
| {NAME} | {URL} |
...
Markdown

توجه داشته باشید ممکن است تایتل یا نام آدرس‌ها با " شروع یا تمام شوند. نیاز است تا شما این کاراکتر‌ها را از تایتل یا نام آدرس حذف کنید؛ اما اگر " در وسط کلمه وجود داشت شما مجاز به حذف آن نیستید. مثلاً اسکریپت شما باید تایتل """"""""I love "IRAN" so much" را به I love "IRAN" so much تبدیل کند. همچنین دقت کنید که ممکن است در بیین تایتل آن‌ها | وجود داشته باشد؛ که این مورد هم باید حذف شود. برای مثال نام جامعه برنامه‌نویسان ایران | Quera در فایل opera.json باید به جامعه برنامه‌نویسان ایران Quera تبدیل شود.

به علامت‌های نگارشی و بزرگی و کوچکی توجه کنید. خروجی اسکریپت شما باید دقیقاً برابر مقدار خواسته‌شده باشد.

راه‌حل خود را درون فایل script.sh موجود در پروژه اولیه، پیاده‌سازی کنید.

برای حل مسئله تنها مجاز به استفاده از دستور jq برای پارس کردن فایل‌های جیسون می‌باشید. داکیومنتیشن این دستور در این لینک قابل مشاهده است.

نمونه اجرا

برای مثال در پروژه‌ی اولیه، نمونه‌ای از فایل ذخیره‌شده‌ی بوک‌مارک در تمامی مرورگر‌های مسئله موجود است. با اجرای اسکریپت شما، باید خروجی خواسته‌شده مشاهده شود:

extensionFromNameTerminal
$ ./script.sh
Reading bookmark file addresses from addresses.txt
Terminal

همچنین محتوای فایل unique_bookmarks.md هم باید به‌صورت زیر باشد:

extensionFromNameunique_bookmarks.md
# Unique bookmarks:

| Name | URL |
|------|-----|
| test 1 | https://test1.com/ |
| Intro  Putting the "You" in CPU | https://cpu.land/ |
| Amazon | https://www.amazon.com/ |
| Hero Wars | https://www.hero-wars.com/ |
| Walmart | https://walmart.com/ |
| eBay | http://www.ebay.com/ |
| test 2 | https://test2.com/ |
| test 3 | https://test3.com/ |
| test 4 | https://test4.com/ |
| test 5 | https://test5.com/ |
| test 6 | https://test6.com/ |
| test 7 | https://test7.com/ |
| تاکسی اینترنتی تپسی - اپلیکیشن درخواست خودرو و پیک  TAPSI | https://tapsi.ir/ |
| ترب  بهترین قیمت بازار | https://torob.com/ |
| جامعه برنامه‌نویسان ایران  Quera | https://quera.org/ |
| دیوار: بزرگترین سایت نیازمندی های رایگان در ایران | https://divar.ir/ |
| سوپر اپلیکیشن اسنپ  سامانه هوشمند حمل‌ونقل  تاکسی اینترنتی | https://snapp.ir/ |
| فروشگاه اینترنتی دیجی‌کالا | https://www.digikala.com/ |
Markdown

نیازی نیست ترتیب جدول خروجی مشابه ترتیب جدول در صورت سؤال باشد و به هر ترتیب دلخواهی مجاز به تشکیل جدول هستید. تعداد سطرهای خروجی اسکریپت شما باید دقیقاً برابر با تعداد سطرهای جدول بالا باشد.

نکات مهم🔗

  • فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
  • فراموش نکنید که اسکریپت خود را درون فایلی با نام script.sh وارد نمایید.
  • برای ارسال پاسخ خود کافی‌ست فایل script.sh را آپلود کنید.
  • خروجی و رفتار دستور jq ممکن است در توزیع‌های مختلف لینوکس رفتار متفاوتی از خود نشان دهد؛ در نتیجه ترجیحا برای تست اسکریپت خود از سیستم‌عامل اوبونتو استفاده کنید.
  • سطح و level ایندنت‌ها و همچنین فیلدهایی که در مرورگرهای مختلف، url یا name را در خود ذخیره کرده‌اند متفاوت است و شما باید به صورت بازگشتی چک کنید تا به پراپرتی url یا name برسید و آن‌ها را استخراج کنید.

سلیب و باجِ بَله


سلیب پس از پیدا کردن سایت موردنظر در میان انبوهی از وب‌سایت‌های خروجی مسئله‌ی قبل، به جزیره رسید و نقشه‌ی گنج را پیدا کرد. پس از باز کردن نقشه، متوجه شد که نقشه به زبان عجیب و غریبی (احتمالاً عبری) نوشته شده و قادر به ترجمه‌ی آن نیست. او که می‌دانست محال است بتواند نقشه را خودش ترجمه کند، به دنبال فردی با دانش عبری گشت. سلیب پس از پرس‌و‌جوهای فراوان با علی روبه‌رو شد که قبول کرد نقشه را برای او ترجمه کند؛ اما در ازای این کار، از او خواست تا تسک iteration فعلی او را برایش انجام دهد. سلیب هم‌اکنون از شما درخواست انجام تسک پاک‌سازی و بهینه کردن ساختار ردیس شرکت علی را دارد.

جزئیات پروژه🔗

برای دانلود پروژه‌ی اولیه روی این لینک کلیک کنید.

برای انجام تسک شرکت علی نیاز به نوشتن یک اسکریپت به زبان bash دارید و اسکریپت شما باید ۴ سطح از پاک‌سازی که در ادامه‌ی مسئله با جزئیات بیشتری توضیحشان می‌دهیم را روی دیتای ردیس فعلی‌شان انجام دهد.

برای حل این تمرین نیاز به دانش کار با دستور redis-cli را دارید که داکیومنتیشن آن را در این لینک می‌توانید مطالعه کنید، همچنین جهت آشنایی با تمامی داده ساختار‌های موجود در ردیس هم از این لینک کمک بگیرید.

پاک‌سازی نوع اول

در این سطح، نیاز است تمامی کلید‌هایی از ردیس که شامل حداقل یک کاراکتر کوچک زبان انگلیسی است را به فرم بزرگ تغییر دهد. برای مثال اگر ردیس ما شامل کلید‌های زیر است:

extensionFromNameterminal
redis:6379> KEYS *
...
redis-key1
Redis-Key2
Redis-KEY3
...
Bash

پس از اجرای اسکریپت شما باید به شکل زیر تغییر کنند:

extensionFromNameterminal
redis:6379> KEYS *
...
REDIS-KEY1
REDIS-KEY2
REDIS-KEY3
...
Bash

اسکریپت شما پس از تغییر هر کلید باید عبارتی به فرم زیر به فایل output.txt اضافه کند.

extensionFromNameoutput.txt
Renamed key: OLD_KEY -> NEW_KEY
Plain text

برای مثال، پس از اجرای اسکریپت روی مثال بالا، محتوای فایل output.txt شامل محتوای زیر باشد:

extensionFromNameoutput.txt
...
Renamed key: redis-key1 -> REDIS-KEY1
Renamed key: Redis-Key2 -> REDIS-KEY2
Renamed key: Redis-KEY3 -> REDIS-KEY3
...
Plain text

توجه داشته باشید تحت این عملیات پاک‌سازی مقدار هیچ کلیدی از ردیس نباید تغییر کند، یعنی پاک‌سازی شما نباید به‌هیچ‌وجه باعث از دست رفتن دیتای موجود شود.

نکته پیرامون حفظ دیتا: ممکن است هنگامی‌که شما کلید را به‌صورت uppercase می‌نویسید، کلید جدید در ردیس وجود داشته باشد. در این صورت، تا زمانی که به کلیدی که در ردیس‌مان وجود نداشته باشد نرسیده‌ایم، عبارت DUPLICATE را به انتهای کلید جدید اضافه می‌کنیم. برای مثال اگر ردیس ما شامل کلید‌های زیر باشد:

extensionFromNameterminal
redis:6379> KEYS *
...
redis-key1
REDIS-KEY1DUPLICATE
...
Bash

اسکریپت شما باید کلید redis-key1 را به REDIS-KEY1DUPLICATEDUPLICATE تغییر دهد. یعنی پس از اجرای اسکریپت شما وضعیت کلید‌های ردیس مانند زیر خواهد بود:

extensionFromNameterminal
redis:6379> KEYS *
...
REDIS-KEY1DUPLICATE
REDIS-KEY1DUPLICATEDUPLICATE
...
Bash

و همچنین محتوای فایل output.txt شامل محتوای زیر باشد:

extensionFromNameoutput.txt
...
Renamed key: redis-key1 -> REDIS-KEY1DUPLICATEDUPLICATE
...
Plain text
پاک‌سازی نوع دوم

در این سطح، نیاز است تا تمامی مقادیری از ردیس که به‌صورت رشته هستند و با کاراکتر [ شروع و با کاراکتر ] تمام می‌شوند را به‌صورت لیست در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل output.txt اضافه کند.

extensionFromNameoutput.txt
Parsed list for key: KEY
Plain text

برای مثال، اگر ردیس ما شامل مقداری مانند زیر است:

extensionFromNameterminal
redis:6379> GET TEST_KEY
"[a, b, c]"
Bash

پس از اجرای اسکریپت شما باید مقدار کلید مذکور به‌صورت داده‌ساختار لیست پیاده‌سازی‌شده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:

extensionFromNameterminal
redis:6379> LRANGE TEST_KEY 0 -1
1) "a"
2) "b"
3) "c"
Bash

برای مثال پس از اجرای اسکریپت روی مثال بالا، محتوای فایل output.txt شامل محتوای زیر باشد:

extensionFromNameoutput.txt
...
Parsed list for key: TEST_KEY
...
Plain text

توجه داشته باشید که اسکریپت شما باید ترتیب لیست را حفظ کند و تغییر در ترتیب مقدار مجاز نیست.

توجه داشته باشید که اگر درون مقدار کاراکتر گیومه (") وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.

پاک‌سازی نوع سوم

در این سطح، نیاز است تا تمامی مقادیری از ردیس که به‌صورت رشته هستند و با کاراکتر ( شروع و با کاراکتر ) تمام می‌شوند را به‌صورت ست در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل output.txt اضافه کند.

extensionFromNameoutput.txt
Parsed set for key: KEY
Plain text

برای مثال اگر ردیس ما شامل مقداری مانند زیر است:

extensionFromNameterminal
redis:6379> GET TEST_KEY
"(a, b, c)"
Bash

پس از اجرای اسکریپت شما باید مقدار کلید مذکور به‌صورت داده‌ساختار ست پیاده‌سازی‌شده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:

extensionFromNameterminal
redis:6379> SMEMBERS TEST_KEY
1) "a"
2) "b"
3) "c"
Bash

برای مثال، پس از اجرای اسکریپت روی مثال بالا، محتوای فایل output.txt شامل محتوای زیر باشد:

extensionFromNameoutput.txt
...
Parsed set for key: TEST_KEY
...
Plain text

توجه داشته باشید که اگر درون مقدار کاراکتر گیومه (") وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.

پاک‌سازی نوع چهارم

در این سطح، نیاز است تمامی مقادیری از ردیس که به‌صورت رشته هستند و با کاراکتر { شروع و با کاراکتر } تمام می‌شوند را به‌صورت هش در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل output.txt اضافه کند.

extensionFromNameoutput.txt
Parsed map for key: KEY
Plain text

برای مثال اگر ردیس ما شامل مقداری مانند زیر است:

extensionFromNameterminal
redis:6379> GET TEST_KEY
"{a:b, c:d, e:f}"
Bash

پس از اجرای اسکریپت شما باید مقدار کلید مذکور به‌صورت داده‌ساختار لیست پیاده‌سازی‌شده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:

extensionFromNameterminal
redis:6379> HGETALL TEST_KEY
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
redis:6379> HGET TEST_KEY a
"b"
Bash

برای مثال پس از اجرای اسکریپت روی مثال بالا، محتوای فایل output.txt شامل محتوای زیر باشد:

extensionFromNameoutput.txt
...
Parsed map for key: TEST_KEY
...
Plain text

توجه داشته باشید که اسکریپت شما باید ترتیب لیست را حفظ کند و تغییر در ترتیب مقدار مجاز نیست.

توجه داشته باشید که اگر درون مقدار کاراکتر گیومه (") وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.

نمونه خروجی

برای اجرای نمونه نیاز به نصب داکر در سیستم خود دارید. نیازی نیست تا شما درون محتوای فایل‌های داکر پروژه هیچ تغییری ایجاد کنید.

درون پروژه اولیه فایل‌هایی جهت تست اسکریپت شما تهیه شده که در ادامه نحوه کار با هرکدام را بررسی می‌کنیم. ابتدا اسکریپت خود را در فایل fixer.sh پیاده‌سازی کنید. سپس با استفاده از دستور زیر کانتینرهای پروژه اولیه را بالا بیاورید:

extensionFromNameTerminal
$ docker-compose up --build -d
Terminal

پس از اتمام اجرای دستور بالا، دستور زیر را اجرا کنید. این دستور محتوای موجود در فایل redis-data.txt را به درون ردیس در حال اجرا می‌ریزد.

extensionFromNameTerminal
$ pip install redis
$ python redis-filler.py
Terminal
نام کلید و مقدار هر کلید در ردیس نمونه

ساختار زیر به صورت "key":"value" است:

test1:1
test2:2
TEST1:TEST1
12345:12345
Test6:Test6
tEsT7:tEsT7
test8:[a,b,c]
test9:(a,b,c)
test10:{a:b,c:d}
Plain text

یعنی مقدار کلید test1 برابر با 1 است.

پس از اتمام اجرای دستور بالا، باید اسکریپت خود را اجرا کنید. برای این کار از دستور زیر استفاده کنید:

extensionFromNameTerminal
$ docker exec -it fixer ./fixer.sh
Terminal

حال باید اسکریپت شما اجرا شده باشد و در فایل output.txt محتوای زیر قابل مشاهده باشد:

extensionFromNameoutput.txt
Renamed key: test10 -> TEST10
Parsed map for key: TEST10
Renamed key: test8 -> TEST8
Parsed list for key: TEST8
Renamed key: test2 -> TEST2
Renamed key: test9 -> TEST9
Parsed set for key: TEST9
Renamed key: Test6 -> TEST6
Renamed key: tEsT7 -> TEST7
Renamed key: test1 -> TEST1DUPLICATE
Plain text

توجه داشته باشید که ترتیب خروجی برای سیستم داوری اهمیتی ندارد و تنها نیاز است تا تمامی موارد به هر ترتیب دلخواهی حتما در خروجی وجود داشته باشند.

پس از اتمام اجرای تست، حتما دستور زیر را اجرا کنید تا تمامی کانتینرها متوقف شوند:

extensionFromNameTerminal
$ docker-compose down
Terminal

نکات تکمیلی🔗

  • فراموش نکنید که در تمامی تمرین‌ها نیاز است تا تمام کاراکتر‌های " از مقدار‌هایی که توسط اسکریپت شما تغییر می‌کنند، حذف شود.
  • اولویت اجرا از پاک‌سازی نوع اول به چهارم است، یعنی ممکن است که ابتدا کلید را به صورت uppercase تغییر بدهیم (پاک‌سازی نوع اول) و سپس بفهمیم که مقدار کلید جدیدی که به صورت uppercase ذخیره کرده‌ایم نیاز به ذخیره‌سازی به‌صورت لیست در ردیس را داشته باشد.
  • باقی کلید‌های موجود در ردیس نباید توسط اسکریپت شما تغییری کنند.
  • برای اتصال به ردیس در اسکریپت خود باید به هاست redis وصل شوید.

نکات مهم🔗

  • فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
  • فراموش نکنید که اسکریپت خود را درون فایلی با نام fixer.sh وارد نمایید.
  • برای ارسال پاسخ خود کافی‌ست فایل fixer.sh را آپلود کنید.

سامانه بلاگ بَله‌آباد


سلیب پس از ترجمه‌ی نقشه متوجه شد که گنج جایی در آبادی «بله‌آباد» است که در پشت کوه‌های جزیره پنهان شده‌است. او سریعاً حرکت کرد و هنگامی‌که به پشت کوه‌های «بله‌آباد» رسید، متوجه شد که مردمان این آبادی، سامانه‌ای شبیه سیستم بلاگ Medium ندارند. برای سلیب این موقعیت خوبی بود تا به جای جست‌وجوی شبانه‌روزی به دنبال گنج، از این طریق ثروتی کسب کند و بی‌خیال گنج بشود؛ پس درخواست پیاده‌سازی این سامانه را با مشخصاتی که در ادامه توضیح می‌دهیم، از شما دارد. :)

جزئیات پروژه🔗

پروژه‌ی اولیه را از این لینک دانلود کنید.

در این سؤال، ما با دو بخش کلی در ارتباط هستیم: بخش کاربران و بخش بلاگ‌ها. در ادامه جزئیات هر بخش را بیان می‌کنیم.

بخش کاربران

برای این بخش نیاز است تعدادی REST API شامل endpointهای زیر پیاده‌سازی شوند:

آدرس عنوان
GET / بررسی up بودن سرویس
POST /auth/signup/ ثبت‌نام
POST /auth/login/ ورود به حساب کاربری
POST /auth/logout/ خروج از حساب کاربری

در این API هر کاربر باید یک توکن داشته باشد. این توکن برای هر کاربر ثابت است.

پیاده‌سازی endpointهای موردنیاز بخش کاربر🔗

در همه‌ی endpointها، پاسخ باید به‌صورت JSON باشد.

اکیداً توصیه می‌گردد برای پیاده‌سازی بخش کاربر از JWT استفاده کنید.

اطلاعات ورودی به‌صورت application/x-www-form-urlencoded به endpointها ارسال می‌شوند.

بررسی up بودن سرویس

پاسخ این endpoint باید به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه: {"message":Welcome to Medium API}
ثبت‌نام

سه پارامتر username و password و email به این endpoint ارسال می‌شوند. درصورتی‌که حداقل یکی از پارامترهای username و password ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

هر دو پارامتر username و password خالی:

  • کد وضعیت: 400

  • بدنه:

    extensionFromNamejson
    {
      "error": {
          "username": ["This field may not be blank."],
          "password": ["This field may not be blank."],
          }
    }
    JSON

    پارامتر username خالی:

  • کد وضعیت: 400

  • بدنه:

    extensionFromNamejson
    {
      "error": {
          "username": ["This field may not be blank."]
          }
    }
    JSON

    پارامتر password خالی:

  • کد وضعیت: 400

  • بدنه:

    extensionFromNamejson
    {
      "error": {
          "password": ["This field may not be blank."]
          }
    }
    JSON

    اگر کاربری با نام کاربری واردشده، از قبل موجود باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400

  • بدنه:

    extensionFromNamejson
    {
      "error": {
          "username": ["A user with that username already exists."],
          }
    }
    JSON

    در غیر این صورت، کاربر باید ساخته شود، یک توکن یکتا برایش تولید شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 201

  • بدنه:

    extensionFromNamejson
    {
      "refresh": "[REFRESH_TOKEN]",
      "access": "[ACCESS_TOKEN]",
    }
    JSON
ورود به حساب کاربری

دو پارامتر username و password باید به این endpoint ارسال شوند. درصورتی‌که حداقل یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {"error": "Invalid credentials"}

اگر نام کاربری یا رمز عبور نادرست باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {"error": "Invalid credentials"}

در غیر این صورت، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "refresh": "[REFRESH_TOKEN]",
      "access": "[ACCESS_TOKEN]",
    }
    JSON
خروج از حساب کاربری

تنها پارامتر refresh باید به این endpoint ارسال شود. درصورتی‌که این پارامتر ارسال نشده باشد یا برابر با رشته‌ی خالی باشد یا حتی مقدار درستی نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه: {}

در غیر این صورت، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 205
  • بدنه: {}
بخش بلاگ

برای این بخش نیاز است تا تعدادی REST API شامل endpointهای زیر باید پیاده‌سازی شوند:

آدرس عنوان
GET /blogs/ دریافت تمامی بلاگ‌های ثبت شده
POST /blogs/create/ ایجاد بلاگ جدید
PUT /blogs/<int:pk>/ آپدیت بلاگ
DELETE /blogs/<int:pk>/ حذف بلاگ
GET /blogs/<int:pk>/detail/ مشاهده بلاگ
POST /blogs/<int:pk>/like/ لایک کردن بلاگ
GET /blogs/analytics/ اوضاع کلی حساب نویسنده

پیاده‌سازی endpointهای موردنیاز بخش بلاگ🔗

در همه‌ی endpointها، پاسخ باید به‌صورت JSON باشد.

اطلاعات ورودی به‌صورت application/x-www-form-urlencoded به endpointها ارسال می‌شوند.

دریافت تمامی بلاگ‌های ثبت‌شده

این endpoint نیازمند authentication نیست.

نیازی به ارسال هیچ پارامتری به این endpoint نیست و در همه‌ی حالات باید جواب برابر با مقدار زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    [
      {
          "id": "[BLOG_ID]",
          "title": "[BLOG_TITLE]",
          "content": "[BLOG_CONTENT]",
          "views": "[BLOG_VIEWS]",
          "likes": "[BLOG_LIKES]",
      },
      ...
    ]
    JSON

بلاگ‌های برگردانده‌شده باید به ترتیب زمان ثبت بلاگ باشند.

ایجاد بلاگ جدید

این endpoint نیازمند authentication است. در ریکوئست ارسالی، مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتر title و content باید به این endpoint ارسال شود. درصورتی‌که یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "title": ["This field may not be blank."],
          "content": ["This field may not be blank."],
          }
    }
    JSON

در غیر این صورت، بلاگ باید ثبت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 201
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
آپدیت بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتر title و content باید به این endpoint ارسال شود. درصورتی‌که یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400

  • بدنه:

    extensionFromNamejson
    {
      "error": {
          "title": ["This field may not be blank."],
          "content": ["This field may not be blank."],
          }
    }
    JSON

    درصورتی‌که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 404

  • بدنه: {}

درصورتی‌که کاربر ارسال‌کننده‌ی ریکوئست برابر با نویسنده‌ی بلاگ نباشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 403
  • بدنه: {}

در غیر این صورت، بلاگ باید آپدیت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
حذف بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتری به این endpoint ارسال نمی‌شود. در صورتی که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در صورتی که کاربر ارسال‌کننده‌ی ریکوئست برابر با نویسنده بلاگ نباشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 403
  • بدنه: {}

در غیر این صورت، بلاگ باید حذف شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 204
  • بدنه: {}
مشاهده‌ی بلاگ

این endpoint نیازمند authentication نیست.

نیازی به ارسال هیچ پارامتری به این endpoint نیست. در همه‌ی حالات، پاسخ باید برابر با مقادیر زیر باشد:

درصورتی‌که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در غیر این‌صورت، بلاگ باید ثبت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
    توجه داشته باشید که درصورتی‌که بلاگ موردنظر وجود داشت و کاربر بدون مشکل می‌توانست آن را مشاهده کند، باید به مقدار viewی بلاگ، یک واحد اضافه کنید و سپس بلاگ را برگردانید. یعنی بازدید فعلی کاربر در میزان بازدید‌های بلاگ برگردانده‌شده باید محاسبه شده باشد.

نکته‌ی ریت لیمیت: در این‌جا نیاز به پیاده‌سازی سازوکاری برای ریت لیمیت داریم. می‌خواهیم هر device_id و ip بتواند تنها 10 بار هر بلاگ را ببیند تا میزان ویو‌های هر بلاگ، شهودی واقعی از میزان دیده‌شدن بلاگ بدهد. تا 10 ریکوئست برای گرفتن یک بلاگ از یک device_id و ip را بدون مشکل باز گردانید و مقدار ویوی بلاگ را هم اضافه کنید. به محض عبور از 10 نیاز است که پاسخ برابر زیر باشد و به مقدار ویوی بلاگ هیچ عددی اضافه نشود.

  • کد وضعیت: 429
  • بدنه: {}
لایک کردن بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتری به این endpoint ارسال نمی‌شود. درصورتی‌که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

درصورتی‌که توکن Authorization وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {}

در غیر این صورت، باید یک عدد به لایک‌های بلاگ اضافه شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
اوضاع کلی حساب نویسنده

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

درصورتی‌که توکن Authorization وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {}

در غیر این صورت، باید مجموع تمام view و likeهای تمام بلاگ‌های نویسنده را محاسبه کند و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "total_views": "int",
      "total_likes": "int"
    }
    JSON

امتیاز ویژه: در این مسئله داکر فایل و داکر کامپوز در اختیار شماست و نحوه‌ی پیاده‌سازی کاملاً بر عهده‌ی خودتان است. اگر دیتابیس مورداستفاده‌تان را ماندگار و یا persist کنید، امتیاز بیشتری از مسئله دریافت می‌کنید.

شما تنها مجاز به استفاده از ایمیج‌های موجود در این لینک در داکر فایل و داکر کامپوز خود هستید.

نکات تکمیلی🔗

نصب نیازمندی‌ها و اجرا

برای حل این سؤال می‌توانید از هر زبان و هر تکنولوژی‌ای که می‌خواهید استفاده کنید. به‌صورتی‌که در یک پوشه به نام medium کد برنامه را بنویسید. توجه کنید که حتماً باید Dockerfile مربوط به پروژه‌ی خود را برای ما ارسال کنید.

  • نیازی به persistent بودن داده‌ها نیست! اما برای دریافت امتیاز کامل مسئله باید دیتاها را persistent کنید.
  • سیستم داوری docker-compose.yml تحویلی شما را که در خارج از فولدر medium قرار دارد، با دستور docker-compose up --build اجرا می‌کند.
extensionFromNamedocker-compose.yml
version: "3"

# add your services here
YAML
  • شما مجاز به تغییر یا ارسال docker-compose.yml دلخواه هستید.
  • نام سرویس و کانتیر کد سرور شما در فایل docker-compose.yml حتما باید برابر با medium باشد.
  • سرویس شما باید روی پورت 80 آدرس localhost قابل دسترسی باشد.
  • توصیه می‌کنیم خود APIتان را روی 0.0.0.0:80 اجرا کنید.
  • ورژن فایل داکرکامپوز شما باید حتما برابر با 3 باشد.

نحوه‌ی ارسال پاسخ🔗

شما می‌توانید تمامی محتوای موجود در پوشه‌ی medium را تغییر دهید و هر فایلی که می‌خواهید اضافه یا کم کنید.

├── medium
├──├──  [ALL_YOUR_PROJECT]   # or main.go somefile.js anyfile.php name.any ...
├──├── Dockerfile
└── docker-compose.yml
Plain text

توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در داکر فایل و داکر کامپوزتان از نام آن برای اجرای پروژه استفاده می‌کنید.

در نهایت پوشه medium را به همراه docker-compose.yml ZIP کرده و ارسال کنید. توجه کنید که پس از extract کردن فایل ZIP شما، باید فایل docker-compose.yml پوشه‌ی medium را ببینیم که درون آن Dockerfile وجود دارد.