417 lines
15 KiB
Python
417 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
# all the imports
|
|
import sqlite3, time, datetime, hashlib, os
|
|
from shutil import copyfile
|
|
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'
|
|
|
|
# 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 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()
|
|
|
|
@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/<shopid>')
|
|
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")
|
|
entries=[]
|
|
content=open(data_file, 'rt').read()
|
|
for i,row in enumerate(open( data_file, 'rt').read().split("\n")):
|
|
# any parsing magick would be here
|
|
if row=="":
|
|
continue
|
|
icon=" "
|
|
extra_class="noitem"
|
|
if "[ ]" in row or "[x]" in row:
|
|
icon=u"\u2714"
|
|
extra_class=""
|
|
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))
|
|
|
|
@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())
|
|
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')
|
|
for row in request.form['edit_md'].split("\n"):
|
|
contents_file.write("%s\n"%row)
|
|
contents_file.close()
|
|
flash('Saved new file.')
|
|
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 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().split("\n")
|
|
contents_file.close()
|
|
changed=False
|
|
for i,row in enumerate(contents):
|
|
if i==req_row:
|
|
if '[ ]' in row:
|
|
contents[i]=row.replace('[ ]','[x]')
|
|
if '[x]' in row:
|
|
contents[i]=row.replace('[x]','[ ]')
|
|
if row!=contents[i]:
|
|
changed=True
|
|
break
|
|
if changed:
|
|
contents_file=open(data_file,'wt')
|
|
contents_file.write("\n".join(contents))
|
|
contents_file.close()
|
|
#~ flash('successfully posted %s (%d)'%(row,req_row))
|
|
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")
|
|
contents_file=open(data_file,'rt')
|
|
contents=[]
|
|
changed=False
|
|
for i,row in enumerate(contents_file.read().split("\n")):
|
|
if '[x]' not in row:
|
|
contents.append(row)
|
|
changed=True
|
|
contents_file.close()
|
|
if changed:
|
|
contents_file=open(data_file,'wt')
|
|
contents_file.write("\n".join(contents))
|
|
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('/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()
|