692 lines
20 KiB
Python
692 lines
20 KiB
Python
import json
|
|
import re
|
|
import typing
|
|
from functools import wraps
|
|
|
|
import flask
|
|
from flask_login import (
|
|
LoginManager,
|
|
current_user,
|
|
login_required,
|
|
login_user,
|
|
logout_user,
|
|
)
|
|
from itsdangerous import URLSafeTimedSerializer
|
|
from werkzeug.wrappers import Response
|
|
|
|
from .database import session
|
|
from .edit import apply_edits
|
|
from .edl import fulfil_edl, fulfil_edl_with_links, fulfil_edl_with_sources, parse_edl
|
|
from .forms import (
|
|
AccountSettingsForm,
|
|
ForgotPasswordForm,
|
|
ItemForm,
|
|
LoginForm,
|
|
PasswordForm,
|
|
SignupForm,
|
|
SourceDocForm,
|
|
)
|
|
from .mail import send_mail
|
|
from .model import Item, Reference, SourceDoc, User, XanaLink, XanaPage
|
|
from .parse import parse_link, parse_xanapage_facet
|
|
from .span import Span
|
|
from .text import add_highlight, iter_lines
|
|
from .url import get_url
|
|
from .utils import nbsp_at_start
|
|
|
|
# from werkzeug.debug.tbtools import get_current_traceback
|
|
# from jinja2 import evalcontextfilter, Markup
|
|
# from sqlalchemy_continuum import version_class
|
|
|
|
|
|
login_manager = LoginManager()
|
|
login_manager.login_view = ".login"
|
|
re_paragraph = re.compile(r"(?:\r\n|\r|\n){2,}")
|
|
re_spanpointer = re.compile(r"([A-Za-z0-9]+),start=(\d+),length=(\d+)")
|
|
bp = flask.Blueprint("view", __name__)
|
|
|
|
|
|
def init_app(app: flask.Flask) -> None:
|
|
"""Initialise app."""
|
|
login_manager.init_app(app)
|
|
app.register_blueprint(bp)
|
|
|
|
|
|
# @app.template_filter()
|
|
# @evalcontextfilter
|
|
# def newline_html(eval_ctx, value):
|
|
# return u'\n\n'.join(Markup(u'<p>') + p.replace('\n', Markup('<br>')) + Markup(u'</p>')
|
|
# for p in re_paragraph.split(value))
|
|
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
return User.query.get(user_id)
|
|
|
|
|
|
# where do we redirect after signup is complete
|
|
view_after_signup = ".home"
|
|
|
|
|
|
@bp.context_processor
|
|
def inject_user():
|
|
return dict(current_user=current_user)
|
|
|
|
|
|
def show_errors(f):
|
|
@wraps(f)
|
|
def wrapper(*args, **kwargs):
|
|
try:
|
|
return f(*args, **kwargs)
|
|
except Exception:
|
|
traceback = get_current_traceback(
|
|
skip=1, show_hidden_frames=False, ignore_system_exceptions=True
|
|
)
|
|
return traceback.render_full().encode("utf-8", "replace")
|
|
|
|
return wrapper
|
|
|
|
|
|
@bp.route("/")
|
|
def home() -> str:
|
|
"""Home page."""
|
|
docs = Item.query.order_by(Item.created)
|
|
docs_info = []
|
|
for item in docs:
|
|
cur = {
|
|
"user": item.user.username,
|
|
"id": item.id,
|
|
"type": item.type,
|
|
"year": item.created.year,
|
|
}
|
|
if item.type == "xanalink":
|
|
cur["link_type"] = item.link_type
|
|
docs_info.append(cur)
|
|
|
|
users = [item_user.username for item_user in User.query]
|
|
years = sorted({item["year"] for item in docs_info})
|
|
|
|
link_types = {item["link_type"] for item in docs_info if item.get("link_type")}
|
|
|
|
return flask.render_template(
|
|
"home.html",
|
|
docs=docs,
|
|
link_types=link_types,
|
|
years=years,
|
|
docs_info=docs_info,
|
|
nbsp_at_start=nbsp_at_start,
|
|
users=users,
|
|
)
|
|
|
|
|
|
@bp.route("/password_reset", methods=["GET", "POST"])
|
|
def password_reset():
|
|
site_name = "perma.pub" # FIXME: move to config
|
|
form = ForgotPasswordForm()
|
|
if not form.validate_on_submit():
|
|
return flask.render_template("auth/password_reset.html", form=form)
|
|
ts = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
|
|
user = User.lookup_user_or_email(form.user_or_email.data)
|
|
if user:
|
|
token = ts.dumps(user.id, salt="password-reset")
|
|
reset_link = flask.url_for(".reset_with_token", token=token, _external=True)
|
|
reset_mail = flask.render_template(
|
|
"mail/password_reset.txt",
|
|
reset_link=reset_link,
|
|
site_name=site_name,
|
|
user=user,
|
|
)
|
|
subject = "Password reset on " + site_name
|
|
send_mail(user, subject, reset_mail)
|
|
return flask.redirect(flask.url_for(".password_reset_sent"))
|
|
|
|
|
|
@bp.route("/password_reset/sent", methods=["GET", "POST"])
|
|
def password_reset_sent():
|
|
return flask.render_template("auth/password_reset_sent.html")
|
|
|
|
|
|
@bp.route("/reset/<token>", methods=["GET", "POST"])
|
|
def reset_with_token(token):
|
|
ts = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
|
|
try:
|
|
user_id = ts.loads(token, salt="password-reset", max_age=86400)
|
|
except Exception:
|
|
flask.abort(404)
|
|
|
|
form = PasswordForm()
|
|
if not form.validate_on_submit():
|
|
return flask.render_template("auth/password_reset_confirm.html", form=form)
|
|
|
|
user = User.query.get(user_id)
|
|
user.set_password(form.password.data)
|
|
session.add(user)
|
|
session.commit()
|
|
|
|
return flask.redirect(flask.url_for(".password_reset_complete"))
|
|
|
|
|
|
@bp.route("/reset/done")
|
|
def password_reset_complete() -> str:
|
|
return flask.render_template("auth/password_reset_complete.html")
|
|
|
|
|
|
@bp.route("/source_doc_upload", methods=["POST"])
|
|
@show_errors
|
|
def source_doc_upload():
|
|
f = flask.request.files["sourcedoc_file"]
|
|
text = f.read()
|
|
doc = SourceDoc(text=text, user=current_user, filename=f.filename)
|
|
session.add(doc)
|
|
session.commit()
|
|
flask.flash("new source document uploaded")
|
|
return flask.redirect(doc.url)
|
|
|
|
|
|
@bp.route("/about")
|
|
def about() -> str:
|
|
"""About page."""
|
|
return flask.render_template("about.html")
|
|
|
|
|
|
@bp.route("/contact")
|
|
def contact() -> str:
|
|
"""Contact page."""
|
|
return flask.render_template("contact.html")
|
|
|
|
|
|
def redirect_to_home() -> Response:
|
|
"""Redirect to home page."""
|
|
return flask.redirect(flask.url_for(".home"))
|
|
|
|
|
|
@bp.route("/login", methods=["GET", "POST"])
|
|
def login() -> Response | str:
|
|
"""Login page."""
|
|
form = LoginForm(next=flask.request.args.get("next"))
|
|
if form.validate_on_submit():
|
|
login_user(form.user, remember=form.remember.data)
|
|
flask.flash("Logged in successfully.")
|
|
return flask.redirect(flask.request.form.get("next") or flask.url_for(".home"))
|
|
return flask.render_template("login.html", form=form)
|
|
|
|
|
|
@bp.route("/logout")
|
|
def logout() -> Response:
|
|
"""Logout and redirect to home."""
|
|
logout_user()
|
|
flask.flash("You have been logged out.")
|
|
return redirect_to_home()
|
|
|
|
|
|
@bp.route("/signup", methods=["GET", "POST"])
|
|
def signup():
|
|
if not flask.current_app.config.get("ALLOW_SIGNUP"):
|
|
flask.abort(404)
|
|
form = SignupForm()
|
|
if not form.validate_on_submit():
|
|
return flask.render_template("signup.html", form=form)
|
|
|
|
data = form.data.copy()
|
|
del data["csrf_token"]
|
|
user = User(**data)
|
|
session.add(user)
|
|
session.commit()
|
|
flask.flash("New account created.")
|
|
login_user(user)
|
|
return flask.redirect(flask.url_for(view_after_signup))
|
|
|
|
|
|
def redirect_to_doc(doc: Item) -> Response:
|
|
"""Redirect to the given item."""
|
|
return flask.redirect(flask.url_for(".view_document", hashid=doc.hashid))
|
|
|
|
|
|
def get_source_doc(username: str, hashid: str) -> SourceDoc:
|
|
"""Get a source doc that belongs to the given uesr."""
|
|
doc = Item.get_by_hashid(hashid)
|
|
if not doc or doc.user.username != username:
|
|
flask.abort(404)
|
|
return typing.cast(SourceDoc, doc)
|
|
|
|
|
|
def get_xanapage(username: str, hashid: str) -> XanaPage:
|
|
"""Get a xanapage that belongs to the given uesr."""
|
|
doc = Item.get_by_hashid(hashid)
|
|
if not doc or doc.type != "xanapage" or doc.user.username != username:
|
|
flask.abort(404)
|
|
return typing.cast(XanaPage, doc)
|
|
|
|
|
|
def get_item(username: str, hashid: str) -> Item:
|
|
"""Get an item with the given hashid, check ownership."""
|
|
doc = Item.get_by_hashid(hashid)
|
|
if not doc or doc.user.username != username:
|
|
flask.abort(404)
|
|
return doc
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/edl")
|
|
def view_edl(username: str, hashid: str) -> str:
|
|
"""Show EDL for xanapage."""
|
|
item = get_xanapage(username, hashid)
|
|
|
|
return flask.render_template(
|
|
"view.html", doc=item, iter_lines=iter_lines, nbsp_at_start=nbsp_at_start
|
|
)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/realize")
|
|
def realize_edl(username, hashid):
|
|
item = get_xanapage(username, hashid)
|
|
|
|
spans = list(fulfil_edl(item.text))
|
|
doc_text = "".join(span["text"] for span in spans)
|
|
|
|
return flask.render_template(
|
|
"realize.html",
|
|
doc=doc_text,
|
|
iter_lines=iter_lines,
|
|
item=item,
|
|
nbsp_at_start=nbsp_at_start,
|
|
)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/raw")
|
|
def view_item_raw(username: str, hashid: str):
|
|
return view_item(username, hashid, raw=True)
|
|
|
|
|
|
def fulfil_xanaflight(item: XanaLink) -> str:
|
|
link = item.parse()
|
|
assert link["type"] == "flight"
|
|
facets = link["facets"]
|
|
docs = []
|
|
edl_list = []
|
|
all_links = []
|
|
for facet in facets:
|
|
xanapage = Item.from_external(parse_xanapage_facet(facet))
|
|
assert xanapage and xanapage.type == "xanapage" and xanapage.text
|
|
edl = parse_edl(xanapage.text)
|
|
edl_list.append((xanapage, edl))
|
|
all_links += [parse_link(link["text"]) for link in edl["links"]]
|
|
|
|
for doc_num, (xanapage, edl) in enumerate(edl_list):
|
|
doc = fulfil_edl_with_links(
|
|
edl, doc_num=doc_num, links=all_links, hide_all_transclusions=True
|
|
)
|
|
doc["hashid"] = xanapage.hashid
|
|
del doc["link_count"]
|
|
docs.append(doc)
|
|
return flask.render_template(
|
|
"view/xanaflight.html", item=item, link_count=len(all_links), docs=docs
|
|
)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/fulfil")
|
|
def fulfil(username, hashid):
|
|
item = get_item(username, hashid)
|
|
|
|
if item.type == "xanapage":
|
|
return flask.render_template(
|
|
"view/xanapage.html", item=item, doc=fulfil_edl_with_sources(item.text)
|
|
)
|
|
if item.type == "xanalink" and item.text.startswith("type=flight"):
|
|
return fulfil_xanaflight(item)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/set_title", methods=["POST"])
|
|
def set_title(username, hashid):
|
|
item = get_item(username, hashid)
|
|
has_title = item.has_title()
|
|
item.set_title(flask.request.form["title"], current_user)
|
|
flask.flash("title change saved" if has_title else "title added")
|
|
return flask.redirect(item.url)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/delete", methods=["POST"])
|
|
def delete_item(username, hashid):
|
|
item = get_item(username, hashid)
|
|
session.delete(item)
|
|
session.commit()
|
|
flask.flash("item deleted")
|
|
return redirect_to_home()
|
|
|
|
|
|
def save_new_xanalink(doc1, doc2):
|
|
start1 = flask.request.form["left_start"]
|
|
length1 = flask.request.form["left_length"]
|
|
start2 = flask.request.form["right_start"]
|
|
length2 = flask.request.form["right_length"]
|
|
|
|
assert length1
|
|
assert length2
|
|
|
|
span1 = f"{doc1.external_url},start={start1},length={length1}"
|
|
span2 = f"{doc2.external_url},start={start2},length={length2}"
|
|
lines = [
|
|
"type=",
|
|
"facet=",
|
|
"span: " + span1,
|
|
"facet=",
|
|
"span: " + span2,
|
|
]
|
|
|
|
text = "".join(line + "\n" for line in lines)
|
|
|
|
obj = XanaLink(user=current_user, text=text)
|
|
session.add(obj)
|
|
session.commit()
|
|
ref1 = Reference(subject_id=obj.id, object_id=doc1.id)
|
|
ref2 = Reference(subject_id=obj.id, object_id=doc2.id)
|
|
session.add(ref1)
|
|
session.add(ref2)
|
|
session.commit()
|
|
|
|
|
|
@bp.route("/build_links", methods=["GET", "POST"])
|
|
def build_links():
|
|
doc1, doc2 = None, None
|
|
hashid1, hashid2 = None, None
|
|
if "doc1" in flask.request.args:
|
|
hashid1 = flask.request.args["doc1"]
|
|
doc1 = Item.get_by_hashid(hashid1)
|
|
if "doc2" in flask.request.args:
|
|
hashid2 = flask.request.args["doc2"]
|
|
doc2 = Item.get_by_hashid(hashid2)
|
|
|
|
if flask.request.method == "POST":
|
|
save_new_xanalink(doc1, doc2)
|
|
return flask.redirect(
|
|
flask.url_for(flask.request.endpoint, doc1=hashid1, doc2=hashid2)
|
|
)
|
|
|
|
if doc1 and doc2:
|
|
links = list({i for i in doc1.subjects} & {i for i in doc2.subjects})
|
|
else:
|
|
links = []
|
|
|
|
return flask.render_template(
|
|
"build_links.html",
|
|
iter_lines=iter_lines,
|
|
nbsp_at_start=nbsp_at_start,
|
|
SourceDoc=SourceDoc,
|
|
hashid1=hashid1,
|
|
hashid2=hashid2,
|
|
doc1=doc1,
|
|
doc2=doc2,
|
|
links=links,
|
|
)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>")
|
|
def view_item(username: str, hashid: str, raw: bool = False) -> str | Response:
|
|
"""View item."""
|
|
if "," in hashid:
|
|
m = re_spanpointer.match(hashid)
|
|
assert m
|
|
hashid, start, length = m.group(1), int(m.group(2)), int(m.group(3))
|
|
item = get_item(username, hashid)
|
|
if raw:
|
|
assert item.text is not None
|
|
return flask.Response(
|
|
item.text[start : length + start], mimetype="text/plain"
|
|
)
|
|
else:
|
|
start, length = None, None
|
|
item = get_item(username, hashid)
|
|
if raw:
|
|
return flask.Response(item.text, mimetype="text/plain")
|
|
|
|
v = flask.request.args.get("v")
|
|
if v:
|
|
if not v.isdigit():
|
|
flask.abort(404)
|
|
try:
|
|
version = item.versions[int(v) - 1]
|
|
except IndexError:
|
|
flask.abort(404)
|
|
text = version.text
|
|
else:
|
|
version = None
|
|
text = item.text
|
|
|
|
if item.type == "xanapage":
|
|
assert item.text
|
|
spans = list(fulfil_edl(item.text))
|
|
doc_text = "".join(span["text"] for span in spans)
|
|
else:
|
|
doc_text = None
|
|
|
|
return flask.render_template(
|
|
"view.html",
|
|
doc=item,
|
|
doc_text=doc_text,
|
|
version=version,
|
|
text=text,
|
|
span_start=start,
|
|
span_length=length,
|
|
add_highlight=add_highlight,
|
|
nbsp_at_start=nbsp_at_start,
|
|
iter_lines=iter_lines,
|
|
)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/history")
|
|
def history(username: str, hashid: str) -> str:
|
|
item = get_item(username, hashid)
|
|
return flask.render_template("history.html", doc=item)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/as_xanapage", methods=["POST"])
|
|
def create_xanapage_from_sourcedoc(username, hashid):
|
|
src_doc = get_source_doc(username, hashid)
|
|
edl = "span: " + src_doc.entire_span + "\n"
|
|
|
|
page = XanaPage(user=current_user, text=edl)
|
|
session.add(page)
|
|
session.commit()
|
|
page.update_references()
|
|
|
|
flask.flash("New xanapage created.")
|
|
return flask.redirect(page.url)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/xanaedit", methods=["GET", "POST"])
|
|
def xanaedit_item(username, hashid):
|
|
if flask.request.method == "POST":
|
|
save_xanaedit(username, hashid)
|
|
|
|
return flask.redirect(
|
|
flask.url_for("xanaedit_item", username=username, hashid=hashid)
|
|
)
|
|
|
|
doc = get_xanapage(username, hashid)
|
|
|
|
spans = list(fulfil_edl(doc.text))
|
|
doc_text = "".join(span["text"] for span in spans)
|
|
|
|
return flask.render_template("xanaedit.html", doc=doc, doc_text=doc_text)
|
|
|
|
|
|
def save_xanaedit(username: str, hashid: str) -> XanaPage:
|
|
"""Save a XanaEdit."""
|
|
page = get_xanapage(username, hashid)
|
|
assert page.text
|
|
current_edl = parse_edl(page.text)
|
|
|
|
spans = [Span(*span) for span in current_edl["spans"]]
|
|
edits = json.loads(flask.request.form["edits"])
|
|
new_text = ""
|
|
new_text_pos = 0
|
|
for edit in edits:
|
|
if edit["op"] not in ("insert", "replace"):
|
|
continue
|
|
new_text += edit["new"]
|
|
edit["span"] = Span("placeholder", new_text_pos, len(edit["new"]))
|
|
new_text_pos += len(edit["new"])
|
|
spans = apply_edits(spans, edits)
|
|
|
|
new_src_doc = SourceDoc(user=current_user, text=new_text)
|
|
session.add(new_src_doc)
|
|
session.commit()
|
|
|
|
for span in spans:
|
|
if span.url == "placeholder":
|
|
span.url = new_src_doc.external_url
|
|
|
|
new_edl = "".join(f"span: {span.for_edl()}\n" for span in spans)
|
|
|
|
page.text = new_edl
|
|
session.commit()
|
|
page.update_references()
|
|
|
|
flask.flash("Edits saved.")
|
|
|
|
return page
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/finish", methods=["POST"])
|
|
def finish_xanaedit(username: str, hashid: str) -> Response:
|
|
page = save_xanaedit(username, hashid)
|
|
|
|
return flask.redirect(page.url)
|
|
|
|
|
|
@bp.route("/<username>/<hashid>/edit", methods=["GET", "POST"])
|
|
def edit_item(username, hashid):
|
|
obj = get_item(username, hashid)
|
|
form = SourceDocForm(obj=obj)
|
|
if form.validate_on_submit():
|
|
form.populate_obj(obj)
|
|
session.commit()
|
|
if obj.type == "xanapage":
|
|
obj.update_references()
|
|
flask.flash("Changes to {} saved.".format(obj.type))
|
|
return flask.redirect(obj.url)
|
|
return flask.render_template("edit.html", form=form, doc=obj)
|
|
|
|
|
|
@bp.route("/source_doc_text/<int:source_doc_id>")
|
|
def source_doc_text(source_doc_id):
|
|
doc = SourceDoc.query.get(source_doc_id)
|
|
|
|
return flask.render_template("source_doc_text.html", doc=doc, iter_lines=iter_lines)
|
|
|
|
|
|
@bp.route("/settings/account", methods=["GET", "POST"])
|
|
@login_required
|
|
def account_settings() -> str | Response:
|
|
"""Account settings."""
|
|
form = AccountSettingsForm(obj=current_user)
|
|
if form.validate_on_submit():
|
|
form.populate_obj(current_user)
|
|
session.commit()
|
|
flask.flash("Account details updated.")
|
|
assert flask.request.endpoint
|
|
return flask.redirect(flask.url_for(flask.request.endpoint))
|
|
return flask.render_template("user/account.html", form=form)
|
|
|
|
|
|
@bp.route("/new/sourcedoc", methods=["GET", "POST"])
|
|
@login_required
|
|
def new_sourcedoc() -> str | Response:
|
|
"""Add a new sourcedoc."""
|
|
form = SourceDocForm()
|
|
if form.validate_on_submit():
|
|
doc = SourceDoc(user=current_user)
|
|
form.populate_obj(doc)
|
|
session.add(doc)
|
|
session.commit()
|
|
flask.flash("New document saved.")
|
|
return flask.redirect(doc.url)
|
|
return flask.render_template("new.html", form=form, item_type="source document")
|
|
|
|
|
|
@bp.route("/new/xanalink/raw", methods=["GET", "POST"])
|
|
@login_required
|
|
def new_xanalink_raw():
|
|
form = ItemForm()
|
|
if form.validate_on_submit():
|
|
obj = XanaLink(user=current_user)
|
|
form.populate_obj(obj)
|
|
session.add(obj)
|
|
session.commit()
|
|
flask.flash("New xanalink saved.")
|
|
return flask.redirect(obj.url)
|
|
return flask.render_template("new.html", form=form, item_type="xanalink")
|
|
|
|
|
|
@bp.route("/new/xanalink", methods=["GET", "POST"])
|
|
@login_required
|
|
def new_xanalink() -> str | Response:
|
|
"""Add a new xanalink."""
|
|
if flask.request.method != "POST":
|
|
return flask.render_template("new_xanalink.html")
|
|
|
|
data = flask.request.get_json()
|
|
lines = ["type=" + data["link_type"]]
|
|
for facet in data["facets"]:
|
|
lines += ["facet="] + facet
|
|
text = "".join(line + "\n" for line in lines)
|
|
obj = XanaLink(user=current_user, text=text)
|
|
session.add(obj)
|
|
session.commit()
|
|
flask.flash("New xanalink saved.")
|
|
return flask.jsonify(url=obj.url)
|
|
|
|
|
|
@bp.route("/new/xanapage", methods=["GET", "POST"])
|
|
@login_required
|
|
def new_xanapage() -> str | Response:
|
|
"""Start a new xanapage."""
|
|
form = ItemForm()
|
|
if form.validate_on_submit():
|
|
obj = XanaPage(user=current_user)
|
|
form.populate_obj(obj)
|
|
session.add(obj)
|
|
session.commit()
|
|
flask.flash("New xanapage saved.")
|
|
return flask.redirect(obj.url)
|
|
return flask.render_template("new.html", form=form, item_type="xanapage")
|
|
|
|
|
|
@bp.route("/edit/<filename>", methods=["GET", "POST"])
|
|
@login_required
|
|
def edit_source_document(filename: str) -> str | Response:
|
|
"""Edit a source document."""
|
|
doc = get_source_doc(current_user.username, filename)
|
|
form = SourceDocForm(obj=doc)
|
|
if form.validate_on_submit():
|
|
form.populate_obj(doc)
|
|
session.add(doc)
|
|
session.commit()
|
|
flask.flash("Changes to document saved.")
|
|
return flask.redirect(doc.url)
|
|
return flask.render_template("edit.html", form=form, doc=doc)
|
|
|
|
|
|
@bp.route("/api/1/get/<username>/<filename>")
|
|
def api_get_document(username: str, filename: str) -> Response:
|
|
doc = get_source_doc(username, filename)
|
|
assert doc.text is not None
|
|
ret = {
|
|
"username": username,
|
|
"filename": filename,
|
|
"character_count": len(doc.text),
|
|
"document_price": str(doc.document_price),
|
|
"price_per_character": str(doc.price_per_character),
|
|
}
|
|
return flask.jsonify(ret)
|
|
|
|
|
|
@bp.route("/get_span.json")
|
|
def get_span() -> Response:
|
|
"""Return JSON representing a span."""
|
|
url = flask.request.args["url"]
|
|
start = int(flask.request.args["start"])
|
|
length = int(flask.request.args["length"])
|
|
spanid = flask.request.args["spanid"]
|
|
text = get_url(url)
|
|
return flask.jsonify(text=text[start : start + length], spanid=spanid)
|