Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bukuserver API] improve tag replacement/deletion #676

Merged
merged 1 commit into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions buku
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ class BukuDb:

return parse_tags(tags)

def replace_tag(self, orig: str, new: List[str] = []) -> bool:
def replace_tag(self, orig: str, new: List[str] = []):
"""Replace original tag by new tags in all records.

Remove original tag if new tag is empty.
Expand All @@ -1971,22 +1971,26 @@ class BukuDb:
new : list
Replacement tags.

Returns
Raises
-------
bool
True on success, False on failure.
ValueError: Invalid input(s) provided.
RuntimeError: Tag deletion failed.

"""

if DELIM in orig:
raise ValueError("Original tag cannot contain delimiter ({}).".format(DELIM))

orig = delim_wrap(orig)
newtags = parse_tags(new) if new else DELIM
newtags = parse_tags([DELIM.join(new)])

if orig == newtags:
print('Tags are same.')
return False
raise ValueError("Original and replacement tags are the same.")

# Remove original tag from DB if new tagset reduces to delimiter
if newtags == DELIM:
return self.delete_tag_at_index(0, orig)
if not self.delete_tag_at_index(0, orig):
raise RuntimeError("Tag deletion failed.")

# Update bookmarks with original tag
query = 'SELECT id, tags FROM bookmarks WHERE tags LIKE ?'
Expand All @@ -2002,8 +2006,6 @@ class BukuDb:

Copy link
Collaborator

@LeXofLeviafan LeXofLeviafan Jun 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print('Index %d updated' % row[0])

…Come to think of it – should this be actually printed out when not in CLI mode?

self.conn.commit()

return True

def get_tagstr_from_taglist(self, id_list, taglist):
"""Get a string of delimiter-separated (and enclosed) string
of tags from a dictionary of tags by matching ids.
Expand Down Expand Up @@ -5974,7 +5976,11 @@ POSITIONAL ARGUMENTS:
if len(args.replace) == 1:
bdb.delete_tag_at_index(0, args.replace[0])
else:
bdb.replace_tag(args.replace[0], args.replace[1:])
try:
bdb.replace_tag(args.replace[0], [' '.join(args.replace[1:])])
except Exception as e:
LOGERR(str(e))
bdb.close_quit(1)

# Export bookmarks
if args.export is not None and not search_opted:
Expand Down
149 changes: 85 additions & 64 deletions bukuserver/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,23 @@
from unittest import mock

from flask.views import MethodView
from flask_api import exceptions, status

import buku
from buku import BukuDb

import flask
from flask import current_app, jsonify, redirect, request, url_for
from flask import current_app, redirect, request, url_for

try:
from . import forms, response
from response import Response
from forms import ApiBookmarkCreateForm, ApiBookmarkEditForm, ApiBookmarkRangeEditForm, ApiTagForm
except ImportError:
from bukuserver import forms, response
from bukuserver.response import Response
from bukuserver.forms import ApiBookmarkCreateForm, ApiBookmarkEditForm, ApiBookmarkRangeEditForm, ApiTagForm


STATISTIC_DATA = None

response_ok = lambda: (jsonify(response.response_template['success']),
status.HTTP_200_OK,
{'ContentType': 'application/json'})
response_bad = lambda: (jsonify(response.response_template['failure']),
status.HTTP_400_BAD_REQUEST,
{'ContentType': 'application/json'})
to_response = lambda ok: response_ok() if ok else response_bad()

def entity(bookmark, id=False):
data = {
Expand Down Expand Up @@ -94,23 +88,35 @@ class ApiTagView(MethodView):
def get(self, tag: T.Optional[str]):
bukudb = get_bukudb()
if tag is None:
return {"tags": search_tag(db=bukudb, limit=5)[0]}
return Response.SUCCESS(data={"tags": search_tag(db=bukudb, limit=5)[0]})
tags = search_tag(db=bukudb, stag=tag)
if tag not in tags[1]:
raise exceptions.NotFound()
return {"name": tag, "usage_count": tags[1][tag]}
return Response.TAG_NOT_FOUND()
return Response.SUCCESS(data={"name": tag, "usage_count": tags[1][tag]})

def put(self, tag: str):
form = ApiTagForm({})
error_response, data = form.process_data(request.get_json())
if error_response is not None:
return error_response(data=data)
bukudb = get_bukudb()
tags = search_tag(db=bukudb, stag=tag)
if tag not in tags[1]:
return Response.TAG_NOT_FOUND()
try:
new_tags = request.data.get('tags') # type: ignore
if new_tags:
new_tags = new_tags.split(',')
else:
return response_bad()
except AttributeError as e:
raise exceptions.ParseError(detail=str(e))
return to_response(bukudb.replace_tag(tag, new_tags))
bukudb.replace_tag(tag, form.tags.data)
return Response.SUCCESS()
except (ValueError, RuntimeError):
return Response.FAILURE()

def delete(self, tag: str):
if buku.DELIM in tag:
return Response.TAG_NOT_VALID()
bukudb = get_bukudb()
tags = search_tag(db=bukudb, stag=tag)
if tag not in tags[1]:
return Response.TAG_NOT_FOUND()
return Response.from_flag(bukudb.delete_tag_at_index(0, tag, chatty=False))


class ApiBookmarkView(MethodView):
Expand All @@ -121,34 +127,40 @@ def get(self, rec_id: T.Union[int, None]):
all_bookmarks = bukudb.get_rec_all()
result = {'bookmarks': [entity(bookmark, id=not request.path.startswith('/api/'))
for bookmark in all_bookmarks]}
res = jsonify(result)
else:
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
bookmark = bukudb.get_rec_by_id(rec_id)
res = (response_bad() if bookmark is None else jsonify(entity(bookmark)))
return res
if bookmark is None:
return Response.BOOKMARK_NOT_FOUND()
result = entity(bookmark)
return Response.SUCCESS(data=result)

def post(self, rec_id: None = None):
form = ApiBookmarkCreateForm({})
error_response, error_data = form.process_data(request.get_json())
if error_response is not None:
return error_response(data=error_data)
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
create_bookmarks_form = forms.ApiBookmarkForm()
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
)
return to_response(result_flag)
form.url.data,
form.title.data,
form.tags_str,
form.description.data)
return Response.from_flag(result_flag)

def put(self, rec_id: int):
form = ApiBookmarkEditForm({})
error_response, error_data = form.process_data(request.get_json())
if error_response is not None:
return error_response(data=error_data)
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'))
return to_response(result_flag)
form.url.data,
form.title.data,
form.tags_str,
form.description.data)
return Response.from_flag(result_flag)

def delete(self, rec_id: T.Union[int, None]):
if rec_id is None:
Expand All @@ -158,45 +170,57 @@ def delete(self, rec_id: T.Union[int, None]):
else:
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
result_flag = bukudb.delete_rec(rec_id)
return to_response(result_flag)
return Response.from_flag(result_flag)


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() or 0
if starting_id > max_id or ending_id > max_id:
return response_bad()
if starting_id > ending_id or ending_id > max_id:
return Response.RANGE_NOT_VALID()
result = {'bookmarks': {i: entity(bukudb.get_rec_by_id(i))
for i in range(starting_id, ending_id + 1)}}
return jsonify(result)
return Response.SUCCESS(data=result)

def put(self, starting_id: int, ending_id: int):
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
max_id = bukudb.get_max_id() or 0
if starting_id > max_id or ending_id > max_id:
return response_bad()
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 response_bad()
return response_ok()
if starting_id > ending_id or ending_id > max_id:
return Response.RANGE_NOT_VALID()
updates = []
errors = {}
for rec_id in range(starting_id, ending_id + 1):
json = request.get_json().get(str(rec_id))
if json is None:
errors[rec_id] = 'Input required.'
continue
form = ApiBookmarkRangeEditForm({})
error_response, error_data = form.process_data(json)
if error_response is not None:
errors[rec_id] = error_data.get('errors')
updates += [{'index': rec_id,
'url': form.url.data,
'title_in': form.title.data,
'tags_in': form.tags_in,
'desc': form.description.data}]

if errors:
return Response.INPUT_NOT_VALID(data={'errors': errors})
for update in updates:
if not bukudb.update_rec(**update):
return Response.FAILURE()
return Response.SUCCESS()

def delete(self, starting_id: int, ending_id: int):
bukudb = getattr(flask.g, 'bukudb', get_bukudb())
max_id = bukudb.get_max_id() or 0
if starting_id > max_id or ending_id > max_id:
return response_bad()
if starting_id > ending_id or ending_id > max_id:
return Response.RANGE_NOT_VALID()
idx = min([starting_id, ending_id])
result_flag = bukudb.delete_rec(idx, starting_id, ending_id, is_range=True)
return to_response(result_flag)
return Response.from_flag(result_flag)


class ApiBookmarkSearchView(MethodView):
Expand All @@ -217,14 +241,11 @@ def get(self):
)
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())
res = None
result = {'bookmarks': [entity(bookmark, id=True)
for bookmark in bukudb.searchdb(keywords, all_keywords, deep, regex)]}
current_app.logger.debug('total bookmarks:{}'.format(len(result['bookmarks'])))
res = jsonify(result)
return res
return Response.SUCCESS(data=result)

def delete(self):
arg_obj = request.form
Expand All @@ -246,8 +267,8 @@ def delete(self):
res = None
for bookmark in bukudb.searchdb(keywords, all_keywords, deep, regex):
if not bukudb.delete_rec(bookmark.id):
res = response_bad()
return res or response_ok()
res = Response.FAILURE()
return res or Response.SUCCESS()


class BookmarkletView(MethodView): # pylint: disable=too-few-public-methods
Expand Down
Loading