Skip to content
Snippets Groups Projects
Commit aa854a68 authored by Pelle Koster's avatar Pelle Koster
Browse files

poc update visitor paid question using cli and/or webpage

parent 97dba260
No related branches found
No related tags found
No related merge requests found
...@@ -9,3 +9,5 @@ node_modules ...@@ -9,3 +9,5 @@ node_modules
docs/build docs/build
.idea .idea
.vscode .vscode
config.json
\ No newline at end of file
recursive-include stripe_checkout/public * recursive-include stripe_checkout/public *
recursive-include stripe_checkout/templates *
...@@ -9,11 +9,17 @@ setup( ...@@ -9,11 +9,17 @@ setup(
url=('TBD'), url=('TBD'),
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
'jsonschema', "jsonschema",
'flask', "flask",
'flask-cors', "flask-cors",
'stripe', "stripe",
'requests', "requests",
"click",
], ],
entry_points={
"console_scripts": [
"visit=stripe_checkout.visit_cli:cli",
]
},
include_package_data=True, include_package_data=True,
) )
...@@ -11,6 +11,15 @@ from stripe_checkout import environment ...@@ -11,6 +11,15 @@ from stripe_checkout import environment
from stripe_checkout import config from stripe_checkout import config
def register_routes(app):
from stripe_checkout.routes import api
from stripe_checkout.routes import visitors
app.register_blueprint(api.routes, url_prefix="/api")
app.register_blueprint(visitors.routes, url_prefix="/visitors")
return app
def create_app(): def create_app():
""" """
overrides default settings with those found overrides default settings with those found
...@@ -32,9 +41,8 @@ def create_app(): ...@@ -32,9 +41,8 @@ def create_app():
app.secret_key = 'super secret session key' app.secret_key = 'super secret session key'
app.config['CONFIG_PARAMS'] = app_config app.config['CONFIG_PARAMS'] = app_config
app.config["TEMPLATES_AUTO_RELOAD"] = True
from stripe_checkout.routes import api register_routes(app)
app.register_blueprint(api.routes, url_prefix='/api')
logging.info('Flask app initialized') logging.info('Flask app initialized')
......
import io
import json import json
import pathlib
from typing import Union, TextIO
import jsonschema import jsonschema
...@@ -14,15 +17,37 @@ CONFIG_SCHEMA = { ...@@ -14,15 +17,37 @@ CONFIG_SCHEMA = {
'required': ['api-key'], 'required': ['api-key'],
'additionalProperties': False 'additionalProperties': False
}, },
'visit-params': { "visit-params": {
'type': 'object', "type": "object",
'properties': { "properties": {
'api-key': {'type': 'string'}, "api-key": {"type": "string"},
'expo-id': {'type': 'string'}, "expo-id": {"type": "string"},
"paid-question": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
},
"required": ["id", "name"],
},
},
},
"required": ["id", "name", "answers"],
},
}, },
'required': ['api-key', 'expo-id'], "required": [
'additionalProperties': False "api-key",
} "expo-id",
],
"additionalProperties": False,
},
}, },
'type': 'object', 'type': 'object',
...@@ -35,13 +60,25 @@ CONFIG_SCHEMA = { ...@@ -35,13 +60,25 @@ CONFIG_SCHEMA = {
} }
def load(f): def load(config: Union[str, TextIO, pathlib.Path]):
""" """
loads, validates and returns configuration parameters loads, validates and returns configuration parameters
:param f: file-like object that produces the config file :param f: file-like object that produces the config file
:return: :return:
""" """
config = json.loads(f.read())
if isinstance(config, pathlib.Path):
return load(config.read_text())
if isinstance(config, io.TextIOBase):
return load(config.read())
if isinstance(config, str):
try:
config = json.loads(config)
except json.JSONDecodeError:
return load(pathlib.Path(config))
if not isinstance(config, dict):
raise TypeError(f"unparseable config of type {type(config).__name__}")
jsonschema.validate(config, CONFIG_SCHEMA) jsonschema.validate(config, CONFIG_SCHEMA)
return config return config
...@@ -34,7 +34,7 @@ def after_request(response): ...@@ -34,7 +34,7 @@ def after_request(response):
:param response: :param response:
:return: :return:
""" """
if response.status_code != 200: if response.status_code >= 400:
try: try:
data = response.data.decode('utf-8') data = response.data.decode('utf-8')
......
from flask import Blueprint, current_app, render_template, request, redirect
from . import common
from stripe_checkout import visit
routes = Blueprint("visitors", __name__)
@routes.after_request
def after_request(resp):
return common.after_request(resp)
@routes.route("/", methods=["GET"])
def visitors():
api = visit.VisitorAPI()
visitors = api.list_visitors()
question_info = get_question_info()
return render_template(
"visitors.html",
visitors=prepare_visitors(visitors, question_info),
paid_items=question_info["answers"],
)
@routes.route("/<visitor_id>/", methods=["POST"])
def update_visitor(visitor_id):
data = request.form
question_info = get_question_info()
available_answers = set(a["id"] for a in question_info["answers"])
given_answers = {key for key, value in data.items() if value == "on"}
valid_answers = given_answers & available_answers
payload = {
"questions": [
{"id": question_info["id"], "answers": [{"id": a} for a in valid_answers]}
]
}
api = visit.VisitorAPI()
api.update_visitor(visitor_id, payload)
return redirect("/visitors/")
def get_question_info():
app_config = current_app.config["CONFIG_PARAMS"]
return app_config["visit"].get("paid-question", {"id": None, "answers": []})
def prepare_visitors(visitors, question_info):
active_visitors = filter(lambda v: not v["deleted"], visitors)
return [
{
"name": (
f"{v.get('contact',{}).get('firstName','')}"
f" {v.get('contact',{}).get('lastName','')}"
),
"id": v.get("id", ""),
"paid": _get_visitor_paid_answers(v, question_info["id"]),
}
for v in active_visitors
]
def _get_visitor_paid_answers(visitor, paid_question_id):
questions = visitor["questions"]
for q in questions:
if q["id"] == paid_question_id:
return {a["id"] for a in q.get("answers", [])}
return set()
<!doctype html>
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock body %}
</body>
</html>
\ No newline at end of file
{% extends "base.html" %}
{% block title %}
Visitors
{% endblock title %}
{% block body %}
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
{% for item in paid_items %}<th>{{ item.name }}</th>{% endfor %}
</tr>
</thead>
<tbody>
{% for visitor in visitors %}
<tr>
<td>{{ visitor.id }}</td>
<td>{{ visitor.name }}</td>
{% for item in paid_items %}
<td>
<input type="checkbox"
form="{{ visitor.id }}_update_form"
name="{{ item.id }}"
autocomplete="off"
{% if item.id in visitor.paid %}checked{% endif %}>
</td>
{% endfor %}
<td>
<form id="{{ visitor.id }}_update_form"
method="POST"
action="/visitors/{{ visitor.id }}/">
<button>Update</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock body %}
from flask import current_app
import requests import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
BASE_URL = 'https://api.visitcloud.com/create/v2' BASE_URL = 'https://api.visitcloud.com/create/v2'
class VisitorAPI:
def __init__(self, visit_params=None):
visit_params = visit_params or current_app.config["CONFIG_PARAMS"]["visit"]
self.auth = HTTPBasicAuth(visit_params["api-key"], "")
self.expo_id = visit_params["expo-id"]
def _request(self, *args, **kwargs):
kwargs.update(
auth=self.auth,
headers={"accept": "application/json"},
)
response = requests.request(*args, **kwargs)
response.raise_for_status()
return response.json()
def list_visitors(self):
return self._request("get", f"{BASE_URL}/visitors/{self.expo_id}")
def get_visitor(self, visitor_id: str):
return self._request("get", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}")
def update_visitor(self, visitor_id: str, payload: dict):
return self._request(
"put", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}", json=payload
)
def get_expos(params): def get_expos(params):
r = requests.get( r = requests.get(
......
import os
import click
from stripe_checkout import config
from stripe_checkout.visit import VisitorAPI
@click.command()
@click.argument("visitor_id")
@click.argument("paids", nargs=-1)
def cli(visitor_id, paids):
settings_filename = os.getenv("SETTINGS_FILENAME", default="config.json")
visit_params = config.load(settings_filename)["visit"]
question_info = visit_params["paid-question"]
api = VisitorAPI(visit_params)
visitor = api.get_visitor(visitor_id)
current_answers = next(
iter(
q["answers"] for q in visitor["questions"] if q["id"] == question_info["id"]
),
[],
)
current_paid = set(a["id"] for a in current_answers)
new_paid = current_paid | set(paids)
payload = {
"questions": [
{"id": question_info["id"], "answers": [{"id": a} for a in new_paid]}
]
}
VisitorAPI(visit_params).update_visitor(visitor_id, payload)
if __name__ == "__main__":
cli()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment