Skip to content

[FEATURE] Add GET /api/events global event feed with filters#648

Open
Chigybillionz wants to merge 2 commits into
ritik4ever:mainfrom
Chigybillionz:global-event
Open

[FEATURE] Add GET /api/events global event feed with filters#648
Chigybillionz wants to merge 2 commits into
ritik4ever:mainfrom
Chigybillionz:global-event

Conversation

@Chigybillionz

@Chigybillionz Chigybillionz commented Jul 1, 2026

Copy link
Copy Markdown

Close #604

Summary

Adds full filtering to the global event feed at GET /api/events. The endpoint
returns stream events across all streams, newest first, with pagination and a
complete set of filters: eventType, streamId, actor, and a from/to
date range.

Root Cause

The /api/events endpoint already existed but only supported a single
eventType filter. The underlying query functions (getGlobalEvents,
countAllEvents) hardcoded that one filter with duplicated, branch-per-filter
SQL, leaving no clean path to add streamId, actor, or a date range.

Solution

Replaced the single-purpose query API with a composable filter-object builder:

  • A single buildEventFilterClause() produces the shared WHERE clause and
    bound params used by both queryEvents() (data) and countEvents() (total),
    so the pagination total can never drift from the rows returned.
  • All filters use parameterized binding — no SQL injection surface.
  • from/to accept either UNIX seconds or ISO-8601 dates, normalized to UNIX
    seconds; from > to is rejected with 400.
  • Legacy functions are kept as delegates for backward compatibility.

Key Changes

  • services/eventHistory.ts — new EventFilters type, buildEventFilterClause(),
    queryEvents(), countEvents(); legacy fns delegate.
  • validation/schemas.tslistEventsQuerySchema extended with streamId,
    actor, from, to; new eventDateBoundSchema; from ≤ to refinement.
  • index.ts/api/events handler builds an EventFilters object.
  • services/db.ts — added idx_stream_events_event_type and
    idx_stream_events_actor indexes.
  • index.test.ts — updated mocks and added coverage for every filter, date
    normalization, from>to validation, and the empty-result contract.

Acceptance Criteria

  • Returns all stream events across all streams, sorted by timestamp DESC.
  • Filters: eventType, streamId, actor, from, to (all combinable).
  • Paginated via page and limit.
  • Empty result returns {data: [], total: 0} with HTTP 200 (never 404).
  • Response time < 200ms for 10k events (index-backed filters + sort).

Trade-offs / Considerations

  • actor and streamId filter on exact equality (matches how the data is
    stored); no partial/prefix matching.
  • from/to are inclusive bounds. ISO dates without a time component resolve
    to midnight UTC.
  • Added two indexes; negligible write overhead, meaningful read speedup at scale.
  • Kept legacy getGlobalEvents/countAllEvents as delegates to avoid breaking
    any other consumers.

Testing Steps

  1. cd backend && npx vitest run src/index.test.ts — all event-feed tests pass.
  2. Manual (npm run dev:backend):
    • GET /api/events → all events, newest first.
    • GET /api/events?eventType=claimed&actor=G...&from=2026-01-01&to=2026-07-01
      → correctly filtered subset.
    • GET /api/events?streamId=1&page=2&limit=10 → paginated slice.
    • GET /api/events?from=2026-12-31&to=2026-01-01 → 400 (from after to).
    • GET /api/events?eventType=paused with no matches → {data: [], total: 0}, 200.

Please kindly checkout this task and please if there is any correction or adjustment regarding my implementaion please do let me, and i would love to here your review from my branch, thank you

Summary by CodeRabbit

  • New Features

    • Event listings now support filtering by event type, stream, actor, and date range.
    • Date inputs accept either timestamps or ISO date strings.
  • Bug Fixes

    • Improved pagination and result counts to stay in sync with applied filters.
    • “No matching events” now returns an empty list with a total of 0 instead of an error.
    • Added validation for invalid actors and reversed date ranges.
  • Performance

    • Event queries are now better optimized for common filtering and lookup patterns.

@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

@Chigybillionz is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Chigybillionz, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 51 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cad23fcd-faa3-40c9-925f-eeeff4273c25

📥 Commits

Reviewing files that changed from the base of the PR and between 921649b and 6da79bc.

📒 Files selected for processing (5)
  • backend/src/index.test.ts
  • backend/src/index.ts
  • backend/src/services/db.ts
  • backend/src/services/eventHistory.ts
  • backend/src/validation/schemas.ts
📝 Walkthrough

Walkthrough

Adds filter support (eventType, streamId, actor, from/to date range) to the global GET /api/events feed, backed by new EventFilters, queryEvents, and countEvents helpers in eventHistory.ts, updated validation schemas, new DB indexes, and revised endpoint logic. Test harness and coverage updated accordingly.

Changes

Global event feed with filters

Layer / File(s) Summary
Filter validation schemas
backend/src/validation/schemas.ts
Adds eventDateBoundSchema (normalizing UNIX seconds/ISO input) and expands listEventsQuerySchema with streamId, actor, from/to, plus from <= to cross-field validation.
Shared query/count helpers
backend/src/services/eventHistory.ts
Adds EventFilters, buildEventFilterClause, queryEvents, countEvents; reimplements getAllEvents, getGlobalEvents, countAllEvents to delegate to shared filtering logic.
Endpoint integration and DB indexes
backend/src/index.ts, backend/src/services/db.ts
/api/events handler builds EventFilters and calls countEvents/queryEvents; migration adds indexes on event_type and actor.
Test harness and coverage
backend/src/index.test.ts
Updates mocks (queryEvents, countEvents, nowInSeconds), fixes handler/res.set() selection, and adds tests for filtering, date-range handling, pagination, and no-match responses.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Endpoint as GET /api/events
  participant Schema as listEventsQuerySchema
  participant Service as eventHistory Service
  participant DB as stream_events table

  Client->>Endpoint: request with eventType/streamId/actor/from/to/page/limit
  Endpoint->>Schema: validate and normalize query
  Schema-->>Endpoint: filters + pagination
  Endpoint->>Service: countEvents(filters)
  Service->>DB: COUNT(*) WHERE filters
  DB-->>Service: total
  Endpoint->>Service: queryEvents(filters, limit, offset, cursor)
  Service->>DB: SELECT WHERE filters ORDER BY timestamp DESC, id DESC
  DB-->>Service: rows
  Service-->>Endpoint: data
  Endpoint-->>Client: { data, total }
Loading

Possibly related PRs

  • ritik4ever/stellar-stream#273: Refactors the same getAllEvents/countAllEvents delegation to queryEvents/countEvents that this PR introduces, directly affecting shared pagination/ordering tests.
  • ritik4ever/stellar-stream#620: Modifies the same /api/events implementation and getGlobalEvents/countAllEvents usage in index.ts and eventHistory.ts.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR implements the global event feed, requested filters, pagination, empty-result handling, and supporting validation/index updates, but the 10k-event latency target is unverified. Provide benchmark or profiling evidence for GET /api/events at 10k events, or report the measured latency under load.
✅ 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 is concise and accurately describes the main change: a filtered global GET /api/events feed.
Out of Scope Changes check ✅ Passed The changes stay within the event-feed feature and its supporting validation, query, test, and index updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@drips-wave

drips-wave Bot commented Jul 1, 2026

Copy link
Copy Markdown

@Chigybillionz Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/src/validation/schemas.ts (1)

150-168: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Uses deprecated ctx.addIssue(); Zod v4 recommends ctx.issues.push().

Per the Zod v4 changelog, ctx.addIssue()/ctx.addIssues() are deprecated in favor of pushing directly onto ctx.issues. There's also a reported behavioral quirk where ctx.addIssue() in transforms doesn't short-circuit the same way ctx.issues.push() does. Since the project is on zod 4.3.6, prefer the non-deprecated API here.

♻️ Suggested change
     const ms = Date.parse(value);
     if (Number.isNaN(ms)) {
-      ctx.addIssue({
-        code: "custom",
-        message:
-          "must be a UNIX timestamp in seconds or an ISO-8601 date (e.g. 2026-07-01).",
-      });
+      ctx.issues.push({
+        code: "custom",
+        input: value,
+        message:
+          "must be a UNIX timestamp in seconds or an ISO-8601 date (e.g. 2026-07-01).",
+      });
       return z.NEVER;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/validation/schemas.ts` around lines 150 - 168, The transform in
eventDateBoundSchema still uses the deprecated ctx.addIssue() API. Update the
invalid-date branch to report the validation error by pushing onto ctx.issues
instead, keeping the same custom message and returning z.NEVER so the transform
stops correctly under Zod v4.3.6.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/services/eventHistory.ts`:
- Around line 112-177: `buildEventFilterClause`, `queryEvents`, and
`countEvents` currently use positional `?` placeholders with a parallel `params`
array, which violates the backend binding convention and makes parameter
ordering fragile. Refactor the SQL construction in `buildEventFilterClause` to
build named bindings (for example, based on `eventType`, `streamId`, `actor`,
`from`, `to`, and `cursor`) and update `queryEvents`/`countEvents` to pass those
named parameters into the `better-sqlite3` prepared statements instead of
relying on array order.

---

Nitpick comments:
In `@backend/src/validation/schemas.ts`:
- Around line 150-168: The transform in eventDateBoundSchema still uses the
deprecated ctx.addIssue() API. Update the invalid-date branch to report the
validation error by pushing onto ctx.issues instead, keeping the same custom
message and returning z.NEVER so the transform stops correctly under Zod v4.3.6.
🪄 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: 42808f3b-5637-49d3-b5fa-89851bee3198

📥 Commits

Reviewing files that changed from the base of the PR and between b3d32c1 and 921649b.

📒 Files selected for processing (5)
  • backend/src/index.test.ts
  • backend/src/index.ts
  • backend/src/services/db.ts
  • backend/src/services/eventHistory.ts
  • backend/src/validation/schemas.ts

Comment on lines +112 to +177
function buildEventFilterClause(
filters: EventFilters,
cursor?: number,
streamId?: string,
since?: number,
): StreamEvent[] {
const db = getDb();
): { clause: string; params: any[] } {
const conditions: string[] = [];
const params: any[] = [];

if (eventType) {
if (filters.eventType !== undefined) {
conditions.push("event_type = ?");
params.push(eventType);
params.push(filters.eventType);
}

if (filters.streamId !== undefined) {
conditions.push("stream_id = ?");
params.push(filters.streamId);
}
if (filters.actor !== undefined) {
conditions.push("actor = ?");
params.push(filters.actor);
}
if (filters.from !== undefined) {
conditions.push("timestamp >= ?");
params.push(filters.from);
}
if (filters.to !== undefined) {
conditions.push("timestamp <= ?");
params.push(filters.to);
}
// Keyset guard for stable pagination when new events arrive mid-scroll.
if (cursor !== undefined) {
conditions.push("id < ?");
params.push(cursor);
}

if (streamId) {
conditions.push("stream_id = ?");
params.push(streamId);
}

if (since !== undefined) {
conditions.push("timestamp > ?");
params.push(since);
}

let query = "SELECT * FROM stream_events";
if (conditions.length > 0) {
query += " WHERE " + conditions.join(" AND ");
}
query += " ORDER BY timestamp DESC, id DESC LIMIT ? OFFSET ?";
params.push(limit, offset);
const clause = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
return { clause, params };
}

const rows = db.prepare(query).all(...params) as EventRow[];
/**
* Return events across all streams matching `filters`, newest first
* (timestamp DESC, id DESC). Used by the global event feed.
*/
export function queryEvents(
filters: EventFilters,
limit: number,
offset: number,
cursor?: number,
): StreamEvent[] {
const db = getDb();
const { clause, params } = buildEventFilterClause(filters, cursor);
const rows = db
.prepare(
`SELECT * FROM stream_events${clause} ORDER BY timestamp DESC, id DESC LIMIT ? OFFSET ?`,
)
.all(...params, limit, offset) as EventRow[];
return rows.map(rowToEvent);
}

export function countAllEvents(
eventType?: StreamEventType,
streamId?: string,
since?: number,
): number {
/** Count events matching `filters` (used for pagination totals). */
export function countEvents(filters: EventFilters): number {
const db = getDb();
const conditions: string[] = [];
const params: any[] = [];

if (eventType) {
conditions.push("event_type = ?");
params.push(eventType);
}

if (streamId) {
conditions.push("stream_id = ?");
params.push(streamId);
}
const { clause, params } = buildEventFilterClause(filters);
const row = db
.prepare(`SELECT COUNT(*) as count FROM stream_events${clause}`)
.get(...params) as { count: number };
return row.count;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Uses ? positional placeholders instead of @name binding, per backend coding guidelines.

buildEventFilterClause, queryEvents, and countEvents all build SQL with ? placeholders and positional params arrays. This is functionally safe from injection (still parameterized), but it violates the stated repo convention and makes it easy to introduce a param-ordering bug later (e.g. reordering a condition without reordering params.push(...)). As per coding guidelines, "use @name parameter binding syntax for better-sqlite3 prepared statements instead of ? placeholders."

♻️ Suggested refactor to named parameters
 function buildEventFilterClause(
   filters: EventFilters,
   cursor?: number,
-): { clause: string; params: any[] } {
+): { clause: string; params: Record<string, unknown> } {
   const conditions: string[] = [];
-  const params: any[] = [];
+  const params: Record<string, unknown> = {};

   if (filters.eventType !== undefined) {
-    conditions.push("event_type = ?");
-    params.push(filters.eventType);
+    conditions.push("event_type = `@eventType`");
+    params.eventType = filters.eventType;
   }
   if (filters.streamId !== undefined) {
-    conditions.push("stream_id = ?");
-    params.push(filters.streamId);
+    conditions.push("stream_id = `@streamId`");
+    params.streamId = filters.streamId;
   }
   if (filters.actor !== undefined) {
-    conditions.push("actor = ?");
-    params.push(filters.actor);
+    conditions.push("actor = `@actor`");
+    params.actor = filters.actor;
   }
   if (filters.from !== undefined) {
-    conditions.push("timestamp >= ?");
-    params.push(filters.from);
+    conditions.push("timestamp >= `@from`");
+    params.from = filters.from;
   }
   if (filters.to !== undefined) {
-    conditions.push("timestamp <= ?");
-    params.push(filters.to);
+    conditions.push("timestamp <= `@to`");
+    params.to = filters.to;
   }
   if (cursor !== undefined) {
-    conditions.push("id < ?");
-    params.push(cursor);
+    conditions.push("id < `@cursor`");
+    params.cursor = cursor;
   }

   const clause = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
   return { clause, params };
 }

 export function queryEvents(
   filters: EventFilters,
   limit: number,
   offset: number,
   cursor?: number,
 ): StreamEvent[] {
   const db = getDb();
   const { clause, params } = buildEventFilterClause(filters, cursor);
   const rows = db
     .prepare(
-      `SELECT * FROM stream_events${clause} ORDER BY timestamp DESC, id DESC LIMIT ? OFFSET ?`,
+      `SELECT * FROM stream_events${clause} ORDER BY timestamp DESC, id DESC LIMIT `@limit` OFFSET `@offset``,
     )
-    .all(...params, limit, offset) as EventRow[];
+    .all({ ...params, limit, offset }) as EventRow[];
   return rows.map(rowToEvent);
 }

 export function countEvents(filters: EventFilters): number {
   const db = getDb();
   const { clause, params } = buildEventFilterClause(filters);
   const row = db
     .prepare(`SELECT COUNT(*) as count FROM stream_events${clause}`)
-    .get(...params) as { count: number };
+    .get(params) as { count: number };
   return row.count;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function buildEventFilterClause(
filters: EventFilters,
cursor?: number,
streamId?: string,
since?: number,
): StreamEvent[] {
const db = getDb();
): { clause: string; params: any[] } {
const conditions: string[] = [];
const params: any[] = [];
if (eventType) {
if (filters.eventType !== undefined) {
conditions.push("event_type = ?");
params.push(eventType);
params.push(filters.eventType);
}
if (filters.streamId !== undefined) {
conditions.push("stream_id = ?");
params.push(filters.streamId);
}
if (filters.actor !== undefined) {
conditions.push("actor = ?");
params.push(filters.actor);
}
if (filters.from !== undefined) {
conditions.push("timestamp >= ?");
params.push(filters.from);
}
if (filters.to !== undefined) {
conditions.push("timestamp <= ?");
params.push(filters.to);
}
// Keyset guard for stable pagination when new events arrive mid-scroll.
if (cursor !== undefined) {
conditions.push("id < ?");
params.push(cursor);
}
if (streamId) {
conditions.push("stream_id = ?");
params.push(streamId);
}
if (since !== undefined) {
conditions.push("timestamp > ?");
params.push(since);
}
let query = "SELECT * FROM stream_events";
if (conditions.length > 0) {
query += " WHERE " + conditions.join(" AND ");
}
query += " ORDER BY timestamp DESC, id DESC LIMIT ? OFFSET ?";
params.push(limit, offset);
const clause = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
return { clause, params };
}
const rows = db.prepare(query).all(...params) as EventRow[];
/**
* Return events across all streams matching `filters`, newest first
* (timestamp DESC, id DESC). Used by the global event feed.
*/
export function queryEvents(
filters: EventFilters,
limit: number,
offset: number,
cursor?: number,
): StreamEvent[] {
const db = getDb();
const { clause, params } = buildEventFilterClause(filters, cursor);
const rows = db
.prepare(
`SELECT * FROM stream_events${clause} ORDER BY timestamp DESC, id DESC LIMIT ? OFFSET ?`,
)
.all(...params, limit, offset) as EventRow[];
return rows.map(rowToEvent);
}
export function countAllEvents(
eventType?: StreamEventType,
streamId?: string,
since?: number,
): number {
/** Count events matching `filters` (used for pagination totals). */
export function countEvents(filters: EventFilters): number {
const db = getDb();
const conditions: string[] = [];
const params: any[] = [];
if (eventType) {
conditions.push("event_type = ?");
params.push(eventType);
}
if (streamId) {
conditions.push("stream_id = ?");
params.push(streamId);
}
const { clause, params } = buildEventFilterClause(filters);
const row = db
.prepare(`SELECT COUNT(*) as count FROM stream_events${clause}`)
.get(...params) as { count: number };
return row.count;
}
function buildEventFilterClause(
filters: EventFilters,
cursor?: number,
): { clause: string; params: Record<string, unknown> } {
const conditions: string[] = [];
const params: Record<string, unknown> = {};
if (filters.eventType !== undefined) {
conditions.push("event_type = `@eventType`");
params.eventType = filters.eventType;
}
if (filters.streamId !== undefined) {
conditions.push("stream_id = `@streamId`");
params.streamId = filters.streamId;
}
if (filters.actor !== undefined) {
conditions.push("actor = `@actor`");
params.actor = filters.actor;
}
if (filters.from !== undefined) {
conditions.push("timestamp >= `@from`");
params.from = filters.from;
}
if (filters.to !== undefined) {
conditions.push("timestamp <= `@to`");
params.to = filters.to;
}
// Keyset guard for stable pagination when new events arrive mid-scroll.
if (cursor !== undefined) {
conditions.push("id < `@cursor`");
params.cursor = cursor;
}
const clause = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
return { clause, params };
}
/**
* Return events across all streams matching `filters`, newest first
* (timestamp DESC, id DESC). Used by the global event feed.
*/
export function queryEvents(
filters: EventFilters,
limit: number,
offset: number,
cursor?: number,
): StreamEvent[] {
const db = getDb();
const { clause, params } = buildEventFilterClause(filters, cursor);
const rows = db
.prepare(
`SELECT * FROM stream_events${clause} ORDER BY timestamp DESC, id DESC LIMIT `@limit` OFFSET `@offset``,
)
.all({ ...params, limit, offset }) as EventRow[];
return rows.map(rowToEvent);
}
/** Count events matching `filters` (used for pagination totals). */
export function countEvents(filters: EventFilters): number {
const db = getDb();
const { clause, params } = buildEventFilterClause(filters);
const row = db
.prepare(`SELECT COUNT(*) as count FROM stream_events${clause}`)
.get(params) as { count: number };
return row.count;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/eventHistory.ts` around lines 112 - 177,
`buildEventFilterClause`, `queryEvents`, and `countEvents` currently use
positional `?` placeholders with a parallel `params` array, which violates the
backend binding convention and makes parameter ordering fragile. Refactor the
SQL construction in `buildEventFilterClause` to build named bindings (for
example, based on `eventType`, `streamId`, `actor`, `from`, `to`, and `cursor`)
and update `queryEvents`/`countEvents` to pass those named parameters into the
`better-sqlite3` prepared statements instead of relying on array order.

Source: Coding guidelines

@Chigybillionz

Copy link
Copy Markdown
Author

you can check it out!

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.

[FEATURE] Add GET /api/events global event feed with filters

1 participant