Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 81 additions & 72 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -1,72 +1,81 @@
# Fix frontend debug logging, accessibility, dead code, and backend pagination

## Summary

This PR addresses four frontend and backend issues:
- Removes debug console logging from SSE event handling and stream-detail page
- Removes unused `users` query parameter that the backend ignores
- Adds accessible ARIA label to the top-up amount input field
- Removes dead legacy Dashboard component
- Adds `page` parameter support to the stream events endpoint for consistent pagination

## Fixes

Closes #509
Closes #512
Closes #514
Closes #517

## Changes

### Frontend (fix/issues-509-512-514-517)

**Issue #509: Remove debug console logging**
- Removed `console.log('SSE connected:', data.clientId)` from useStreamEvents hook
- Removed `console.error()` calls from SSE message and event parsing
- Removed `console.log()` call from reconnect retry logic
- Removed debug logging from stream-detail event handler
- Simplified onmessage handler which wasn't being used for event processing

**Issue #512: Add accessible label to top-up input**
- Added `aria-label="Top-up amount"` to the number input field on stream detail page
- Maintains existing placeholder as a visual hint, not the sole accessible label
- No visual changes to the UI

**Issue #514: Remove dead components**
- Deleted `frontend/src/components/Dashboard.tsx` which contained hardcoded mock stream data
- This was a legacy component not used by the application (live dashboard is at `app/dashboard/page.tsx`)
- Kept `Progressbar.tsx` and `Livecounter.tsx` as they are actively used in the stream-detail page

### Backend (fix/issues-509-512-514-517)

**Issue #517: Add page parameter support**
- Updated `getStreamEvents` controller to accept optional `page` query parameter
- Maps 1-based page number to offset: `offset = (page - 1) * limit`
- Page parameter is only used when `offset` and `cursor` are not provided
- Maintains full backward compatibility with existing offset/cursor-based pagination
- Mirrors the behavior of the sibling `/v1/events` endpoint

## Test Plan

- [ ] Frontend builds successfully: `cd frontend && npm run build`
- [ ] Backend builds successfully: `cd backend && npm run build`
- [ ] Frontend lint passes: `cd frontend && npm run lint`
- [ ] Backend tests pass: `cd backend && npm run test`
- Manually verify:
- [ ] Stream detail page loads without console errors
- [ ] SSE events update stream state without debug logs in browser console
- [ ] Top-up input is properly labeled in accessibility tools (e.g., browser dev tools, screen readers)
- [ ] Stream event pagination works with page parameter (e.g., `/v1/streams/123/events?page=2&limit=10`)
- [ ] Backward compatibility: offset/cursor-based pagination still works

## Architecture Notes

- No schema or API contract changes (page parameter is additive)
- User-scoped events continue to arrive via server-side user subscription (via authenticated public key)
- Removed unused `users` parameter reduces noise in URL construction and server processing

## Branch

`fix/issues-509-512-514-517`

Push ready. Manual PR creation needed.
## Description
This PR resolves two backend issues:
1. **Issue #524 [Backend] Indexer ignores fee_config_updated and admin_transferred governance events**: Added indexing and SSE broadcasting for `fee_config_updated` and `admin_transferred` contract governance events. Used an elegant, referentially safe `streamId = 0` system stream fallback to preserve Prisma schema integrity and foreign-key constraints.
2. **Issue #525 [Backend] migration_lock.toml declares sqlite but the datasource is postgresql**: Fixed a production-blocking validation mismatch in Prisma by changing `provider` from `"sqlite"` to `"postgresql"` in `migration_lock.toml`.

## Type of Change
<!-- Mark the relevant option with an 'x' -->

- [x] 🐛 Bug fix (non-breaking change which fixes an issue)
- [x] ✨ New feature (non-breaking change which adds functionality)
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] 📚 Documentation update
- [ ] 🔧 Refactoring (no functional changes)
- [ ] ⚡ Performance improvement
- [x] 🧪 Test addition or update

## Related Issues
<!-- Link related issues using keywords like "Closes", "Fixes", "Resolves" -->
<!-- Example: Closes #123, Fixes #456 -->

Closes #524
Closes #525

## Changes Made
- **Database Configuration**:
- Updated `provider` in `backend/prisma/migrations/migration_lock.toml` to `"postgresql"`.
- **Worker Indexer (`soroban-event-worker.ts`)**:
- Added `decodeU32` helper function to decode `u32` event values.
- Implemented `ensureSystemStream` to upsert a system user and stream with `streamId = 0` during transaction callbacks to prevent database foreign-key constraint violations when writing protocol-level events.
- Modified `processEvent` to allow single-topic events (`topic.length === 1`) for the two new protocol events while keeping the `streamId` requirement for per-stream events.
- Implemented `handleFeeConfigUpdated` and `handleAdminTransferred` handlers to write records of types `FEE_CONFIG_UPDATED` and `ADMIN_TRANSFERRED` and broadcast payloads over the SSE admin channels `stream.fee_config_updated` and `stream.admin_transferred`.
- **Classification Lists**:
- Added `FEE_CONFIG_UPDATED` and `ADMIN_TRANSFERRED` to allowed list arrays in `events.routes.ts` (`EVENT_TYPES`) and `stream.controller.ts` (`validEventTypes`).
- **Syntax and Compile Fixes**:
- Resolved a pre-existing syntax error (missing arrow function opening brace `{`) in `backend/tests/integration/stream-actions.test.ts` to allow the test runner to compile all files.

## Testing
<!-- Describe the tests you ran and how to verify your changes -->

### Test Coverage
- [x] Unit tests added/updated
- [x] Integration tests added/updated
- [x] Manual testing performed

### Test Steps
<!-- If applicable, provide steps to test the changes -->
1. Run mocked event worker unit tests validating governance event decoding, database upserts, and SSE broadcast trigger:
```bash
npx vitest run tests/soroban-event-worker.test.ts
```
2. Run database-mocked integration tests asserting the event lifecycle:
```bash
DATABASE_URL=postgresql://localhost/flowfi npx vitest run src/__tests__/integration/streams.test.ts
```
3. Verify that the Prisma migrate status check successfully resolves schema and migration lock providers without crashing:
```bash
DATABASE_URL=postgresql://localhost/flowfi npx prisma migrate status
```

## Breaking Changes
<!-- If this PR includes breaking changes, describe them here -->
<!-- If none, you can remove this section -->

## Screenshots/Demo
<!-- If applicable, add screenshots or a link to a demo -->

## Checklist
<!-- Mark completed items with an 'x' -->

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published
- [x] I have checked for breaking changes and documented them if applicable

## Additional Notes
<!-- Any additional information that reviewers should know -->
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@types/supertest": "^7.2.0",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.6",
"@vitest/coverage-v8": "^2.1.8",
"eventsource": "^2.0.2",
"nodemon": "^3.1.11",
"prisma": "^7.4.1",
Expand Down
2 changes: 1 addition & 1 deletion backend/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"
provider = "postgresql"
124 changes: 124 additions & 0 deletions backend/src/__tests__/integration/streams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { mockPrisma, mockSseService } = vi.hoisted(() => ({
addClient: vi.fn(),
broadcastToStream: vi.fn(),
broadcastToUser: vi.fn(),
broadcastToAdmin: vi.fn(),
},
mockPrisma: {
stream: {
Expand Down Expand Up @@ -243,4 +244,127 @@ describeIfDatabase('Stream Lifecycle Integration Tests', () => {
expect(Array.isArray(res.body.data)).toBe(true);
expect(res.body.data.length).toBe(1);
});

it('Indexer processes fee_config_updated -> persists FEE_CONFIG_UPDATED record and broadcasts SSE', async () => {
const adminKey = Keypair.random().publicKey();
const oldTreasury = Keypair.random().publicKey();
const newTreasury = Keypair.random().publicKey();

const event = {
id: 'fee-config-event-1',
txHash: 'hash-fee-config',
ledger: 105,
inSuccessfulContractCall: true,
topic: [
xdr.ScVal.scvSymbol('fee_config_updated'),
],
value: xdr.ScVal.scvMap([
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('admin'),
val: nativeToScVal(adminKey, { type: 'address' }),
}),
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('old_treasury'),
val: nativeToScVal(oldTreasury, { type: 'address' }),
}),
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('new_treasury'),
val: nativeToScVal(newTreasury, { type: 'address' }),
}),
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('old_fee_rate_bps'),
val: nativeToScVal(100, { type: 'u32' }),
}),
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('new_fee_rate_bps'),
val: nativeToScVal(200, { type: 'u32' }),
}),
]),
} as any;

await sorobanEventWorker.processEvent(event);

expect(mockPrisma.user.upsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { publicKey: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF' },
})
);

expect(mockPrisma.stream.upsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { streamId: 0 },
})
);

expect(mockPrisma.streamEvent.upsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { transactionHash_eventType: { transactionHash: 'hash-fee-config', eventType: 'FEE_CONFIG_UPDATED' } },
create: expect.objectContaining({
streamId: 0,
eventType: 'FEE_CONFIG_UPDATED',
transactionHash: 'hash-fee-config',
ledgerSequence: 105,
}),
})
);

expect(mockSseService.broadcastToAdmin).toHaveBeenCalledWith(
'stream.fee_config_updated',
expect.objectContaining({
admin: adminKey,
oldTreasury,
newTreasury,
oldFeeRateBps: 100,
newFeeRateBps: 200,
})
);
});

it('Indexer processes admin_transferred -> persists ADMIN_TRANSFERRED record and broadcasts SSE', async () => {
const previousAdmin = Keypair.random().publicKey();
const newAdmin = Keypair.random().publicKey();

const event = {
id: 'admin-transfer-event-1',
txHash: 'hash-admin-transfer',
ledger: 106,
inSuccessfulContractCall: true,
topic: [
xdr.ScVal.scvSymbol('admin_transferred'),
],
value: xdr.ScVal.scvMap([
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('previous_admin'),
val: nativeToScVal(previousAdmin, { type: 'address' }),
}),
new xdr.ScMapEntry({
key: xdr.ScVal.scvSymbol('new_admin'),
val: nativeToScVal(newAdmin, { type: 'address' }),
}),
]),
} as any;

await sorobanEventWorker.processEvent(event);

expect(mockPrisma.streamEvent.upsert).toHaveBeenCalledWith(
expect.objectContaining({
where: { transactionHash_eventType: { transactionHash: 'hash-admin-transfer', eventType: 'ADMIN_TRANSFERRED' } },
create: expect.objectContaining({
streamId: 0,
eventType: 'ADMIN_TRANSFERRED',
transactionHash: 'hash-admin-transfer',
ledgerSequence: 106,
}),
})
);

expect(mockSseService.broadcastToAdmin).toHaveBeenCalledWith(
'stream.admin_transferred',
expect.objectContaining({
previousAdmin,
newAdmin,
})
);
});
});

2 changes: 1 addition & 1 deletion backend/src/controllers/stream.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export const getStreamEvents = async (req: Request, res: Response) => {

const whereClause: any = { streamId: parsedStreamId };
if (eventType) {
const validEventTypes = ['CREATED', 'TOPPED_UP', 'WITHDRAWN', 'CANCELLED', 'COMPLETED', 'PAUSED', 'RESUMED', 'FEE_COLLECTED'];
const validEventTypes = ['CREATED', 'TOPPED_UP', 'WITHDRAWN', 'CANCELLED', 'COMPLETED', 'PAUSED', 'RESUMED', 'FEE_COLLECTED', 'FEE_CONFIG_UPDATED', 'ADMIN_TRANSFERRED'];
if (!validEventTypes.includes(eventType)) {
return res.status(400).json({
error: 'Invalid eventType parameter',
Expand Down
2 changes: 2 additions & 0 deletions backend/src/routes/v1/events.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const EVENT_TYPES = new Set([
'PAUSED',
'RESUMED',
'FEE_COLLECTED',
'FEE_CONFIG_UPDATED',
'ADMIN_TRANSFERRED',
]);

const MAX_EVENT_LIMIT = 200;
Expand Down
Loading
Loading