Skip to content

feat(reddit): OAuth2 credential and 5 authenticated operations (closes #21)#80

Open
jackulau wants to merge 1 commit into
wespreadjam:mainfrom
jackulau:21
Open

feat(reddit): OAuth2 credential and 5 authenticated operations (closes #21)#80
jackulau wants to merge 1 commit into
wespreadjam:mainfrom
jackulau:21

Conversation

@jackulau

Copy link
Copy Markdown

Closes #21.

Summary

Extends the existing redditMonitorNode (unauthenticated public search.json scraping) with five authenticated Reddit operations and the OAuth2 credential they share. Mirrors the twitter-extended pattern already established in packages/nodes/src/integrations/social/ — one file per operation, one shared *-client.ts auth helper, one consolidated *-extended.test.ts.

Acceptance Criteria

  • OAuth2 credential definition
  • All 5 new operations
  • Zod schemas
  • Unit tests

What's in the diff

Credential

redditCredential defined in packages/nodes/src/integrations/social/credentials.ts via defineOAuth2Credential.

Field Value
name reddit
type oauth2 (non-PKCE)
authorizationUrl https://www.reddit.com/api/v1/authorize
tokenUrl https://www.reddit.com/api/v1/access_token
scopes identity, submit, read, history, mysubreddits
Schema clientId, clientSecret, accessToken, refreshToken?, expiresAt?

NodeCredentials in @jam-nodes/core also gains a typed reddit entry so nodes can read context.credentials.reddit.accessToken without casts — matching the existing twitter / slack / googleSheets entries.

Operations

Each operation is a standalone file under packages/nodes/src/integrations/social/:

Node Endpoint Notes
redditCreatePostNode POST /api/submit kind: 'self' | 'link' | 'image' with .superRefine guard requiring text for self and url for link/image. Form-encoded body via URLSearchParams. Returns postId, postName (t3_… fullname), url, synthesized permalink, title, subreddit.
redditCreateCommentNode POST /api/comment Normalizes thing_id — if caller passes raw postId (e.g. abc123), prepends t3_; if already prefixed (t3_abc123 / t1_abc123), passes through unchanged.
redditReplyToCommentNode POST /api/comment Same pattern with t1_ prefix. Kept as a separate node for API clarity since the target thing_id type differs.
redditSearchPostsNode GET /search or GET /r/<sr>/search Schema defaults: sort='new', time='day', limit=25 (max 100). Sends restrict_sr=on when a subreddit is provided. Returns normalized post list, pagination after token, and resultCount.
redditGetPostCommentsNode GET /comments/<postId> Strips any t3_ prefix from the input id. Iterative BFS flatten of nested comment trees (not recursive — verified safe at 50-level depth in tests). Skips kind: 'more' stubs and gracefully handles replies: "" leaves. Emits parentId and depth per comment.

Shared client (reddit-client.ts)

  • redditRequest<T>(context, path, init) wraps fetchWithRetry (which already handles 429 + Retry-After, 5xx backoff, timeouts, and throws on 401/403) and injects:
    • Authorization: Bearer <accessToken>
    • User-Agent: jam-nodes/1.0 — Reddit rejects unrecognized / default-fetch UAs
  • getRedditAccessToken(context) returns the token or null for the per-node guard clause.
  • formatRedditErrors(errors) flattens Reddit's [code, message, field] error tuples into readable strings, since /api/submit and /api/comment return 200 OK with non-empty json.errors for logical failures.

Architectural alignment

Test plan

Single consolidated file: packages/nodes/src/integrations/social/reddit-extended.test.ts (33 tests, all passing). Uses the vi.stubGlobal('fetch', ...) pattern from twitter-extended.test.ts.

Coverage:

  • Credential metadata: scopes exact match, authorization/token URLs, schema accept/reject.
  • Missing-token guard for each of the 5 nodes.
  • Happy path for each node with a mocked Reddit response shape.
  • Form-encoded body assertions (URL encoding of &, Content-Type header).
  • thing_id normalization: four cases covering raw + prefixed input for both comment nodes.
  • Reddit json.errors non-empty → node returns success: false with the formatted error for all three write nodes.
  • Search-posts: all-subreddits vs restricted, schema defaults, schema rejection of limit > 100, normalized post shape, empty result set.
  • Comment flatten: simple, multi-level with depth, kind: 'more' skipped, empty listing, 50-level deep chain (stack-safety).

Verification

Run from the packages/nodes directory:

npx vitest run src/integrations/social/reddit-extended.test.ts
# 33 tests, 33 pass
npm test                     # full suite: 224/228 pass
                             # (the 4 failures are pre-existing on main —
                             # webhook-trigger x3, apify x1 — unrelated)
  • packages/core npm run typecheck passes.
  • packages/nodes npm run typecheck surfaces the same 18 pre-existing errors as main (apify / google-sheets / slack), zero new from this change.
  • packages/core npm run build passes.

…espreadjam#21)

Extends the existing reddit-monitor (unauthenticated public search) with
authenticated operations for posting, commenting, replying, searching, and
fetching comment trees. Mirrors the twitter-extended pattern established in
the same directory.

New credential:
- redditCredential (OAuth2, scopes: identity, submit, read, history, mysubreddits)

New nodes (all under packages/nodes/src/integrations/social/):
- redditCreatePostNode — self / link / image posts with schema-level kind/url guard
- redditCreateCommentNode — top-level post comments (auto t3_ prefix)
- redditReplyToCommentNode — comment replies (auto t1_ prefix)
- redditSearchPostsNode — query search with subreddit / sort / time / limit
- redditGetPostCommentsNode — iterative flatten of nested comment trees with depth

Shared helper reddit-client.ts wraps fetchWithRetry with Bearer auth and the
required 'jam-nodes/1.0' User-Agent. Reddit's form-encoded /api/submit and
/api/comment endpoints are handled via URLSearchParams; json.errors responses
are surfaced as node failures rather than swallowed.

NodeCredentials in @jam-nodes/core gains a reddit field so nodes can read
context.credentials.reddit.accessToken in a typed way, matching the existing
twitter / slack / googleSheets entries.

33 new vitest cases in reddit-extended.test.ts cover credential metadata, the
missing-token path for each node, happy paths with mocked fetch, schema
validation edge cases, thing_id prefix normalization, nested reply flattening,
"more" stub skipping, and deep-chain stack safety.
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.

[Integration] Reddit - Extended operations (posting, comments)

1 participant