diff options
-rw-r--r-- | src/hydrilla/proxy/state_impl/repos.py | 72 | ||||
-rw-r--r-- | src/hydrilla/proxy/web_ui/repos.py | 68 | ||||
-rw-r--r-- | src/hydrilla/proxy/web_ui/templates/repos.html.jinja | 2 | ||||
-rw-r--r-- | src/hydrilla/proxy/web_ui/templates/repos__show_single.html.jinja | 116 |
4 files changed, 164 insertions, 94 deletions
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/<string:repo_id>') -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/<string:repo_id>', 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/<string:repo_id>', 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/<string:repo_id>', 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/<string:repo_id>', 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 %} <h3>{{ _('web_ui.repos.heading') }}</h3> <div> - <a href="{{ url_for('.add_repo_get') }}" class="green-button"> + <a href="{{ url_for('.add_repo') }}" class="green-button"> {{ _('web_ui.repos.add_repo_button') }} </a> </div> 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 -%} - <h3> - {% 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 %} + <h3>{{ _('web_ui.repos.local_packages_semirepo') }}</h3> + {% else -%} + <h3> {{ _('web_ui.repos.single.heading.name_{}').format(display_info.name) }} - {% endif -%} - </h3> - {% if display_info.deleted and not display_info.is_local_semirepo -%} - <div> - {{ _('web_ui.repos.single.repo_is_deleted') }} - </div> - {% elif not display_info.deleted -%} - <input id="show_url_edit_form" type="checkbox" class="chbx-tricks-show-hide" - checked=""> + </h3> + {%- 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 %} + <input id="hide_name_edit_form" type="checkbox" + class="chbx-tricks-show-hide" {{ checked_attr }}> <div> - <div> - {{ display_info.url }} - </div> - <div> - <label for="show_url_edit_form" class="green-button"> - {{ _('web_ui.repos.single.update_url_button') }} - </label> - </div> + <label for="hide_name_edit_form" class="green-button"> + {{ _('web_ui.repos.single.update_name_button') }} + </label> </div> - {% set action_url = url_for('.update_repo_url', repo_id=repo_id) -%} - <form method="POST" action="{{ action_url }}"> - <input type="hidden" name="action" value="update_url"> + <form method="POST"> + <input type="hidden" name="action" value="update_repo_data"> + {%- if repo_name_invalid is defined %} + <aside class="error-note"> + {{ _('web_ui.repos.add.repo_name_invalid') }} + </aside> + {%- endif %} + {%- if repo_name_taken is defined %} + <aside class="error-note"> + {{ _('web_ui.repos.add.repo_name_taken') }} + </aside> + {%- endif %} <div> - <input name="url" value="{{ display_info.url }}"> + <input name="name" value="{{ display_info.name }}" required=""> </div> <div> <button class="green-button"> - {{ _('web_ui.repos.single.commit_update_url_button') }} + {{ _('web_ui.repos.single.commit_update_name_button') }} </button> - <label for="show_url_edit_form" class="green-button"> - {{ _('web_ui.repos.single.abort_update_url_button') }} + <label for="hide_url_edit_form" class="green-button"> + {{ _('web_ui.repos.single.abort_update_name_button') }} </label> </div> </form> + {%- endif %}{# not display_info.deleted #} + {%- endif %}{# display_info.is_local_semirepo (else) #} + {% if display_info.deleted and not display_info.is_local_semirepo -%} + <div> + {{ _('web_ui.repos.single.repo_is_deleted') }} + </div> + {%- elif not display_info.deleted %} + {%- if repo_url_invalid is defined %} + {%- set checked_attr = '' %} + {%- else %} + {%- set checked_attr = 'checked=""' %} + {%- endif %} + <input id="hide_url_edit_form" type="checkbox" class="chbx-tricks-show-hide" + {{ checked_attr }}> + <div> + <div> + {{ display_info.url }} + </div> + <div> + <label for="hide_url_edit_form" class="green-button"> + {{ _('web_ui.repos.single.update_url_button') }} + </label> + </div> + </div> + <form method="POST"> + <input type="hidden" name="action" value="update_repo_data"> + {%- if repo_url_invalid is defined %} + <aside class="error-note"> + {{ _('web_ui.repos.add.repo_url_invalid') }} + </aside> + {%- endif %} + <div> + <input name="url" value="{{ display_info.url }}" required=""> + </div> + <div> + <button class="green-button"> + {{ _('web_ui.repos.single.commit_update_url_button') }} + </button> + <label for="hide_url_edit_form" class="green-button"> + {{ _('web_ui.repos.single.abort_update_url_button') }} + </label> + </div> + </form> <div> {% 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) -%} - <form method="POST" action="{{ action_url }}"> + <form method="POST"> <input type="hidden" name="action" value="refresh_repo"> <button class="green-button"> {{ _('web_ui.repos.single.refresh_now_button') }} </button> </form> </div> - {% endif -%} + {% endif -%}{# not display_info.deleted (elif) #} <div> {{ _('web_ui.repos.item_count_{mappings}_{resources}') @@ -94,9 +140,9 @@ in a proprietary work, I am not going to enforce this in court. ) }} </div> - {% if not display_info.is_local_semirepo -%} - {% set action_url = url_for('.remove_repo', repo_id=repo_id) -%} - <form method="POST" action="{{ action_url }}"> + {% if not display_info.is_local_semirepo and not display_info.deleted -%} + <form method="POST"> + <input type="hidden" name="action" value="remove_repo"> <button class="green-button"> {{ _('web_ui.repos.single.remove_button') }} </button> |