# -*- coding: utf-8 -*-
# all the imports
import sqlite3, time, datetime, hashlib, os,re
from shutil import copyfile, move
from flask import Flask, request, session, g, redirect, url_for, \
abort, render_template, flash
from revprox import ReverseProxied
# configuration
DATABASE = 'shop.db'
DATADIR = 'data'
DEBUG = False
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'
URLFINDER = re.compile("((news|telnet|nttp|file|http|ftp|https)://[^ ]+)")
URLPARSER = re.compile(r'(\[)([^\]]+)(\])\(([^\)]+)\)')
# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)
def connect_db():
if not os.path.exists(app.config['DATABASE']):
db=sqlite3.connect(app.config['DATABASE'])
for command in open('schema.sql','rt').read().split(';'):
db.execute(command)
db.commit()
return sqlite3.connect(app.config['DATABASE'])
def password_hash(s):
return hashlib.sha224(s).hexdigest()
def get_username(id):
cur = g.db.execute('select * from users')
for row in cur.fetchall():
if id==row[0]:
return row[1]
return None
def get_userid(name):
cur = g.db.execute('select * from users')
for row in cur.fetchall():
if name==row[1]:
return row[0]
return None
def get_shares(id):
cur = g.db.execute('select * from shares')
shares=[]
for row in cur.fetchall():
if id==row[1]:
shares.append(row[0])
return shares
def get_shop_date(id):
date=""
cur = g.db.execute('select * from shops')
for row in cur.fetchall():
if id==row[0]:
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')
return date
def get_shop_backup_date(id):
date=""
cur = g.db.execute('select * from shops')
for row in cur.fetchall():
if id==row[0]:
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')
return date
def scan_for_new_documents(id):
user=get_username(id)
data_dir=os.path.join(DATADIR, user)
if not os.path.exists(data_dir):
return
cur = g.db.execute('select * from shops')
existing=[]
non_existing=[]
for row in cur.fetchall():
if row[2]!=id:
continue
existing.append(row[1])
for row in os.listdir(data_dir):
if row.endswith(".md"):
if row[:-3] not in existing:
non_existing.append(row[:-3])
for shop in non_existing:
g.db.execute('insert into shops (shop,owner) values (?, ?)',
[shop, id])
g.db.commit()
def urlify(s):
if URLPARSER.search(s):
return URLPARSER.sub(r'[\2]',s)
return URLFINDER.sub(r'\1', s)
@app.before_request
def before_request():
g.db = connect_db()
@app.teardown_request
def teardown_request(exception):
db = getattr(g, 'db', None)
if db is not None:
db.close()
@app.route('/shop/')
def show_shop(shopid):
if not session.get('logged_in'):
return redirect(url_for('login',error=None))
try:
shopid=int(shopid)
except ValueError:
return redirect(url_for('login',error=None))
has_access=False
cur = g.db.execute('select * from shops')
shared=get_shares(session.get('user'))
for row in cur.fetchall():
if row[0]==shopid:
if row[2]==session.get('user') or row[0] in shared:
has_access=True
shopname=row[1]
break
if not has_access:
return redirect(url_for('list_shops'))
data_dir=os.path.join(DATADIR, get_username(row[2]))
data_file=os.path.join(data_dir, row[1]+".md")
if not os.path.exists(data_file):
open(data_file, 'wt').close()
entries=[]
content=open(data_file, 'rt').read().decode('utf-8')
for i,row in enumerate(open( data_file, 'rt').read().decode('utf-8').split("\n")):
# any parsing magick would be here
row=row.rstrip()
if row=="":
continue
icon=" "
extra_class="noitem"
if "[ ]" in row:
icon=u" "
extra_class=""
if "[x]" in row:
icon=u"\u2714"
extra_class=""
row=urlify(row).encode('ascii', 'xmlcharrefreplace')
if row.startswith("#"):
row=""+row+""
entries.append( dict(row=i, text=row, icon=icon, extra_class=extra_class) )
shared_to=[]
cur = g.db.execute('select * from shares')
for row in cur.fetchall():
if row[0]==shopid:
shared_to.append(get_username(row[1]))
return render_template('show_shop.html', entries=entries, shop=shopname,
shopid=shopid, content=content,shares=shared_to,
date=get_shop_date(shopid),date_bkp=get_shop_backup_date(shopid))
@app.route('/')
def list_shops():
if not session.get('logged_in'):
return redirect(url_for('login',error=None))
scan_for_new_documents(session.get('user'))
cur = g.db.execute('select * from shops order by shop')
entries=[]
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) )
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) )
return render_template('list_shops.html', entries=entries)
@app.route('/add', methods=['POST'])
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]
ownername=get_username(ownerid)
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
count=0
contents_file=open(data_file,'at')
for row in request.form['add_md'].split("\n"):
if row.strip()=="":
continue
count+=1
contents_file.write("[ ] %s\n"%row.strip().encode('utf-8'))
contents_file.close()
flash('Added %d items'%(count))
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/edit', methods=['POST'])
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]
ownername=get_username(ownerid)
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
backup=data_file+".bkp"
copyfile(data_file, backup)
contents_file=open(data_file,'wt')
contents_list=request.form['edit_md'].split("\n")
while contents_list[-1].strip()=='':
contents_list.pop()
for row in contents_list:
contents_file.write("%s\n"%row.encode('utf-8'))
contents_file.close()
flash('Saved new file.')
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/restore', methods=['POST'])
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]
ownername=get_username(ownerid)
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
backup=data_file+".bkp"
backup_tmp=data_file+".tmp"
if not os.path.exists(backup):
flash('Backup does not exist')
return redirect(url_for('show_shop',shopid=shopid))
copyfile(data_file,backup_tmp)
copyfile(backup, data_file)
move(backup_tmp, backup)
flash('Backup restored')
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/toggle', methods=['POST'])
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]
ownername=get_username(ownerid)
req_row=None
for key in request.form:
if key.startswith('item'):
req_row=int(key[4:])
if key=='toggleAll':
# Special meaning: toggle all rows
req_row=-1
if req_row==None:
return redirect(url_for('show_shop',shopid=shopid))
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
contents_file=open(data_file,'rt')
contents=contents_file.read().decode('utf-8').split("\n")
contents_file.close()
changed=False
for i,row in enumerate(contents):
if i==req_row or req_row==-1:
if '[ ]' in row:
contents[i]=row.replace('[ ]','[x]')
if '[x]' in row:
contents[i]=row.replace('[x]','[ ]')
if row!=contents[i]:
changed=True
if changed:
contents_file=open(data_file,'wt')
contents_file.write("\n".join(contents).encode('utf-8'))
contents_file.close()
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/remove_toggled', methods=['POST'])
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]
ownername=get_username(ownerid)
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
backup=data_file+".bkp"
contents_file=open(data_file,'rt')
contents=[]
changed=False
for i,row in enumerate(contents_file.read().decode('utf-8').split("\n")):
if '[x]' not in row:
contents.append(row)
else:
changed=True
contents_file.close()
if changed:
copyfile(data_file, backup)
contents_file=open(data_file,'wt')
contents_file.write("\n".join(contents).encode('utf-8'))
contents_file.close()
#~ flash('successfully posted %s (%d)'%(row,req_row))
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/add_shop', methods=['POST'])
def add_shop():
if not session.get('logged_in'):
abort(401)
import re, string
pattern = re.compile('[\W]+')
shopname=pattern.sub('', request.form['shop'])
if shopname=="":
flash('Shop name empty!')
return redirect(url_for('list_shops'))
cur = g.db.execute('select * from shops order by shop')
for row in cur.fetchall():
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.commit()
new_dir=os.path.join(DATADIR, get_username(session['user']))
new_file=os.path.join(new_dir, shopname+".md")
if not os.path.exists(new_dir):
os.mkdir(new_dir)
open( new_file, 'at')
flash('successfully created new shop: '+shopname)
return redirect(url_for('list_shops'))
@app.route('/add_share', methods=['POST'])
def add_share():
if not session.get('logged_in'):
abort(401)
import re, string
pattern = re.compile('[\W]+')
username=pattern.sub('', request.form['share'])
shopid=pattern.sub('', request.form['shopid'])
if username=="":
flash('User name empty!')
return redirect(url_for('show_shop',shopid=shopid))
userid=get_userid(username)
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]
if session.get('user')!=ownerid:
flash('Not your shop!')
return redirect(url_for('show_shop',shopid=shopid))
g.db.execute('insert into shares (shopid,userid) values (?, ?)',
[shopid, userid])
g.db.commit()
flash('Shared to %s'%(username))
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/remove_share', methods=['POST'])
def remove_share():
if not session.get('logged_in'):
abort(401)
import re, string
pattern = re.compile('[\W]+')
username=pattern.sub('', request.form['user'])
shopid=pattern.sub('', request.form['shopid'])
if username=="":
flash('User name empty!')
return redirect(url_for('show_shop',shopid=shopid))
userid=get_userid(username)
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]
if session.get('user')!=ownerid:
flash('Not your shop!')
return redirect(url_for('show_shop',shopid=shopid))
g.db.execute('delete from shares where shopid=? and userid=?',
[shopid, userid])
g.db.commit()
return redirect(url_for('show_shop',shopid=shopid))
@app.route('/remove_shop', methods=['POST'])
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]
ownername=get_username(ownerid)
data_dir=os.path.join(DATADIR, ownername)
data_file=os.path.join(data_dir, shopname+".md")
if session.get('user')!=ownerid:
flash('Not your shop!')
return redirect(url_for('show_shop',shopid=shopid))
# remove shop DB
g.db.execute('delete from shops where id=?',
[shopid])
g.db.commit()
# backup data, and remove
backup=data_file+".bkp"
copyfile(data_file, backup)
os.remove(data_file)
# remove shares
g.db.execute('delete from shares where shopid=?',
[shopid])
g.db.commit()
flash('successfully deleted shop %s'%(shopname))
return redirect(url_for('list_shops'))
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
cur = g.db.execute('select * from users')
for row in cur.fetchall():
if request.form['username'] == row[1]:
if password_hash(request.form['password']) == row[2]:
session['logged_in'] = True
session['user'] = row[0]
# scan_for_new_documents(row[0])
return redirect(url_for('list_shops'))
error='Invalid user/pass'
return render_template('login.html', error=error)
@app.route('/register', methods=['GET', 'POST'])
def register():
error = None
if request.method == 'POST':
import re, string
pattern = re.compile('[\W]+')
username=pattern.sub('', request.form['username'])
password=password_hash(request.form['password'])
if len(username)==0:
error="No username given"
return render_template('register.html', error=error)
if len(request.form['password'])<5:
error="Password too short"
return render_template('register.html', error=error)
cur = g.db.execute('select * from users')
for row in cur.fetchall():
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.commit()
flash('successfully registered user "%s". Now login.'%username)
return redirect(url_for('login'))
return render_template('register.html', error=error)
@app.route('/profile', methods=['GET', 'POST'])
def profile():
if not session.get('logged_in'):
return redirect(url_for('login'))
error = None
user=get_username(session.get('user'))
if request.method == 'POST':
import re, 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.commit()
flash('successfully updated profile.')
return redirect(url_for('profile'))
return render_template('profile.html', error=error,user=user)
@app.route('/logout')
def logout():
session.pop('logged_in', None)
session.pop('user', None)
flash('You were logged out')
return render_template('login.html', error=None)
if __name__ == '__main__':
app.run()