Skip to content

Commit 5ae3d59

Browse files
authored
Merge pull request #2 from AlisProject/master
upd
2 parents b6ddb2c + 4afc73a commit 5ae3d59

File tree

19 files changed

+330
-28
lines changed

19 files changed

+330
-28
lines changed

api-template.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ Parameters:
123123
Type: 'AWS::SSM::Parameter::Value<String>'
124124
BurnAddress:
125125
Type: 'AWS::SSM::Parameter::Value<String>'
126+
BadgeOperationUrl:
127+
Type: 'AWS::SSM::Parameter::Value<String>'
128+
Web3ServiceBaseUrl:
129+
Type: 'AWS::SSM::Parameter::Value<String>'
126130

127131
Globals:
128132
Function:
@@ -172,6 +176,8 @@ Globals:
172176
PRIVATE_CHAIN_ALIS_TOKEN_ADDRESS: !Ref PrivateChainAlisTokenAddress
173177
PRIVATE_CHAIN_BRIDGE_ADDRESS: !Ref PrivateChainBridgeAddress
174178
BURN_ADDRESS: !Ref BurnAddress
179+
BADGE_OPERATION_URL: !Ref BadgeOperationUrl
180+
WEB3_SERVICE_BASE_URL: !Ref Web3ServiceBaseUrl
175181

176182
Resources:
177183
RestApi:

deploy.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ aws cloudformation deploy \
9696
AuthleteApiKey=${SSM_PARAMS_PREFIX}AuthleteApiKey \
9797
AuthleteApiSecret=${SSM_PARAMS_PREFIX}AuthleteApiSecret \
9898
BurnAddress=${SSM_PARAMS_PREFIX}BurnAddress \
99+
BadgeOperationUrl=${SSM_PARAMS_PREFIX}BadgeOperationUrl \
100+
Web3ServiceBaseUrl=${SSM_PARAMS_PREFIX}Web3ServiceBaseUrl \
99101
AllTokenHistoryCsvDownloadS3Bucket=${SSM_PARAMS_PREFIX}AllTokenHistoryCsvDownloadS3Bucket \
100102
CognitoIdentityPoolId=${SSM_PARAMS_PREFIX}CognitoIdentityPoolId\
101103
--capabilities CAPABILITY_IAM \

src/common/es_util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ def search_tag(elasticsearch, word, limit, page):
4040
return tags
4141

4242
@staticmethod
43-
def search_tags_count(elasticsearch, size=500, term=604800):
43+
def search_tags_count(elasticsearch, tags, size=500, term=604800):
4444
from_time = round(time.time()) - term
4545
body = {
4646
'size': 0,
4747
'query': {
4848
'bool': {
4949
'must': [
50-
{'range': {'published_at': {'gte': from_time}}}
50+
{'range': {'published_at': {'gte': from_time}}},
51+
{'terms': {'tags.keyword': tags}}
5152
]
5253
}
5354
},

src/common/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,7 @@ class PrivateChainApiError(Error):
124124

125125
class LimitExceeded(Error):
126126
pass
127+
128+
129+
class Web3ServiceApiError(Error):
130+
pass

src/common/settings.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@
382382

383383
TAG_DENIED_SYMBOL_PATTERN = '([!-,./:-@[-`{-~]|--| {2})'
384384
TAG_ALLOWED_SYMBOLS = ['-', ' ']
385-
385+
VIP_TAG_NAME = 'NFTオーナー'
386+
VIP_TAG_BADGE_TYPES = list(range(9, 109)) + [110]
386387

387388
YAHOO_API_WELL_KNOWN_URL = 'https://auth.login.yahoo.co.jp/yconnect/v2/.well-known/openid-configuration'
388389
YAHOO_API_PUBLIC_KEY_URL = 'https://auth.login.yahoo.co.jp/yconnect/v2/public-keys'

src/common/tag_util.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import re
22
import time
3-
43
import settings
54
from jsonschema import ValidationError
5+
from web3_util import Web3Util
66

77

88
class TagUtil:
@@ -91,7 +91,7 @@ def get_tags_with_name_collation(cls, elasticsearch, tag_names):
9191
return results
9292

9393
@staticmethod
94-
def validate_format(tags):
94+
def validate_tags(tags, user_id=None):
9595
pattern = re.compile(settings.TAG_DENIED_SYMBOL_PATTERN)
9696

9797
for tag in tags:
@@ -104,6 +104,12 @@ def validate_format(tags):
104104
if tag[0] == symbol or tag[-1] == symbol:
105105
raise ValidationError("tags don't support {str} with start and end of character".format(str=symbol))
106106

107+
# 対象バッジ取得者限定タグを利用していた場合、該当バッジを保持しているかを確認
108+
if settings.VIP_TAG_NAME in tags:
109+
user_types = Web3Util.get_badge_types(user_id)
110+
if len(set(settings.VIP_TAG_BADGE_TYPES) & set(user_types)) <= 0:
111+
raise ValidationError(f"Tag name {settings.VIP_TAG_NAME} is not available")
112+
107113
@classmethod
108114
def __get_item_case_insensitive(cls, elasticsearch, tag_name):
109115
body = {

src/common/web3_util.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import os
2+
import json
3+
import re
4+
import requests
5+
import asyncio
6+
from exceptions import Web3ServiceApiError
7+
from web3 import Web3
8+
9+
10+
class Web3Util:
11+
BADGE_CONTRACT_ABI = [
12+
{
13+
"constant": True,
14+
"inputs": [
15+
{
16+
"internalType": "address",
17+
"name": "owner",
18+
"type": "address"
19+
}
20+
],
21+
"name": "balanceOf",
22+
"outputs": [
23+
{
24+
"internalType": "uint256",
25+
"name": "",
26+
"type": "uint256"
27+
}
28+
],
29+
"payable": False,
30+
"stateMutability": "view",
31+
"type": "function"
32+
},
33+
{
34+
"constant": True,
35+
"inputs": [
36+
{
37+
"internalType": "address",
38+
"name": "owner",
39+
"type": "address"
40+
},
41+
{
42+
"internalType": "uint256",
43+
"name": "index",
44+
"type": "uint256"
45+
}
46+
],
47+
"name": "tokenOfOwnerByIndex",
48+
"outputs": [
49+
{
50+
"internalType": "uint256",
51+
"name": "",
52+
"type": "uint256"
53+
}
54+
],
55+
"payable": False,
56+
"stateMutability": "view",
57+
"type": "function"
58+
},
59+
{
60+
"constant": True,
61+
"inputs": [
62+
{
63+
"internalType": "uint256",
64+
"name": "tokenId",
65+
"type": "uint256"
66+
}
67+
],
68+
"name": "tokenURI",
69+
"outputs": [
70+
{
71+
"internalType": "string",
72+
"name": "",
73+
"type": "string"
74+
}
75+
],
76+
"payable": False,
77+
"stateMutability": "view",
78+
"type": "function"
79+
},
80+
{
81+
"constant": True,
82+
"inputs": [
83+
{
84+
"internalType": "uint256",
85+
"name": "",
86+
"type": "uint256"
87+
}
88+
],
89+
"name": "badgeTypeSupply",
90+
"outputs": [
91+
{
92+
"internalType": "uint256",
93+
"name": "",
94+
"type": "uint256"
95+
}
96+
],
97+
"payable": False,
98+
"stateMutability": "view",
99+
"type": "function"
100+
}
101+
]
102+
103+
@staticmethod
104+
def create_contract_object(operation_url, abi, contract_address):
105+
# web3の初期化
106+
provider = Web3.HTTPProvider(operation_url)
107+
web3 = Web3(provider)
108+
# コントラクトの作成
109+
return web3.eth.contract(web3.toChecksumAddress(contract_address), abi=abi)
110+
111+
@staticmethod
112+
def create_badge_contract_object():
113+
response = requests.get(os.environ['WEB3_SERVICE_BASE_URL'] + '/api/badge/eth_address')
114+
if response.status_code is not 200:
115+
raise Web3ServiceApiError(response.text)
116+
badge_address = json.loads(response.text)['badge_contract_address']
117+
return Web3Util.create_contract_object(os.environ['BADGE_OPERATION_URL'], Web3Util.BADGE_CONTRACT_ABI,
118+
badge_address)
119+
120+
@staticmethod
121+
def get_badge_types(user_id):
122+
badge_contract = Web3Util.create_badge_contract_object()
123+
response = requests.get(f"{os.environ['WEB3_SERVICE_BASE_URL']}/api/users/{user_id}/eth_address")
124+
if response.status_code is not 200:
125+
raise Web3ServiceApiError(response.text)
126+
# バッジ連携していない場合は空配列を返却
127+
badge_address = json.loads(response.text).get('public_chain_address')
128+
result = []
129+
if badge_address is None:
130+
return result
131+
# 該当ユーザの全バッジのtypeを取得
132+
# バッジの数を取得
133+
badge_len = badge_contract.functions.balanceOf(badge_address).call()
134+
# バッジの情報を並列に取得
135+
loop = asyncio.get_event_loop()
136+
coros = [Web3Util.__get_badge_type(badge_contract, badge_address, i, loop) for i in range(badge_len)]
137+
groups = asyncio.gather(*coros)
138+
results = loop.run_until_complete(groups)
139+
return list(set(results))
140+
141+
@staticmethod
142+
async def __get_badge_type(badge_contract, badge_address, badge_index, loop):
143+
# トークンIDを取得
144+
token_id = await loop.run_in_executor(None, badge_contract.functions.tokenOfOwnerByIndex(badge_address,
145+
badge_index).call)
146+
# トークンのメタデータURIを取得
147+
token_uri = await loop.run_in_executor(None, badge_contract.functions.tokenURI(token_id).call)
148+
type_match = re.match('.*/(\\d+)/metadata.json', token_uri)
149+
if type_match is not None:
150+
return int(type_match.group(1))
151+
return 0

src/handlers/me/articles/drafts/publish/me_articles_drafts_publish.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ def validate_params(self):
3737

3838
if self.params.get('tags'):
3939
ParameterUtil.validate_array_unique(self.params['tags'], 'tags', case_insensitive=True)
40-
TagUtil.validate_format(self.params['tags'])
40+
TagUtil.validate_tags(
41+
self.params['tags'],
42+
self.event['requestContext']['authorizer']['claims']['cognito:username']
43+
)
4144

4245
DBUtil.validate_article_existence(
4346
self.dynamodb,

src/handlers/me/articles/drafts/publish_with_header/me_articles_drafts_publish_with_header.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@ def validate_params(self):
4949

5050
if self.params.get('tags'):
5151
ParameterUtil.validate_array_unique(self.params['tags'], 'tags', case_insensitive=True)
52-
TagUtil.validate_format(self.params['tags'])
52+
TagUtil.validate_tags(
53+
self.params['tags'],
54+
self.event['requestContext']['authorizer']['claims']['cognito:username']
55+
)
5356

5457
DBUtil.validate_article_existence(
5558
self.dynamodb,

src/handlers/me/articles/public/republish/me_articles_public_republish.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ def validate_params(self):
3535

3636
if self.params.get('tags'):
3737
ParameterUtil.validate_array_unique(self.params['tags'], 'tags', case_insensitive=True)
38-
TagUtil.validate_format(self.params['tags'])
38+
TagUtil.validate_tags(
39+
self.params['tags'],
40+
self.event['requestContext']['authorizer']['claims']['cognito:username']
41+
)
3942

4043
DBUtil.validate_article_existence(
4144
self.dynamodb,

0 commit comments

Comments
 (0)