تیم فنی *اسنپ* قصد دارد تا سیستم جدیدی برای مدیریت دستاوردهای کاربران و دادن امتیاز به آنها با بهرهگیری از *gamification* پیادهسازی کند، اما به دلیل درخواست این تیم برای جذب نیروی فنی، آنها این تسک را به برخی از افرادی سپردهاند که در مرحلهی اول مصاحبهی *اسنپ* قبول شدهاند. مهدی که سرش شلوغ است، اما میخواهد در *اسنپ* استخدام شود، از شما میخواهد تا این تسک را برایش انجام دهید. نسخهی اولیهی این سیستم قرار است یک *API* ساده باشد.
# جزئیات پروژه
پروژهی اولیه را از [این لینک](/problemset/assignments/4367/download_problem_initial_project/113614/) دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
```
snapp_baz
├── achievements
│ └── achievements.go
├── db
│ └── connection.go
├── handlers
│ └── achievements.go
├── test
│ └── achievements_sample_test.go
├── go.mod
├── go.sum
└── main.go
```
## پایگاه داده
در این پروژه از پایگاه دادهی *PostgreSQL* استفاده شده است. برنامه شامل جداول زیر است:
1. کاربران (`users`):
| نام ستون | نوع | تعریف | ملاحضات |
|:-------|:-------------|-------------:|:--------------|
| `id` | `BIGSERIAL` | شناسهی کاربر | `PRIMARY KEY` |
| `name` | `VARCHAR(255)` | نام | |
| `phone` | `VARCHAR(255)` | شماره تلفن | |
2. دستاوردها (`achievements`):
| نام ستون | نوع | تعریف | ملاحظات |
|:-------------|:-------------|--------------------------------:|:-------------------------------------|
| `id` | `BIGSERIAL` | شناسهی دستاورد | `PRIMARY KEY` |
| `title` | `VARCHAR(255)` | عنوان دستاورد | |
3. دستاوردهای کاربران (`user_achievements`):
| نام ستون | نوع | تعریف |
| :--------------- | :------- | -------------: |
| `user_id` | `BIGINT` | شناسهی کاربر |
| `achievement_id` | `BIGINT` | شناسهی دستاورد |
دسترسی به پایگاه داده از طریق تابع `db.GetConnection` صورت میگیرد که به شکل *singleton* پیادهسازی شده است.
## منطق کسب دستاوردها
یک `map[int](func(int) bool)` به شکل *singleton* در برنامه تعریف شده که از طریق تابع `achievements.GetMap` قابل دسترسی است. این مپ، نگاشتی از شناسهی دستاوردها به توابعی است که با دریافت شناسهی یک کاربر، مشخص میکنند که آیا آن دستاورد توسط کاربر کسب شده است یا خیر.
## روتها
نسخهی اولیهی این *API* شامل روتهای زیر است:
- `/is_achieved`: این روت به تابع `handlers.IsAchieved` مپ شده است که با دریافت یک `user_id` و یک `achievement_id` از *query string*، مشخص میکند که آیا دستاورد توسط کاربر ذکرشده کسب شده است یا خیر.
- اگر پارامتر `user_id` مقداردهی نشده باشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "no user id provided"}` باشد.
- اگر پارامتر `user_id` مقداردهی شده باشد، اما پارامتر `achievement_id` مقداردهی نشده باشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "no achievement id provided"}` باشد.
- اگر مقدار پارامتر `user_id` عددی نباشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "invalid user id"}` باشد.
- اگر مقدار پارامتر `user_id` عددی باشد، اما مقدار پارامتر `achievement_id` عددی نباشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "invalid achievement id"}` باشد.
- اگر کاربری با شناسهی `user_id` در جدول `users` موجود نباشد، کد پاسخ باید `404` و بدنهی پاسخ باید `{"error": "user not found"}` باشد.
- اگر کاربری با شناسهی `user_id` در جدول `users` موجود باشد، اما دستاوردی با شناسهی `achievement_id` در جدول `achievements` موجود نباشد، کد پاسخ باید `404` و بدنهی پاسخ باید `{"error": "achievement not found"}` باشد.
- اگر هیچ یک از شرایط فوق برقرار نباشد، کد پاسخ باید `200` و بدنهی پاسخ باید `{"is_achieved": true}` یا `{"is_achieved": false}` باشد (بسته به این که کاربر دستاورد را کسب کرده است یا خیر).
- `/get_achievements`: این روت به تابع `handlers.GetAchievements` مپ شده است که با دریافت یک `user_id` از *query string*، لیست دستاوردهای کاربر را برمیگرداند.
- اگر پارامتر `user_id` مقداردهی نشده باشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "no user id provided"}` باشد.
- اگر مقدار پارامتر `user_id` عددی نباشد، کد پاسخ باید `400` و بدنهی پاسخ باید `{"error": "invalid user id"}` باشد.
- اگر کاربری با شناسهی `user_id` در جدول `users` موجود نباشد، کد پاسخ باید `404` و بدنهی پاسخ باید `{"error": "user not found"}` باشد.
- اگر هیچ یک از شرایط فوق برقرار نباشد، کد پاسخ باید `200` و بدنهی پاسخ باید به فرم `{"achievements": ["achievement 1 name", "achievement 2 name"]}` باشد (نام دستاوردها باید بهترتیب صعودی شناسهشان باشد).
- `/refresh_achievements`: این روت به تابع `handlers.RefreshAchievements` مپ شده است که با دریافت یک `user_id` از *query_string*، لیست دستاوردهای کاربر را بهروز میکند. در واقع، دستاوردهایی که تاکنون توسط کاربر کسب نشدهاند باید مجدداً بررسی شوند و در صورتی که دستاورد جدیدی کسب شده بود، در جدول `user_achievements` درج شود.
- کد و بدنهی پاسخ این روت باید مشابه روت `/get_achievements` باشد، با این تفاوت که دستاوردهای کسبنشده مجدداً بررسی میشوند.
**توجه:** مقدار هدر `Content-Type` در پاسخ همهی درخواستها باید برابر با `application/json` قرار داده شود.
# آنچه باید آپلود کنید
پس از پیادهسازی برنامه، یک فایل زیپ آپلود کنید که وقتی آن را باز میکنیم، با ساختار زیر مواجه شویم:
```
snapp_baz
├── handlers
│ └── achievements.go
├── go.mod (Optional)
└── go.sum (Optional)
```