-
Notifications
You must be signed in to change notification settings - Fork 11
Main #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Main #39
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| export type EventType = | ||
| | 'contract_deploy' | ||
| | 'payment' | ||
| | 'trust_change' | ||
| | 'contract_invoke' | ||
| | 'data_entry' | ||
| | 'account_create'; | ||
|
|
||
| export type Significance = 'low' | 'medium' | 'high' | 'critical'; | ||
|
|
||
| export interface MonitoredAccount { | ||
| id: string; | ||
| stellar_address: string; | ||
| label: string; | ||
| account_type: string; | ||
| monitor_events: string[]; | ||
| active: boolean; | ||
| last_cursor?: string | null; | ||
| created_at: string; | ||
| } | ||
|
|
||
| export interface ActivityEvent { | ||
| id: string; | ||
| account_id: string; | ||
| source_account: string; | ||
| event_type: EventType; | ||
| significance: Significance; | ||
| raw_data: any; | ||
| summary?: string; | ||
| processed: boolean; | ||
| draft_article_id?: string | null; | ||
| detected_at: string; | ||
| tx_hash?: string | null; | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,14 @@ | ||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||
| import { createAdminClient } from '@/lib/supabase/admin'; | ||||||||||||||||||||||||||||
| import { ActivityFeed } from '@/components/modules/admin/activity/ActivityFeed'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export default async function ActivityPage() { | ||||||||||||||||||||||||||||
| const supabase = createAdminClient(); | ||||||||||||||||||||||||||||
| const { data: events } = await supabase.from('activity_events').select('*').order('detected_at', { ascending: false }).limit(50); | ||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||
| <h1>Admin β Activity</h1> | ||||||||||||||||||||||||||||
| <ActivityFeed events={(events || []) as any} /> | ||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π©Ί Stability & Availability | π Major | β‘ Quick win Don't hide query failures behind an empty feed. If the Supabase query errors, this silently renders no activity and looks like a real empty state. Throw or surface Possible fix- const { data: events } = await supabase.from('activity_events').select('*').order('detected_at', { ascending: false }).limit(50);
+ const { data: events, error } = await supabase.from('activity_events').select('*').order('detected_at', { ascending: false }).limit(50);
+ if (error) throw error;π Committable suggestion
Suggested change
π§° Toolsπͺ ESLint[error] 11-11: Unexpected any. Specify a different type. ( πͺ GitHub Check: lint-and-build[failure] 11-11: π€ Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Maintainability & Code Quality | π‘ Minor | β‘ Quick win Remove the This is the lint failure on this file, and it defeats the Possible fix- <ActivityFeed events={(events || []) as any} />
+ <ActivityFeed events={events ?? []} />π Committable suggestion
Suggested change
π§° Toolsπͺ ESLint[error] 11-11: Unexpected any. Specify a different type. ( πͺ GitHub Check: lint-and-build[failure] 11-11: π€ Prompt for AI AgentsSources: Linters/SAST tools, Pipeline failures |
||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import React from 'react'; | ||
| import type { ActivityEvent } from '@/@types/activity'; | ||
|
|
||
| export function ActivityFeed({ events }: { events: ActivityEvent[] }) { | ||
| return ( | ||
| <div> | ||
| <h2>Activity Feed</h2> | ||
| <ul> | ||
| {events.map((e) => ( | ||
| <li key={e.id}> | ||
| <strong>{e.event_type}</strong> β {e.summary} β <em>{e.significance}</em> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||||||||||
| import fetch from 'node-fetch'; | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π©Ί Stability & Availability | π Major π§© Analysis chainπ Script executed: #!/bin/bash
# Description: Verify whether node-fetch is declared for the runtimes that import src/lib/stellar/index.ts.
set -euo pipefail
rg -n "from ['\"]node-fetch['\"]|require\\(['\"]node-fetch['\"]\\)" .
fd -t f '^(package\.json|deno\.jsonc?|import_map\.json)$' . --exec sh -c '
echo "--- $1"
sed -n "1,220p" "$1"
' sh {}Repository: ACTA-Team/news-acta Length of output: 1472 Avoid
Remove the unused import to prevent potential build failures: Proposed diff-import fetch from 'node-fetch';π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export interface HorizonOperation { | ||||||||||||||||||||||
| id: string; | ||||||||||||||||||||||
| paging_token: string; | ||||||||||||||||||||||
| type: string; | ||||||||||||||||||||||
| transaction_hash?: string; | ||||||||||||||||||||||
| source_account?: string; | ||||||||||||||||||||||
| [key: string]: any; | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π Major π§© Analysis chainπ Script executed: cat -n src/lib/stellar/index.tsRepository: ACTA-Team/news-acta Length of output: 2590 π Script executed: python3 - <<'PY'
import json
# Simulate the TypeScript index signature behavior
# The original: [key: string]: any
# TS Behavior: A property accessed with [string] can accept value of type 'any'.
# Replacing with 'unknown':
# 1. The interface becomes: { [key: string]: unknown }.
# 2. Assigning an object `data` (JSON) to a variable of this type is valid (record).
# 3. The issue is `map((r: any) => r as HorizonOperation)`.
# If the interface has `[key: string]: any`, and data comes in, mapping `r` to `HorizonOperation` is fine.
# If we change index to `unknown`, the variable `r` inside map (inferred from `records`) would be `unknown`.
# `r as HorizonOperation` on `unknown` is valid.
# 4. However, the proposed fix in the original comment suggests a structural type `HorizonOperationsResponse`.
# 5. Let's verify if the specific `no-explicit-any` check flags the index signature.
# Yes, in strict mode `any` in an index signature triggers `no-explicit-any` if it's not a workaround.
# But wait, does the map usage need fixing?
# Line 25: const ops: HorizonOperation[] = (data._embedded?.records || []).map((r: any) => r as HorizonOperation);
# If we fix line 9 to 'unknown', line 25's `(r: any)` is still an explicit `any`.
# The original comment says "lines 9-9 and 25-25".
# If index signature is `any`, it flags line 9.
# If parameter `r` is `any`, it flags line 25.
# Proposed change:
# Line 9: [key: string]: unknown; -> This is a known workaround for unknown additional props but often requires type narrowing when accessing.
# BUT, the `no-explicit-any` rule *specifically* prohibits `any`. `unknown` is the replacement.
# However, changing line 9 to `unknown` makes line 25's `r` type `unknown` (implicitly) or `any` (explicitly).
# The proposed fix in the original comment adds an interface structure for better typing.
# Let's check if the proposed fix is better practice.
# Current: `[key: string]: any` allows everything.
# Better: `Record<string, unknown>` or specific types.
# The code uses `r as HorizonOperation`. If `data._embedded.records` has `unknown` entries, we still need to cast or verify.
# The `any` on index signature is definitely a lint violation if `no-explicit-any` is on.
# The `any` on the map parameter `r` is ALSO a lint violation.
# Conclusion: The issue is real on both lines.
# Line 9: `[key: string]: any` -> Replace with `Record<string, unknown>` or `[key: string]: unknown`.
# Line 25: `r: any` -> Remove the type annotation and let TS infer `unknown` from the array, or use the cast directly.
# The original comment suggests `[key: string]: unknown` and updating the response type.
# I will rewrite the comment to include both fixes (line 9 and 25) clearly.
print("Verification complete: Both line 9 and 25 contain explicit 'any' triggering lint rules.")
PYRepository: ACTA-Team/news-acta Length of output: 149 Replace explicit Update the
export interface HorizonOperation {
// ...
- [key: string]: any;
+ [key: string]: unknown;
}- const ops: HorizonOperation[] = (data._embedded?.records || []).map((r: any) => r as HorizonOperation);
+ const ops: HorizonOperation[] = (data._embedded?.records || []).map((r) => r as HorizonOperation);The parameter π§° Toolsπͺ ESLint[error] 9-9: Unexpected any. Specify a different type. ( πͺ GitHub Check: lint-and-build[failure] 9-9: π€ Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export async function fetchOperationsSince( | ||||||||||||||||||||||
| horizonUrl: string, | ||||||||||||||||||||||
| account: string, | ||||||||||||||||||||||
| cursor?: string | ||||||||||||||||||||||
| ): Promise<{ operations: HorizonOperation[]; nextCursor?: string }> { | ||||||||||||||||||||||
| const url = new URL(`${horizonUrl.replace(/\/$/, '')}/accounts/${account}/operations`); | ||||||||||||||||||||||
| url.searchParams.set('limit', '200'); | ||||||||||||||||||||||
| url.searchParams.set('order', 'asc'); | ||||||||||||||||||||||
| if (cursor) url.searchParams.set('cursor', cursor); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const res = await fetch(url.toString()); | ||||||||||||||||||||||
| if (!res.ok) throw new Error(`Horizon fetch failed: ${res.status}`); | ||||||||||||||||||||||
|
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π©Ί Stability & Availability | π Major Bound the Horizon request with a timeout The Suggested implementation const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 10_000);
- const res = await fetch(url.toString());
+ const res = await fetch(url.toString(), { signal: controller.signal }).finally(() => {
+ clearTimeout(timeout);
+ });
if (!res.ok) throw new Error(`Horizon fetch failed: ${res.status}`);π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||
| const data = await res.json(); | ||||||||||||||||||||||
| const ops: HorizonOperation[] = (data._embedded?.records || []).map((r: any) => r as HorizonOperation); | ||||||||||||||||||||||
| const nextCursor = ops.length ? ops[ops.length - 1].paging_token : undefined; | ||||||||||||||||||||||
| return { operations: ops, nextCursor }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export function classifyOperation(op: HorizonOperation): { eventType: string; significance: string; summary?: string } { | ||||||||||||||||||||||
| const t = op.type; | ||||||||||||||||||||||
| if (t === 'create_account') return { eventType: 'account_create', significance: 'low', summary: `Account created: ${op.account}` }; | ||||||||||||||||||||||
| if (t === 'payment' || t === 'payment_strict_receive') { | ||||||||||||||||||||||
| const amount = op.amount || op.value || '0'; | ||||||||||||||||||||||
| const significance = Number(amount) > 100 ? 'high' : 'medium'; | ||||||||||||||||||||||
| return { eventType: 'payment', significance, summary: `Payment ${amount} ${op.asset_type || 'XLM'}` }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+33
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π‘ Minor π§© Analysis chainπ Web query:
π‘ Result: In the Stellar Horizon API, the operation record type for a path payment where the source amount is fixed is represented by the string "path_payment_strict_send" [1][2]. When querying Horizon for operations, this operation is identified in JSON responses by the following key fields: - type: "path_payment_strict_send" [1] - type_i: 13 [1] The path_payment_strict_send operation is used to send a payment from one account to another through a path of order books, where the sender designates the exact amount of the source asset to be sent [1]. This contrasts with a standard path_payment_strict_receive operation, where the receiver designates the exact amount they wish to receive [2]. Key properties present in the operation record include: - source_account: The account originating the payment [1]. - source_asset: The asset type and details (code/issuer) being sent [1]. - source_amount: The exact amount of the source asset to be sent [1]. - destination: The account receiving the payment [1]. - dest_asset: The asset the destination account receives [1]. - dest_min: The minimum amount of the destination asset the sender is willing to accept [1]. - path: An array of assets representing the path taken through the order books [1]. For developers, these operations are handled via Stellar SDKs (such as stellar-sdk for Python or JavaScript), which provide dedicated objects and builders (e.g., PathPaymentStrictSend) that map to the underlying XDR operation type PATH_PAYMENT_STRICT_SEND [3][4][5][6]. Citations:
π Script executed: cat -n src/lib/stellar/index.ts | sed -n '30,50p'Repository: ACTA-Team/news-acta Length of output: 1407 Include The operation type Update the condition at line 33 to match strict send payments: - if (t === 'payment' || t === 'payment_strict_receive') {
+ if (t === 'payment' || t === 'payment_strict_receive' || t === 'payment_strict_send') {π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||
| if (t === 'change_trust') return { eventType: 'trust_change', significance: 'medium', summary: `Trustline change` }; | ||||||||||||||||||||||
| if (t === 'manage_data') return { eventType: 'data_entry', significance: 'low', summary: `Data entry updated` }; | ||||||||||||||||||||||
| if (t === 'invoke_host_function' || t === 'invoke_contract') return { eventType: 'contract_invoke', significance: 'high', summary: `Contract invoked` }; | ||||||||||||||||||||||
| if (t === 'deploy_contract') return { eventType: 'contract_deploy', significance: 'high', summary: `Contract deployed` }; | ||||||||||||||||||||||
| return { eventType: t, significance: 'low', summary: `Operation ${t}` }; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,55 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { createAdminClient } from '../../../src/lib/supabase/admin'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { fetchOperationsSince, classifyOperation } from '../../../src/lib/stellar'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const HORIZON_URL = process.env.STELLAR_HORIZON_URL || 'https://horizon.stellar.org'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default async function handler() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const supabase = createAdminClient(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // fetch active monitored accounts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: accounts } = await supabase.from('monitored_accounts').select('*').eq('active', true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!accounts) return { processed: 0 }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π©Ί Stability & Availability | π Major π§© Analysis chainπ Script executed: cat -n supabase/functions/activity_scanner/index.tsRepository: ACTA-Team/news-acta Length of output: 2461 π Script executed: cd /workspace/supabase/functions/activity_scanner
cat -n index.tsRepository: ACTA-Team/news-acta Length of output: 290 π Script executed: cat -n "supabase/functions/activity_scanner/index.ts"Repository: ACTA-Team/news-acta Length of output: 2461 π Script executed: cat -n "src/lib/supabase/admin.ts"Repository: ACTA-Team/news-acta Length of output: 644 π Script executed: python3 - <<'PY'
import json
# Simulating Supabase client response for select().eq()
# If query fails (e.g., network issue, permission denied, DB error),
# the standard supabase-js response is: error != null, and data is null.
# If query succeeds but finds no rows, response is: error == null, data == [].
# Current Code Logic at lines 10-11:
# const { data: accounts } = await supabase.from('monitored_accounts').select('*').eq('active', true);
# if (!accounts) return { processed: 0 };
# Scenario 1: Query FAILED (e.g., 500 error)
# Response: { data: null, error: { message: '...', ... } }
# Variable accounts = null
# if (!accounts) is TRUE -> Returns { processed: 0 } silently.
# CONSEQUENCE: The caller (e.g., Scheduled Function) sees success but 0 processed.
# An outage is masked as "nothing to do".
# Scenario 2: Query SUCCESS, no active accounts
# Response: { data: [], error: null }
# Variable accounts = []
# typeof [] is "object", ![] is FALSE.
# Loop `for (const acct of accounts)` iterates 0 times.
# Returns { processed: 0 } explicitly.
# CONSEQUENCE: Correctly reflects "no work done".
# The bug is specific to Scenario 1 (Error vs No Results).
# The proposed fix in the original comment checks for `accountsError` explicitly.
# Let's verify if the file has any import for `SupabaseResponse` type or similar
# to see if error destructuring is expected elsewhere.
# We also need to confirm if the current code throws on error or swallows it.
print("Analysis Result:")
print("The current code destructures ONLY `data`.")
print("When Supabase query fails, `data` is `null`.")
print("The check `if (!accounts)` treats `null` (error) and potentially other falsy values identically to 'no data'.")
print("However, if the query returns an empty array (success), `data` is `[]`.")
print("'!' converts `[]` to `false`, so `if (!accounts)` is FALSE for empty results.")
print("Wait, let's re-verify JS truthy/falsy.")
print(f"Boolean({[]}): {bool([])}") # This is True (Objects are truthy)
# Actually `![]` is false. So `if (!accounts)` with accounts=[] is FALSE.
# The code proceeds to `for (const acct of [])` which does nothing.
# It returns `{ processed: 0 }` at the end. That is fine.
# But for Error: accounts = null. `!null` is True.
# Code returns `{ processed: 0 }` immediately.
# Error is ignored. No alerting, no propagation.
# Conclusion: The original review comment is correct.
# The missing error check swallows database failures.
PYRepository: ACTA-Team/news-acta Length of output: 580 π Script executed: node - <<'JS'
// Verify the truthiness of an empty array in JavaScript
const emptyArr = [];
console.log("Boolean([]):", Boolean(emptyArr));
console.log("![]:", !emptyArr);
console.log("Type of []:", typeof emptyArr);
// Verify the logic of the current code
// Scenario: Query fails
const errorData = null;
console.log("\nScenario: Query Failed (data is null)");
// Simulating: const { data: accounts } = { data: null, error: ... };
const accountsErrorCase = errorData;
if (!accountsErrorCase) {
console.log("-> Current: Treates as 'no work', returns { processed: 0 }");
} else {
console.log("-> Current: Proceeds to loop");
}
// Scenario: Query succeeds, no rows
const successData = [];
console.log("\nScenario: Success, empty results (data is [])");
console.log("Current check '!successData':", !successData);
if (!successData) {
console.log("-> Current: Treats as 'no work'");
} else {
console.log("-> Current: Proceeds to loop");
for (const item of successData) {
console.log("Processing item");
}
console.log("-> Loop finished empty, returns { processed: 0 }");
}
JSRepository: ACTA-Team/news-acta Length of output: 464 π Script executed: sed -n '18p;27p;54p' "supabase/functions/activity_scanner/index.ts"Repository: ACTA-Team/news-acta Length of output: 269 π Script executed: grep -n "error" "supabase/functions/activity_scanner/index.ts"Repository: ACTA-Team/news-acta Length of output: 222 Hide ingestion outages when Destructure the Proposed fix- const { data: accounts } = await supabase.from('monitored_accounts').select('*').eq('active', true);
- if (!accounts) return { processed: 0 };
+ const { data: accounts, error: accountsError } = await supabase
+ .from('monitored_accounts')
+ .select('*')
+ .eq('active', true);
+
+ if (accountsError) throw accountsError;
+ if (!accounts?.length) return { processed: 0 };π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let processed = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const acct of accounts) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { operations, nextCursor } = await fetchOperationsSince(HORIZON_URL, acct.stellar_address, acct.last_cursor || undefined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const op of operations) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const classification = classifyOperation(op as any); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // basic dedupe: by tx_hash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const txHash = (op as any).transaction_hash || null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π― Functional Correctness | π Major π§© Analysis chainπ Script executed: cat -n supabase/functions/activity_scanner/index.tsRepository: ACTA-Team/news-acta Length of output: 2461 π Script executed: grep -r "type HorizonOperation" --include="*.ts"Repository: ACTA-Team/news-acta Length of output: 157 π Script executed: grep -r "function classifyOperation" --include="*.ts" -A 5 --include="*.d.ts"Repository: ACTA-Team/news-acta Length of output: 758 π Script executed: cat -n src/lib/stellar/index.tsRepository: ACTA-Team/news-acta Length of output: 2590 Remove the redundant
const classification = classifyOperation(op);
// basic dedupe: by tx_hash
- const txHash = (op as any).transaction_hash || null;
+ const txHash = op.transaction_hash ?? null;Also, note that π Committable suggestion
Suggested change
π§° Toolsπͺ ESLint[error] 19-19: Unexpected any. Specify a different type. ( [error] 21-21: Unexpected any. Specify a different type. ( πͺ GitHub Check: lint-and-build[failure] 21-21: [failure] 19-19: π€ Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: existing } = await supabase | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .from('activity_events') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .select('id') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .eq('tx_hash', txHash) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .limit(1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (existing && existing.length) continue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | ποΈ Heavy lift Make dedupe operation-scoped and atomic. Dedupe by π§° Toolsπͺ ESLint[error] 21-21: Unexpected any. Specify a different type. ( πͺ GitHub Check: lint-and-build[failure] 21-21: π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await supabase.from('activity_events').insert([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| account_id: acct.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_account: op.source_account || acct.stellar_address, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event_type: classification.eventType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| significance: classification.significance, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw_data: op, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary: classification.summary, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tx_hash: txHash, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detected_at: new Date().toISOString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processed++; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // update cursor if we got one | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (nextCursor) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await supabase.from('monitored_accounts').update({ last_cursor: nextCursor }).eq('id', acct.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+29
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | β‘ Quick win Check write errors before incrementing or advancing the cursor. Failed inserts/updates are currently ignored, so Proposed fix- await supabase.from('activity_events').insert([
+ const { error: insertError } = await supabase.from('activity_events').insert([
{
account_id: acct.id,
source_account: op.source_account || acct.stellar_address,
@@
detected_at: new Date().toISOString(),
},
]);
+ if (insertError) throw insertError;
processed++;
@@
- await supabase.from('monitored_accounts').update({ last_cursor: nextCursor }).eq('id', acct.id);
+ const { error: cursorError } = await supabase
+ .from('monitored_accounts')
+ .update({ last_cursor: nextCursor })
+ .eq('id', acct.id);
+ if (cursorError) throw cursorError;π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // log and continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error('scan error', acct.stellar_address, err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { processed }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| -- ============================================================================= | ||
| -- Add monitored_accounts table for automated activity monitoring | ||
| -- ============================================================================= | ||
|
|
||
| create table if not exists public.monitored_accounts ( | ||
| id uuid primary key default gen_random_uuid(), | ||
| stellar_address text unique not null, | ||
| label text not null, | ||
| account_type text not null, | ||
| monitor_events text[] not null default '{}', | ||
| active boolean not null default true, | ||
| last_cursor text, | ||
| created_at timestamptz not null default now() | ||
| ); | ||
|
|
||
| create index if not exists monitored_accounts_stellar_address_idx | ||
| on public.monitored_accounts (stellar_address); | ||
|
|
||
| alter table public.monitored_accounts enable row level security; | ||
|
|
||
| create policy "admins can manage monitored accounts" | ||
| on public.monitored_accounts | ||
| for all | ||
| to authenticated | ||
| using (public.is_admin()) | ||
| with check (public.is_admin()); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- ============================================================================= | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Add activity_events table to store detected Stellar activity | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -- ============================================================================= | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| create table if not exists public.activity_events ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| id uuid primary key default gen_random_uuid(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| account_id uuid not null references public.monitored_accounts(id) on delete cascade, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source_account text not null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| event_type text not null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| significance text not null, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raw_data jsonb not null default '{}'::jsonb, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary text, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processed boolean not null default false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| draft_article_id uuid, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detected_at timestamptz not null default now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tx_hash text | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ποΈ Data Integrity & Integration | π Major | β‘ Quick win Constrain The TS layer already limits these fields to a fixed set, but the table still accepts arbitrary text. That lets malformed rows slip in and breaks the contract this schema is trying to establish. Possible fix create table if not exists public.activity_events (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references public.monitored_accounts(id) on delete cascade,
source_account text not null,
- event_type text not null,
- significance text not null,
+ event_type text not null check (event_type in ('contract_deploy', 'payment', 'trust_change', 'contract_invoke', 'data_entry', 'account_create')),
+ significance text not null check (significance in ('low', 'medium', 'high', 'critical')),
raw_data jsonb not null default '{}'::jsonb,π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| create index if not exists activity_events_account_id_detected_at_idx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on public.activity_events (account_id, detected_at desc); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| create index if not exists activity_events_processed_idx | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on public.activity_events (processed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alter table public.activity_events enable row level security; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| create policy "admins can manage activity events" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on public.activity_events | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for all | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| to authenticated | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using (public.is_admin()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with check (public.is_admin()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
Replace
anywith a JSON-safe type.raw_datais persisted JSON, soanyweakens the new contract and already fails lint.unknown(or the concrete payload shape) is safer here.Possible fix
π Committable suggestion
π§° Tools
πͺ ESLint
[error] 28-28: Unexpected any. Specify a different type.
(
@typescript-eslint/no-explicit-any)πͺ GitHub Actions: CI / 0_lint-and-build.txt
[error] 28-28: ESLint (
@typescript-eslint/no-explicit-any): Unexpected any. Specify a different typeπͺ GitHub Actions: CI / lint-and-build
[error] 28-28: ESLint (
@typescript-eslint/no-explicit-any): Unexpected any. Specify a different type.πͺ GitHub Check: lint-and-build
[failure] 28-28:
Unexpected any. Specify a different type
π€ Prompt for AI Agents
Sources: Linters/SAST tools, Pipeline failures