mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
basic implementation of DB upgrades
This commit is contained in:
parent
29f50f47b8
commit
b02f5dac5b
12
src/app.py
12
src/app.py
@ -14,6 +14,7 @@ from password_api import password_api
|
|||||||
from settings_api import settings_api
|
from settings_api import settings_api
|
||||||
from notes_history_api import notes_history_api
|
from notes_history_api import notes_history_api
|
||||||
from audit_api import audit_api
|
from audit_api import audit_api
|
||||||
|
from migration_api import migration_api, APP_DB_VERSION
|
||||||
import config_provider
|
import config_provider
|
||||||
import my_scrypt
|
import my_scrypt
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ app.register_blueprint(password_api)
|
|||||||
app.register_blueprint(settings_api)
|
app.register_blueprint(settings_api)
|
||||||
app.register_blueprint(notes_history_api)
|
app.register_blueprint(notes_history_api)
|
||||||
app.register_blueprint(audit_api)
|
app.register_blueprint(audit_api)
|
||||||
|
app.register_blueprint(migration_api)
|
||||||
|
|
||||||
class User(UserMixin):
|
class User(UserMixin):
|
||||||
pass
|
pass
|
||||||
@ -48,8 +50,18 @@ def login_form():
|
|||||||
@app.route('/app', methods=['GET'])
|
@app.route('/app', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def show_app():
|
def show_app():
|
||||||
|
db_version = int(getOption('db_version'))
|
||||||
|
|
||||||
|
if db_version != APP_DB_VERSION:
|
||||||
|
return redirect('migration')
|
||||||
|
|
||||||
return render_template('app.html')
|
return render_template('app.html')
|
||||||
|
|
||||||
|
@app.route('/migration', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def show_migration():
|
||||||
|
return render_template('migration.html')
|
||||||
|
|
||||||
@app.route('/logout', methods=['POST'])
|
@app.route('/logout', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
|
@ -7,25 +7,29 @@ from shutil import copyfile
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
def backup():
|
def regular_backup():
|
||||||
now = utils.nowTimestamp()
|
now = utils.nowTimestamp()
|
||||||
last_backup_date = int(getOption('last_backup_date'))
|
last_backup_date = int(getOption('last_backup_date'))
|
||||||
|
|
||||||
if now - last_backup_date > 43200:
|
if now - last_backup_date > 43200:
|
||||||
config = config_provider.getConfig()
|
backup_now()
|
||||||
|
|
||||||
document_path = config['Document']['documentPath']
|
|
||||||
backup_directory = config['Backup']['backupDirectory']
|
|
||||||
|
|
||||||
date_str = datetime.utcnow().strftime("%Y-%m-%d %H:%M")
|
|
||||||
|
|
||||||
copyfile(document_path, backup_directory + "/" + "backup-" + date_str + ".db")
|
|
||||||
|
|
||||||
setOption('last_backup_date', now)
|
|
||||||
commit()
|
|
||||||
|
|
||||||
cleanup_old_backups()
|
cleanup_old_backups()
|
||||||
|
|
||||||
|
def backup_now():
|
||||||
|
now = utils.nowTimestamp()
|
||||||
|
|
||||||
|
config = config_provider.getConfig()
|
||||||
|
|
||||||
|
document_path = config['Document']['documentPath']
|
||||||
|
backup_directory = config['Backup']['backupDirectory']
|
||||||
|
|
||||||
|
date_str = datetime.utcnow().strftime("%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
|
copyfile(document_path, backup_directory + "/" + "backup-" + date_str + ".db")
|
||||||
|
|
||||||
|
setOption('last_backup_date', now)
|
||||||
|
commit()
|
||||||
|
|
||||||
def cleanup_old_backups():
|
def cleanup_old_backups():
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
|
72
src/migration_api.py
Normal file
72
src/migration_api.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
from sql import getOption, setOption, commit, execute_script
|
||||||
|
|
||||||
|
import backup
|
||||||
|
|
||||||
|
APP_DB_VERSION = 0
|
||||||
|
|
||||||
|
MIGRATIONS_DIR = "src/migrations"
|
||||||
|
|
||||||
|
migration_api = Blueprint('migration_api', __name__)
|
||||||
|
|
||||||
|
@migration_api.route('/api/migration', methods = ['GET'])
|
||||||
|
@login_required
|
||||||
|
def getMigrationInfo():
|
||||||
|
return jsonify({
|
||||||
|
'db_version': int(getOption('db_version')),
|
||||||
|
'app_db_version': APP_DB_VERSION
|
||||||
|
})
|
||||||
|
|
||||||
|
@migration_api.route('/api/migration', methods = ['POST'])
|
||||||
|
@login_required
|
||||||
|
def runMigration():
|
||||||
|
migrations = []
|
||||||
|
|
||||||
|
backup.backup_now()
|
||||||
|
|
||||||
|
current_db_version = int(getOption('db_version'))
|
||||||
|
|
||||||
|
for file in os.listdir(MIGRATIONS_DIR):
|
||||||
|
match = re.search(r"([0-9]{4})__([a-zA-Z0-9_ ]+)\.sql", file)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
db_version = int(match.group(1))
|
||||||
|
|
||||||
|
if db_version > current_db_version:
|
||||||
|
name = match.group(2)
|
||||||
|
|
||||||
|
migration_record = {
|
||||||
|
'db_version': db_version,
|
||||||
|
'name': name
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations.append(migration_record)
|
||||||
|
|
||||||
|
with open(MIGRATIONS_DIR + "/" + file, 'r') as sql_file:
|
||||||
|
sql = sql_file.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
execute_script(sql)
|
||||||
|
|
||||||
|
setOption('db_version', db_version)
|
||||||
|
commit()
|
||||||
|
|
||||||
|
migration_record['success'] = True
|
||||||
|
except:
|
||||||
|
migration_record['success'] = False
|
||||||
|
migration_record['error'] = traceback.format_exc()
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
migrations.sort(key=lambda x: x['db_version'])
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'migrations': migrations
|
||||||
|
})
|
@ -64,6 +64,11 @@ def execute(sql, params=[]):
|
|||||||
cursor.execute(sql, params)
|
cursor.execute(sql, params)
|
||||||
return cursor
|
return cursor
|
||||||
|
|
||||||
|
def execute_script(sql):
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.executescript(sql)
|
||||||
|
return cursor
|
||||||
|
|
||||||
def getResults(sql, params=[]):
|
def getResults(sql, params=[]):
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
query = cursor.execute(sql, params)
|
query = cursor.execute(sql, params)
|
||||||
|
@ -98,9 +98,9 @@
|
|||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<button class="btn btn-sm" id="recentNotesJumpTo">Jump to</button>
|
<button class="btn btn-sm" id="recentNotesJumpTo">Jump to (enter)</button>
|
||||||
|
|
||||||
<button class="btn btn-sm" id="recentNotesAddLink">Add link</button>
|
<button class="btn btn-sm" id="recentNotesAddLink">Add link (l)</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
57
src/templates/migration.html
Normal file
57
src/templates/migration.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Migration</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="width: 800px; margin: auto;">
|
||||||
|
<h1>Migration</h1>
|
||||||
|
|
||||||
|
<div id="up-to-date" style="display:none;">
|
||||||
|
<p>Your database is up-to-date with the application.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="need-to-migrate" style="display:none;">
|
||||||
|
<p>Your database needs to be migrated to new version before you can use the application again.
|
||||||
|
Database will be backed up before migration in case of something going wrong.</p>
|
||||||
|
|
||||||
|
<table class="table table-bordered" style="width: 200px;">
|
||||||
|
<tr>
|
||||||
|
<th>Application version:</th>
|
||||||
|
<td id="app-db-version" style="text-align: right;"></td>
|
||||||
|
<tr>
|
||||||
|
<th>Database version:</th>
|
||||||
|
<td id="db-version" style="text-align: right;"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<button class="btn btn-warning" id="run-migration">Run migration</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="migration-result" style="display:none;">
|
||||||
|
<h2>Migration result</h2>
|
||||||
|
|
||||||
|
<table id="migration-table" class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Database version</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Success</th>
|
||||||
|
<th>Error</th>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
const baseApiUrl = 'api/';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="stat/lib/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<link href="stat/lib/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||||
|
<script src="stat/lib/bootstrap/js/bootstrap.js"></script>
|
||||||
|
|
||||||
|
<script src="stat/js/migration.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -13,7 +13,7 @@ tree_api = Blueprint('tree_api', __name__)
|
|||||||
@tree_api.route('/api/tree', methods = ['GET'])
|
@tree_api.route('/api/tree', methods = ['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def getTree():
|
def getTree():
|
||||||
backup.backup()
|
backup.regular_backup()
|
||||||
|
|
||||||
notes = getResults("select "
|
notes = getResults("select "
|
||||||
"notes_tree.*, "
|
"notes_tree.*, "
|
||||||
|
43
static/js/migration.js
Normal file
43
static/js/migration.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
$(document).ready(() => {
|
||||||
|
$.get(baseApiUrl + 'migration').then(result => {
|
||||||
|
const appDbVersion = result.app_db_version;
|
||||||
|
const dbVersion = result.db_version;
|
||||||
|
|
||||||
|
if (appDbVersion === dbVersion) {
|
||||||
|
$("#up-to-date").show();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#need-to-migrate").show();
|
||||||
|
|
||||||
|
$("#app-db-version").html(appDbVersion);
|
||||||
|
$("#db-version").html(dbVersion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#run-migration").click(() => {
|
||||||
|
$("#run-migration").prop("disabled", true);
|
||||||
|
|
||||||
|
$("#migration-result").show();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: baseApiUrl + 'migration',
|
||||||
|
type: 'POST',
|
||||||
|
success: result => {
|
||||||
|
for (const migration of result.migrations) {
|
||||||
|
const row = $('<tr>')
|
||||||
|
.append($('<td>').html(migration.db_version))
|
||||||
|
.append($('<td>').html(migration.name))
|
||||||
|
.append($('<td>').html(migration.success ? 'Yes' : 'No'))
|
||||||
|
.append($('<td>').html(migration.success ? 'N/A' : migration.error));
|
||||||
|
|
||||||
|
if (!migration.success) {
|
||||||
|
row.addClass("danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#migration-table").append(row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => alert("Migration failed with unknown error")
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user