در این سؤال، باید سامانهی فروش بلیت مسابقات فوتبال در استادیومهای مختلف را پیادهسازی کنید!
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/157780/) دانلود کنید.
+ این پروژه از *Maven* استفاده میکند و وابستگیها در *POM* تعریف شدهاند. امکان تغییر این وابستگیها وجود ندارد.
+ برای اجرای تستها، از دیتابیس *[H2](https://www.h2database.com/)* استفاده میشود.
+ در این پروژه، وابستگی *[Project Lombok](https://projectlombok.org/)* تعریف شده و قابل استفاده است.
+ بستهی `org.quera.ticket` شامل موجودیتهای *JPA* برنامه است که مطابق با *schema* دیتابیس طراحی شدهاند. شناسه (`id`) موجودیتها بهصورت خودکار توسط *Hibernate* تولید میشود.
+ بستهی `org.quera.ticket.security` شامل کلاسهایی برای مدیریت احراز هویت است.
## سرویسها
تعدادی وبسرویس *REST* مطابق نیازمندیهای زیر باید پیادهسازی شود:
| آدرس | عنوان |
| :------------------------ | ----------------------: |
| `GET /api/ping` | بررسی صحت برنامه |
| `POST /api/users/{id}/update_balance` | تغییر موجودی یک کاربر |
| `GET /api/stadiums` | دریافت اطلاعات استادیومها |
| `POST /api/stadiums` | ایجاد استادیوم جدید |
| `GET /api/stadiums/{id}` | دریافت اطلاعات یک استادیوم |
| `DELETE /api/stadiums/{id}` | حذف یک استادیوم |
| `GET /api/teams` | دریافت اطلاعات تیمها |
| `POST /api/teams` | ایجاد تیم جدید |
| `GET /api/teams/{id}` | دریافت اطلاعات یک تیم |
| `DELETE /api/teams/{id}` | حذف یک تیم |
| `GET /api/matches` | دریافت اطلاعات مسابقات |
| `POST /api/matches` | ایجاد مسابقهی جدید |
| `GET /api/matches/{id}` | دریافت اطلاعات یک مسابقه |
| `DELETE /api/matches/{id}` | حذف یک مسابقه |
| `GET /api/seat_classes` | دریافت اطلاعات مجموعه جایگاهها |
| `POST /api/seat_classes` | ایجاد مجموعه جایگاه جدید |
| `GET /api/seat_classes/{id}` | دریافت اطلاعات یک مجموعه جایگاه |
| `DELETE /api/seat_classes/{id}` | حذف یک مجموعه جایگاه |
| `GET /api/tickets` | دریافت لیست بلیتهای کاربر فعلی |
| `POST /api/tickets` | خرید بلیت برای یک صندلی |
### بررسی صحت برنامه
با ارسال درخواست به این *endpoint* ، پاسخ زیر باید برگردانده شود:
```json
{
"message": "ok"
}
```
### تغییر موجودی یک کاربر
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* بههمراه پارامتر `balance` در بدنهی درخواست، مقدار موجودی کاربر ورودی باید بهروز شود.
### دریافت اطلاعات استادیومها
با ارسال درخواست به این *endpoint* ، اطلاعات همهی استادیومها در قالب یک آرایه باید برگردانده شوند.
### ایجاد استادیوم جدید
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. یک استادیوم با اطلاعات اولیهی دادهشده ایجاد میشود و مشخصات آن برگردانده میشود. مقادیر `name` و `capacity` در بدنهی درخواست ارسال میشوند. در صورتی که مقدار `balance` واردشده منفی باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid balance"
}
```
در صورتی که کاربری با شناسهی واردشده موجود نباشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: user not found"
}
```
### دریافت اطلاعات یک استادیوم
با ارسال درخواست به این *endpoint* ، اطلاعات استادیومی که شناسهی آن وارد شده است باید برگردانده شود. در صورتی که استادیومی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: stadium not found"
}
```
### حذف یک استادیوم
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* ، استادیومی که شناسهی آن وارد شده است باید حذف شود. در صورتی که استادیومی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: stadium not found"
}
```
### دریافت اطلاعات تیمها
با ارسال درخواست به این *endpoint* ، اطلاعات همهی تیمها در قالب یک آرایه باید برگردانده شوند.
### ایجاد تیم جدید
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* ، یک تیم با اطلاعات اولیهی دادهشده ایجاد میشود و مشخصات آن برگردانده میشود. مقدار `name` در بدنهی درخواست ارسال میشوند. در صورتی که تیمی با نام واردشده از قبل وجود داشته باشد، کد پاسخ باید `400` باشد.
### دریافت اطلاعات یک تیم
با ارسال درخواست به این *endpoint* ، اطلاعات تیمی که شناسهی آن وارد شده است باید برگردانده شود. در صورتی که تیمی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: team not found"
}
```
### حذف یک تیم
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* ، تیمی که شناسهی آن وارد شده است باید حذف شود. در صورتی که تیمی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: team not found"
}
```
### دریافت اطلاعات مسابقات
با ارسال درخواست به این *endpoint* ، اطلاعات همهی مسابقات در قالب یک آرایه باید برگردانده شوند.
### ایجاد مسابقهی جدید
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. یک مسابقه با اطلاعات اولیهی دادهشده ایجاد میشود و مشخصات آن برگردانده میشود. مقادیر زیر در بدنهی درخواست ارسال میشوند:
+ شناسهی تیم میزبان (`home_id`)
+ شناسهی تیم میهمان (`away_id`)
+ شناسهی استادیوم (`stadium_id`)
+ تاریخ برگزاری مسابقه (`date`) با فرمت `yyyy-MM-dd`
اگر از تاریخ مسابقه گذشته باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: the given date has been passed"
}
```
در صورتی که شناسهی تیم میزبان معتبر نباشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid home team id"
}
```
در صورتی که شناسهی تیم میهمان معتبر نباشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid away team id"
}
```
در صورتی که شناسهی استادیوم معتبر نباشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid stadium id"
}
```
اگر استادیوم در تاریخ ذکرشده رزرو باشد (مسابقهی دیگری در آن روز در استادیوم وجود داشته باشد)، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: stadium is full in the given date"
}
```
### دریافت اطلاعات یک مسابقه
با ارسال درخواست به این *endpoint* ، اطلاعات مسابقهای که شناسهی آن وارد شده است باید برگردانده شود. در صورتی که مسابقهای با شناسهی واردشده وجود داشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: match not found"
}
```
### حذف یک مسابقه
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* ، مسابقهای که شناسهی آن وارد شده است باید حذف شود. در صورتی که مسابقهای با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: match not found"
}
```
### دریافت اطلاعات مجموعه جایگاهها
با ارسال درخواست به این *endpoint* ، اطلاعات همهی مجموعه جایگاهها در قالب یک آرایه باید برگردانده شوند.
### ایجاد مجموعه جایگاه جدید
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. یک مجموعه جایگاه با اطلاعات اولیهی دادهشده ایجاد میشود و مشخصات آن برگردانده میشود. مقادیر زیر در بدنهی درخواست ارسال میشوند:
+ عدد شروع صندلیهای مجموعه جایگاه (`min_number`)
+ عدد پایان صندلیهای مجموعه جایگاه (`max_number`)
+ شناسهی مسابقه (`match_id`)
+ قیمت صندلیهای موجود در مجموعه جایگاه (`price`)
در صورتی که مسابقهای با شناسهی واردشده موجود نباشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid match id"
}
```
در صورتی که مقدار `min_number` کوچکتر از ۱ باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid min number"
}
```
در صورتی که مقدار `max_number` کوچکتر از `min_number` باشد یا بزرگتر از ظرفیت استادیوم باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid max number"
}
```
در صورتی که مقدار `price` منفی باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid price"
}
```
اگر مجموعه جایگاهی برای بازی موردنظر وجود داشته باشد که شماره صندلی مشترکی با مجموعه جایگاه فعلی داشته باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: overlapping seat class exists"
}
```
### دریافت اطلاعات یک مجموعه جایگاه
با ارسال درخواست به این *endpoint* ، اطلاعات مجموعه جایگاهی که شناسهی آن وارد شده است باید برگردانده شود. در صورتی که مجموعه جایگاهی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد.
### حذف یک مجموعه جایگاه
این *endpoint* باید تنها برای کاربرانی قابل دسترس باشد که نقششان `ADMIN` است. با ارسال درخواست به این *endpoint* ، مجموعه جایگاهی که شناسهی آن وارد شده است باید حذف شود. در صورتی که مجموعه جایگاهی با شناسهی واردشده وجود نداشته باشد، کد پاسخ باید `404` باشد.
### دریافت لیست بلیتهای کاربر فعلی
با ارسال درخواست به این *endpoint* ، لیست بلیتهای کاربر واردشدهی فعلی باید در قالب یک آرایه برگردانده شود.
### خرید بلیت برای یک صندلی
با ارسال درخواست به این *endpoint* ، عملیات خرید بلیت یک صندلی برای کاربر فعلی باید انجام شود. مقادیر `match_id` و `seat_number` در بدنهی درخواست موجود خواهند بود.
در صورتی که مسابقهای با شناسهی واردشده موجود نباشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid match id"
}
```
در صورتی که از تاریخ مسابقه گذشته باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: the match has been finished"
}
```
در صورتی که مقدار `seat_number` کوچکتر از ۱ باشد یا بزرگتر از ظرفیت استادیوم بازی باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: invalid seat number"
}
```
در صورتی که مجموعه جایگاهی برای شماره صندلی موردنظر تعریف نشده باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: the seat is not available"
}
```
در صورتی که کاربر فعلی، صندلی فعلی را از قبل خریده باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: you have already reserved this seat"
}
```
در صورتی که کاربر دیگری صندلی فعلی را از قبل خریده باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: the seat is reserved by another user"
}
```
در صورتی که میزان موجودی کاربر کمتر از قیمت صندلی باشد، کد پاسخ باید `400` باشد و بدنهی پاسخ باید بهصورت زیر باشد:
```json
{
"message": "Error: not enough balance"
}
```
# نکات
+ در *endpoint* هایی که نیازمند دسترسی کاربر هستند، در صورت عدم وجود دسترسی، کد پاسخ `403` باید برگردانده شود. در صورتی که هیچ کاربری وارد برنامه نشده باشد، کد پاسخ `401` باید برگردانده شود.
+ فیلدهایی که در پاسخها باید برگردند، باید مطابق با تعریف مدلها باشند.
# آنچه باید آپلود کنید
پس از پیادهسازی موارد خواستهشده، پوشهی `src` پروژه را زیپ کرده و ارسال کنید. توجه داشته باشید که فقط تغییرات اعمالشده در پوشهی `src/main/java/org/quera/ticket` در نظر گرفته میشوند.