mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-30 12:13:52 +08:00 
			
		
		
		
	app broken up into individual files/modules
This commit is contained in:
		
							parent
							
								
									415a5f58f1
								
							
						
					
					
						commit
						191f70477c
					
				| @ -50,7 +50,7 @@ confidence= | |||||||
| # --enable=similarities". If you want to run only the classes checker, but have | # --enable=similarities". If you want to run only the classes checker, but have | ||||||
| # no Warning level messages displayed, use"--disable=all --enable=classes | # no Warning level messages displayed, use"--disable=all --enable=classes | ||||||
| # --disable=W" | # --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 | # 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 | # 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 | # Enter below credentials with with which you want to authenticate to Notecase web app | ||||||
| username=adam | username=adam | ||||||
| # This is bcrypt password hash. You can use generate-password.py (in this directory) to hash your password | # 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] | ||||||
| sync-server-url=http://localhost:57201 | sync-server-url=https://localhost:57201 | ||||||
| sync-server-username=syncuser | sync-server-username=syncuser | ||||||
| sync-server-password=password | sync-server-password=password | ||||||
| @ -1,8 +1,9 @@ | |||||||
| #!/usr/bin/python | #!/usr/bin/python | ||||||
| 
 | 
 | ||||||
| import bcrypt # pip install bcrypt |  | ||||||
| import getpass | import getpass | ||||||
| 
 | 
 | ||||||
|  | import bcrypt  # pip install bcrypt | ||||||
|  | 
 | ||||||
| password1 = getpass.getpass() | password1 = getpass.getpass() | ||||||
| 
 | 
 | ||||||
| print('Repeat the same password:') | print('Repeat the same password:') | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								run.sh
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								run.sh
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | |||||||
| #!/bin/sh | #!/bin/sh | ||||||
| 
 | 
 | ||||||
| export FLASK_DEBUG=1 | export FLASK_DEBUG=1 | ||||||
| export FLASK_APP=app.py | export FLASK_APP=src/app.py | ||||||
| 
 | 
 | ||||||
| flask run | 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> | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|     <script src="static/lib/jquery.js"></script> |     <script src="stat/lib/jquery.js"></script> | ||||||
|     <script src="static/lib/jqueryui/jquery-ui.js"></script> |     <script src="stat/lib/jqueryui/jquery-ui.js"></script> | ||||||
|     <!-- Include Fancytree skin and library --> |     <!-- Include Fancytree skin and library --> | ||||||
|     <link href="static/lib/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet"> |     <link href="stat/lib/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet"> | ||||||
|     <script src="static/lib/fancytree/jquery.fancytree-all.js"></script> |     <script src="stat/lib/fancytree/jquery.fancytree-all.js"></script> | ||||||
| 
 | 
 | ||||||
|     <link href="static/lib/bootstrap/css/bootstrap.css" rel="stylesheet"> |     <link href="stat/lib/bootstrap/css/bootstrap.css" rel="stylesheet"> | ||||||
|     <script src="static/lib/bootstrap/js/bootstrap.js"></script>  |     <script src="stat/lib/bootstrap/js/bootstrap.js"></script>  | ||||||
| 
 | 
 | ||||||
|     <link href="static/lib/summernote/summernote.css" rel="stylesheet"> |     <link href="stat/lib/summernote/summernote.css" rel="stylesheet"> | ||||||
|     <script src="static/lib/summernote/summernote.js"></script> |     <script src="stat/lib/summernote/summernote.js"></script> | ||||||
| 
 | 
 | ||||||
|     <script src="static/lib/jquery.hotkeys.js"></script> |     <script src="stat/lib/jquery.hotkeys.js"></script> | ||||||
|     <script src="static/lib/jquery.fancytree.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"> |     <script type="text/javascript"> | ||||||
|       const baseUrl = ''; |       const baseUrl = ''; | ||||||
|     </script> |     </script> | ||||||
| 
 | 
 | ||||||
|     <script src="static/js/tree.js"></script> |     <script src="stat/js/tree.js"></script> | ||||||
|     <script src="static/js/note.js"></script> |     <script src="stat/js/note.js"></script> | ||||||
|     <script src="static/js/notecase2html.js"></script> |     <script src="stat/js/notecase2html.js"></script> | ||||||
|     <script src="static/js/html2notecase.js"></script> |     <script src="stat/js/html2notecase.js"></script> | ||||||
|     <script src="static/js/utils.js"></script> |     <script src="stat/js/utils.js"></script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 azivner
						azivner