2018-03-22 22:30:13 -05:00
|
|
|
#!/usr/bin/env python
|
2018-03-30 07:25:28 -05:00
|
|
|
# pylint: disable=wrong-import-order, ungrouped-imports
|
2018-03-27 21:16:54 -05:00
|
|
|
"""Server module."""
|
2019-05-14 02:44:14 -05:00
|
|
|
from typing import Any, Dict, Union # NOQA; type: ignore
|
2019-05-14 01:35:47 -05:00
|
|
|
from unittest import mock
|
2019-05-14 01:05:50 -05:00
|
|
|
from urllib.parse import urlparse
|
2018-03-26 07:06:12 -05:00
|
|
|
import os
|
2018-06-28 09:04:35 -05:00
|
|
|
import sys
|
2018-03-26 07:06:12 -05:00
|
|
|
|
2018-06-28 09:04:35 -05:00
|
|
|
from buku import BukuDb, __version__, network_handler
|
2018-03-22 22:30:13 -05:00
|
|
|
from flask.cli import FlaskGroup
|
2019-05-14 01:05:50 -05:00
|
|
|
from flask.views import MethodView
|
2018-06-28 09:04:35 -05:00
|
|
|
from flask_admin import Admin
|
|
|
|
from flask_api import exceptions, FlaskAPI, status
|
2018-03-23 07:05:31 -05:00
|
|
|
from flask_bootstrap import Bootstrap
|
2018-03-26 07:06:12 -05:00
|
|
|
from flask_paginate import Pagination, get_page_parameter, get_per_page_parameter
|
2020-02-08 06:07:19 -06:00
|
|
|
try:
|
|
|
|
from flask_reverse_proxy_fix.middleware import ReverseProxyPrefixFix
|
2020-02-08 18:33:49 -06:00
|
|
|
except ImportError:
|
2020-02-08 06:07:19 -06:00
|
|
|
ReverseProxyPrefixFix = None
|
2018-03-30 07:25:28 -05:00
|
|
|
from markupsafe import Markup
|
2018-03-22 22:30:13 -05:00
|
|
|
import click
|
|
|
|
import flask
|
2019-04-27 19:13:56 -05:00
|
|
|
from flask import ( # type: ignore
|
2018-06-28 09:04:35 -05:00
|
|
|
__version__ as flask_version,
|
2018-03-31 23:41:23 -05:00
|
|
|
abort,
|
2018-03-30 07:25:28 -05:00
|
|
|
current_app,
|
|
|
|
flash,
|
|
|
|
jsonify,
|
|
|
|
redirect,
|
|
|
|
render_template,
|
|
|
|
request,
|
|
|
|
url_for,
|
|
|
|
)
|
2017-03-28 13:28:23 -05:00
|
|
|
|
2018-03-23 19:21:45 -05:00
|
|
|
try:
|
2018-06-28 09:04:35 -05:00
|
|
|
from . import response, forms, views
|
2018-03-23 19:21:45 -05:00
|
|
|
except ImportError:
|
2018-06-28 09:04:35 -05:00
|
|
|
from bukuserver import response, forms, views
|
2017-03-28 13:28:23 -05:00
|
|
|
|
2018-03-26 07:06:12 -05:00
|
|
|
|
2018-04-19 19:17:16 -05:00
|
|
|
STATISTIC_DATA = None
|
|
|
|
|
2018-12-15 11:30:48 -06:00
|
|
|
def get_bukudb():
|
|
|
|
"""get bukudb instance"""
|
|
|
|
db_file = current_app.config.get('BUKUSERVER_DB_FILE', None)
|
|
|
|
return BukuDb(dbfile=db_file)
|
2018-03-26 07:06:12 -05:00
|
|
|
|
2017-03-28 13:28:23 -05:00
|
|
|
def get_tags():
|
2018-03-23 07:05:31 -05:00
|
|
|
"""get tags."""
|
2018-12-30 20:49:38 -06:00
|
|
|
tags = getattr(flask.g, 'bukudb', get_bukudb()).get_tag_all()
|
2017-03-28 13:28:23 -05:00
|
|
|
result = {
|
|
|
|
'tags': tags[0]
|
|
|
|
}
|
2018-03-23 07:05:31 -05:00
|
|
|
if request.path.startswith('/api/'):
|
|
|
|
res = jsonify(result)
|
|
|
|
else:
|
2018-03-23 19:07:45 -05:00
|
|
|
res = render_template('bukuserver/tags.html', result=result)
|
2018-03-23 07:05:31 -05:00
|
|
|
return res
|
2017-03-28 13:28:23 -05:00
|
|
|
|
|
|
|
|
2019-05-14 06:49:52 -05:00
|
|
|
def handle_network():
|
2018-06-28 09:04:35 -05:00
|
|
|
failed_resp = response.response_template['failure'], status.HTTP_400_BAD_REQUEST
|
|
|
|
url = request.data.get('url', None)
|
|
|
|
if not url:
|
|
|
|
return failed_resp
|
|
|
|
try:
|
|
|
|
res = network_handler(url)
|
2019-05-14 07:07:09 -05:00
|
|
|
keys = ['title', 'description', 'tags', 'recognized mime', 'bad url']
|
|
|
|
res_dict = dict(zip(keys, res))
|
|
|
|
return jsonify(res_dict)
|
2018-06-28 09:04:35 -05:00
|
|
|
except Exception as e:
|
|
|
|
current_app.logger.debug(str(e))
|
|
|
|
return failed_resp
|
|
|
|
|
|
|
|
|
2017-03-28 13:28:23 -05:00
|
|
|
def update_tag(tag):
|
2018-03-26 07:30:40 -05:00
|
|
|
res = None
|
2018-03-31 23:41:23 -05:00
|
|
|
if request.method in ('PUT', 'POST'):
|
|
|
|
new_tags = request.form.getlist('tags')
|
2018-12-15 11:30:48 -06:00
|
|
|
result_flag = getattr(flask.g, 'bukudb', get_bukudb()).replace_tag(tag, new_tags)
|
2018-03-31 23:41:23 -05:00
|
|
|
op_text = 'replace tag [{}] with [{}]'.format(tag, ', '.join(new_tags))
|
|
|
|
if request.method == 'PUT' and result_flag and request.path.startswith('/api/'):
|
2018-12-30 20:49:38 -06:00
|
|
|
res = (jsonify(response.response_template['success']),
|
|
|
|
status.HTTP_200_OK,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-31 23:41:23 -05:00
|
|
|
elif request.method == 'PUT' and request.path.startswith('/api/'):
|
2018-12-30 20:49:38 -06:00
|
|
|
res = (jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-31 23:41:23 -05:00
|
|
|
elif request.method == 'POST' and result_flag:
|
|
|
|
flash(Markup('Success {}'.format(op_text)), 'success')
|
|
|
|
res = redirect(url_for('get_tags-html'))
|
|
|
|
elif request.method == 'POST':
|
|
|
|
flash(Markup('Failed {}'.format(op_text)), 'danger')
|
|
|
|
res = redirect(url_for('get_tags-html'))
|
|
|
|
else:
|
|
|
|
abort(400, description="Unknown Condition")
|
2018-03-26 07:30:40 -05:00
|
|
|
return res
|
2017-03-28 13:28:23 -05:00
|
|
|
|
|
|
|
|
2019-05-14 03:12:44 -05:00
|
|
|
def refresh_bookmark(rec_id: Union[int, None]):
|
|
|
|
if rec_id is not None:
|
|
|
|
result_flag = getattr(flask.g, 'bukudb', get_bukudb()).refreshdb(rec_id, request.form.get('threads', 4))
|
|
|
|
else:
|
|
|
|
result_flag = getattr(flask.g, 'bukudb', get_bukudb()).refreshdb(0, request.form.get('threads', 4))
|
|
|
|
if result_flag:
|
|
|
|
res = (jsonify(response.response_template['success']),
|
|
|
|
status.HTTP_200_OK,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
else:
|
|
|
|
res = (jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-26 07:30:40 -05:00
|
|
|
return res
|
2017-03-28 13:28:23 -05:00
|
|
|
|
|
|
|
|
2019-05-14 05:57:05 -05:00
|
|
|
def get_tiny_url(rec_id):
|
|
|
|
shortened_url = getattr(flask.g, 'bukudb', get_bukudb()).tnyfy_url(rec_id)
|
|
|
|
if shortened_url is not None:
|
|
|
|
result = {'url': shortened_url}
|
|
|
|
res = jsonify(result)
|
|
|
|
else:
|
|
|
|
res = (
|
|
|
|
jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-26 07:30:40 -05:00
|
|
|
return res
|
2017-03-28 13:28:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
def search_bookmarks():
|
2018-03-26 20:08:58 -05:00
|
|
|
arg_obj = request.form if request.method == 'DELETE' else request.args
|
2018-03-27 21:16:54 -05:00
|
|
|
search_bookmarks_form = forms.SearchBookmarksForm(request.args)
|
2018-03-26 20:08:58 -05:00
|
|
|
is_api_request_path = request.path.startswith('/api/')
|
|
|
|
if is_api_request_path:
|
2018-03-27 21:16:54 -05:00
|
|
|
keywords = arg_obj.getlist('keywords')
|
|
|
|
all_keywords = arg_obj.get('all_keywords')
|
|
|
|
deep = arg_obj.get('deep')
|
|
|
|
regex = arg_obj.get('regex')
|
|
|
|
# api request is more strict
|
2018-03-26 20:08:58 -05:00
|
|
|
all_keywords = False if all_keywords is None else all_keywords
|
|
|
|
deep = False if deep is None else deep
|
|
|
|
regex = False if regex is None else regex
|
2018-12-30 20:49:38 -06:00
|
|
|
all_keywords = (
|
|
|
|
all_keywords if isinstance(all_keywords, bool) else
|
|
|
|
all_keywords.lower() == 'true'
|
|
|
|
)
|
|
|
|
deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
|
|
|
|
regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
|
2018-03-27 21:16:54 -05:00
|
|
|
else:
|
|
|
|
keywords = search_bookmarks_form.keywords.data
|
|
|
|
all_keywords = search_bookmarks_form.all_keywords.data
|
|
|
|
deep = search_bookmarks_form.deep.data
|
|
|
|
regex = search_bookmarks_form.regex.data
|
2017-03-28 13:28:23 -05:00
|
|
|
|
2018-03-26 20:12:22 -05:00
|
|
|
result = {'bookmarks': []}
|
2018-12-15 11:30:48 -06:00
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
2018-03-23 07:42:28 -05:00
|
|
|
found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
|
2018-03-26 20:08:58 -05:00
|
|
|
found_bookmarks = [] if found_bookmarks is None else found_bookmarks
|
|
|
|
page = request.args.get(get_page_parameter(), type=int, default=1)
|
|
|
|
per_page = request.args.get(
|
|
|
|
get_per_page_parameter(),
|
|
|
|
type=int,
|
|
|
|
default=int(
|
2018-06-28 09:04:35 -05:00
|
|
|
current_app.config.get('BUKUSERVER_PER_PAGE', views.DEFAULT_PER_PAGE))
|
2018-03-26 20:08:58 -05:00
|
|
|
)
|
2017-03-28 13:28:23 -05:00
|
|
|
|
2018-03-26 07:30:40 -05:00
|
|
|
res = None
|
2017-03-28 13:28:23 -05:00
|
|
|
if request.method == 'GET':
|
2019-05-14 02:44:14 -05:00
|
|
|
if found_bookmarks is not None:
|
2017-03-28 13:28:23 -05:00
|
|
|
for bookmark in found_bookmarks:
|
|
|
|
result_bookmark = {
|
|
|
|
'id': bookmark[0],
|
|
|
|
'url': bookmark[1],
|
|
|
|
'title': bookmark[2],
|
2021-02-27 23:42:45 -06:00
|
|
|
'tags': list(filter(lambda x: x, bookmark[3].split(','))),
|
2017-03-28 13:28:23 -05:00
|
|
|
'description': bookmark[4]
|
|
|
|
}
|
2018-03-26 20:12:22 -05:00
|
|
|
result['bookmarks'].append(result_bookmark)
|
|
|
|
current_app.logger.debug('total bookmarks:{}'.format(len(result['bookmarks'])))
|
2018-03-26 20:08:58 -05:00
|
|
|
if is_api_request_path:
|
2018-03-26 20:12:22 -05:00
|
|
|
res = jsonify(result)
|
2018-03-26 20:08:58 -05:00
|
|
|
else:
|
2018-03-26 20:12:22 -05:00
|
|
|
pagination_total = len(result['bookmarks'])
|
2018-06-28 09:04:35 -05:00
|
|
|
bms = list(views.chunks(result['bookmarks'], per_page))
|
2018-03-28 15:49:02 -05:00
|
|
|
try:
|
|
|
|
result['bookmarks'] = bms[page-1]
|
|
|
|
except IndexError as err:
|
|
|
|
current_app.logger.debug('{}:{}, result bookmarks:{}, page:{}'.format(
|
|
|
|
type(err), err, len(result['bookmarks']), page
|
|
|
|
))
|
2018-03-26 20:08:58 -05:00
|
|
|
pagination = Pagination(
|
|
|
|
page=page, total=pagination_total, per_page=per_page,
|
|
|
|
search=False, record_name='bookmarks', bs_version=3
|
|
|
|
)
|
2018-03-27 21:16:54 -05:00
|
|
|
res = render_template(
|
|
|
|
'bukuserver/bookmarks.html',
|
|
|
|
result=result, pagination=pagination,
|
2018-03-31 03:43:38 -05:00
|
|
|
search_bookmarks_form=search_bookmarks_form,
|
2018-06-28 09:04:35 -05:00
|
|
|
create_bookmarks_form=forms.BookmarkForm(),
|
2018-03-31 03:43:38 -05:00
|
|
|
)
|
2017-03-28 13:28:23 -05:00
|
|
|
elif request.method == 'DELETE':
|
|
|
|
if found_bookmarks is not None:
|
|
|
|
for bookmark in found_bookmarks:
|
2018-03-23 07:42:28 -05:00
|
|
|
result_flag = bukudb.delete_rec(bookmark[0])
|
2017-03-28 13:28:23 -05:00
|
|
|
if result_flag is False:
|
2018-12-30 20:49:38 -06:00
|
|
|
res = (jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-26 07:30:40 -05:00
|
|
|
if res is None:
|
2018-12-30 20:49:38 -06:00
|
|
|
res = (jsonify(response.response_template['success']),
|
|
|
|
status.HTTP_200_OK,
|
|
|
|
{'ContentType': 'application/json'})
|
2018-03-26 07:30:40 -05:00
|
|
|
return res
|
2017-03-28 13:28:23 -05:00
|
|
|
|
|
|
|
|
2021-02-27 23:57:50 -06:00
|
|
|
def get_bool_from_env_var(key: str, default_value: bool)->bool:
|
|
|
|
"""Get bool value from env var."""
|
|
|
|
value = os.getenv(key)
|
|
|
|
if value is None:
|
|
|
|
return default_value
|
|
|
|
if value.lower() in ['true', '1']:
|
|
|
|
return True
|
|
|
|
if value.lower() in ['false', '0']:
|
|
|
|
return False
|
|
|
|
return default_value
|
|
|
|
|
|
|
|
|
2019-05-07 07:14:36 -05:00
|
|
|
def create_app(db_file=None):
|
2018-03-22 22:30:13 -05:00
|
|
|
"""create app."""
|
2018-06-28 09:04:35 -05:00
|
|
|
app = FlaskAPI(__name__)
|
2018-07-18 15:57:15 -05:00
|
|
|
per_page = int(os.getenv('BUKUSERVER_PER_PAGE', str(views.DEFAULT_PER_PAGE)))
|
2018-06-28 09:04:35 -05:00
|
|
|
per_page = per_page if per_page > 0 else views.DEFAULT_PER_PAGE
|
2018-05-02 07:31:45 -05:00
|
|
|
app.config['BUKUSERVER_PER_PAGE'] = per_page
|
2018-06-28 09:04:35 -05:00
|
|
|
url_render_mode = os.getenv('BUKUSERVER_URL_RENDER_MODE', views.DEFAULT_URL_RENDER_MODE)
|
2018-05-08 18:33:49 -05:00
|
|
|
if url_render_mode not in ('full', 'netloc'):
|
2018-06-28 09:04:35 -05:00
|
|
|
url_render_mode = views.DEFAULT_URL_RENDER_MODE
|
2018-05-08 18:33:49 -05:00
|
|
|
app.config['BUKUSERVER_URL_RENDER_MODE'] = url_render_mode
|
2018-05-02 07:34:01 -05:00
|
|
|
app.config['SECRET_KEY'] = os.getenv('BUKUSERVER_SECRET_KEY') or os.urandom(24)
|
2019-05-27 08:23:26 -05:00
|
|
|
app.config['BUKUSERVER_DISABLE_FAVICON'] = \
|
2021-02-27 23:57:50 -06:00
|
|
|
get_bool_from_env_var('BUKUSERVER_DISABLE_FAVICON', True)
|
2019-08-11 16:31:58 -05:00
|
|
|
open_in_new_tab = os.getenv('BUKUSERVER_OPEN_IN_NEW_TAB', 'false')
|
|
|
|
app.config['BUKUSERVER_OPEN_IN_NEW_TAB'] = \
|
|
|
|
False if open_in_new_tab.lower() in ['false', '0'] else bool(open_in_new_tab)
|
2019-05-07 07:14:36 -05:00
|
|
|
app.config['BUKUSERVER_DB_FILE'] = os.getenv('BUKUSERVER_DB_FILE') or db_file
|
2019-12-28 20:37:56 -06:00
|
|
|
reverse_proxy_path = os.getenv('BUKUSERVER_REVERSE_PROXY_PATH')
|
|
|
|
if reverse_proxy_path:
|
|
|
|
if not reverse_proxy_path.startswith('/'):
|
|
|
|
print('Warning: reverse proxy path should include preceding slash')
|
|
|
|
if reverse_proxy_path.endswith('/'):
|
|
|
|
print('Warning: reverse proxy path should not include trailing slash')
|
|
|
|
app.config['REVERSE_PROXY_PATH'] = reverse_proxy_path
|
2020-02-08 06:07:19 -06:00
|
|
|
if ReverseProxyPrefixFix:
|
|
|
|
ReverseProxyPrefixFix(app)
|
|
|
|
else:
|
2020-02-08 18:33:49 -06:00
|
|
|
raise ImportError('Failed to import ReverseProxyPrefixFix')
|
2018-12-15 11:30:48 -06:00
|
|
|
bukudb = BukuDb(dbfile=app.config['BUKUSERVER_DB_FILE'])
|
2018-03-22 22:30:13 -05:00
|
|
|
app.app_context().push()
|
|
|
|
setattr(flask.g, 'bukudb', bukudb)
|
|
|
|
|
|
|
|
@app.shell_context_processor
|
|
|
|
def shell_context():
|
|
|
|
"""Shell context definition."""
|
|
|
|
return {'app': app, 'bukudb': bukudb}
|
|
|
|
|
2018-05-01 04:55:44 -05:00
|
|
|
app.jinja_env.filters['netloc'] = lambda x: urlparse(x).netloc # pylint: disable=no-member
|
|
|
|
|
2018-03-23 07:05:31 -05:00
|
|
|
Bootstrap(app)
|
2018-06-28 09:04:35 -05:00
|
|
|
admin = Admin(
|
2020-01-07 21:07:17 -06:00
|
|
|
app, name='buku server', template_mode='bootstrap3',
|
2018-06-28 09:04:35 -05:00
|
|
|
index_view=views.CustomAdminIndexView(
|
|
|
|
template='bukuserver/home.html', url='/'
|
|
|
|
)
|
|
|
|
)
|
2018-03-22 22:30:13 -05:00
|
|
|
# routing
|
2018-06-28 09:04:35 -05:00
|
|
|
# api
|
2019-05-14 01:05:50 -05:00
|
|
|
tag_api_view = ApiTagView.as_view('tag_api')
|
|
|
|
app.add_url_rule('/api/tags', defaults={'tag': None}, view_func=tag_api_view, methods=['GET'])
|
|
|
|
app.add_url_rule('/api/tags/<tag>', view_func=tag_api_view, methods=['GET', 'PUT'])
|
2019-05-14 02:44:14 -05:00
|
|
|
bookmark_api_view = ApiBookmarkView.as_view('bookmark_api')
|
|
|
|
app.add_url_rule('/api/bookmarks', defaults={'rec_id': None}, view_func=bookmark_api_view, methods=['GET', 'POST', 'DELETE'])
|
|
|
|
app.add_url_rule('/api/bookmarks/<int:rec_id>', view_func=bookmark_api_view, methods=['GET', 'PUT', 'DELETE'])
|
2019-05-14 03:12:44 -05:00
|
|
|
app.add_url_rule('/api/bookmarks/refresh', 'refresh_bookmark', refresh_bookmark, defaults={'rec_id': None}, methods=['POST'])
|
|
|
|
app.add_url_rule('/api/bookmarks/<int:rec_id>/refresh', 'refresh_bookmark', refresh_bookmark, methods=['POST'])
|
2019-05-14 05:57:05 -05:00
|
|
|
app.add_url_rule('/api/bookmarks/<int:rec_id>/tiny', 'get_tiny_url', get_tiny_url, methods=['GET'])
|
2019-05-14 06:49:52 -05:00
|
|
|
app.add_url_rule('/api/network_handle', 'network_handle', handle_network, methods=['POST'])
|
2019-05-16 08:01:10 -05:00
|
|
|
bookmark_range_api_view = ApiBookmarkRangeView.as_view('bookmark_range_api')
|
2018-03-22 22:30:13 -05:00
|
|
|
app.add_url_rule(
|
2019-05-16 08:01:10 -05:00
|
|
|
'/api/bookmarks/<int:starting_id>/<int:ending_id>',
|
|
|
|
view_func=bookmark_range_api_view, methods=['GET', 'PUT', 'DELETE'])
|
2019-05-16 08:40:29 -05:00
|
|
|
bookmark_search_api_view = ApiBookmarkSearchView.as_view('bookmark_search_api')
|
|
|
|
app.add_url_rule('/api/bookmarks/search', view_func=bookmark_search_api_view, methods=['GET', 'DELETE'])
|
2020-07-22 16:53:06 -05:00
|
|
|
bookmarklet_view = BookmarkletView.as_view('bookmarklet')
|
|
|
|
app.add_url_rule('/bookmarklet', view_func=bookmarklet_view, methods=['GET'])
|
2018-06-28 09:04:35 -05:00
|
|
|
# non api
|
|
|
|
admin.add_view(views.BookmarkModelView(
|
|
|
|
bukudb, 'Bookmarks', page_size=per_page, url_render_mode=url_render_mode))
|
|
|
|
admin.add_view(views.TagModelView(
|
|
|
|
bukudb, 'Tags', page_size=per_page))
|
2018-12-13 05:25:52 -06:00
|
|
|
admin.add_view(views.StatisticView(
|
|
|
|
bukudb, 'Statistic', endpoint='statistic'))
|
2018-03-22 22:30:13 -05:00
|
|
|
return app
|
|
|
|
|
|
|
|
|
2019-05-14 01:05:50 -05:00
|
|
|
class ApiTagView(MethodView):
|
|
|
|
|
|
|
|
def get(self, tag: Union[str, None]):
|
|
|
|
bukudb = get_bukudb()
|
|
|
|
if tag is None:
|
|
|
|
tags = bukudb.get_tag_all()
|
|
|
|
result = {'tags': tags[0]}
|
|
|
|
return result
|
|
|
|
tags = bukudb.get_tag_all()
|
|
|
|
if tag not in tags[1]:
|
|
|
|
raise exceptions.NotFound()
|
|
|
|
res = dict(name=tag, usage_count=tags[1][tag])
|
|
|
|
return res
|
|
|
|
|
2019-05-14 02:44:14 -05:00
|
|
|
def put(self, tag: str):
|
2019-05-14 01:05:50 -05:00
|
|
|
bukudb = get_bukudb()
|
|
|
|
res = None
|
|
|
|
try:
|
2019-05-14 02:44:14 -05:00
|
|
|
new_tags = request.data.get('tags') # type: ignore
|
2019-05-14 01:05:50 -05:00
|
|
|
if new_tags:
|
|
|
|
new_tags = new_tags.split(',')
|
|
|
|
else:
|
|
|
|
return response.response_template['failure'], status.HTTP_400_BAD_REQUEST
|
|
|
|
except AttributeError as e:
|
|
|
|
raise exceptions.ParseError(detail=str(e))
|
|
|
|
result_flag = bukudb.replace_tag(tag, new_tags)
|
|
|
|
if result_flag:
|
|
|
|
res = response.response_template['success'], status.HTTP_200_OK
|
|
|
|
else:
|
|
|
|
res = response.response_template['failure'], status.HTTP_400_BAD_REQUEST
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2019-05-14 02:44:14 -05:00
|
|
|
class ApiBookmarkView(MethodView):
|
|
|
|
|
|
|
|
def get(self, rec_id: Union[int, None]):
|
|
|
|
if rec_id is None:
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
all_bookmarks = bukudb.get_rec_all()
|
|
|
|
result = {'bookmarks': []} # type: Dict[str, Any]
|
|
|
|
for bookmark in all_bookmarks:
|
|
|
|
result_bookmark = {
|
|
|
|
'url': bookmark[1],
|
|
|
|
'title': bookmark[2],
|
2021-02-27 23:42:45 -06:00
|
|
|
'tags': list(filter(lambda x: x, bookmark[3].split(','))),
|
2019-05-14 02:44:14 -05:00
|
|
|
'description': bookmark[4]
|
|
|
|
}
|
|
|
|
if not request.path.startswith('/api/'):
|
|
|
|
result_bookmark['id'] = bookmark[0]
|
|
|
|
result['bookmarks'].append(result_bookmark)
|
|
|
|
res = jsonify(result)
|
|
|
|
else:
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
bookmark = bukudb.get_rec_by_id(rec_id)
|
|
|
|
if bookmark is not None:
|
|
|
|
result = {
|
|
|
|
'url': bookmark[1],
|
|
|
|
'title': bookmark[2],
|
2021-02-27 23:42:45 -06:00
|
|
|
'tags': list(filter(lambda x: x, bookmark[3].split(','))),
|
2019-05-14 02:44:14 -05:00
|
|
|
'description': bookmark[4]
|
|
|
|
}
|
|
|
|
res = jsonify(result)
|
|
|
|
else:
|
|
|
|
res = jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
|
|
|
|
{'ContentType': 'application/json'}
|
|
|
|
return res
|
|
|
|
|
|
|
|
def post(self, rec_id: None = None):
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
create_bookmarks_form = forms.BookmarkForm()
|
|
|
|
url_data = create_bookmarks_form.url.data
|
|
|
|
result_flag = bukudb.add_rec(
|
|
|
|
url_data,
|
|
|
|
create_bookmarks_form.title.data,
|
|
|
|
create_bookmarks_form.tags.data,
|
|
|
|
create_bookmarks_form.description.data
|
|
|
|
)
|
|
|
|
if result_flag != -1:
|
|
|
|
res = jsonify(response.response_template['success'])
|
|
|
|
else:
|
|
|
|
res = jsonify(response.response_template['failure'])
|
|
|
|
res.status_code = status.HTTP_400_BAD_REQUEST
|
|
|
|
return res
|
|
|
|
|
|
|
|
def put(self, rec_id: int):
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
result_flag = bukudb.update_rec(
|
|
|
|
rec_id,
|
|
|
|
request.form.get('url'),
|
|
|
|
request.form.get('title'),
|
|
|
|
request.form.get('tags'),
|
|
|
|
request.form.get('description'))
|
|
|
|
if result_flag:
|
|
|
|
res = (jsonify(response.response_template['success']),
|
|
|
|
status.HTTP_200_OK,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
else:
|
|
|
|
res = (jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
return res
|
|
|
|
|
|
|
|
def delete(self, rec_id: Union[int, None]):
|
|
|
|
if rec_id is None:
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
with mock.patch('buku.read_in', return_value='y'):
|
|
|
|
result_flag = bukudb.cleardb()
|
|
|
|
if result_flag:
|
|
|
|
res = jsonify(response.response_template['success'])
|
|
|
|
else:
|
|
|
|
res = jsonify(response.response_template['failure'])
|
|
|
|
res.status_code = status.HTTP_400_BAD_REQUEST
|
|
|
|
else:
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
result_flag = bukudb.delete_rec(rec_id)
|
|
|
|
if result_flag:
|
|
|
|
res = (jsonify(response.response_template['success']),
|
|
|
|
status.HTTP_200_OK,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
else:
|
|
|
|
res = (jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2019-05-16 08:01:10 -05:00
|
|
|
class ApiBookmarkRangeView(MethodView):
|
|
|
|
|
|
|
|
def get(self, starting_id: int, ending_id: int):
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
max_id = bukudb.get_max_id()
|
|
|
|
if starting_id > max_id or ending_id > max_id:
|
|
|
|
return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
|
|
|
|
{'ContentType': 'application/json'}
|
|
|
|
result = {'bookmarks': {}} # type: ignore
|
|
|
|
for i in range(starting_id, ending_id + 1, 1):
|
|
|
|
bookmark = bukudb.get_rec_by_id(i)
|
|
|
|
result['bookmarks'][i] = {
|
|
|
|
'url': bookmark[1],
|
|
|
|
'title': bookmark[2],
|
2021-02-27 23:42:45 -06:00
|
|
|
'tags': list(filter(lambda x: x, bookmark[3].split(','))),
|
2019-05-16 08:01:10 -05:00
|
|
|
'description': bookmark[4]
|
|
|
|
}
|
|
|
|
res = jsonify(result)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def put(self, starting_id: int, ending_id: int):
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
max_id = bukudb.get_max_id()
|
|
|
|
if starting_id > max_id or ending_id > max_id:
|
|
|
|
return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
|
|
|
|
{'ContentType': 'application/json'}
|
|
|
|
for i in range(starting_id, ending_id + 1, 1):
|
|
|
|
updated_bookmark = request.data.get(str(i)) # type: ignore
|
|
|
|
result_flag = bukudb.update_rec(
|
|
|
|
i,
|
|
|
|
updated_bookmark.get('url'),
|
|
|
|
updated_bookmark.get('title'),
|
|
|
|
updated_bookmark.get('tags'),
|
|
|
|
updated_bookmark.get('description'))
|
|
|
|
if result_flag is False:
|
|
|
|
return (
|
|
|
|
jsonify(response.response_template['failure']),
|
|
|
|
status.HTTP_400_BAD_REQUEST,
|
|
|
|
{'ContentType': 'application/json'})
|
|
|
|
res = jsonify(response.response_template['success'])
|
|
|
|
return res
|
|
|
|
|
|
|
|
def delete(self, starting_id: int, ending_id: int):
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
max_id = bukudb.get_max_id()
|
|
|
|
if starting_id > max_id or ending_id > max_id:
|
|
|
|
return jsonify(response.response_template['failure']), status.HTTP_400_BAD_REQUEST, \
|
|
|
|
{'ContentType': 'application/json'}
|
|
|
|
idx = min([starting_id, ending_id])
|
|
|
|
result_flag = bukudb.delete_rec(idx, starting_id, ending_id, is_range=True)
|
|
|
|
if result_flag is False:
|
|
|
|
res = jsonify(response.response_template['failure'])
|
|
|
|
res.status_code = status.HTTP_400_BAD_REQUEST
|
|
|
|
else:
|
|
|
|
res = jsonify(response.response_template['success'])
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2019-05-16 08:40:29 -05:00
|
|
|
class ApiBookmarkSearchView(MethodView):
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
arg_obj = request.args
|
|
|
|
keywords = arg_obj.getlist('keywords')
|
|
|
|
all_keywords = arg_obj.get('all_keywords')
|
|
|
|
deep = arg_obj.get('deep')
|
|
|
|
regex = arg_obj.get('regex')
|
|
|
|
# api request is more strict
|
|
|
|
all_keywords = False if all_keywords is None else all_keywords
|
|
|
|
deep = False if deep is None else deep
|
|
|
|
regex = False if regex is None else regex
|
|
|
|
all_keywords = (
|
|
|
|
all_keywords if isinstance(all_keywords, bool) else
|
|
|
|
all_keywords.lower() == 'true'
|
|
|
|
)
|
|
|
|
deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
|
|
|
|
regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
|
|
|
|
|
|
|
|
result = {'bookmarks': []}
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
|
|
|
|
found_bookmarks = [] if found_bookmarks is None else found_bookmarks
|
|
|
|
res = None
|
|
|
|
if found_bookmarks is not None:
|
|
|
|
for bookmark in found_bookmarks:
|
|
|
|
result_bookmark = {
|
|
|
|
'id': bookmark[0],
|
|
|
|
'url': bookmark[1],
|
|
|
|
'title': bookmark[2],
|
2021-02-27 23:42:45 -06:00
|
|
|
'tags': list(filter(lambda x: x, bookmark[3].split(','))),
|
2019-05-16 08:40:29 -05:00
|
|
|
'description': bookmark[4]
|
|
|
|
}
|
|
|
|
result['bookmarks'].append(result_bookmark)
|
|
|
|
current_app.logger.debug('total bookmarks:{}'.format(len(result['bookmarks'])))
|
|
|
|
res = jsonify(result)
|
|
|
|
return res
|
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
arg_obj = request.form
|
|
|
|
keywords = arg_obj.getlist('keywords')
|
|
|
|
all_keywords = arg_obj.get('all_keywords')
|
|
|
|
deep = arg_obj.get('deep')
|
|
|
|
regex = arg_obj.get('regex')
|
|
|
|
# api request is more strict
|
|
|
|
all_keywords = False if all_keywords is None else all_keywords
|
|
|
|
deep = False if deep is None else deep
|
|
|
|
regex = False if regex is None else regex
|
|
|
|
all_keywords = (
|
|
|
|
all_keywords if isinstance(all_keywords, bool) else
|
|
|
|
all_keywords.lower() == 'true'
|
|
|
|
)
|
|
|
|
deep = deep if isinstance(deep, bool) else deep.lower() == 'true'
|
|
|
|
regex = regex if isinstance(regex, bool) else regex.lower() == 'true'
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
found_bookmarks = bukudb.searchdb(keywords, all_keywords, deep, regex)
|
|
|
|
found_bookmarks = [] if found_bookmarks is None else found_bookmarks
|
|
|
|
res = None
|
|
|
|
if found_bookmarks is not None:
|
|
|
|
for bookmark in found_bookmarks:
|
|
|
|
result_flag = bukudb.delete_rec(bookmark[0])
|
|
|
|
if result_flag is False:
|
|
|
|
res = jsonify(response.response_template['failure'])
|
|
|
|
res.status = status.HTTP_400_BAD_REQUEST
|
|
|
|
if res is None:
|
|
|
|
res = jsonify(response.response_template['success'])
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
2020-07-22 16:53:06 -05:00
|
|
|
class BookmarkletView(MethodView):
|
|
|
|
def get(self):
|
|
|
|
url = request.args.get('url')
|
|
|
|
title = request.args.get('title')
|
|
|
|
description = request.args.get('description')
|
|
|
|
|
|
|
|
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
|
|
|
|
rec_id = bukudb.get_rec_id(url)
|
|
|
|
if rec_id >= 0:
|
2020-07-22 17:24:43 -05:00
|
|
|
return redirect(url_for('bookmark.edit_view', id=rec_id))
|
2020-07-22 17:32:11 -05:00
|
|
|
return redirect(url_for('bookmark.create_view', url=url, title=title, description=description))
|
2020-07-22 16:53:06 -05:00
|
|
|
|
|
|
|
|
2018-06-28 09:04:35 -05:00
|
|
|
class CustomFlaskGroup(FlaskGroup): # pylint: disable=too-few-public-methods
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
super().__init__(**kwargs)
|
|
|
|
self.params[0].help = 'Show the program version'
|
|
|
|
self.params[0].callback = get_custom_version
|
|
|
|
|
|
|
|
|
|
|
|
def get_custom_version(ctx, param, value):
|
|
|
|
if not value or ctx.resilient_parsing:
|
|
|
|
return
|
|
|
|
message = '%(app_name)s %(app_version)s\nFlask %(version)s\nPython %(python_version)s'
|
|
|
|
click.echo(message % {
|
2020-01-07 21:07:17 -06:00
|
|
|
'app_name': 'buku',
|
2018-06-28 09:04:35 -05:00
|
|
|
'app_version': __version__,
|
|
|
|
'version': flask_version,
|
|
|
|
'python_version': sys.version,
|
|
|
|
}, color=ctx.color)
|
|
|
|
ctx.exit()
|
|
|
|
|
|
|
|
|
|
|
|
@click.group(cls=CustomFlaskGroup, create_app=create_app)
|
2018-03-22 22:30:13 -05:00
|
|
|
def cli():
|
2019-02-08 05:37:31 -06:00
|
|
|
"""This is a script for the bukuserver application."""
|
2018-03-22 22:30:13 -05:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
cli()
|