text: #!/usr/local/bin/python3
  # coding: utf-8
  
  # YYeTsBot - handler.py
  # 6/16/21 20:30
  #
  
  __author__ = "Benny <benny.think@gmail.com>"
  
  import contextlib
  import importlib
  import json
  import logging
  import os
  import pathlib
  import re
  import sys
  import time
  import uuid
  from concurrent.futures import ThreadPoolExecutor
  from datetime import date, timedelta
  from hashlib import sha1
  from http import HTTPStatus
  
  import filetype
  import zhconv
  from tornado import escape, gen, web
  from tornado.concurrent import run_on_executor
  
  from database import CaptchaResource, Redis
  from utils import add_cf_blacklist
  
  escape.json_encode = lambda value: json.dumps(value, ensure_ascii=False)
  logging.basicConfig(level=logging.INFO)
  
  if getattr(sys, '_MEIPASS', None):
      adapter = "SQLite"
  else:
      adapter = "Mongo"
  
  logging.info("%s Running with %s. %s", "#" * 10, adapter, "#" * 10)
  
  index = pathlib.Path(__file__).parent.joinpath("templates", "index.html").as_posix()
  
  
  class SecurityHandler(web.RequestHandler):
      key = "user_blacklist"
  
      def __init__(self, application, request, **kwargs):
          super().__init__(application, request, **kwargs)
          self.r = Redis().r
  
      def prepare(self):
          if self.check_request():
              self.set_status(HTTPStatus.FORBIDDEN)
              self.finish()
  
      def data_received(self, chunk):
          pass
  
      def check_request(self):
          ban = self.__ip_check()
          user = self.__user_check()
          result = ban or user
          if result:
              self.ban()
          return result
  
      def get_real_ip(self):
          x_real = self.request.headers.get("X-Real-IP")
          remote_ip = self.request.remote_ip
          logging.debug("X-Real-IP:%s, Remote-IP:%s", x_real, remote_ip)
          return x_real or remote_ip
  
      def ban(self):
          ip = self.get_real_ip()
          self.r.incr(ip)
          count = int(self.r.get(ip))
          # ban rule: (count-10)*600
          if count <= 10:
              ex = 120
          else:
              ex = (count - 10) * 600
          if count >= 30:
              add_cf_blacklist(ip)
          self.r.set(ip, count, ex)
          user = self.get_current_user()
          if user:
              self.r.hincrby(self.key, user)
  
      def get_current_user(self) -> str:
          username = self.get_secure_cookie("username") or b""
          return username.decode("u8")
  
      def __user_check(self):
          count = self.r.hget(self.key, self.get_current_user()) or 0
          count = int(count)
          if count >= 20:
              return True
  
      def __ip_check(self):
          d = self.r.get(self.get_real_ip()) or 0
          if int(d) >= 10:
              return True
  
  
  class BaseHandler(SecurityHandler):
      executor = ThreadPoolExecutor(200)
      class_name = f"Fake{adapter}Resource"
      adapter_module = importlib.import_module(f"{adapter}")
  
      def __init__(self, application, request, **kwargs):
          super().__init__(application, request, **kwargs)
          self.json = {}
          with contextlib.suppress(ValueError):
              self.json: dict = json.loads(self.request.body)
          self.instance = getattr(self.adapter_module, self.class_name)()
  
      def write_error(self, status_code, **kwargs):
          if status_code in [HTTPStatus.FORBIDDEN,
                             HTTPStatus.UNAUTHORIZED,
                             HTTPStatus.NOT_FOUND,
                             HTTPStatus.INTERNAL_SERVER_ERROR,
                             ]:
              self.write(str(kwargs.get('exc_info')))
  
  
  class TopHandler(BaseHandler):
      class_name = f"Top{adapter}Resource"
  
      # from Mongo import TopMongoResource
      # instance = TopMongoResource()
  
      def get_user_like(self) -> list:
          username = self.get_current_user()
          return self.instance.get_user_like(username)
  
      def get_most(self) -> list:
          return self.instance.get_most()
  
      @run_on_executor()
      def get_top_resource(self):
          return self.instance.get_top_resource()
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_top_resource()
          self.write(resp)
  
  
  class IndexHandler(BaseHandler):
  
      @run_on_executor()
      def send_index(self):
          with open(index, encoding="u8") as f:
              html = f.read()
          return html
  
      @gen.coroutine
      def get(self):
          resp = yield self.send_index()
          self.write(resp)
  
  
  class UserHandler(BaseHandler):
      class_name = f"User{adapter}Resource"
  
      # from Mongo import UserMongoResource
      # instance = UserMongoResource()
  
      def set_login(self, username):
          self.set_secure_cookie("username", username, 365)
  
      @run_on_executor()
      def login_user(self):
          data = self.json
          username = data["username"]
          password = data["password"]
          captcha = data.get("captcha")
          captcha_id = data.get("captcha_id", "")
          ip = self.get_real_ip()
          browser = self.request.headers['user-agent']
  
          response = self.instance.login_user(username, password, captcha, captcha_id, ip, browser)
          if response["status_code"] in (HTTPStatus.CREATED, HTTPStatus.OK):
              self.set_login(username)
          else:
              self.set_status(response["status_code"])
  
          return response
  
      @run_on_executor()
      def update_info(self):
          result = self.instance.update_user_info(self.current_user, self.json)
          self.set_status(result.get("status_code", HTTPStatus.IM_A_TEAPOT))
          return result
  
      @run_on_executor()
      def get_user_info(self) -> dict:
          username = self.get_current_user()
          if username:
              data = self.instance.get_user_info(username)
          else:
              # self.set_status(HTTPStatus.UNAUTHORIZED)
              self.clear_cookie("username")
              data = {"message": "Please try to login"}
          return data
  
      @gen.coroutine
      def post(self):
          resp = yield self.login_user()
          self.write(resp)
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_user_info()
          self.write(resp)
  
          # everytime we receive a GET request to this api, we'll update last_date and last_ip
          username = self.get_current_user()
          if username:
              now_ip = self.get_real_ip()
              self.instance.update_user_last(username, now_ip)
  
      @gen.coroutine
      @web.authenticated
      def patch(self):
          resp = yield self.update_info()
          self.write(resp)
  
  
  class ResourceHandler(BaseHandler):
      class_name = f"Resource{adapter}Resource"
  
      # from Mongo import ResourceMongoResource
      # instance = ResourceMongoResource()
  
      @run_on_executor()
      def get_resource_data(self):
  
          resource_id = int(self.get_query_argument("id"))
          username = self.get_current_user()
          data = self.instance.get_resource_data(resource_id, username)
          if not data:
              self.ban()
              self.set_status(HTTPStatus.NOT_FOUND)
              data = {}
  
          return data
  
      @run_on_executor()
      def search_resource(self):
          kw = self.get_query_argument("keyword").lower()
          # convert any text to zh-hans
          kw = zhconv.convert(kw, "zh-hans")
          return self.instance.search_resource(kw)
  
      @gen.coroutine
      def get(self):
          if self.get_query_argument("id", None):
              resp = yield self.get_resource_data()
          elif self.get_query_argument("keyword", None):
              resp = yield self.search_resource()
          else:
              resp = "error"
          self.write(resp)
  
      # patch and post are available to every login user
      # these are rare operations, so no gen.coroutine and run_on_executor
      @web.authenticated
      def patch(self):
          if self.instance.is_admin(self.get_current_user()):
              # may consider add admin restrictions
              pass
          for item in self.json["items"].values():
              for i in item:
                  i["creator"] = self.get_current_user()
                  i["itemid"] = uuid.uuid4().hex
          self.instance.patch_resource(self.json)
          self.set_status(HTTPStatus.CREATED)
          self.write({})
  
      @web.authenticated
      def post(self):
          self.json["data"]["list"] = []
          self.json["data"]["info"]["creator"] = self.get_current_user()
          self.set_status(HTTPStatus.CREATED)
          resp = self.instance.add_resource(self.json)
          self.write(resp)
  
      @web.authenticated
      def delete(self):
          if not self.instance.is_admin(self.get_current_user()):
              self.set_status(HTTPStatus.FORBIDDEN)
              self.write({"status": False, "message": "admin only"})
              return
          self.instance.delete_resource(self.json)
          self.set_status(HTTPStatus.ACCEPTED)
          self.write({})
  
  
  class ResourceLatestHandler(BaseHandler):
      class_name = f"ResourceLatest{adapter}Resource"
  
      # from Mongo import ResourceLatestMongoResource
      # instance = ResourceLatestMongoResource()
      @run_on_executor()
      def get_latest(self):
          size = int(self.get_query_argument("size", "100"))
          result = self.instance.get_latest_resource()
          result["data"] = result["data"][:size]
          return result
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_latest()
          self.write(resp)
  
  
  #
  # class ResourceLatestHandler(BaseHandler):
  #     from concurrent.futures import ProcessPoolExecutor
  #
  #     class_name = f"ResourceLatest{adapter}Resource"
  #     executor = ProcessPoolExecutor(200)
  #
  #     # from Mongo import ResourceLatestMongoResource
  #     # instance = ResourceLatestMongoResource()
  #
  #     @gen.coroutine
  #     def get(self):
  #         # This returns a concurrent.futures.Future
  #         fut = self.executor.submit(self.instance.get_latest_resource)
  #         ret = yield fut
  #         self.write(ret)
  
  
  class LikeHandler(BaseHandler):
      class_name = f"Like{adapter}Resource"
  
      # from Mongo import LikeMongoResource
      # instance = UserLikeMongoResource()
  
      @run_on_executor()
      def like_data(self):
          username = self.get_current_user()
          return {"LIKE": self.instance.get_user_like(username)}
  
      @gen.coroutine
      @web.authenticated
      def get(self):
          resp = yield self.like_data()
          self.write(resp)
  
      @run_on_executor()
      def add_remove_fav(self):
          data = self.json
          resource_id = int(data["resource_id"])
          username = self.get_current_user()
          if username:
              response = self.instance.add_remove_fav(resource_id, username)
              self.set_status(response["status_code"])
          else:
              response = {"message": "请先登录"}
              self.set_status(HTTPStatus.UNAUTHORIZED)
  
          return response["message"]
  
      @gen.coroutine
      @web.authenticated
      def patch(self):
          resp = yield self.add_remove_fav()
          self.write(resp)
  
  
  class NameHandler(BaseHandler):
      class_name = f"Name{adapter}Resource"
  
      # from Mongo import NameMongoResource
      # instance = NameMongoResource()
  
      @run_on_executor()
      def get_names(self):
          is_readable = self.get_query_argument("human", None)
          return self.instance.get_names(is_readable)
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_names()
          self.write(resp)
  
  
  class CommentHandler(BaseHandler):
      class_name = f"Comment{adapter}Resource"
  
      # from Mongo import CommentMongoResource
      # instance = CommentMongoResource()
  
      @staticmethod
      def hide_phone(data: list):
          for item in data:
              if item["username"].isdigit() and len(item["username"]) == 11:
                  item["username"] = re.sub(r"(\d{3})\d{4}(\d{4})", r"\g<1>****\g<2>", item["username"])
          return data
  
      @run_on_executor()
      def get_comment(self):
          resource_id = int(self.get_argument("resource_id", "0"))
          size = int(self.get_argument("size", "5"))
          page = int(self.get_argument("page", "1"))
          inner_size = int(self.get_argument("inner_size", "15"))
          inner_page = int(self.get_argument("inner_page", "1"))
          comment_id = self.get_argument("comment_id", None)
  
          if not resource_id:
              self.set_status(HTTPStatus.BAD_REQUEST)
              return {"status": False, "message": "请提供resource id"}
          comment_data = self.instance.get_comment(
              resource_id, page, size,
              inner_size=inner_size, inner_page=inner_page,
              comment_id=comment_id
          )
          self.hide_phone((comment_data["data"]))
          return comment_data
  
      @run_on_executor()
      def add_comment(self):
          payload = self.json
          captcha = payload["captcha"]
          captcha_id = payload["id"]
          content = payload["content"]
          resource_id = payload["resource_id"]
          comment_id = payload.get("comment_id")
  
          real_ip = self.get_real_ip()
          username = self.get_current_user()
          browser = self.request.headers['user-agent']
  
          result = self.instance.add_comment(captcha, captcha_id, content, resource_id, real_ip,
                                             username, browser, comment_id)
          self.set_status(result["status_code"])
          return result
  
      @run_on_executor()
      def delete_comment(self):
          # need resource_id & id
          # payload = {"id":  "obj_id"}
          payload = self.json
          username = self.get_current_user()
          comment_id = payload["comment_id"]
  
          if self.instance.is_admin(username):
              result = self.instance.delete_comment(comment_id)
              self.set_status(result["status_code"])
              return result
          else:
              self.set_status(HTTPStatus.UNAUTHORIZED)
              return {"count": 0, "message": "You're unauthorized to delete comment."}
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_comment()
          self.write(resp)
  
      @gen.coroutine
      @web.authenticated
      def post(self):
          resp = yield self.add_comment()
          self.write(resp)
  
      @gen.coroutine
      @web.authenticated
      def delete(self):
          resp = yield self.delete_comment()
          self.write(resp)
  
  
  class CommentReactionHandler(BaseHandler):
      class_name = f"CommentReaction{adapter}Resource"
  
      # from Mongo import CommentReactionMongoResource
      # instance = CommentReactionMongoResource()
  
      @run_on_executor()
      def comment_reaction(self):
          self.json.update(method=self.request.method)
          username = self.get_current_user()
          result = self.instance.react_comment(username, self.json)
          self.set_status(result.get("status_code"))
          return result
  
      @gen.coroutine
      @web.authenticated
      def post(self):
          resp = yield self.comment_reaction()
          self.write(resp)
  
      @gen.coroutine
      @web.authenticated
      def delete(self):
          resp = yield self.comment_reaction()
          self.write(resp)
  
  
  class CommentChildHandler(CommentHandler):
      class_name = f"CommentChild{adapter}Resource"
  
      # from Mongo import CommentChildResource
      # instance = CommentChildResource()
  
      @run_on_executor()
      def get_comment(self):
          parent_id = self.get_argument("parent_id", "0")
          size = int(self.get_argument("size", "15"))
          page = int(self.get_argument("page", "1"))
  
          if not parent_id:
              self.set_status(HTTPStatus.BAD_REQUEST)
              return {"status": False, "message": "请提供 parent_id"}
          comment_data = self.instance.get_comment(parent_id, page, size)
          self.hide_phone((comment_data["data"]))
          return comment_data
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_comment()
          self.write(resp)
  
  
  class CommentNewestHandler(CommentHandler):
      class_name = f"CommentNewest{adapter}Resource"
  
      # from Mongo import CommentNewestResource
      # instance = CommentNewestResource()
  
      @run_on_executor()
      def get_comment(self):
          size = int(self.get_argument("size", "5"))
          page = int(self.get_argument("page", "1"))
  
          comment_data = self.instance.get_comment(page, size)
          self.hide_phone((comment_data["data"]))
          return comment_data
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_comment()
          self.write(resp)
  
  
  class CommentSearchHandler(CommentHandler):
      class_name = f"CommentSearch{adapter}Resource"
  
      # from Mongo import CommentNewestResource
      # instance = CommentNewestResource()
  
      @run_on_executor()
      def search_comment(self):
          size = int(self.get_argument("size", "5"))
          page = int(self.get_argument("page", "1"))
          keyword = self.get_argument("keyword", "")
          comment_data = self.instance.get_comment(page, size, keyword)
          self.hide_phone((comment_data["data"]))
          return comment_data
  
      @gen.coroutine
      def get(self):
          resp = yield self.search_comment()
          self.write(resp)
  
  
  class AnnouncementHandler(BaseHandler):
      class_name = f"Announcement{adapter}Resource"
  
      # from Mongo import AnnouncementMongoResource
      # instance = AnnouncementMongoResource()
  
      @run_on_executor()
      def get_announcement(self):
          size = int(self.get_argument("size", "5"))
          page = int(self.get_argument("page", "1"))
          return self.instance.get_announcement(page, size)
  
      @run_on_executor()
      def add_announcement(self):
          username = self.get_current_user()
          if not self.instance.is_admin(username):
              self.set_status(HTTPStatus.FORBIDDEN)
              return {"message": "只有管理员可以设置公告"}
  
          payload = self.json
          content = payload["content"]
          real_ip = self.get_real_ip()
          browser = self.request.headers['user-agent']
  
          self.instance.add_announcement(username, content, real_ip, browser)
          self.set_status(HTTPStatus.CREATED)
          return {"message": "添加成功"}
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_announcement()
          self.write(resp)
  
      @gen.coroutine
      @web.authenticated
      def post(self):
          resp = yield self.add_announcement()
          self.write(resp)
  
  
  class CaptchaHandler(BaseHandler, CaptchaResource):
  
      @run_on_executor()
      def verify_captcha(self):
          data = self.json
          captcha_id = data.get("id", None)
          userinput = data.get("captcha", None)
          if captcha_id is None or userinput is None:
              self.set_status(HTTPStatus.BAD_REQUEST)
              return "Please supply id or captcha parameter."
          returned = self.verify_code(userinput, captcha_id)
          status_code = returned.get("status")
          if not status_code:
              self.set_status(HTTPStatus.FORBIDDEN)
          return returned
  
      @run_on_executor()
      def captcha(self):
          request_id = self.get_argument("id", None)
          if request_id is None:
              self.set_status(HTTPStatus.BAD_REQUEST)
              return "Please supply id parameter."
  
          return self.get_captcha(request_id)
  
      @gen.coroutine
      def get(self):
          resp = yield self.captcha()
          self.write(resp)
  
      @gen.coroutine
      def post(self):
          resp = yield self.verify_captcha()
          self.write(resp)
  
  
  class MetricsHandler(BaseHandler):
      class_name = f"Metrics{adapter}Resource"
  
      # from Mongo import MetricsMongoResource
      # instance = MetricsMongoResource()
  
      @run_on_executor()
      def set_metrics(self):
          payload = self.json
          metrics_type = payload["type"]
  
          self.instance.set_metrics(metrics_type)
          self.set_status(HTTPStatus.CREATED)
          return {}
  
      @run_on_executor()
      def get_metrics(self):
          if not self.instance.is_admin(self.get_current_user()):
              self.set_status(HTTPStatus.NOT_FOUND)
              return ""
  
          # only return latest 7 days. with days parameter to generate different range
          from_date = self.get_query_argument("from", None)
          to_date = self.get_query_argument("to", None)
          if to_date is None:
              to_date = time.strftime("%Y-%m-%d", time.localtime())
          if from_date is None:
              from_date = time.strftime("%Y-%m-%d", time.localtime(time.time() - 3600 * 24 * 7))
  
          return self.instance.get_metrics(from_date, to_date)
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_metrics()
          self.write(resp)
  
      @gen.coroutine
      def post(self):
          resp = yield self.set_metrics()
          self.write(resp)
  
  
  class GrafanaIndexHandler(BaseHandler):
  
      def get(self):
          self.write({})
  
  
  class GrafanaSearchHandler(BaseHandler):
  
      def post(self):
          data = ["resource", "top", "home", "search", "extra", "discuss", "multiDownload", "download", "user", "share",
                  "me", "database", "help", "backOld", "favorite", "unFavorite", "comment"]
          self.write(json.dumps(data))
  
  
  class GrafanaQueryHandler(BaseHandler):
      class_name = f"GrafanaQuery{adapter}Resource"
  
      # from Mongo import GrafanaQueryMongoResource
      # instance = GrafanaQueryMongoResource()
  
      @staticmethod
      def generate_date_series(start: str, end: str) -> list:
          start_int = [int(i) for i in start.split("-")]
          end_int = [int(i) for i in end.split("-")]
          sdate = date(*start_int)  # start date
          edate = date(*end_int)  # end date
  
          delta = edate - sdate  # as timedelta
          days = []
          for i in range(delta.days + 1):
              day = sdate + timedelta(days=i)
              days.append(day.strftime("%Y-%m-%d"))
          return days
  
      @staticmethod
      def time_str_int(text):
          return time.mktime(time.strptime(text, "%Y-%m-%d"))
  
      def post(self):
          payload = self.json
          start = payload["range"]["from"].split("T")[0]
          end = payload["range"]["to"].split("T")[0]
          date_series = self.generate_date_series(start, end)
          targets = [i["target"] for i in payload["targets"] if i["target"]]
          grafana_data = []
          for target in targets:
              data_points = []
              result = self.instance.get_grafana_data(date_series)
              i: dict
              for i in result:
                  datum = [i[target], self.time_str_int(i["date"]) * 1000] if i.get(target) else []
                  data_points.append(datum)
              temp = {
                  "target": target,
                  "datapoints": data_points
              }
              grafana_data.append(temp)
          self.write(json.dumps(grafana_data))
  
  
  class BlacklistHandler(BaseHandler):
      class_name = f"Blacklist{adapter}Resource"
  
      # from Mongo import BlacklistMongoResource
      # instance = BlacklistMongoResource()
  
      @run_on_executor()
      def get_black_list(self):
          return self.instance.get_black_list()
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_black_list()
          self.write(resp)
  
  
  class NotFoundHandler(BaseHandler):
      def get(self):  # for react app
          # if self.request.uri not in ["/", "/home", "/discuss", "/login", "/404", "/search",
          #                             "/resource", "/me", "/database", "help", "/statistics"
          #                             ]:
          #     self.ban()
          self.render(index)
  
  
  class DBDumpHandler(BaseHandler):
  
      @staticmethod
      def sizeof_fmt(num: int, suffix='B'):
          for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
              if abs(num) < 1024.0:
                  return "%3.1f%s%s" % (num, unit, suffix)
              num /= 1024.0
          return "%.1f%s%s" % (num, 'Yi', suffix)
  
      @staticmethod
      def ts_date(ts):
          return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(ts))
  
      def file_info(self, file_path) -> dict:
          result = {}
          for fp in file_path:
              try:
                  checksum = self.checksum(fp)
                  creation = self.ts_date(os.stat(fp).st_ctime)
                  size = self.sizeof_fmt(os.stat(fp).st_size)
                  result[fp] = [checksum, creation, size]
              except Exception as e:
                  result[fp] = str(e), "", ""
          return result
  
      @staticmethod
      def checksum(file_path) -> str:
          sha = sha1()
          try:
              with open(file_path, "rb") as f:
                  sha.update(f.read())
                  checksum = sha.hexdigest()
          except Exception as e:
              checksum = str(e)
  
          return checksum
  
      @run_on_executor()
      @Redis.cache(3600)
      def get_hash(self):
          file_list = [
              "templates/dump/yyets_mongo.gz",
              "templates/dump/yyets_mysql.zip",
              "templates/dump/yyets_sqlite.zip"
          ]
          result = {}
          data = self.file_info(file_list)
          for file, value in data.items():
              filename = pathlib.Path(file).name
              result[filename] = {
                  "checksum": value[0],
                  "date": value[1],
                  "size": value[2],
              }
          print(result)
          return result
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_hash()
          self.write(resp)
  
  
  class DoubanHandler(BaseHandler):
      class_name = f"Douban{adapter}Resource"
  
      # from Mongo import DoubanMongoResource
      # instance = DoubanMongoResource()
  
      @run_on_executor()
      def douban_data(self):
          rid = self.get_query_argument("resource_id")
          data = self.instance.get_douban_data(int(rid))
          data.pop("posterData")
          return data
  
      def get_image(self) -> bytes:
          rid = self.get_query_argument("resource_id")
          return self.instance.get_douban_image(int(rid))
  
      @gen.coroutine
      def get(self):
          _type = self.get_query_argument("type", None)
          if _type == "image":
              data = self.get_image()
              self.set_header("content-type", filetype.guess_mime(data))
              self.write(data)
          else:
              resp = yield self.douban_data()
              self.write(resp)
  
  
  class DoubanReportHandler(BaseHandler):
      class_name = f"DoubanReport{adapter}Resource"
  
      # from Mongo import DoubanReportMongoResource
      # instance = DoubanReportMongoResource()
  
      @run_on_executor()
      def get_error(self):
          return self.instance.get_error()
  
      @run_on_executor()
      def report_error(self):
          data = self.json
          user_captcha = data["captcha_id"]
          captcha_id = data["id"]
          content = data["content"]
          resource_id = data["resource_id"]
          returned = self.instance.report_error(user_captcha, captcha_id, content, resource_id)
          status_code = returned.get("status_code", HTTPStatus.CREATED)
          self.set_status(status_code)
          return self.instance.report_error(user_captcha, captcha_id, content, resource_id)
  
      @gen.coroutine
      def post(self):
          resp = yield self.report_error()
          self.write(resp)
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_error()
          self.write(resp)
  
  
  class NotificationHandler(BaseHandler):
      class_name = f"Notification{adapter}Resource"
  
      # from Mongo import NotificationResource
      # instance = NotificationResource()
  
      @run_on_executor()
      def get_notification(self):
          username = self.get_current_user()
          size = int(self.get_argument("size", "5"))
          page = int(self.get_argument("page", "1"))
  
          return self.instance.get_notification(username, page, size)
  
      @run_on_executor()
      def update_notification(self):
          username = self.get_current_user()
          verb = self.json["verb"]
          comment_id = self.json["comment_id"]
          if verb not in ["read", "unread"]:
              self.set_status(HTTPStatus.BAD_REQUEST)
              return {"status": False, "message": "verb: read or unread"}
          self.set_status(HTTPStatus.CREATED)
          return self.instance.update_notification(username, verb, comment_id)
  
      @gen.coroutine
      @web.authenticated
      def get(self):
          resp = yield self.get_notification()
          self.write(resp)
  
      @gen.coroutine
      @web.authenticated
      def patch(self):
          resp = yield self.update_notification()
          self.write(resp)
  
  
  class UserEmailHandler(BaseHandler):
      class_name = f"UserEmail{adapter}Resource"
  
      # from Mongo import UserEmailResource
      # instance = UserEmailResource()
  
      @run_on_executor()
      def verify_email(self):
          result = self.instance.verify_email(self.get_current_user(), self.json["code"])
          self.set_status(result.get("status_code"))
          return result
  
      @gen.coroutine
      @web.authenticated
      def post(self):
          resp = yield self.verify_email()
          self.write(resp)
  
  
  class CategoryHandler(BaseHandler):
      class_name = f"Category{adapter}Resource"
  
      # from Mongo import CategoryResource
      # instance = CategoryResource()
  
      @run_on_executor()
      def get_data(self):
          self.json = {k: self.get_argument(k) for k in self.request.arguments}
          self.json["size"] = int(self.json.get("size", 15))
          self.json["page"] = int(self.json.get("page", 1))
          self.json["douban"] = self.json.get("douban", False)
          return self.instance.get_category(self.json)
  
      @gen.coroutine
      def get(self):
          resp = yield self.get_data()
          self.write(resp)
  
  
  class SpamProcessHandler(BaseHandler):
      class_name = f"SpamProcess{adapter}Resource"
  
      # from Mongo import SpamProcessMongoResource
      # instance = SpamProcessMongoResource()
  
      def process(self, method):
          obj_id = self.json.get("obj_id")
          token = self.json.get("token")
          ua = self.request.headers['user-agent']
          ip = self.get_real_ip()
          logging.info("Authentication %s(%s) for spam API now...", ua, ip)
          if token == os.getenv("TOKEN"):
              return getattr(self.instance, method)(obj_id)
          else:
              self.set_status(HTTPStatus.FORBIDDEN)
              return {"status": False, "message": "this token is not allowed to access this API"}
  
      @gen.coroutine
      def post(self):
          self.write(self.process("restore_spam"))
  
      @gen.coroutine
      def delete(self):
          self.write(self.process("delete_spam"))
  