سلام دوست عزیز😃👋

به مسابقه «بله‌کمپ ۷ - مرحله دوم (Back-End)» خوش آمدی!

نکات مفید برای شرکت در مسابقه:

  • هرگونه استفاده از ابزارهای تولید کد، مثل chatGPT و... در مسابقات کوئرا ممنوع است و بعد از شناسایی از لیست شرکت‌کنندگان مسابقه حذف می‌شوید.
  • هر گونه ارتباط با سایر شرکت‌کنندگان ممنوع است.
  • می‌توانید سوال‌ها و مشکلات خود را از بخش «سوال بپرسید» با ما در میان بگذارید.

سوال «نظرات وبلاگ» مهارت شما را در پیاده‌سازی می‌سنجد (با زبان برنامه‌نویسی دلخواه‌تان می‌توانید حل کنید).🔗

سوال «چالش اطلاعات» مهارت شما را در کار با دیتابیس می‌سنجد (کوئری‌های شما باید روی MySQL قابل اجرا باشند).🔗

سوال «سامانه بلاگ بله آباد» مهارت شما را در حوزه بک‌اند می‌سنجد (این را سوال را صرفا با یک زبان حل کنید و حل آن با چند زبان مختلف تاثیری بر نتیجه شما ندارد).🔗

موفق باشید و بهتون خوش بگذره 😉✌

سامانه بلاگ بَله آباد - Node.js


توجه کنید که سؤال «سامانه بلاگ بَله آباد» را باید با دقیقاً یکی از زبان‌های Python ،PHP ،Golang یا Node.js حل کنید. در صورتی که تمایل دارید سؤال را با Node.js حل کنید، می‌توانید از طریق این بخش اقدام به حل کنید. خروجی نهایی در در زبان‌های مختلف تفاوتی ندارد و می‌توانید زبان مورد نظر را با توجه به دانش خود انتخاب کنید.


سلیب پس از ترجمه نقشه متوجه شد که گنج جایی در آبادی « بله آباد » است که در پشت کوه‌های جزیره پنهان شده است. او سریعاً حرکت کرد و هنگامی که به پشت کوه‌های « بله آباد » رسید، متوجه شده که مردمان این آبادی، سامانه‌ای شبیه سیستم بلاگ medium ندارند. برای سلیب این موقعیت خوبی‌بود تا به جای جست‌وجوی شبانه روزی به دنبال گنج، از این طریق ثروتی کسب کند و بیخیال گنج بشود. پس سریعا درخواست پیاده‌سازی این سامانه را با مشخصاتی که در ادامه توضیح می‌دهیم را از شما دارد :‌).

جزئیات پروژه🔗

پروژه‌ی اولیه را از این لینک دانلود کنید.

در این سؤال، ما با دو بخش کلی در ارتباط هستیم، بخش کاربران و بخش بلاگ‌ها. در ادامه به بیان جزئیات هر بخش می‌پردازیم.

بخش کاربران

برای این بخش نیاز است تا تعدادی 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 ها ارسال می‌شوند.

بررسی up بودن سرویس

پاسخ این endpoint باید به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه: {"message":Welcome to Medium API}
ثبت‌نام

سه پارامتر username و password و email به این endpoint ارسال می‌شوند. در صورتی که حداقل یکی از پارامترهای username و password ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

هر دو پارامتر username و password خالی:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "username": ["This field may not be blank."],
          "password": ["This field may not be blank."],
          }
    }
    JSON

پارامتر username خالی:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "username": ["This field may not be blank."]
          }
    }
    JSON

پارامتر password خالی:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "password": ["This field may not be blank."]
          }
    }
    JSON

اگر کاربری با نام کاربری واردشده از قبل موجود باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "username": ["A user with that username already exists."],
          }
    }
    JSON

در غیر این‌صورت، کاربر باید ساخته شود، یک توکن یکتا برایش تولید شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 201
  • بدنه:
    extensionFromNamejson
    {
      "refresh": "[REFRESH_TOKEN]",
      "access": "[ACCESS_TOKEN]",
    }
    JSON
ورود به حساب کاربری

دو پارامتر username و password باید به این endpoint ارسال شوند. در صورتی که حداقل یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {"error": "Invalid credentials"}

اگر نام کاربری یا رمز عبور نادرست باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {"error": "Invalid credentials"}

در غیر این‌صورت، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "refresh": "[REFRESH_TOKEN]",
      "access": "[ACCESS_TOKEN]",
    }
    JSON
خروج از حساب کاربری

تنها پارامتر refresh باید به این endpoint ارسال شود. در صورتی که این پارامتر ارسال نشده باشد یا برابر با رشته‌ی خالی باشد یا حتی مقدار درستی نداشته باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه: {}

در غیر این‌صورت، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 205
  • بدنه: {}
بخش بلاگ

برای این بخش نیاز است تا تعدادی 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/ لایک کردن بلاگ
POST /blogs/analytics/ اوضاع کلی حساب نویسنده

پیاده‌سازی endpoint های موردنیاز بخش کاربر🔗

در همه‌ی endpoint ها، پاسخ باید به‌صورت JSON باشد.

اطلاعات ورودی به‌صورت application/x-www-form-urlencoded به endpoint ها ارسال می‌شوند.

دریافت تمامی بلاگ‌های ثبت شده

این endpoint نیازمند authentication نیست.

نیازی به ارسال هیچ پارامتری به این endpoint نیست. و در همه حالات باید جواب برابر با مقدار زیر باشد.

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    [
      {
          "id": "[BLOG_ID]",
          "title": "[BLOG_TITLE]",
          "content": "[BLOG_CONTENT]",
          "views": "[BLOG_VIEWS]",
          "likes": "[BLOG_LIKES]",
      },
      ...
    ]
    JSON

بلاگ‌های بازگشت داده شده باید به ترتیب زمان ثبت بلاگ باشند.

ایجاد بلاگ جدید

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتر title و content باید به این endpoint ارسال شود. در صورتی که یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "title": ["This field may not be blank."],
          "content": ["This field may not be blank."],
          }
    }
    JSON

در غیر این‌صورت، بلاگ باید ثبت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 201
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
آپدیت بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتر title و content باید به این endpoint ارسال شود. در صورتی که یکی از این پارامترها ارسال نشده باشد یا برابر با رشته‌ی خالی باشد، پاسخ باید به‌صورت زیر باشد:

  • کد وضعیت: 400
  • بدنه:
    extensionFromNamejson
    {
      "error": {
          "title": ["This field may not be blank."],
          "content": ["This field may not be blank."],
          }
    }
    JSON

در صورتی که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در صورتی که یوزر در حال ارسال ریکوئست برابر با نویسنده بلاگ نباشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 403
  • بدنه: {}

در غیر این‌صورت، بلاگ باید آپدیت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
حذف بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتری به این endpoint ارسال نمی‌شود. در صورتی که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در صورتی که یوزر در حال ارسال ریکوئست برابر با نویسنده بلاگ نباشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 403
  • بدنه: {}

در غیر این‌صورت، بلاگ باید حذف شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 204
  • بدنه: {}
مشاهده بلاگ

این endpoint نیازمند authentication نیست.

نیازی به ارسال هیچ پارامتری به این endpoint نیست. و در همه حالات باید جواب برابر با مقادیر زیر باشد:

در صورتی که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در غیر این‌صورت، بلاگ باید ثبت شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON

توجه داشته باشید که در صورتی که بلاگ مورد نظر وجود داشت و کاربر بدون مشکل می‌توانست آن را مشاهده کند، باید به میزان view بلاگ یک واحد اضافه کنید و سپس بلاگ را برگردانید. یعنی بازدید فعلی کاربر در میزان بازدید‌های بلاگ باز گردانده شده باید محاسبه شده باشد.

نکته ریت لیمیت: در اینجا نیاز به پیاده‌سازی ساز و کاری برای پیاده‌سازی ریت لیمیت داریم. می‌خواهیم تا هر device_id و ip بتواند تنها 10 بار هر بلاگ را ببیند تا میزان ویو‌های هر بلاگ شهودی واقعی از میزان دیده شدن بلاگ بدهد. تا 10 ریکوئست برای گرفتن یک بلاگ از یک device_id و ip را بدون مشکل باز گردانید و میزان ویو بلاگ را هم اضافه کنید. به محض عبور از 10 نیاز است تا پاسخ برابر زیر باشد و به میزان ویو بلاگ هیچ عددی اضافه نشود.

  • کد وضعیت: 429
  • بدنه: {}
لایک کردن بلاگ

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

پارامتری به این endpoint ارسال نمی‌شود. در صورتی که pk موجود در URL در دیتابیس وجود نداشته باشد، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 404
  • بدنه: {}

در صورتی که توکن Authorization وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {}

در غیر این‌صورت، باید یک عدد به لایک‌های بلاگ اضافه شود و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "id": "[BLOG_ID]",
      "title": "[BLOG_TITLE]",
      "content": "[BLOG_CONTENT]",
      "views": "[BLOG_VIEWS]",
      "likes": "[BLOG_LIKES]",
    }
    JSON
اوضاع کلی حساب نویسنده

این endpoint نیازمند authentication است. در ریکوئست ارسالی مقدار هدر Authorization باید برابر با توکن کاربر باشد (توکن همواره همراه با Bearer ارسال می‌شود.).

در صورتی که توکن Authorization وجود نداشت و در اصل یوزر لاگین نبود، پاسخ باید به صورت زیر باشد:

  • کد وضعیت: 401
  • بدنه: {}

در غیر این‌صورت، باید مجموع تمام view و like های تمام بلاگ‌های نویسنده را محسابه کند و پاسخ به‌صورت زیر باشد:

  • کد وضعیت: 200
  • بدنه:
    extensionFromNamejson
    {
      "total_views": "int",
      "total_likes": "int"
    }
    JSON

نکات تکمیلی🔗

نحوه پیاده‌سازی کاملا بر عهده خودتان است اما برای دریافت نمره کامل مسئله نیاز است تا تمامی دیتاهای مسئله را persist کنید. برای این کار در سیستم داوری ما دیتابیس پستگرسی با مشخصات زیر بالاست:

دیتابیس postgres🔗

هاست: medium_postgres پورت: 5432 یوزرنیم: quera پسورد: quera دیتابیس: quera

نصب نیازمندی‌ها و اجرا

برای حل این سؤال در پوشه medium کد برنامه را نوشته و در فایلی به نام entry.sh که توسط sh اجرا می‌شود، باید برنامه‌ی خود را اجرا کنید. توجه کنید که ما در سیستم داوری اسکریپت entry.sh شما را اجرا می‌کنیم و پس از آن به آدرس localhost ریکوئست می‌زنیم.

برای نصب نیازمندی‌های نودجی‌اس از package.json استفاده کنید.

  • نیازی به persistent بودن داده‌ها نیست! اما برای دریافت امتیاز کامل مسئله باید دیتاها را persistent کنید.
  • شما مجاز به تغییر یا ارسال docker-compose.ymlو داکرفایل دلخواه نیستید.
  • سرویس شما باید روی پورت 80 آدرس localhost قابل دسترسی باشد.
  • توصیه می‌کنیم در entry.sh خود APIتان را روی 0.0.0.0:80 اجرا کنید.
  • فراموش نکنید که حتما فایل package.json را در دایرکتوری medium بسازید و تمامی نیازمندی‌های پروژه‌تان را درون آن بنویسید.

نحوه ارسال پاسخ🔗

شما می‌توانید تمامی محتوای موجود در پوشه‌ی medium را تغییر دهید و هر فایلی که می‌خواهید اضافه یا کم کنید.

medium
├──  ...  
├── package.json 
└── entry.sh
Plain text

توجه کنید که نام فایل کد شما برای سیستم داوری اهمیتی ندارد و این خود شما هستید که در entry.sh از نام آن برای اجرای پروژه استفاده می‌کنید.

در نهایت این پوشه را ZIP کرده و ارسال کنید. توجه کنید که پس از extract کردن فایل ZIP شما، باید پوشه‌ی medium را ببینیم که درون آن فایل‌های پروژه وجود دارد.

ارسال پاسخ برای این سؤال
در حال حاضر شما دسترسی ندارید.