تیم فنی اسنپ قصد دارد تا سیستم جدیدی برای مدیریت دستاوردهای کاربران و دادن امتیاز به آنها با بهرهگیری از gamification پیادهسازی کند، اما به دلیل درخواست این تیم برای جذب نیروی فنی، آنها این تسک را به برخی از افرادی سپردهاند که در مرحلهی اول مصاحبهی اسنپ قبول شدهاند. مهدی که سرش شلوغ است، اما میخواهد در اسنپ استخدام شود، از شما میخواهد تا این تسک را برایش انجام دهید. نسخهی اولیهی این سیستم قرار است یک API ساده باشد.
جزئیات پروژه
پروژهی اولیه را از این لینک دانلود کنید. ساختار فایلهای پروژه بهصورت زیر است:
snapp_baz
├── achievements
│   └── achievements.go
├── db
│   └── connection.go
├── handlers
│   └── achievements.go
├── test
│   └── achievements_sample_test.go
├── go.mod
├── go.sum
└── main.go
پایگاه داده
در این پروژه از پایگاه دادهی PostgreSQL استفاده شده است. برنامه شامل جداول زیر است:
- کاربران (
users): 
| نام ستون | نوع | تعریف | ملاحضات | 
|---|---|---|---|
id | 
BIGSERIAL | 
شناسهی کاربر | PRIMARY KEY | 
name | 
VARCHAR(255) | 
نام | |
phone | 
VARCHAR(255) | 
شماره تلفن | 
- دستاوردها (
achievements): 
| نام ستون | نوع | تعریف | ملاحظات | 
|---|---|---|---|
id | 
BIGSERIAL | 
شناسهی دستاورد | PRIMARY KEY | 
title | 
VARCHAR(255) | 
عنوان دستاورد | 
- دستاوردهای کاربران (
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)
ارسال پاسخ برای این سؤال