From e255c2f353ad16d3ed0460dabe84a11b119902da Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Tue, 23 Aug 2022 13:34:30 +0200 Subject: make it possible to change repo's name and URL using web UI --- src/hydrilla/proxy/state_impl/repos.py | 72 +++++++++---- src/hydrilla/proxy/web_ui/repos.py | 68 ++++++------ .../proxy/web_ui/templates/repos.html.jinja | 2 +- .../web_ui/templates/repos__show_single.html.jinja | 116 ++++++++++++++------- 4 files changed, 164 insertions(+), 94 deletions(-) (limited to 'src/hydrilla') diff --git a/src/hydrilla/proxy/state_impl/repos.py b/src/hydrilla/proxy/state_impl/repos.py index ae3b70c..2670ae9 100644 --- a/src/hydrilla/proxy/state_impl/repos.py +++ b/src/hydrilla/proxy/state_impl/repos.py @@ -54,6 +54,28 @@ from . import base from . import _operations +repo_name_regex = re.compile(r''' +^ +(?: + []a-zA-Z0-9()<>^&$.!,?@#|;:%"'*{}[/_=+-]+ # allowed non-whitespace characters + + (?: # optional additional words separated by single spaces + [ ] + []a-zA-Z0-9()<>^&$.!,?@#|;:%"'*{}[/_=+-]+ + )* +) +$ +''', re.VERBOSE) + +def sanitize_repo_name(name: str) -> str: + name = name.strip() + + if repo_name_regex.match(name) is None: + raise st.RepoNameInvalid() + + return name + + def sanitize_repo_url(url: str) -> str: try: parsed = urlparse(url) @@ -220,20 +242,39 @@ class ConcreteRepoRef(st.RepoRef): url: t.Optional[str] = None ) -> None: if name is not None: - raise NotImplementedError() + if name.isspace(): + raise st.RepoNameInvalid() - if url is None: - return + name = sanitize_repo_name(name) - url = sanitize_repo_url(url) + if url is not None: + if url.isspace(): + raise st.RepoUrlInvalid() + + url = sanitize_repo_url(url) + + if name is None and url is None: + return with self.state.cursor(transaction=True) as cursor: ensure_repo_not_deleted(cursor, self.id) - cursor.execute( - 'UPDATE repos SET url = ? WHERE repo_id = ?;', - (url, self.id) - ) + if url is not None: + cursor.execute( + 'UPDATE repos SET url = ? WHERE repo_id = ?;', + (url, self.id) + ) + + if name is not None: + try: + cursor.execute( + 'UPDATE repos SET name = ? WHERE repo_id = ?;', + (name, self.id) + ) + except sqlite3.IntegrityError: + raise st.RepoNameTaken() + + self.state.recompute_dependencies() def refresh(self) -> st.RepoIterationRef: with self.state.cursor(transaction=True) as cursor: @@ -256,7 +297,7 @@ class ConcreteRepoRef(st.RepoRef): RemoteFileResolver(repo_url) ) - self.state.recompute_dependencies() + self.state.rebuild_structures() cursor.execute( ''' @@ -297,19 +338,6 @@ class ConcreteRepoRef(st.RepoRef): return make_repo_display_info(self, *row) -repo_name_regex = re.compile(r''' -^ -(?: - []a-zA-Z0-9()<>^&$.!,?@#|;:%"'*{}[/_=+-]+ # allowed non-whitespace characters - - (?: # optional additional words separated by single spaces - [ ] - []a-zA-Z0-9()<>^&$.!,?@#|;:%"'*{}[/_=+-]+ - )* -) -$ -''', re.VERBOSE) - @dc.dataclass(frozen=True) class ConcreteRepoStore(st.RepoStore): state: base.HaketiloStateWithFields diff --git a/src/hydrilla/proxy/web_ui/repos.py b/src/hydrilla/proxy/web_ui/repos.py index 166cf53..629e399 100644 --- a/src/hydrilla/proxy/web_ui/repos.py +++ b/src/hydrilla/proxy/web_ui/repos.py @@ -43,7 +43,7 @@ from . import _app bp = flask.Blueprint('repos', __package__) @bp.route('/repos/add', methods=['GET']) -def add_repo_get(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: +def add_repo(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: html = flask.render_template('repos__add.html.jinja', **errors) return flask.make_response(html, 200) @@ -51,7 +51,7 @@ def add_repo_get(errors: t.Mapping[str, bool] = {}) -> werkzeug.Response: def add_repo_post() -> werkzeug.Response: form_data = flask.request.form if 'name' not in form_data or 'url' not in form_data: - return add_repo_get() + return add_repo() try: new_repo_ref = _app.get_haketilo_state().repo_store().add( @@ -59,11 +59,11 @@ def add_repo_post() -> werkzeug.Response: url = form_data['url'] ) except st.RepoNameInvalid: - return add_repo_get({'repo_name_invalid': True}) + return add_repo({'repo_name_invalid': True}) except st.RepoNameTaken: - return add_repo_get({'repo_name_taken': True}) + return add_repo({'repo_name_taken': True}) except st.RepoUrlInvalid: - return add_repo_get({'repo_url_invalid': True}) + return add_repo({'repo_url_invalid': True}) return flask.redirect(flask.url_for('.show_repo', repo_id=new_repo_ref.id)) @@ -81,56 +81,52 @@ def repos() -> werkzeug.Response: return flask.make_response(html, 200) @bp.route('/repos/view/') -def show_repo(repo_id: str) -> werkzeug.Response: +def show_repo(repo_id: str, errors: t.Mapping[str, bool] = {}) \ + -> werkzeug.Response: try: store = _app.get_haketilo_state().repo_store() display_info = store.get(repo_id).get_display_info() html = flask.render_template( 'repos__show_single.html.jinja', - display_info = display_info + display_info = display_info, + **errors ) return flask.make_response(html, 200) except st.MissingItemError: flask.abort(404) -def sanitize_altered_repo_id(repo_id: str) -> str: +@bp.route('/repos/view/', methods=['POST']) +def alter_repo(repo_id: str) -> werkzeug.Response: repo_id = str(int(repo_id)) if repo_id == '1': # Protect local semi-repo. flask.abort(403) - return repo_id + repo_ref = _app.get_haketilo_state().repo_store().get(repo_id) -@bp.route('/repos/update_url/', methods=['POST']) -def update_repo_url(repo_id: str) -> werkzeug.Response: - repo_id = sanitize_altered_repo_id(repo_id) - - try: - repo_ref = _app.get_haketilo_state().repo_store().get(repo_id) - repo_ref.update() - except st.MissingItemError: - flask.abort(404) - - return flask.redirect(flask.url_for('.show_repo', repo_id=repo_id)) - -@bp.route('/repos/remove/', methods=['POST']) -def remove_repo(repo_id: str): - repo_id = sanitize_altered_repo_id(repo_id) - - try: - _app.get_haketilo_state().repo_store().get(repo_id).remove() - except st.MissingItemError: - flask.abort(404) - - return flask.redirect(flask.url_for('.repos')) - -@bp.route('/repos/refresh/', methods=['POST']) -def refresh_repo(repo_id: str): - repo_id = sanitize_altered_repo_id(repo_id) + form_data = flask.request.form + action = form_data['action'] try: - _app.get_haketilo_state().repo_store().get(repo_id).refresh() + if action == 'remove_repo': + repo_ref.remove() + return flask.redirect(flask.url_for('.repos')) + elif action == 'refresh_repo': + repo_ref.refresh() + elif action == 'update_repo_data': + repo_ref.update( + url = form_data.get('url'), + name = form_data.get('name') + ) + else: + raise ValueError() + except st.RepoNameInvalid: + return show_repo(repo_id, {'repo_name_invalid': True}) + except st.RepoNameTaken: + return show_repo(repo_id, {'repo_name_taken': True}) + except st.RepoUrlInvalid: + return show_repo(repo_id, {'repo_url_invalid': True}) except st.MissingItemError: flask.abort(404) diff --git a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja index 3e61a83..54465a1 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos.html.jinja @@ -29,7 +29,7 @@ in a proprietary work, I am not going to enforce this in court. {% block main %}

{{ _('web_ui.repos.heading') }}

diff --git a/src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja b/src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja index 96100ce..5783a20 100644 --- a/src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja +++ b/src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja @@ -27,46 +27,93 @@ in a proprietary work, I am not going to enforce this in court. {% include 'include/checkbox_tricks_style.css.jinja' %} {% endblock %} {% block main %} - {% set repo_id = display_info.ref.id -%} -

- {% if display_info.is_local_semirepo -%} - {{ _('web_ui.repos.local_packages_semirepo') }} - {% else -%} + {%- set repo_id = display_info.ref.id %} + {%- if display_info.is_local_semirepo %} +

{{ _('web_ui.repos.local_packages_semirepo') }}

+ {% else -%} +

{{ _('web_ui.repos.single.heading.name_{}').format(display_info.name) }} - {% endif -%} -

- {% if display_info.deleted and not display_info.is_local_semirepo -%} -
- {{ _('web_ui.repos.single.repo_is_deleted') }} -
- {% elif not display_info.deleted -%} - + + {%- if repo_name_invalid is defined or repo_name_taken is defined %} + {%- set checked_attr = '' %} + {%- else %} + {%- set checked_attr = 'checked=""' %} + {%- endif %} + {%- if not display_info.deleted %} +
-
- {{ display_info.url }} -
-
- -
+
- {% set action_url = url_for('.update_repo_url', repo_id=repo_id) -%} -
- + + + {%- if repo_name_invalid is defined %} + + {%- endif %} + {%- if repo_name_taken is defined %} + + {%- endif %}
- +
-
+ {%- endif %}{# not display_info.deleted #} + {%- endif %}{# display_info.is_local_semirepo (else) #} + {% if display_info.deleted and not display_info.is_local_semirepo -%} +
+ {{ _('web_ui.repos.single.repo_is_deleted') }} +
+ {%- elif not display_info.deleted %} + {%- if repo_url_invalid is defined %} + {%- set checked_attr = '' %} + {%- else %} + {%- set checked_attr = 'checked=""' %} + {%- endif %} + +
+
+ {{ display_info.url }} +
+
+ +
+
+
+ + {%- if repo_url_invalid is defined %} + + {%- endif %} +
+ +
+
+ + +
+
{% if display_info.last_refreshed is none -%} {{ _('web_ui.repos.single.repo_never_refreshed') }} @@ -76,15 +123,14 @@ in a proprietary work, I am not going to enforce this in court. .format(display_info.last_refreshed.strftime('%F %H:%M')) }} {% endif -%} - {% set action_url = url_for('.refresh_repo', repo_id=repo_id) -%} -
+
- {% endif -%} + {% endif -%}{# not display_info.deleted (elif) #}
{{ _('web_ui.repos.item_count_{mappings}_{resources}') @@ -94,9 +140,9 @@ in a proprietary work, I am not going to enforce this in court. ) }}
- {% if not display_info.is_local_semirepo -%} - {% set action_url = url_for('.remove_repo', repo_id=repo_id) -%} -
+ {% if not display_info.is_local_semirepo and not display_info.deleted -%} + + -- cgit v1.2.3