Skip to content

feat(api): notifications/activities wiring and guest auth (4/4) - #437

Merged
motirebuma merged 2 commits into
presence-persistfrom
api-wiring
Jul 24, 2026
Merged

feat(api): notifications/activities wiring and guest auth (4/4)#437
motirebuma merged 2 commits into
presence-persistfrom
api-wiring

Conversation

@niamao

@niamao niamao commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Stack

Part 4 of 4 — merge last, after Part 3 (#436 / presence-persist).

Order Branch PR
1 profile-bio #434
2 avatar-consistency #435
3 presence-persist #436
4 api-wiring this PR (#437; base = Part 3)

After Part 3 merges, retarget this PR’s base to main (or rebase onto main).

Related issue: #362

Summary

  • Register notificationResolver and activityResolver in the GraphQL server
  • Guest / Apollo UNAUTHENTICATED handling and related auth URL helpers

Test plan

  • Notifications query works when authenticated
  • Activities query works when authenticated
  • Guest session does not hard-crash on protected GraphQL ops

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quotevote Ready Ready Preview, Comment Jul 24, 2026 9:38am

@motirebuma motirebuma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hi @niamao, looks good. Resolvers are simple and well-tested.

What's good

  1. activityResolver - properly paginated query that supports search, date filtering, activity type filtering + "activity" following feed fallback if user_id is empty. normalizeActivityEvents parser handles both cases when activityEvent comes as JSON string (query variables) or array. toActivityEntity properly normalizes DB ObjectIds to strings.

  2. notificationResolver - simple and correct, queries status: 'new' and sorts by created: -1, properly normalizes IDs.

  3. Schema wiring - both resolvers are attached in server.ts, SDL queries are properly defined. activities query accepts all the pagination/filter arguments, resolves to expected object. notifications query returns [Notification!]!, which is a list of non-null Notification objects.

  4. Tests - activity resolver is tested for auth, pagination, filtering, and fallback. Notification resolver is tested for auth and empty state, both using the same mockContext pattern as before.

Issues

  1. activityEvent is a JSON scalar in SDL - the activities query defines activityEvent: JSON. While it's handled correctly by the normalizeActivityEvents parser, it's not ideal for a couple of reasons:

GraphQL scalar JSON is just a string, so this field could technically accept any string, including non-array values, and it'd be handled as "any JSON". It's better to specify this field as either [String!] (array of strings, each representing an event type) or create an ActivityEventType enum if there's a finite list of event types. With this design, activityEvent field in SDL can be changed to [String!] and the query parser will still work.

  1. notificationResolver lacks pagination. As written, it'll return all the notifications with status new, ordered by created DESC. If someone has thousands of notifications, this could become a problem. Perhaps add a limit argument to the query. Even though it's not used in the UI, it'll help with performance.

  2. toActivityEntity assumes that doc.userId is present. userId: toId(doc.userId) ?? '' will silently swallow any cases when doc.userId was unexpectedly absent. It's better to fail loudly, or at the very least, log this condition, since user ID is always supposed to be present in activity documents.

Nits

  • normalizeActivityEvents swallows any errors that may occur when parsing JSON string, which is correct and expected, but it may be worth logging in case of unexpected issues on the client side.

  • The line Activity.find(searchArgs).sort({ created: -1 }).skip(offset).limit(limit).lean() is correct, but if it's not already indexed, adding an index on userId and created fields (as a descending array) would improve performance.

thank you @niamao

@flyblackbox @niamao

@niamao
niamao force-pushed the presence-persist branch from 2ddf6ad to bb84069 Compare July 24, 2026 08:57
Register notification and activity resolvers, and improve guest-session
handling in Apollo/auth utilities so unauthenticated flows fail cleanly.

Co-authored-by: Cursor <[email protected]>
Use ActivityEventType[] instead of JSON for activityEvent, fail loudly on
missing activity userId, paginate notifications with a capped limit, and
add a userId+created index for the activity feed.

Co-authored-by: Cursor <[email protected]>
@niamao

niamao commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review suggestions:

  1. activityEvent typing — switched SDL from JSON to [ActivityEventType!] (enum already existed) and updated the client query accordingly. normalizeActivityEvents still accepts a legacy JSON string for safety, logs on parse / non-array input, and filters to known event types.

  2. notifications pagination — added optional limit (default 50, max 100) so unread notification reads stay bounded.

  3. toActivityEntity / missing userId — now throws INTERNAL_SERVER_ERROR instead of silently coercing to ''.

  4. Activity index — added { userId: 1, created: -1 } for the paginated feed sort/filter path.

@motirebuma @flyblackbox

@motirebuma motirebuma left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hi @niamao, all review points addressed. Good job all around.

Previous issues

All addressed.

  1. activityEvent type was too loose - FIXED. Changed from activityEvent: JSON to activityEvent: [ActivityEventType!] - normalizeActivityEvents parses string as ActivityEventType array. Invalid event strings are filtered out (logger.warn). JSON parse errors are not silenced - function exported and has tests for arrays, legacy strings, and filtering of unknown event types.

  2. Notifications query lacked pagination - FIXED. Added limit: Int to the notifications query. Default limit is 50, max is 100 (see DEFAULT_NOTIFICATION_LIMIT and MAX_NOTIFICATION_LIMIT constants). Input is sanitized (Math.floor, clamped to [1, MAX] etc.). The resolver’s parameter type is changed to { limit?: number | null }.

  3. toActivityEntity was swallowing errors by defaulting to empty string - FIXED. Now throws GraphQLError with message missing required userId. Has a test case for it ('rejects activities missing userId').

What's good

Same as in the previous reviews:

  • activityResolver: pagination + search + dates + activity type filter + activity feed for followed users

  • notificationResolver: unread count query is simple and does the right thing

  • Schema wiring in the server.ts is good (has SDL) - no issues found

  • Test coverage for both resolvers is good - used the same mockContext pattern as in the existing code

Thank you @niamao

@flyblackbox @niamao

@motirebuma
motirebuma merged commit 960e75f into presence-persist Jul 24, 2026
2 checks passed
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.

2 participants