예제 #1
0
import yaml
from ubuntu_release_info.data import Data
from canonicalwebteam.store_api.stores.snapstore import SnapStore
from canonicalwebteam.launchpad import Launchpad
from geolite2 import geolite2
from requests.exceptions import HTTPError


# Local
from webapp.login import empty_session, user_info
from webapp.advantage import AdvantageContracts


ip_reader = geolite2.reader()
session = talisker.requests.get_session()
store_api = SnapStore(session=talisker.requests.get_session())


def download_thank_you(category):
    context = {"http_host": flask.request.host}

    version = flask.request.args.get("version", "")
    architecture = flask.request.args.get("architecture", "")

    # Sanitise for paths
    # (https://bugs.launchpad.net/ubuntu-website-content/+bug/1586361)
    version_pattern = re.compile(r"(\d+(?:\.\d+)+).*")
    architecture = architecture.replace("..", "")
    architecture = architecture.replace("/", "+").replace(" ", "+")

    if architecture and version_pattern.match(version):
예제 #2
0
def store_blueprint(store_query=None):
    api = SnapStore(session, store_query)

    store = flask.Blueprint(
        "store",
        __name__,
        template_folder="/templates",
        static_folder="/static",
    )

    def _handle_error(api_error: StoreApiError):
        status_code = 502
        error = {"message": str(api_error)}

        if type(api_error) is StoreApiTimeoutError:
            status_code = 504
        elif type(api_error) is StoreApiResponseDecodeError:
            status_code = 502
        elif type(api_error) is StoreApiResponseErrorList:
            error["errors"] = api_error.errors
            status_code = 502
        elif type(api_error) is StoreApiResponseError:
            status_code = 502
        elif type(api_error) is StoreApiConnectionError:
            status_code = 502
        elif type(api_error) is StoreApiCircuitBreaker:
            # Special case for this one, because it is the only case where we
            # don't want the user to be able to access the page.
            return flask.abort(503)

        return status_code, error

    snap_details_views(store, api, _handle_error)

    @store.route("/discover")
    def discover():
        return flask.redirect(flask.url_for(".homepage"))

    def store_view():
        error_info = {}
        status_code = 200

        try:
            categories_results = api.get_categories()
        except StoreApiError:
            categories_results = []

        categories = logic.get_categories(categories_results)

        try:
            featured_snaps = api.get_featured_items()["results"]
        except (StoreApiError, ApiError) as api_error:
            status_code, error_info = _handle_error(api_error)
            return flask.abort(status_code)

        if not featured_snaps:
            return flask.abort(503)

        # if the first snap (banner snap) doesn't have an icon, remove the last
        # snap from the list to avoid a hanging snap (grid of 9)
        if len(featured_snaps) == 10 and featured_snaps[0]["icon_url"] == "":
            featured_snaps = featured_snaps[:-1]

        for index in range(len(featured_snaps)):
            featured_snaps[index] = logic.get_snap_banner_url(
                featured_snaps[index])

        livestream = snapcraft_logic.get_livestreams()

        return (
            flask.render_template(
                "store/store.html",
                categories=categories,
                has_featured=True,
                featured_snaps=featured_snaps,
                error_info=error_info,
                livestream=livestream,
            ),
            status_code,
        )

    def brand_store_view():
        error_info = {}
        status_code = 200

        try:
            snaps = api.get_all_items(size=16)["results"]
        except (StoreApiError, ApiError) as api_error:
            snaps = []
            status_code, error_info = _handle_error(api_error)

        return (
            flask.render_template("brand-store/store.html",
                                  snaps=snaps,
                                  error_info=error_info),
            status_code,
        )

    def search_snap():
        snap_searched = flask.request.args.get("q", default="", type=str)
        snap_category = flask.request.args.get("category",
                                               default="",
                                               type=str)
        page = flask.request.args.get("page", default=1, type=int)

        if snap_category:
            snap_category_display = snap_category.capitalize().replace(
                "-", " ")
        else:
            snap_category_display = None

        if not snap_searched and not snap_category:
            return flask.redirect(flask.url_for(".homepage"))

        # The default size is 44
        # (11 rows of 4) - on search results pages
        # or (1 + 5 rows of 3 + 7 rows of 4) - on category page 1

        size = 44

        try:
            searched_results = api.search(
                snap_searched,
                category=snap_category,
                size=size,
                page=page,
            )
        except (StoreApiError, ApiError) as api_error:
            status_code, error_info = _handle_error(api_error)
            return flask.abort(status_code)

        total_pages = None

        if "total" in searched_results:
            total_results_count = searched_results["total"]
            total_pages = ceil(total_results_count / size)
        else:
            total_results_count = None

        snaps_results = searched_results["results"]

        links = {}

        if page > 1:
            links["first"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=1,
            )
            links["prev"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=page - 1,
            )

        if not total_pages or page < total_pages:
            links["next"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=page + 1,
            )
            if total_pages:
                links["last"] = logic.build_pagination_link(
                    snap_searched=snap_searched,
                    snap_category=snap_category,
                    page=total_pages,
                )

        featured_snaps = []

        # These are the hand-selected "featured snaps" in each category.
        # We don't have this information on the API, so it's hardcoded.
        number_of_featured_snaps = 16

        if snap_category_display and page == 1:
            if snaps_results and snaps_results[0]:
                if snaps_results[0]["icon_url"] == "":
                    snaps_results = logic.promote_snap_with_icon(snaps_results)

                snaps_results[0] = logic.get_snap_banner_url(snaps_results[0])

                if (snap_category == "featured"
                        or len(snaps_results) < number_of_featured_snaps):
                    featured_snaps = snaps_results
                    snaps_results = []
                else:
                    featured_snaps = snaps_results[:number_of_featured_snaps]
                    snaps_results = snaps_results[number_of_featured_snaps:]

        context = {
            "query": snap_searched,
            "category": snap_category,
            "category_display": snap_category_display,
            "searched_snaps": snaps_results,
            "featured_snaps": featured_snaps,
            "total": total_results_count,
            "links": links,
            "page": page,
        }

        return flask.render_template("store/search.html", **context)

    def brand_search_snap():
        status_code = 200
        snap_searched = flask.request.args.get("q", default="", type=str)

        if not snap_searched:
            return flask.redirect(flask.url_for(".homepage"))

        size = flask.request.args.get("limit", default=25, type=int)
        offset = flask.request.args.get("offset", default=0, type=int)

        try:
            page = floor(offset / size) + 1
        except ZeroDivisionError:
            size = 10
            page = floor(offset / size) + 1

        error_info = {}
        searched_results = []

        try:
            searched_results = api.search(snap_searched, size=size, page=page)
        except (StoreApiError, ApiError) as api_error:
            status_code, error_info = _handle_error(api_error)

        snaps_results = searched_results["results"]
        links = logic.get_pages_details(
            flask.request.base_url,
            (searched_results["_links"]
             if "_links" in searched_results else []),
        )

        context = {
            "query": snap_searched,
            "snaps": snaps_results,
            "links": links,
            "error_info": error_info,
        }

        return (
            flask.render_template("brand-store/search.html", **context),
            status_code,
        )

    @store.route("/youtube/<video_id>")
    def get_video_thumbnail_data(video_id):
        thumbnail_url = "https://www.googleapis.com/youtube/v3/videos"
        thumbnail_data = session.get(
            f"{thumbnail_url}?id={video_id}&part=snippet&key={YOUTUBE_API_KEY}"
        )

        if thumbnail_data:
            return thumbnail_data.json()

        return {}

    @store.route("/publisher/<regex('[a-z0-9-]*[a-z][a-z0-9-]*'):publisher>")
    def publisher_details(publisher):
        """
        A view to display the publisher details page for specific publisher.
        """

        publisher_content_path = flask.current_app.config["CONTENT_DIRECTORY"][
            "PUBLISHER_PAGES"]

        context = helpers.get_yaml(publisher_content_path + publisher +
                                   ".yaml",
                                   typ="safe")

        if not context:
            flask.abort(404)

        if "publishers" in context:
            context["snaps"] = []
            for publisher in context["publishers"]:
                snaps_results = []
                try:
                    snaps_results = api.get_publisher_items(publisher,
                                                            size=500,
                                                            page=1)["results"]
                except StoreApiError:
                    pass

                context["snaps"].extend(
                    [snap for snap in snaps_results if snap["apps"]])

        if "snaps" not in context:
            snaps = helpers.get_yaml(publisher_content_path + publisher +
                                     "-snaps.yaml",
                                     typ="safe")

            context["snaps"] = snaps["snaps"]

        featured_snaps = [
            snap["package_name"] for snap in context["featured_snaps"]
        ]

        context["snaps"] = [
            snap for snap in context["snaps"]
            if snap["package_name"] not in featured_snaps
        ]

        return flask.render_template("store/publisher-details.html", **context)

    @store.route("/store/categories/<category>")
    def store_category(category):
        status_code = 200
        error_info = {}
        snaps_results = []

        try:
            snaps_results = api.get_category_items(category=category,
                                                   size=10,
                                                   page=1)["results"]
        except (StoreApiError, ApiError) as api_error:
            status_code, error_info = _handle_error(api_error)

        # if the first snap (banner snap) doesn't have an icon, remove the last
        # snap from the list to avoid a hanging snap (grid of 9)
        if len(snaps_results) == 10 and snaps_results[0]["icon_url"] == "":
            snaps_results = snaps_results[:-1]

        for index in range(len(snaps_results)):
            snaps_results[index] = logic.get_snap_banner_url(
                snaps_results[index])

        context = {
            "category": category,
            "has_featured": True,
            "snaps": snaps_results,
            "error_info": error_info,
        }

        return (
            flask.render_template("store/_category-partial.html", **context),
            status_code,
        )

    @store.route("/store/featured-snaps/<category>")
    def featured_snaps_in_category(category):
        snaps_results = []

        try:
            snaps_results = api.get_category_items(category=category,
                                                   size=3,
                                                   page=1)["results"]
        except (StoreApiError, ApiError) as api_error:
            status_code, error_info = _handle_error(api_error)
            return (
                flask.jsonify({"error": error_info}),
                status_code,
            )

        return flask.jsonify(snaps_results)

    @store.route("/store/sitemap.xml")
    def sitemap():
        base_url = "https://snapcraft.io/store"

        snaps = []
        page = 0
        url = f"https://api.snapcraft.io/api/v1/snaps/search?page={page}"
        while url:
            response = session.get(url)
            try:
                snaps_response = response.json()
            except Exception:
                continue

            for snap in snaps_response["_embedded"]["clickindex:package"]:
                try:
                    last_udpated = (parser.parse(snap["last_updated"]).replace(
                        tzinfo=None).strftime("%Y-%m-%d"))
                    snaps.append({
                        "url":
                        "https://snapcraft.io/" + snap["package_name"],
                        "last_udpated":
                        last_udpated,
                    })
                except Exception:
                    continue
            if "next" in snaps_response["_links"]:
                url = snaps_response["_links"]["next"]["href"]
            else:
                url = None

        xml_sitemap = flask.render_template(
            "sitemap/sitemap.xml",
            base_url=base_url,
            links=snaps,
        )

        response = flask.make_response(xml_sitemap)
        response.headers["Content-Type"] = "application/xml"
        response.headers["Cache-Control"] = "public, max-age=43200"

        return response

    if store_query:
        store.add_url_rule("/", "homepage", brand_store_view)
        store.add_url_rule("/search", "search", brand_search_snap)
    else:
        store.add_url_rule("/store", "homepage", store_view)
        store.add_url_rule("/search", "search", search_snap)

    return store
예제 #3
0
from webapp import helpers
from webapp.api import dashboard as api
from webapp.api import requests
from webapp.api.exceptions import ApiError, ApiResponseErrorList
from webapp.decorators import login_required
from webapp.markdown import parse_markdown_description
from webapp.publisher.snaps import logic, preview_data
from webapp.publisher.views import _handle_error, _handle_error_list
from webapp.store.logic import (
    filter_screenshots,
    get_categories,
    get_icon,
    get_videos,
)

store_api = SnapStore(talisker.requests.get_session(requests.Session))


def get_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".get_listing_snap", snap_name=snap_name))


def redirect_post_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".post_listing_snap", snap_name=snap_name))


@login_required
def get_listing_snap(snap_name):
    try:
예제 #4
0
# Local
from webapp import helpers
from webapp.helpers import api_publisher_session
from webapp.api.exceptions import ApiError
from webapp.decorators import login_required
from webapp.markdown import parse_markdown_description
from webapp.publisher.snaps import logic, preview_data
from webapp.publisher.views import _handle_error, _handle_error_list
from webapp.store.logic import (
    filter_screenshots,
    get_categories,
    get_icon,
    get_video,
)

store_api = SnapStore(api_publisher_session)
publisher_api = SnapPublisher(api_publisher_session)


def get_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".get_listing_snap", snap_name=snap_name))


def redirect_post_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".post_listing_snap", snap_name=snap_name))


@login_required
def get_listing_snap(snap_name):
예제 #5
0
def store_blueprint(store_query=None, testing=False):
    if testing:
        session = talisker.requests.get_session(requests.Session)
    else:
        session = talisker.requests.get_session(requests.CachedSession)

    api = SnapStore(session, store_query)

    store = flask.Blueprint(
        "store",
        __name__,
        template_folder="/templates",
        static_folder="/static",
    )

    def _handle_error(api_error: StoreApiError):
        status_code = 502
        error = {"message": str(api_error)}

        if type(api_error) is StoreApiTimeoutError:
            status_code = 504
        elif type(api_error) is StoreApiResponseDecodeError:
            status_code = 502
        elif type(api_error) is StoreApiResponseErrorList:
            error["errors"] = api_error.errors
            status_code = 502
        elif type(api_error) is StoreApiResponseError:
            status_code = 502
        elif type(api_error) is StoreApiConnectionError:
            status_code = 502
        elif type(api_error) is StoreApiCircuitBreaker:
            # Special case for this one, because it is the only case where we
            # don't want the user to be able to access the page.
            return flask.abort(503)

        return status_code, error

    snap_details_views(store, api, _handle_error)

    @store.route("/discover")
    def discover():
        return flask.redirect(flask.url_for(".homepage"))

    def store_view():
        error_info = {}
        status_code = 200

        try:
            categories_results = api.get_categories()
        except StoreApiError:
            categories_results = []

        categories = logic.get_categories(categories_results)

        try:
            featured_snaps_results = api.get_featured_items()
        except StoreApiError as api_error:
            status_code, error_info = _handle_error(api_error)
            return flask.abort(status_code)

        featured_snaps = logic.get_searched_snaps(featured_snaps_results)

        if not featured_snaps:
            return flask.abort(503)

        # if the first snap (banner snap) doesn't have an icon, remove the last
        # snap from the list to avoid a hanging snap (grid of 9)
        if len(featured_snaps) == 10 and featured_snaps[0]["icon_url"] == "":
            featured_snaps = featured_snaps[:-1]

        for index in range(len(featured_snaps)):
            featured_snaps[index] = logic.get_snap_banner_url(
                featured_snaps[index])

        livestream = snapcraft_logic.get_livestreams()

        return (
            flask.render_template(
                "store/store.html",
                categories=categories,
                has_featured=True,
                featured_snaps=featured_snaps,
                error_info=error_info,
                livestream=livestream,
            ),
            status_code,
        )

    def brand_store_view():
        error_info = {}
        status_code = 200

        try:
            snaps_results = api.get_all_items(size=16)
        except StoreApiError as api_error:
            snaps_results = []
            status_code, error_info = _handle_error(api_error)

        snaps = logic.get_searched_snaps(snaps_results)

        return (
            flask.render_template("brand-store/store.html",
                                  snaps=snaps,
                                  error_info=error_info),
            status_code,
        )

    def search_snap():
        status_code = 200
        snap_searched = flask.request.args.get("q", default="", type=str)
        snap_category = flask.request.args.get("category",
                                               default="",
                                               type=str)
        page = flask.request.args.get("page", default=1, type=int)

        if snap_category:
            snap_category_display = snap_category.capitalize().replace(
                "-", " ")
        else:
            snap_category_display = None

        if not snap_searched and not snap_category:
            return flask.redirect(flask.url_for(".homepage"))

        # The default size is 44
        # (11 rows of 4) - on search results pages
        # or (1 + 5 rows of 3 + 7 rows of 4) - on category page 1

        size = 44

        error_info = {}
        searched_results = []

        try:
            searched_results = api.search(
                quote_plus(snap_searched),
                category=snap_category,
                size=size,
                page=page,
            )
        except StoreApiError as api_error:
            status_code, error_info = _handle_error(api_error)

        total_pages = None

        if "total" in searched_results:
            total_results_count = searched_results["total"]
            total_pages = ceil(total_results_count / size)
        else:
            total_results_count = None

        snaps_results = logic.get_searched_snaps(searched_results)

        links = {}

        if page > 1:
            links["first"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=1,
            )
            links["prev"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=page - 1,
            )

        if not total_pages or page < total_pages:
            links["next"] = logic.build_pagination_link(
                snap_searched=snap_searched,
                snap_category=snap_category,
                page=page + 1,
            )
            if total_pages:
                links["last"] = logic.build_pagination_link(
                    snap_searched=snap_searched,
                    snap_category=snap_category,
                    page=total_pages,
                )

        featured_snaps = []

        # These are the hand-selected "featured snaps" in each category.
        # We don't have this information on the API, so it's hardcoded.
        number_of_featured_snaps = 16

        if snap_category_display and page == 1:
            if snaps_results and snaps_results[0]:
                if snaps_results[0]["icon_url"] == "":
                    snaps_results = logic.promote_snap_with_icon(snaps_results)

                snaps_results[0] = logic.get_snap_banner_url(snaps_results[0])

                if (snap_category == "featured"
                        or len(snaps_results) < number_of_featured_snaps):
                    featured_snaps = snaps_results
                    snaps_results = []
                else:
                    featured_snaps = snaps_results[:number_of_featured_snaps]
                    snaps_results = snaps_results[number_of_featured_snaps:]

        context = {
            "query": snap_searched,
            "category": snap_category,
            "category_display": snap_category_display,
            "searched_snaps": snaps_results,
            "featured_snaps": featured_snaps,
            "total": total_results_count,
            "links": links,
            "page": page,
            "error_info": error_info,
        }

        return (
            flask.render_template("store/search.html", **context),
            status_code,
        )

    def brand_search_snap():
        status_code = 200
        snap_searched = flask.request.args.get("q", default="", type=str)

        if not snap_searched:
            return flask.redirect(flask.url_for(".homepage"))

        size = flask.request.args.get("limit", default=25, type=int)
        offset = flask.request.args.get("offset", default=0, type=int)

        try:
            page = floor(offset / size) + 1
        except ZeroDivisionError:
            size = 10
            page = floor(offset / size) + 1

        error_info = {}
        searched_results = []

        try:
            searched_results = api.search(quote_plus(snap_searched),
                                          size=size,
                                          page=page)
        except StoreApiError as api_error:
            status_code, error_info = _handle_error(api_error)

        snaps_results = logic.get_searched_snaps(searched_results)
        links = logic.get_pages_details(
            flask.request.base_url,
            (searched_results["_links"]
             if "_links" in searched_results else []),
        )

        context = {
            "query": snap_searched,
            "snaps": snaps_results,
            "links": links,
            "error_info": error_info,
        }

        return (
            flask.render_template("brand-store/search.html", **context),
            status_code,
        )

    @store.route("/publisher/<regex('[a-z0-9-]*[a-z][a-z0-9-]*'):publisher>")
    def publisher_details(publisher):
        """
        A view to display the publisher details page for specific publisher.
        """

        publisher_content_path = flask.current_app.config["CONTENT_DIRECTORY"][
            "PUBLISHER_PAGES"]

        context = helpers.get_yaml(publisher_content_path + publisher +
                                   ".yaml",
                                   typ="safe")

        if not context:
            flask.abort(404)

        if "publishers" in context:
            context["snaps"] = []
            for publisher in context["publishers"]:
                searched_results = []
                try:
                    searched_results = api.get_publisher_items(publisher,
                                                               size=500,
                                                               page=1)
                except StoreApiError:
                    pass

                snaps_results = logic.get_searched_snaps(searched_results)
                context["snaps"].extend(
                    [snap for snap in snaps_results if snap["apps"]])

        if "snaps" not in context:
            snaps = helpers.get_yaml(publisher_content_path + publisher +
                                     "-snaps.yaml",
                                     typ="safe")

            context["snaps"] = snaps["snaps"]

        featured_snaps = [
            snap["package_name"] for snap in context["featured_snaps"]
        ]

        context["snaps"] = [
            snap for snap in context["snaps"]
            if snap["package_name"] not in featured_snaps
        ]

        return flask.render_template("store/publisher-details.html", **context)

    @store.route("/store/categories/<category>")
    def store_category(category):
        status_code = 200
        error_info = {}
        category_results = []

        try:
            category_results = api.get_category_items(category=category,
                                                      size=10,
                                                      page=1)
        except StoreApiError as api_error:
            status_code, error_info = _handle_error(api_error)

        snaps_results = logic.get_searched_snaps(category_results)

        # if the first snap (banner snap) doesn't have an icon, remove the last
        # snap from the list to avoid a hanging snap (grid of 9)
        if len(snaps_results) == 10 and snaps_results[0]["icon_url"] == "":
            snaps_results = snaps_results[:-1]

        for index in range(len(snaps_results)):
            snaps_results[index] = logic.get_snap_banner_url(
                snaps_results[index])

        context = {
            "category": category,
            "has_featured": True,
            "snaps": snaps_results,
            "error_info": error_info,
        }

        return (
            flask.render_template("store/_category-partial.html", **context),
            status_code,
        )

    if store_query:
        store.add_url_rule("/", "homepage", brand_store_view)
        store.add_url_rule("/search", "search", brand_search_snap)
    else:
        store.add_url_rule("/store", "homepage", store_view)
        store.add_url_rule("/search", "search", search_snap)

    return store
예제 #6
0
 def setUp(self):
     # configure self.attribute
     self.client = SnapStore()
     return super(SnapStoreTest, self).setUp()
예제 #7
0
# Local
from webapp import helpers
from webapp.helpers import api_session
from webapp.api.exceptions import ApiError
from webapp.decorators import login_required
from webapp.markdown import parse_markdown_description
from webapp.publisher.snaps import logic, preview_data
from webapp.publisher.views import _handle_error, _handle_error_list
from webapp.store.logic import (
    filter_screenshots,
    get_categories,
    get_icon,
    get_videos,
)

store_api = SnapStore(api_session)
publisher_api = SnapPublisher(api_session)


def get_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".get_listing_snap", snap_name=snap_name)
    )


def redirect_post_market_snap(snap_name):
    return flask.redirect(
        flask.url_for(".post_listing_snap", snap_name=snap_name)
    )