سلام!
به مسابقه *بَله کمپ دوآپس* خوش آمدید.
به عنوان تمرین اول از شما میخواهیم تا اسکریپتی بنویسید که به تمام ورودیهای دریافتی سلام کند! یعنی پس از اجرا صرفا عبارت زیر را چاپ کند.
```terminal Terminal terminal
$ salam.sh ahali quera
Salam ahali quera
```
به علامتهای نگارشی و بزرگی و کوچکی **توجه کنید.** خروجی اسکریپت شما باید دقیقا برابر مقدار خواسته شده باشد.
<details class="blue">
<summary> **مثال** </summary>
```terminal Terminal terminal
$ ./salam.sh a b c d e f g
Salam a b c d e f g
$ ./salam.sh ali
Salam ali
```
</details>
## پروژه اولیه
برای دریافت پروژه اولیه [این لینک](/contest/assignments/68134/download_problem_initial_project/234209/) را دانلود کنید. درون لینک ساختار فایلی زیر را مشاهده میکنید:
```
Salam
└── salam.sh
```
راهحل خود را درون فایل `salam.sh` پیادهسازی کنید.
## توجه کنید
+ فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
+ فراموش نکنید که اسکریپت خود را درون فایلی با نام `salam.sh` وارد نمایید.
+ برای ارسال پاسخ خود کافیست فایل `salam.sh` را آپلود کنید.
*سلیب* فارغ از دنیا و آخرت در حال خوشگذرانی بود که ناگهان *امین* پیش او آمد و گفت که در پیامرسان *بله* خبر از گنجی به او رسیده. *سلیب* هم که برای مالی بادآورده جان میدهد، قبول کرد تا به دنبال گنج بگردد. برای این کار نیاز بود تا خود را با کشتی به جزیرهای برساند که در آنجا نقشهی گنج دفن شده بود.
*سلیب* مطمئن بود که سایتی برای خرید بلیط کشتی به ارزانترین حالت ممکن را قبلاً دیده و آن را بوکمارک کرده، اما نمیدانست که سایت را در کجای کدام مرورگر خود ذخیره کردهاست. او که به انجام ندادن کارهای خود عادت کرده، از شما خواسته تا اسکریپتی به زبان `bash` برای او بنویسید تا تمامی بوکمارکهای تمامی مرورگرهای *سلیب* را به صورت *markdown* درون فایلی ذخیره کند. در ادامه به بیان جزئیات بیشتر مسئله میپردازیم.
# جزئیات پروژه
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/68134/download_problem_initial_project/234210/) کلیک کنید.
در لپتاپ شخصی *سلیب* مرورگرهای *chrome ،opera و operagx* موجود هستند.
تمامی مرورگرها بوکمارکهای خود را در ساختاری مختص به خود درون فایل `json` ذخیره میکنند. *سلیب* جهت آسانتر کردن کار شما، آدرس فایلهای مربوط به هر مرورگر را در فایلی با نام `addresses.txt` در کنار اسکریپت شما ذخیره کردهاست. نمونه محتوای این فایل به شکل زیر است:
```text addresses.txt text
operagx:./samples/operagx.json
opera:./samples/opera.json
chrome:./samples/chrome.json
```
توجه داشته باشید ممکن است در آدرس کاراکترهای اسپیس (` `)، نقطه (`.`) و تیلدا (`~`) موجود باشد، همچنین ترتیب مرورگرها درون فایل همواره ثابت نیست ولی همواره تمامی مرورگرها درون فایل موجود هستند.
اسکریپت شما باید آدرس و تایتل تمامی سایتهای بوکمارک موجود در هر یک از این مرورگرها را بیابد و سپس موارد تکراری موجود را حذف کند و در نهایت مطابق شکل زیر، موارد را درون فایلی با نام `unique_bookmarks.md` ذخیره کند.
```markdown unique_bookmarks.md markdown
# Unique bookmarks:
| Name | URL |
|------|-----|
| <mark class="yellow" title="نام یا تایتل سایت">{NAME}</mark> | <mark class="yellow" title="آدرس سایت">{URL}</mark> |
| <mark class="yellow" title="نام یا تایتل سایت">{NAME}</mark> | <mark class="yellow" title="آدرس سایت">{URL}</mark> |
| <mark class="yellow" title="نام یا تایتل سایت">{NAME}</mark> | <mark class="yellow" title="آدرس سایت">{URL}</mark> |
<mark class="yellow" title="ممکن است ادامه داشته باشد.">...</mark>
```
**توجه داشته باشید ممکن است تایتل یا نام آدرسها با `"` شروع یا تمام شوند. نیاز است تا شما این کاراکترها را از تایتل یا نام آدرس حذف کنید؛ اما اگر `"` در وسط کلمه وجود داشت شما مجاز به حذف آن نیستید.** مثلاً اسکریپت شما باید تایتل `""""""""I love "IRAN" so much"` را به `I love "IRAN" so much` تبدیل کند. همچنین دقت کنید که ممکن است در بیین تایتل آنها `|` وجود داشته باشد؛ که این مورد هم باید حذف شود. برای مثال نام `جامعه برنامهنویسان ایران | Quera` در فایل `opera.json` باید به `جامعه برنامهنویسان ایران Quera` تبدیل شود.
به علامتهای نگارشی و بزرگی و کوچکی **توجه کنید.** خروجی اسکریپت شما باید دقیقاً برابر مقدار خواستهشده باشد.
راهحل خود را درون فایل `script.sh` موجود در پروژه اولیه، پیادهسازی کنید.
**برای حل مسئله تنها مجاز به استفاده از دستور** `jq` **برای پارس کردن فایلهای جیسون میباشید.** داکیومنتیشن این دستور در [این لینک](https://jqlang.github.io/jq/manual/) قابل مشاهده است.
<details class="blue">
<summary> **نمونه اجرا** </summary>
برای مثال در پروژهی اولیه، نمونهای از فایل ذخیرهشدهی بوکمارک در تمامی مرورگرهای مسئله موجود است. با اجرای اسکریپت شما، باید خروجی خواستهشده مشاهده شود:
```terminal Terminal terminal
$ ./script.sh
Reading bookmark file addresses from addresses.txt
```
همچنین محتوای فایل `unique_bookmarks.md` هم باید بهصورت زیر باشد:
```markdown unique_bookmarks.md markdown
# 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/ |
```
**نیازی نیست ترتیب جدول خروجی مشابه ترتیب جدول در صورت سؤال باشد و به هر ترتیب دلخواهی مجاز به تشکیل جدول هستید.** تعداد سطرهای خروجی اسکریپت شما باید دقیقاً برابر با تعداد سطرهای جدول بالا باشد.
</details>
</details>
## نکات مهم
+ فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
+ فراموش نکنید که اسکریپت خود را درون فایلی با نام `script.sh` وارد نمایید.
+ برای ارسال پاسخ خود کافیست فایل `script.sh` را آپلود کنید.
+ خروجی و رفتار دستور `jq` ممکن است در توزیعهای مختلف لینوکس رفتار متفاوتی از خود نشان دهد؛ در نتیجه ترجیحا برای تست اسکریپت خود از سیستمعامل اوبونتو استفاده کنید.
+ سطح و *level* ایندنتها و همچنین فیلدهایی که در مرورگرهای مختلف، `url` یا `name` را در خود ذخیره کردهاند متفاوت است و شما باید به صورت **بازگشتی** چک کنید تا به پراپرتی `url` یا `name` برسید و آنها را استخراج کنید.
*سلیب* پس از پیدا کردن سایت موردنظر در میان انبوهی از وبسایتهای خروجی مسئلهی قبل، به جزیره رسید و نقشهی گنج را پیدا کرد. پس از باز کردن نقشه، متوجه شد که نقشه به زبان عجیب و غریبی (احتمالاً عبری) نوشته شده و قادر به ترجمهی آن نیست. او که میدانست محال است بتواند نقشه را خودش ترجمه کند، به دنبال فردی با دانش عبری گشت. *سلیب* پس از پرسوجوهای فراوان با *علی* روبهرو شد که قبول کرد نقشه را برای او ترجمه کند؛ اما در ازای این کار، از او خواست تا تسک *iteration* فعلی او را برایش انجام دهد. *سلیب* هماکنون از شما درخواست انجام تسک پاکسازی و بهینه کردن ساختار ردیس شرکت *علی* را دارد.
# جزئیات پروژه
برای دانلود پروژهی اولیه روی [این لینک](/contest/assignments/68134/download_problem_initial_project/234211/) کلیک کنید.
برای انجام تسک شرکت *علی* نیاز به نوشتن **یک اسکریپت** به زبان `bash` دارید و اسکریپت شما باید ۴ سطح از پاکسازی که در ادامهی مسئله با جزئیات بیشتری توضیحشان میدهیم را روی دیتای ردیس فعلیشان انجام دهد.
برای حل این تمرین نیاز به دانش کار با دستور `redis-cli` را دارید که داکیومنتیشن آن را در [این لینک](https://redis.io/docs/latest/develop/get-started/) میتوانید مطالعه کنید، همچنین جهت آشنایی با تمامی داده ساختارهای موجود در ردیس هم از [این لینک](https://redis.io/docs/latest/develop/data-types/) کمک بگیرید.
<details class="blue">
<summary> **پاکسازی نوع اول** </summary>
در این سطح، نیاز است **تمامی کلیدهایی** از ردیس که شامل حداقل یک کاراکتر کوچک زبان انگلیسی است را به فرم بزرگ تغییر دهد. برای مثال اگر ردیس ما شامل کلیدهای زیر است:
```bash terminal bash
redis:6379> KEYS *
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
redis-key1
Redis-Key2
Redis-KEY3
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
پس از اجرای اسکریپت شما باید به شکل زیر تغییر کنند:
```bash terminal bash
redis:6379> KEYS *
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
REDIS-KEY1
REDIS-KEY2
REDIS-KEY3
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
اسکریپت شما پس از تغییر هر کلید باید عبارتی به فرم زیر به فایل `output.txt` **اضافه کند.**
```text output.txt text
Renamed key: <mark class="yellow" title="کلید قدیمی">OLD_KEY</mark> -> <mark class="yellow" title="کلید جدید">NEW_KEY</mark>
```
برای مثال، پس از اجرای اسکریپت روی مثال بالا، محتوای فایل `output.txt` شامل محتوای زیر باشد:
```text output.txt text
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
Renamed key: redis-key1 -> REDIS-KEY1
Renamed key: Redis-Key2 -> REDIS-KEY2
Renamed key: Redis-KEY3 -> REDIS-KEY3
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
**توجه داشته باشید تحت این عملیات پاکسازی مقدار هیچ کلیدی از ردیس نباید تغییر کند، یعنی پاکسازی شما نباید بههیچوجه باعث از دست رفتن دیتای موجود شود.**
**نکته پیرامون حفظ دیتا:** ممکن است هنگامیکه شما کلید را بهصورت *uppercase* مینویسید، کلید جدید در ردیس وجود داشته باشد. در این صورت، **تا زمانی که به کلیدی که در ردیسمان وجود نداشته باشد نرسیدهایم،** عبارت `DUPLICATE` را به انتهای کلید جدید اضافه میکنیم. برای مثال اگر ردیس ما شامل کلیدهای زیر باشد:
```bash terminal bash
redis:6379> KEYS *
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
redis-key1
REDIS-KEY1DUPLICATE
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
اسکریپت شما باید کلید `redis-key1` را به `REDIS-KEY1DUPLICATEDUPLICATE` تغییر دهد. یعنی پس از اجرای اسکریپت شما وضعیت کلیدهای ردیس مانند زیر خواهد بود:
```bash terminal bash
redis:6379> KEYS *
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
REDIS-KEY1DUPLICATE
REDIS-KEY1DUPLICATEDUPLICATE
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
و همچنین محتوای فایل `output.txt` شامل محتوای زیر باشد:
```text output.txt text
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
Renamed key: redis-key1 -> REDIS-KEY1DUPLICATEDUPLICATE
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
</details>
<details class="blue">
<summary> **پاکسازی نوع دوم** </summary>
در این سطح، نیاز است تا **تمامی مقادیری** از ردیس که بهصورت رشته هستند و با کاراکتر `[` شروع و با کاراکتر `]` تمام میشوند را بهصورت لیست در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل `output.txt` **اضافه کند.**
```text output.txt text
Parsed list for key: <mark class="yellow" title="کلیدی که مقدار آن تغییر یافته">KEY</mark>
```
برای مثال، اگر ردیس ما شامل مقداری مانند زیر است:
```bash terminal bash
redis:6379> GET TEST_KEY
"[a, b, c]"
```
پس از اجرای اسکریپت شما باید مقدار کلید مذکور بهصورت دادهساختار لیست پیادهسازیشده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:
```bash terminal bash
redis:6379> LRANGE TEST_KEY 0 -1
1) "a"
2) "b"
3) "c"
```
برای مثال پس از اجرای اسکریپت روی مثال بالا، محتوای فایل `output.txt` شامل محتوای زیر باشد:
```text output.txt text
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
Parsed list for key: TEST_KEY
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
**توجه داشته باشید که اسکریپت شما باید ترتیب لیست را حفظ کند و تغییر در ترتیب مقدار مجاز نیست.**
**توجه داشته باشید که اگر درون مقدار کاراکتر گیومه** (`"`) **وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.**
</details>
<details class="blue">
<summary> **پاکسازی نوع سوم** </summary>
در این سطح، نیاز است تا **تمامی مقادیری** از ردیس که بهصورت رشته هستند و با کاراکتر `(` شروع و با کاراکتر `)` تمام میشوند را بهصورت ست در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل `output.txt` **اضافه کند.**
```text output.txt text
Parsed set for key: <mark class="yellow" title="کلیدی که مقدار آن تغییر یافته">KEY</mark>
```
برای مثال اگر ردیس ما شامل مقداری مانند زیر است:
```bash terminal bash
redis:6379> GET TEST_KEY
"(a, b, c)"
```
پس از اجرای اسکریپت شما باید مقدار کلید مذکور بهصورت دادهساختار ست پیادهسازیشده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:
```bash terminal bash
redis:6379> SMEMBERS TEST_KEY
1) "a"
2) "b"
3) "c"
```
برای مثال، پس از اجرای اسکریپت روی مثال بالا، محتوای فایل `output.txt` شامل محتوای زیر باشد:
```text output.txt text
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
Parsed set for key: TEST_KEY
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
**توجه داشته باشید که اگر درون مقدار کاراکتر گیومه** (`"`) **وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.**
</details>
<details class="blue">
<summary> **پاکسازی نوع چهارم** </summary>
در این سطح، نیاز است **تمامی مقادیری** از ردیس که بهصورت رشته هستند و با کاراکتر `{` شروع و با کاراکتر `}` تمام میشوند را بهصورت هش در ردیس ذخیره کند. همچنین پس از تغییر هر مقدار باید عبارتی به فرم زیر به فایل `output.txt` **اضافه کند.**
```text output.txt text
Parsed map for key: <mark class="yellow" title="کلیدی که مقدار آن تغییر یافته">KEY</mark>
```
برای مثال اگر ردیس ما شامل مقداری مانند زیر است:
```bash terminal bash
redis:6379> GET TEST_KEY
"{a:b, c:d, e:f}"
```
پس از اجرای اسکریپت شما باید مقدار کلید مذکور بهصورت دادهساختار لیست پیادهسازیشده در ردیس ذخیره شده باشد و بتوانیم مانند دستور زیر به محتوای آن دسترسی داشته باشیم:
```bash terminal bash
redis:6379> HGETALL TEST_KEY
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
redis:6379> HGET TEST_KEY a
"b"
```
برای مثال پس از اجرای اسکریپت روی مثال بالا، محتوای فایل `output.txt` شامل محتوای زیر باشد:
```text output.txt text
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
Parsed map for key: TEST_KEY
<mark class="blue" title="ممکن است ادامه داشته باشد.">...</mark>
```
**توجه داشته باشید که اسکریپت شما باید ترتیب لیست را حفظ کند و تغییر در ترتیب مقدار مجاز نیست.**
**توجه داشته باشید که اگر درون مقدار کاراکتر گیومه** (`"`) **وجود داشت، این کاراکتر باید از مقدار جدید حذف شود.**
</details>
<details class="yellow">
<summary> **نمونه خروجی** </summary>
برای اجرای نمونه نیاز به نصب داکر در سیستم خود دارید. نیازی نیست تا شما درون محتوای فایلهای داکر پروژه هیچ تغییری ایجاد کنید.
درون پروژه اولیه فایلهایی جهت تست اسکریپت شما تهیه شده که در ادامه نحوه کار با هرکدام را بررسی میکنیم. ابتدا اسکریپت خود را در فایل `fixer.sh` پیادهسازی کنید. سپس با استفاده از دستور زیر کانتینرهای پروژه اولیه را بالا بیاورید:
```terminal Terminal terminal
$ docker-compose up --build -d
```
پس از اتمام اجرای دستور بالا، دستور زیر را اجرا کنید. این دستور محتوای موجود در فایل `redis-data.txt` را به درون ردیس در حال اجرا میریزد.
```terminal Terminal terminal
$ pip install redis
$ python redis-filler.py
```
<details class="blue">
<summary> **نام کلید و مقدار هر کلید در ردیس نمونه** </summary>
ساختار زیر به صورت `"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}
```
یعنی مقدار کلید `test1` برابر با `1` است.
</details>
پس از اتمام اجرای دستور بالا، باید اسکریپت خود را اجرا کنید. برای این کار از دستور زیر استفاده کنید:
```terminal Terminal terminal
$ docker exec -it fixer ./fixer.sh
```
حال باید اسکریپت شما اجرا شده باشد و در فایل `output.txt` محتوای زیر قابل مشاهده باشد:
```text output.txt text
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
```
توجه داشته باشید که ترتیب خروجی برای سیستم داوری اهمیتی ندارد و تنها نیاز است تا تمامی موارد به هر ترتیب دلخواهی حتما در خروجی وجود داشته باشند.
پس از اتمام اجرای تست، **حتما** دستور زیر را اجرا کنید تا تمامی کانتینرها متوقف شوند:
```terminal Terminal terminal
$ docker-compose down
```
</details>
# نکات تکمیلی
* فراموش نکنید که در تمامی تمرینها نیاز است تا تمام کاراکترهای `"` از مقدارهایی که توسط اسکریپت شما تغییر میکنند، حذف شود.
* اولویت اجرا از پاکسازی نوع اول به چهارم است، یعنی ممکن است که ابتدا کلید را به صورت *uppercase* تغییر بدهیم (پاکسازی نوع اول) و سپس بفهمیم که مقدار **کلید جدیدی که به صورت** *uppercase* ذخیره کردهایم نیاز به ذخیرهسازی بهصورت لیست در ردیس را داشته باشد.
* باقی کلیدهای موجود در ردیس نباید توسط اسکریپت شما تغییری کنند.
* **برای اتصال به ردیس در اسکریپت خود باید به هاست** `redis` **وصل شوید.**
## نکات مهم
+ فراموش نکنید که اسکریپتی کامل را در پاسختان تحویل دهید.
+ فراموش نکنید که اسکریپت خود را درون فایلی با نام `fixer.sh` وارد نمایید.
+ برای ارسال پاسخ خود کافیست فایل `fixer.sh` را آپلود کنید.
*سلیب* پس از ترجمهی نقشه متوجه شد که گنج جایی در آبادی *«بلهآباد»* است که در پشت کوههای جزیره پنهان شدهاست. او سریعاً حرکت کرد و هنگامیکه به پشت کوههای *«بلهآباد»* رسید، متوجه شد که مردمان این آبادی، سامانهای شبیه سیستم بلاگ *Medium* ندارند. برای *سلیب* این موقعیت خوبی بود تا به جای جستوجوی شبانهروزی به دنبال گنج، از این طریق ثروتی کسب کند و بیخیال گنج بشود؛ پس درخواست پیادهسازی این سامانه را با مشخصاتی که در ادامه توضیح میدهیم، از شما دارد. :)
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/68134/download_problem_initial_project/234212/) دانلود کنید.
در این سؤال، ما با دو بخش کلی در ارتباط هستیم: بخش کاربران و بخش بلاگها. در ادامه جزئیات هر بخش را بیان میکنیم.
<details class="blue">
<summary> **بخش کاربران** </summary>
برای این بخش نیاز است تعدادی *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ها* ارسال میشوند.
<details class="red">
<summary> **بررسی *up* بودن سرویس** </summary>
پاسخ این *endpoint* باید بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه: `{"message":Welcome to Medium API}`
</details>
<details class="red">
<summary> **ثبتنام** </summary>
سه پارامتر `username` و `password` و `email` به این *endpoint* ارسال میشوند. درصورتیکه حداقل یکی از پارامترهای `username` و `password` ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
هر دو پارامتر `username` و `password` خالی:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"username": ["This field may not be blank."],
"password": ["This field may not be blank."],
}
}
```
پارامتر `username` خالی:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"username": ["This field may not be blank."]
}
}
```
پارامتر `password` خالی:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"password": ["This field may not be blank."]
}
}
```
اگر کاربری با نام کاربری واردشده، از قبل موجود باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"username": ["A user with that username already exists."],
}
}
```
در غیر این صورت، کاربر باید ساخته شود، یک توکن یکتا برایش تولید شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `201`
+ بدنه:
```json json json
{
"refresh": <mark class="yellow" title="رفرش توکن کاربر ساخته شده.">"[REFRESH_TOKEN]"</mark>,
"access": <mark class="yellow" title="اکسس توکن کاربر ساخته شده.">"[ACCESS_TOKEN]"</mark>,
}
```
</details>
<details class="red">
<summary> **ورود به حساب کاربری** </summary>
دو پارامتر `username` و `password` باید به این *endpoint* ارسال شوند. درصورتیکه حداقل یکی از این پارامترها ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `401`
+ بدنه: `{"error": "Invalid credentials"}`
اگر نام کاربری یا رمز عبور نادرست باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `401`
+ بدنه: `{"error": "Invalid credentials"}`
در غیر این صورت، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
{
"refresh": <mark class="yellow" title="رفرش توکن کاربر ساخته شده.">"[REFRESH_TOKEN]"</mark>,
"access": <mark class="yellow" title="اکسس توکن کاربر ساخته شده.">"[ACCESS_TOKEN]"</mark>,
}
```
</details>
<details class="red">
<summary> **خروج از حساب کاربری** </summary>
تنها پارامتر `refresh` باید به این *endpoint* ارسال شود. درصورتیکه این پارامتر ارسال نشده باشد یا برابر با رشتهی خالی باشد یا حتی مقدار درستی نداشته باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{}`
در غیر این صورت، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `205`
+ بدنه: `{}`
</details>
</details>
<details class="blue">
<summary> **بخش بلاگ** </summary>
برای این بخش نیاز است تا تعدادی *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ها* ارسال میشوند.
<details class="red">
<summary> **دریافت تمامی بلاگهای ثبتشده** </summary>
این *endpoint* نیازمند *authentication* **نیست**.
نیازی به ارسال هیچ پارامتری به این *endpoint* نیست و در همهی حالات باید جواب برابر با مقدار زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
[
{
"id": <mark class="yellow" title="آیدی بلاگ">"[BLOG_ID]"</mark>,
"title": <mark class="yellow" title="تایتل بلاگ">"[BLOG_TITLE]"</mark>,
"content": <mark class="yellow" title="محتوای بلاگ">"[BLOG_CONTENT]"</mark>,
"views": <mark class="yellow" title="تعداد ویوهای بلاگ">"[BLOG_VIEWS]"</mark>,
"likes": <mark class="yellow" title="تعداد لایکهای بلاگ">"[BLOG_LIKES]"</mark>,
},
<mark class="blue" title="به ازای تمامی بلاگهای ثبت شده یک رکورد باز گردانده میشود.">...</mark>
]
```
**بلاگهای برگرداندهشده باید به ترتیب زمان ثبت بلاگ باشند.**
</details>
<details class="red">
<summary> **ایجاد بلاگ جدید** </summary>
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی، مقدار هدر `Authorization` باید برابر با توکن کاربر باشد **(توکن همواره همراه با `Bearer` ارسال میشود.)**.
پارامتر `title` و `content` باید به این *endpoint* ارسال شود. درصورتیکه یکی از این پارامترها ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"title": ["This field may not be blank."],
"content": ["This field may not be blank."],
}
}
```
در غیر این صورت، بلاگ باید ثبت شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `201`
+ بدنه:
```json json json
{
"id": <mark class="yellow" title="آیدی بلاگ">"[BLOG_ID]"</mark>,
"title": <mark class="yellow" title="تایتل بلاگ">"[BLOG_TITLE]"</mark>,
"content": <mark class="yellow" title="محتوای بلاگ">"[BLOG_CONTENT]"</mark>,
"views": <mark class="yellow" title="تعداد ویوهای بلاگ">"[BLOG_VIEWS]"</mark>,
"likes": <mark class="yellow" title="تعداد لایکهای بلاگ">"[BLOG_LIKES]"</mark>,
}
```
</details>
<details class="red">
<summary> **آپدیت بلاگ** </summary>
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد **(توکن همواره همراه با `Bearer` ارسال میشود.)**.
پارامتر `title` و `content` باید به این *endpoint* ارسال شود. درصورتیکه یکی از این پارامترها ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه:
```json json json
{
"error": {
"title": ["This field may not be blank."],
"content": ["This field may not be blank."],
}
}
```
درصورتیکه `pk` موجود در *URL* در دیتابیس وجود نداشته باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `404`
+ بدنه: `{}`
درصورتیکه کاربر ارسالکنندهی ریکوئست برابر با نویسندهی بلاگ نباشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `403`
+ بدنه: `{}`
در غیر این صورت، بلاگ باید آپدیت شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
{
"id": <mark class="yellow" title="آیدی بلاگ">"[BLOG_ID]"</mark>,
"title": <mark class="yellow" title="تایتل بلاگ">"[BLOG_TITLE]"</mark>,
"content": <mark class="yellow" title="محتوای بلاگ">"[BLOG_CONTENT]"</mark>,
"views": <mark class="yellow" title="تعداد ویوهای بلاگ">"[BLOG_VIEWS]"</mark>,
"likes": <mark class="yellow" title="تعداد لایکهای بلاگ">"[BLOG_LIKES]"</mark>,
}
```
</details>
<details class="red">
<summary> **حذف بلاگ** </summary>
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد **(توکن همواره همراه با `Bearer` ارسال میشود.)**.
پارامتری به این *endpoint* ارسال نمیشود. در صورتی که `pk` موجود در *URL* در دیتابیس وجود نداشته باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `404`
+ بدنه: `{}`
در صورتی که کاربر ارسالکنندهی ریکوئست برابر با نویسنده بلاگ نباشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `403`
+ بدنه: `{}`
در غیر این صورت، بلاگ باید حذف شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `204`
+ بدنه: `{}`
</details>
<details class="red">
<summary> **مشاهدهی بلاگ** </summary>
این *endpoint* نیازمند *authentication* **نیست**.
نیازی به ارسال هیچ پارامتری به این *endpoint* نیست. در همهی حالات، پاسخ باید برابر با مقادیر زیر باشد:
درصورتیکه `pk` موجود در *URL* در دیتابیس وجود نداشته باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `404`
+ بدنه: `{}`
در غیر اینصورت، بلاگ باید ثبت شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
{
"id": <mark class="yellow" title="آیدی بلاگ">"[BLOG_ID]"</mark>,
"title": <mark class="yellow" title="تایتل بلاگ">"[BLOG_TITLE]"</mark>,
"content": <mark class="yellow" title="محتوای بلاگ">"[BLOG_CONTENT]"</mark>,
"views": <mark class="yellow" title="تعداد ویوهای بلاگ">"[BLOG_VIEWS]"</mark>,
"likes": <mark class="yellow" title="تعداد لایکهای بلاگ">"[BLOG_LIKES]"</mark>,
}
```
توجه داشته باشید که درصورتیکه بلاگ موردنظر وجود داشت و کاربر بدون مشکل میتوانست آن را مشاهده کند، باید به مقدار `view`ی بلاگ، یک واحد اضافه کنید **و سپس بلاگ را برگردانید.** یعنی بازدید فعلی کاربر در میزان بازدیدهای بلاگ برگرداندهشده باید **محاسبه شده باشد.**
**نکتهی ریت لیمیت:** در اینجا نیاز به پیادهسازی سازوکاری برای ریت لیمیت داریم. میخواهیم هر `device_id` و `ip` بتواند تنها *10* بار **هر بلاگ** را ببیند تا میزان ویوهای هر بلاگ، شهودی واقعی از میزان دیدهشدن بلاگ بدهد. تا *10* ریکوئست برای گرفتن یک بلاگ از یک `device_id` و `ip` را بدون مشکل باز گردانید و مقدار ویوی بلاگ را هم اضافه کنید. به محض عبور از *10* نیاز است که پاسخ برابر زیر باشد و به مقدار ویوی بلاگ **هیچ عددی اضافه نشود.**
+ کد وضعیت: `429`
+ بدنه: `{}`
</details>
<details class="red">
<summary> **لایک کردن بلاگ** </summary>
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد **(توکن همواره همراه با `Bearer` ارسال میشود.)**.
پارامتری به این *endpoint* ارسال نمیشود. درصورتیکه `pk` موجود در *URL* در دیتابیس وجود نداشته باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `404`
+ بدنه: `{}`
درصورتیکه توکن `Authorization` وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `401`
+ بدنه: `{}`
در غیر این صورت، باید یک عدد به لایکهای بلاگ اضافه شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
{
"id": <mark class="yellow" title="آیدی بلاگ">"[BLOG_ID]"</mark>,
"title": <mark class="yellow" title="تایتل بلاگ">"[BLOG_TITLE]"</mark>,
"content": <mark class="yellow" title="محتوای بلاگ">"[BLOG_CONTENT]"</mark>,
"views": <mark class="yellow" title="تعداد ویوهای بلاگ">"[BLOG_VIEWS]"</mark>,
"likes": <mark class="yellow" title="تعداد لایکهای بلاگ">"[BLOG_LIKES]"</mark>,
}
```
</details>
<details class="red">
<summary> **اوضاع کلی حساب نویسنده** </summary>
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد **(توکن همواره همراه با `Bearer` ارسال میشود.)**.
درصورتیکه توکن `Authorization` وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `401`
+ بدنه: `{}`
در غیر این صورت، باید مجموع تمام `view` و `like`های تمام بلاگهای نویسنده را محاسبه کند و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه:
```json json json
{
"total_views": <mark class="yellow" title="مجموع ویوهای همه بلاگهای نویسنده">"int"</mark>,
"total_likes": <mark class="yellow" title="مجموع لایکهای همه بلاگهای نویسنده">"int"</mark>
}
```
</details>
</details>
**امتیاز ویژه:** در این مسئله داکر فایل و داکر کامپوز در اختیار شماست و نحوهی پیادهسازی کاملاً بر عهدهی خودتان است. اگر دیتابیس مورداستفادهتان را ماندگار و یا `persist` کنید، امتیاز بیشتری از مسئله دریافت میکنید.
**شما تنها مجاز به استفاده از ایمیجهای موجود در [این لینک](https://gitlab.com/qio/standard/container_registry/?orderBy=UPDATED&sort=desc) در داکر فایل و داکر کامپوز خود هستید.**
# نکات تکمیلی
<details class="blue">
<summary>
نصب نیازمندیها و اجرا
</summary>
برای حل این سؤال میتوانید از هر زبان و هر تکنولوژیای که میخواهید استفاده کنید. بهصورتیکه در یک پوشه به نام `medium` کد برنامه را بنویسید. توجه کنید که حتماً باید `Dockerfile` مربوط به پروژهی خود را برای ما ارسال کنید.
</details>
+ نیازی به *persistent* بودن دادهها نیست! اما برای دریافت امتیاز کامل مسئله باید دیتاها را *persistent* کنید.
+ سیستم داوری `docker-compose.yml` تحویلی شما را که در خارج از فولدر `medium` قرار دارد، با دستور `docker-compose up --build` اجرا میکند.
```yaml docker-compose.yml yaml
version: "3"
# add your services here
```
+ شما مجاز به تغییر یا ارسال `docker-compose.yml` دلخواه هستید.
+ **نام سرویس و کانتیر کد سرور شما در فایل** `docker-compose.yml` **حتما باید برابر با** `medium` **باشد.**
+ سرویس شما باید روی پورت `80` آدرس `localhost` قابل دسترسی باشد.
+ توصیه میکنیم خود *API*تان را روی `0.0.0.0:80` اجرا کنید.
+ ورژن فایل داکرکامپوز شما باید حتما برابر با `3` باشد.
## نحوهی ارسال پاسخ
شما میتوانید تمامی محتوای موجود در پوشهی `medium` را تغییر دهید و هر فایلی که میخواهید اضافه یا کم کنید.
```text
├── medium
├──├── <mark class="purple" title="نام این فایلها و فولدرها اهمیتی ندارند."> [ALL_YOUR_PROJECT] </mark> # or main.go somefile.js anyfile.php name.any ...
├──├── Dockerfile
└── docker-compose.yml
```
توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در داکر فایل و داکر کامپوزتان از نام آن برای اجرای پروژه استفاده میکنید.
در نهایت پوشه `medium` را به همراه `docker-compose.yml` _ZIP_ کرده و ارسال کنید. توجه کنید که پس از _extract_ کردن فایل _ZIP_ شما، باید فایل `docker-compose.yml` پوشهی `medium` را ببینیم که درون آن `Dockerfile` وجود دارد.