Skip to content

Conversation

@IvanIvanoff
Copy link
Member

@IvanIvanoff IvanIvanoff commented Nov 24, 2025

  • Add schema for sanbase notifications
  • Run migration
  • backup
  • backup

Changes

Ticket

Checklist:

  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • I have tried to find clearer solution before commenting hard-to-understand parts of code
  • I have added tests that prove my fix is effective or that my feature works

Note

Introduces in-app notifications with persistence/read tracking and EventBus subscriber that notifies followers on insight publish and watchlist create/update; adds watchlist visibility timestamp and emits granular update events.

  • Backend – In‑App Notifications
    • Add context Sanbase.AppNotifications with create_notification/1, list_notifications_for_user/2, and mark_notification_as_read/2.
    • New schemas: Sanbase.AppNotifications.Notification and NotificationUserRead (with read_at virtual on Notification when joined).
    • Migrations: create tables sanbase_notifications and sanbase_notification_user_reads with FKs and unique index.
  • Event Bus
    • New subscriber Sanbase.EventBus.AppNotificationsSubscriber; listens to events and inserts notifications for publish_insight, create_watchlist, and update_watchlist (public only), including JSON changed_fields and list item deltas.
    • Register subscriber in Sanbase.EventBus; extend validation to accept :update_watchlist.
  • Watchlists
    • Track user_lists.is_public_updated_at; auto-update when is_public changes.
    • Emit :update_watchlist events with change metadata; include counts when items are added/removed.
    • Improve by_id/2 to support optional preloads; add maybe_preload/2.
  • Accounts/Followers
    • Add Sanbase.Accounts.UserFollower.followed_by_user_ids/1 helper for follower targeting.

Written by Cursor Bugbot for commit 27438ab. This will update automatically on new commits. Configure here.

@tspenov
Copy link
Collaborator

tspenov commented Nov 27, 2025

@cursor review

},
else: %{}
)
|> dbg()
Copy link

Choose a reason for hiding this comment

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

Bug: Debug statements left in production code

Two dbg() calls are left in the create_notification function. These debug statements will output diagnostic information to logs in production, which is unintended and could clutter logs or leak internal data structures.

Additional Locations (1)

Fix in Cursor Fix in Web

#
defp followers_user_ids(user_id) do
[user_id] ++ Sanbase.Accounts.UserFollower.followed_by_user_ids(user_id)
end
Copy link

Choose a reason for hiding this comment

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

Bug: Notifications sent to wrong users due to inverted query

The followers_user_ids/1 function calls followed_by_user_ids(user_id) which returns users that the author follows, not users who follow the author. When an author publishes an insight or creates a watchlist, notifications should go to the author's followers. The correct function to use would be one that queries where uf.user_id == ^user_id (like the existing followers_of/1), not where uf.follower_id == ^user_id.

Fix in Cursor Fix in Web


defp maybe_emit_event({:ok, watchlist}, :update_watchlist, data) do
emit_event({:ok, watchlist}, :update_watchlist, data)
end
Copy link

Choose a reason for hiding this comment

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

Bug: Missing error clause in maybe_emit_event causes crash

The maybe_emit_event/3 function only handles the {:ok, watchlist} pattern. When called after by_id/2 in remove_user_list_items/2 and add_user_list_items/2, if by_id returns an error tuple, the function will crash with a FunctionClauseError. The existing maybe_create_event/3 follows the pattern of including a fallback clause defp maybe_create_event(error_result, _, _), do: error_result which is missing here.

Fix in Cursor Fix in Web

@IvanIvanoff IvanIvanoff force-pushed the notifications-system branch from 27438ab to c51d6c3 Compare January 5, 2026 09:40
@type t :: %__MODULE__{
id: pos_integer(),
type: String.t(),
title: String.t(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
title: String.t(),
title: String.t() | nil,

Copy link
Collaborator

Choose a reason for hiding this comment

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

since it is in @optional_fields

@type t :: %__MODULE__{
id: pos_integer(),
type: String.t(),
title: String.t(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

since it is in @optional_fields

field(:is_deleted, :boolean, default: false)

# Virtual field populated when we join the user reads
field(:read_at, :utc_datetime, virtual: true)
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should probably add to the type definition as well

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.

3 participants