class GithubView(MethodView): decorators = [cross_origin()] @cached_property def repo(self): client = github3.login(token=config.github_token) return client.repository('18F', 'fec') @use_kwargs({ 'referer': fields.Url( required=True, validate=validate_referer, location='headers', ), 'action': fields.Str(), 'feedback': fields.Str(), 'about': fields.Str(), }) def post(self, **kwargs): if not any([kwargs['action'], kwargs['feedback'], kwargs['about']]): return jsonify({ 'message': 'Must provide one of "action", "feedback", or "about".', }), 422 title = 'User feedback on {}'.format(kwargs['referer']) body = render_template('feedback.html', headers=request.headers, **kwargs) issue = self.repo.create_issue(title, body=body) return jsonify(issue.to_json()), 201
def get_predict_args(): parser = OrderedDict() default_conf = config.CONF default_conf = OrderedDict([('testing', default_conf['testing'])]) # Add options for modelname timestamp = default_conf['testing']['timestamp'] timestamp_list = next(os.walk(paths.get_models_dir()))[1] timestamp_list.remove('common') # common files do not count as full model timestamp_list = sorted(timestamp_list) if not timestamp_list: timestamp['value'] = '' else: timestamp['value'] = timestamp_list[-1] timestamp['choices'] = timestamp_list # Add data and url fields parser['files'] = fields.Field( required=False, missing=None, type="file", data_key="data", location="form", description="Select the audio file you want to classify.") parser['urls'] = fields.Url( required=False, missing=None, description="Select an URL of the audio file you want to classify.") # missing action="append" --> append more than one url return populate_parser(parser, default_conf)
class PredictArgsSchema(Schema): class Meta: unknown = INCLUDE # support 'full_paths' parameter # full list of fields: https://marshmallow.readthedocs.io/en/stable/api_reference.html # to be able to upload a file for prediction files = fields.Field( required=False, missing=None, type="file", data_key="data", location="form", description="Select a file for the prediction" ) # to be able to provide an URL for prediction urls = fields.Url( required=False, missing=None, description="Provide an URL of the data for the prediction" ) # an input parameter for prediction arg1 = fields.Integer( required=False, missing=1, description="Input argument 1 for the prediction" )
class PredictArgsSchema(Schema): class Meta: unknown = INCLUDE # supports extra parameters trained_graph = fields.Str( required=False, missing='1540408813_cpu', enum=['1540408813_cpu', '1533577729_gpu'], description="Pre-trained graph to use" ) files = fields.Field( required=False, missing=None, type="file", data_key="data", location="form", description="Select the image you want to classify." ) urls = fields.Url( required=False, missing=None, description="Select an URL of the image you want to classify." )
class SavePluginAPI(HTTPEndpoint): @use_args({"zip_url": fields.Url(required=True)}) @requires("root_login") async def post(self, request: Request, paramters: dict) -> response: """Used to save a version of the plugins locally. Parameters ---------- request : Request paramters : dict Returns ------- response """ # Temp return error_response("Not implemented") async with Sessions.aiohttp.get(paramters["zip_url"]) as resp: if resp.status != 200: return error_response("Unable to download file", status_code=resp.status) zip_pathway = path.join(Config.plugin_dir, "plugins.zip") async with aiofiles.open(zip_pathway, "w+") as file_: await file_.truncate() await file_.write(await resp.read()) with ZipFile(zip_pathway) as zf: zf.extractall(path.join(Config.plugin_dir, "extracted")) return response("Files unzipped and cached")
class User(HTTPEndpoint): @use_args({ "steam_id": fields.String(required=True), "ip": fields.String(max=39), "name": fields.String(max=36), "discord_id": fields.Integer(), "pfp": fields.Url() }) async def post(self, request, args): """ Get user. """ return Responder(await modulelift.CLIENT.user().create(**args)).json()
class TrainArgsSchema(Schema): class Meta: unknown = INCLUDE # support 'full_paths' parameter # to be able to provide an URL for prediction urls = fields.Url( required=False, missing=None, description="Provide an URL of the data for the training" ) # available fields are e.g. fields.Integer(), fields.Str(), fields.Boolean() # full list of fields: https://marshmallow.readthedocs.io/en/stable/api_reference.html arg1 = fields.Integer( required=False, missing=1, description="Input argument 1 for training" )
class PredictArgsSchema(Schema): class Meta: unknown = INCLUDE # supports extra parameters network = fields.Str(required=False, missing=cnn_list[0], enum=cnn_list, description="Neural model to use for prediction") files = fields.Field(required=False, missing=None, type="file", data_key="data", location="form", description="Select the image you want to classify.") urls = fields.Url( required=False, missing=None, description="Select an URL of the image you want to classify.")
class CheckoutRetreatController(Resource): post_args = { "proposal_id": fields.Int(requred=True), "retreat_id": fields.Int(required=True), "redirect_url": fields.Url(required=True), } @jwt.requires_auth @use_args(post_args, location="json") def post(self, post_args: Dict[str, any]): flok_fee = 12500 # $125 flok_line_item = "Flok fee (per employee)" retreat = retreat_manager.get_retreat(post_args["retreat_id"], g.user) if retreat: matches: List[RetreatProposal] = list( filter(lambda p: p.id == post_args["proposal_id"], retreat.proposals)) if matches: proposal = matches[0] stripe_customer = robin_manager.get_stripe_customer(g.user) if not stripe_customer: stripe_customer = robin_manager.create_stripe_customer( g.user) stripe_checkout_session = robin_manager.create_stripe_checkout_session( customer=stripe_customer, image=proposal.image_url, name=flok_line_item, price=flok_fee, quantity=retreat.num_employees, success_url=post_args["redirect_url"], cancel_url=post_args["redirect_url"], metadata={ "retreat_id": retreat.id, "proposal_id": proposal.id, }, ) robin_manager.commit_changes() return responses.success( {"session_id": stripe_checkout_session.id})
def get_predict_args(): parser = OrderedDict() default_conf = config.CONF default_conf = OrderedDict([('general', default_conf['general']), ('testing', default_conf['testing'])]) # Add data and url fields parser['files'] = fields.Field(required=False, missing=None, type="file", data_key="data", location="form", description="Select the file you want to classify.") parser['urls'] = fields.Url(required=False, missing=None, description="Select an URL of the file you want to classify.") # missing action="append" --> append more than one url # Add format type of the response parser['accept'] = fields.Str(description="Media type(s) that is/are acceptable for the response.", validate=validate.OneOf(["image/tiff"])) return populate_parser(parser, default_conf)
class PredictArgsSchema(Schema): class Meta: unknown = INCLUDE # support 'full_paths' parameter # full list of fields: https://marshmallow.readthedocs.io/en/stable/api_reference.html # to be able to upload a file for prediction files = fields.Field(required=False, missing=None, type="file", data_key="data", location="form", description="Select a file for the prediction") # to be able to provide an URL for prediction urls = fields.Url( required=False, missing=None, description="Provide an URL of the data for the prediction") # input parameters for prediction energy = fields.Float(required=False, missing=1e9, description="energy in GeV") theta = fields.Float(required=False, missing=0.0, description="zenith angle in deg") phi = fields.Float(required=False, missing=0.0, description="azimuth angle in deg") primary_particle = fields.Str( required=False, missing="proton", description="primary particle type: proton|helium|oxygen|iron")
from backend.decorators import auth_required, confirm_required, check_stoken from backend.api.v1.schemas import tasks_schema, task_detail_schema, flush_task_schema, option_schema, options_schema, \ envs_schema, task_options_schema from ansible_index import AnsibleOpt # 校验option参数 options_args = { 'name': fields.Str(validate=lambda p: len(p) > 0, required=True, error_messages=dict( required="option名称为必填项", validator_failed="name不能为空", invalid="请输入字符串" )), 'playbook_id': fields.Int(validate=validate_playbook_id, required=True, error_messages=dict( required="playbook必填项" )), 'content': fields.Dict(required=True), 'env_id': fields.Int(required=False, validate=validate_env_id, allow_none=True, missing=None, default=None), 'url': fields.Url(required=False, allow_none=True, missing=None, default=None) } class PlayBookOptionAPI(MethodView): decorators = [auth_required] def get(self, option_id): """获取单一playbook参数接口""" option = Options.query.get_or_404(option_id) return jsonify(option_schema(option)) @use_args(options_args, location='json') def put(self, args, option_id): """编辑playbook参数接口""" option = Options.query.get_or_404(option_id)
allow_none=True, validate=[validate.Length(min=4), validate.Regexp('^[0-9-]*$')]), 'mplselect': fields.String(allow_none=True, validate=validate.Regexp('^(MPL-|DR)([0-9]{1,2}$)')) }, 'galaxy': { 'plateifu': fields.String(allow_none=True, validate=validate.Length(min=8, max=11)), 'toggleon': fields.String(allow_none=True, validate=validate.OneOf(['true', 'false'])), 'image': fields.Url(allow_none=True), 'imheight': fields.Integer(allow_none=True, validate=validate.Range(min=0, max=1000)), 'imwidth': fields.Integer(allow_none=True, validate=validate.Range(min=0, max=1000)), 'type': fields.String(allow_none=True, validate=validate.OneOf(['optical', 'heatmap'])), 'x': fields.String(allow_none=True), 'y': fields.String(allow_none=True), 'mousecoords[]': fields.List(fields.String(), allow_none=True),
class Mods(RouteCog): @staticmethod def dict_all(models): return [m.to_dict() for m in models] @multiroute("/api/v1/mods", methods=["GET"], other_methods=["POST"]) @json @use_kwargs( { "q": fields.Str(), "page": fields.Int(missing=0), "limit": fields.Int(missing=50), "category": EnumField(ModCategory), "rating": fields.Int(validate=validate.OneOf([1, 2, 3, 4, 5])), "status": EnumField(ModStatus), "sort": EnumField(ModSorting), "ascending": fields.Bool(missing=False), }, locations=("query", ), ) async def get_mods( self, q: str = None, page: int = None, limit: int = None, category: ModCategory = None, rating: int = None, status: ModStatus = None, sort: ModSorting = None, ascending: bool = None, ): if not 1 <= limit <= 100: limit = max(1, min( limit, 100)) # Clamp `limit` to 1 or 100, whichever is appropriate page = page - 1 if page > 0 else 0 query = Mod.query.where(Mod.verified) if q is not None: like = f"%{q}%" query = query.where( and_( Mod.title.match(q), Mod.tagline.match(q), Mod.description.match(q), Mod.title.ilike(like), Mod.tagline.ilike(like), Mod.description.ilike(like), )) if category is not None: query = query.where(Mod.status == category) if rating is not None: query = query.where(rating + 1 > db.select([ func.avg( Review.select("rating").where(Review.mod_id == Mod.id)) ]) >= rating) if status is not None: query = query.where(Mod.status == status) if sort is not None: sort_by = mod_sorters[sort] query = query.order_by( sort_by.asc() if ascending else sort_by.desc()) results = await paginate(query, page, limit).gino.all() total = await query.alias().count().gino.scalar() return jsonify(total=total, page=page, limit=limit, results=self.dict_all(results)) @multiroute("/api/v1/mods", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs( { "title": fields.Str(required=True, validate=validate.Length(max=64)), "tagline": fields.Str(required=True, validate=validate.Length(max=100)), "description": fields.Str(required=True, validate=validate.Length(min=100, max=10000)), "website": fields.Url(required=True), "status": EnumField(ModStatus, required=True), "category": EnumField(ModCategory, required=True), "authors": fields.List(fields.Nested(AuthorSchema), required=True), "icon": b64img_field("icon"), "banner": b64img_field("banner"), "media": fields.List(b64img_field("media"), required=True), "is_private_beta": fields.Bool(missing=False), "mod_playtester": fields.List(fields.Str()), "color": EnumField(ModColor, missing=ModColor.default), "recaptcha": fields.Str(required=True), }, locations=("json", ), ) async def post_mods( self, title: str, tagline: str, description: str, website: str, authors: List[dict], status: ModStatus, category: ModCategory, icon: str, banner: str, media: List[str], recaptcha: str, color: ModColor, is_private_beta: bool = None, mod_playtester: List[str] = None, ): score = await verify_recaptcha(recaptcha, self.core.aioh_sess, "create_mod") if score < 0.5: # TODO: discuss what to do here abort(400, "Possibly a bot") user_id = await get_token_user() # Check if any mod with a similar enough name exists already. generalized_title = generalize_text(title) mods = await Mod.get_any(True, generalized_title=generalized_title).first() if mods is not None: abort(400, "A mod with that title already exists") if status is ModStatus.archived: abort(400, "Can't create a new archived mod") mod = Mod( title=title, tagline=tagline, description=description, website=website, status=status, category=category, theme_color=color, ) icon_mimetype, icon_data = validate_img(icon, "icon") banner_mimetype, banner_data = validate_img(banner, "banner") media = [validate_img(x, "media") for x in media] for i, author in enumerate(authors): if author["id"] == user_id: authors.pop(i) continue elif not await User.exists(author["id"]): abort(400, f"Unknown user '{author['id']}'") authors.append({"id": user_id, "role": AuthorRole.owner}) if is_private_beta is not None: mod.is_private_beta = is_private_beta if mod_playtester is not None: if not is_private_beta: abort(400, "No need for `ModPlaytester` if open beta") for playtester in mod_playtester: if not await User.exists(playtester): abort(400, f"Unknown user '{playtester}'") # Decode images and add name for mimetypes icon_data = base64.b64decode(icon_data) banner_data = base64.b64decode(banner_data) icon_ext = icon_mimetype.split("/")[1] banner_ext = banner_mimetype.split("/")[1] icon_resp = await ipfs_upload(icon_data, f"icon.{icon_ext}", self.core.aioh_sess) banner_resp = await ipfs_upload(banner_data, f"banner.{banner_ext}", self.core.aioh_sess) mod.icon = icon_resp["Hash"] mod.banner = banner_resp["Hash"] media_hashes = [] for mime, data in media: ext = mime.split("/")[1] resp = await ipfs_upload(data, f"media.{ext}", self.core.aioh_sess) media_hashes += [resp] await mod.create() await ModAuthor.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) await ModPlaytester.insert().gino.all( *[dict(user_id=user, mod_id=mod.id) for user in mod_playtester]) if media_hashes: await Media.insert().gino.all(*[ dict(type=MediaType.image, hash=hash_, mod_id=mod.id) for hash_ in media_hashes ]) return jsonify(mod.to_dict()) @route("/api/v1/mods/recent_releases") @json async def get_recent_releases(self): mods = (await Mod.query.where( and_(Mod.verified, Mod.status == ModStatus.released) ).order_by(Mod.released_at.desc()).limit(10).gino.all()) return jsonify(self.dict_all(mods)) @route("/api/v1/mods/most_loved") @json async def get_most_loved(self): love_counts = (select( [func.count()]).where(UserFavorite.mod_id == Mod.id).as_scalar()) mods = await Mod.query.order_by(love_counts.desc() ).limit(10).gino.all() return jsonify(self.dict_all(mods)) @route("/api/v1/mods/most_downloads") @json async def get_most_downloads(self): mods = (await Mod.query.where(and_(Mod.verified, Mod.released_at is not None) ).order_by(Mod.downloads.desc() ).limit(10).gino.all()) return jsonify(self.dict_all(mods)) @route("/api/v1/mods/trending") @json async def get_trending(self): # TODO: implement return jsonify([]) @route("/api/v1/mods/editors_choice") @json async def get_ec(self): mod_ids = [x.mod_id for x in await EditorsChoice.query.gino.all()] mods = (await Mod.query.where(Mod.id in mod_ids ).order_by(Mod.downloads.desc() ).limit(10).gino.all()) return jsonify(mods) @multiroute("/api/v1/mods/<mod_id>", methods=["GET"], other_methods=["PATCH", "DELETE"]) @json async def get_mod(self, mod_id: str): # mod = await Mod.get(mod_id) mod = (await Mod.load(authors=ModAuthor, media=Media).where(Mod.id == mod_id).gino.first()) if mod is None: abort(404, "Unknown mod") return jsonify(mod.to_dict()) @multiroute("/api/v1/mods/<mod_id>", methods=["PATCH"], other_methods=["GET", "DELETE"]) @requires_login @json @use_kwargs( { "title": fields.Str(validate=validate.Length(max=64)), "tagline": fields.Str(validate=validate.Length(max=100)), "description": fields.Str(validate=validate.Length(min=100, max=10000)), "website": fields.Url(), "status": EnumField(ModStatus), "category": EnumField(ModCategory), "authors": fields.List(fields.Nested(AuthorSchema)), "icon": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`icon` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'"), )), "banner": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`banner` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'"), )), "color": EnumField(ModColor), "is_private_beta": fields.Bool(), "mod_playtester": fields.List(fields.Str()), }, locations=("json", ), ) async def patch_mod( self, mod_id: str = None, authors: List[dict] = None, mod_playtester: List[str] = None, icon: str = None, banner: str = None, **kwargs, ): if not await Mod.exists(mod_id): abort(404, "Unknown mod") mod = await Mod.get(mod_id) updates = mod.update(**kwargs) if authors is not None: authors = [ author for author in authors if await User.exists(author["id"]) ] # TODO: if user is owner or co-owner, allow them to change the role of others to ones below them. authors = [ author for author in authors if not await ModAuthor.query.where( and_(ModAuthor.user_id == author["id"], ModAuthor.mod_id == mod_id)).gino.first() ] if mod_playtester is not None: for playtester in mod_playtester: if not await User.exists(playtester): abort(400, f"Unknown user '{playtester}'") elif await ModPlaytester.query.where( and_( ModPlaytester.user_id == playtester, ModPlaytester.mod_id == mod.id, )).gino.all(): abort(400, f"{playtester} is already enrolled.") if icon is not None: icon_mimetype, icon_data = validate_img(icon, "icon") icon_data = base64.b64decode(icon_data) icon_ext = icon_mimetype.split("/")[1] icon_resp = await ipfs_upload(icon_data, f"icon.{icon_ext}", self.core.aioh_sess) updates = updates.update(icon=icon_resp["Hash"]) if banner is not None: banner_mimetype, banner_data = validate_img(banner, "banner") banner_data = base64.b64decode(banner_data) banner_ext = banner_mimetype.split("/")[1] banner_resp = await ipfs_upload(banner_data, f"banner.{banner_ext}", self.core.aioh_sess) updates = updates.update(banner=banner_resp["Hash"]) await updates.apply() await ModAuthor.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) await ModPlaytester.insert().gino.all( *[dict(user_id=user, mod_id=mod.id) for user in ModPlaytester]) return jsonify(mod.to_dict()) # TODO: decline route with reason, maybe doesn't 100% delete it? idk @multiroute("/api/v1/mods/<mod_id>", methods=["DELETE"], other_methods=["GET", "PATCH"]) @requires_login @json async def delete_mod(self, mod_id: str): await Mod.delete.where(Mod.id == mod_id).gino.status() return jsonify(True) @route("/api/v1/mods/<mod_id>/download") @json async def get_download(self, mod_id: str): user_id = await get_token_user() mod = await Mod.get(mod_id) if mod is None: abort(404, "Unknown mod") if user_id is None and mod.is_private_beta: abort(403, "Private beta mods requires authentication.") if not await ModPlaytester.query.where( and_(ModPlaytester.user_id == user_id, ModPlaytester.mod_id == mod.id)).gino.all(): abort(403, "You are not enrolled to the private beta.") elif not mod.zip_url: abort(404, "Mod has no download") return jsonify(url=mod.zip_url) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["GET"], other_methods=["POST"]) @json @use_kwargs( { "page": fields.Int(missing=0), "limit": fields.Int(missing=10), "rating": UnionField( [ fields.Int(validate=validate.OneOf([1, 2, 3, 4, 5])), fields.Str(validate=validate.Equal("all")), ], missing="all", ), "sort": EnumField(ReviewSorting, missing=ReviewSorting.best), }, locations=("query", ), ) async def get_reviews( self, mod_id: str, page: int, limit: int, rating: Union[int, str], sort: ReviewSorting, ): if not await Mod.exists(mod_id): abort(404, "Unknown mod") if not 1 <= limit <= 25: limit = max(1, min( limit, 25)) # Clamp `limit` to 1 or 100, whichever is appropriate page = page - 1 if page > 0 else 0 upvote_aggregate = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.upvote, )).as_scalar()) downvote_aggregate = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.downvote, )).as_scalar()) funny_aggregate = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.funny, )).as_scalar()) query = Review.load( author=User, upvotes=upvote_aggregate, downvotes=downvote_aggregate, funnys=funny_aggregate, ).where(Review.mod_id == mod_id) user_id = await get_token_user() if user_id: did_upvote_q = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.user_id == user_id, ReviewReaction.reaction == ReactionType.upvote, )).limit(1).as_scalar()) did_downvote_q = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.user_id == user_id, ReviewReaction.reaction == ReactionType.downvote, )).limit(1).as_scalar()) did_funny_q = (select([func.count()]).where( and_( ReviewReaction.review_id == Review.id, ReviewReaction.user_id == user_id, ReviewReaction.reaction == ReactionType.funny, )).limit(1).as_scalar()) query = query.gino.load( user_reacted_upvote=did_upvote_q, user_reacted_downvote=did_downvote_q, user_reacted_funny=did_funny_q, ) if review_sorters[sort]: query = query.order_by(review_sorters[sort]) elif sort == ReviewSorting.best: query = query.order_by(upvote_aggregate - downvote_aggregate) elif sort == ReviewSorting.funniest: query = query.order_by(funny_aggregate.desc()) if isinstance(rating, int): values = [rating, rating + 0.5] if rating == 1: # Also get reviews with a 0.5 star rating, otherwise they'll never appear. values.append(0.5) query = query.where(Review.rating.in_(values)) reviews = await paginate(query, page, limit).gino.all() total = await query.alias().count().gino.scalar() return jsonify(total=total, page=page, limit=limit, results=self.dict_all(reviews)) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs({ "rating": fields.Int( required=True, validate=[ # Only allow increments of 0.5, up to 5. lambda x: 5 >= x >= 1, lambda x: x % 0.5 == 0, ], ), "content": fields.Str(required=True, validate=validate.Length(max=2000)), "title": fields.Str(required=True, validate=validate.Length(max=32)), }) async def post_review(self, mod_id: str, rating: int, content: str, title: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") user_id = await get_token_user() if await Review.query.where( and_(Review.author_id == user_id, Review.mod_id == mod_id)).gino.first(): abort(400, "Review already exists") review = await Review.create( title=title, content=content, rating=rating, author_id=user_id, mod_id=mod_id, ) return jsonify(review.to_json()) @route("/api/v1/reviews/<review_id>/react", methods=["POST"]) @requires_login @json @use_kwargs({ "undo": fields.Bool(missing=False), "type": EnumField(ReactionType, data_key="type", required=True), }) async def react_review(self, review_id: str, undo: bool, type_: EnumField): user_id = await get_token_user() where_opts = [ ReviewReaction.review_id == review_id, ReviewReaction.user_id == user_id, ReviewReaction.reaction == type_, ] create_opts = { "review_id": review_id, "user_id": user_id, "reaction": type_ } exists = bool(await ReviewReaction.select("id").where(and_(*where_opts) ).gino.scalar()) if (exists and not undo) or (not exists and undo): pass elif not undo: if type_ == ReactionType.upvote: # Negate any downvotes by the user as upvoting and downvoting at the same time is stupid await ReviewReaction.delete.where( and_(*where_opts[:-1], ReviewReaction.type == ReactionType.downvote) ).gino.status() elif type_ == ReactionType.downvote: # Ditto for any upvotes if trying to downvote await ReviewReaction.delete.where( and_(*where_opts[:-1], ReviewReaction.type == ReactionType.downvote) ).gino.status() await ReviewReaction.create(**create_opts) else: await ReviewReaction.delete.where(and_(*where_opts)).gino.status() # Return user's current reaction results results = await ReviewReaction.query.where(and_(*where_opts[:-1]) ).gino.all() results = [x.reaction for x in results] return jsonify( upvote=ReactionType.upvote in results, downvote=ReactionType.downvote in results, funny=ReactionType.funny in results, ) # This handles POST requests to add zip_url. # Usually this would be done via a whole entry but this # is designed for existing content. @route("/api/v1/mods/<mod_id>/upload_content", methods=["POST"]) @json @requires_supporter @requires_login async def upload(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") abort(501, "Coming soon") @route("/api/v1/mods/<mod_id>/report", methods=["POST"]) @json @use_kwargs( { "content": fields.Str(required=True, validate=validate.Length(min=100, max=1000)), "type_": EnumField(ReportType, data_key="type", required=True), "recaptcha": fields.Str(required=True), }, locations=("json", ), ) @requires_login @limiter.limit("2 per hour") async def report_mod(self, mod_id: str, content: str, type_: ReportType, recaptcha: str): score = await verify_recaptcha(recaptcha, self.core.aioh_sess) if score < 0.5: # TODO: send email/other 2FA when below 0.5 abort(400, "Possibly a bot") user_id = await get_token_user() report = await Report.create(content=content, author_id=user_id, mod_id=mod_id, type=type_) return jsonify(report.to_dict())
class Feedback(db.Model): id = db.Column(db.Integer, primary_key=True) url = db.Column(db.String) referer = db.Column(db.String) timestamp = db.Column(db.DateTime) settings = db.Column(JSON) upvote = db.Column(db.Boolean) comment = db.Column(db.Text) @app.route('/feedback/', methods=['POST']) @use_kwargs({ 'url': fields.Url(required=True), 'referer': fields.Url(missing=None), 'upvote': fields.Boolean(missing=None), 'comment': fields.Str(missing=None), }) def submit_feedback(**kwargs): kwargs.update({ 'timestamp': datetime.datetime.utcnow(), 'settings': { 'headers': dict(request.headers), }, }) feedback = Feedback(**kwargs) db.session.add(feedback) db.session.commit() return jsonify({'status': 'success'}), 201
'order': fields.String(missing='asc', validate=validate.OneOf(['asc', 'desc'])), 'rettype': fields.String(allow_none=True, validate=validate.OneOf(['cube', 'spaxel', 'maps', 'rss', 'modelcube'])), 'params': fields.DelimitedList(fields.String(), allow_none=True), 'return_all': fields.Boolean(allow_none=True), 'format_type': fields.String(allow_none=True, validate=validate.OneOf(['list', 'listdict', 'dictlist'])), 'caching': fields.Boolean(allow_none=True) }, 'search': {'searchbox': fields.String(required=True), 'parambox': fields.DelimitedList(fields.String(), allow_none=True) }, 'index': {'galid': fields.String(allow_none=True, validate=[validate.Length(min=4), validate.Regexp('^[0-9-]*$')]), 'mplselect': fields.String(allow_none=True, validate=validate.Regexp('MPL-[1-9]')) }, 'galaxy': {'plateifu': fields.String(allow_none=True, validate=validate.Length(min=8, max=11)), 'toggleon': fields.String(allow_none=True, validate=validate.OneOf(['true', 'false'])), 'image': fields.Url(allow_none=True), 'imheight': fields.Integer(allow_none=True, validate=validate.Range(min=0, max=1000)), 'imwidth': fields.Integer(allow_none=True, validate=validate.Range(min=0, max=1000)), 'type': fields.String(allow_none=True, validate=validate.OneOf(['optical', 'heatmap'])), 'x': fields.String(allow_none=True), 'y': fields.String(allow_none=True), 'mousecoords[]': fields.List(fields.String(), allow_none=True), 'bintemp': fields.String(allow_none=True), 'params[]': fields.List(fields.String(), allow_none=True) } } # Add a custom Flask session location handler @parser.location_handler('session') def parse_session(req, name, field):
class Mods(RouteCog): @staticmethod def dict_all(models): return [m.to_dict() for m in models] @multiroute("/api/v1/mods", methods=["GET"], other_methods=["POST"]) @json @use_kwargs( { "q": fields.Str(), "page": fields.Int(missing=0), "limit": fields.Int(missing=50), "category": EnumField(ModCategory), "rating": fields.Int(validate=validate.OneOf([1, 2, 3, 4, 5])), "status": EnumField(ModStatus), "sort": EnumField(ModSorting), "ascending": fields.Bool(missing=False) }, locations=("query", )) async def get_mods(self, q: str = None, page: int = None, limit: int = None, category: ModCategory = None, rating: int = None, status: ModStatus = None, sort: ModSorting = None, ascending: bool = None): if not 1 <= limit <= 100: limit = max(1, min( limit, 100)) # Clamp `limit` to 1 or 100, whichever is appropriate page = page - 1 if page > 0 else 0 query = Mod.query.where(Mod.verified) if q is not None: query = query.where( and_(Mod.title.match(q), Mod.tagline.match(q), Mod.description.match(q))) if category is not None: query = query.where(Mod.status == category) if rating is not None: query = query.where(rating + 1 > db.select([ func.avg( Review.select('rating').where(Review.mod_id == Mod.id)) ]) >= rating) if status is not None: query = query.where(Mod.status == status) if sort is not None: sort_by = sorters[sort] query = query.order_by( sort_by.asc() if ascending else sort_by.desc()) results = await paginate(query, page, limit).gino.all() total = await query.alias().count().gino.scalar() return jsonify(total=total, page=page, limit=limit, results=self.dict_all(results)) @multiroute("/api/v1/mods", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs( { "title": fields.Str(required=True, validate=validate.Length(max=64)), "tagline": fields.Str(required=True, validate=validate.Length(max=100)), "description": fields.Str(required=True, validate=validate.Length(max=10000)), "website": fields.Url(required=True), "authors": fields.List(fields.Nested(AuthorSchema), required=True), "status": EnumField(ModStatus, required=True), "icon": None }, locations=("json", )) async def post_mods(self, title: str, tagline: str, description: str, website: str, authors: List[dict], status: str, icon: str): token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] # TODO: maybe strip out stuff like whitespace and punctuation so people can't be silly. mods = await Mod.get_any(True, title=title).first() if mods is not None: abort(400, "A mod with that title already exists") mod = Mod(title=title, tagline=tagline, description=description, website=website, icon=icon, status=status) for i, author in enumerate(authors): if author["id"] == user_id: authors.pop(i) continue elif not await User.exists(author["id"]): abort(400, f"Unknown user '{author['id']}'") authors.append({"id": user_id, "role": AuthorRole.Owner}) await mod.create() await ModAuthors.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) return jsonify(mod.to_dict()) @route("/api/v1/mods/recent_releases") @json async def get_recent_releases(self): mods = await Mod.query.where(Mod.verified ).order_by(Mod.released_at.desc() ).limit(10).gino.all() return jsonify(self.dict_all(mods)) @route("/api/v1/mods/popular") @json async def get_popular(self): mods = await Mod.query.where( and_(Mod.verified, Mod.released_at is not None) ).order_by(Mod.downloads.desc()).limit(10).gino.all() return jsonify(self.dict_all(mods)) @multiroute("/api/v1/mods/<mod_id>", methods=["GET"], other_methods=["PATCH"]) @json async def get_mod(self, mod_id: str): mod = await Mod.get(mod_id) if mod is None: abort(404, "Unknown mod") return jsonify(mod.to_dict()) @multiroute("/api/v1/mods/<mod_id>", methods=["PATCH"], other_methods=["GET"]) @requires_login @json @use_kwargs( { "title": fields.Str(validate=validate.Length(max=64)), "tagline": fields.Str(validate=validate.Length(max=100)), "description": fields.Str(validate=validate.Length(max=10000)), "website": fields.Url(), "authors": fields.List(fields.Nested(AuthorSchema)), "status": EnumField(ModStatus), "icon": None }, locations=("json", )) async def patch_mod(self, mod_id: str = None, **kwargs): if not await Mod.exists(mod_id): abort(404, "Unknown mod") mod = await Mod.get(mod_id) updates = mod.update() authors = kwargs.pop('authors') if 'authors' in kwargs else None for attr, item in kwargs.items(): updates = updates.update(**{attr: item}) if authors is not None: authors = [ author for author in authors if await User.exists(author["id"]) ] # TODO: if user is owner or co-owner, allow them to change the role of others to ones below them. authors = [ author for author in authors if not await ModAuthors.query.where( and_(ModAuthors.user_id == author["id"], ModAuthors.mod_id == mod_id)).gino.first() ] await updates.apply() await ModAuthors.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) return jsonify(mod.to_dict()) @route("/api/v1/mods/<mod_id>/download") @json async def get_download(self, mod_id: str): mod = await Mod.get(mod_id) if mod is None: abort(404, "Unknown mod") elif not mod.zip_url: abort(404, "Mod has no download") return jsonify(url=mod.zip_url) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["GET"], other_methods=["POST"]) @json async def get_reviews(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") reviews = await Review.query.where(Review.mod_id == mod_id).gino.all() return jsonify(self.dict_all(reviews)) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs({ "rating": fields.Int(required=True, validate=[lambda x: 5 >= x >= 1, lambda x: x % 0.5 == 0]), "content": fields.Str(required=True, validate=validate.Length(max=2000)) }) async def post_review(self, mod_id: str, rating: int, content: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] review = await Review.create(content=content, rating=rating, author_id=user_id, mod_id=mod_id) return jsonify(review.to_json()) @route("/api/v1/mods/<mod_id>/authors") @json async def get_authors(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") author_pairs = await ModAuthors.query.where(ModAuthors.mod_id == mod_id ).gino.all() author_pairs = [x.user_id for x in author_pairs] authors = await User.query.where(User.id.in_(author_pairs)).gino.all() return jsonify(self.dict_all(authors)) # This handles POST requests to add zip_url. # Usually this would be done via a whole entry but this # is designed for existing content. @route("/api/v1/mods/<mod_id>/upload_content", methods=["POST"]) @json @requires_supporter @requires_login async def upload(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") abort(501, "Coming soon")
if not link: return { 'alias': alias, 'err_code': '002', 'description': 'Shortened URL not found' }, 404 link.increment_visits() return redirect(link.long_url, code=302) @short.route('/addlink', methods=['POST']) @use_kwargs( { 'url': fields.Url(required=True), 'custom_alias': fields.Str(required=False) }, location='query') def add_link(**kwargs): """ Shorten URL Endpoint """ start_time = datetime.now() if 'custom_alias' in kwargs: alias = kwargs['custom_alias'] else: alias = shorten_url(kwargs['url']) new_short_url = Link(long_url=kwargs['url'], alias=alias) new_short_url.save_to_database()
class Mods(RouteCog): @staticmethod def dict_all(models): return [m.to_dict() for m in models] @multiroute("/api/v1/mods", methods=["GET"], other_methods=["POST"]) @json @use_kwargs( { "q": fields.Str(), "page": fields.Int(missing=0), "limit": fields.Int(missing=50), "category": EnumField(ModCategory), "rating": fields.Int(validate=validate.OneOf([1, 2, 3, 4, 5])), "status": EnumField(ModStatus), "sort": EnumField(ModSorting), "ascending": fields.Bool(missing=False) }, locations=("query", )) async def get_mods(self, q: str = None, page: int = None, limit: int = None, category: ModCategory = None, rating: int = None, status: ModStatus = None, sort: ModSorting = None, ascending: bool = None): if not 1 <= limit <= 100: limit = max(1, min( limit, 100)) # Clamp `limit` to 1 or 100, whichever is appropriate page = page - 1 if page > 0 else 0 query = Mod.query.where(Mod.verified) if q is not None: like = f"%{q}%" query = query.where( and_(Mod.title.match(q), Mod.tagline.match(q), Mod.description.match(q), Mod.title.ilike(like), Mod.tagline.ilike(like), Mod.description.ilike(like))) if category is not None: query = query.where(Mod.status == category) if rating is not None: query = query.where(rating + 1 > db.select([ func.avg( Review.select("rating").where(Review.mod_id == Mod.id)) ]) >= rating) if status is not None: query = query.where(Mod.status == status) if sort is not None: sort_by = mod_sorters[sort] query = query.order_by( sort_by.asc() if ascending else sort_by.desc()) results = await paginate(query, page, limit).gino.all() total = await query.alias().count().gino.scalar() return jsonify(total=total, page=page, limit=limit, results=self.dict_all(results)) @multiroute("/api/v1/mods", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs( { "title": fields.Str(required=True, validate=validate.Length(max=64)), "tagline": fields.Str(required=True, validate=validate.Length(max=100)), "description": fields.Str(required=True, validate=validate.Length(min=100, max=10000)), "website": fields.Url(required=True), "status": EnumField(ModStatus, required=True), "category": EnumField(ModCategory, required=True), "authors": fields.List(fields.Nested(AuthorSchema), required=True), "icon": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`icon` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'")), required=True), "banner": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`banner` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'"), ), required=True), "is_private_beta": fields.Bool(missing=False), "mod_playtester": fields.List(fields.Str()), "color": EnumField(ModColor, missing=ModColor.default), "recaptcha": fields.Str(required=True) }, locations=("json", )) async def post_mods(self, title: str, tagline: str, description: str, website: str, authors: List[dict], status: ModStatus, category: ModCategory, icon: str, banner: str, recaptcha: str, color: ModColor, is_private_beta: bool = None, mod_playtester: List[str] = None): score = await verify_recaptcha(recaptcha, self.core.aioh_sess, 3, "create_mod") if score < 0.5: # TODO: discuss what to do here abort(400, "Possibly a bot") token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] # Check if any mod with a similar enough name exists already. generalized_title = generalize_text(title) mods = await Mod.get_any(True, generalized_title=generalized_title).first() if mods is not None: abort(400, "A mod with that title already exists") if status is ModStatus.archived: abort(400, "Can't create a new archived mod") mod = Mod(title=title, tagline=tagline, description=description, website=website, status=status, category=category, theme_color=color) icon_mimetype, icon_data = validate_img(icon, "icon") banner_mimetype, banner_data = validate_img(banner, "banner") for i, author in enumerate(authors): if author["id"] == user_id: authors.pop(i) continue elif not await User.exists(author["id"]): abort(400, f"Unknown user '{author['id']}'") authors.append({"id": user_id, "role": AuthorRole.owner}) if is_private_beta is not None: mod.is_private_beta = is_private_beta if mod_playtester is not None: if not is_private_beta: abort(400, "No need for `ModPlaytester` if open beta") for playtester in mod_playtester: if not await User.exists(playtester): abort(400, f"Unknown user '{playtester}'") # Decode images and add name for mimetypes icon_data = base64.b64decode(icon_data) banner_data = base64.b64decode(banner_data) icon_ext = icon_mimetype.split("/")[1] banner_ext = banner_mimetype.split("/")[1] icon_data = NamedBytes(icon_data, name=f"icon.{icon_ext}") banner_data = NamedBytes(banner_data, name=f"banner.{banner_ext}") img_urls = await owo.async_upload_files(icon_data, banner_data) mod.icon = img_urls[icon_data.name] mod.banner = img_urls[banner_data.name] await mod.create() await ModAuthor.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) if ModPlaytester is not None: await ModPlaytester.insert().gino.all( * [dict(user_id=user, mod_id=mod.id) for user in mod_playtester]) return jsonify(mod.to_dict()) @route("/api/v1/mods/recent_releases") @json async def get_recent_releases(self): mods = await Mod.query.where( and_(Mod.verified, Mod.status == ModStatus.released) ).order_by(Mod.released_at.desc()).limit(10).gino.all() return jsonify(self.dict_all(mods)) @route("/api/v1/mods/most_loved") @json async def get_most_loved(self): love_counts = select( [func.count()]).where(UserFavorite.mod_id == Mod.id).as_scalar() mods = await Mod.query.order_by(love_counts.desc() ).limit(10).gino.all() return jsonify(self.dict_all(mods)) @route("/api/v1/mods/most_downloads") @json async def get_most_downloads(self): mods = await Mod.query.where( and_(Mod.verified, Mod.released_at is not None) ).order_by(Mod.downloads.desc()).limit(10).gino.all() return jsonify(self.dict_all(mods)) @route("/api/v1/mods/trending") @json async def get_trending(self): # TODO: implement return jsonify([]) @multiroute("/api/v1/mods/<mod_id>", methods=["GET"], other_methods=["PATCH", "DELETE"]) @json async def get_mod(self, mod_id: str): mod = await Mod.get(mod_id) if mod is None: abort(404, "Unknown mod") return jsonify(mod.to_dict()) @multiroute("/api/v1/mods/<mod_id>", methods=["PATCH"], other_methods=["GET", "DELETE"]) @requires_login @json @use_kwargs( { "title": fields.Str(validate=validate.Length(max=64)), "tagline": fields.Str(validate=validate.Length(max=100)), "description": fields.Str(validate=validate.Length(min=100, max=10000)), "website": fields.Url(), "status": EnumField(ModStatus), "category": EnumField(ModCategory), "authors": fields.List(fields.Nested(AuthorSchema)), "icon": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`icon` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'"))), "banner": fields.Str(validate=validate.Regexp( DATA_URI_RE, error= ("`banner` should be a data uri like 'data:image/png;base64,<data>' or " "'data:image/jpeg;base64,<data>'"), )), "color": EnumField(ModColor), "is_private_beta": fields.Bool(), "mod_playtester": fields.List(fields.Str()) }, locations=("json", )) async def patch_mod(self, mod_id: str = None, authors: List[dict] = None, mod_playtester: List[str] = None, icon: str = None, banner: str = None, **kwargs): if not await Mod.exists(mod_id): abort(404, "Unknown mod") mod = await Mod.get(mod_id) updates = mod.update(**kwargs) if authors is not None: authors = [ author for author in authors if await User.exists(author["id"]) ] # TODO: if user is owner or co-owner, allow them to change the role of others to ones below them. authors = [ author for author in authors if not await ModAuthor.query.where( and_(ModAuthor.user_id == author["id"], ModAuthor.mod_id == mod_id)).gino.first() ] if mod_playtester is not None: for playtester in mod_playtester: if not await User.exists(playtester): abort(400, f"Unknown user '{playtester}'") elif await ModPlaytester.query.where( and_(ModPlaytester.user_id == playtester, ModPlaytester.mod_id == mod.id)).gino.all(): abort(400, f"{playtester} is already enrolled.") to_upload = [] if icon is not None: icon_mimetype, icon_data = validate_img(icon, "icon") icon_data = base64.b64decode(icon_data) icon_ext = icon_mimetype.split("/")[1] icon_data = NamedBytes(icon_data, name=f"icon.{icon_ext}") to_upload.append(icon_data) if banner is not None: banner_mimetype, banner_data = validate_img(banner, "banner") banner_data = base64.b64decode(banner_data) banner_ext = banner_mimetype.split("/")[1] banner_data = NamedBytes(banner_data, name=f"banner.{banner_ext}") to_upload.append(banner_data) img_urls = await owo.async_upload_files(*to_upload) img_updates = {} if icon is not None: img_updates["icon"] = img_urls[icon_data.name] if banner is not None: img_updates["banner"] = img_urls[banner_data.name] # Lump together image updates because lessening operations or some shit. updates = updates.update(**img_updates) await updates.apply() await ModAuthor.insert().gino.all(*[ dict(user_id=author["id"], mod_id=mod.id, role=author["role"]) for author in authors ]) await ModPlaytester.insert().gino.all( *[dict(user_id=user, mod_id=mod.id) for user in ModPlaytester]) return jsonify(mod.to_dict()) # TODO: decline route with reason, maybe doesn't 100% delete it? idk @multiroute("/api/v1/mods/<mod_id>", methods=["DELETE"], other_methods=["GET", "PATCH"]) @requires_login @json async def delete_mod(self, mod_id: str): await Mod.delete.where(Mod.id == mod_id).gino.status() return jsonify(True) @route("/api/v1/mods/<mod_id>/download") @json async def get_download(self, mod_id: str): token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] mod = await Mod.get(mod_id) if mod is None: abort(404, "Unknown mod") if user_id is None and mod.is_private_beta: abort(403, "Private beta mods requires authentication.") if not await ModPlaytester.query.where(and_(ModPlaytester.user_id == user_id, ModPlaytester.mod_id == mod.id))\ .gino.all(): abort(403, "You are not enrolled to the private beta.") elif not mod.zip_url: abort(404, "Mod has no download") return jsonify(url=mod.zip_url) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["GET"], other_methods=["POST"]) @json @use_kwargs( { "page": fields.Int(missing=0), "limit": fields.Int(missing=10), # Probably won't work right now, will need union field. "rating": fields.Int(validate=validate.OneOf([1, 2, 3, 4, 5, "all"]), missing="all"), "sort": EnumField(ReviewSorting, missing=ReviewSorting.best) }, locations=("query", )) async def get_reviews(self, mod_id: str, page: int, limit: int, rating: Union[int, str], sort: ReviewSorting): if not await Mod.exists(mod_id): abort(404, "Unknown mod") if not 1 <= limit <= 25: limit = max(1, min( limit, 25)) # Clamp `limit` to 1 or 100, whichever is appropriate page = page - 1 if page > 0 else 0 query = Review.query.where(Review.mod_id == mod_id) if review_sorters[sort]: query = query.order_by(review_sorters[sort]) elif sort == ReviewSorting.best: upvoters_count = select([func.count()]).where( and_(ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.upvote)).as_scalar() downvoters_count = select([func.count()]).where( and_(ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.downvote)).as_scalar() query = query.order_by(upvoters_count - downvoters_count) elif sort == ReviewSorting.funniest: # Get count of all funny ratings by review. sub_order = select([func.count()]).where( and_(ReviewReaction.review_id == Review.id, ReviewReaction.reaction == ReactionType.funny)).as_scalar() query = query.order_by(sub_order.desc()) if isinstance(rating, int): values = [rating, rating + 0.5] if rating == 1: # Also get reviews with a 0.5 star rating, otherwise they'll never appear. values.append(0.5) query = query.where(Review.rating.in_(values)) reviews = await query.gino.all() return jsonify(self.dict_all(reviews)) @multiroute("/api/v1/mods/<mod_id>/reviews", methods=["POST"], other_methods=["GET"]) @requires_login @json @use_kwargs({ "rating": fields.Int( required=True, validate=[ # Only allow increments of 0.5, up to 5. lambda x: 5 >= x >= 1, lambda x: x % 0.5 == 0 ]), "content": fields.Str(required=True, validate=validate.Length(max=2000)), "title": fields.Str(required=True, validate=validate.Length(max=32)) }) async def post_review(self, mod_id: str, rating: int, content: str, title: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] if await Review.query.where( and_(Review.author_id == user_id, Review.mod_id == mod_id)).gino.first(): abort(400, "Review already exists") review = await Review.create(title=title, content=content, rating=rating, author_id=user_id, mod_id=mod_id) return jsonify(review.to_json()) @route("/api/v1/mods/<mod_id>/authors") @json async def get_authors(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") author_pairs = await ModAuthor.query.where(ModAuthor.mod_id == mod_id ).gino.all() author_pairs = [x.user_id for x in author_pairs] authors = await User.query.where(User.id.in_(author_pairs)).gino.all() return jsonify(self.dict_all(authors)) # This handles POST requests to add zip_url. # Usually this would be done via a whole entry but this # is designed for existing content. @route("/api/v1/mods/<mod_id>/upload_content", methods=["POST"]) @json @requires_supporter @requires_login async def upload(self, mod_id: str): if not await Mod.exists(mod_id): abort(404, "Unknown mod") abort(501, "Coming soon") @route("/api/v1/mods/<mod_id>/report", methods=["POST"]) @json @use_kwargs( { "content": fields.Str(required=True, validate=validate.Length(min=100, max=1000)), "type_": EnumField(ReportType, required=True), "recaptcha": fields.Str(required=True) }, locations=("json", )) @requires_login @limiter.limit("2 per hour") async def report_mod(self, mod_id: str, content: str, type_: ReportType, recaptcha: str): await verify_recaptcha(recaptcha, self.core.aioh_sess, 2) token = request.headers.get("Authorization", request.cookies.get("token")) parsed_token = await jwt_service.verify_login_token(token, True) user_id = parsed_token["id"] report = await Report.create(content=content, author_id=user_id, mod_id=mod_id, type=type_) return jsonify(report.to_dict())
predict_args = {'trained_graph': fields.Str(missing='1540408813_cpu', enum=['1540408813_cpu', '1533577729_gpu'], description='Pre-trained graph to use', required=False ), 'files': fields.Field( required=False, missing=None, type="file", data_key="data", location="form", description="Select the image you want to classify." ), 'urls': fields.Url( required=False, missing=None, description="Select an URL of the image you want to classify." ) } class PredictArgsSchema(Schema): class Meta: unknown = INCLUDE # supports extra parameters trained_graph = fields.Str( required=False, missing='1540408813_cpu', enum=['1540408813_cpu', '1533577729_gpu'], description="Pre-trained graph to use"