[Enhance] API for purchases, items, operations

- Add API endpoints for purchases, items, and operations
- Header checker? :)
This commit is contained in:
Lost-MSth
2023-02-08 18:18:04 +08:00
parent fbd5d83626
commit 6f39274b99
19 changed files with 538 additions and 76 deletions

View File

@@ -1,10 +1,11 @@
from flask import Blueprint from flask import Blueprint
from . import users from . import (users, songs, token, system, items, purchases)
from . import songs
from . import token
bp = Blueprint('api', __name__, url_prefix='/api/v1') bp = Blueprint('api', __name__, url_prefix='/api/v1')
bp.register_blueprint(users.bp) bp.register_blueprint(users.bp)
bp.register_blueprint(songs.bp) bp.register_blueprint(songs.bp)
bp.register_blueprint(token.bp) bp.register_blueprint(token.bp)
bp.register_blueprint(system.bp)
bp.register_blueprint(items.bp)
bp.register_blueprint(purchases.bp)

View File

@@ -1,15 +1,17 @@
from functools import wraps
from traceback import format_exc
from base64 import b64decode from base64 import b64decode
from functools import wraps
from json import loads from json import loads
from traceback import format_exc
from flask import current_app
from core.api_user import APIUser from core.api_user import APIUser
from core.config_manager import Config from core.config_manager import Config
from core.error import ArcError, NoAccess, PostError from core.error import ArcError, InputError, NoAccess, PostError
from core.sql import Connect from core.sql import Connect
from flask import current_app
from .api_code import error_return from .api_code import error_return
from .constant import Constant
def role_required(request, powers=[]): def role_required(request, powers=[]):
@@ -48,7 +50,7 @@ def role_required(request, powers=[]):
return decorator return decorator
def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False): def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False, is_batch: bool = False):
''' '''
提取post参数返回dict写成了修饰器\ 提取post参数返回dict写成了修饰器\
parameters: \ parameters: \
@@ -64,7 +66,7 @@ def request_json_handle(request, required_keys: list = [], optional_keys: list =
data = {} data = {}
if request.data: if request.data:
json_data = request.json json_data = request.get_json()
else: else:
if request.method == 'GET' and 'query' in request.args: if request.method == 'GET' and 'query' in request.args:
# 处理axios没法GET传data的问题 # 处理axios没法GET传data的问题
@@ -78,15 +80,24 @@ def request_json_handle(request, required_keys: list = [], optional_keys: list =
for key in required_keys: for key in required_keys:
if key not in json_data: if key not in json_data:
return error_return(PostError(f'Missing parameter: {key}', api_error_code=-100)) return error_return(InputError(f'Missing parameter: {key}', api_error_code=-100))
data[key] = json_data[key] data[key] = json_data[key]
for key in optional_keys: if is_batch:
if key in json_data: for key in Constant.PATCH_KEYS:
data[key] = json_data[key] if key in json_data:
data[key] = json_data[key]
if not isinstance(data[key], list):
return error_return(InputError(f'Parameter {key} must be a list', api_error_code=-100))
if not data:
return error_return(InputError('No change', api_error_code=-100))
else:
for key in optional_keys:
if key in json_data:
data[key] = json_data[key]
if must_change and not data: if must_change and not data:
return error_return(PostError('No change', api_error_code=-100)) return error_return(InputError('No change', api_error_code=-100))
return view(data, *args, **kwargs) return view(data, *args, **kwargs)

View File

@@ -18,6 +18,11 @@ CODE_MSG = {
-104: 'Invalid sort order parameter', -104: 'Invalid sort order parameter',
-105: 'Invalid URL parameter', -105: 'Invalid URL parameter',
-110: 'Invalid user_id', -110: 'Invalid user_id',
-120: 'Invalid item type',
-121: 'No such item',
-122: 'Item already exists',
-123: 'Purchase already has this item',
-124: 'Purchase does not have this item',
-200: 'No permission', # 2xx用户相关错误 -200: 'No permission', # 2xx用户相关错误
-201: 'Wrong username or password', -201: 'Wrong username or password',
-202: 'User is banned', -202: 'User is banned',

View File

@@ -1,2 +1,4 @@
class Constant: class Constant:
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort'] QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']
PATCH_KEYS = ['create', 'update', 'remove']

115
latest version/api/items.py Normal file
View File

@@ -0,0 +1,115 @@
from flask import Blueprint, request
from core.error import DataExist, InputError, NoData
from core.item import Item, ItemFactory
from core.sql import Connect, Query, Sql
from .api_auth import api_try, request_json_handle, role_required
from .api_code import success_return
from .constant import Constant
bp = Blueprint('items', __name__, url_prefix='/items')
@bp.route('', methods=['GET'])
@role_required(request, ['select'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
@api_try
def items_get(data, user):
'''查询全物品信息'''
with Connect() as c:
query = Query(['item_id', 'type'], ['item_id'],
['item_id']).from_dict(data)
x = Sql(c).select('item', query=query)
r: 'list[Item]' = []
for i in x:
r.append(ItemFactory.from_dict({
'item_id': i[0],
'type': i[1],
'is_available': i[2] == 1
}))
if not r:
raise NoData(api_error_code=-2)
return success_return([x.to_dict(has_is_available=True, has_amount=False) for x in r])
ALLOW_ITEM_TYPE = ['pack', 'single', 'world_song', 'character']
@bp.route('', methods=['POST'])
@role_required(request, ['change'])
@request_json_handle(request, required_keys=['item_id', 'type'], optional_keys=['is_available'])
@api_try
def items_post(data, user):
'''新增物品'''
if data['type'] not in ALLOW_ITEM_TYPE:
raise InputError(
f'Invalid item type: `{data["type"]}`', api_error_code=-120)
with Connect() as c:
item = ItemFactory.from_dict(data, c=c)
if item.select_exists():
raise DataExist(
f'Item `{item.item_type}`: `{item.item_id}` already exists', api_error_code=-122)
item.insert()
return success_return(item.to_dict(has_is_available=True, has_amount=False))
@bp.route('/<string:item_type>/<string:item_id>', methods=['DELETE'])
@role_required(request, ['change'])
@api_try
def items_item_delete(user, item_type, item_id):
'''删除物品'''
if item_type not in ALLOW_ITEM_TYPE:
raise InputError(
f'Invalid item type: `{item_type}`', api_error_code=-120)
with Connect() as c:
item = ItemFactory.from_dict({
'item_id': item_id,
'type': item_type
}, c=c)
if not item.select_exists():
raise NoData(
f'No such item `{item_type}`: `{item_id}`', api_error_code=-121)
item.delete()
return success_return()
@bp.route('/<string:item_type>/<string:item_id>', methods=['PUT'])
@role_required(request, ['change'])
@request_json_handle(request, optional_keys=['is_available'], must_change=True)
@api_try
def items_item_put(data, user, item_type, item_id):
'''修改物品'''
if item_type not in ALLOW_ITEM_TYPE:
raise InputError(
f'Invalid item type: `{item_type}`', api_error_code=-120)
if not isinstance(data['is_available'], bool):
raise InputError('`is_available` must be a boolean',
api_error_code=-101)
with Connect() as c:
item = ItemFactory.from_dict({
'item_id': item_id,
'type': item_type,
'is_available': data['is_available']
}, c=c)
if not item.select_exists():
raise NoData(
f'No such item `{item_type}`: `{item_id}`', api_error_code=-121)
item.update()
return success_return(item.to_dict(has_is_available=True, has_amount=False))
@bp.route('/<string:item_type>/<string:item_id>', methods=['GET'])
@role_required(request, ['select'])
@api_try
def items_item_get(user, item_type, item_id):
'''查询单个物品信息'''
with Connect() as c:
item = ItemFactory.from_dict({
'item_id': item_id,
'type': item_type
}, c=c)
item.select()
return success_return(item.to_dict(has_is_available=True, has_amount=False))

View File

@@ -0,0 +1,164 @@
from flask import Blueprint, request
from core.error import DataExist, InputError, NoData
from core.item import ItemFactory
from core.purchase import Purchase
from core.sql import Connect, Query, Sql
from .api_auth import api_try, request_json_handle, role_required
from .api_code import success_return
from .constant import Constant
bp = Blueprint('purchases', __name__, url_prefix='/purchases')
@bp.route('', methods=['GET'])
@role_required(request, ['select'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
@api_try
def purchases_get(data, user):
'''查询全购买信息'''
with Connect() as c:
query = Query(['purchase_name', 'discount_reason'], ['purchase_name'], [
'price', 'orig_price', 'discount_from', 'discount_to']).from_dict(data)
x = Sql(c).select('purchase', query=query)
r = [Purchase().from_list(i) for i in x]
if not r:
raise NoData(api_error_code=-2)
return success_return([x.to_dict(has_items=False, show_real_price=False) for x in r])
@bp.route('', methods=['POST'])
@role_required(request, ['change'])
@request_json_handle(request, required_keys=['purchase_name', 'orig_price'], optional_keys=['price', 'discount_from', 'discount_to', 'discount_reason', 'items'])
@api_try
def purchases_post(data, user):
'''新增购买注意可以有items不存在的item会自动创建'''
with Connect() as c:
purchase = Purchase(c).from_dict(data)
if purchase.select_exists():
raise DataExist(
f'Purchase `{purchase.purchase_name}` already exists')
purchase.insert_all()
return success_return(purchase.to_dict(has_items='items' in data, show_real_price=False))
@bp.route('/<string:purchase_name>', methods=['GET'])
@role_required(request, ['select'])
@api_try
def purchases_purchase_get(user, purchase_name: str):
'''查询单个购买信息'''
with Connect() as c:
return success_return(Purchase(c).select(purchase_name).to_dict(show_real_price=False))
@bp.route('/<string:purchase_name>', methods=['DELETE'])
@role_required(request, ['change'])
@api_try
def purchases_purchase_delete(user, purchase_name: str):
'''删除单个购买信息会连带删除purchase_item'''
with Connect() as c:
purchase = Purchase(c).select(purchase_name)
purchase.delete_all()
return success_return()
@bp.route('/<string:purchase_name>', methods=['PUT'])
@role_required(request, ['change'])
@request_json_handle(request, optional_keys=['price', 'orig_price', 'discount_from', 'discount_to', 'discount_reason'], must_change=True)
@api_try
def purchases_purchase_put(data, user, purchase_name: str):
'''修改单个购买信息注意不能有items'''
with Connect() as c:
purchase = Purchase(c).select(purchase_name)
t = ['price', 'orig_price', 'discount_from', 'discount_to']
for i in t:
if i in data:
setattr(purchase, i, int(data[i]))
if 'discount_reason' in data:
purchase.discount_reason = str(data['discount_reason'])
purchase.update()
return success_return(purchase.to_dict(has_items=False, show_real_price=False))
@bp.route('/<string:purchase_name>/items', methods=['GET'])
@role_required(request, ['select'])
@api_try
def purchases_purchase_items_get(user, purchase_name: str):
'''查询单个购买的所有items'''
with Connect() as c:
p = Purchase(c)
p.purchase_name = purchase_name
p.select_items()
return success_return([x.to_dict(has_is_available=True) for x in p.items])
# @bp.route('/<string:purchase_name>/items', methods=['POST'])
# @role_required(request, ['change'])
# @request_json_handle(request, required_keys=['item_id', 'type'], optional_keys=['amount'])
# @api_try
# def purchases_purchase_items_post(data, user, purchase_name: str):
# '''新增单个购买的批量items'''
# with Connect() as c:
# p = Purchase(c)
# p.purchase_name = purchase_name
# p.select_items()
# p.add_items([ItemFactory().from_dict(data)])
# return success_return([x.to_dict(has_is_available=True) for x in p.items])
@bp.route('/<string:purchase_name>/items', methods=['PATCH'])
@role_required(request, ['change'])
@request_json_handle(request, is_batch=True)
@api_try
def purchases_purchase_items_patch(data, user, purchase_name: str):
'''增删改单个购买的批量items'''
with Connect() as c:
p = Purchase(c)
p.purchase_name = purchase_name
p.select_items()
p.delete_items([ItemFactory.from_dict(x, c=c)
for x in data.get('remove', [])])
p.add_items([ItemFactory.from_dict(x, c=c)
for x in data.get('create', [])])
updates = data.get('update', [])
for x in updates:
if 'amount' not in x:
raise InputError('`amount` is required in `update`')
if not isinstance(x['amount'], int) or x['amount'] <= 0:
raise InputError(
'`amount` must be a positive integer', api_error_code=-101)
p.update_items([ItemFactory.from_dict(x, c=c) for x in updates])
return success_return([x.to_dict(has_is_available=True) for x in p.items])
# @bp.route('/<string:purchase_name>/items/<string:item_type>/<string:item_id>', methods=['DELETE'])
# @role_required(request, ['change'])
# @api_try
# def purchases_purchase_items_item_delete(user, purchase_name: str, item_type: str, item_id: str):
# '''删除单个购买的单个item'''
# with Connect() as c:
# p = Purchase(c)
# p.purchase_name = purchase_name
# p.select_items()
# p.delete_items([ItemFactory().from_dict(
# {'item_type': item_type, 'item_id': item_id})])
# return success_return()
# @bp.route('/<string:purchase_name>/items/<string:item_type>/<string:item_id>', methods=['PUT'])
# @role_required(request, ['change'])
# @request_json_handle(request, optional_keys=['amount', 'is_available'], must_change=True)
# @api_try
# def purchases_purchase_items_item_put(data, user, purchase_name: str, item_type: str, item_id: str):
# '''修改单个购买的单个item'''
# with Connect() as c:
# p = Purchase(c)
# p.purchase_name = purchase_name
# pass
# return success_return()

View File

@@ -0,0 +1,32 @@
from flask import Blueprint, request
from core.error import ArcError
from core.operation import BaseOperation
from .api_auth import api_try, role_required
from .api_code import success_return
bp = Blueprint('system', __name__, url_prefix='/system')
operation_dict = {i._name: i for i in BaseOperation.__subclasses__()}
@bp.route('/operations', methods=['GET'])
@role_required(request, ['system'])
@api_try
def operations_get(user):
return success_return(list(operation_dict.keys()))
@bp.route('/operations/<string:operation_name>', methods=['POST'])
@role_required(request, ['system'])
@api_try
def operations_operation_post(user, operation_name: str):
if operation_name not in operation_dict:
raise ArcError(
f'No such operation: `{operation_name}`', api_error_code=-1, status=404)
x = operation_dict[operation_name]()
x.set_params(**request.get_json())
x.run()
return success_return()

View File

@@ -1,9 +1,10 @@
from base64 import b64decode from base64 import b64decode
from flask import Blueprint, request
from core.api_user import APIUser from core.api_user import APIUser
from core.error import PostError from core.error import PostError
from core.sql import Connect from core.sql import Connect
from flask import Blueprint, request
from .api_auth import api_try, request_json_handle, role_required from .api_auth import api_try, request_json_handle, role_required
from .api_code import success_return from .api_code import success_return

View File

@@ -1,9 +1,10 @@
from flask import Blueprint, request
from core.api_user import APIUser
from core.error import InputError, NoAccess, NoData from core.error import InputError, NoAccess, NoData
from core.score import Potential, UserScoreList from core.score import Potential, UserScoreList
from core.sql import Connect, Query, Sql from core.sql import Connect, Query, Sql
from core.user import UserChanger, UserInfo, UserRegister from core.user import UserChanger, UserInfo, UserRegister
from core.api_user import APIUser
from flask import Blueprint, request
from .api_auth import api_try, request_json_handle, role_required from .api_auth import api_try, request_json_handle, role_required
from .api_code import error_return, success_return from .api_code import error_return, success_return

View File

@@ -97,12 +97,6 @@ class DatabaseInit:
with open(self.single_path, 'rb') as f: with open(self.single_path, 'rb') as f:
self.insert_purchase_item(load(f)) self.insert_purchase_item(load(f))
self.c.execute(
'''select exists(select * from item where item_id='epilogue')''')
if self.c.fetchone() == (0,):
self.c.execute(
'''insert into item values('epilogue','pack',1)''')
def course_init(self) -> None: def course_init(self) -> None:
'''初始化课题信息''' '''初始化课题信息'''
courses = [] courses = []

View File

@@ -5,10 +5,14 @@ from .error import InputError, ItemNotEnough, ItemUnavailable, NoData
class Item: class Item:
item_type = None item_type = None
def __init__(self) -> None: def __init__(self, c=None) -> None:
self.item_id = None self.item_id = None
self.__amount = None self.__amount = None
self.is_available = None self.is_available = None
self.c = c
def __eq__(self, other: 'Item') -> bool:
return self.item_id == other.item_id and self.item_type == other.item_type
@property @property
def amount(self): def amount(self):
@@ -18,12 +22,13 @@ class Item:
def amount(self, value: int): def amount(self, value: int):
self.__amount = int(value) self.__amount = int(value)
def to_dict(self, has_is_available: bool = False) -> dict: def to_dict(self, has_is_available: bool = False, has_amount: bool = True) -> dict:
r = { r = {
'id': self.item_id, 'id': self.item_id,
'amount': self.amount,
'type': self.item_type 'type': self.item_type
} }
if has_amount:
r['amount'] = self.amount
if has_is_available: if has_is_available:
r['is_available'] = self.is_available r['is_available'] = self.is_available
return r return r
@@ -32,6 +37,32 @@ class Item:
# parameter: user - User类或子类的实例 # parameter: user - User类或子类的实例
pass pass
def select_exists(self):
self.c.execute('''select exists(select * from item where item_id=? and type=?)''',
(self.item_id, self.item_type))
return bool(self.c.fetchone()[0])
def insert(self):
self.c.execute('''insert into item values(?,?,?)''',
(self.item_id, self.item_type, self.is_available))
def delete(self):
self.c.execute('''delete from item where item_id=? and type=?''',
(self.item_id, self.item_type))
def update(self):
self.c.execute('''update item set is_available=? where item_id=? and type=?''',
(self.is_available, self.item_id, self.item_type))
def select(self):
self.c.execute('''select is_available from item where item_id=? and type=?''',
(self.item_id, self.item_type))
x = self.c.fetchone()
if not x:
raise NoData(
f'No such item `{self.item_type}`: `{self.item_id}`', api_error_code=-121)
self.is_available = x[0]
class UserItem(Item): class UserItem(Item):
@@ -40,7 +71,7 @@ class UserItem(Item):
self.c = c self.c = c
self.user = None self.user = None
def select(self, user=None): def select_user_item(self, user=None):
''' '''
查询用户item\ 查询用户item\
parameter: `user` - `User`类或子类的实例 parameter: `user` - `User`类或子类的实例

View File

@@ -1,4 +1,5 @@
from .download import DownloadList from .download import DownloadList
from .error import NoData
from .save import SaveData from .save import SaveData
from .score import Score from .score import Score
from .sql import Connect, Sql from .sql import Connect, Sql
@@ -214,6 +215,11 @@ class UnlockUserItem(BaseOperation):
def _one_user_insert(self): def _one_user_insert(self):
with Connect() as c: with Connect() as c:
c.execute(
'''select exists(select * from user where user_id = ?)''', (self.user.user_id,))
if not c.fetchone()[0]:
raise NoData(
f'No such user: `{self.user.user_id}`', api_error_code=-110)
c.execute( c.execute(
f'''select item_id, type from item where type in ({','.join(['?'] * len(self.item_types))})''', self.item_types) f'''select item_id, type from item where type in ({','.join(['?'] * len(self.item_types))})''', self.item_types)
sql_list = [(self.user.user_id, i[0], i[1]) sql_list = [(self.user.user_id, i[0], i[1])

View File

@@ -1,6 +1,6 @@
from time import time from time import time
from .error import NoData, TicketNotEnough from .error import DataExist, InputError, NoData, TicketNotEnough
from .item import ItemFactory from .item import ItemFactory
@@ -34,31 +34,35 @@ class Purchase:
if self.discount_reason == 'anni5tix': if self.discount_reason == 'anni5tix':
x = ItemFactory(self.c).get_item('anni5tix') x = ItemFactory(self.c).get_item('anni5tix')
x.item_id = 'anni5tix' x.item_id = 'anni5tix'
x.select(self.user) x.select_user_item(self.user)
if x.amount >= 1: if x.amount >= 1:
return 0 return 0
return self.price return self.price
return self.orig_price return self.orig_price
def to_dict(self) -> dict: def to_dict(self, has_items: bool = True, show_real_price: bool = True) -> dict:
price = self.price_displayed if show_real_price:
price = self.price_displayed
r = { r = {
'name': self.purchase_name, 'name': self.purchase_name,
'price': price, 'price': price if show_real_price else self.price,
'orig_price': self.orig_price, 'orig_price': self.orig_price,
'items': [x.to_dict(has_is_available=True) for x in self.items]
} }
if has_items:
r['items'] = [x.to_dict(has_is_available=True) for x in self.items]
if self.discount_from > 0 and self.discount_to > 0: if self.discount_from > 0 and self.discount_to > 0:
r['discount_from'] = self.discount_from r['discount_from'] = self.discount_from
r['discount_to'] = self.discount_to r['discount_to'] = self.discount_to
if self.discount_reason == 'anni5tix' and price == 0: if not show_real_price or (self.discount_reason == 'anni5tix' and price == 0):
r['discount_reason'] = self.discount_reason r['discount_reason'] = self.discount_reason
return r return r
def from_dict(self, d: dict) -> 'Purchase': def from_dict(self, d: dict) -> 'Purchase':
self.purchase_name = d['name'] self.purchase_name = d.get('name') or d.get('purchase_name')
self.price = d['price'] if not self.purchase_name:
self.orig_price = d['orig_price'] raise InputError('purchase_name is required')
self.orig_price = int(d['orig_price'])
self.price = d.get('price', self.orig_price)
self.discount_from = d.get('discount_from', -1) self.discount_from = d.get('discount_from', -1)
self.discount_to = d.get('discount_to', -1) self.discount_to = d.get('discount_to', -1)
self.discount_reason = d.get('discount_reason', '') self.discount_reason = d.get('discount_reason', '')
@@ -67,24 +71,50 @@ class Purchase:
return self return self
def from_list(self, l: list) -> 'Purchase':
self.purchase_name = l[0]
self.price = l[1]
self.orig_price = l[2]
self.discount_from = l[3] if l[3] else -1
self.discount_to = l[4] if l[4] else -1
self.discount_reason = l[5] if l[5] else ''
return self
def insert_all(self) -> None: def insert_all(self) -> None:
'''向数据库插入包括item表和purchase_item表''' '''向数据库插入包括item表和purchase_item表'''
self.insert()
self.insert_items()
def insert(self) -> None:
'''向数据库插入不包括item表和purchase_item表'''
self.c.execute('''insert into purchase values(?,?,?,?,?,?)''', self.c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(self.purchase_name, self.price, self.orig_price, self.discount_from, self.discount_to, self.discount_reason)) (self.purchase_name, self.price, self.orig_price, self.discount_from, self.discount_to, self.discount_reason))
self.insert_items()
# def insert_only_purchase_item(self) -> None:
# '''向数据库插入purchase_item表'''
# for i in self.items:
# self.c.execute('''insert into purchase_item values(?,?,?,?)''',
# (self.purchase_name, i.item_id, i.item_type, i.amount))
def insert_items(self) -> None: def insert_items(self) -> None:
'''向数据库插入物品,注意已存在的物品不会变更''' '''向数据库插入物品,注意已存在的物品不会变更'''
for i in self.items: for i in self.items:
self.c.execute( self.c.execute('''insert or ignore into item values(?,?,?)''',
'''select exists(select * from item where item_id=?)''', (i.item_id,)) (i.item_id, i.item_type, i.is_available))
if self.c.fetchone() == (0,):
self.c.execute('''insert into item values(?,?,?)''',
(i.item_id, i.item_type, i.is_available))
self.c.execute('''insert into purchase_item values(?,?,?,?)''', self.c.execute('''insert or ignore into purchase_item values(?,?,?,?)''',
(self.purchase_name, i.item_id, i.item_type, i.amount)) (self.purchase_name, i.item_id, i.item_type, i.amount))
def select_exists(self, purchase_name: str = None) -> bool:
'''
用purchase_name查询存在性
'''
if purchase_name:
self.purchase_name = purchase_name
self.c.execute(
'''select exists(select * from purchase where purchase_name=?)''', (self.purchase_name,))
return self.c.fetchone() == (1,)
def select(self, purchase_name: str = None) -> 'Purchase': def select(self, purchase_name: str = None) -> 'Purchase':
''' '''
用purchase_name查询信息 用purchase_name查询信息
@@ -93,11 +123,11 @@ class Purchase:
self.purchase_name = purchase_name self.purchase_name = purchase_name
self.c.execute( self.c.execute(
'''select * from purchase where purchase_name=:name''', {'name': purchase_name}) '''select * from purchase where purchase_name=:name''', {'name': self.purchase_name})
x = self.c.fetchone() x = self.c.fetchone()
if not x: if not x:
raise NoData('The purchase `%s` does not exist.' % raise NoData(
purchase_name, 501) f'Purchase `{self.purchase_name}` does not exist.', 501)
self.price = x[1] self.price = x[1]
self.orig_price = x[2] self.orig_price = x[2]
@@ -112,9 +142,9 @@ class Purchase:
self.c.execute( self.c.execute(
'''select item_id, type, amount from purchase_item where purchase_name=:a''', {'a': self.purchase_name}) '''select item_id, type, amount from purchase_item where purchase_name=:a''', {'a': self.purchase_name})
x = self.c.fetchall() x = self.c.fetchall()
if not x: # if not x:
raise NoData('The items of the purchase `%s` does not exist.' % # raise NoData(
self.purchase_name, 501) # f'The items of the purchase `{self.purchase_name}` does not exist.', 501)
self.items = [] self.items = []
t = None t = None
@@ -162,6 +192,61 @@ class Purchase:
for i in self.items: for i in self.items:
i.user_claim_item(self.user) i.user_claim_item(self.user)
def delete_purchase_item(self) -> None:
'''删除purchase_item表'''
self.c.execute(
'''delete from purchase_item where purchase_name=?''', (self.purchase_name, ))
def delete(self) -> None:
'''删除purchase表'''
self.c.execute(
'''delete from purchase where purchase_name=?''', (self.purchase_name, ))
def delete_all(self) -> None:
'''删除purchase表和purchase_item表'''
self.delete_purchase_item()
self.delete()
def update(self) -> None:
'''更新purchase表'''
self.c.execute('''update purchase set price=:a, orig_price=:b, discount_from=:c, discount_to=:d, discount_reason=:e where purchase_name=:f''', {
'a': self.price, 'b': self.orig_price, 'c': self.discount_from, 'd': self.discount_to, 'e': self.discount_reason, 'f': self.purchase_name})
def add_items(self, items: list) -> None:
'''添加purchase_item表'''
for i in items:
if not i.select_exists():
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
if i in self.items:
raise DataExist(
f'Item `{i.item_type}`: `{i.item_id}` already exists in purchase `{self.purchase_name}`', api_error_code=-123)
self.c.executemany('''insert into purchase_item values (?, ?, ?, ?)''', [
(self.purchase_name, i.item_id, i.item_type, i.amount) for i in items])
self.items.extend(items)
def delete_items(self, items: list) -> None:
'''删除purchase_item表'''
for i in items:
if i not in self.items:
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124)
self.c.executemany('''delete from purchase_item where purchase_name=? and item_id=? and type=?''', [
(self.purchase_name, i.item_id, i.item_type) for i in items])
for i in items:
self.items.remove(i)
def update_items(self, items: list) -> None:
'''更新purchase_item表只能更新amount'''
for i in items:
if i not in self.items:
raise NoData(
f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124)
self.c.executemany('''update purchase_item set amount=? where purchase_name=? and item_id=? and type=?''', [
(i.amount, self.purchase_name, i.item_id, i.item_type) for i in items])
for i in items:
self.items[self.items.index(i)].amount = i.amount
class PurchaseList: class PurchaseList:
''' '''

View File

@@ -381,13 +381,6 @@ class DatabaseMigrator:
c.executemany('''insert into user_char_full values(?,?,?,?,?,?)''', [ c.executemany('''insert into user_char_full values(?,?,?,?,?,?)''', [
(j[0], i[0], i[1], exp, i[2], 0) for j in y]) (j[0], i[0], i[1], exp, i[2], 0) for j in y])
@staticmethod
def update_user_epilogue(c) -> None:
'''给用户添加epilogue包'''
c.execute('''select user_id from user''')
Sql(c).insert_many('user_item', [], [(i[0], 'epilogue', 'pack', 1)
for i in c.fetchall()], insert_type='ignore')
def update_database(self) -> None: def update_database(self) -> None:
''' '''
将c1数据库不存在数据加入或覆盖到c2数据库上 将c1数据库不存在数据加入或覆盖到c2数据库上
@@ -402,7 +395,6 @@ class DatabaseMigrator:
self.update_one_table(c1, c2, 'character') self.update_one_table(c1, c2, 'character')
self.update_user_char_full(c2) # 更新user_char_full self.update_user_char_full(c2) # 更新user_char_full
self.update_user_epilogue(c2) # 更新user的epilogue
class MemoryDatabase: class MemoryDatabase:

View File

@@ -301,7 +301,7 @@ class UserMap(Map):
if self.require_type in ['pack', 'single']: if self.require_type in ['pack', 'single']:
item = ItemFactory(self.c).get_item(self.require_type) item = ItemFactory(self.c).get_item(self.require_type)
item.item_id = self.require_id item.item_id = self.require_id
item.select(self.user) item.select_user_item(self.user)
if not item.amount: if not item.amount:
self.is_locked = True self.is_locked = True

View File

@@ -1,13 +1,12 @@
import base64 import base64
from functools import wraps from functools import wraps
from core.config_manager import Config
from core.error import ArcError, NoAccess from core.error import ArcError, NoAccess
from core.sql import Connect from core.sql import Connect
from core.user import UserAuth, UserLogin from core.user import UserAuth, UserLogin
from flask import Blueprint, g, jsonify, request from flask import Blueprint, g, jsonify, request, current_app
from .func import arc_try, error_return from .func import arc_try, error_return, header_check
bp = Blueprint('auth', __name__, url_prefix='/auth') bp = Blueprint('auth', __name__, url_prefix='/auth')
@@ -16,9 +15,9 @@ bp = Blueprint('auth', __name__, url_prefix='/auth')
@arc_try @arc_try
def login(): def login():
headers = request.headers headers = request.headers
if Config.ALLOW_APPVERSION: # 版本检查 e = header_check(request)
if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION: if e is not None:
raise NoAccess('Invalid app version.', 1203) raise e
request.form['grant_type'] request.form['grant_type']
with Connect() as c: with Connect() as c:
@@ -44,9 +43,11 @@ def auth_required(request):
headers = request.headers headers = request.headers
if Config.ALLOW_APPVERSION: # 版本检查 e = header_check(request)
if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION: if e is not None:
return error_return(NoAccess('Invalid app version.', 1203)) current_app.logger.warning(
f' - {e.error_code}|{e.api_error_code}: {e}')
return error_return(e)
with Connect() as c: with Connect() as c:
try: try:

View File

@@ -19,7 +19,7 @@ def course_me(user_id):
user = UserOnline(c, user_id) user = UserOnline(c, user_id)
core = ItemCore(c) core = ItemCore(c)
core.item_id = 'core_course_skip_purchase' core.item_id = 'core_course_skip_purchase'
core.select(user) core.select_user_item(user)
x = UserCourseList(c, user) x = UserCourseList(c, user)
x.select_all() x.select_all()
return success_return({ return success_return({

View File

@@ -1,10 +1,18 @@
from functools import wraps from functools import wraps
from traceback import format_exc from traceback import format_exc
from core.config_manager import Config
from core.error import ArcError
from flask import current_app, g, jsonify from flask import current_app, g, jsonify
from core.config_manager import Config
from core.error import ArcError, NoAccess
has_arc_hash = False
try:
from core.arc_crypto import ArcHashChecker # type: ignore
has_arc_hash = True
except ModuleNotFoundError:
pass
default_error = ArcError('Unknown Error', status=500) default_error = ArcError('Unknown Error', status=500)
@@ -89,3 +97,16 @@ def arc_try(view):
return error_return(e) return error_return(e)
return wrapped_view return wrapped_view
def header_check(request) -> ArcError:
'''检查请求头是否合法'''
headers = request.headers
if Config.ALLOW_APPVERSION: # 版本检查
if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return NoAccess('Invalid app version', 1203)
if has_arc_hash and not ArcHashChecker(request).check():
return NoAccess('Invalid request')
return None

View File

@@ -8,7 +8,7 @@ from core.user import User, UserLogin, UserOnline, UserRegister
from flask import Blueprint, request from flask import Blueprint, request
from .auth import auth_required from .auth import auth_required
from .func import arc_try, success_return from .func import arc_try, header_check, success_return
bp = Blueprint('user', __name__, url_prefix='/user') bp = Blueprint('user', __name__, url_prefix='/user')
@@ -17,9 +17,9 @@ bp = Blueprint('user', __name__, url_prefix='/user')
@arc_try @arc_try
def register(): def register():
headers = request.headers headers = request.headers
if Config.ALLOW_APPVERSION: # 版本检查 error = header_check(request)
if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION: if error is not None:
raise NoAccess('Invalid app version.', 1203) raise error
with Connect() as c: with Connect() as c:
new_user = UserRegister(c) new_user = UserRegister(c)