mirror of
https://github.com/zadam/trilium.git
synced 2024-12-26 17:21:23 +08:00
app broken up into individual files/modules
This commit is contained in:
parent
415a5f58f1
commit
191f70477c
17 changed files with 426 additions and 352 deletions
|
@ -50,7 +50,7 @@ confidence=
|
|||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,C0301,C0111,R0201,W0102,C0103
|
||||
disable=print-statement,parameter-unpacking,unpacking-in-except,old-raise-syntax,backtick,long-suffix,old-ne-operator,old-octal-literal,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,old-division,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,deprecated-string-function,deprecated-str-translate-call,C0301,C0111,R0201,W0102,C0103,C0325
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
|
|
331
app.py
331
app.py
|
@ -1,331 +0,0 @@
|
|||
import sqlite3
|
||||
import base64
|
||||
from flask import Flask, request, send_from_directory
|
||||
from flask_restful import Resource, Api
|
||||
from flask_cors import CORS
|
||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
|
||||
import time
|
||||
import math
|
||||
import random
|
||||
import string
|
||||
import configparser
|
||||
import bcrypt
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import binascii
|
||||
import hashlib
|
||||
|
||||
from flask import render_template, redirect
|
||||
|
||||
def dict_factory(cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
if isinstance(row[idx], buffer):
|
||||
d[col[0]] = base64.b64encode(row[idx])
|
||||
else:
|
||||
d[col[0]] = row[idx]
|
||||
|
||||
return d
|
||||
|
||||
conn = sqlite3.connect('demo.ncdb')
|
||||
conn.row_factory = dict_factory
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'dshjkjsdhfk9832fsdlhf'
|
||||
|
||||
class User(UserMixin):
|
||||
pass
|
||||
|
||||
@app.route('/login', methods=['GET'])
|
||||
def login_form():
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/app', methods=['GET'])
|
||||
@login_required
|
||||
def show_app():
|
||||
return render_template('app.html')
|
||||
|
||||
@app.route('/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect('login')
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
user = User()
|
||||
user.id = config['Login']['username']
|
||||
|
||||
hashedPassword = config['Login']['password-hash'].encode('utf-8')
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login_post():
|
||||
inputPassword = request.form['password'].encode('utf-8')
|
||||
|
||||
if request.form['username'] == user.id and bcrypt.hashpw(inputPassword, hashedPassword) == hashedPassword:
|
||||
rememberMe = True if 'remember-me' in request.form else False
|
||||
|
||||
login_user(user, remember=rememberMe)
|
||||
|
||||
return redirect('app')
|
||||
else:
|
||||
return render_template('login.html', failedAuth=True)
|
||||
|
||||
CORS(app)
|
||||
|
||||
@app.route('/static/<path:path>')
|
||||
def send_static(path):
|
||||
return send_from_directory('static', path)
|
||||
|
||||
api = Api(app)
|
||||
|
||||
def insert(tablename, rec):
|
||||
keys = ','.join(rec.keys())
|
||||
question_marks = ','.join(list('?'*len(rec)))
|
||||
values = tuple(rec.values())
|
||||
cursor = execute('INSERT INTO '+tablename+' ('+keys+') VALUES ('+question_marks+')', values)
|
||||
return cursor.lastrowid
|
||||
|
||||
def delete(tablename, note_id):
|
||||
execute("DELETE FROM " + tablename + " WHERE note_id = ?", [note_id])
|
||||
|
||||
def execute(sql, params=[]):
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(sql, params)
|
||||
return cursor
|
||||
|
||||
def getResults(sql, params=[]):
|
||||
cursor = conn.cursor()
|
||||
query = cursor.execute(sql, params)
|
||||
return query.fetchall()
|
||||
|
||||
def getSingleResult(sql, params=()):
|
||||
cursor = conn.cursor()
|
||||
query = cursor.execute(sql, params)
|
||||
return query.fetchone()
|
||||
|
||||
class Query(Resource):
|
||||
def get(self):
|
||||
sql = request.args.get('sql')
|
||||
|
||||
return getResults(sql)
|
||||
|
||||
api.add_resource(Query, '/query')
|
||||
|
||||
class Notes(Resource):
|
||||
def get(self, note_id):
|
||||
return {
|
||||
'detail': getSingleResult("select * from notes where note_id = ?", [note_id]),
|
||||
'formatting': getResults("select * from formatting where note_id = ? order by note_offset", [note_id]),
|
||||
'links': getResults("select * from links where note_id = ? order by note_offset", [note_id]),
|
||||
'images': getResults("select * from images where note_id = ? order by note_offset", [note_id])
|
||||
}
|
||||
|
||||
def put(self, note_id):
|
||||
note = request.get_json(force=True)
|
||||
|
||||
now = math.floor(time.time())
|
||||
|
||||
execute("update notes set note_text = ?, note_title = ?, date_modified = ? where note_id = ?", [note['detail']['note_text'], note['detail']['note_title'], now, note_id])
|
||||
|
||||
delete("formatting", note_id)
|
||||
|
||||
for fmt in note['formatting']:
|
||||
insert("formatting", fmt)
|
||||
|
||||
delete("images", note_id)
|
||||
|
||||
for img in note['images']:
|
||||
img['image_data'] = buffer(base64.b64decode(img['image_data']))
|
||||
|
||||
insert("images", img)
|
||||
|
||||
delete("links", note_id)
|
||||
|
||||
for link in note['links']:
|
||||
insert("links", link)
|
||||
|
||||
conn.commit()
|
||||
|
||||
return {}
|
||||
|
||||
def delete(self, note_id):
|
||||
children = getResults("select note_id from notes_tree where note_pid = ?", [note_id])
|
||||
|
||||
for child in children:
|
||||
self.delete(child['note_id'])
|
||||
|
||||
delete("notes_tree", note_id)
|
||||
delete("notes", note_id)
|
||||
|
||||
conn.commit()
|
||||
|
||||
api.add_resource(Notes, '/notes/<string:note_id>')
|
||||
|
||||
class NotesChildren(Resource):
|
||||
def post(self, parent_note_id):
|
||||
note = request.get_json(force=True)
|
||||
|
||||
noteId = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(22))
|
||||
|
||||
if parent_note_id == "root":
|
||||
parent_note_id = ""
|
||||
|
||||
new_note_pos = 0
|
||||
|
||||
if note['target'] == 'into':
|
||||
res = getSingleResult('select max(note_pos) as max_note_pos from notes_tree where note_pid = ?', [parent_note_id])
|
||||
max_note_pos = res['max_note_pos']
|
||||
|
||||
if max_note_pos is None: # no children yet
|
||||
new_note_pos = 0
|
||||
else:
|
||||
new_note_pos = max_note_pos + 1
|
||||
elif note['target'] == 'after':
|
||||
after_note = getSingleResult('select note_pos from notes_tree where note_id = ?', [note['target_note_id']])
|
||||
|
||||
new_note_pos = after_note['note_pos'] + 1
|
||||
|
||||
execute('update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos > ?', [parent_note_id, after_note['note_pos']])
|
||||
else:
|
||||
raise Exception('Unknown target: ' + note['target'])
|
||||
|
||||
now = math.floor(time.time())
|
||||
|
||||
insert("notes", {
|
||||
'note_id': noteId,
|
||||
'note_title': note['note_title'],
|
||||
'note_text': '',
|
||||
'note_clone_id': '',
|
||||
'date_created': now,
|
||||
'date_modified': now,
|
||||
'icon_info': 'pencil',
|
||||
'is_finished': 0
|
||||
})
|
||||
|
||||
insert("notes_tree", {
|
||||
'note_id': noteId,
|
||||
'note_pid': parent_note_id,
|
||||
'note_pos': new_note_pos,
|
||||
'is_expanded': 0
|
||||
})
|
||||
|
||||
conn.commit()
|
||||
|
||||
return {
|
||||
'note_id': noteId
|
||||
}
|
||||
|
||||
api.add_resource(NotesChildren, '/notes/<string:parent_note_id>/children')
|
||||
|
||||
class MoveAfterNote(Resource):
|
||||
def put(self, note_id, after_note_id):
|
||||
after_note = getSingleResult("select * from notes_tree where note_id = ?", [after_note_id])
|
||||
|
||||
if after_note <> None:
|
||||
execute("update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos > ?", [after_note['note_pid'], after_note['note_pos']])
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [after_note['note_pid'], after_note['note_pos'] + 1, note_id])
|
||||
|
||||
conn.commit()
|
||||
|
||||
api.add_resource(MoveAfterNote, '/notes/<string:note_id>/moveAfter/<string:after_note_id>')
|
||||
|
||||
class MoveBeforeNote(Resource):
|
||||
def put(self, note_id, before_note_id):
|
||||
before_note = getSingleResult("select * from notes_tree where note_id = ?", [before_note_id])
|
||||
|
||||
if before_note <> None:
|
||||
execute("update notes_tree set note_pos = note_pos + 1 where note_id = ?", [before_note_id])
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [before_note['note_pid'], before_note['note_pos'], note_id])
|
||||
|
||||
conn.commit()
|
||||
|
||||
api.add_resource(MoveBeforeNote, '/notes/<string:note_id>/moveBefore/<string:before_note_id>')
|
||||
|
||||
class MoveToNote(Resource):
|
||||
def put(self, note_id, parent_id):
|
||||
res = getSingleResult('select max(note_pos) as max_note_pos from notes_tree where note_pid = ?', [parent_id])
|
||||
max_note_pos = res['max_note_pos']
|
||||
new_note_pos = 0
|
||||
|
||||
if max_note_pos is None: # no children yet
|
||||
new_note_pos = 0
|
||||
else:
|
||||
new_note_pos = max_note_pos + 1
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [parent_id, new_note_pos, note_id])
|
||||
|
||||
conn.commit()
|
||||
|
||||
api.add_resource(MoveToNote, '/notes/<string:note_id>/moveTo/<string:parent_id>')
|
||||
|
||||
class ExpandedNote(Resource):
|
||||
def put(self, note_id, expanded):
|
||||
execute("update notes_tree set is_expanded = ? where note_id = ?", [expanded, note_id])
|
||||
|
||||
conn.commit()
|
||||
|
||||
api.add_resource(ExpandedNote, '/notes/<string:note_id>/expanded/<int:expanded>')
|
||||
|
||||
class Tree(Resource):
|
||||
def get(self):
|
||||
notes = getResults("select notes_tree.*, notes.note_title from notes_tree join notes on notes.note_id = notes_tree.note_id order by note_pid, note_pos")
|
||||
|
||||
rootNotes = []
|
||||
notesMap = {}
|
||||
|
||||
for note in notes:
|
||||
note['children'] = []
|
||||
|
||||
if not note['note_pid']:
|
||||
rootNotes.append(note)
|
||||
|
||||
notesMap[note['note_id']] = note
|
||||
|
||||
for note in notes:
|
||||
if note['note_pid'] != "":
|
||||
parent = notesMap[note['note_pid']]
|
||||
|
||||
parent['children'].append(note)
|
||||
parent['folder'] = True
|
||||
|
||||
return rootNotes
|
||||
|
||||
api.add_resource(Tree, '/tree')
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login_form'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
if user_id == user.id:
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
|
||||
syncServerUrl = config['Sync']['sync-server-url']
|
||||
syncServerUsername = config['Sync']['sync-server-username']
|
||||
syncServerPassword = config['Sync']['sync-server-password']
|
||||
|
||||
nonce = binascii.hexlify(bytearray(os.urandom(32)))
|
||||
|
||||
print('Nonce: ' + nonce)
|
||||
|
||||
# SHA256(user + ":" + SHA256(user + ":" + password) + ":" + nonce) where SHA256 is a hex encoded value
|
||||
auth = hashlib.sha256(syncServerUsername + ":" + hashlib.sha256(syncServerPassword + ":" + syncServerPassword).hexdigest() + ":" + nonce).hexdigest()
|
||||
|
||||
response = requests.post(syncServerUrl + "/login", json={
|
||||
'user': syncServerUsername,
|
||||
'nonce': nonce,
|
||||
'auth': auth
|
||||
})
|
||||
|
||||
print(response)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0')
|
|
@ -2,9 +2,9 @@
|
|||
# Enter below credentials with with which you want to authenticate to Notecase web app
|
||||
username=adam
|
||||
# This is bcrypt password hash. You can use generate-password.py (in this directory) to hash your password
|
||||
password-hash=$2b$12$jcbhRx6WRbCRogpCckH1hehWrHWgFaFYC3u3ebdVURJX36..fdAca
|
||||
password-hash=$2b$12$4Ia5lYbbpOv3pxxdoDgjUeAJ9z4FhqyEhhX52ra78FH03wPGx8zGu
|
||||
|
||||
[Sync]
|
||||
sync-server-url=http://localhost:57201
|
||||
sync-server-url=https://localhost:57201
|
||||
sync-server-username=syncuser
|
||||
sync-server-password=password
|
|
@ -1,8 +1,9 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import bcrypt # pip install bcrypt
|
||||
import getpass
|
||||
|
||||
import bcrypt # pip install bcrypt
|
||||
|
||||
password1 = getpass.getpass()
|
||||
|
||||
print('Repeat the same password:')
|
||||
|
|
2
run.sh
2
run.sh
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
export FLASK_DEBUG=1
|
||||
export FLASK_APP=app.py
|
||||
export FLASK_APP=src/app.py
|
||||
|
||||
flask run
|
89
src/app.py
Normal file
89
src/app.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
import bcrypt
|
||||
import configparser
|
||||
import os
|
||||
from flask import Flask, request, send_from_directory
|
||||
from flask import render_template, redirect
|
||||
from flask_cors import CORS
|
||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user
|
||||
from flask_restful import Api
|
||||
from move_after_note import MoveAfterNote
|
||||
from move_to_note import MoveToNote
|
||||
from notes import Notes
|
||||
from notes_children import NotesChildren
|
||||
|
||||
from expanded_note import ExpandedNote
|
||||
from move_before_note import MoveBeforeNote
|
||||
from tree import Tree
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'dshjkjsdhfk9832fsdlhf'
|
||||
|
||||
class User(UserMixin):
|
||||
pass
|
||||
|
||||
@app.route('/login', methods=['GET'])
|
||||
def login_form():
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/app', methods=['GET'])
|
||||
@login_required
|
||||
def show_app():
|
||||
return render_template('app.html')
|
||||
|
||||
@app.route('/logout', methods=['POST'])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect('login')
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
user = User()
|
||||
user.id = config['Login']['username']
|
||||
|
||||
hashedPassword = config['Login']['password-hash'].encode('utf-8')
|
||||
|
||||
@app.route('/login', methods=['POST'])
|
||||
def login_post():
|
||||
inputPassword = request.form['password'].encode('utf-8')
|
||||
|
||||
if request.form['username'] == user.id and bcrypt.hashpw(inputPassword, hashedPassword) == hashedPassword:
|
||||
rememberMe = True if 'remember-me' in request.form else False
|
||||
|
||||
login_user(user, remember=rememberMe)
|
||||
|
||||
return redirect('app')
|
||||
else:
|
||||
return render_template('login.html', failedAuth=True)
|
||||
|
||||
CORS(app)
|
||||
|
||||
@app.route('/stat/<path:path>')
|
||||
def send_stc(path):
|
||||
return send_from_directory(os.path.join(os.getcwd(), 'static'), path)
|
||||
|
||||
api = Api(app)
|
||||
|
||||
api.add_resource(NotesChildren, '/notes/<string:parent_note_id>/children')
|
||||
api.add_resource(MoveAfterNote, '/notes/<string:note_id>/moveAfter/<string:after_note_id>')
|
||||
api.add_resource(MoveBeforeNote, '/notes/<string:note_id>/moveBefore/<string:before_note_id>')
|
||||
api.add_resource(MoveToNote, '/notes/<string:note_id>/moveTo/<string:parent_id>')
|
||||
api.add_resource(ExpandedNote, '/notes/<string:note_id>/expanded/<int:expanded>')
|
||||
api.add_resource(Tree, '/tree')
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'login_form'
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
if user_id == user.id:
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
|
||||
api.add_resource(Notes, '/notes/<string:note_id>')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0')
|
10
src/expanded_note.py
Normal file
10
src/expanded_note.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from flask_restful import Resource
|
||||
|
||||
from sql import execute, commit
|
||||
|
||||
|
||||
class ExpandedNote(Resource):
|
||||
def put(self, note_id, expanded):
|
||||
execute("update notes_tree set is_expanded = ? where note_id = ?", [expanded, note_id])
|
||||
|
||||
commit()
|
15
src/move_after_note.py
Normal file
15
src/move_after_note.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from flask_restful import Resource
|
||||
|
||||
from sql import execute, getSingleResult, commit
|
||||
|
||||
|
||||
class MoveAfterNote(Resource):
|
||||
def put(self, note_id, after_note_id):
|
||||
after_note = getSingleResult("select * from notes_tree where note_id = ?", [after_note_id])
|
||||
|
||||
if after_note <> None:
|
||||
execute("update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos > ?", [after_note['note_pid'], after_note['note_pos']])
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [after_note['note_pid'], after_note['note_pos'] + 1, note_id])
|
||||
|
||||
commit()
|
15
src/move_before_note.py
Normal file
15
src/move_before_note.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from flask_restful import Resource
|
||||
|
||||
from sql import execute, getSingleResult, commit
|
||||
|
||||
|
||||
class MoveBeforeNote(Resource):
|
||||
def put(self, note_id, before_note_id):
|
||||
before_note = getSingleResult("select * from notes_tree where note_id = ?", [before_note_id])
|
||||
|
||||
if before_note <> None:
|
||||
execute("update notes_tree set note_pos = note_pos + 1 where note_id = ?", [before_note_id])
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [before_note['note_pid'], before_note['note_pos'], note_id])
|
||||
|
||||
commit()
|
19
src/move_to_note.py
Normal file
19
src/move_to_note.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from flask_restful import Resource
|
||||
|
||||
from sql import execute, getSingleResult, commit
|
||||
|
||||
|
||||
class MoveToNote(Resource):
|
||||
def put(self, note_id, parent_id):
|
||||
res = getSingleResult('select max(note_pos) as max_note_pos from notes_tree where note_pid = ?', [parent_id])
|
||||
max_note_pos = res['max_note_pos']
|
||||
new_note_pos = 0
|
||||
|
||||
if max_note_pos is None: # no children yet
|
||||
new_note_pos = 0
|
||||
else:
|
||||
new_note_pos = max_note_pos + 1
|
||||
|
||||
execute("update notes_tree set note_pid = ?, note_pos = ? where note_id = ?", [parent_id, new_note_pos, note_id])
|
||||
|
||||
commit()
|
57
src/notes.py
Normal file
57
src/notes.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import base64
|
||||
import math
|
||||
import time
|
||||
|
||||
from flask import request
|
||||
from flask_restful import Resource
|
||||
|
||||
from sql import delete, execute, insert, getResults, getSingleResult, commit
|
||||
|
||||
|
||||
class Notes(Resource):
|
||||
def get(self, note_id):
|
||||
return {
|
||||
'detail': getSingleResult("select * from notes where note_id = ?", [note_id]),
|
||||
'formatting': getResults("select * from formatting where note_id = ? order by note_offset", [note_id]),
|
||||
'links': getResults("select * from links where note_id = ? order by note_offset", [note_id]),
|
||||
'images': getResults("select * from images where note_id = ? order by note_offset", [note_id])
|
||||
}
|
||||
|
||||
def put(self, note_id):
|
||||
note = request.get_json(force=True)
|
||||
|
||||
now = math.floor(time.time())
|
||||
|
||||
execute("update notes set note_text = ?, note_title = ?, date_modified = ? where note_id = ?", [note['detail']['note_text'], note['detail']['note_title'], now, note_id])
|
||||
|
||||
delete("formatting", note_id)
|
||||
|
||||
for fmt in note['formatting']:
|
||||
insert("formatting", fmt)
|
||||
|
||||
delete("images", note_id)
|
||||
|
||||
for img in note['images']:
|
||||
img['image_data'] = buffer(base64.b64decode(img['image_data']))
|
||||
|
||||
insert("images", img)
|
||||
|
||||
delete("links", note_id)
|
||||
|
||||
for link in note['links']:
|
||||
insert("links", link)
|
||||
|
||||
commit()
|
||||
|
||||
return {}
|
||||
|
||||
def delete(self, note_id):
|
||||
children = getResults("select note_id from notes_tree where note_pid = ?", [note_id])
|
||||
|
||||
for child in children:
|
||||
self.delete(child['note_id'])
|
||||
|
||||
delete("notes_tree", note_id)
|
||||
delete("notes", note_id)
|
||||
|
||||
commit()
|
64
src/notes_children.py
Normal file
64
src/notes_children.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import math
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
|
||||
from flask import request
|
||||
from flask_restful import Resource
|
||||
|
||||
from sql import execute, insert, getSingleResult, commit
|
||||
|
||||
|
||||
class NotesChildren(Resource):
|
||||
def post(self, parent_note_id):
|
||||
note = request.get_json(force=True)
|
||||
|
||||
noteId = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(22))
|
||||
|
||||
if parent_note_id == "root":
|
||||
parent_note_id = ""
|
||||
|
||||
new_note_pos = 0
|
||||
|
||||
if note['target'] == 'into':
|
||||
res = getSingleResult('select max(note_pos) as max_note_pos from notes_tree where note_pid = ?', [parent_note_id])
|
||||
max_note_pos = res['max_note_pos']
|
||||
|
||||
if max_note_pos is None: # no children yet
|
||||
new_note_pos = 0
|
||||
else:
|
||||
new_note_pos = max_note_pos + 1
|
||||
elif note['target'] == 'after':
|
||||
after_note = getSingleResult('select note_pos from notes_tree where note_id = ?', [note['target_note_id']])
|
||||
|
||||
new_note_pos = after_note['note_pos'] + 1
|
||||
|
||||
execute('update notes_tree set note_pos = note_pos + 1 where note_pid = ? and note_pos > ?', [parent_note_id, after_note['note_pos']])
|
||||
else:
|
||||
raise Exception('Unknown target: ' + note['target'])
|
||||
|
||||
now = math.floor(time.time())
|
||||
|
||||
insert("notes", {
|
||||
'note_id': noteId,
|
||||
'note_title': note['note_title'],
|
||||
'note_text': '',
|
||||
'note_clone_id': '',
|
||||
'date_created': now,
|
||||
'date_modified': now,
|
||||
'icon_info': 'pencil',
|
||||
'is_finished': 0
|
||||
})
|
||||
|
||||
insert("notes_tree", {
|
||||
'note_id': noteId,
|
||||
'note_pid': parent_note_id,
|
||||
'note_pos': new_note_pos,
|
||||
'is_expanded': 0
|
||||
})
|
||||
|
||||
commit()
|
||||
|
||||
return {
|
||||
'note_id': noteId
|
||||
}
|
43
src/sql.py
Normal file
43
src/sql.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import sqlite3
|
||||
import base64
|
||||
|
||||
def dict_factory(cursor, row):
|
||||
d = {}
|
||||
for idx, col in enumerate(cursor.description):
|
||||
if isinstance(row[idx], buffer):
|
||||
d[col[0]] = base64.b64encode(row[idx])
|
||||
else:
|
||||
d[col[0]] = row[idx]
|
||||
|
||||
return d
|
||||
|
||||
conn = sqlite3.connect('demo.ncdb')
|
||||
conn.row_factory = dict_factory
|
||||
|
||||
def insert(tablename, rec):
|
||||
keys = ','.join(rec.keys())
|
||||
question_marks = ','.join(list('?'*len(rec)))
|
||||
values = tuple(rec.values())
|
||||
cursor = execute('INSERT INTO '+tablename+' ('+keys+') VALUES ('+question_marks+')', values)
|
||||
return cursor.lastrowid
|
||||
|
||||
def delete(tablename, note_id):
|
||||
execute("DELETE FROM " + tablename + " WHERE note_id = ?", [note_id])
|
||||
|
||||
def execute(sql, params=[]):
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(sql, params)
|
||||
return cursor
|
||||
|
||||
def getResults(sql, params=[]):
|
||||
cursor = conn.cursor()
|
||||
query = cursor.execute(sql, params)
|
||||
return query.fetchall()
|
||||
|
||||
def getSingleResult(sql, params=()):
|
||||
cursor = conn.cursor()
|
||||
query = cursor.execute(sql, params)
|
||||
return query.fetchone()
|
||||
|
||||
def commit():
|
||||
conn.commit()
|
62
src/sync.py
Normal file
62
src/sync.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import configparser
|
||||
import requests
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read('config.ini')
|
||||
|
||||
syncServerUrl = config['Sync']['sync-server-url']
|
||||
syncServerUsername = config['Sync']['sync-server-username']
|
||||
syncServerPassword = config['Sync']['sync-server-password']
|
||||
|
||||
nonce = binascii.hexlify(bytearray(os.urandom(32)))
|
||||
|
||||
print('Nonce: ' + nonce)
|
||||
|
||||
authContent = syncServerUsername + ":" + hashlib.sha256(syncServerUsername + ":" + syncServerPassword).hexdigest() + ":" + nonce
|
||||
|
||||
print('Auth content: ' + authContent)
|
||||
|
||||
# SHA256(user + ":" + SHA256(user + ":" + password) + ":" + nonce) where SHA256 is a hex encoded value
|
||||
auth = hashlib.sha256(authContent).hexdigest()
|
||||
|
||||
response = requests.post(syncServerUrl + "/login", json={
|
||||
'user': syncServerUsername,
|
||||
'nonce': nonce,
|
||||
'auth': auth,
|
||||
'protocol': '2'
|
||||
}, verify=False)
|
||||
|
||||
# verify='/home/adam/.notecase/server.pem'
|
||||
|
||||
def printResp(resp):
|
||||
print('Status: ' + str(resp.status_code))
|
||||
|
||||
for key in response.headers:
|
||||
print(key + ': ' + resp.headers[key])
|
||||
|
||||
print('Body: ' + resp.content)
|
||||
|
||||
printResp(response)
|
||||
|
||||
session = response.headers['Auth']
|
||||
|
||||
response = requests.get(syncServerUrl + "/document/list", headers={
|
||||
'Auth': session
|
||||
}, verify=False)
|
||||
|
||||
printResp(response)
|
||||
|
||||
response = requests.post(syncServerUrl + "/document/tree", headers={
|
||||
'Auth': session
|
||||
},
|
||||
json={
|
||||
'id': 1,
|
||||
'password': ''
|
||||
},
|
||||
verify=False)
|
||||
|
||||
printResp(response)
|
|
@ -49,31 +49,31 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script src="static/lib/jquery.js"></script>
|
||||
<script src="static/lib/jqueryui/jquery-ui.js"></script>
|
||||
<script src="stat/lib/jquery.js"></script>
|
||||
<script src="stat/lib/jqueryui/jquery-ui.js"></script>
|
||||
<!-- Include Fancytree skin and library -->
|
||||
<link href="static/lib/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
|
||||
<script src="static/lib/fancytree/jquery.fancytree-all.js"></script>
|
||||
<link href="stat/lib/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet">
|
||||
<script src="stat/lib/fancytree/jquery.fancytree-all.js"></script>
|
||||
|
||||
<link href="static/lib/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<script src="static/lib/bootstrap/js/bootstrap.js"></script>
|
||||
<link href="stat/lib/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<script src="stat/lib/bootstrap/js/bootstrap.js"></script>
|
||||
|
||||
<link href="static/lib/summernote/summernote.css" rel="stylesheet">
|
||||
<script src="static/lib/summernote/summernote.js"></script>
|
||||
<link href="stat/lib/summernote/summernote.css" rel="stylesheet">
|
||||
<script src="stat/lib/summernote/summernote.js"></script>
|
||||
|
||||
<script src="static/lib/jquery.hotkeys.js"></script>
|
||||
<script src="static/lib/jquery.fancytree.hotkeys.js"></script>
|
||||
<script src="stat/lib/jquery.hotkeys.js"></script>
|
||||
<script src="stat/lib/jquery.fancytree.hotkeys.js"></script>
|
||||
|
||||
<link href="static/style.css" rel="stylesheet">
|
||||
<link href="stat/style.css" rel="stylesheet">
|
||||
|
||||
<script type="text/javascript">
|
||||
const baseUrl = '';
|
||||
</script>
|
||||
|
||||
<script src="static/js/tree.js"></script>
|
||||
<script src="static/js/note.js"></script>
|
||||
<script src="static/js/notecase2html.js"></script>
|
||||
<script src="static/js/html2notecase.js"></script>
|
||||
<script src="static/js/utils.js"></script>
|
||||
<script src="stat/js/tree.js"></script>
|
||||
<script src="stat/js/note.js"></script>
|
||||
<script src="stat/js/notecase2html.js"></script>
|
||||
<script src="stat/js/html2notecase.js"></script>
|
||||
<script src="stat/js/utils.js"></script>
|
||||
</body>
|
||||
</html>
|
30
src/tree.py
Normal file
30
src/tree.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from flask_restful import Resource
|
||||
from sql import getResults
|
||||
from flask_restful import Resource
|
||||
|
||||
from sql import getResults
|
||||
|
||||
|
||||
class Tree(Resource):
|
||||
def get(self):
|
||||
notes = getResults("select notes_tree.*, notes.note_title from notes_tree join notes on notes.note_id = notes_tree.note_id order by note_pid, note_pos")
|
||||
|
||||
rootNotes = []
|
||||
notesMap = {}
|
||||
|
||||
for note in notes:
|
||||
note['children'] = []
|
||||
|
||||
if not note['note_pid']:
|
||||
rootNotes.append(note)
|
||||
|
||||
notesMap[note['note_id']] = note
|
||||
|
||||
for note in notes:
|
||||
if note['note_pid'] != "":
|
||||
parent = notesMap[note['note_pid']]
|
||||
|
||||
parent['children'].append(note)
|
||||
parent['folder'] = True
|
||||
|
||||
return rootNotes
|
Loading…
Reference in a new issue