Compare commits
8 Commits
93e8ebd2c2
...
ISSUE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2c947ca00 | ||
|
|
96253f469e | ||
|
|
6888dd36e5 | ||
|
|
e006da8033 | ||
|
|
5d3fc47286 | ||
|
|
dd66e6f3b7 | ||
|
|
3563b08d6d | ||
|
|
a4f8dd5abb |
88
README.md
88
README.md
@@ -23,9 +23,88 @@ do not deploy this way, the packaged flask server is not secure. production inst
|
||||
- re-building the entire database with `flask --app myriad init-db` (losing all the data inside) will be necessary as development continues. DO NOT STORE ANYTHING IMPORTANT DURING DEVELOPMENT
|
||||
- start the site with `flask --app myriad run --debug` as usual
|
||||
|
||||
# prod set up
|
||||
# prod set up (linux)
|
||||
|
||||
it is recommended to run waitress in a tmux window for easier management
|
||||
|
||||
create new tmux window
|
||||
|
||||
`tmux new -s myriad`
|
||||
|
||||
detach from tmux window
|
||||
|
||||
`ctrl+b` -> `d`
|
||||
|
||||
attach to existing tmux window
|
||||
|
||||
`tmux attach -t myriad`
|
||||
|
||||
## installing
|
||||
|
||||
clone the repo and set up virtual env
|
||||
|
||||
`git clone https://tea.cubes.link/cube/myriad.git`
|
||||
|
||||
`cd myriad`
|
||||
|
||||
`python3 -m venv venv`
|
||||
|
||||
`source venv/bin/activate`
|
||||
|
||||
install necessary packages in the virtual env
|
||||
|
||||
`pip install flask`
|
||||
|
||||
`pip install waitress`
|
||||
|
||||
make sure all directories exist
|
||||
|
||||
`mkdir instance`
|
||||
|
||||
`mkdir myriad/static/icons`
|
||||
|
||||
`mkdir myriad/static/blinkies`
|
||||
|
||||
`mkdir myriad/static/stamps`
|
||||
|
||||
`mkdir myriad/static/tmp`
|
||||
|
||||
copy sample from readme and paste into the following
|
||||
|
||||
`sudo nano instance/config.py`
|
||||
|
||||
in a window where you can copy the output, (or in python itself) generate a secret key and copy it to the clipboard
|
||||
|
||||
`python -c 'import secrets; print(secrets.token_hex())'`
|
||||
|
||||
add a new line to config.py and paste in the key
|
||||
|
||||
`SECRET_KEY = '(paste generated key here)'`
|
||||
|
||||
initialize the database
|
||||
|
||||
`flask --app myriad init-db`
|
||||
|
||||
start running the site
|
||||
|
||||
`waitress-serve --port=80 --call 'myriad:create_app'`
|
||||
|
||||
after running and setting up first user stop waitress (CTRL+C), then edit the config to disable user registration for security
|
||||
|
||||
`sudo nano instance/config.py`
|
||||
|
||||
`REGISTRATION = False`
|
||||
|
||||
once you have disabled registration you will need to go to /auth/login in order to log in. removing the link in the sidebar just (slightly) obfuscates the possibilty from passing users
|
||||
|
||||
## updating
|
||||
|
||||
stop waitress (CTRL+C) and use `git pull` to pull changes
|
||||
|
||||
start waitress again to reload
|
||||
|
||||
if it is a database changing update, use backup features, re-init db, then re-import from backup
|
||||
|
||||
- not ready yet
|
||||
|
||||
# config
|
||||
|
||||
@@ -37,8 +116,6 @@ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} # Can be anything you want
|
||||
ICON_UPLOAD_FOLDER = 'myriad/static/icons' # where member icons will be stored
|
||||
BLINKIES_UPLOAD_FOLDER = 'myriad/static/blinkies' # where site assets "blinkies" will be stored
|
||||
STAMPS_UPLOAD_FOLDER = 'myriad/static/stamps' # where site assets "stamps" will be stored
|
||||
INLINE_UPLOAD_FOLDER = 'myriad/static/inline' # where site assets "misc inline" will be stored
|
||||
MISC_UPLOAD_FOLDER = 'myriad/static/misc' # where other small images will be stored
|
||||
THEMES_FOLDER = 'myriad/static/themes' # where theme choices will be stored
|
||||
TMP_FOLDER = 'myriad/static/tmp' # folder for creating export files
|
||||
```
|
||||
@@ -70,6 +147,3 @@ TMP_FOLDER = 'myriad/static/tmp' # folder for creating export files
|
||||
|
||||
- a sample part of the administration
|
||||

|
||||
|
||||
- the theme editor is pretty ugly right now but it does work
|
||||

|
||||
259
myriad/manage.py
259
myriad/manage.py
@@ -13,11 +13,12 @@ bp = Blueprint('manage', __name__, url_prefix='/manage')
|
||||
def new():
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
subtitle = request.form["subtitle"]
|
||||
bio = request.form['bio']
|
||||
user_id = g.user[0]
|
||||
db = get_db()
|
||||
|
||||
db.execute("INSERT INTO member (user_id, member_name, bio) VALUES (?, ?, ?)",(user_id, name, bio))
|
||||
db.execute("INSERT INTO member (user_id, member_name, bio, subtitle) VALUES (?, ?, ?, ?)",(user_id, name, bio, subtitle))
|
||||
db.commit()
|
||||
return redirect(url_for('home.full_list'))
|
||||
|
||||
@@ -25,7 +26,7 @@ def new():
|
||||
|
||||
@bp.route("/import", methods=("GET","POST"))
|
||||
@login_required
|
||||
def import_member():
|
||||
def import_member_route():
|
||||
db = get_db()
|
||||
response=""
|
||||
data=None
|
||||
@@ -36,25 +37,7 @@ def import_member():
|
||||
file = request.files["file"]
|
||||
if file.filename.split(".")[1].lower() == "json":
|
||||
response = json.loads(file.read())
|
||||
|
||||
date_raw = response["date-created"].split(",")
|
||||
date = date_raw[0]
|
||||
day,month,year = date.split("/")
|
||||
time = date_raw[1]
|
||||
hour,minute,second = time.split(":")
|
||||
date_created = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
|
||||
|
||||
user_id = g.user[0]
|
||||
|
||||
if response["privacy"] == "public":
|
||||
privacy = 1
|
||||
else:
|
||||
privacy = 0
|
||||
|
||||
data = [date_created, user_id, response["name"], response["description"], privacy]
|
||||
db.execute("INSERT INTO member (created,user_id, member_name, bio,public) VALUES (?, ?, ?, ?, ?)",(data[0], data[1], data[2], data[3], data[4]))
|
||||
db.commit()
|
||||
|
||||
import_member(response)
|
||||
last = db.execute('SELECT last_insert_rowid()').fetchone()
|
||||
mid = last[0]
|
||||
else:
|
||||
@@ -253,13 +236,88 @@ def remove_home(mid,location):
|
||||
else:
|
||||
return redirect(url_for('home.full_list'))
|
||||
|
||||
|
||||
def import_groups(groups):
|
||||
db = get_db()
|
||||
for group in groups:
|
||||
gid = group["id"]
|
||||
name = group["name"]
|
||||
description = group["description"]
|
||||
|
||||
if group["privacy"] == "public":
|
||||
privacy = 1
|
||||
else:
|
||||
privacy = 0
|
||||
|
||||
db.execute("INSERT INTO groups (id, group_name, group_description, public) VALUES (?, ?, ?, ?)", (gid, name, description, privacy))
|
||||
db.commit()
|
||||
|
||||
def import_member(member):
|
||||
db = get_db()
|
||||
|
||||
date_raw = member["date-created"].split(",")
|
||||
date = date_raw[0]
|
||||
day,month,year = date.split("/")
|
||||
time = date_raw[1]
|
||||
hour,minute,second = time.split(":")
|
||||
date_created = datetime.datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
|
||||
|
||||
name = member["name"]
|
||||
subtitle = member["subtitle"]
|
||||
description = member["description"]
|
||||
|
||||
if member["privacy"] == "public":
|
||||
privacy = 1
|
||||
else:
|
||||
privacy = 0
|
||||
|
||||
theme = member["theme"]
|
||||
|
||||
#db.execute("INSERT INTO member (created,user_id, member_name, bio,public) VALUES (?, ?, ?, ?, ?)",(data[0], data[1], data[2], data[3], data[4]))
|
||||
#db.commit()
|
||||
|
||||
|
||||
@bp.route("/admin", methods=("GET", "POST"))
|
||||
@login_required
|
||||
def admin():
|
||||
db = get_db()
|
||||
|
||||
if request.method == "POST":
|
||||
if "json" in request.files:
|
||||
file = request.files["json"].read()
|
||||
file_json = json.loads(file)
|
||||
|
||||
groups = file_json["groups"]
|
||||
import_groups(groups)
|
||||
|
||||
members = file_json["members"]
|
||||
|
||||
for member in members:
|
||||
import_member(member)
|
||||
|
||||
|
||||
return "<a href='/'>go home</a>"
|
||||
|
||||
elif "zip" in request.files:
|
||||
return "upload zip"
|
||||
|
||||
users = db.execute("SELECT * FROM user").fetchall()
|
||||
|
||||
return render_template("manage/admin.html", users=users)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# DATA EXPORTS
|
||||
|
||||
@bp.route("/export_fields/<mid>")
|
||||
@login_required
|
||||
def export_fields(mid):
|
||||
def generate_json(mid):
|
||||
db = get_db()
|
||||
member = db.execute("SELECT * FROM member WHERE id=(?)",(mid,)).fetchone()
|
||||
groups_r = db.execute("SELECT group_id FROM group_members WHERE member_id=(?)",(mid,)).fetchall()
|
||||
blog_r = db.execute("SELECT * FROM blog WHERE member_id=(?)",(mid,)).fetchall()
|
||||
|
||||
if member[9] == 1:
|
||||
privacy = "public"
|
||||
@@ -268,16 +326,76 @@ def export_fields(mid):
|
||||
|
||||
date_created = member[2].strftime("%d/%m/%Y, %H:%M:%S")
|
||||
|
||||
groups = []
|
||||
for group in groups_r:
|
||||
groups.append(group[0])
|
||||
blog = []
|
||||
for post in blog_r:
|
||||
d_c = post[2].strftime("%d/%m/%Y, %H:%M:%S")
|
||||
|
||||
if post[5] == 1:
|
||||
priv = "public"
|
||||
else:
|
||||
priv = "private"
|
||||
|
||||
p = {
|
||||
"id":post[0],
|
||||
"member-id":post[1],
|
||||
"date-created":d_c,
|
||||
"date-format":"d/m/Y, H:M:S",
|
||||
"title":post[3],
|
||||
"content":post[4],
|
||||
"privacy":priv
|
||||
}
|
||||
|
||||
blog.append(p)
|
||||
|
||||
|
||||
data = {
|
||||
"id":mid,
|
||||
"date-created":date_created,
|
||||
"date-format":"d/m/Y, H:M:S",
|
||||
"name":member[3],
|
||||
"subtitle":member[4],
|
||||
"description":member[5],
|
||||
"privacy":privacy
|
||||
"privacy":privacy,
|
||||
"theme":member[10],
|
||||
"groups":groups,
|
||||
"blog":blog
|
||||
}
|
||||
|
||||
filename = str(member[0]) + "-" + member[3] + ".json"
|
||||
return data
|
||||
|
||||
def generate_json_groups():
|
||||
db = get_db()
|
||||
groups_r = db.execute("SELECT * FROM groups").fetchall()
|
||||
|
||||
groups = []
|
||||
for group in groups_r:
|
||||
if group[3] == 1:
|
||||
priv = "public"
|
||||
else:
|
||||
priv = "private"
|
||||
|
||||
g = {
|
||||
"id":group[0],
|
||||
"name":group[1],
|
||||
"description":group[2],
|
||||
"privacy":priv
|
||||
}
|
||||
|
||||
groups.append(g)
|
||||
return groups
|
||||
|
||||
@bp.route("/export_fields/<mid>")
|
||||
@login_required
|
||||
def export_fields(mid):
|
||||
db = get_db()
|
||||
member = db.execute("SELECT * FROM member WHERE id=(?)",(mid,)).fetchone()
|
||||
|
||||
data = generate_json(mid)
|
||||
|
||||
filename = str(mid) + "-" + member[3] + ".json"
|
||||
full_path = current_app.config["TMP_FOLDER"] + "/" + filename
|
||||
with open(full_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
@@ -346,21 +464,7 @@ def export_member(mid):
|
||||
blinkies = db.execute("SELECT blinkie_location FROM blinkies WHERE member_id=(?)",(mid,)).fetchall()
|
||||
stamps = db.execute("SELECT stamp_location FROM stamps WHERE member_id=(?)",(mid,)).fetchall()
|
||||
|
||||
if member[9] == 1:
|
||||
privacy = "public"
|
||||
else:
|
||||
privacy = "private"
|
||||
|
||||
date_created = member[2].strftime("%d/%m/%Y, %H:%M:%S")
|
||||
|
||||
data = {
|
||||
"date-created":date_created,
|
||||
"date-format":"d/m/Y, H:M:S",
|
||||
"name":member[3],
|
||||
"subtitle":member[4],
|
||||
"description":member[5],
|
||||
"privacy":privacy
|
||||
}
|
||||
data = generate_json(mid)
|
||||
|
||||
data_name = str(member[0]) + "-" + member[3] + ".json"
|
||||
data_full_path = current_app.config["TMP_FOLDER"] + "/" + data_name
|
||||
@@ -384,6 +488,70 @@ def export_member(mid):
|
||||
|
||||
return send_file("static/tmp/"+zip_name, as_attachment=True)
|
||||
|
||||
@bp.route("/export_system")
|
||||
@login_required
|
||||
def export_system():
|
||||
db = get_db()
|
||||
members = db.execute("SELECT * FROM member").fetchall()
|
||||
groups_r = db.execute("SELECT * FROM groups").fetchall()
|
||||
|
||||
data = {}
|
||||
data["members"] = []
|
||||
for member in members:
|
||||
d = generate_json(member[0])
|
||||
data["members"].append(d)
|
||||
|
||||
groups = generate_json_groups()
|
||||
data["groups"] = groups
|
||||
|
||||
filename = "myriad_system_textonly.json"
|
||||
file_full_path = current_app.config["TMP_FOLDER"] + "/" + filename
|
||||
with open(file_full_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
return send_file("static/tmp/"+filename, as_attachment=True)
|
||||
|
||||
@bp.route("/export_system_full")
|
||||
@login_required
|
||||
def export_system_full():
|
||||
db = get_db()
|
||||
members = db.execute("SELECT * FROM member").fetchall()
|
||||
icons = db.execute("SELECT icon_location FROM icons").fetchall()
|
||||
blinkies = db.execute("SELECT blinkie_location FROM blinkies").fetchall()
|
||||
stamps = db.execute("SELECT stamp_location FROM stamps").fetchall()
|
||||
|
||||
data = {}
|
||||
data["members"] = []
|
||||
for member in members:
|
||||
d = generate_json(member[0])
|
||||
data["members"].append(d)
|
||||
|
||||
groups = generate_json_groups()
|
||||
data["groups"] = groups
|
||||
|
||||
filename = "myriad_system.json"
|
||||
file_full_path = current_app.config["TMP_FOLDER"] + "/" + filename
|
||||
with open(file_full_path, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
zip_name = "myriad_system.zip"
|
||||
zip_path = current_app.config["TMP_FOLDER"] + "/" + zip_name
|
||||
with zipfile.ZipFile(zip_path, "w") as zipf:
|
||||
zipf.write(file_full_path)
|
||||
for icon in icons:
|
||||
iname = icon[0]
|
||||
zipf.write(current_app.config["ICON_UPLOAD_FOLDER"] + "/" + iname)
|
||||
for blinkie in blinkies:
|
||||
bname = blinkie[0]
|
||||
zipf.write(current_app.config["BLINKIES_UPLOAD_FOLDER"] + "/" + bname)
|
||||
for stamp in stamps:
|
||||
sname = stamp[0]
|
||||
zipf.write(current_app.config["STAMPS_UPLOAD_FOLDER"] + "/" + sname)
|
||||
|
||||
return send_file("static/tmp/"+zip_name, as_attachment=True)
|
||||
|
||||
|
||||
|
||||
|
||||
# ASSETS
|
||||
|
||||
@@ -507,10 +675,7 @@ def group_delete(gid):
|
||||
|
||||
return redirect(url_for("manage.groups"))
|
||||
|
||||
@bp.route("/admin")
|
||||
@login_required
|
||||
def admin():
|
||||
db = get_db()
|
||||
users = db.execute("SELECT * FROM user").fetchall()
|
||||
|
||||
return render_template("manage/admin.html", users=users)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ DROP TABLE IF EXISTS group_members;
|
||||
DROP TABLE IF EXISTS blog;
|
||||
DROP TABLE IF EXISTS blinkies;
|
||||
DROP TABLE IF EXISTS stamps;
|
||||
DROP TABLE IF EXISTS sections;
|
||||
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -74,3 +75,11 @@ CREATE TABLE stamps (
|
||||
stamp_location TEXT NOT NULL,
|
||||
FOREIGN KEY (member_id) REFERENCES member (id)
|
||||
);
|
||||
|
||||
CREATE TABLE sections (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
member_id INTEGER NOT NULL,
|
||||
title TEXT,
|
||||
content TEXT,
|
||||
FOREIGN KEY (member_id) REFERENCES member (id)
|
||||
);
|
||||
@@ -6,7 +6,35 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="heading">Backup</div>
|
||||
<a href="{{ url_for('manage.export_system') }}">Export entire system TEXT ONLY</a><br>
|
||||
<a href="{{ url_for('manage.export_system_full') }}">Export entire system WITH ATTACHMENTS</a>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="heading">Import system from JSON</div>
|
||||
<p>You must only run full system imports on a fresh database</p>
|
||||
<form method="post" enctype="multipart/form-data" id="json">
|
||||
<input type="file" name="json">
|
||||
<input type="submit" value="Upload JSON">
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="heading">Import system from ZIP</div>
|
||||
<p>You must only run full system imports on a fresh database</p>
|
||||
<form method="post" enctype="multipart/form-data" id="zip">
|
||||
<input type="file" name="zip">
|
||||
<input type="submit" value="Upload ZIP">
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<a class="danger">Clear database</a> WARNING: this action is permanent and irreversible<br>
|
||||
|
||||
<br><br>
|
||||
|
||||
<div class="heading big pink">WIP ZONE</div>
|
||||
<div class="heading">Site Users</div>
|
||||
<div class="maintext">These are the usernames that can log in to the site</div>
|
||||
{% for user in users %}
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<form method="post">
|
||||
<label for="name">Name</label>
|
||||
<input name="name" id="name" required><br>
|
||||
<label for="subtitle">Subtitle</label>
|
||||
<input name="subtitle" id="subtitle"><br>
|
||||
<label for="bio">Description</label>
|
||||
<textarea name="bio" id="bio"></textarea><br>
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[project]
|
||||
name = "myriad"
|
||||
version = "1.0.0"
|
||||
description = "First release of myriad"
|
||||
dependencies = [
|
||||
"flask",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["flit_core<4"]
|
||||
build-backend = "flit_core.buildapi"
|
||||
Reference in New Issue
Block a user