**این سؤال تنها با زبانهای _PHP_ ، _Python_ ، _Go_ و _JS (Node.js)_ قابل حل است.**
---
دیجیکالا قصد دارد برای بخش تحویل محصولات خود یک سامانهی سادهی ثبت پیشنهادات و انتقادات راهاندازی کند. از شما میخواهیم یک *API* برای این سامانه طراحی کنید.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/contest/assignments/44198/download_problem_initial_project/148078/) دانلود کنید.
در این سؤال، یک *REST API* شامل *endpoint* های زیر باید پیادهسازی شود:
| آدرس | عنوان |
| :------------------------ | ----------------------: |
| `GET /` | بررسی *up* بودن سرویس |
| `POST /signup` | ثبتنام |
| `POST /login` | ورود به حساب کاربری |
| `POST /urls` | کوتاه کردن لینک |
| `GET /urls` | دریافت لیست لینکهای کوتاهشده |
| `GET /<mark title="متغیر است">{slug}</mark>` | لینک کوتاهشده |
در این *API* هر کاربر باید یک توکن داشته باشد. این توکن برای هر کاربر ثابت است.
%align_right_start%
## *endpoint* های موردنیاز
%align_end%
**در همهی _endpoint_ ها، پاسخ باید بهصورت _JSON_ باشد.**
اطلاعات ورودی بهصورت `application/x-www-form-urlencoded` به *endpoint* ها ارسال میشوند.
### بررسی *up* بودن سرویس
پاسخ این *endpoint* باید بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه: `{"ok":true}`
### ثبتنام
دو پارامتر `username` و `password` باید به این *endpoint* ارسال شوند. در صورتی که حداقل یکی از این پارامترها ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{"ok":false,"error":"no username or password provided"}`
اگر کاربری با نام کاربری واردشده از قبل موجود باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{"ok":false,"error":"user already exists"}`
در غیر اینصورت، کاربر باید ساخته شود، یک توکن یکتا برایش تولید شود و پاسخ بهصورت زیر باشد:
+ کد وضعیت: `201`
+ بدنه: `{"ok":true,"token":"<mark title="توکن کاربر">USER_TOKEN</mark>"}`
### ورود به حساب کاربری
دو پارامتر `username` و `password` باید به این *endpoint* ارسال شوند. در صورتی که حداقل یکی از این پارامترها ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{"ok":false,"error":"no username or password provided"}`
اگر نام کاربری یا رمز عبور نادرست باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{"ok":false,"error":"invalid username or password"}`
در غیر اینصورت، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `200`
+ بدنه: `{"ok":true,"token":"<mark title="توکن کاربر">USER_TOKEN</mark>"}`
### کوتاه کردن لینک
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد (**بدون `Bearer` یا موارد مشابه**).
پارامتر `url` (لینک) باید به این *endpoint* ارسال شود. در صورتی که این پارامتر ارسال نشده باشد یا برابر با رشتهی خالی باشد، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `400`
+ بدنه: `{"ok":false,"error":"no url provided"}`
در غیر اینصورت، لینک باید کوتاه شود و پاسخ بهصورت زیر باشد (مقدار `{slug}` میتواند رندوم باشد):
+ کد وضعیت: `201`
+ بدنه: `{"ok":true,"url":"http://localhost/{slug}"}`
### دریافت لیست لینکهای کوتاهشده
این *endpoint* نیازمند *authentication* است. در ریکوئست ارسالی مقدار هدر `Authorization` باید برابر با توکن کاربر باشد (**بدون `Bearer` یا موارد مشابه**).
این *endpoint* باید لیست لینکهای کوتاهشدهی کاربر به همراه تعداد بازدید هر کدام را در قالب یک لیست برگرداند.
+ کد وضعیت: `200`
+ مثالی از پاسخ:
```json
[
{
"short_url": "http://localhost/SDas2",
"url": "https://quera.org",
"visits_count": 3
},
{
"short_url": "http://localhost/zxcA54",
"url": "https://google.com",
"visits_count": 0
}
]
```
### لینک کوتاهشده
آدرس این درخواست بهصورت `/{slug}` است که `{slug}` همان رشتهای است که به لینک کوتاهشده توسط برنامه تخصیص مییابد. اگر `{slug}` ورودی یافت نشود، پاسخ باید بهصورت زیر باشد:
+ کد وضعیت: `404`
در غیر اینصورت، کاربر باید به لینک اصلی با کد پاسخ `301` هدایت شود.
# نکات تکمیلی
<details class="blue">
<summary>
نصب نیازمندیها و اجرا
</summary>
برای حل این سؤال میتوانید از هر زبان و هر تکنولوژیای که میخواهید استفاده کنید. بهصورتی که در یک پوشه به نام `api` کد برنامه را نوشته و در فایلی به نام `runner.sh` که توسط `sh` اجرا میشود، باید برنامهی خود را اجرا کنید. توجه کنید که حتماً باید `Dockerfile` مربوط به پروژهی خود را برای ما ارسال کنید.
در پروژهی اولیه، ۴ داکرفایل برای `php`، `python`، `golang` و `node` قرار دادیم که میتوانید از آنها مستقیماً استفاده کنید. در صورتی که از یکی از این زبانها برای حل سؤال استفاده میکنید، کافیست که `Dockerfile` مربوط به آن را در پوشهی `api` کپی کنید و طبق توضیحات داده شده، سؤال را حل کنید. برای نصب نیازمندیهای پایتون از `requirements.txt`، برای پیاچپی از `composer.json`، برای گولنگ از `go.mod` و برای نودجیاس از `package.json` استفاده کنید.
در صورتی که زبان مورد استفادهی شما، چیزی به جز این ۴ مورد است، باید خودتان داکرفایلی در پوشهی `api` بهشکلی بنویسید که بتواند نیازمندیهای پروژهی شما را نصب کرده و برنامهی شما را مانند داکرفایلهای موجود اجرا کند.
</details>
+ نیازی به *persistent* بودن دادهها نیست!
+ سیستم داوری `docker-compose.yml` زیر را خارج از فولدر `api` پاسخ شما قرار میدهد و با دستور `docker-compose up --build` آن را اجرا میکند.
```yaml docker-compose.yml
version: "3"
services:
api:
build: "./api"
container_name: "api"
ports:
- "80:80"
```
+ شما مجاز به تغییر یا ارسال `docker-compose.yml` دلخواه نیستید.
+ سرویس شما باید روی پورت `80` آدرس `localhost` قابل دسترسی باشد.
+ توصیه میکنیم در `runner.sh` خود *API*تان را روی `0.0.0.0:80` اجرا کنید.
<details class="green">
<summary>
تغییر `Dockerfile`
</summary>
امکان تغییر فایل `Dockerfile` وجود ندارد، اما در اسکریپت `runner.sh` میتوانید هر دستوری را اجرا کنید.
</details>
## نحوه ارسال پاسخ
شما میتوانید تمامی محتوای موجود در پوشهی `api` را تغییر دهید و هر فایلی که میخواهید اضافه یا کم کنید.
```text
api
├── <mark class="purple" title="نام این فایل اهمیتی ندارد"> api.py </mark> # or main.go somefile.js anyfile.php name.any ...
├── Dockerfile
├── requirements.txt # or go.mod package.json composer.json
└── runner.sh
```
توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در `runner.sh` از نام آن برای اجرای پروژه استفاده میکنید.
در نهایت این پوشه را _zip_ کرده و ارسال کنید. توجه کنید که پس از _extract_ کردن فایل _zip_ شما، باید پوشهی `api` را ببینیم که درون آن `Dockerfile` وجود دارد.