From db363367da153c5afe6def9d975b02884d82a9d5 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Mon, 6 Nov 2023 13:05:58 +0000 Subject: [PATCH] Support circles with multiple group chats, remove default group chat --- snikket_web/admin.py | 62 ++++++++ snikket_web/prosodyclient.py | 63 +++++++- snikket_web/templates/admin_circles.html | 2 +- .../templates/admin_create_circle_chat.html | 5 + .../admin_create_circle_group_chat_form.html | 15 ++ snikket_web/templates/admin_edit_circle.html | 50 ++++--- snikket_web/translations/messages.pot | 137 ++++++++++++------ 7 files changed, 269 insertions(+), 65 deletions(-) create mode 100644 snikket_web/templates/admin_create_circle_chat.html create mode 100644 snikket_web/templates/admin_create_circle_group_chat_form.html diff --git a/snikket_web/admin.py b/snikket_web/admin.py index 4668fdd..b6d7cb6 100644 --- a/snikket_web/admin.py +++ b/snikket_web/admin.py @@ -458,6 +458,8 @@ class EditCircleForm(BaseForm): _l("Add user") ) + action_remove_group_chat = wtforms.StringField() + @bp.route("/circle/", methods=["GET", "POST"]) @client.require_admin_session() @@ -530,6 +532,15 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: _("User removed from circle"), "success", ) + elif form.action_remove_group_chat.data: + await client.remove_group_chat( + id_, + form.action_remove_group_chat.data, + ) + await flash( + _("Chat removed from circle"), + "success", + ) return redirect(url_for(".edit_circle", id_=id_)) @@ -537,6 +548,7 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]: "admin_edit_circle.html", target_circle=circle, form=form, + circle_chats=circle.chats, circle_members=circle_members, invite_form=invite_form, ) @@ -583,6 +595,56 @@ async def delete_circle(id_: str) -> typing.Union[str, werkzeug.Response]: ) +class AddCircleChatForm(BaseForm): + name = wtforms.StringField( + _l("Group chat name"), + validators=[wtforms.validators.InputRequired()], + ) + + action_save = wtforms.SubmitField( + _l("Create group chat") + ) + + +@bp.route("/circle//add_chat", methods=["GET", "POST"]) +@client.require_admin_session() +async def edit_circle_add_chat( + id_: str +) -> typing.Union[str, werkzeug.Response]: + async with client.authenticated_session() as session: + try: + circle = await client.get_group_by_id( + id_, + session=session, + ) + except aiohttp.ClientResponseError as exc: + if exc.status == 404: + await flash( + _("No such circle exists"), + "alert", + ) + return redirect(url_for(".circles")) + raise + + form = AddCircleChatForm() + + if form.validate_on_submit(): + if form.action_save.data: + await client.add_group_chat(id_, form.name.data) + await flash( + _("New group chat added to circle"), + "success", + ) + + return redirect(url_for(".edit_circle", id_=id_)) + + return await render_template( + "admin_create_circle_chat.html", + target_circle=circle, + group_chat_form=form, + ) + + _CPU_EPOCH = time.process_time() _MONOTONIC_EPOCH = time.monotonic() diff --git a/snikket_web/prosodyclient.py b/snikket_web/prosodyclient.py index a5ba1a8..80b3a11 100644 --- a/snikket_web/prosodyclient.py +++ b/snikket_web/prosodyclient.py @@ -117,12 +117,30 @@ def from_api_response( ) +@dataclasses.dataclass(frozen=True) +class AdminGroupChatInfo: + id_: str + jid: str + name: str + + @classmethod + def from_api_response( + cls, + data: typing.Mapping[str, typing.Any], + ) -> "AdminGroupChatInfo": + return cls( + id_=data["id"], + jid=data["jid"], + name=data["name"], + ) + + @dataclasses.dataclass(frozen=True) class AdminGroupInfo: id_: str name: str - muc_jid: typing.Optional[str] members: typing.Collection[str] + chats: typing.Collection[AdminGroupChatInfo] @classmethod def from_api_response( @@ -132,8 +150,11 @@ def from_api_response( return cls( id_=data["id"], name=data["name"], - muc_jid=data.get("muc_jid") or None, members=data.get("members", []), + chats=[ + AdminGroupChatInfo.from_api_response(x) + for x in data.get("chats", []) + ] ) @@ -1032,7 +1053,7 @@ async def create_group( self, name: str, *, - create_muc: bool = True, + create_muc: bool = False, session: aiohttp.ClientSession, ) -> AdminGroupInfo: payload = { @@ -1107,6 +1128,27 @@ async def add_group_member( ) as resp: self._raise_error_from_response(resp) + @autosession + async def add_group_chat( + self, + id_: str, + name: str, + *, + session: aiohttp.ClientSession, + ) -> None: + + payload: typing.Dict[str, typing.Any] = { + "name": name, + } + + async with session.post( + self._admin_v1_endpoint( + "/groups/{}/chats".format(id_) + ), + json=payload, + ) as resp: + self._raise_error_from_response(resp) + @autosession async def remove_group_member( self, @@ -1122,6 +1164,21 @@ async def remove_group_member( ) as resp: self._raise_error_from_response(resp) + @autosession + async def remove_group_chat( + self, + group_id: str, + chat_id: str, + *, + session: aiohttp.ClientSession, + ) -> None: + async with session.delete( + self._admin_v1_endpoint( + "/groups/{}/chats/{}".format(group_id, chat_id) + ), + ) as resp: + self._raise_error_from_response(resp) + @autosession async def delete_group( self, diff --git a/snikket_web/templates/admin_circles.html b/snikket_web/templates/admin_circles.html index 535494e..0b43b57 100644 --- a/snikket_web/templates/admin_circles.html +++ b/snikket_web/templates/admin_circles.html @@ -3,7 +3,7 @@ {% block content %}

{% trans %}Manage circles{% endtrans %}

{% trans %}Circles aim to help people who are in the same social circle find each other on your service.{% endtrans %}

-

{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle has a group chat where the circle members are included.{% endtrans %}

+

{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle may have group chats where the circle members are included.{% endtrans %}

{%- if circles -%}
{{- invite_form.csrf_token -}} diff --git a/snikket_web/templates/admin_create_circle_chat.html b/snikket_web/templates/admin_create_circle_chat.html new file mode 100644 index 0000000..84b254c --- /dev/null +++ b/snikket_web/templates/admin_create_circle_chat.html @@ -0,0 +1,5 @@ +{% extends "admin_app.html" %} +{% block content %} +

{{ target_circle.name }}

+{%- include "admin_create_circle_group_chat_form.html" -%} +{% endblock %} diff --git a/snikket_web/templates/admin_create_circle_group_chat_form.html b/snikket_web/templates/admin_create_circle_group_chat_form.html new file mode 100644 index 0000000..bf1a9f2 --- /dev/null +++ b/snikket_web/templates/admin_create_circle_group_chat_form.html @@ -0,0 +1,15 @@ +{% from "library.j2" import form_button, render_errors %} + +{{- group_chat_form.csrf_token -}} +
+

{% trans %}Create new circle group chat{% endtrans %}

+

{% trans %}Add a chat to your circle so its members can hold group discussions.{% endtrans %}

+

{% trans %}Tip:{% endtrans %} {% trans %}This is only for creating group chats that automatically include all members of the circle. If you want a normal group chat, create it in the Snikket app instead.{% endtrans %}

+
+ {{ group_chat_form.name.label }} + {{ group_chat_form.name }} +
+
+ {%- call form_button("add", group_chat_form.action_save, class="primary") %}{% endcall -%} +
+
diff --git a/snikket_web/templates/admin_edit_circle.html b/snikket_web/templates/admin_edit_circle.html index 0659e98..d2d3de6 100644 --- a/snikket_web/templates/admin_edit_circle.html +++ b/snikket_web/templates/admin_edit_circle.html @@ -13,13 +13,6 @@

{% trans circle_name=(target_circle | circle_name) %}Edit circle {{ circle_n
{% trans %}This is your main circle{% endtrans %}

{% trans %}This circle is managed automatically and cannot be removed or renamed.{% endtrans %}

- {%- if target_circle.muc_jid -%} -
-
- {%- call clipboard_button(target_circle.muc_jid, show_label=True) -%} - {%- trans -%}Copy address{%- endtrans -%} - {%- endcall -%} - {%- endif -%}
{%- else -%}
@@ -28,17 +21,6 @@

{% trans %}Circle information{% endtrans %}

{{ form.name.label }} {{ form.name }}
-
- {%- if target_circle.muc_jid -%} - - - {%- call clipboard_button(target_circle.muc_jid, show_label=True) -%} - {%- trans -%}Copy address{%- endtrans -%} - {%- endcall -%} - {%- else -%} -

{% trans %}This circle has no group chat associated.{% endtrans %}

- {%- endif -%} -

{%- call standard_button("back", url_for(".circles"), class="tertiary") -%} {% trans %}Return to circle list{% endtrans %} @@ -52,7 +34,39 @@

{% trans %}Delete circle{% endtrans %}

{%- endif -%} + +

{% trans %}Group chats{% endtrans %}

+

{% trans %}These group chats will be available to all members of the circle.{% endtrans %}

+ +{%- if circle_chats -%} +
+ + + + + +{%- for chat in circle_chats -%} + + + + +{%- endfor -%} + +
{% trans %}Name{% endtrans %}{% trans %}Actions{% endtrans %}
{% call value_or_hint(chat.name) %}{% endcall %} + {%- call custom_form_button("delete", form.action_remove_group_chat.name, chat.id_, class="primary danger", slim=True) -%} + {% trans name=chat.name %}Delete group chat '{{ name }}'{% endtrans %} + {%- endcall -%} +
+{%- else -%} +

{% trans %}This circle currently has no group chats.{% endtrans %}

+{%- endif -%} +{%- call standard_button("add", url_for(".edit_circle_add_chat", id_=target_circle.id_), class="secondary") -%} + {% trans %}Add group chat{% endtrans %} +{%- endcall -%} +

{% trans %}Circle members{% endtrans %}

+

{% trans %}All members of the circle will see each other in their contact list.{% endtrans %}

+ {%- if circle_members -%}
diff --git a/snikket_web/translations/messages.pot b/snikket_web/translations/messages.pot index b154298..f73636d 100644 --- a/snikket_web/translations/messages.pot +++ b/snikket_web/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-10-25 16:15+0100\n" +"POT-Creation-Date: 2023-11-06 13:46+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,13 +18,13 @@ msgstr "" "Generated-By: Babel 2.13.1\n" #: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10 -#: snikket_web/templates/admin_edit_circle.html:59 +#: snikket_web/templates/admin_edit_circle.html:73 #: snikket_web/templates/admin_users.html:8 msgid "Login name" msgstr "" #: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12 -#: snikket_web/templates/admin_edit_circle.html:60 +#: snikket_web/templates/admin_edit_circle.html:74 #: snikket_web/templates/admin_users.html:9 snikket_web/user.py:63 msgid "Display name" msgstr "" @@ -143,6 +143,7 @@ msgstr "" #: snikket_web/admin.py:394 snikket_web/admin.py:442 #: snikket_web/templates/admin_delete_circle.html:10 +#: snikket_web/templates/admin_edit_circle.html:44 msgid "Name" msgstr "" @@ -166,47 +167,63 @@ msgstr "" msgid "Add user" msgstr "" -#: snikket_web/admin.py:474 snikket_web/admin.py:563 +#: snikket_web/admin.py:476 snikket_web/admin.py:575 snikket_web/admin.py:623 msgid "No such circle exists" msgstr "" -#: snikket_web/admin.py:511 +#: snikket_web/admin.py:513 msgid "Circle data updated" msgstr "" -#: snikket_web/admin.py:521 +#: snikket_web/admin.py:523 msgid "User added to circle" msgstr "" -#: snikket_web/admin.py:530 +#: snikket_web/admin.py:532 msgid "User removed from circle" msgstr "" -#: snikket_web/admin.py:547 +#: snikket_web/admin.py:541 +msgid "Chat removed from circle" +msgstr "" + +#: snikket_web/admin.py:559 msgid "Delete circle permanently" msgstr "" -#: snikket_web/admin.py:574 +#: snikket_web/admin.py:586 msgid "Circle deleted" msgstr "" -#: snikket_web/admin.py:640 +#: snikket_web/admin.py:600 +msgid "Group chat name" +msgstr "" + +#: snikket_web/admin.py:605 +msgid "Create group chat" +msgstr "" + +#: snikket_web/admin.py:635 +msgid "New group chat added to circle" +msgstr "" + +#: snikket_web/admin.py:702 msgid "Message contents" msgstr "" -#: snikket_web/admin.py:646 +#: snikket_web/admin.py:708 msgid "Only send to online users" msgstr "" -#: snikket_web/admin.py:650 +#: snikket_web/admin.py:712 msgid "Post to all users" msgstr "" -#: snikket_web/admin.py:654 +#: snikket_web/admin.py:716 msgid "Send preview to yourself" msgstr "" -#: snikket_web/admin.py:676 +#: snikket_web/admin.py:738 msgid "Announcement sent!" msgstr "" @@ -474,8 +491,8 @@ msgstr "" #: snikket_web/templates/admin_circles.html:6 msgid "" "Users who are in the same circle will see each other in their contact " -"list. In addition, each circle has a group chat where the circle members " -"are included." +"list. In addition, each circle may have group chats where the circle " +"members are included." msgstr "" #: snikket_web/templates/admin_circles.html:13 @@ -487,7 +504,8 @@ msgid "Members" msgstr "" #: snikket_web/templates/admin_circles.html:15 -#: snikket_web/templates/admin_edit_circle.html:61 +#: snikket_web/templates/admin_edit_circle.html:45 +#: snikket_web/templates/admin_edit_circle.html:75 #: snikket_web/templates/admin_invites.html:24 #: snikket_web/templates/admin_users.html:10 msgid "Actions" @@ -523,6 +541,25 @@ msgstr "" msgid "New circle" msgstr "" +#: snikket_web/templates/admin_create_circle_group_chat_form.html:5 +msgid "Create new circle group chat" +msgstr "" + +#: snikket_web/templates/admin_create_circle_group_chat_form.html:6 +msgid "Add a chat to your circle so its members can hold group discussions." +msgstr "" + +#: snikket_web/templates/admin_create_circle_group_chat_form.html:7 +msgid "Tip:" +msgstr "" + +#: snikket_web/templates/admin_create_circle_group_chat_form.html:7 +msgid "" +"This is only for creating group chats that automatically include " +"all members of the circle. If you want a normal group chat, " +"create it in the Snikket app instead." +msgstr "" + #: snikket_web/templates/admin_create_invite.html:3 msgid "Create invitation" msgstr "" @@ -565,8 +602,8 @@ msgid "Delete circle %(circle_name)s" msgstr "" #: snikket_web/templates/admin_delete_circle.html:6 -#: snikket_web/templates/admin_edit_circle.html:48 -#: snikket_web/templates/admin_edit_circle.html:51 +#: snikket_web/templates/admin_edit_circle.html:30 +#: snikket_web/templates/admin_edit_circle.html:33 msgid "Delete circle" msgstr "" @@ -625,69 +662,78 @@ msgstr "" msgid "This circle is managed automatically and cannot be removed or renamed." msgstr "" -#: snikket_web/templates/admin_edit_circle.html:17 -#: snikket_web/templates/admin_edit_circle.html:33 -msgid "Group chat address" +#: snikket_web/templates/admin_edit_circle.html:19 +msgid "Circle information" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:20 -#: snikket_web/templates/admin_edit_circle.html:36 -#: snikket_web/templates/invite_success.html:15 -#: snikket_web/templates/user_home.html:21 -msgid "Copy address" +#: snikket_web/templates/admin_edit_circle.html:26 +msgid "Return to circle list" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:26 -msgid "Circle information" +#: snikket_web/templates/admin_edit_circle.html:31 +msgid "Deleting a circle does not delete any users in the circle." +msgstr "" + +#: snikket_web/templates/admin_edit_circle.html:38 +msgid "Group chats" msgstr "" #: snikket_web/templates/admin_edit_circle.html:39 -msgid "This circle has no group chat associated." +msgid "These group chats will be available to all members of the circle." msgstr "" -#: snikket_web/templates/admin_edit_circle.html:44 -msgid "Return to circle list" +#: snikket_web/templates/admin_edit_circle.html:53 +#, python-format +msgid "Delete group chat '%(name)s'" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:49 -msgid "Deleting a circle does not delete any users in the circle." +#: snikket_web/templates/admin_edit_circle.html:61 +msgid "This circle currently has no group chats." +msgstr "" + +#: snikket_web/templates/admin_edit_circle.html:64 +msgid "Add group chat" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:55 +#: snikket_web/templates/admin_edit_circle.html:67 msgid "Circle members" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:71 +#: snikket_web/templates/admin_edit_circle.html:68 +msgid "All members of the circle will see each other in their contact list." +msgstr "" + +#: snikket_web/templates/admin_edit_circle.html:85 msgid "The user has been deleted from the server." msgstr "" -#: snikket_web/templates/admin_edit_circle.html:71 +#: snikket_web/templates/admin_edit_circle.html:85 #: snikket_web/templates/library.j2:108 msgid "deleted" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:77 +#: snikket_web/templates/admin_edit_circle.html:91 #, python-format msgid "Remove user %(username)s from circle" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:85 +#: snikket_web/templates/admin_edit_circle.html:99 msgid "This circle currently has no members." msgstr "" -#: snikket_web/templates/admin_edit_circle.html:87 +#: snikket_web/templates/admin_edit_circle.html:101 msgid "Invite more members" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:90 +#: snikket_web/templates/admin_edit_circle.html:104 msgid "Add existing user" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:101 +#: snikket_web/templates/admin_edit_circle.html:115 msgid "All users added" msgstr "" -#: snikket_web/templates/admin_edit_circle.html:102 +#: snikket_web/templates/admin_edit_circle.html:116 msgid "All users on this service are already in this circle." msgstr "" @@ -1238,6 +1284,11 @@ msgstr "" msgid "Your address" msgstr "" +#: snikket_web/templates/invite_success.html:15 +#: snikket_web/templates/user_home.html:21 +msgid "Copy address" +msgstr "" + #: snikket_web/templates/invite_success.html:17 msgid "" "You can now set up your legacy XMPP client with the above address and the"