Skip to content

Fixed Issue #258#365

Merged
EDOHWARES merged 1 commit intoEDOHWARES:mainfrom
Juwonlo:subscriptions
May 1, 2026
Merged

Fixed Issue #258#365
EDOHWARES merged 1 commit intoEDOHWARES:mainfrom
Juwonlo:subscriptions

Conversation

@Juwonlo
Copy link
Copy Markdown
Contributor

@Juwonlo Juwonlo commented Apr 28, 2026

Closes #258


GraphQL Subscriptions for real-time dashboard --- Fixed Issue #258

Copilot AI review requested due to automatic review settings April 28, 2026 00:40
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 28, 2026

@Juwonlo 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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements the backend pieces for GraphQL Subscriptions intended to stream execution logs to a real-time dashboard, using WebSockets + Redis-backed PubSub.

Changes:

  • Added GraphQL subscription schema (ExecutionLog) and subscription resolver with optional triggerId filtering.
  • Added a WebSocket subscription server wrapper using graphql-ws, with JWT validation during WS connect.
  • Introduced Redis-based PubSub and began publishing execution logs from the BullMQ worker (currently on failures only), while also changing webhook delivery behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
backend/src/worker/typeDefs.js Adds GraphQL SDL for ExecutionLog + Subscription.
backend/src/worker/server.js Adds Apollo/graphql-ws WebSocket server configuration with JWT auth on connect.
backend/src/worker/resolvers.js Adds subscription resolver using withFilter + PubSub iterator.
backend/src/worker/pubsub.js Adds Redis-backed PubSub implementation for subscriptions.
backend/src/worker/processor.js Publishes execution logs on job failure; changes webhook sending from signed webhooks to plain axios.post.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 100 to +109
case 'webhook': {
if (!actionUrl) {
throw new Error('Missing actionUrl for webhook trigger');
}

const payload = {
return await axios.post(actionUrl, {
contractId,
eventName,
payload: eventPayload,
};

return await webhookService.sendSignedWebhook(
actionUrl,
payload,
trigger.webhookSecret
);
});
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Webhook delivery no longer uses webhookService.sendSignedWebhook(...) and instead posts unsigned JSON directly. This removes HMAC signing that’s documented (backend/WEBHOOK_VERIFICATION.md) and implemented in src/services/webhook.service.js, breaking existing webhook verification and reducing security. Restore signed delivery (or provide an explicit, backwards-compatible opt-out).

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +43
try {
const decoded = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET || 'secret');
return { user: decoded }; // Pass user info into context
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

jwt.verify(..., process.env.JWT_SECRET || 'secret') introduces an insecure fallback: if JWT_SECRET is unset/misconfigured, anyone can mint valid tokens using the known string secret. Require JWT_SECRET (fail fast on startup) and avoid a hard-coded default.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +44
return { user: decoded }; // Pass user info into context
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

onConnect in graphql-ws is only for accepting/rejecting the connection; returning { user: decoded } does not automatically become the GraphQL execution context (despite the comment). If resolvers need the authenticated user, persist it on ctx.extra in onConnect and provide a context function in useServer (or use onSubscribe) to inject { user } into the operation context.

Suggested change
return { user: decoded }; // Pass user info into context
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
}
ctx.extra.user = decoded;
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
},
context: async (ctx) => ({
user: ctx.extra.user,
}),

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +67
async function configureGraphQLServer(httpServer) {
const schema = makeExecutableSchema({ typeDefs, resolvers });

// Creating the WebSocket server for subscriptions
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});

// Connect WebSocket server with GraphQL
const serverCleanup = useServer({
schema,
onConnect: async (ctx) => {
// Secure auth over WebSocket connections
const token = ctx.connectionParams?.authToken || ctx.connectionParams?.Authorization;
if (!token) {
logger.warn('WebSocket connection attempted without auth token');
throw new Error('Authentication token is required');
}

try {
const decoded = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET || 'secret');
return { user: decoded }; // Pass user info into context
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
}
}, wsServer);

const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
],
});

await server.start();

logger.info('Apollo GraphQL Subscriptions server initialized');
return server;
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

configureGraphQLServer is exported but not invoked anywhere in backend/src (no references found), so subscriptions won’t actually be enabled. If this PR is meant to implement Issue #258 end-to-end, wire this into the HTTP server startup (or document the separate entrypoint) so /graphql WS is reachable.

Copilot uses AI. Check for mistakes.
Comment on lines 290 to +316
worker.on('completed', (job) => {
logger.info('Job completed', {
jobId: job.id,
actionType: job.data.trigger.actionType,
});
});

worker.on('failed', (job, err) => {
logger.error('Job failed', {
jobId: job?.id,
actionType: job?.data?.trigger?.actionType,
error: err.message,
attemptsRemaining: job ? job.opts.attempts - job.attemptsMade : 0,
});

if (job) {
pubsub.publish('EXECUTION_LOG', {
executionLog: {
jobId: job.id,
triggerId: String(job.data.trigger._id),
actionType: job.data.trigger.actionType,
status: 'FAILED',
error: err.message,
timestamp: new Date().toISOString()
}
});
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Execution log publishing is currently only emitted on job failure. For “real-time dashboard” execution logs, you likely also want to publish on completed (and possibly start/progress) with status: 'SUCCEEDED' (and error: null) so subscribers can reflect successful executions too.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +45
const serverCleanup = useServer({
schema,
onConnect: async (ctx) => {
// Secure auth over WebSocket connections
const token = ctx.connectionParams?.authToken || ctx.connectionParams?.Authorization;
if (!token) {
logger.warn('WebSocket connection attempted without auth token');
throw new Error('Authentication token is required');
}

try {
const decoded = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET || 'secret');
return { user: decoded }; // Pass user info into context
} catch (error) {
logger.error('WebSocket authentication failed', { error: error.message });
throw new Error('Authentication token is invalid');
}
}
}, wsServer);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

There are existing Node tests under backend/__tests__, but no unit/integration tests were added for the new subscription auth + filtering behavior. Add tests that cover: (1) rejecting WS connections without/with invalid JWT, (2) allowing valid JWT, and (3) executionLog(triggerId) filtering behavior.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,16 @@
c.onst typeDefs = `#graphql
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Line 1 has a syntax error (c.onst). This file won’t load and will prevent the GraphQL schema from being created. Change it to a valid const declaration.

Suggested change
c.onst typeDefs = `#graphql
const typeDefs = `#graphql

Copilot uses AI. Check for mistakes.
error: String
timestamp: String!
}

Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

The schema defines Subscription but no Query root (and no explicit schema { subscription: Subscription }). makeExecutableSchema/GraphQL validation will fail with “Query root type must be provided”. Add an explicit schema definition or include at least a minimal type Query { _empty: String }.

Suggested change
type Query {
_empty: String
}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
const { ApolloServer } = require('@apollo/server');
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');
const jwt = require('jsonwebtoken');
const typeDefs = require('./typeDefs');
const resolvers = require('./resolvers');
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

This module imports new runtime dependencies (@apollo/server, @graphql-tools/schema, graphql-ws, ws, graphql-subscriptions, graphql-redis-subscriptions) that are not present in backend/package.json. When this code is wired in, require() will fail at runtime. Add the required packages to backend/package.json (dependencies) and lockfile.

Copilot uses AI. Check for mistakes.
const telegramService = require('../services/telegram.service');
const webhookService = require('../services/webhook.service');
const logger = require('../config/logger');
const pubsub = require('../graphql/pubsub');
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

const pubsub = require('../graphql/pubsub'); points to a non-existent path (backend/src/graphql does not exist). This throws during worker initialization and disables the BullMQ worker (caught in src/server.js, effectively turning off background processing). Import the actual pubsub module (likely ./pubsub within src/worker) and ensure both publisher and subscription server use the same instance/config.

Suggested change
const pubsub = require('../graphql/pubsub');
const pubsub = require('./pubsub');

Copilot uses AI. Check for mistakes.
@EDOHWARES EDOHWARES merged commit e34ae12 into EDOHWARES:main May 1, 2026
4 of 5 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.

Backend: GraphQL Subscriptions for real-time dashboard

3 participants