Add a self-service user activity log, and repair the Rust/Soroban CI - #655
Merged
Abdulazeem-code merged 2 commits intoAug 31, 2026
Merged
Conversation
|
@daveades is attempting to deploy a commit to the Abdulazeem's projects Team on Vercel. A member of the Team first needs to authorize it. |
Owner
|
RESOLVE CONFLICTS |
Adds an ActivityLog model and GET /users/:username/activity, authenticated with a Stellar signature over activity:<username>, with page/limit paging and startDate/endDate filtering. Registration, transfer, unregistration, webhook create/delete and block are recorded. Signature verification moves out of webhookRoutes into ownershipService so the webhook routes and the activity endpoint share one implementation. POST /admin/block now flags every username on the address with updateMany. It had kept a single update keyed on address, which stopped resolving when request.
src/lib.rs had been mangled by earlier merges: two thirds of it was one line with literal \n escapes instead of newlines, so nothing downstream of parsing ever ran. Restoring the newlines exposed what the parse error had been hiding: - 12 test functions defined twice, the stale copies calling std::println! in a no_std crate - the soroban vec! macro used in the test module without importing it - three unused bindings in a stub test - a stale Cargo.lock and test snapshots, neither regenerated since the crate last built .cargo/config.toml enabled clippy::pedantic and clippy::nursery under -Dwarnings, which the contract has never satisfied. It now allows the lints that Soroban's macros make unavoidable and drops nursery, which clippy documents as unstable. test_route_payments_multi_token_batch is ignored rather than deleted: route_payments calls require_auth once per payment, so a batch with two payments from the same sender fails authorization. Two different senders pass, so this is a contract bug and not a test bug. lib_main.rs removed: a UTF-16 copy of lib.rs with nothing unique in it.
daveades
force-pushed
the
feat/user-activity-log
branch
from
August 31, 2026 12:39
914438c to
b2359de
Compare
|
@daveades 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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #599
Two things in one branch: the activity log the issue asks for, and a repair of the Rust/Soroban CI jobs.
1. Self-service activity log
GET /users/:username/activityOwnership is proven the way the webhook endpoints already prove it: a signature over
activity:<username>made with the account key, either a Freighter-style signed message or a multi-signer threshold.The signature is also accepted in the body as
signature/signerAddress, matchingGET /webhooks.Paging is
page/limit(default 10, capped at 100) through the existingparsePagination/paginatedResponsehelpers, so the response carriesmeta.totalandmeta.totalPages.startDateandendDateboundcreated_at; an unparseable or inverted range is a 400 before the query runs. Rows come back newest first, tie-broken onidso pages stay stable for events written in the same millisecond.The trail is read for the authenticated username, not the path parameter, so a valid signature for one account cannot read another's rows.
Logged actions:
user.registered,user.unregistered,user.transferred,user.blocked,webhook.created,webhook.deleted.Writes go through
recordActivity, which resolves tonullrather than throwing: an activity row records a request, it is not part of one, so a logging failure must not turn a successful registration into a 500. Metadata over 2KB is replaced with{ truncated: true }.POST /registeris handled twice, byserver.jsand byuserRoutes.js, with theserver.jsmount winning. Both are instrumented, since which one runs is not obvious from either file.Two changes beyond the new code
Signature verification moved out of
webhookRoutes.jsintosrc/services/ownershipService.js. It was defined inside the router factory, so it could not be imported, and the activity endpoint needs the same check.authenticateWebhookCallis now a thin wrapper over the shared function and its behaviour is unchanged.POST /admin/blockwas returning 500 for every request. It usedprisma.user.update({ where: { address } }), which stopped resolving when #613 dropped the unique index onaddress:It now uses
updateMany, which is also the correct semantics: an address can carry several usernames and blocking it has to flag all of them. The response keepsusername(the primary) and gainsusernames. It is in this PR because blocking is one of the events the issue asks to be logged, and the fix and the logging land on the same lines.Verification
tests/activity.test.js(24 tests) covers the service;tests/activity-endpoint.test.js(11 tests) mounts the realuserRoutesrouter and covers the signed message, 401/404 propagation, reading the owner's trail rather than the path parameter, the response shape, paging, limit clamping, date filtering, and both signature sources.End to end against a scratch PostgreSQL 16 instance with the server running, 21 checks: register, create and delete a webhook, then read the trail as the owner. Missing signature 400, non-owner signer 401, valid signature for a different account 401, newest-first ordering, metadata round trip, correct
meta.total, non-overlapping pages, futurestartDateempty, spanning range complete, bad dates 400, and a second user seeing only their own row. Blocking an address with three usernames flags and logs all three and reports the primary first; an unknown address is still 404.prisma migrate diffreports no drift foractivity_logs. Backend suite: 50 suites, 835 tests.2. Rust and Soroban CI
payment_router/src/lib.rshad been mangled by earlier merges. Two thirds of it was a single 85KB line with literal\nescapes instead of newlines, so every Rust job died at parsing:That failure had been masking everything behind it. Restoring the newlines surfaced:
std::println!in ano_stdcrate. The surviving copies are the newer ones, which uselog!and carry the#[ignore]attributes.vec!used in the test module without importing it. Soroban'svec!now comes in with the othersoroban_sdkimports.Cargo.lockand stale test snapshots, neither regenerated since the crate last compiled.proptest's dependencies were missing from the lockfile entirely..cargo/config.tomlenabledclippy::pedanticandclippy::nurseryunder-Dwarnings, a combination the contract has never satisfied: 92 errors, 47 of themneedless_pass_by_valueon entrypoints that#[contractimpl]requires to takeEnvandAddressby value. The config now allows the lints Soroban's macros make unavoidable, keepsclippy::allandpedanticotherwise, and dropsnursery, which clippy documents as unstable and not meant to gate CI. Each allow carries its reason. This is the one judgement call here and it is a policy choice you may want to tighten lint by lint.lib_main.rsis removed: a UTF-16 copy oflib.rscontaining no function absent from it.One thing left failing, deliberately
test_route_payments_multi_token_batchis marked#[ignore]with a reason rather than fixed.route_paymentscallssender.require_auth()once per payment, so a batch containing two payments from the same sender fails authorization and aborts the host. A batch with two different senders passes, which is how I isolated it. That is a contract bug, not a test bug, and changing auth logic in a payment router is not something to slip into this PR. Worth its own issue.lib_head.rsis left in place: unlikelib_main.rsit holds three functions that exist nowhere else (route_payment_with_memo,test_route_payment_with_memo,test_route_payment_with_invalid_memo), so a memo feature appears to have been lost in the same merge. It is mojibake-encoded and I have not tried to recover it.Verification
Every step the two Rust workflows run, locally:
The
cargo dylintandcargo auditsteps need network installs and were not run locally, so those two remain unverified.