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.
* 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

View File

@@ -1,7 +1,7 @@
class ReverseProxied(object):
'''Wrap the application in this middleware and configure the
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
"""Wrap the application in this middleware and configure the
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
different than what is used locally.
In nginx:
@@ -14,19 +14,20 @@ class ReverseProxied(object):
}
:param app: the WSGI application
'''
"""
def __init__(self, app):
self.app = app
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:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
environ["SCRIPT_NAME"] = script_name
path_info = environ["PATH_INFO"]
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:
environ['wsgi.url_scheme'] = scheme
environ["wsgi.url_scheme"] = scheme
return self.app(environ, start_response)

View File

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