Skip to content

Commit 6a71dd4

Browse files
FEATURE (notifiers): Add thread to Telegram notifications
1 parent 65c7178 commit 6a71dd4

File tree

12 files changed

+158
-20
lines changed

12 files changed

+158
-20
lines changed

backend/internal/features/backups/backups/usecases/postgresql/create_backup_uc.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ func (uc *CreatePostgresqlBackupUsecase) Execute(
7171

7272
// Use zstd compression level 5 for PostgreSQL 15+ (better compression and speed)
7373
// Fall back to gzip compression level 5 for older versions
74-
if pg.Version == tools.PostgresqlVersion13 || pg.Version == tools.PostgresqlVersion14 || pg.Version == tools.PostgresqlVersion15 {
74+
if pg.Version == tools.PostgresqlVersion13 || pg.Version == tools.PostgresqlVersion14 ||
75+
pg.Version == tools.PostgresqlVersion15 {
7576
args = append(args, "-Z", "5")
7677
uc.logger.Info("Using gzip compression level 5 (zstd not available)", "version", pg.Version)
7778
} else {

backend/internal/features/notifiers/models/telegram/model.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log/slog"
88
"net/http"
99
"net/url"
10+
"strconv"
1011
"strings"
1112

1213
"github.com/google/uuid"
@@ -16,6 +17,7 @@ type TelegramNotifier struct {
1617
NotifierID uuid.UUID `json:"notifierId" gorm:"primaryKey;column:notifier_id"`
1718
BotToken string `json:"botToken" gorm:"not null;column:bot_token"`
1819
TargetChatID string `json:"targetChatId" gorm:"not null;column:target_chat_id"`
20+
ThreadID *int64 `json:"threadId" gorm:"column:thread_id"`
1921
}
2022

2123
func (t *TelegramNotifier) TableName() string {
@@ -47,6 +49,10 @@ func (t *TelegramNotifier) Send(logger *slog.Logger, heading string, message str
4749
data.Set("text", fullMessage)
4850
data.Set("parse_mode", "HTML")
4951

52+
if t.ThreadID != nil && *t.ThreadID != 0 {
53+
data.Set("message_thread_id", strconv.FormatInt(*t.ThreadID, 10))
54+
}
55+
5056
req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
5157
if err != nil {
5258
return fmt.Errorf("failed to create request: %w", err)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
4+
ALTER TABLE telegram_notifiers
5+
ADD COLUMN thread_id BIGINT;
6+
7+
-- +goose StatementEnd
8+
9+
-- +goose Down
10+
-- +goose StatementBegin
11+
12+
ALTER TABLE telegram_notifiers
13+
DROP COLUMN IF EXISTS thread_id;
14+
15+
-- +goose StatementEnd
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
export interface TelegramNotifier {
22
botToken: string;
33
targetChatId: string;
4+
threadId?: number;
5+
6+
// temp field
7+
isSendToThreadEnabled?: boolean;
48
}

frontend/src/entity/notifiers/models/telegram/validateTelegramNotifier.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,10 @@ export const validateTelegramNotifier = (notifier: TelegramNotifier): boolean =>
99
return false;
1010
}
1111

12+
// If thread is enabled, thread ID must be present and valid
13+
if (notifier.isSendToThreadEnabled && (!notifier.threadId || notifier.threadId <= 0)) {
14+
return false;
15+
}
16+
1217
return true;
1318
};

frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export function EditNotifierComponent({
208208
)}
209209

210210
<div className="mb-1 flex items-center">
211-
<div className="min-w-[110px]">Type</div>
211+
<div className="w-[130px] min-w-[130px]">Type</div>
212212

213213
<Select
214214
value={notifier?.notifierType}

frontend/src/features/notifiers/ui/edit/notifiers/EditDiscordNotifierComponent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export function EditDiscordNotifierComponent({ notifier, setNotifier, setIsUnsav
1212
return (
1313
<>
1414
<div className="flex">
15-
<div className="max-w-[110px] min-w-[110px] pr-3">Channel webhook URL</div>
15+
<div className="w-[130px] max-w-[130px] min-w-[130px] pr-3">Channel webhook URL</div>
1616

1717
<div className="w-[250px]">
1818
<Input
@@ -35,7 +35,7 @@ export function EditDiscordNotifierComponent({ notifier, setNotifier, setIsUnsav
3535
</div>
3636
</div>
3737

38-
<div className="ml-[110px] max-w-[250px]">
38+
<div className="ml-[130px] max-w-[250px]">
3939
<div className="mt-1 text-xs text-gray-500">
4040
<strong>How to get Discord webhook URL:</strong>
4141
<br />

frontend/src/features/notifiers/ui/edit/notifiers/EditEmailNotifierComponent.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
1313
return (
1414
<>
1515
<div className="mb-1 flex items-center">
16-
<div className="min-w-[110px]">Target email</div>
16+
<div className="w-[130px] min-w-[130px]">Target email</div>
1717
<Input
1818
value={notifier?.emailNotifier?.targetEmail || ''}
1919
onChange={(e) => {
@@ -39,7 +39,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
3939
</div>
4040

4141
<div className="mb-1 flex items-center">
42-
<div className="min-w-[110px]">SMTP host</div>
42+
<div className="w-[130px] min-w-[130px]">SMTP host</div>
4343
<Input
4444
value={notifier?.emailNotifier?.smtpHost || ''}
4545
onChange={(e) => {
@@ -61,7 +61,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
6161
</div>
6262

6363
<div className="mb-1 flex items-center">
64-
<div className="min-w-[110px]">SMTP port</div>
64+
<div className="w-[130px] min-w-[130px]">SMTP port</div>
6565
<Input
6666
type="number"
6767
value={notifier?.emailNotifier?.smtpPort || ''}
@@ -84,7 +84,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
8484
</div>
8585

8686
<div className="mb-1 flex items-center">
87-
<div className="min-w-[110px]">SMTP user</div>
87+
<div className="w-[130px] min-w-[130px]">SMTP user</div>
8888
<Input
8989
value={notifier?.emailNotifier?.smtpUser || ''}
9090
onChange={(e) => {
@@ -106,7 +106,7 @@ export function EditEmailNotifierComponent({ notifier, setNotifier, setIsUnsaved
106106
</div>
107107

108108
<div className="mb-1 flex items-center">
109-
<div className="min-w-[110px]">SMTP password</div>
109+
<div className="w-[130px] min-w-[130px]">SMTP password</div>
110110
<Input
111111
value={notifier?.emailNotifier?.smtpPassword || ''}
112112
onChange={(e) => {

frontend/src/features/notifiers/ui/edit/notifiers/EditSlackNotifierComponent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface Props {
1111
export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved }: Props) {
1212
return (
1313
<>
14-
<div className="mb-1 ml-[110px] max-w-[200px]" style={{ lineHeight: 1 }}>
14+
<div className="mb-1 ml-[130px] max-w-[200px]" style={{ lineHeight: 1 }}>
1515
<a
1616
className="text-xs !text-blue-600"
1717
href="https://postgresus.com/notifier-slack"
@@ -23,7 +23,7 @@ export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved
2323
</div>
2424

2525
<div className="mb-1 flex items-center">
26-
<div className="min-w-[110px]">Bot token</div>
26+
<div className="w-[130px] min-w-[130px]">Bot token</div>
2727

2828
<div className="w-[250px]">
2929
<Input
@@ -48,7 +48,7 @@ export function EditSlackNotifierComponent({ notifier, setNotifier, setIsUnsaved
4848
</div>
4949

5050
<div className="mb-1 flex items-center">
51-
<div className="min-w-[110px]">Target chat ID</div>
51+
<div className="w-[130px] min-w-[130px]">Target chat ID</div>
5252

5353
<div className="w-[250px]">
5454
<Input

frontend/src/features/notifiers/ui/edit/notifiers/EditTelegramNotifierComponent.tsx

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { InfoCircleOutlined } from '@ant-design/icons';
2-
import { Input, Tooltip } from 'antd';
3-
import { useState } from 'react';
2+
import { Input, Switch, Tooltip } from 'antd';
3+
import { useEffect, useState } from 'react';
44

55
import type { Notifier } from '../../../../../entity/notifiers';
66

@@ -13,10 +13,22 @@ interface Props {
1313
export function EditTelegramNotifierComponent({ notifier, setNotifier, setIsUnsaved }: Props) {
1414
const [isShowHowToGetChatId, setIsShowHowToGetChatId] = useState(false);
1515

16+
useEffect(() => {
17+
if (notifier.telegramNotifier?.threadId && !notifier.telegramNotifier.isSendToThreadEnabled) {
18+
setNotifier({
19+
...notifier,
20+
telegramNotifier: {
21+
...notifier.telegramNotifier,
22+
isSendToThreadEnabled: true,
23+
},
24+
});
25+
}
26+
}, [notifier]);
27+
1628
return (
1729
<>
1830
<div className="flex items-center">
19-
<div className="min-w-[110px]">Bot token</div>
31+
<div className="w-[130px] min-w-[130px]">Bot token</div>
2032

2133
<div className="w-[250px]">
2234
<Input
@@ -39,7 +51,7 @@ export function EditTelegramNotifierComponent({ notifier, setNotifier, setIsUnsa
3951
</div>
4052
</div>
4153

42-
<div className="mb-1 ml-[110px]">
54+
<div className="mb-1 ml-[130px]">
4355
<a
4456
className="text-xs !text-blue-600"
4557
href="https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token"
@@ -51,7 +63,7 @@ export function EditTelegramNotifierComponent({ notifier, setNotifier, setIsUnsa
5163
</div>
5264

5365
<div className="mb-1 flex items-center">
54-
<div className="min-w-[110px]">Target chat ID</div>
66+
<div className="w-[130px] min-w-[130px]">Target chat ID</div>
5567

5668
<div className="w-[250px]">
5769
<Input
@@ -82,7 +94,7 @@ export function EditTelegramNotifierComponent({ notifier, setNotifier, setIsUnsa
8294
</Tooltip>
8395
</div>
8496

85-
<div className="ml-[110px] max-w-[250px]">
97+
<div className="ml-[130px] max-w-[250px]">
8698
{!isShowHowToGetChatId ? (
8799
<div
88100
className="mt-1 cursor-pointer text-xs text-blue-600"
@@ -107,6 +119,94 @@ export function EditTelegramNotifierComponent({ notifier, setNotifier, setIsUnsa
107119
</div>
108120
)}
109121
</div>
122+
123+
<div className="mt-4 mb-1 flex items-center">
124+
<div className="w-[130px] min-w-[130px] break-all">Send to group topic</div>
125+
126+
<Switch
127+
checked={notifier?.telegramNotifier?.isSendToThreadEnabled || false}
128+
onChange={(checked) => {
129+
if (!notifier?.telegramNotifier) return;
130+
131+
setNotifier({
132+
...notifier,
133+
telegramNotifier: {
134+
...notifier.telegramNotifier,
135+
isSendToThreadEnabled: checked,
136+
// Clear thread ID if disabling
137+
threadId: checked ? notifier.telegramNotifier.threadId : undefined,
138+
},
139+
});
140+
setIsUnsaved(true);
141+
}}
142+
size="small"
143+
/>
144+
145+
<Tooltip
146+
className="cursor-pointer"
147+
title="Enable this to send messages to a specific thread in a group chat"
148+
>
149+
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
150+
</Tooltip>
151+
</div>
152+
153+
{notifier?.telegramNotifier?.isSendToThreadEnabled && (
154+
<>
155+
<div className="mb-1 flex items-center">
156+
<div className="w-[130px] min-w-[130px]">Thread ID</div>
157+
158+
<div className="w-[250px]">
159+
<Input
160+
value={notifier?.telegramNotifier?.threadId?.toString() || ''}
161+
onChange={(e) => {
162+
if (!notifier?.telegramNotifier) return;
163+
164+
const value = e.target.value.trim();
165+
const threadId = value ? parseInt(value, 10) : undefined;
166+
167+
setNotifier({
168+
...notifier,
169+
telegramNotifier: {
170+
...notifier.telegramNotifier,
171+
threadId: !isNaN(threadId!) ? threadId : undefined,
172+
},
173+
});
174+
setIsUnsaved(true);
175+
}}
176+
size="small"
177+
className="w-full"
178+
placeholder="3"
179+
type="number"
180+
min="1"
181+
/>
182+
</div>
183+
184+
<Tooltip
185+
className="cursor-pointer"
186+
title="The ID of the thread where messages should be sent"
187+
>
188+
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
189+
</Tooltip>
190+
</div>
191+
192+
<div className="ml-[130px] max-w-[250px]">
193+
<div className="mt-1 text-xs text-gray-500">
194+
To get the thread ID, go to the thread in your Telegram group, tap on the thread name
195+
at the top, then tap &ldquo;Thread Info&rdquo;. Copy the thread link and take the last
196+
number from the URL.
197+
<br />
198+
<br />
199+
<strong>Example:</strong> If the thread link is{' '}
200+
<code className="rounded bg-gray-100 px-1">https://t.me/c/2831948048/3</code>, the
201+
thread ID is <code className="rounded bg-gray-100 px-1">3</code>
202+
<br />
203+
<br />
204+
<strong>Note:</strong> Thread functionality only works in group chats, not in private
205+
chats.
206+
</div>
207+
</div>
208+
</>
209+
)}
110210
</>
111211
);
112212
}

0 commit comments

Comments
 (0)