Skip to content

Add KLIPY GIF proxy, routes and tests#30

Merged
danieldaquino merged 1 commit into
damus-io:masterfrom
danieldaquino:gh-3695
May 4, 2026
Merged

Add KLIPY GIF proxy, routes and tests#30
danieldaquino merged 1 commit into
damus-io:masterfrom
danieldaquino:gh-3695

Conversation

@danieldaquino
Copy link
Copy Markdown
Collaborator

@danieldaquino danieldaquino commented May 3, 2026

Introduce a server-side proxy for KLIPY GIF search and featured endpoints,
add authenticated routes that require an active Purple subscription, and extend the test client/controller and router tests.

Requires KLIPY_APP_KEY to be set (KLIPY_API_BASE_URL is optional).

Testing

Summary by CodeRabbit

  • New Features

    • Added GIF search and featured GIF endpoints with pagination, filters, and curated results.
    • Endpoints require active account authentication and perform input validation; they return clear error responses for invalid requests or missing authorization.
  • Tests

    • Added end-to-end tests and test client helpers covering successful proxying, authentication checks, and negative validation/error cases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

@danieldaquino has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 36 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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: e6bda721-3e19-443c-b814-51b7ab8d4c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 89f7e2d and 7a303fa.

📒 Files selected for processing (5)
  • src/klipy_proxy.js
  • src/router_config.js
  • test/controllers/purple_test_client.js
  • test/controllers/purple_test_controller.js
  • test/router_config.test.js
📝 Walkthrough

Walkthrough

Adds KLIPY GIF proxy handlers and routes that validate requests, require NIP-98 auth and an active Purple account, forward queries to KLIPY upstream, and include tests and test-client helpers; environment variables for KLIPY are set in the test harness.

Changes

KLIPY GIF Proxy Integration

Layer / File(s) Summary
Proxy Constants & Helpers
src/klipy_proxy.js
Introduces default/allowed filters, timeout, parse_integer_query_param, append_if_present, get_klipy_base_url, and get_klipy_app_key (errors when missing).
Core Proxy Logic
src/klipy_proxy.js
Adds proxy_klipy_request(res, upstream_url) performing abortable fetch with timeout, enforcing JSON responses, mapping upstream non-2xx to forwarded responses, timeouts to 504, and other failures to 502.
Search Handler
src/klipy_proxy.js
Adds proxy_klipy_gif_search(app, req, res) validating page/per_page and optional q, locale, content_filter, format_filter; enforces allowed content_filter, computes customer_id, builds /api/v1/{app_key}/gifs/search query, and proxies request.
Featured Handler
src/klipy_proxy.js
Adds proxy_klipy_gif_featured(req, res) validating limit and optional pos, locale, country, contentfilter, media_filter; enforces allowed contentfilter, applies default media_filter, builds /v2/featured query (includes key), and proxies request.
Routing & Authorization
src/router_config.js
Imports proxy handlers; adds require_active_purple_user(app, req, res) (checks req.authorized_pubkey via check_account and returns 401 on failure); registers GET /gifs/search and GET /gifs/featured routes that require NIP-98 auth and active Purple account before proxying.
Test Environment Setup
test/controllers/purple_test_controller.js
Sets KLIPY_APP_KEY and KLIPY_API_BASE_URL in test env.
Test Client Methods
test/controllers/purple_test_client.js
Adds search_gifs(query, options) and featured_gifs(query, options) helpers that enable NIP-98 auth, serialize query params, and GET the new endpoints.
Integration Tests
test/router_config.test.js
Adds tests stubbing global.fetch to assert upstream URL, query params, Accept header, and signal; tests successful proxying with active Purple account and negative cases for inactive account and missing NIP-98 auth.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router as Express Router
    participant Auth as Purple Auth Service
    participant KlipyProxy as KLIPY Proxy Handler
    participant KlipyApi as KLIPY API

    Client->>Router: GET /gifs/search (NIP-98 auth)
    Router->>Auth: check_account(authorized_pubkey)
    alt account inactive
        Auth-->>Router: not found
        Router-->>Client: 401 { error: 'Account not found' }
    else account active
        Auth-->>Router: account ok
        Router->>KlipyProxy: proxy_klipy_gif_search(app, req)
        KlipyProxy->>KlipyApi: GET /api/v1/{app_key}/gifs/search?...
        KlipyApi-->>KlipyProxy: JSON response (or non-2xx)
        KlipyProxy-->>Router: parsed upstream payload/status
        Router-->>Client: forwarded status + payload
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through routes and queries bright,

Built a tunnel for GIFs to take flight,
With Purple keys and timeout clocks,
I fetch and forward — no paradox!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main changes: adding a KLIPY GIF proxy system with associated routes and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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
Review rate limit: 0/1 reviews remaining, refill in 44 minutes and 36 seconds.

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: 2

🤖 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/klipy_proxy.js`:
- Around line 8-15: The parse_integer_query_param function currently uses
Number.parseInt which accepts partial numeric strings like "2foo"; update
parse_integer_query_param to reject any value that is not a fully-formed integer
string before parsing (e.g., match the input against a strict integer regex such
as /^\d+$/ or /^[+-]?\d+$/ depending on desired sign policy), only call
Number.parseInt(String(value), 10) when the regex passes, keep returning
fallback for undefined/null/'' and return null for malformed values; reference
the existing function name parse_integer_query_param to locate and change the
validation logic.
- Around line 39-70: The proxy_klipy_request function currently calls fetch
without a timeout; wrap the fetch call with an AbortController: create a
controller, pass controller.signal into fetch(upstream_url, { method:'GET',
headers:{...}, signal }), set a timer (e.g., const timeoutId = setTimeout(() =>
controller.abort(), <desired_ms>)), and clearTimeout(timeoutId) after fetch
completes (both in success and in the catch); in the catch branch detect an
abort (request_error.name === 'AbortError') and return a 504 or a clear
timeout-specific error via error_response(res, 'KLIPY request timed out', 504),
otherwise keep the existing error handling for other request errors. Ensure the
timer is cleared even when JSON.parse throws and before every return.
🪄 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: 6f8bc401-1469-48f5-a2d6-aeab537a0767

📥 Commits

Reviewing files that changed from the base of the PR and between e1c75dc and 7ef468b.

📒 Files selected for processing (5)
  • src/klipy_proxy.js
  • src/router_config.js
  • test/controllers/purple_test_client.js
  • test/controllers/purple_test_controller.js
  • test/router_config.test.js

Comment thread src/klipy_proxy.js
Comment thread src/klipy_proxy.js
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: 1

♻️ Duplicate comments (1)
test/router_config.test.js (1)

193-219: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Same global.fetch mock ordering concern applies here.

The mock is installed at line 193 before ln_flow_buy_subscription runs at line 219 — same risk as in the search test. Apply the same fix: move the mock setup to after the subscription call.

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

In `@test/router_config.test.js` around lines 193 - 219, The global.fetch mock in
test/router_config.test.js is installed before calling
purple_api_controller.ln_flow_buy_subscription which risks the same ordering
issue as the search test; move the mock setup (the assignment to global.fetch
and its t.teardown restoration) to after invoking ln_flow_buy_subscription so
the live call runs before the test-specific fetch override, and ensure the
teardown still restores original_fetch after the mock is installed.
🧹 Nitpick comments (1)
src/router_config.js (1)

16-24: 💤 Low value

require_active_purple_user is clean — consider reusing it in the existing /translate route too.

Lines 43–47 in the /translate handler duplicate this exact same inline account check. Swapping to require_active_purple_user there would keep the two authorization paths consistent.

♻️ Proposed refactor for the `/translate` route
  router.get('/translate', required_nip98_auth, async (req, res) => {
-   const check_account_result = check_account(app, req.authorized_pubkey)
-   if (!check_account_result.ok) {
-     unauthorized_response(res, check_account_result.message)
+   if (!require_active_purple_user(app, req, res)) {
      return
    }
    handle_translate(app, req, res)
  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/router_config.js` around lines 16 - 24, Duplicate inline account-check
logic in the /translate route should be replaced with a call to the existing
require_active_purple_user function: inside the /translate handler, remove the
manual check_account(...) + unauthorized_response(...) block and instead call if
(!require_active_purple_user(app, req, res)) return; so the handler reuses
require_active_purple_user(app, req, res) (which itself wraps check_account and
unauthorized_response) and keeps authorization paths consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/router_config.test.js`:
- Around line 110-141: The global.fetch mock is installed before creating the
subscription and may capture unintended fetches from
purple_api_controller.ln_flow_buy_subscription; move the entire mock setup (the
global.fetch assignment with its assertions and the t.teardown that restores
original_fetch) to after you call purple_api_controller.set_account_uuid(pubkey,
'TEST-UUID-123') and await
purple_api_controller.ln_flow_buy_subscription(pubkey, 'purple_one_month') so
only the subsequent search call (the GIF search) triggers the mock; keep the
same mock body and teardown but relocate it to just before the code that invokes
the search_gifs call.

---

Duplicate comments:
In `@test/router_config.test.js`:
- Around line 193-219: The global.fetch mock in test/router_config.test.js is
installed before calling purple_api_controller.ln_flow_buy_subscription which
risks the same ordering issue as the search test; move the mock setup (the
assignment to global.fetch and its t.teardown restoration) to after invoking
ln_flow_buy_subscription so the live call runs before the test-specific fetch
override, and ensure the teardown still restores original_fetch after the mock
is installed.

---

Nitpick comments:
In `@src/router_config.js`:
- Around line 16-24: Duplicate inline account-check logic in the /translate
route should be replaced with a call to the existing require_active_purple_user
function: inside the /translate handler, remove the manual check_account(...) +
unauthorized_response(...) block and instead call if
(!require_active_purple_user(app, req, res)) return; so the handler reuses
require_active_purple_user(app, req, res) (which itself wraps check_account and
unauthorized_response) and keeps authorization paths consistent.
🪄 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: 82eaf904-6942-4aee-8d78-bac2b7759746

📥 Commits

Reviewing files that changed from the base of the PR and between 7ef468b and 89f7e2d.

📒 Files selected for processing (5)
  • src/klipy_proxy.js
  • src/router_config.js
  • test/controllers/purple_test_client.js
  • test/controllers/purple_test_controller.js
  • test/router_config.test.js
✅ Files skipped from review due to trivial changes (2)
  • src/klipy_proxy.js
  • test/controllers/purple_test_controller.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/controllers/purple_test_client.js

Comment thread test/router_config.test.js Outdated
Introduce a server-side proxy for KLIPY GIF search and featured
endpoints,
add authenticated routes that require an active Purple subscription, and
extend the test client/controller and router tests.

Requires KLIPY_APP_KEY to be set (KLIPY_API_BASE_URL is optional).
@danieldaquino danieldaquino merged commit 6728f22 into damus-io:master May 4, 2026
2 checks passed
@danieldaquino
Copy link
Copy Markdown
Collaborator Author

CC @jb55, please let me know if you have any concerns or suggestions for a follow up improvement.

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.

1 participant