Skip to content

feat: Product Follow Contract Service#342

Open
Ai-chan-0411 wants to merge 4 commits intoStarShopCr:mainfrom
Ai-chan-0411:feat/product-follow-service-286
Open

feat: Product Follow Contract Service#342
Ai-chan-0411 wants to merge 4 commits intoStarShopCr:mainfrom
Ai-chan-0411:feat/product-follow-service-286

Conversation

@Ai-chan-0411
Copy link
Copy Markdown

@Ai-chan-0411 Ai-chan-0411 commented Apr 10, 2026

Closes #286

Summary

Implements a comprehensive TypeScript service layer for the Product Follow Contract that manages product following, notifications, alerts, and user preferences within the StarShop marketplace.

Structure

src/shared/services/product_follow/
  follow.service.ts                // Main service class (ProductFollowService)
  index.ts                         // Module exports & convenience functions
  types/
    follow.types.ts               // Core follow TypeScript interfaces
    notification.types.ts         // Notification types
    alert.types.ts                // Alert types
  utils/
    follow.utils.ts               // Validation & helper functions
  constants/
    follow.constants.ts           // Contract methods, error codes, defaults
  README.md                       // Documentation

Implemented Features

1. Follow Management

  • followProduct(productId, user) - Follow a product
  • unfollowProduct(productId, user) - Unfollow a product
  • getFollowers(productId) - Get product followers (paginated)
  • getFollowing(user) - Get user's followed products (paginated)
  • isFollowing(productId, user) - Check follow status

2. Notification Management

  • setNotificationPreferences(user, preferences) - Set notification preferences
  • getNotificationPreferences(user) - Get notification preferences
  • sendNotification(productId, type, data) - Send notification to followers
  • getNotificationHistory(query) - Get notification history (paginated)

3. Alert Management

  • createAlert(user, request) - Create price/stock/custom alerts
  • updateAlert(user, request) - Update alert conditions
  • deleteAlert(user, alertId) - Delete an alert
  • getAlerts(query) - Get user alerts (paginated)
  • triggerAlert(alertId, values) - Check alert conditions against current values

4. Rate Limiting & Validation

  • Per-user rate limiting for follow/unfollow/notification actions
  • Stellar address validation
  • Product ID format validation
  • Notification format validation
  • Alert condition validation

Additional Features

  • In-memory caching with configurable TTL and max size
  • Event system for follow state changes (FOLLOWED/UNFOLLOWED/FOLLOWERS_UPDATED)
  • Follows existing service patterns (subscription_contract, referral_contract, etc.)
  • Branded types for type safety (ProductId, UserAddress, FollowId)

/attempt 286

Summary by CodeRabbit

Release Notes

  • New Features
    • Added product follow service enabling users to follow/unfollow products, view followers and following lists, manage notification preferences, receive notifications about followed products, and create/manage alerts (price thresholds, stock updates, version releases).
    • Includes built-in rate limiting and input validation for addresses, products, and alert conditions.
    • Supports both testnet and mainnet networks with factory functions for convenient initialization.

Implements comprehensive TypeScript service layer for Product Follow Contract:
- Follow management (follow/unfollow/getFollowers/getFollowing/isFollowing)
- Notification management with preferences and history
- Alert system for price/stock conditions
- Rate limiting and input validation
- In-memory caching with TTL
- Event system for follow state changes
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Warning

Rate limit exceeded

@aoi-dev-0411 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 6 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 6 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 936ddf1e-1a53-435c-aad8-829c1ec5772c

📥 Commits

Reviewing files that changed from the base of the PR and between 4060f6f and 52f9a49.

📒 Files selected for processing (4)
  • src/shared/services/product_follow/constants/follow.constants.ts
  • src/shared/services/product_follow/follow.service.ts
  • src/shared/services/product_follow/index.ts
  • src/shared/services/product_follow/utils/follow.utils.ts
📝 Walkthrough

Walkthrough

A new Product Follow Contract Service layer is introduced with comprehensive TypeScript implementation for managing product following, notifications, and alerts. The service includes follow/unfollow operations, follower/following queries, notification preferences and delivery, alert creation and evaluation, plus supporting infrastructure for rate limiting, caching, validation, and event handling.

Changes

Cohort / File(s) Summary
Documentation
src/shared/services/product_follow/README.md
Service overview describing folder structure, three core domains (follow, notification, alert management), rate limiting and validation features, and TypeScript usage example.
Type Definitions
src/shared/services/product_follow/types/follow.types.ts, notification.types.ts, alert.types.ts
Core domain interfaces and enums for product follows, notifications with channels/preferences, and alerts with conditions/operators/trigger outcomes. Includes configuration, response wrapper, pagination, and event types.
Configuration & Constants
src/shared/services/product_follow/constants/follow.constants.ts
Network configurations (testnet/mainnet), operational defaults (timeout, fees, rate limits, caching), contract method names, validation rules (regex/limits), error message mappings, default notification preferences, and cache key generators.
Utilities
src/shared/services/product_follow/utils/follow.utils.ts
Validation helpers for addresses/products/notifications/preferences/alerts, condition evaluation logic, ID generators, error message lookup, cache expiry checks, and retry-with-backoff utility.
Service Implementation
src/shared/services/product_follow/follow.service.ts
ProductFollowService class with follow/unfollow, follower/following queries, notification management, alert lifecycle, rate limiting (sliding window), event subscriptions, in-memory caching with TTL/size eviction, and response/error handling.
Public API & Factory Functions
src/shared/services/product_follow/index.ts
Module re-exports ProductFollowService, all types and constants, and provides factory functions createProductFollowService(), createTestnetFollowService(), and createMainnetFollowService() for instantiation.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Service as ProductFollowService
    participant RateLimit as RateLimiter
    participant Cache
    participant Events as Event System
    
    Client->>Service: followProduct(productId, userAddress)
    Service->>RateLimit: checkRateLimit(userAddress, 'follow')
    RateLimit-->>Service: allowed/denied
    
    alt Rate limit exceeded
        Service-->>Client: error response
    else Rate limit OK
        Service->>Cache: check isFollowing cache
        Cache-->>Service: cache result or miss
        Service->>Service: validate inputs
        Service->>Service: create ProductFollow object
        Service->>Cache: invalidate follower/following caches
        Service->>Events: emit('follow', event)
        Events-->>Service: event processed
        Service-->>Client: FollowResponse<ProductFollow>
    end
Loading
sequenceDiagram
    participant Client
    participant Service as ProductFollowService
    participant Cache
    participant Validation as Validators
    
    Client->>Service: sendNotification(productId, type, data)
    Service->>Validation: validateNotificationFormat(data)
    Validation-->>Service: validation result
    
    alt Validation fails
        Service-->>Client: error response
    else Validation passes
        Service->>Service: generate notification ID
        Service->>Service: create FollowNotification object
        Service->>Cache: store notification
        Service->>Cache: invalidate notification history cache
        Service-->>Client: FollowResponse<FollowNotification>
    end
Loading
sequenceDiagram
    participant Client
    participant Service as ProductFollowService
    participant Cache
    participant Evaluator as Condition Evaluator
    
    Client->>Service: triggerAlert(alertId, currentValues)
    Service->>Cache: retrieve alert from storage
    Cache-->>Service: FollowAlert object
    
    Service->>Evaluator: evaluate all conditions
    loop For each condition
        Evaluator->>Evaluator: compare currentValue vs condition value
        Evaluator-->>Service: condition result
    end
    
    Service->>Service: build AlertTriggerResult
    Service->>Cache: update alert isTriggered flag
    Service-->>Client: FollowResponse<AlertTriggerResult>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • aguilar1x

Poem

🐰 A service hops into town, following products with glee,
Notifying and alerting, as swift as can be,
With caches that spring and rate limits that bind,
This follow service leaves all bugs far behind! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: implementing a comprehensive Product Follow Contract Service for the TypeScript service layer.
Description check ✅ Passed The PR description is well-structured, providing a clear summary, file structure, and comprehensive list of all implemented features matching the requirements from issue #286.
Linked Issues check ✅ Passed All functional requirements from issue #286 are implemented: follow management (5 methods), notification management (4 methods), alert management (5 methods), and rate limiting/validation utilities (checkRateLimit, validation helpers, per-user rate limiting).
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #286 requirements: service structure, types, utils, constants, and documentation follow the specified scope without extraneous modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (9)
src/shared/services/product_follow/types/follow.types.ts (1)

118-118: Consider using unknown instead of any for the default type parameter.

ESLint flags the any type. Using unknown as the default provides stricter type safety while maintaining flexibility.

♻️ Suggested fix
-export interface FollowResponse<T = any> {
+export interface FollowResponse<T = unknown> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/types/follow.types.ts` at line 118, Change
the default generic type for FollowResponse from any to unknown to improve type
safety: update the interface declaration FollowResponse<T = any> to use T =
unknown and adjust any downstream usages that rely on implicit any (e.g., places
constructing FollowResponse without a type arg) to either assert the correct
type parameter or handle T as unknown; ensure related utility types or function
signatures that expect FollowResponse use explicit type parameters or proper
narrowing where necessary.
src/shared/services/product_follow/follow.service.ts (5)

143-143: Remove unused publicKey variable.

The variable is assigned but never used.

🧹 Suggested fix
-      const publicKey = await getPublicKey();
       const follow: ProductFollow = {

If getPublicKey() is needed for authorization or the follow record, use it; otherwise remove the call entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/follow.service.ts` at line 143, The
assignment to the local variable publicKey (from calling getPublicKey()) is
unused; either remove the call and the publicKey variable entirely or, if
getPublicKey() is required for auth or to populate the follow record, wire the
returned value into the relevant logic (e.g., pass it to the function that
creates/saves the follow or include it in the payload). Locate the usage in
follow.service.ts where const publicKey = await getPublicKey() appears and
either delete that line or replace it by using the publicKey in the appropriate
method/field so the value is consumed.

88-88: Replace any with specific type in cache Map.

The any type reduces type safety. Use a union or generic constraint.

♻️ Suggested fix
-  private cache: Map<string, CacheEntry<any>> = new Map();
+  private cache: Map<string, CacheEntry<unknown>> = new Map();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/follow.service.ts` at line 88, The cache
Map currently uses CacheEntry<any> which weakens type safety; update the
declaration of private cache to use a concrete generic type (e.g.,
CacheEntry<FollowInfo> or a union like CacheEntry<FollowCount | FollowDetails>)
that matches the actual values stored, and adjust any usages of cache.get/set
and CacheEntry<T> consumers in FollowService to use that concrete type;
reference the private cache property and the CacheEntry<T> generic in
follow.service.ts and update related methods (e.g., wherever cache.set,
cache.get, or cache entries are created) so the compiler enforces correct value
shapes.

697-705: FIFO eviction may not be optimal for cache.

Using this.cache.keys().next().value evicts the first-inserted entry (FIFO), but LRU (least-recently-used) would better retain frequently accessed data. For a simple in-memory cache this is acceptable, but consider if access patterns warrant LRU.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/follow.service.ts` around lines 697 - 705,
The current setCache method uses FIFO eviction by deleting
this.cache.keys().next().value; change to LRU by ensuring accesses update
insertion order and newly-set keys move to the end: in setCache(key, ...) if the
key already exists, delete it first then set it so it becomes
most-recently-used; when reading the cache (e.g., getCache or any lookup
methods), upon a hit delete the key and re-set it with the same value to mark it
as recently used; keep the eviction logic to remove
this.cache.keys().next().value when size >= maxSize so the oldest
(least-recently-used) entry is removed. Ensure you update both setCache and the
corresponding cache read method(s) (getCache/lookup functions) to implement the
LRU behavior.

277-295: Cache returns null for false values.

In isFollowing, cached false values are indistinguishable from cache misses because getFromCache returns null for missing entries, but the check if (cached !== null) won't catch cached false since false !== null is true. However, if (cached) would skip false. The current code is correct but brittle.

Consider returning a wrapper object or using a sentinel value to distinguish "not cached" from "cached as false":

const cached = this.getFromCache<boolean>(cacheKey);
if (cached !== null) return this.successResponse(cached);

This works correctly since false !== null. Just noting for awareness during future maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/follow.service.ts` around lines 277 - 295,
The cache ambiguity can bite future maintainers; change getFromCache to return a
discriminated CacheResult<T> (e.g., { found: boolean; value?: T }) and update
isFollowing to call getFromCache<boolean>(cacheKey) and branch on cached.found —
return successResponse(cached.value as boolean) when found, otherwise continue
to contract call and setCache; update callers like isFollowing to use the new
CacheResult shape and adjust setCache usage accordingly (refer to isFollowing,
getFromCache, setCache, and CACHE_KEYS.IS_FOLLOWING).

1-65: Clean up unused imports.

ESLint correctly identifies numerous unused imports. Remove them to improve code clarity and prevent confusion about which features are actually implemented.

🧹 Suggested cleanup
 import {
-  signTransaction,
   getPublicKey,
   isWalletConnected
 } from '../../utils/wallet';
 import {
-  NETWORKS,
   DEFAULT_CONFIG,
-  CONTRACT_METHODS,
   CACHE_KEYS,
   VALIDATION,
-  ERROR_MESSAGES,
   DEFAULT_NOTIFICATION_PREFERENCES
 } from './constants/follow.constants';
 import {
   validateAddress,
   validateProductId,
   validateNotificationFormat,
   validateUserPreferences,
   validateAlertConditions,
-  evaluateCondition,
   generateFollowId,
   generateAlertId,
   generateNotificationId,
   getErrorMessage,
-  isCacheExpired,
-  retryWithBackoff
+  isCacheExpired
 } from './utils/follow.utils';
 import {
   FollowServiceConfig,
-  FollowNetworkConfig,
   FollowResponse,
-  FollowTransactionResult,
   ProductFollow,
-  FollowStatus,
   Follower,
   FollowedProduct,
   FollowErrorCode,
   FollowEventType,
   FollowEventData,
   FollowEventListener,
   EventSubscription,
-  PaginationParams,
-  PaginatedResponse,
-  RateLimitConfig,
-  CacheConfig
+  PaginationParams,
+  PaginatedResponse
 } from './types/follow.types';
 import {
   FollowNotification,
   NotificationType,
   NotificationPreferences,
   NotificationTypePreference,
-  NotificationChannel,
-  SendNotificationRequest,
   NotificationHistoryQuery
 } from './types/notification.types';
 import {
   FollowAlert,
-  AlertType,
   AlertCondition,
   CreateAlertRequest,
   UpdateAlertRequest,
   AlertTriggerResult,
   AlertQuery
 } from './types/alert.types';

Note: Keep evaluateCondition and retryWithBackoff if you plan to implement the pending functionality soon.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/follow.service.ts` around lines 1 - 65,
Remove the unused imports flagged by ESLint in follow.service.ts to clean up the
top-of-file import lists; specifically drop unused symbols from the constants
group (e.g., NETWORKS, DEFAULT_CONFIG, CONTRACT_METHODS, CACHE_KEYS, VALIDATION,
ERROR_MESSAGES, DEFAULT_NOTIFICATION_PREFERENCES) and from the utils group
(e.g., validateAddress, validateProductId, validateNotificationFormat,
validateUserPreferences, validateAlertConditions, generateFollowId,
generateAlertId, generateNotificationId, getErrorMessage, isCacheExpired), but
retain evaluateCondition and retryWithBackoff as noted; ensure the types and
notification/alert type imports that are actually referenced by the file (check
FollowServiceConfig, FollowResponse, FollowTransactionResult, ProductFollow,
FollowStatus, Follower, FollowedProduct, FollowErrorCode, FollowEventType,
FollowEventData, FollowEventListener, EventSubscription, PaginationParams,
PaginatedResponse, RateLimitConfig, CacheConfig, FollowNotification,
NotificationType, NotificationPreferences, NotificationTypePreference,
NotificationChannel, SendNotificationRequest, NotificationHistoryQuery,
FollowAlert, AlertType, AlertCondition, CreateAlertRequest, UpdateAlertRequest,
AlertTriggerResult, AlertQuery) remain, and run ESLint to verify no remaining
unused imports.
src/shared/services/product_follow/README.md (1)

7-19: Add a language specifier to the fenced code block.

The directory structure code block is missing a language specifier. Use text or plaintext to satisfy linting and improve rendering consistency.

📝 Suggested fix
-```
+```text
 src/shared/services/product_follow/
   follow.service.ts                // Main service class
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/README.md` around lines 7 - 19, Update the
fenced code block in src/shared/services/product_follow/README.md to include a
language specifier (e.g., "text" or "plaintext") after the opening triple
backticks so the directory tree renders and lints correctly; locate the
triple-backtick block that contains the directory listing (the lines showing
follow.service.ts, index.ts, types/, utils/, constants/) and change the opening
``` to ```text (or ```plaintext).
src/shared/services/product_follow/constants/follow.constants.ts (1)

3-3: Remove unused import.

AlertType is imported but never used in this file.

🧹 Suggested fix
 import { FollowErrorCode } from '../types/follow.types';
 import { NotificationType, NotificationChannel } from '../types/notification.types';
-import { AlertType } from '../types/alert.types';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/constants/follow.constants.ts` at line 3,
Remove the unused import AlertType from follow.constants.ts: locate the import
statement "import { AlertType } from '../types/alert.types';" in the module and
delete it so there are no unused imports; ensure no other references to
AlertType remain in functions/constants in this file (e.g., any constants
exported from follow.constants.ts) before committing.
src/shared/services/product_follow/types/alert.types.ts (1)

1-1: Remove unused u32 import.

Only u64 is used in this file for timestamp fields.

🧹 Suggested fix
-import type { u32, u64 } from '@stellar/stellar-sdk';
+import type { u64 } from '@stellar/stellar-sdk';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/services/product_follow/types/alert.types.ts` at line 1, Remove
the unused import u32 from the import statement that currently brings in both
u32 and u64 from '@stellar/stellar-sdk'; keep only u64 since timestamp fields
use u64 (remove the u32 symbol from the import to eliminate the unused import
warning).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/shared/services/product_follow/constants/follow.constants.ts`:
- Around line 14-19: The mainnet rpcUrl in the exported mainnet config object
(property name "mainnet" and key "rpcUrl" in follow.constants.ts) currently
points to Horizon and must be replaced with a Soroban-compatible RPC provider;
update the rpcUrl value to a verified Soroban endpoint (for example
https://rpc.ankr.com/stellar_soroban or
https://soroban-rpc.mainnet.stellar.gateway.fm/ or another production-grade
provider) and ensure the isTestnet flag remains false; after swapping the URL,
verify any integration tests or calls that use the mainnet.rpcUrl still function
and consider documenting provider choice and rate-limit/reliability
considerations for production.

In `@src/shared/services/product_follow/follow.service.ts`:
- Around line 137-141: The current check uses if (isAlreadyFollowing.data) which
treats both false and undefined as falsy and can miss errors from isFollowing;
update the logic to explicitly handle an undefined result first (e.g., if
(isAlreadyFollowing.data === undefined) return this.errorResponse(/* appropriate
error code or propagate isAlreadyFollowing.error */)), then only treat a true
value as already-following (if (isAlreadyFollowing.data === true) return
this.errorResponse(FollowErrorCode.ALREADY_FOLLOWING)); reference the
isFollowing call and the existing errorResponse/FollowErrorCode usage when
implementing this.
- Around line 516-518: The updateAlert flow currently invalidates cache then
returns an unsafe empty object cast to FollowAlert; instead, have updateAlert
(the method containing this.invalidateCache and return this.successResponse)
return a valid FollowAlert instance or explicitly error: fetch or construct the
updated alert (e.g., reload from storage or use the updated fields) and pass
that object to this.successResponse, or throw a clear error/NotImplemented
exception if the actual update/return is not implemented; ensure references to
CACHE_KEYS.ALERTS(userAddress), FollowAlert, and successResponse are used so
consumers receive a complete alert with required properties like alertId and
conditions.
- Around line 582-603: triggerAlert currently ignores currentValues and never
calls evaluateCondition, so matchedConditions stays empty; modify triggerAlert
to fetch the alert's conditions (via the existing alert lookup method on this,
e.g., this.getAlertById or this.getAlertConditions), iterate over those
AlertCondition items and call evaluateCondition(condition, currentValues) for
each, push any satisfied conditions into matchedConditions, then set
result.triggered = matchedConditions.length > 0 and set checkedAt = Date.now()
(as a number) before returning this.successResponse(result); keep the existing
errorResponse handling unchanged.
- Line 148: The code is illegally coercing BigInt to number for u64 fields
(e.g., the createdAt assignments in follow.service.ts) which bypasses the
u64/UnsignedHyper contract; locate the nine occurrences (createdAt and other
timestamp fields around lines noted) and replace the double-cast pattern
(BigInt(Date.now()) as unknown as number) by either preserving a BigInt (use
BigInt(Date.now())) if your downstream accepts bigint, or by constructing the
Stellar SDK UnsignedHyper explicitly (new UnsignedHyper(Number(Date.now())) or
the appropriate UnsignedHyper constructor) so the value conforms to the u64
type; ensure imports reference UnsignedHyper from the Stellar SDK and update any
call sites that expect a primitive number accordingly.

In `@src/shared/services/product_follow/index.ts`:
- Around line 76-99: The factory functions createTestnetFollowService and
createMainnetFollowService duplicate network configuration; replace the
hardcoded network objects with the corresponding entries from the NETWORKS
constant (imported from follow.constants.ts) when constructing new
ProductFollowService instances so they read e.g. new ProductFollowService({
network: NETWORKS.TESTNET }) and new ProductFollowService({ network:
NETWORKS.MAINNET }); ensure NETWORKS is imported and used for contractId,
networkPassphrase, rpcUrl, and isTestnet to keep configuration DRY.

In `@src/shared/services/product_follow/utils/follow.utils.ts`:
- Around line 181-183: generateFollowId currently builds IDs from productId,
userAddress and Date.now(), which can collide on rapid consecutive calls; update
the generateFollowId function to append a secure random component (e.g., a UUID
v4 or hex from crypto.randomBytes) to the template so the ID becomes globally
unique, and ensure the implementation mirrors the randomness approach used by
generateAlertId/generateNotificationId to avoid collisions.

---

Nitpick comments:
In `@src/shared/services/product_follow/constants/follow.constants.ts`:
- Line 3: Remove the unused import AlertType from follow.constants.ts: locate
the import statement "import { AlertType } from '../types/alert.types';" in the
module and delete it so there are no unused imports; ensure no other references
to AlertType remain in functions/constants in this file (e.g., any constants
exported from follow.constants.ts) before committing.

In `@src/shared/services/product_follow/follow.service.ts`:
- Line 143: The assignment to the local variable publicKey (from calling
getPublicKey()) is unused; either remove the call and the publicKey variable
entirely or, if getPublicKey() is required for auth or to populate the follow
record, wire the returned value into the relevant logic (e.g., pass it to the
function that creates/saves the follow or include it in the payload). Locate the
usage in follow.service.ts where const publicKey = await getPublicKey() appears
and either delete that line or replace it by using the publicKey in the
appropriate method/field so the value is consumed.
- Line 88: The cache Map currently uses CacheEntry<any> which weakens type
safety; update the declaration of private cache to use a concrete generic type
(e.g., CacheEntry<FollowInfo> or a union like CacheEntry<FollowCount |
FollowDetails>) that matches the actual values stored, and adjust any usages of
cache.get/set and CacheEntry<T> consumers in FollowService to use that concrete
type; reference the private cache property and the CacheEntry<T> generic in
follow.service.ts and update related methods (e.g., wherever cache.set,
cache.get, or cache entries are created) so the compiler enforces correct value
shapes.
- Around line 697-705: The current setCache method uses FIFO eviction by
deleting this.cache.keys().next().value; change to LRU by ensuring accesses
update insertion order and newly-set keys move to the end: in setCache(key, ...)
if the key already exists, delete it first then set it so it becomes
most-recently-used; when reading the cache (e.g., getCache or any lookup
methods), upon a hit delete the key and re-set it with the same value to mark it
as recently used; keep the eviction logic to remove
this.cache.keys().next().value when size >= maxSize so the oldest
(least-recently-used) entry is removed. Ensure you update both setCache and the
corresponding cache read method(s) (getCache/lookup functions) to implement the
LRU behavior.
- Around line 277-295: The cache ambiguity can bite future maintainers; change
getFromCache to return a discriminated CacheResult<T> (e.g., { found: boolean;
value?: T }) and update isFollowing to call getFromCache<boolean>(cacheKey) and
branch on cached.found — return successResponse(cached.value as boolean) when
found, otherwise continue to contract call and setCache; update callers like
isFollowing to use the new CacheResult shape and adjust setCache usage
accordingly (refer to isFollowing, getFromCache, setCache, and
CACHE_KEYS.IS_FOLLOWING).
- Around line 1-65: Remove the unused imports flagged by ESLint in
follow.service.ts to clean up the top-of-file import lists; specifically drop
unused symbols from the constants group (e.g., NETWORKS, DEFAULT_CONFIG,
CONTRACT_METHODS, CACHE_KEYS, VALIDATION, ERROR_MESSAGES,
DEFAULT_NOTIFICATION_PREFERENCES) and from the utils group (e.g.,
validateAddress, validateProductId, validateNotificationFormat,
validateUserPreferences, validateAlertConditions, generateFollowId,
generateAlertId, generateNotificationId, getErrorMessage, isCacheExpired), but
retain evaluateCondition and retryWithBackoff as noted; ensure the types and
notification/alert type imports that are actually referenced by the file (check
FollowServiceConfig, FollowResponse, FollowTransactionResult, ProductFollow,
FollowStatus, Follower, FollowedProduct, FollowErrorCode, FollowEventType,
FollowEventData, FollowEventListener, EventSubscription, PaginationParams,
PaginatedResponse, RateLimitConfig, CacheConfig, FollowNotification,
NotificationType, NotificationPreferences, NotificationTypePreference,
NotificationChannel, SendNotificationRequest, NotificationHistoryQuery,
FollowAlert, AlertType, AlertCondition, CreateAlertRequest, UpdateAlertRequest,
AlertTriggerResult, AlertQuery) remain, and run ESLint to verify no remaining
unused imports.

In `@src/shared/services/product_follow/README.md`:
- Around line 7-19: Update the fenced code block in
src/shared/services/product_follow/README.md to include a language specifier
(e.g., "text" or "plaintext") after the opening triple backticks so the
directory tree renders and lints correctly; locate the triple-backtick block
that contains the directory listing (the lines showing follow.service.ts,
index.ts, types/, utils/, constants/) and change the opening ``` to ```text (or
```plaintext).

In `@src/shared/services/product_follow/types/alert.types.ts`:
- Line 1: Remove the unused import u32 from the import statement that currently
brings in both u32 and u64 from '@stellar/stellar-sdk'; keep only u64 since
timestamp fields use u64 (remove the u32 symbol from the import to eliminate the
unused import warning).

In `@src/shared/services/product_follow/types/follow.types.ts`:
- Line 118: Change the default generic type for FollowResponse from any to
unknown to improve type safety: update the interface declaration
FollowResponse<T = any> to use T = unknown and adjust any downstream usages that
rely on implicit any (e.g., places constructing FollowResponse without a type
arg) to either assert the correct type parameter or handle T as unknown; ensure
related utility types or function signatures that expect FollowResponse use
explicit type parameters or proper narrowing where necessary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a3ced788-f68c-49df-9154-9753a9ae66c0

📥 Commits

Reviewing files that changed from the base of the PR and between caf26b1 and 4060f6f.

📒 Files selected for processing (8)
  • src/shared/services/product_follow/README.md
  • src/shared/services/product_follow/constants/follow.constants.ts
  • src/shared/services/product_follow/follow.service.ts
  • src/shared/services/product_follow/index.ts
  • src/shared/services/product_follow/types/alert.types.ts
  • src/shared/services/product_follow/types/follow.types.ts
  • src/shared/services/product_follow/types/notification.types.ts
  • src/shared/services/product_follow/utils/follow.utils.ts

aoi-dev-0411 added 2 commits April 11, 2026 06:23
- Fix mainnet RPC URL: use soroban-rpc.stellar.org instead of horizon
- Fix isFollowing false negative: check success before data
- Remove unsafe BigInt casts: use Date.now() directly
- Fix updateAlert: return actual updated alert instead of empty object
- Fix triggerAlert: evaluate conditions using evaluateCondition utility
- Fix generateFollowId: add random suffix to prevent collisions
- Fix factory functions: use NETWORKS constant instead of duplicating config
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Service] Product Follow Contract Service for StarShop Frontend

1 participant