From aa59daba8fa033c54ae96f34491ce55df9843471 Mon Sep 17 00:00:00 2001 From: guyskk Date: Fri, 16 Sep 2022 17:40:54 +0800 Subject: [PATCH] feat: add ezrevenue integration --- rssant_api/urls.py | 8 ++-- rssant_api/views/ezrevenue.py | 28 +++++++++++++ rssant_api/views/user.py | 15 ++++--- rssant_common/ezrevenue.py | 76 +++++++++++++++++++++++++++++++++++ rssant_config/env.py | 10 ++++- 5 files changed, 126 insertions(+), 11 deletions(-) create mode 100644 rssant_api/views/ezrevenue.py create mode 100644 rssant_common/ezrevenue.py diff --git a/rssant_api/urls.py b/rssant_api/urls.py index 061bcd5..bf7c9de 100644 --- a/rssant_api/urls.py +++ b/rssant_api/urls.py @@ -1,10 +1,11 @@ -from django.urls import path, include +from django.urls import include, path -from .views import index, error +from .views import error, index +from .views.ezrevenue import EzrevenueView from .views.feed import FeedView +from .views.shopant import ShopantView from .views.story import StoryView from .views.user import UserView -from .views.shopant import ShopantView app_name = 'rssant_api' urlpatterns = [ @@ -14,4 +15,5 @@ urlpatterns = [ path('', include(StoryView.urls)), path('', include(UserView.urls)), path('', include(ShopantView.urls)), + path('', include(EzrevenueView.urls)), ] diff --git a/rssant_api/views/ezrevenue.py b/rssant_api/views/ezrevenue.py new file mode 100644 index 0000000..fa1feb7 --- /dev/null +++ b/rssant_api/views/ezrevenue.py @@ -0,0 +1,28 @@ +from django.contrib.auth.models import AbstractUser +from rest_framework.response import Response + +from django_rest_validr import RestRouter, T +from rssant_common.ezrevenue import EZREVENUE_CLIENT +from rssant_config import CONFIG + +EzrevenueView = RestRouter() + + +@EzrevenueView.post('ezrevenue/customer.info') +def ezrevenue_customer_info( + request, + include_balance: T.bool.default(True), +) -> T.dict: + if not EZREVENUE_CLIENT: + return Response(status=501) + user: AbstractUser = request.user + params = dict( + paywall_id=CONFIG.ezrevenue_paywall_id, + customer=dict( + external_id=user.id, + nickname=user.username, + external_dt_created=user.date_joined.isoformat(), + ), + include_balance=include_balance, + ) + return EZREVENUE_CLIENT.call('customer.info', params) diff --git a/rssant_api/views/user.py b/rssant_api/views/user.py index a3ff7f1..3a682fc 100644 --- a/rssant_api/views/user.py +++ b/rssant_api/views/user.py @@ -1,15 +1,14 @@ -from django_rest_validr import RestRouter, T +from allauth.socialaccount.models import SocialAccount from django.contrib import auth as django_auth from django.contrib.auth.models import User -from rest_framework.response import Response from rest_framework.authtoken.models import Token -from rest_framework.permissions import AllowAny from rest_framework.exceptions import AuthenticationFailed -from allauth.socialaccount.models import SocialAccount +from rest_framework.permissions import AllowAny +from rest_framework.response import Response -from rssant_config import CONFIG +from django_rest_validr import RestRouter, T from rssant_common.hashid import HASH_ID - +from rssant_config import CONFIG UserSchema = T.dict( id=T.str, @@ -22,6 +21,8 @@ UserSchema = T.dict( avatar_url=T.str.optional, )).optional, shopant_enable=T.bool.default(False), + ezrevenue_enable=T.bool.default(False), + ezrevenue_vip_equity_id=T.str.optional, image_proxy=T.dict( enable=T.bool, url_s=T.list(T.str).optional, @@ -55,6 +56,8 @@ def serialize_user(user): token=token.key, social_accounts=social_accounts_info, shopant_enable=CONFIG.shopant_enable, + ezrevenue_enable=CONFIG.ezrevenue_enable, + ezrevenue_vip_equity_id=CONFIG.ezrevenue_vip_equity_id, image_proxy=image_proxy ) diff --git a/rssant_common/ezrevenue.py b/rssant_common/ezrevenue.py new file mode 100644 index 0000000..d7e7795 --- /dev/null +++ b/rssant_common/ezrevenue.py @@ -0,0 +1,76 @@ +import logging +import secrets +import time + +import jwt +import requests + +from rssant_config import CONFIG + +LOG = logging.getLogger(__name__) + +_TOKEN_ALGORITHM = "HS256" +_TOKEN_FIELDS = ["exp", "nonce"] +_TOKEN_EXPIRES_IN = 30 * 60 + +_BASE_URL = 'https://revenue.ezfuns.com/api/v1/server' + + +class EzrevenueClient: + def __init__( + self, + *, + project_id: str, + project_secret: str, + base_url: str = None, + ) -> None: + self.project_id = project_id + self.project_secret = project_secret + self.base_url = base_url or _BASE_URL + + def decode_token(self, token: str) -> dict: + try: + payload = jwt.decode( + token, + key=self.project_secret, + options={'require': _TOKEN_FIELDS}, + algorithms=[_TOKEN_ALGORITHM], + ) + except jwt.InvalidTokenError as ex: + raise ex + return payload + + def encode_token(self, payload: dict) -> str: + exp = int(time.time()) + int(_TOKEN_EXPIRES_IN) + nonce = secrets.token_urlsafe(10) + payload = dict(payload, exp=exp, nonce=nonce) + token_bytes = jwt.encode( + payload, + key=self.project_secret, + algorithm=_TOKEN_ALGORITHM, + headers=dict(project_id=self.project_id), + ) + return token_bytes.decode('ascii') + + def call(self, api: str, params: dict): + payload = {"method": api, "params": params} + content = self.encode_token(payload) + url = self.base_url + '/' + api + response = requests.post(url, data=content) + try: + response.raise_for_status() + except requests.HTTPError: + err_msg = '%s failed status=%s, body=%s' + LOG.warning(err_msg, api, response.status_code, response.text) + raise + result = self.decode_token(response.text) + return result['result'] + + +EZREVENUE_CLIENT = None +if CONFIG.ezrevenue_enable: + EZREVENUE_CLIENT = EzrevenueClient( + project_id=CONFIG.ezrevenue_project_id, + project_secret=CONFIG.ezrevenue_project_secret, + base_url=CONFIG.ezrevenue_base_url, + ) diff --git a/rssant_config/env.py b/rssant_config/env.py index d9f1c34..6902d58 100644 --- a/rssant_config/env.py +++ b/rssant_config/env.py @@ -4,11 +4,10 @@ from functools import cached_property from urllib.parse import urlparse from dotenv import load_dotenv -from validr import T, Compiler, modelclass, fields, Invalid +from validr import Compiler, Invalid, T, fields, modelclass from rssant_common.network_helper import LOCAL_NODE_NAME - MAX_FEED_COUNT = 5000 @@ -97,6 +96,13 @@ class EnvConfig(ConfigModel): shopant_product_id: int = T.int.optional shopant_product_secret: str = T.str.optional shopant_url: str = T.url.optional + # ezrevenue + ezrevenue_enable: bool = T.bool.default(False) + ezrevenue_project_id: str = T.str.optional + ezrevenue_project_secret: str = T.str.optional + ezrevenue_paywall_id: str = T.str.optional + ezrevenue_vip_equity_id: str = T.str.optional + ezrevenue_base_url: str = T.url.optional # image proxy image_proxy_enable: bool = T.bool.default(True) image_proxy_urls: bool = T.str.default('origin').desc('逗号分隔的URL列表') -- Gitee