Repository: cowprotocol/bff
Pull request #120: Get susbcribed users and create draft for trade event producer
Author: login: anxolin
Created: 2025-04-17T13:41:32Z | Merged: 2025-04-18T11:58:10Z

This PR creates

## SubscriptionsRepository
For now only used to get the relevant accounts. This repository needs caching because its used by multiple producers. For now I implement it easily with a basic in-memory caching, but should follow our repo structure and use our cache repository to take full advantage of our current infra REDIS/in-memory cache layer, etc.

This repo is important to not post notifications to the queue that are irrelevant for the consumer.

For implementing the `getSubscribedAccounts` I did a small refactor because is very similar to how we get all pages for the subscriptions 

## Trade producer
Creates some skeleton for the trade producer and inject already some relevant repositories. Follow up PRs will continue its implementation



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced trade notification processing with a new notification producer for trade-related events.
  - Added support for managing and caching subscribed accounts to improve notification delivery.

- **Improvements**
  - Enhanced notification filtering to ensure only subscribed accounts receive CMS push notifications.
  - Improved efficiency and reliability of fetching all subscribed accounts with a new pagination helper.

- **Refactor**
  - Streamlined repository and producer integration for better maintainability and extensibility.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/apps/notification-producer/src/SubscriptionsRepository.ts b/apps/notification-producer/src/SubscriptionsRepository.ts
new file mode 100644
index 00000000..ab8062a1
--- /dev/null
+++ b/apps/notification-producer/src/SubscriptionsRepository.ts
@@ -0,0 +1,21 @@
+import { getAllSubscribedAccounts } from '@cowprotocol/cms-api';
+
+const CACHE_TIME = 30000;
+
+// TODO: Move to repositories and make a proper cached repository and use DI
+export class SubscriptionRepository {
+  private lastCheck: number | null = null;
+  private cachedAccounts: string[] | null = null;
+
+  async getAllSubscribedAccounts(): Promise<string[]> {
+    const now = Date.now();
+    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
+      this.cachedAccounts = Array.from(
+        new Set(await getAllSubscribedAccounts())
+      );
+      this.lastCheck = now;
+      return this.cachedAccounts;
+    }
+    return this.cachedAccounts || [];
+  }
+}
diff --git a/apps/notification-producer/src/main.ts b/apps/notification-producer/src/main.ts
index a5570f2d..6a0a1083 100644
--- a/apps/notification-producer/src/main.ts
+++ b/apps/notification-producer/src/main.ts
@@ -1,6 +1,8 @@
 import { Runnable } from '../types';
 import { NotificationsRepository } from './NotificationsRepository';
 import { CmsNotificationProducer } from './producers/CmsNotificationProducer';
+import { TradeNotificationProducer } from './producers/TradeNotificationProducer';
+import { SubscriptionRepository } from './SubscriptionsRepository';
 
 /**
  * Main loop: Run and re-attempt on error
@@ -10,11 +12,18 @@ async function mainLoop() {
 
   // TODO: Move to DI
   const notificationsRepository = new NotificationsRepository();
+  const subscriptionRepository = new SubscriptionRepository();
+
+  const repositories = {
+    notificationsRepository,
+    subscriptionRepository,
+  };
 
   // Create all producers
   const producers: Runnable[] = [
     // CMS producer: Fetch PUSH notifications
-    new CmsNotificationProducer(notificationsRepository),
+    new CmsNotificationProducer(repositories),
+    new TradeNotificationProducer(repositories),
   ];
 
   const promises = [];
diff --git a/apps/notification-producer/src/producers/CmsNotificationProducer.ts b/apps/notification-producer/src/producers/CmsNotificationProducer.ts
index b575ecfc..517cca37 100644
--- a/apps/notification-producer/src/producers/CmsNotificationProducer.ts
+++ b/apps/notification-producer/src/producers/CmsNotificationProducer.ts
@@ -7,9 +7,15 @@ import { NotificationsRepository } from '../NotificationsRepository';
 import { doForever } from '../utils';
 import Mustache from 'mustache';
 import { Runnable } from '../../types';
+import { SubscriptionRepository } from '../SubscriptionsRepository';
 
 const WAIT_TIME = 30000;
 
+export type CmsNotificationProducerProps = {
+  notificationsRepository: NotificationsRepository;
+  subscriptionRepository: SubscriptionRepository;
+};
+
 export class CmsNotificationProducer implements Runnable {
   /**
    * This in-memory state just adds some resilience in case there's an error posting the message.
@@ -19,9 +25,7 @@ export class CmsNotificationProducer implements Runnable {
    */
   pendingNotifications = new Map<string, Notification>();
 
-  constructor(
-    private readonly notificationsRepository: NotificationsRepository
-  ) {}
+  constructor(private props: CmsNotificationProducerProps) {}
 
   /**
    * Main loop: Run the CMS notification producer. This method runs indefinitely,
@@ -40,8 +44,15 @@ export class CmsNotificationProducer implements Runnable {
   }
 
   async fetchAndSend(): Promise<void> {
+    const accounts =
+      await this.props.subscriptionRepository.getAllSubscribedAccounts();
+
     // Get PUSH notifications
-    const cmsPushNotifications = await getPushNotifications();
+    const cmsPushNotifications = (await getPushNotifications()).filter(
+      // Include only the notifications for subscribed accounts
+      ({ account }) => accounts.includes(account)
+    );
+
     const pendingNotifications = Array.from(this.pendingNotifications.values());
     const pushNotifications = cmsPushNotifications
       .map(fromCmsToNotifications)
@@ -61,10 +72,10 @@ export class CmsNotificationProducer implements Runnable {
     );
 
     // Connect
-    await this.notificationsRepository.connect();
+    await this.props.notificationsRepository.connect();
 
     // Post notifications to queue
-    this.notificationsRepository.sendNotifications(pushNotifications);
+    this.props.notificationsRepository.sendNotifications(pushNotifications);
     this.pendingNotifications.clear();
   }
 }
diff --git a/apps/notification-producer/src/producers/TradeNotificationProducer.ts b/apps/notification-producer/src/producers/TradeNotificationProducer.ts
new file mode 100644
index 00000000..c2473535
--- /dev/null
+++ b/apps/notification-producer/src/producers/TradeNotificationProducer.ts
@@ -0,0 +1,64 @@
+import { Notification } from '@cowprotocol/notifications';
+import { NotificationsRepository } from '../NotificationsRepository';
+import { doForever } from '../utils';
+
+import { Runnable } from '../../types';
+import { SubscriptionRepository } from '../SubscriptionsRepository';
+
+const WAIT_TIME = 30000;
+
+export type TradeNotificationProducerProps = {
+  notificationsRepository: NotificationsRepository;
+  subscriptionRepository: SubscriptionRepository;
+};
+
+export class TradeNotificationProducer implements Runnable {
+  /**
+   * This in-memory state just adds some resilience in case there's an error posting the message.
+   * Because the PUSH notifications are currently consumed just by reading, in case of a failure the notification is lost
+   *
+   * This solution is a patch until we properly implement a more reliable consumption
+   */
+  pendingNotifications = new Map<string, Notification>();
+
+  constructor(private props: TradeNotificationProducerProps) {}
+
+  /**
+   * Main loop: Run the CMS notification producer. This method runs indefinitely,
+   * fetching notifications and sending them to the queue.
+   *
+   * The method should not throw or finish.
+   */
+  async start(): Promise<void> {
+    doForever(
+      'TradeNotificationProducer',
+      async () => {
+        await this.fetchAndSend();
+      },
+      WAIT_TIME
+    );
+  }
+
+  async fetchAndSend(): Promise<void> {
+    // Get last indexed block
+    // TODO:
+    // Get last block
+    // TODO:
+    // Get trade events from block to last block
+    // TODO:
+    // Filter events, only keep relevant ones
+    // TODO:
+    // Convert events to notifications
+    // TODO:
+    // Send notifications
+    // TODO:
+
+    const accounts =
+      await this.props.subscriptionRepository.getAllSubscribedAccounts();
+
+    console.log(
+      'For now, nothing to do. I plan to fetch trades for: ' +
+        accounts.join(', ')
+    );
+  }
+}
diff --git a/libs/cms-api/src/index.ts b/libs/cms-api/src/index.ts
index 02270b18..380479da 100644
--- a/libs/cms-api/src/index.ts
+++ b/libs/cms-api/src/index.ts
@@ -1,5 +1,4 @@
 import { CmsClient, components } from '@cowprotocol/cms';
-import assert from 'assert';
 
 type Schemas = components['schemas'];
 export type CmsNotification = Schemas['NotificationListResponseDataItem'];
@@ -137,31 +136,50 @@ async function getNotificationsPage({
   return data.data;
 }
 
+export async function getAllSubscribedAccounts(): Promise<string[]> {
+  return getAllPages({
+    pageSize: PAGE_SIZE,
+    getPage: (params) => getSubscribedAccounts(params),
+  });
+}
+
 export async function getAllTelegramSubscriptionsForAccounts(
   accounts: string[]
 ): Promise<CmsTelegramSubscription[]> {
+  return getAllPages({
+    pageSize: PAGE_SIZE,
+    getPage: (params) =>
+      getTelegramSubscriptionsForAccounts({
+        ...params,
+        accounts,
+      }),
+  });
+}
+
+async function getAllPages<T>({
+  pageSize = PAGE_SIZE,
+  getPage,
+}: PaginationParam & {
+  getPage: (params: PaginationParam) => Promise<T[]>;
+}): Promise<T[]> {
   const allSubscriptions = [];
   let page = 0;
 
-  let subscriptions = await getTelegramSubscriptionsForAccounts({
+  let subscriptions = await getPage({
     page,
-    pageSize: PAGE_SIZE + 1,
-    accounts,
+    pageSize: pageSize + 1,
   }); // Get one extra to check if there's more pages
 
   allSubscriptions.push(
-    subscriptions.length > PAGE_SIZE
-      ? subscriptions.slice(0, -1)
-      : subscriptions
+    subscriptions.length > pageSize ? subscriptions.slice(0, -1) : subscriptions
   );
 
-  while (subscriptions.length > PAGE_SIZE) {
-    subscriptions = await getTelegramSubscriptionsForAccounts({
+  while (subscriptions.length > pageSize) {
+    subscriptions = await getPage({
       page,
-      pageSize: PAGE_SIZE + 1,
-      accounts,
+      pageSize: pageSize + 1,
     }); // Get one extra to check if there's more pages
-    const hasMorePages = subscriptions.length > PAGE_SIZE;
+    const hasMorePages = subscriptions.length > pageSize;
     allSubscriptions.push(
       hasMorePages ? subscriptions.slice(0, -1) : subscriptions
     );
@@ -204,6 +222,27 @@ async function getTelegramSubscriptionsForAccounts({
   return data;
 }
 
+async function getSubscribedAccounts({
+  page = 0,
+  pageSize = PAGE_SIZE,
+}: PaginationParam): Promise<string[]> {
+  const { data, error, response } = await cmsClient.GET(`/tg-subscriptions`, {
+    // Pagination
+    'pagination[page]': page,
+    'pagination[pageSize]': pageSize,
+  });
+
+  if (error) {
+    console.error(
+      `Error ${response.status} getting telegram subscriptions: ${response.url}. Page${page}`,
+      error
+    );
+    throw error;
+  }
+
+  return (data as CmsTelegramSubscription[]).map(({ account }) => account);
+}
+
 export async function getPushNotifications(): Promise<CmsPushNotification[]> {
   const { data, error, response } = await cmsClient.GET('/push-notifications');
 


Reviews:
user: login: shoom3301
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T13:48:12Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary><blockquote>

`5-21`: **Enhance cache validation in SubscriptionRepository.**

The caching implementation is simple but effective. However, as previously noted in a review, the cache check should be improved to handle edge cases.


```diff
-    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
+    if (!this.cachedAccounts || !this.lastCheck || now - this.lastCheck > CACHE_TIME) {
```

This ensures we don't return an empty array when there is a valid `lastCheck` but no `cachedAccounts`.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>libs/cms-api/src/index.ts (1)</summary><blockquote>

`226-245`: **Type assertion could be improved in getSubscribedAccounts.**

While the function implementation is solid, there's a type assertion that could be improved for better type safety.


```diff
-  return (data as CmsTelegramSubscription[]).map(({ account }) => account);
+  return data.map(({ account }: CmsTelegramSubscription) => account);
```

This change moves the type assertion inside the map callback, which is more precise and provides better type checking.

</blockquote></details>
<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary><blockquote>

`3-3`: **Consider making CACHE_TIME configurable.**

The hardcoded cache time of 30 seconds might not be optimal for all environments (development, testing, production). Consider making this configurable through environment variables or constructor parameters.


```diff
-const CACHE_TIME = 30000;
+const CACHE_TIME = Number(process.env.SUBSCRIPTION_CACHE_TIME) || 30000;
```

</blockquote></details>
<details>
<summary>apps/notification-producer/src/producers/TradeNotificationProducer.ts (3)</summary><blockquote>

`42-55`: **Consider adding TODO tickets references for tracking future implementation.**

The method contains several TODO comments outlining the planned implementation steps, but lacks references to specific tickets/issues that would track these tasks.


```diff
  async fetchAndSend(): Promise<void> {
    // Get last indexed block
-   // TODO:
+   // TODO: [TICKET-123] Implement get last indexed block
    // Get last block
-   // TODO:
+   // TODO: [TICKET-124] Implement get last block
    // Get trade events from block to last block
-   // TODO:
+   // TODO: [TICKET-125] Implement fetching trade events from range
    // Filter events, only keep relevant ones
-   // TODO:
+   // TODO: [TICKET-126] Implement event filtering logic
    // Convert events to notifications
-   // TODO:
+   // TODO: [TICKET-127] Implement event to notification conversion
    // Send notifications
-   // TODO:
+   // TODO: [TICKET-128] Implement notification sending
```

---

`56-63`: **Add type safety to console.log messages when joining arrays.**

When joining arrays for logging purposes, consider checking if the array is empty to avoid unclear output.


```diff
    const accounts =
      await this.props.subscriptionRepository.getAllSubscribedAccounts();

    console.log(
-     'For now, nothing to do. I plan to fetch trades for: ' +
-       accounts.join(', ')
+     'For now, nothing to do. I plan to fetch trades for: ' +
+       (accounts.length > 0 ? accounts.join(', ') : 'no subscribed accounts')
    );
```

---

`42-63`: **Consider implementing basic error handling for the subscription repository calls.**

The fetchAndSend method calls the subscription repository but doesn't handle potential errors from that call specifically. Since this is running in a doForever loop, errors will be caught at that level, but more specific error handling could be helpful.


```diff
  async fetchAndSend(): Promise<void> {
    // Get last indexed block
    // TODO:
    // Get last block
    // TODO:
    // Get trade events from block to last block
    // TODO:
    // Filter events, only keep relevant ones
    // TODO:
    // Convert events to notifications
    // TODO:
    // Send notifications
    // TODO:

-   const accounts =
-     await this.props.subscriptionRepository.getAllSubscribedAccounts();
-
-   console.log(
-     'For now, nothing to do. I plan to fetch trades for: ' +
-       accounts.join(', ')
-   );
+   try {
+     const accounts =
+       await this.props.subscriptionRepository.getAllSubscribedAccounts();
+
+     console.log(
+       'For now, nothing to do. I plan to fetch trades for: ' +
+       (accounts.length > 0 ? accounts.join(', ') : 'no subscribed accounts')
+     );
+   } catch (error) {
+     console.error('Failed to fetch subscribed accounts:', error);
+   }
  }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a8bd6885c1d5aa92a11d5723a9b93ffc8f86b778 and 5a40fdaf7fad4ea0147372dca4badfccfd636cfb.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `apps/notification-producer/src/SubscriptionsRepository.ts` (1 hunks)
* `apps/notification-producer/src/main.ts` (2 hunks)
* `apps/notification-producer/src/producers/CmsNotificationProducer.ts` (4 hunks)
* `apps/notification-producer/src/producers/TradeNotificationProducer.ts` (1 hunks)
* `libs/cms-api/src/index.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>apps/notification-producer/src/main.ts (4)</summary><blockquote>

<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary>

* `SubscriptionRepository` (6-21)

</details>
<details>
<summary>apps/notification-producer/types.ts (1)</summary>

* `Runnable` (4-9)

</details>
<details>
<summary>apps/notification-producer/src/producers/CmsNotificationProducer.ts (1)</summary>

* `CmsNotificationProducer` (19-81)

</details>
<details>
<summary>apps/notification-producer/src/producers/TradeNotificationProducer.ts (1)</summary>

* `TradeNotificationProducer` (15-64)

</details>

</blockquote></details>
<details>
<summary>apps/notification-producer/src/producers/CmsNotificationProducer.ts (3)</summary><blockquote>

<details>
<summary>apps/notification-producer/src/NotificationsRepository.ts (1)</summary>

* `NotificationsRepository` (10-66)

</details>
<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary>

* `SubscriptionRepository` (6-21)

</details>
<details>
<summary>libs/cms-api/src/index.ts (1)</summary>

* `getPushNotifications` (247-259)

</details>

</blockquote></details>
<details>
<summary>apps/notification-producer/src/producers/TradeNotificationProducer.ts (5)</summary><blockquote>

<details>
<summary>apps/notification-producer/src/NotificationsRepository.ts (1)</summary>

* `NotificationsRepository` (10-66)

</details>
<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary>

* `SubscriptionRepository` (6-21)

</details>
<details>
<summary>apps/notification-producer/types.ts (1)</summary>

* `Runnable` (4-9)

</details>
<details>
<summary>libs/notifications/src/index.ts (1)</summary>

* `Notification` (15-21)

</details>
<details>
<summary>apps/notification-producer/src/utils.ts (1)</summary>

* `doForever` (3-21)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>libs/cms-api/src/index.ts (3)</summary>

`140-145`: **Nice implementation of the getAllSubscribedAccounts function.**

This function elegantly leverages the new generic pagination helper to retrieve all subscribed accounts. The reuse of pagination logic reduces code duplication.

---

`150-158`: **Good refactoring of getAllTelegramSubscriptionsForAccounts.**

The function now uses the generic pagination helper, eliminating duplicated pagination logic. This improves maintainability and consistency.

---

`160-197`: **Well-implemented generic pagination helper.**

The `getAllPages` function abstracts the pagination logic effectively, handling the edge cases properly:
- Fetching one extra item to detect if more pages exist
- Slicing off the extra item when needed
- Proper accumulation of results from multiple pages
- Handling the case when no more pages are available

This abstraction will simplify future pagination implementations.

</details>
<details>
<summary>apps/notification-producer/src/main.ts (2)</summary>

`15-20`: **Good approach to organize repositories for dependency injection.**

Creating a shared repositories object with both repositories is a clean approach that makes dependency injection more manageable. This structure will scale well as more repositories are added in the future.

---

`25-26`: **New producers using consistent initialization pattern.**

Both producers now receive the same repositories object, maintaining a consistent initialization pattern. The addition of the TradeNotificationProducer aligns with the PR objective of creating a skeleton for trade event processing.

</details>
<details>
<summary>apps/notification-producer/src/SubscriptionsRepository.ts (1)</summary>

`5-5`: **ToDo indicator for future improvements.**

Good practice to mark this class with a TODO for future improvements. This acknowledges that the current implementation is a starting point with a plan for proper integration with the repository structure and dependency injection.

</details>
<details>
<summary>apps/notification-producer/src/producers/CmsNotificationProducer.ts (3)</summary>

`14-17`: **Well-defined props type for consistent repository injection.**

The props type clearly defines the dependencies required by this producer, making the code more maintainable and the dependencies explicit.

---

`46-54`: **Efficient notification filtering based on subscriptions.**

This optimization ensures that only notifications for subscribed accounts are processed, which:
1. Reduces unnecessary processing
2. Prevents posting irrelevant notifications to the queue
3. Aligns with the PR objective of preventing irrelevant notifications

The filter implementation is clean and effective.

---

`75-78`: **References to notificationsRepository correctly updated.**

All references to the notifications repository have been properly updated to use the properties from the injected repositories object.

</details>
<details>
<summary>apps/notification-producer/src/producers/TradeNotificationProducer.ts (3)</summary>

`10-13`: **Props type definition looks good.**

The props type is well-defined with clear dependencies on the required repositories.

---

`15-23`: **The pendingNotifications map is declared but not used.**

The in-memory state for adding resilience is a good idea, but it's not implemented in the current code. While this is documented as a patch solution, there's no logic that uses this map yet.


Are there plans to implement the usage of this map in a follow-up PR? If not, consider either implementing the resilience logic or removing the unused map until needed.

---

`26-40`: **Implementation of the Runnable interface looks good.**

The start method correctly implements the Runnable interface by using doForever to create an indefinite loop that handles errors and includes appropriate wait time between iterations.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T09:06:47Z
user: login: alfetopito
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-18T09:28:24Z
user: login: anxolin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-18T11:46:33Z

Review comments:
diff_hunk: @@ -0,0 +1,21 @@
+import { getAllSubscribedAccounts } from '@cowprotocol/cms-api';
+
+const CACHE_TIME = 30000;
+
+// TODO: Move to repositories and make a proper cached repository and use DI
+export class SubscriptionRepository {
+  private lastCheck: number | null = null;
+  private cachedAccounts: string[] | null = null;
+
+  async getAllSubscribedAccounts(): Promise<string[]> {
+    const now = Date.now();
+    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
path: apps/notification-producer/src/SubscriptionsRepository.ts
user: login: shoom3301
body: Just to be more safe, we can check the condition to:

```
if (!this.cachedAccounts || !this.lastCheck || now - this.lastCheck > CACHE_TIME)
```

Otherwise, in theory we could return an empty array when there is a valid `lastCheck` but no `cachedAccounts`.
created_at: 2025-04-17T13:48:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+import { Notification } from '@cowprotocol/notifications';
+import { NotificationsRepository } from '../NotificationsRepository';
+import { doForever } from '../utils';
+
+import { Runnable } from '../../types';
+import { SubscriptionRepository } from '../SubscriptionsRepository';
+
+const WAIT_TIME = 30000;
+
+export type TradeNotificationProducerProps = {
+  notificationsRepository: NotificationsRepository;
+  subscriptionRepository: SubscriptionRepository;
+};
+
+export class TradeNotificationProducer implements Runnable {
+  /**
+   * This in-memory state just adds some resilience in case there's an error posting the message.
+   * Because the PUSH notifications are currently consumed just by reading, in case of a failure the notification is lost
+   *
+   * This solution is a patch until we properly implement a more reliable consumption
+   */
+  pendingNotifications = new Map<string, Notification>();
+
+  constructor(private props: TradeNotificationProducerProps) {}
+
+  /**
+   * Main loop: Run the CMS notification producer. This method runs indefinitely,
+   * fetching notifications and sending them to the queue.
+   *
+   * The method should not throw or finish.
+   */
+  async start(): Promise<void> {
+    doForever(
+      'TradeNotificationProducer',
+      async () => {
+        await this.fetchAndSend();
+      },
+      WAIT_TIME
+    );
+  }
+
+  async fetchAndSend(): Promise<void> {
+    // Get last indexed block
+    // TODO:
+    // Get last block
+    // TODO:
+    // Get trade events from block to last block
+    // TODO:
+    // Filter events, only keep relevant ones
+    // TODO:
+    // Convert events to notifications
+    // TODO:
+    // Send notifications
+    // TODO:
+
+    const accounts =
+      await this.props.subscriptionRepository.getAllSubscribedAccounts();
+
+    console.log(
+      'For now, nothing to do. I plan to fetch trades for: ' +
+        accounts.join(', ')
+    );
+  }
+}
path: apps/notification-producer/src/producers/TradeNotificationProducer.ts
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider adding integration with the notificationsRepository.**

The class accepts both repositories as dependencies, but only uses the subscription repository. While this is a skeleton implementation for now, it would be helpful to add a reference to how the notificationsRepository will be used in future implementations.


Since you've included the notificationsRepository in the constructor props, I assume you plan to use it when implementing the "Send notifications" TODO. It might be worth adding a brief comment or method stub to clarify how it will be used.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's a similar pattern in the CmsNotificationProducer
rg -A 10 "notificationsRepository" apps/notification-producer/src/producers/CmsNotificationProducer.ts
```

Length of output: 961

---



</details>

**Stub out notificationsRepository integration**  

To mirror how CmsNotificationProducer uses its notificationsRepository, add a placeholder for connecting, sending, and clearing notifications in TradeNotificationProducer. This will make it clear where and how you’ll integrate the repository when you implement the “Send notifications” TODO.

• File: apps/notification-producer/src/producers/TradeNotificationProducer.ts  
• Location: inside `fetchAndSend` (after you convert events to notifications)

Suggested diff:
```diff
   async fetchAndSend(): Promise<void> {
     // …fetch and convert events to notifications…
     const accounts =
       await this.props.subscriptionRepository.getAllSubscribedAccounts();

     console.log(
       'For now, nothing to do. I plan to fetch trades for: ' +
         accounts.join(', ')
     );
+    // TODO: integrate with notificationsRepository
+    // await this.props.notificationsRepository.connect();
+    // this.props.notificationsRepository.sendNotifications([...]);
+    // this.pendingNotifications.clear();
   }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
export class TradeNotificationProducer implements Runnable {
  /**
   * This in-memory state just adds some resilience in case there's an error posting the message.
   * Because the PUSH notifications are currently consumed just by reading, in case of a failure the notification is lost
   *
   * This solution is a patch until we properly implement a more reliable consumption
   */
  pendingNotifications = new Map<string, Notification>();

  constructor(private props: TradeNotificationProducerProps) {}

  /**
   * Main loop: Run the CMS notification producer. This method runs indefinitely,
   * fetching notifications and sending them to the queue.
   *
   * The method should not throw or finish.
   */
  async start(): Promise<void> {
    doForever(
      'TradeNotificationProducer',
      async () => {
        await this.fetchAndSend();
      },
      WAIT_TIME
    );
  }

  async fetchAndSend(): Promise<void> {
    // Get last indexed block
    // TODO:
    // Get last block
    // TODO:
    // Get trade events from block to last block
    // TODO:
    // Filter events, only keep relevant ones
    // TODO:
    // Convert events to notifications
    // TODO:
    // Send notifications
    // TODO:

    const accounts =
      await this.props.subscriptionRepository.getAllSubscribedAccounts();

    console.log(
      'For now, nothing to do. I plan to fetch trades for: ' +
        accounts.join(', ')
    );

    // TODO: integrate with notificationsRepository
    // await this.props.notificationsRepository.connect();
    // this.props.notificationsRepository.sendNotifications([...]);
    // this.pendingNotifications.clear();
  }
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-04-18T09:06:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,21 @@
+import { getAllSubscribedAccounts } from '@cowprotocol/cms-api';
+
+const CACHE_TIME = 30000;
+
+// TODO: Move to repositories and make a proper cached repository and use DI
+export class SubscriptionRepository {
+  private lastCheck: number | null = null;
+  private cachedAccounts: string[] | null = null;
+
+  async getAllSubscribedAccounts(): Promise<string[]> {
+    const now = Date.now();
+    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
+      this.cachedAccounts = Array.from(
+        new Set(await getAllSubscribedAccounts())
+      );
path: apps/notification-producer/src/SubscriptionsRepository.ts
user: login: alfetopito
body: Why array -> set -> array?
Do we expect to have duplicates in the getAllSubscribedAccounts?
created_at: 2025-04-18T09:07:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,21 @@
+import { getAllSubscribedAccounts } from '@cowprotocol/cms-api';
+
+const CACHE_TIME = 30000;
+
+// TODO: Move to repositories and make a proper cached repository and use DI
+export class SubscriptionRepository {
+  private lastCheck: number | null = null;
+  private cachedAccounts: string[] | null = null;
+
+  async getAllSubscribedAccounts(): Promise<string[]> {
+    const now = Date.now();
+    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
path: apps/notification-producer/src/SubscriptionsRepository.ts
user: login: alfetopito
body: I think it's fine because it could have cached no accounts.
created_at: 2025-04-18T09:09:36Z
author_association: COLLABORATOR
diff_hunk: @@ -40,8 +44,15 @@ export class CmsNotificationProducer implements Runnable {
   }
 
   async fetchAndSend(): Promise<void> {
+    const accounts =
+      await this.props.subscriptionRepository.getAllSubscribedAccounts();
+
     // Get PUSH notifications
-    const cmsPushNotifications = await getPushNotifications();
+    const cmsPushNotifications = (await getPushNotifications()).filter(
+      // Include only the notifications for subscribed accounts
+      ({ account }) => accounts.includes(account)
path: apps/notification-producer/src/producers/CmsNotificationProducer.ts
user: login: alfetopito
body: Are the accounts lower cased already?
created_at: 2025-04-18T09:12:15Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,21 @@
+import { getAllSubscribedAccounts } from '@cowprotocol/cms-api';
+
+const CACHE_TIME = 30000;
+
+// TODO: Move to repositories and make a proper cached repository and use DI
+export class SubscriptionRepository {
+  private lastCheck: number | null = null;
+  private cachedAccounts: string[] | null = null;
+
+  async getAllSubscribedAccounts(): Promise<string[]> {
+    const now = Date.now();
+    if (!this.lastCheck || now - this.lastCheck > CACHE_TIME) {
+      this.cachedAccounts = Array.from(
+        new Set(await getAllSubscribedAccounts())
+      );
path: apps/notification-producer/src/SubscriptionsRepository.ts
user: login: anxolin
body: remove duplicates
created_at: 2025-04-18T11:46:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-17T13:41:38Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update introduces a subscription caching repository and integrates it into the notification producer system. A new `SubscriptionRepository` class provides cached retrieval of subscribed accounts, leveraging a new `getAllSubscribedAccounts` API function built on a generic pagination helper. The main notification producer loop is refactored to instantiate and pass both the notifications and subscriptions repositories to its producers. The `CmsNotificationProducer` is updated to filter notifications by active subscriptions, and a new `TradeNotificationProducer` class is added, currently with placeholder logic for future trade event processing.

## Changes

| File(s)                                                                                   | Change Summary |
|------------------------------------------------------------------------------------------|---------------|
| `apps/notification-producer/src/SubscriptionsRepository.ts`                              | Added `SubscriptionRepository` class for cached retrieval of subscribed accounts. |
| `apps/notification-producer/src/main.ts`                                                 | Refactored main loop to instantiate and pass both `NotificationsRepository` and `SubscriptionRepository` to producers; added `TradeNotificationProducer`. |
| `apps/notification-producer/src/producers/CmsNotificationProducer.ts`                    | Updated constructor to accept both repositories; filters notifications by subscribed accounts; introduced `CmsNotificationProducerProps` type. |
| `apps/notification-producer/src/producers/TradeNotificationProducer.ts`                  | Added new `TradeNotificationProducer` class and props type, with placeholder logic for future trade event processing. |
| `libs/cms-api/src/index.ts`                                                              | Added generic pagination helper `getAllPages`; added `getAllSubscribedAccounts` function; refactored `getAllTelegramSubscriptionsForAccounts` to use new helper; added internal `getSubscribedAccounts` function. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Main
    participant NotificationsRepository
    participant SubscriptionRepository
    participant CmsNotificationProducer
    participant TradeNotificationProducer
    participant CMSAPI

    Main->>NotificationsRepository: instantiate
    Main->>SubscriptionRepository: instantiate
    Main->>CmsNotificationProducer: pass repositories
    Main->>TradeNotificationProducer: pass repositories

    loop Every 30s (CmsNotificationProducer)
        CmsNotificationProducer->>SubscriptionRepository: getAllSubscribedAccounts()
        SubscriptionRepository->>CMSAPI: getAllSubscribedAccounts()
        CMSAPI-->>SubscriptionRepository: accounts[]
        SubscriptionRepository-->>CmsNotificationProducer: accounts[]
        CmsNotificationProducer->>NotificationsRepository: get notifications
        CmsNotificationProducer->>CmsNotificationProducer: filter by accounts[]
        CmsNotificationProducer->>NotificationsRepository: send notifications
    end

    loop Every 30s (TradeNotificationProducer)
        TradeNotificationProducer->>SubscriptionRepository: getAllSubscribedAccounts()
        SubscriptionRepository->>CMSAPI: getAllSubscribedAccounts()
        CMSAPI-->>SubscriptionRepository: accounts[]
        SubscriptionRepository-->>TradeNotificationProducer: accounts[]
        TradeNotificationProducer->>TradeNotificationProducer: (future) fetch and send trade notifications
    end
```

## Poem

> In the warren where code bunnies dwell,  
> A cache of subscriptions now works so well!  
> With paginated hops, accounts we retrieve,  
> For CMS and trades, new producers believe.  
> Notifications filtered, the system feels bright—  
> Rabbits rejoice in the data’s swift flight!  
> 🐇✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cowprotocol/bff&utm_content=120):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: alfetopito
created_at: 2025-04-18T09:04:34Z
body: @coderabbitai review
author_association: COLLABORATOR
user: login: coderabbitai[bot]
created_at: 2025-04-18T09:04:40Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR