Пример #1
0
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
Пример #2
0
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)
Пример #3
0
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"
    )
Пример #4
0
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."
    )
Пример #5
0
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")
Пример #6
0
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()
Пример #7
0
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"
    )
Пример #8
0
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.")
Пример #9
0
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})
Пример #10
0
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)
Пример #11
0
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")
Пример #12
0
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)
Пример #13
0
         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),
Пример #14
0
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())
Пример #15
0
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
Пример #16
0
                    '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):
Пример #17
0
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")
Пример #18
0
    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()
Пример #19
0
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())
Пример #20
0
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"