From 7cd3fcbc61522ddfaf968b3c61db55ae47cad5bc Mon Sep 17 00:00:00 2001 From: Elliot Chen Date: Thu, 1 Feb 2024 20:24:58 +0800 Subject: [PATCH] fix: wrong order of the timeline in the resource page & add webhook when create memos using Telegram bot (#2886) * fix: wrong order in resource page timeline * feat: add webhook when create memos using Telegram bot * rename variables and fix typos for static checks --- server/integration/telegram.go | 92 +++++++++++++++++++++++++++++++++- web/src/pages/Resources.tsx | 19 ++++--- 2 files changed, 104 insertions(+), 7 deletions(-) diff --git a/server/integration/telegram.go b/server/integration/telegram.go index a1226eee..71830281 100644 --- a/server/integration/telegram.go +++ b/server/integration/telegram.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" "strconv" + "time" "unicode/utf16" "github.com/lithammer/shortuuid/v4" @@ -13,7 +14,9 @@ import ( apiv1 "github.com/usememos/memos/api/v1" "github.com/usememos/memos/plugin/telegram" + "github.com/usememos/memos/plugin/webhook" storepb "github.com/usememos/memos/proto/gen/store" + "github.com/usememos/memos/server/service/metric" "github.com/usememos/memos/store" ) @@ -109,6 +112,9 @@ func (t *TelegramHandler) MessageHandle(ctx context.Context, bot *telegram.Bot, keyboard := generateKeyboardForMemoID(memoMessage.ID) _, err = bot.EditMessage(ctx, message.Chat.ID, reply.MessageID, fmt.Sprintf("Saved as %s Memo %d", memoMessage.Visibility, memoMessage.ID), keyboard) + + _ = t.dispatchMemoRelatedWebhook(ctx, *memoMessage, "memos.memo.created") + return err } @@ -135,7 +141,15 @@ func (t *TelegramHandler) CallbackQueryHandle(ctx context.Context, bot *telegram return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("Failed to EditMessage %s", err)) } - return bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("Success changing Memo %d to %s", memoID, visibility)) + err = bot.AnswerCallbackQuery(ctx, callbackQuery.ID, fmt.Sprintf("Success changing Memo %d to %s", memoID, visibility)) + + memo, webhookErr := t.store.GetMemo(ctx, &store.FindMemo{ + ID: &memoID, + }) + if webhookErr == nil { + _ = t.dispatchMemoRelatedWebhook(ctx, *memo, "memos.memo.updated") + } + return err } func generateKeyboardForMemoID(id int32) [][]telegram.InlineKeyboardButton { @@ -204,3 +218,79 @@ func convertToMarkdown(text string, messageEntities []telegram.MessageEntity) st return string(output) } + +func (t *TelegramHandler) dispatchMemoRelatedWebhook(ctx context.Context, memo store.Memo, activityType string) error { + webhooks, err := t.store.ListWebhooks(ctx, &store.FindWebhook{ + CreatorID: &memo.CreatorID, + }) + if err != nil { + return err + } + metric.Enqueue("webhook dispatch") + for _, hook := range webhooks { + payload := t.convertMemoToWebhookPayload(ctx, memo) + payload.ActivityType = activityType + payload.URL = hook.Url + err := webhook.Post(*payload) + if err != nil { + return errors.Wrap(err, "failed to post webhook") + } + } + return nil +} + +func (t *TelegramHandler) convertMemoToWebhookPayload(ctx context.Context, memo store.Memo) (payload *webhook.WebhookPayload) { + payload = &webhook.WebhookPayload{ + CreatorID: memo.CreatorID, + CreatedTs: time.Now().Unix(), + Memo: &webhook.Memo{ + ID: memo.ID, + CreatorID: memo.CreatorID, + CreatedTs: memo.CreatedTs, + UpdatedTs: memo.UpdatedTs, + Content: memo.Content, + Visibility: memo.Visibility.String(), + Pinned: memo.Pinned, + ResourceList: make([]*webhook.Resource, 0), + RelationList: make([]*webhook.MemoRelation, 0), + }, + } + + resourceList, err := t.store.ListResources(ctx, &store.FindResource{ + MemoID: &memo.ID, + }) + + if err != nil { + return payload + } + for _, resource := range resourceList { + payload.Memo.ResourceList = append(payload.Memo.ResourceList, &webhook.Resource{ + ID: resource.ID, + CreatorID: resource.CreatorID, + CreatedTs: resource.CreatedTs, + UpdatedTs: resource.UpdatedTs, + Filename: resource.Filename, + Type: resource.Type, + Size: resource.Size, + InternalPath: resource.InternalPath, + ExternalLink: resource.ExternalLink, + }) + } + + relationList, err := t.store.ListMemoRelations(ctx, &store.FindMemoRelation{ + MemoID: &memo.ID, + }) + + if err != nil { + return payload + } + + for _, relation := range relationList { + payload.Memo.RelationList = append(payload.Memo.RelationList, &webhook.MemoRelation{ + MemoID: relation.MemoID, + RelatedMemoID: relation.RelatedMemoID, + Type: string(relation.Type), + }) + } + return payload +} diff --git a/web/src/pages/Resources.tsx b/web/src/pages/Resources.tsx index f7b1229d..36e53e7a 100644 --- a/web/src/pages/Resources.tsx +++ b/web/src/pages/Resources.tsx @@ -15,8 +15,15 @@ import { Resource } from "@/types/proto/api/v2/resource_service"; import { useTranslate } from "@/utils/i18n"; function groupResourcesByDate(resources: Resource[]) { + const tmp_resources: Resource[] = resources.slice(); + tmp_resources.sort((a: Resource, b: Resource) => { + const a_date = new Date(a.createTime as any); + const b_date = new Date(b.createTime as any); + return b_date.getTime() - a_date.getTime(); + }); + const grouped = new Map(); - resources.forEach((item) => { + tmp_resources.forEach((item) => { const date = new Date(item.createTime as any); const year = date.getFullYear(); const month = date.getMonth() + 1; @@ -41,15 +48,15 @@ const Resources = () => { }); const memoStore = useMemoStore(); const [resources, setResources] = useState([]); - const filteredResources = resources.filter((resource) => includes(resource.filename, state.searchQuery)); - const groupedResources = groupResourcesByDate(filteredResources.filter((resoure) => resoure.memoId)); - const unusedResources = filteredResources.filter((resoure) => !resoure.memoId); + const filteredResources = resources.filter((resource: any) => includes(resource.filename, state.searchQuery)); + const groupedResources = groupResourcesByDate(filteredResources.filter((resource: any) => resource.memoId)); + const unusedResources = filteredResources.filter((resource: any) => !resource.memoId); useEffect(() => { resourceServiceClient.listResources({}).then(({ resources }) => { setResources(resources); loadingState.setFinish(); - Promise.all(resources.map((resource) => (resource.memoId ? memoStore.getOrFetchMemoById(resource.memoId) : null))); + Promise.all(resources.map((resource: any) => (resource.memoId ? memoStore.getOrFetchMemoById(resource.memoId) : null))); }); }, []); @@ -63,7 +70,7 @@ const Resources = () => { for (const resource of unusedResources) { await resourceServiceClient.deleteResource({ id: resource.id }); } - setResources(resources.filter((resoure) => resoure.memoId)); + setResources(resources.filter((resource) => resource.memoId)); }, }); };