Skip to content

Commit 3bc30fd

Browse files
committed
Fix chat links
1 parent fac1b8d commit 3bc30fd

22 files changed

Lines changed: 865 additions & 86 deletions

astra_app/core/chatnicknames.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ def build_chat_nickname_link(value: str, *, scheme_override: str | None = None)
201201
display = f"{display}@{server}"
202202
return ChatNicknameLink(href=href, title=title, display=display)
203203

204+
if not _IRC_NICK_RE.match(nick):
205+
return None
206+
204207
href = f"irc://{server}/{nick},isnick"
205208
title = f"IRC on {server}"
206209
display = nick
@@ -209,37 +212,47 @@ def build_chat_nickname_link(value: str, *, scheme_override: str | None = None)
209212
return ChatNicknameLink(href=href, title=title, display=display)
210213

211214
if scheme == ChatScheme.matrix:
215+
localpart = nick.lstrip("@")
216+
if not _MATRIX_LOCALPART_RE.match(localpart):
217+
return None
218+
212219
args = settings.CHAT_MATRIX_TO_ARGS
213220
args = args if isinstance(args, str) and args else ""
214221

215-
href = f"https://matrix.to/#/@{nick}:{server}"
222+
href = f"https://matrix.to/#/@{localpart}:{server}"
216223
if args:
217224
href = f"{href}?{args}"
218225

219226
title = f"Matrix on {server}"
220227

221-
display = f"@{nick}"
228+
display = f"@{localpart}"
222229
if not default_server or server != default_server:
223230
display = f"{display}:{server}"
224231

225232
return ChatNicknameLink(href=href, title=title, display=display, external=True)
226233

227234
if scheme == ChatScheme.mattermost:
235+
if not _SERVER_RE.match(server) or not _MATTERMOST_USERNAME_RE.match(nick):
236+
return None
237+
228238
if not team:
229239
# For default server we can fall back to the configured default team.
230240
if default_server and server == default_server and default_team:
231241
team = default_team
232242
else:
233243
return None
234244

245+
if not _MATTERMOST_TEAM_RE.match(team):
246+
return None
247+
235248
href = f"https://{server}/{team}/messages/@{nick}"
236249
title = f"Mattermost on {server} ({team})"
237250

238251
display = f"@{nick}"
239252
if (default_server and server != default_server) or (default_team and team != default_team) or (not default_server) or (not default_team):
240253
display = f"@{nick}:{server}:{team}"
241254

242-
return ChatNicknameLink(href=href, title=title, display=display)
255+
return ChatNicknameLink(href=href, title=title, display=display, external=True)
243256

244257
return None
245258

@@ -405,6 +418,9 @@ def build_chat_channel_link(value: str, *, scheme_override: str | None = None) -
405418
if not channel or not server:
406419
return None
407420

421+
if not _SERVER_RE.match(server) or not _MATTERMOST_CHANNEL_RE.match(channel):
422+
return None
423+
408424
default_server = _get_default_server(ChatScheme.mattermost) or ""
409425
default_team = _get_default_team() or ""
410426

@@ -414,14 +430,17 @@ def build_chat_channel_link(value: str, *, scheme_override: str | None = None) -
414430
else:
415431
return None
416432

433+
if not _MATTERMOST_TEAM_RE.match(team):
434+
return None
435+
417436
href = f"https://{server}/{team}/channels/{channel}"
418437
title = f"Mattermost channel on {server} ({team})"
419438

420439
display = f"~{channel}"
421440
if (default_server and server != default_server) or (default_team and team != default_team) or (not default_server) or (not default_team):
422441
display = f"~{channel}:{server}:{team}"
423442

424-
return ChatChannelLink(href=href, title=title, display=display)
443+
return ChatChannelLink(href=href, title=title, display=display, external=True)
425444

426445
if not channel or not server:
427446
return None

astra_app/core/templates/core/group_detail.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
data-group-detail-leaders-api-url="{% url 'api-group-detail-leaders' group.cn %}"
1919
data-group-detail-members-api-url="{% url 'api-group-detail-members' group.cn %}"
2020
data-group-detail-action-url="{% url 'api-group-action' group.cn %}"
21+
data-group-detail-chat-irc-default-server="{{ chat_irc_default_server }}"
22+
data-group-detail-chat-matrix-default-server="{{ chat_matrix_default_server }}"
23+
data-group-detail-chat-mattermost-default-server="{{ chat_mattermost_default_server }}"
24+
data-group-detail-chat-mattermost-default-team="{{ chat_mattermost_default_team }}"
25+
data-group-detail-chat-matrix-to-args="{{ chat_matrix_to_args }}"
2126
data-group-detail-current-username="{{ request.user.get_username }}"
2227
data-group-detail-url-template="{{ group_detail_url_template }}"
2328
data-group-detail-edit-url-template="{{ group_edit_url_template }}"

astra_app/core/templates/core/user_profile.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
<div
1616
data-user-profile-root
1717
data-user-profile-api-url="{% url 'api-user-profile-detail' profile_username %}"
18+
data-user-profile-chat-irc-default-server="{{ chat_irc_default_server }}"
19+
data-user-profile-chat-matrix-default-server="{{ chat_matrix_default_server }}"
20+
data-user-profile-chat-mattermost-default-server="{{ chat_mattermost_default_server }}"
21+
data-user-profile-chat-mattermost-default-team="{{ chat_mattermost_default_team }}"
22+
data-user-profile-chat-matrix-to-args="{{ chat_matrix_to_args }}"
1823
data-user-profile-settings-profile-url="{{ settings_profile_url }}"
1924
data-user-profile-settings-country-code-url="{{ settings_country_code_url }}"
2025
data-user-profile-settings-emails-url="{{ settings_emails_url }}"

astra_app/core/templatetags/core_chatnickname.py

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import json
2+
from pathlib import Path
3+
4+
from django.test import SimpleTestCase, override_settings
5+
6+
from core.chatnicknames import build_chat_channel_link, build_chat_nickname_link
7+
8+
9+
def _parity_fixture_path() -> Path:
10+
return Path(__file__).resolve().parents[3] / "frontend" / "src" / "shared" / "__tests__" / "fixtures" / "chatLinkParityCases.json"
11+
12+
13+
def _load_parity_fixture() -> dict[str, object]:
14+
return json.loads(_parity_fixture_path().read_text(encoding="utf-8"))
15+
16+
17+
@override_settings(
18+
CHAT_NETWORKS={
19+
"irc": {"default_server": "irc.libera.chat"},
20+
"matrix": {"default_server": "matrix.org"},
21+
"mattermost": {
22+
"default_server": "chat.almalinux.org",
23+
"default_team": "almalinux",
24+
},
25+
},
26+
CHAT_MATRIX_TO_ARGS="web-instance[element.io]=app.element.io",
27+
)
28+
class ChatLinkParityTests(SimpleTestCase):
29+
def test_python_builders_match_the_shared_parity_fixture(self) -> None:
30+
fixture = _load_parity_fixture()
31+
cases = fixture["cases"]
32+
33+
self.assertIsInstance(cases, list)
34+
35+
for case in cases:
36+
self.assertIsInstance(case, dict)
37+
raw = case["raw"]
38+
kind = case["kind"]
39+
expected = case["expected"]
40+
41+
self.assertIsInstance(raw, str)
42+
self.assertIn(kind, {"nickname", "channel"})
43+
44+
link = (
45+
build_chat_nickname_link(raw)
46+
if kind == "nickname"
47+
else build_chat_channel_link(raw)
48+
)
49+
50+
with self.subTest(case=case["id"]):
51+
if expected is None:
52+
self.assertIsNone(link)
53+
continue
54+
55+
self.assertIsNotNone(link)
56+
self.assertEqual(
57+
{
58+
"href": link.href,
59+
"title": link.title,
60+
"display": link.display,
61+
"external": link.external,
62+
},
63+
expected,
64+
)

astra_app/core/tests/test_group_detail_renders_chat_channels.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ def test_group_detail_renders_chat_channels_links(self) -> None:
4242
self.assertEqual(resp.status_code, 200)
4343
self.assertContains(resp, "data-group-detail-root")
4444
self.assertContains(resp, reverse("api-group-detail-info", args=["fas1"]))
45+
self.assertContains(resp, 'data-group-detail-chat-irc-default-server="irc.libera.chat"')
46+
self.assertContains(resp, 'data-group-detail-chat-matrix-default-server="matrix.org"')
47+
self.assertContains(resp, 'data-group-detail-chat-mattermost-default-server="chat.almalinux.org"')
48+
self.assertContains(resp, 'data-group-detail-chat-mattermost-default-team="almalinux"')
49+
self.assertContains(resp, 'data-group-detail-chat-matrix-to-args="web-instance[element.io]=app.element.io"')
4550
self.assertEqual(info_resp.status_code, 200)
4651
self.assertEqual(
4752
info_resp.json()["group"]["fas_irc_channels"],

astra_app/core/tests/test_user_profile_vue.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ def test_user_profile_page_renders_vue_controller_shell_contract(self) -> None:
6363
self.assertContains(response, 'data-user-profile-membership-notes-can-write="false"')
6464
self.assertContains(response, 'data-user-profile-group-detail-url-template="/group/__group_name__/"')
6565
self.assertContains(response, 'data-user-profile-agreements-url-template="/settings/?tab=agreements&amp;agreement=__agreement_cn__"')
66+
self.assertContains(response, 'data-user-profile-chat-irc-default-server="irc.libera.chat"')
67+
self.assertContains(response, 'data-user-profile-chat-matrix-default-server="matrix.org"')
68+
self.assertContains(response, 'data-user-profile-chat-mattermost-default-server="chat.almalinux.org"')
69+
self.assertContains(response, 'data-user-profile-chat-mattermost-default-team="almalinux"')
70+
self.assertContains(response, 'data-user-profile-chat-matrix-to-args="web-instance[element.io]=app.element.io"')
6671
self.assertContains(response, 'src="http://localhost:5173/src/entrypoints/userProfile.ts"')
6772
self.assertNotContains(response, "data-user-profile-summary-root")
6873
self.assertNotContains(response, "user-profile-summary-bootstrap")
@@ -238,6 +243,10 @@ def test_user_profile_detail_api_returns_data_only_payload(self) -> None:
238243
self.assertEqual(payload["summary"]["username"], username)
239244
self.assertNotIn("currentTimeLabel", payload["summary"])
240245
self.assertEqual(payload["summary"]["countryCode"], "US")
246+
self.assertEqual(payload["summary"]["ircNicks"], ["aliceirc"])
247+
self.assertTrue(all(isinstance(nick, str) for nick in payload["summary"]["ircNicks"]))
248+
self.assertNotIn("chatLinks", payload["summary"])
249+
self.assertNotIn("ircNickLinks", payload["summary"])
241250
self.assertNotIn("profileCountry", payload["summary"])
242251
self.assertEqual(payload["summary"]["socialProfiles"], [{"platform": "x", "urls": ["https://x.com/alice"]}])
243252
self.assertNotIn("label", payload["summary"]["socialProfiles"][0])

astra_app/core/views_groups.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
_normalize_str,
2626
agreement_settings_url,
2727
build_page_url_prefix,
28+
chat_link_bootstrap_context,
2829
get_username,
2930
normalize_freeipa_group_name,
3031
paginate_and_build_context,
@@ -304,6 +305,7 @@ def group_detail(request: HttpRequest, name: str) -> HttpResponse:
304305
"placeholder-agreement", "__agreement_cn__"
305306
),
306307
"agreements_list_url": agreement_settings_url(None),
308+
**chat_link_bootstrap_context(),
307309
},
308310
)
309311

astra_app/core/views_users.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from core.views_utils import (
4545
_normalize_str,
4646
agreement_settings_url,
47+
chat_link_bootstrap_context,
4748
get_username,
4849
normalize_freeipa_username,
4950
settings_url,
@@ -1192,6 +1193,7 @@ def user_profile(request: HttpRequest, username: str) -> HttpResponse:
11921193
"membership_notes_add_url": reverse("api-membership-notes-aggregate-add"),
11931194
"membership_notes_can_view": membership_can_view,
11941195
"membership_notes_can_write": membership_can_write,
1196+
**chat_link_bootstrap_context(),
11951197
},
11961198
)
11971199

astra_app/core/views_utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,16 @@ def agreement_settings_url(agreement_cn: str | None, *, return_to: str | None =
243243
return settings_url(tab="agreements", agreement=agreement_cn_value, return_to=return_to)
244244

245245

246+
def chat_link_bootstrap_context() -> dict[str, str]:
247+
return {
248+
"chat_irc_default_server": settings.CHAT_NETWORKS["irc"]["default_server"],
249+
"chat_matrix_default_server": settings.CHAT_NETWORKS["matrix"]["default_server"],
250+
"chat_mattermost_default_server": settings.CHAT_NETWORKS["mattermost"]["default_server"],
251+
"chat_mattermost_default_team": settings.CHAT_NETWORKS["mattermost"]["default_team"],
252+
"chat_matrix_to_args": settings.CHAT_MATRIX_TO_ARGS,
253+
}
254+
255+
246256
def block_action_without_country_code(
247257
request: HttpRequest,
248258
*,

0 commit comments

Comments
 (0)