def json(self): try: return json.loads(self.body) except JSONDecodeError: raise InvalidException('Body isn\'t a valid JSON data') except TypeError: raise InvalidException('Body isn\'t a valid JSON data')
def buy_item(event: HTTPEvent): category = event.params['category'] try: release = int(event.params['release']) except ValueError: return NotFoundException( f"Unknown release {event.params['release']}, it should be an int") try: id_ = int(event.params['id']) except ValueError: raise NotFoundException( f"Unknown id {event.params['id']}, it should be an int") area = event.params['area'] if area not in VALID_AREAS: raise NotFoundException(f"Area {area} does not exist") amount = event.json.get('amount', 1) if type(amount) is not int: raise InvalidException(f"The amount to be bought must be an integer") if amount < 1: raise InvalidException(f"The amount must be one or more") try: result = BeneficiariesService.buy_item(event.authorizer, area, category, release, id_, amount) except BeneficiariesService.exceptions().ConditionalCheckFailedException: raise ForbiddenException( f"You don't have enough {area} score to buy this item") if not result: return NotFoundException(f"Item not found") return JSONResponse(result)
def create_group(event: HTTPEvent): if not event.authorizer.is_admin: raise ForbiddenException("Only an admin can access this endpoint") district = event.params["district"] item = event.json code = item['code'] try: del item['code'] GroupsService.create(district, code, item, event.authorizer.sub, event.authorizer.full_name) except GroupsService.exceptions().ConditionalCheckFailedException: raise InvalidException( f"Group in district {district} with code {code} already exists") except SchemaError as e: raise InvalidException(f"Item content is invalid: \"{e.code}\"") return JSONResponse({"message": "OK"})
def get_random(cls, category: RewardType, release: int, rarity: RewardRarity): reward = Reward.factory(category, rarity) if reward is not None: return reward release = int(release) if release < 1: raise InvalidException('Release must be positive and non-zero') top = abs(release * REWARDS_PER_RELEASE - 1) if rarity == RewardRarity.RARE: top = -top lowest = min(0, top) highest = max(0, top) random_point = random.randint(lowest, highest) index = cls.get_interface() result = index.query( category.name, (Operator.BETWEEN, lowest, random_point), attributes=['category', 'description', 'release-id', 'price'], limit=1) if len(result.items) == 0: result = index.query( category.name, (Operator.BETWEEN, random_point, highest), attributes=['category', 'description', 'release-id', 'price'], limit=1) if len(result.items) == 0: raise NotFoundException(f'No reward of type {category.name} found') return Reward.from_db_map(result.items[0])
def set_reward_index(cls, authorizer: Authorizer, index: int): interface = cls.get_interface() updates = {'n_claimed_tokens': index} conditions = "#attr_n_claimed_tokens < :val_n_claimed_tokens" try: return interface.update(authorizer.sub, updates, None, conditions=conditions) except interface.client.exceptions.ConditionalCheckFailedException: raise InvalidException('This token has already been claimed')
def create_log(event: HTTPEvent): user_sub: str = event.params['sub'] tag: str = event.params['tag'].upper() if event.authorizer.sub != user_sub: raise ForbiddenException("Only the same user can create logs") parent_tag = LogTag.from_short(split_key(tag)[0]) if parent_tag not in USER_VALID_TAGS: raise ForbiddenException(f"A user can only create logs with the following tags: {USER_VALID_TAGS}") body = event.json log = body['log'] data = body.get('data') if len(body) > 1024: raise InvalidException(f"A log can't have more than 1024 characters") if data is not None: if len(json.dumps(data)) > 2048: raise InvalidException(f"Log data is too big") response_body = {} if parent_tag == LogTag.PROGRESS: if tag != parent_tag.short: raise InvalidException(f"A progress log tag can't be compound") if body.get('token') is None: raise InvalidException(f"To post a PROGRESS log you must provide the task token") objective = TasksService.get_task_token_objective(body['token'], authorizer=event.authorizer) tag = join_key(LogTag.PROGRESS.value, objective).upper() now = int(datetime.now(timezone.utc).timestamp() * 1000) last_progress_log = LogsService.get_last_log_with_tag(event.authorizer.sub, tag.upper()) if last_progress_log is None or now - last_progress_log.timestamp > 24 * 60 * 60 * 1000: response_body['token'] = RewardsFactory.get_reward_token_by_reason(authorizer=event.authorizer, area=split_key(objective)[1], reason=RewardReason.PROGRESS_LOG) log = LogsService.create(user_sub, tag, log_text=log, data=body.get('data'), append_timestamp_to_tag=True) response_body['item'] = log.to_api_map() return JSONResponse(body=response_body)
def claim_reward(event: HTTPEvent): body = event.json token = body.get('token') if token is None: raise InvalidException('No reward token given') box_index = body.get('box_index') if box_index is not None: try: box_index = int(box_index) except ValueError: raise InvalidException('Box index must be an int') rewards = RewardsService.claim_reward(event.authorizer, reward_token=token, box_index=box_index, release=1) return JSONResponse({ 'message': 'Claimed rewards!', 'rewards': [reward.to_api_map() for reward in rewards] })
def query_logs(event: HTTPEvent): user_sub = event.params['sub'] tag = LogTag.normalize(split_key(event.params['tag'])).upper() if event.params.get('tag') else None limit = event.queryParams.get('limit', 25) if not isinstance(limit, int) or limit > 100: raise InvalidException("Limit must be an integer and lower or equal than 100") logs = LogsService.query(user_sub, tag, limit=limit) return JSONResponse(body=QueryResult.from_list([log.to_api_map() for log in logs]).as_dict())
def from_api_map(cls, item: dict): try: return Reward(category=RewardType.from_value(item['category']), release=int(item['release']), id_=item.get('id'), description=item['description'], rarity=RewardRarity.from_name(item['rarity']), price=item.get('price')) except KeyError as e: raise InvalidException(f'Missing key: {e.args}')
def mark_as_initialized(cls, authorizer: Authorizer): interface = cls.get_interface() updates = { 'set_base_tasks': True, } try: return interface.update(authorizer.sub, updates, return_values=UpdateReturnValues.UPDATED_NEW, conditions=Attr('set_base_tasks').eq(False)) except interface.client.exceptions.ConditionalCheckFailedException: raise InvalidException('Beneficiary already initialized')
def normalize(tag: List[str], short=False): parent_tag_full = LogTag.from_tag(tag, short=False) parent_tag = LogTag.from_tag( tag, short=True) if parent_tag_full is None else parent_tag_full if parent_tag is None: raise InvalidException(f'Tag {join_key(*tag)} does not exist') source_is_short = parent_tag_full is None tag_body = tag[ 1 if source_is_short else len(split_key(parent_tag.value)):] if not short: return join_key(parent_tag.value, *tag_body) else: return join_key(parent_tag.short, *tag_body)
def join_as_scouter(cls, authorizer: Authorizer, district: str, group: str, code: str): interface = cls.get_interface() try: interface.update(district, { 'scouters.' + authorizer.sub: { 'name': authorizer.full_name, 'role': 'scouter' } }, group, condition_equals={'scouters_code': code}) except cls.exceptions().ConditionalCheckFailedException: raise InvalidException('Wrong scouters code') UsersCognito.add_to_scout_group(authorizer.username, district, group, authorizer.scout_groups)
def buy_item(cls, authorizer: Authorizer, area: str, item_category: str, item_release: int, item_id: int, amount: int = 1): interface = cls.get_interface() item = RewardsService.get(item_category, item_release, item_id).item if item is None: return False price = item.get('price') if price is None: raise InvalidException('This reward cannot be bought') release_id = item_release * 100000 + item_id return interface.update(authorizer.sub, None, None, add_to={ f'bought_items.{item_category}{release_id}': amount, f'score.{area}': int(-amount * price) }, conditions=Attr(f'score.{area}').gte(int(amount * price)), return_values=UpdateReturnValues.UPDATED_NEW)[ 'Attributes']
def get_task_token_objective(cls, token: str, authorizer: Authorizer) -> str: jwk_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'jwk.json') with open(jwk_path, 'r') as f: jwk = jwt.jwk_from_dict(json.load(f)) decoded = jwt.JWT().decode(token, jwk) try: Schema({ "sub": str, "objective": str, "exp": int, "iat": int }).validate(decoded) except SchemaError: raise InvalidException("The given task token is not valid") if authorizer.sub != decoded['sub']: raise ForbiddenException( "The given task token does not belong to this user") return decoded['objective']
def create(cls, district: str, group: str, authorizer: Authorizer): interface = cls.get_interface() beneficiary = Beneficiary( user_sub=authorizer.sub, district=district, group=group, unit=authorizer.unit, full_name=authorizer.full_name, nickname=authorizer.nickname, birthdate=authorizer.birth_date, target=None, set_base_tasks=False, score={area: 0 for area in VALID_AREAS}, n_tasks={area: 0 for area in VALID_AREAS}, bought_items={} ) try: interface.create(authorizer.sub, beneficiary.to_db_dict(), raise_if_exists_partition=True) except cls.exceptions().ConditionalCheckFailedException: raise InvalidException("Already joined a group")
def clear_active_task(cls, authorizer: Authorizer, return_values: UpdateReturnValues = UpdateReturnValues.UPDATED_OLD, receive_score=False ): interface = cls.get_interface() updates = {'target': None} add_to = None if receive_score: beneficiary = BeneficiariesService.get(authorizer.sub, ["target"]) if beneficiary.target is None: return None score = ScoreConfiguration.instance().base_score area = split_key(beneficiary.target.objective_key)[1] add_to = { f'score.{area}': score, f'n_tasks.{area}': 1 } try: return interface.update(authorizer.sub, updates, None, return_values=return_values, add_to=add_to, conditions=Attr('target').ne(None))["Attributes"] except interface.client.exceptions.ConditionalCheckFailedException: raise InvalidException('No active target')
def from_value(value: str): for member in RewardType: if value.upper() == member.value: return member raise InvalidException(f"Unknown reward type: {value}")
def sub(self): sub = self.attributes.get('sub') if sub is None: raise InvalidException('Can\'t get user ID') return sub
def from_name(name: str): for member in RewardRarity: if name.upper() == member.name: return member raise InvalidException(f"Unknown reward rarity: {name}")
def claim_reward(cls, authorizer: Authorizer, reward_token: str, release: int, box_index: int = None) -> \ List[Reward]: from core.services.beneficiaries import BeneficiariesService jwk_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'jwk.json') with open(jwk_path, 'r') as f: jwk = jwt.jwk_from_dict(json.load(f)) decoder = jwt.JWT() try: decoded = decoder.decode(reward_token, jwk) except JWTDecodeError as e: raise InvalidException(f'Invalid token: {e.args}') now = get_int_from_datetime(datetime.now()) if now > decoded["exp"]: raise ForbiddenException("The reward token has expired") if authorizer.sub != decoded["sub"]: raise ForbiddenException( "This token does not belong to the claimer") BeneficiariesService.set_reward_index(authorizer, decoded['index']) boxes = decoded["boxes"] probabilities: List[RewardProbability] = [ RewardProbability.from_map(reward) for reward in decoded["static"] ] if len(boxes) > 0: if box_index is None: raise InvalidException("A box must be chosen") if box_index >= len(boxes): raise InvalidException( f"Box index out of range, it must be between 0 (inclusive) and {len(boxes)} (exclusive)" ) probabilities += [ RewardProbability.from_map(reward) for reward in boxes[box_index] ] rewards = [ RewardsService.get_random(probability.type, release, probability.rarity) for probability in probabilities ] LogsService.batch_create(logs=[ Log(sub=authorizer.sub, tag=join_key(LogTag.REWARD.name, rewards[reward_i].type.name, rewards[reward_i].id), log='Won a reward', data=rewards[reward_i].to_api_map(), append_timestamp=rewards[reward_i].type != RewardType.AVATAR) for reward_i in range(len(rewards)) ]) area: Optional[str] = decoded.get('area') areas: List[str] = VALID_AREAS if area is None else [area] scores = {} for r in rewards: if r.type != RewardType.POINTS: continue total_score = r.description['amount'] for a in areas: scores[a] = scores.get(a, 0) + (total_score / len(areas)) for key in scores: scores[key] = math.ceil(scores[key]) BeneficiariesService.add_score(authorizer.sub, scores) return rewards