gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-blog] 05/08: actual blog logic


From: gnunet
Subject: [GNUnet-SVN] [taler-blog] 05/08: actual blog logic
Date: Fri, 24 Nov 2017 20:24:45 +0100

This is an automated email from the git hooks/post-receive script.

marcello pushed a commit to branch master
in repository blog.

commit 5b29c82bcd05efd43152ef0dd39e1d7f3b457933
Author: Marcello Stanisci <address@hidden>
AuthorDate: Fri Nov 24 20:00:37 2017 +0100

    actual blog logic
---
 talerblog/blog/blog.py    | 248 ++++++++++++++++++++++++++++++++++++++++++++++
 talerblog/blog/content.py | 131 ++++++++++++++++++++++++
 2 files changed, 379 insertions(+)

diff --git a/talerblog/blog/blog.py b/talerblog/blog/blog.py
new file mode 100644
index 0000000..02c6c2d
--- /dev/null
+++ b/talerblog/blog/blog.py
@@ -0,0 +1,248 @@
+# This file is part of GNU TALER.
+# Copyright (C) 2014-2017 INRIA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free 
Software
+# Foundation; either version 2.1, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+#
+# You should have received a copy of the GNU Lesser General Public License 
along with
+# GNU TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+# @author Florian Dold
+# @author Marcello Stanisci
+
+
+"""
+Implement URL handlers and payment logic for the blog merchant.
+"""
+
+import flask
+from urllib.parse import urljoin, urlencode, quote, parse_qsl
+import requests
+import logging
+import os
+import base64
+import random
+import time
+import json
+import datetime
+from pprint import pprint
+from talerfrontends.talerconfig import TalerConfig
+from talerfrontends.helpers import (make_url,
+        expect_parameter, join_urlparts, get_query_string,
+        backend_error)
+from talerfrontends.blog.content import (articles,
+get_article_file, get_image_file)
+
+logger = logging.getLogger(__name__)
+
+base_dir = os.path.dirname(os.path.abspath(__file__))
+
+app = flask.Flask(__name__, template_folder=base_dir)
+app.debug = True
+app.secret_key = base64.b64encode(os.urandom(64)).decode('utf-8')
+
+tc = TalerConfig.from_env()
+
+BACKEND_URL = tc["frontends"]["backend"].value_string(required=True)
+CURRENCY = tc["taler"]["currency"].value_string(required=True)
+INSTANCE = tc["blog"]["instance"].value_string(required=True)
+ARTICLE_AMOUNT = dict(value=1, fraction=0, currency=CURRENCY)
+
+app.config.from_object(__name__)
+
+
address@hidden
+def utility_processor():
+    def url(my_url):
+        return join_urlparts(flask.request.script_root, my_url)
+    def env(name, default=None):
+        return os.environ.get(name, default)
+    return dict(url=url, env=env)
+
+
address@hidden("/")
+def index():
+    return flask.render_template("templates/index.html",
+                                 merchant_currency=CURRENCY,
+                                 articles=articles.values())
+
+
address@hidden("/javascript")
+def javascript_licensing():
+    return flask.render_template("templates/javascript.html")
+
+# Triggers the refund by serving /refund/test?order_id=XY.
+# Will be triggered by a "refund button".
address@hidden("/refund", methods=["GET", "POST"])
+def refund():
+    if flask.request.method == "POST":
+        payed_articles = flask.session["payed_articles"] = 
flask.session.get("payed_articles", {})
+        article_name = flask.request.form.get("article_name")
+        if not article_name:
+            return flask.jsonify(dict(error="No article_name found in form")), 
400
+        logger.info("Looking for %s to refund" % article_name)
+        order_id = payed_articles.get(article_name)
+        if not order_id:
+            return flask.jsonify(dict(error="Aborting refund: article not 
payed")), 401
+        r = requests.post(urljoin(BACKEND_URL, "refund"),
+                          json=dict(order_id=order_id,
+                                    refund=dict(value=1, fraction=0, 
currency=CURRENCY),
+                                    reason="Demo reimbursement",
+                                    instance=INSTANCE))
+        if 200 != r.status_code:
+            return backend_error(r)
+        payed_articles[article_name] = "__refunded"
+        response = flask.make_response()
+        response.headers["X-Taler-Refund-Url"] = make_url("/refund", 
("order_id", order_id))
+        return response, 402
+
+    else:
+        order_id = expect_parameter("order_id", False)
+        if not order_id:
+            logger.error("Missing parameter 'order_id'")
+            return flask.jsonify(dict(error="Missing parameter 'order_id'")), 
400
+        r = requests.get(urljoin(BACKEND_URL, "refund"), 
params=dict(order_id=order_id,
+                                                                     
instance=INSTANCE))
+        if 200 != r.status_code:
+            return backend_error(r)
+        return flask.jsonify(r.json()), r.status_code
+
+
address@hidden("/generate-contract", methods=["GET"])
+def generate_contract():
+    article_name = expect_parameter("article_name")
+    pretty_name = article_name.replace("_", " ")
+    order = dict(
+        summary=pretty_name,
+        nonce=flask.request.args.get("nonce"),
+        amount=ARTICLE_AMOUNT,
+        max_fee=dict(value=1, fraction=0, currency=CURRENCY),
+        products=[
+            dict(
+                description="Essay: " + pretty_name,
+                quantity=1,
+                product_id=0,
+                price=ARTICLE_AMOUNT,
+            ),
+        ],
+        fulfillment_url=make_url("/essay/" + quote(article_name)),
+        pay_url=make_url("/pay"),
+        merchant=dict(
+            instance=INSTANCE,
+            address="nowhere",
+            name="Kudos Inc.",
+            jurisdiction="none",
+        ),
+        extra=dict(article_name=article_name),
+    )
+    r = requests.post(urljoin(BACKEND_URL, "proposal"), json=dict(order=order))
+    if r.status_code != 200:
+        return backend_error(r)
+    proposal_resp = r.json()
+    return flask.jsonify(**proposal_resp)
+
+
address@hidden("/cc-payment/<name>")
+def cc_payment(name):
+    return flask.render_template("templates/cc-payment.html",
+                                 article_name=name)
+
+
address@hidden("/essay/<name>")
address@hidden("/essay/<name>/data/<data>")
+def article(name, data=None):
+    logger.info("processing %s" % name)
+    payed_articles = flask.session.get("payed_articles", {})
+
+    if payed_articles.get(name, "") == "__refunded":
+        return flask.render_template("templates/article_refunded.html", 
article_name=name)
+
+    if name in payed_articles:
+        article = articles[name]
+        if article is None:
+            flask.abort(500)
+        if data is not None:
+            if data in article.extra_files:
+                return flask.send_file(get_image_file(data))
+            else:
+                return "permission denied", 400
+        return flask.render_template("templates/article_frame.html",
+                                     article_file=get_article_file(article),
+                                     article_name=name)
+
+    contract_url = make_url("/generate-contract", ("article_name",name))
+    response = 
flask.make_response(flask.render_template("templates/fallback.html"), 402)
+    response.headers["X-Taler-Contract-Url"] = contract_url
+    response.headers["X-Taler-Contract-Query"] = "fulfillment_url"
+    # Useless (?) header, as X-Taler-Contract-Url takes always (?) precedence
+    # over X-Offer-Url.  This one might only be useful if the contract 
retrieval
+    # goes wrong.
+    response.headers["X-Taler-Offer-Url"] = make_url("/essay/" + quote(name))
+    return response
+
+
address@hidden("/pay", methods=["POST"])
+def pay():
+    deposit_permission = flask.request.get_json()
+    if deposit_permission is None:
+        e = flask.jsonify(error="no json in body"),
+        return e, 400
+    r = requests.post(urljoin(BACKEND_URL, "pay"), json=deposit_permission)
+    if 200 != r.status_code:
+        return backend_error(r)
+    proposal_data = r.json()["contract_terms"]
+    article_name = proposal_data["extra"]["article_name"]
+    payed_articles = flask.session["payed_articles"] = 
flask.session.get("payed_articles", {})
+    if len(r.json()["refund_permissions"]) != 0:
+        # we had some refunds on the article purchase already!
+        logger.info("Article %s was refunded, before /pay" % article_name)
+        payed_articles[article_name] = "__refunded"
+        return flask.jsonify(r.json()), 200
+    if not deposit_permission["order_id"]:
+        logger.error("order_id missing from deposit_permission!")
+        return flask.jsonify(dict(error="internal error: ask for refund!")), 
500
+    if article_name not in payed_articles:
+        logger.info("Article %s goes in state" % article_name)
+        payed_articles[article_name] = deposit_permission["order_id"]
+    return flask.jsonify(r.json()), 200
+
+
address@hidden("/history")
+def history():
+    qs = get_query_string().decode("utf-8")
+    url = urljoin(BACKEND_URL, "history")
+    r = requests.get(url, params=dict(parse_qsl(qs)))
+    if 200 != r.status_code:
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
+
+
address@hidden("/backoffice")
+def track():
+    response = 
flask.make_response(flask.render_template("templates/backoffice.html"))
+    return response
+
+
address@hidden("/track/transfer")
+def track_transfer():
+    qs = get_query_string().decode("utf-8")
+    url = urljoin(BACKEND_URL, "track/transfer")
+    r = requests.get(url, params=dict(parse_qsl(qs)))
+    if 200 != r.status_code:
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
+
+
address@hidden("/track/order")
+def track_order():
+    qs = get_query_string().decode("utf-8")
+    url = urljoin(BACKEND_URL, "track/transaction")
+    r = requests.get(url, params=dict(parse_qsl(qs)))
+    if 200 != r.status_code:
+        return backend_error(r)
+    return flask.jsonify(r.json()), r.status_code
diff --git a/talerblog/blog/content.py b/talerblog/blog/content.py
new file mode 100644
index 0000000..1c9e98d
--- /dev/null
+++ b/talerblog/blog/content.py
@@ -0,0 +1,131 @@
+# This file is part of GNU TALER.
+# Copyright (C) 2014-2016 INRIA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free 
Software
+# Foundation; either version 2.1, or (at your option) any later version.
+#
+# TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+#
+# You should have received a copy of the GNU Lesser General Public License 
along with
+# GNU TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+# @author Florian Dold
+
+"""
+Define content and associated metadata that is served on the blog.
+"""
+
+from collections import OrderedDict
+from bs4 import BeautifulSoup
+from pkg_resources import resource_stream, resource_filename
+from collections import namedtuple
+import logging
+import os
+import re
+
+logger = logging.getLogger(__name__)
+
+Article = namedtuple("Article", "slug title teaser main_file extra_files")
+
+articles = OrderedDict()
+
+
+def add_article(slug, title, teaser, main_file, extra_files=[]):
+    articles[slug] = Article(slug, title, teaser, main_file, extra_files)
+
+
+def get_image_file(image):
+    f = resource_filename("talerfrontends", os.path.join("blog/data/", image)) 
+    return os.path.abspath(f)
+
+
+def get_article_file(article):
+    f = resource_filename("talerfrontends", article.main_file)
+    return os.path.basename(f)
+
+
+def add_from_html(resource_name, teaser_paragraph=0, title=None):
+    """
+    Extract information from article html.
+    """
+    res = resource_stream("talerfrontends", resource_name)
+    soup = BeautifulSoup(res, 'html.parser')
+    if title is None:
+        title_el = soup.find("h1", attrs={"class":["chapter", "unnumbered"]})
+        if title_el is None:
+            logger.warn("Can't extract title from '%s'", resource_name)
+            title = resource_name
+        else:
+            title = title_el.get_text().strip()
+    slug = title.replace(" ", "_")
+    paragraphs = soup.find_all("p")
+    
+    teaser = soup.find("p", attrs={"id":["teaser"]})
+    if teaser is None:
+        teaser = str(paragraphs[teaser_paragraph])
+    p = re.compile("^/essay/[^/]+/data/[^/]+$")
+    imgs = soup.find_all("img")
+    extra_files = []
+    for img in imgs:
+        # We require that any image whose access is regulated is src'd
+        # as "<slug>/data/img.png". We also need to check if the <slug>
+        # component actually matches the article's slug
+        if p.match(img['src']):
+            if img['src'].split(os.sep)[2] == slug:
+                logger.info("extra file for %s is %s" % (slug, 
os.path.basename(img['src'])))
+                extra_files.append(os.path.basename(img['src']))
+            else:
+                logger.warning("Image src and slug don't match: '%s' != '%s'" 
% (img['src'].split(os.sep)[2], slug))
+    add_article(slug, title, teaser, resource_name, extra_files)
+
+
+add_from_html("blog/articles/scrap1_U.0.html", 0)
+add_from_html("blog/articles/scrap1_U.1.html", 0)
+add_from_html("blog/articles/scrap1_1.html", 1)
+add_from_html("blog/articles/scrap1_2.html")
+add_from_html("blog/articles/scrap1_3.html")
+add_from_html("blog/articles/scrap1_4.html")
+add_from_html("blog/articles/scrap1_5.html")
+add_from_html("blog/articles/scrap1_6.html")
+add_from_html("blog/articles/scrap1_7.html")
+add_from_html("blog/articles/scrap1_8.html")
+add_from_html("blog/articles/scrap1_9.html")
+add_from_html("blog/articles/scrap1_10.html")
+add_from_html("blog/articles/scrap1_11.html")
+add_from_html("blog/articles/scrap1_12.html")
+add_from_html("blog/articles/scrap1_13.html", 1)
+add_from_html("blog/articles/scrap1_14.html")
+add_from_html("blog/articles/scrap1_15.html")
+add_from_html("blog/articles/scrap1_16.html")
+add_from_html("blog/articles/scrap1_17.html")
+add_from_html("blog/articles/scrap1_18.html")
+add_from_html("blog/articles/scrap1_19.html")
+add_from_html("blog/articles/scrap1_20.html", 1)
+add_from_html("blog/articles/scrap1_21.html")
+add_from_html("blog/articles/scrap1_22.html")
+add_from_html("blog/articles/scrap1_23.html")
+add_from_html("blog/articles/scrap1_24.html")
+add_from_html("blog/articles/scrap1_25.html", 1)
+add_from_html("blog/articles/scrap1_26.html", 1)
+add_from_html("blog/articles/scrap1_27.html")
+add_from_html("blog/articles/scrap1_28.html", 1)
+add_from_html("blog/articles/scrap1_29.html")
+add_from_html("blog/articles/scrap1_30.html", 1)
+add_from_html("blog/articles/scrap1_31.html", 1)
+add_from_html("blog/articles/scrap1_32.html")
+add_from_html("blog/articles/scrap1_33.html")
+add_from_html("blog/articles/scrap1_34.html")
+add_from_html("blog/articles/scrap1_35.html")
+add_from_html("blog/articles/scrap1_36.html")
+add_from_html("blog/articles/scrap1_37.html")
+add_from_html("blog/articles/scrap1_38.html")
+add_from_html("blog/articles/scrap1_39.html")
+add_from_html("blog/articles/scrap1_40.html")
+add_from_html("blog/articles/scrap1_41.html")
+add_from_html("blog/articles/scrap1_42.html")
+add_from_html("blog/articles/scrap1_43.html", 2)
+add_from_html("blog/articles/scrap1_46.html", 1)
+add_from_html("blog/articles/scrap1_47.html")

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]