def create_item(event: HTTPEvent): category = event.params['category'] if not event.authorizer.is_admin: raise ForbiddenException( "This endpoint can only be accessed by an administrator") try: release = int(event.params['release']) except ValueError: return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, f"Invalid release {event.params['release']}, it should be an int") body = event.json body['category'] = category body['release'] = release reward = Reward.from_api_map(body) result = RewardsService.create(reward.description, reward.type, reward.release, reward.rarity, reward.price) reward = Reward.from_db_map(result.item) return JSONResponse({ 'message': 'Created item', 'item': reward.to_api_map() })
def fetch_user_tasks(event: HTTPEvent) -> JSONResponse: sub = event.params.get('sub') stage = event.params.get('stage') area = event.params.get('area') if stage is not None and stage not in VALID_STAGES: return JSONResponse.generate_error(HTTPError.NOT_FOUND, f"Stage {stage} not found") if area is not None and area not in VALID_AREAS: return JSONResponse.generate_error(HTTPError.NOT_FOUND, f"Area {area} not found") line_key: Optional[str] = event.params.get('subline') if line_key is not None: lines = line_key.split('.') if len(lines) != 2: return JSONResponse.generate_error( HTTPError.NOT_FOUND, f"Subline {line_key} not valid") line, subline = lines try: line = int(line) subline = int(subline) except ValueError: return JSONResponse.generate_error( HTTPError.NOT_FOUND, f"Subline {line_key} not valid") result = TasksService.get(sub, stage, area, line, subline).to_api_dict() else: result = TasksService.query(sub, stage, area).as_dict(lambda t: t.to_api_dict()) return JSONResponse(result)
def update_active_task(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] schema = Schema({ 'description': str, 'sub-tasks': [{ 'description': str, 'completed': bool }] }) try: body = schema.validate(event.json) except SchemaError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e)) if event.authorizer.sub != sub and not event.authorizer.is_scouter: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "You have no access to this resource with this user") return JSONResponse({ 'message': 'Updated active task', 'active-task': TasksService.update_active_task(event.authorizer, body['description'], body['sub-tasks']) })
def signup_beneficiary(event: HTTPEvent): data = json.loads(event.body) try: attrs = { 'name': data['name'], 'family_name': data['family_name'], 'birthdate': data['birthdate'], 'gender': data['unit'], 'nickname': data['nickname'] } try: datetime.strptime(attrs["birthdate"], "%d-%m-%Y") except ValueError: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, "Invalid date format, it must match " "%d-%m-%Y format") middle_name = data.get('middle_name') if middle_name is not None: attrs['middle_name'] = middle_name UsersCognito.sign_up(data['email'], data['password'], attrs) except UsersCognito.get_client().exceptions.UsernameExistsException: return JSONResponse.generate_error(HTTPError.EMAIL_ALREADY_IN_USE, "E-mail already in use") except UsersCognito.get_client().exceptions.InvalidPasswordException: return JSONResponse.generate_error(HTTPError.EMAIL_ALREADY_IN_USE, "Invalid password. Password must have " "uppercase, lowercase, numbers and be at " "least 6 characters long") except ParamValidationError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e)) UsersCognito.add_to_group(data['email'], "Beneficiaries") return JSONResponse({"message": "OK"})
def complete_active_task(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] if event.authorizer.sub != sub and not event.authorizer.is_scouter: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "You have no access to this resource with this user") completed_task = TasksService.complete_active_task(event.authorizer) if completed_task is None: return JSONResponse.generate_error(HTTPError.NOT_FOUND, "No active task found") area = split_key(completed_task['objective'])[1] response = JSONResponse({ 'message': 'Completed task', 'task': completed_task, 'reward': RewardsFactory.get_reward_token_by_reason( authorizer=event.authorizer, area=area, reason=RewardReason.COMPLETE_OBJECTIVE), }) LogsService.create( event.authorizer.sub, LogTag.COMPLETED.join(completed_task['objective'].upper()), 'Completed an objective!', {}) return response
def update_avatar(event: HTTPEvent) -> JSONResponse: sub = event.params["sub"] if event.authorizer.sub != sub: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "Only the avatar owner can update the avatar") new_avatar = BeneficiariesService.update_avatar(event.authorizer.sub, event.json) return JSONResponse({'avatar': new_avatar})
def confirm_user(event: HTTPEvent): data = json.loads(event.body) try: return UsersCognito.confirm(data['email'], data['code']) except UsersCognito.get_client().exceptions.UserNotFoundException: return JSONResponse.generate_error(HTTPError.UNKNOWN_USER, "User not found") except ParamValidationError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e))
def get_beneficiary(event: HTTPEvent): result = BeneficiariesService.get(event.params["sub"]) if result is None: return JSONResponse.generate_error(HTTPError.NOT_FOUND, "This user does not have a beneficiaries assigned") has_full_access = event.authorizer is not None and ( event.authorizer.is_scouter or event.authorizer.sub == event.params["sub"] ) return JSONResponse(result.to_api_dict(full=has_full_access))
def list_beneficiaries_unit(event: HTTPEvent): district = event.params["district"] group = event.params["group"] unit = event.params["unit"] if unit not in VALID_UNITS: return JSONResponse.generate_error(HTTPError.NOT_FOUND, f"Unknown unit: {unit}") result = BeneficiariesService.query_unit(district, group, unit) result.items = [item.to_api_dict() for item in result.items] return JSONResponse(result.as_dict())
def update_beneficiary(event: HTTPEvent): if event.authorizer.sub != event.params["sub"]: return JSONResponse.generate_error(HTTPError.FORBIDDEN, "You can not access data from this beneficiary") result = BeneficiariesService.update(event.authorizer, profile_picture=event.json.get('profile_picture'), nickname=event.json.get('nickname')) if result is None: return JSONResponse.generate_error(HTTPError.NOT_FOUND, "This user does not have a beneficiaries assigned") return JSONResponse({"message": "Updated successfully"})
def get_handler(event: HTTPEvent): district = event.params["district"] group = event.params["group"] code = event.params.get("code") if code is None: result = get_scouters(district, group, event) else: result = get_scouter(district, group, code, event) if result is None: return JSONResponse.generate_error(HTTPError.NOT_FOUND, "Scouter not found") return JSONResponse(result)
def list_shop_category(event: HTTPEvent): category = event.params['category'] try: release = int(event.params['release']) except ValueError: return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, f"Invalid release {event.params['release']}, it should be an int") return JSONResponse( RewardsService.query(RewardType.from_value(category.upper()), release).as_dict())
def dismiss_active_task(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] if event.authorizer.sub != sub and not event.authorizer.is_scouter: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "You have no access to this resource with this user") return JSONResponse({ 'message': 'Task dismissed', 'task': TasksService.dismiss_active_task(event.authorizer) })
def handler(event: dict, _) -> dict: event = HTTPEvent(event) if event.method == "GET": result = get_handler(event) elif event.method == "POST": if event.resource == "/api/auth/scouters-signup": result = signup_scouter(event) else: result = JSONResponse.generate_error(HTTPError.UNKNOWN_RESOURCE, f"Resource {event.resource} unknown") else: result = JSONResponse.generate_error(HTTPError.NOT_IMPLEMENTED, f"Method {event.method} is not valid") return result.as_dict()
def route(self, event: HTTPEvent) -> JSONResponse: resources = self.routes.get(event.method) if resources is None: return JSONResponse.generate_error(HTTPError.UNKNOWN_RESOURCE, f"Unknown method {event.method}") resource = self.standardize_resource(event.resource) fun = resources.get(resource) if fun is None: return JSONResponse.generate_error(HTTPError.UNKNOWN_RESOURCE, f"Unknown resource {event.resource}") try: return fun(event) except ForbiddenException as e: return JSONResponse.generate_error(HTTPError.FORBIDDEN, e.message) except NotFoundException as e: return JSONResponse.generate_error(HTTPError.NOT_FOUND, e.message) except InvalidException as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, e.message) except UnauthorizedException as e: return JSONResponse.generate_error(HTTPError.UNAUTHORIZED, e.message) except SchemaError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, f"Bad schema: {e}") except Exception as e: body = { "code": HTTPError.SERVER_ERROR.name, "message": "An unknown error ocurred" } error = { "type": str(type(e)), "args": str(e.args), "traceback": [f.strip() for f in traceback.format_tb(e.__traceback__)] } if event.authorizer is not None and event.authorizer.is_admin: body["error"] = error return JSONResponse(body, 500)
def confirm(cls, username: str, code: str): client = cls.get_client() try: client.confirm_sign_up(ClientId=cls.get_client_id(), Username=username, ConfirmationCode=code) return JSONResponse({"message": "Confirmed account"}) except client.exceptions.CodeMismatchException: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, "Wrong confirmation code") except client.exceptions.NotAuthorizedException: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, "Already confirmed")
def refresh_token(event: HTTPEvent): data = json.loads(event.body) try: token = UsersCognito.refresh(data['token']) if token is None: return JSONResponse.generate_error(HTTPError.FORBIDDEN, "Invalid credentials") return JSONResponse({ "message": "Refresh successful", "token": token.as_dict() }) except UsersCognito.get_client().exceptions.UserNotFoundException: return JSONResponse.generate_error(HTTPError.UNKNOWN_USER, "User not found") except ParamValidationError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e))
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 get_district(event: HTTPEvent): code = event.params["district"] response = DistrictModel.get({"code": code}) if response.item is None: raise NotFoundException(f"District '{code}' was not found") response.item = District.from_db(response.item).to_api_map() return JSONResponse(response.as_dict())
def get_my_rewards(event: HTTPEvent): category_name: str = event.params.get('category') category = RewardType.from_value(category_name.upper()) return JSONResponse({ 'rewards': [ log.to_api_map() for log in RewardsService.get_user_rewards( event.authorizer, category) ] })
def handler(event: dict, _) -> dict: event = HTTPEvent(event) if event.method == "POST": if event.resource == "/api/auth/login": result = login(event) elif event.resource == "/api/auth/confirm": result = confirm_user(event) elif event.resource == "/api/auth/refresh": result = refresh_token(event) else: result = JSONResponse.generate_error( HTTPError.UNKNOWN_RESOURCE, f"Resource {event.resource} unknown") else: result = JSONResponse.generate_error( HTTPError.NOT_IMPLEMENTED, f"Method {event.method} is not valid") return result.as_dict()
def get_item(event: HTTPEvent): category = event.params['category'] try: release = int(event.params['release']) except ValueError: return JSONResponse.generate_error( HTTPError.NOT_FOUND, f"Unknown release {event.params['release']}, it should be an int") try: id_ = int(event.params['id']) except ValueError: return JSONResponse.generate_error( HTTPError.NOT_FOUND, f"Unknown id {event.params['id']}, it should be an int") return JSONResponse(RewardsService.get(category, release, id_).as_dict())
def signup_scouter(event: HTTPEvent): data = json.loads(event.body) try: UsersCognito.sign_up(data['email'], data['password'], { 'name': data['name'], 'middle_name': data.get('middle_name'), 'family_name': data['family_name'] }) except UsersCognito.get_client().exceptions.UsernameExistsException: return JSONResponse.generate_error(HTTPError.EMAIL_ALREADY_IN_USE, "E-mail already in use") except UsersCognito.get_client().exceptions.InvalidPasswordException: return JSONResponse.generate_error(HTTPError.EMAIL_ALREADY_IN_USE, "Invalid password. Password must have " "uppercase, lowercase, numbers and be at " "least 6 characters long") except ParamValidationError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e)) UsersCognito.add_to_group(data['email'], "Scouters") return JSONResponse({"message": "OK"})
def login(event: HTTPEvent): data = json.loads(event.body) try: token = UsersCognito.log_in(data['email'], data['password']) if token is None: return JSONResponse.generate_error(HTTPError.FORBIDDEN, "Invalid credentials") return JSONResponse({ "message": "Log-in successful", "token": token.as_dict() }) except UsersCognito.get_client().exceptions.UserNotConfirmedException: return JSONResponse.generate_error( HTTPError.UNCONFIRMED_USER, "User is unconfirmed, check your e-mail for " "the confirmation code.") except ParamValidationError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, e.message)
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 start_task(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] stage = event.params['stage'] area = event.params['area'] subline = event.params['subline'] schema = Schema({'description': str, 'sub-tasks': [str]}) try: body = schema.validate(event.json) except SchemaError as e: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, str(e)) if event.authorizer.sub != sub: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "You have no access to this resource with this user") try: task = TasksService.start_task(event.authorizer, stage, area, subline, body['sub-tasks'], body['description']) if task is None: return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, 'An active task already exists') except NotFoundException: JSONResponse.generate_error(HTTPError.NOT_FOUND, 'Objective not found') return JSONResponse({'message': 'Started new task'})
def get_user_active_task(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] result = TasksService.get_active_task(sub) beneficiary_authorizer = event.authorizer if event.authorizer is not None and event.authorizer.sub == sub else None d = result.to_api_dict(authorizer=beneficiary_authorizer) if beneficiary_authorizer is not None: # generate log reward claim token last_task_log = LogsService.get_last_log_with_tag( sub, tag=join_key(LogTag.PROGRESS.value, result.objective_key).upper()) d['eligible_for_progress_reward'] = last_task_log is None or int( time.time() * 1000) - last_task_log.timestamp > 24 * 60 * 60 * 1000 return JSONResponse(d)
def initialize_tasks(event: HTTPEvent) -> JSONResponse: sub = event.params['sub'] if event.authorizer.sub != sub: return JSONResponse.generate_error( HTTPError.FORBIDDEN, "You have no access to this resource with this user") body = event.json if 'objectives' not in body: return JSONResponse.generate_error(HTTPError.INVALID_CONTENT, "No objectives found") if not isinstance(body['objectives'], list): return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, "Objectives must be a list of objects") objectives: List[ObjectiveKey] = [] for obj in body['objectives']: if not isinstance(obj, dict): return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, "Each objective must be an object") if 'line' not in obj or not isinstance(obj['line'], int): return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, "Each objective must have the key 'line' and it must be an int" ) if 'subline' not in obj or not isinstance(obj['subline'], int): return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, "Each objective must have the key 'subline' and it must be an int" ) if 'area' not in obj or obj['area'] not in VALID_AREAS: return JSONResponse.generate_error( HTTPError.INVALID_CONTENT, f"Each objective must have the key 'area' and it must a valid area " f"name: {VALID_AREAS}") objectives.append( ObjectiveKey(line=obj['line'], subline=obj['subline'], area=obj['area'])) return JSONResponse({ 'message': 'Task dismissed', 'reward': TasksService.initialize(event.authorizer, objectives) })
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] })