dokuwiki support

This commit is contained in:
Ville Rantanen
2023-11-30 09:08:14 +02:00
parent 84f40bb16e
commit 62479844cf
3 changed files with 115 additions and 106 deletions

View File

@@ -13,6 +13,12 @@ A simple shopping list app:
Note! Register page is enabled by default, but there is no link to it anywhere. Note! Register page is enabled by default, but there is no link to it anywhere.
* Disable registering with env variable: `ENABLE_REGISTER=false` * Disable registering with env variable: `ENABLE_REGISTER=false`
* MARKDOWN_STYLE=dokuwiki or markdown
* markdown syntax, unticked = `[ ]`, ticked = `[x]`
* dokuwiki syntax (todo plugin), unticked = `<todo>item</todo>`, ticked = `<todo #user>item</todo>`
* SECRET_KEY=somerandomstring
* SESSION_COOKIE_NAME=name for cookies
## Running ## Running

View File

@@ -1,5 +1,5 @@
class ReverseProxied(object): class ReverseProxied(object):
'''Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is this to a URL other than / and to an HTTP scheme that is
different than what is used locally. different than what is used locally.
@@ -14,19 +14,20 @@ class ReverseProxied(object):
} }
:param app: the WSGI application :param app: the WSGI application
''' """
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '') script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
if script_name: if script_name:
environ['SCRIPT_NAME'] = script_name environ["SCRIPT_NAME"] = script_name
path_info = environ['PATH_INFO'] path_info = environ["PATH_INFO"]
if path_info.startswith(script_name): if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):] environ["PATH_INFO"] = path_info[len(script_name) :]
scheme = environ.get('HTTP_X_SCHEME', '') scheme = environ.get("HTTP_X_SCHEME", "")
if scheme: if scheme:
environ['wsgi.url_scheme'] = scheme environ["wsgi.url_scheme"] = scheme
return self.app(environ, start_response) return self.app(environ, start_response)

View File

@@ -1,17 +1,23 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# all the imports # all the imports
import sqlite3, time, datetime, hashlib, os, re import datetime
import hashlib
import os
import re
import sqlite3
import time
from shutil import copyfile, move from shutil import copyfile, move
from flask import ( from flask import (
Flask, Flask,
request, abort,
session, flash,
g, g,
redirect, redirect,
url_for,
abort,
render_template, render_template,
flash, request,
session,
url_for,
) )
from revprox import ReverseProxied from revprox import ReverseProxied
@@ -22,17 +28,20 @@ DEBUG = False
SECRET_KEY = os.getenv("SECRET_KEY", "development key") SECRET_KEY = os.getenv("SECRET_KEY", "development key")
USERNAME = os.getenv("ADMIN_USER", "admin") USERNAME = os.getenv("ADMIN_USER", "admin")
PASSWORD = os.getenv("ADMIN_PASSWD", "default") PASSWORD = os.getenv("ADMIN_PASSWD", "default")
MARKDOWN_STYLE = os.getenv("MARKDOWN_STYLE", "markdown")
URLFINDER = re.compile("((news|telnet|nttp|file|http|ftp|https)://[^ ]+)") URLFINDER = re.compile("((news|telnet|nttp|file|http|ftp|https)://[^ ]+)")
URLPARSER = re.compile(r"(\[)([^\]]+)(\])\(([^\)]+)\)") URLPARSER = re.compile(r"(\[)([^\]]+)(\])\(([^\)]+)\)")
BOLDFINDER = re.compile(r"\*([^\*]+)\*") BOLDFINDER = re.compile(r"\*([^\*]+)\*")
CODEFINDER = re.compile(r"\`([^\`]+)\`") CODEFINDER = re.compile(r"\`([^\`]+)\`")
CHECKBOX_DOKUWIKI_TICKED = re.compile(r"<todo #([^>]*)>([^<]+)</todo>")
CHECKBOX_DOKUWIKI_UNTICKED = re.compile(r"<todo>([^<]+)</todo>")
SHOPNAME_INVALIDCHARS = re.compile("[^A-Za-z0-9_-]+")
# create our little application :) # create our little application :)
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(__name__) app.config.from_object(__name__)
app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "mdshop") app.config["SESSION_COOKIE_NAME"] = os.getenv("SESSION_COOKIE_NAME", "mdshop")
app.config["register"] = os.getenv("ENABLE_REGISTER", "true") != "false" app.config["register"] = os.getenv("ENABLE_REGISTER", "true") != "false"
print(app.config)
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
@@ -82,9 +91,7 @@ def get_shop_date(id):
data_dir = os.path.join(DATADIR, get_username(row[2])) data_dir = os.path.join(DATADIR, get_username(row[2]))
data_file = os.path.join(data_dir, row[1] + ".md") data_file = os.path.join(data_dir, row[1] + ".md")
if os.path.exists(data_file): if os.path.exists(data_file):
date = datetime.datetime.fromtimestamp( date = datetime.datetime.fromtimestamp(os.path.getmtime(data_file)).strftime("%m/%d %H:%M")
os.path.getmtime(data_file)
).strftime("%m/%d %H:%M")
return date return date
@@ -96,9 +103,7 @@ def get_shop_backup_date(id):
data_dir = os.path.join(DATADIR, get_username(row[2])) data_dir = os.path.join(DATADIR, get_username(row[2]))
data_file = os.path.join(data_dir, row[1] + ".md.bkp") data_file = os.path.join(data_dir, row[1] + ".md.bkp")
if os.path.exists(data_file): if os.path.exists(data_file):
date = datetime.datetime.fromtimestamp( date = datetime.datetime.fromtimestamp(os.path.getmtime(data_file)).strftime("%m/%d %H:%M")
os.path.getmtime(data_file)
).strftime("%m/%d %H:%M")
return date return date
@@ -127,6 +132,8 @@ def markdown_parse(s):
s = s.decode("utf8") s = s.decode("utf8")
s = BOLDFINDER.sub(r'*<span class="md_bold">\1</span>*', s) s = BOLDFINDER.sub(r'*<span class="md_bold">\1</span>*', s)
s = CODEFINDER.sub(r'`<span class="md_code">\1</span>`', s) s = CODEFINDER.sub(r'`<span class="md_code">\1</span>`', s)
s = CHECKBOX_DOKUWIKI_TICKED.sub(r"\2 (\1)", s)
s = CHECKBOX_DOKUWIKI_UNTICKED.sub(r"\1", s)
return s return s
@@ -136,6 +143,37 @@ def urlify(s):
return URLFINDER.sub(r'<a href="\1" target="_blank">\1</a>', s) return URLFINDER.sub(r'<a href="\1" target="_blank">\1</a>', s)
def checkbox_add(item):
if MARKDOWN_STYLE == "markdown":
return f"[ ] {item}" + "\n"
if MARKDOWN_STYLE == "dokuwiki":
return f"<todo>{item}</todo>" + "\n"
def untick(item):
if MARKDOWN_STYLE == "markdown":
return item.replace("[x]", "[ ]")
if MARKDOWN_STYLE == "dokuwiki":
return CHECKBOX_DOKUWIKI_TICKED.sub(r"<todo>\2</todo>", item)
return item
def tick(item, user=""):
if MARKDOWN_STYLE == "markdown":
return item.replace("[ ]", "[x]")
if MARKDOWN_STYLE == "dokuwiki":
return CHECKBOX_DOKUWIKI_UNTICKED.sub(r"<todo #{}>\1</todo>".format(user), item)
return item
def is_ticked(item):
return "[x]" in item or "<todo #" in item
def is_unticked(item):
return "[ ]" in item or "<todo>" in item
@app.before_request @app.before_request
def before_request(): def before_request():
g.db = connect_db() g.db = connect_db()
@@ -180,11 +218,11 @@ def show_shop(shopid):
continue continue
icon = " " icon = " "
extra_class = "noitem" extra_class = "noitem"
if "[ ]" in row: if is_unticked(row):
icon = u" " icon = " "
extra_class = "" extra_class = ""
if "[x]" in row: if is_ticked(row):
icon = u"\u2714" icon = "\u2714"
extra_class = "" extra_class = ""
row = urlify(row).encode("ascii", "xmlcharrefreplace") row = urlify(row).encode("ascii", "xmlcharrefreplace")
row = markdown_parse(row) row = markdown_parse(row)
@@ -227,17 +265,13 @@ def list_shops():
for row in cur.fetchall(): for row in cur.fetchall():
if session.get("user") == row[2]: # owner if session.get("user") == row[2]: # owner
date = get_shop_date(row[0]) date = get_shop_date(row[0])
entries.append( entries.append(dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date))
dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date)
)
cur = g.db.execute("select * from shops order by shop") cur = g.db.execute("select * from shops order by shop")
shares = get_shares(session.get("user")) shares = get_shares(session.get("user"))
for row in cur.fetchall(): for row in cur.fetchall():
if row[0] in shares: # Has been shared to if row[0] in shares: # Has been shared to
date = get_shop_date(row[0]) date = get_shop_date(row[0])
entries.append( entries.append(dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date))
dict(shop=row[1], shopid=row[0], owner=get_username(row[2]), date=date)
)
return render_template("list_shops.html", entries=entries) return render_template("list_shops.html", entries=entries)
@@ -247,12 +281,8 @@ def add_items():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
data_dir = os.path.join(DATADIR, ownername) data_dir = os.path.join(DATADIR, ownername)
data_file = os.path.join(data_dir, shopname + ".md") data_file = os.path.join(data_dir, shopname + ".md")
@@ -262,7 +292,7 @@ def add_items():
if row.strip() == "": if row.strip() == "":
continue continue
count += 1 count += 1
contents_file.write("[ ] %s\n" % row.strip()) contents_file.write(checkbox_add(row.strip()))
contents_file.close() contents_file.close()
flash("Added %d items" % (count)) flash("Added %d items" % (count))
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
@@ -273,12 +303,8 @@ def edit_md():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
data_dir = os.path.join(DATADIR, ownername) data_dir = os.path.join(DATADIR, ownername)
data_file = os.path.join(data_dir, shopname + ".md") data_file = os.path.join(data_dir, shopname + ".md")
@@ -300,12 +326,8 @@ def restore_md():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
data_dir = os.path.join(DATADIR, ownername) data_dir = os.path.join(DATADIR, ownername)
data_file = os.path.join(data_dir, shopname + ".md") data_file = os.path.join(data_dir, shopname + ".md")
@@ -326,13 +348,10 @@ def toggle_item():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
user = get_username(session.get("user"))
req_row = None req_row = None
for key in request.form: for key in request.form:
if key.startswith("item"): if key.startswith("item"):
@@ -355,10 +374,10 @@ def toggle_item():
for i, row in enumerate(contents): for i, row in enumerate(contents):
if i == req_row or req_row < 0: if i == req_row or req_row < 0:
if req_row != -2: # no ticking if unticking all if req_row != -2: # no ticking if unticking all
if "[ ]" in row: if is_unticked(row):
contents[i] = row.replace("[ ]", "[x]") contents[i] = tick(row, user)
if "[x]" in row: if is_ticked(row):
contents[i] = row.replace("[x]", "[ ]") contents[i] = untick(row)
if row != contents[i]: if row != contents[i]:
changed = True changed = True
if changed: if changed:
@@ -375,12 +394,8 @@ def remove_toggled():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
data_dir = os.path.join(DATADIR, ownername) data_dir = os.path.join(DATADIR, ownername)
data_file = os.path.join(data_dir, shopname + ".md") data_file = os.path.join(data_dir, shopname + ".md")
@@ -389,7 +404,7 @@ def remove_toggled():
contents = [] contents = []
changed = False changed = False
for i, row in enumerate(contents_file.read().split("\n")): for i, row in enumerate(contents_file.read().split("\n")):
if "[x]" not in row: if not is_ticked(row):
contents.append(row) contents.append(row)
else: else:
changed = True changed = True
@@ -407,10 +422,11 @@ def remove_toggled():
def add_shop(): def add_shop():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
import re, string import re
import string
shopname = SHOPNAME_INVALIDCHARS.sub("", request.form["shop"])
pattern = re.compile("[\W]+")
shopname = pattern.sub("", request.form["shop"])
if shopname == "": if shopname == "":
flash("Shop name empty!") flash("Shop name empty!")
return redirect(url_for("list_shops")) return redirect(url_for("list_shops"))
@@ -419,9 +435,7 @@ def add_shop():
if shopname == row[1]: if shopname == row[1]:
flash("Shop already exists! " + shopname) flash("Shop already exists! " + shopname)
return redirect(url_for("list_shops")) return redirect(url_for("list_shops"))
g.db.execute( g.db.execute("insert into shops (shop,owner) values (?, ?)", [shopname, session["user"]])
"insert into shops (shop,owner) values (?, ?)", [shopname, session["user"]]
)
g.db.commit() g.db.commit()
new_dir = os.path.join(DATADIR, get_username(session["user"])) new_dir = os.path.join(DATADIR, get_username(session["user"]))
new_file = os.path.join(new_dir, shopname + ".md") new_file = os.path.join(new_dir, shopname + ".md")
@@ -436,11 +450,11 @@ def add_shop():
def add_share(): def add_share():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
import re, string import re
import string
pattern = re.compile("[\W]+") username = SHOPNAME_INVALIDCHARS.sub("", request.form["share"])
username = pattern.sub("", request.form["share"]) shopid = SHOPNAME_INVALIDCHARS.sub("", request.form["shopid"])
shopid = pattern.sub("", request.form["shopid"])
if username == "": if username == "":
flash("User name empty!") flash("User name empty!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
@@ -448,9 +462,7 @@ def add_share():
if userid == None: if userid == None:
flash("No such user!") flash("No such user!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
if session.get("user") != ownerid: if session.get("user") != ownerid:
flash("Not your shop!") flash("Not your shop!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
@@ -464,11 +476,11 @@ def add_share():
def remove_share(): def remove_share():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
import re, string import re
import string
pattern = re.compile("[\W]+") username = SHOPNAME_INVALIDCHARS.sub("", request.form["user"])
username = pattern.sub("", request.form["user"]) shopid = SHOPNAME_INVALIDCHARS.sub("", request.form["shopid"])
shopid = pattern.sub("", request.form["shopid"])
if username == "": if username == "":
flash("User name empty!") flash("User name empty!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
@@ -476,9 +488,7 @@ def remove_share():
if userid == None: if userid == None:
flash("No such user!") flash("No such user!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
if session.get("user") != ownerid: if session.get("user") != ownerid:
flash("Not your shop!") flash("Not your shop!")
return redirect(url_for("show_shop", shopid=shopid)) return redirect(url_for("show_shop", shopid=shopid))
@@ -493,12 +503,8 @@ def remove_shop():
if not session.get("logged_in"): if not session.get("logged_in"):
abort(401) abort(401)
shopid = int(request.form["shopid"]) shopid = int(request.form["shopid"])
ownerid = g.db.execute( ownerid = g.db.execute("select owner from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
"select owner from shops where id=?", (request.form["shopid"],) shopname = g.db.execute("select shop from shops where id=?", (request.form["shopid"],)).fetchall()[0][0]
).fetchall()[0][0]
shopname = g.db.execute(
"select shop from shops where id=?", (request.form["shopid"],)
).fetchall()[0][0]
ownername = get_username(ownerid) ownername = get_username(ownerid)
data_dir = os.path.join(DATADIR, ownername) data_dir = os.path.join(DATADIR, ownername)
data_file = os.path.join(data_dir, shopname + ".md") data_file = os.path.join(data_dir, shopname + ".md")
@@ -553,10 +559,10 @@ def register():
if not app.config["register"]: if not app.config["register"]:
return "" return ""
if request.method == "POST": if request.method == "POST":
import re, string import re
import string
pattern = re.compile("[\W]+") username = SHOPNAME_INVALIDCHARS.sub("", request.form["username"])
username = pattern.sub("", request.form["username"])
password = password_hash(request.form["password"]) password = password_hash(request.form["password"])
if len(username) == 0: if len(username) == 0:
error = "No username given" error = "No username given"
@@ -569,9 +575,7 @@ def register():
if username == row[1]: if username == row[1]:
error = "Username already exists" error = "Username already exists"
return render_template("register.html", error=error) return render_template("register.html", error=error)
g.db.execute( g.db.execute("insert into users (user,pass) values (?, ?)", [username, password])
"insert into users (user,pass) values (?, ?)", [username, password]
)
g.db.commit() g.db.commit()
flash('successfully registered user "%s". Now login.' % username) flash('successfully registered user "%s". Now login.' % username)
return redirect(url_for("login")) return redirect(url_for("login"))
@@ -585,16 +589,14 @@ def profile():
error = None error = None
user = get_username(session.get("user")) user = get_username(session.get("user"))
if request.method == "POST": if request.method == "POST":
import re, string import re
import string
pattern = re.compile("[\W]+")
password = password_hash(request.form["password"]) password = password_hash(request.form["password"])
if len(request.form["password"]) < 5: if len(request.form["password"]) < 5:
error = "Password too short" error = "Password too short"
return render_template("profile.html", error=error, user=user) return render_template("profile.html", error=error, user=user)
g.db.execute( g.db.execute("update users set pass=? where id=?", [password, session.get("user")])
"update users set pass=? where id=?", [password, session.get("user")]
)
g.db.commit() g.db.commit()
flash("successfully updated profile.") flash("successfully updated profile.")
return redirect(url_for("profile")) return redirect(url_for("profile"))