-
Notifications
You must be signed in to change notification settings - Fork 7.9k
feat: Implement AI cryptocurrency analysis features #6390
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
base: main
Are you sure you want to change the base?
Conversation
This commit introduces a new set of features for AI-driven cryptocurrency analysis and portfolio management. Key additions include: 1. **Backend Mock API Endpoints (`apps/backend-mock/api/crypto/`)**: * `recommendations.post.ts`: Generates AI trading suggestions (mocked) based on currency, timeframe, and risk appetite. Simulates token deduction. * `portfolio.get.ts`: Fetches your cryptocurrency portfolio (mocked). * `apikeys.post.ts`: Allows binding of exchange API keys (mocked). * `recharge.post.ts`: Handles token recharges (mocked). * Includes a simple in-memory `tokenStore.ts` for managing mock token balances. 2. **Frontend Vue Components (`apps/web-naive/src/views/crypto/`)**: * `Recommendations.vue`: UI for getting AI recommendations, displaying results, and current token balance. * `Portfolio.vue`: UI for displaying current crypto holdings and historical positions. * `ApiKeys.vue`: UI for managing exchange API keys. * `Recharge.vue`: UI for recharging 'Like' or 'USDT' tokens. * All components are built using Naive UI and designed with mobile-first principles in mind. 3. **Routing (`apps/web-naive/src/router/routes/modules/crypto.ts`)**: * Added new routes under the `/crypto` path for all the new feature components. * Configured with metadata for automatic sidebar menu generation. 4. **State Management (`apps/web-naive/src/store/crypto.ts`)**: * Introduced `useCryptoStore` (Pinia) to manage state for token balances, portfolio data, and API keys. * Includes actions for interacting with the mock backend services. 5. **Localization (`packages/locales/src/langs/zh-CN/crypto.json`)**: * Added Chinese translations for all new UI elements and menu items. * Route metadata updated to use localization keys. 6. **Unit Tests**: * `apps/web-naive/src/store/crypto.spec.ts`: Tests for the Pinia store (initial state, actions, getters). * `apps/web-naive/src/views/crypto/tests/`: Component tests covering rendering, basic interactions, and store integration where applicable. These features provide a foundational framework for the requested cryptocurrency analysis tools within the Vben Admin structure.
|
WalkthroughThis update introduces a comprehensive cryptocurrency management module, adding backend API endpoints for portfolio, token, and recommendation operations, a mock token store, and a frontend Vue store with corresponding views and routes. It also includes localization, extensive unit tests, and user interfaces for portfolio, recommendations, API key management, and token recharge. Changes
Sequence Diagram(s)Crypto Recommendation FlowsequenceDiagram
participant User
participant RecommendationsView
participant CryptoStore
participant BackendAPI
User->>RecommendationsView: Submit recommendation form
RecommendationsView->>CryptoStore: generateRecommendation(payload)
CryptoStore->>BackendAPI: POST /api/crypto/recommendations
BackendAPI-->>CryptoStore: Recommendation & order details
CryptoStore-->>RecommendationsView: Recommendation data
RecommendationsView-->>User: Display recommendation & order
API Key Binding FlowsequenceDiagram
participant User
participant ApiKeysView
participant CryptoStore
participant BackendAPI
User->>ApiKeysView: Submit API key form
ApiKeysView->>CryptoStore: bindApiKey(payload)
CryptoStore->>BackendAPI: POST /api/crypto/apikeys
BackendAPI-->>CryptoStore: Success response
CryptoStore-->>ApiKeysView: Update API keys
ApiKeysView-->>User: Show updated API key list
Token Recharge FlowsequenceDiagram
participant User
participant RechargeView
participant CryptoStore
participant BackendAPI
User->>RechargeView: Submit recharge form
RechargeView->>CryptoStore: rechargeTokens(payload)
CryptoStore->>BackendAPI: POST /api/crypto/recharge
BackendAPI-->>CryptoStore: Updated token balances
CryptoStore-->>RechargeView: Update balances
RechargeView-->>User: Show new balances
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Good |
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.
Actionable comments posted: 14
♻️ Duplicate comments (2)
apps/web-naive/src/views/crypto/tests/Portfolio.spec.ts (1)
8-15
: Samevi.mock
issue as aboveUse
vi.importActual
instead of the unused parameter to avoid a runtime
TypeError.apps/web-naive/src/views/crypto/tests/Recharge.spec.ts (1)
8-15
: Repeat of incorrectvi.mock
factoryFix as noted in ApiKeys.spec.ts to prevent runtime failure.
🧹 Nitpick comments (18)
apps/backend-mock/api/crypto/tokenStore.ts (1)
7-10
: Preferconst
overlet
for the store reference
userTokenBalances
is mutated but never reassigned. Declaring the binding asconst
prevents accidental rebinding while still allowing internal mutation.-export let userTokenBalances: TokenBalances = { +export const userTokenBalances: TokenBalances = {apps/backend-mock/api/crypto/recharge.post.ts (1)
28-34
: Token type check is case-sensitive; consider normalising inputEnd-users may send
"LIKE"
/"USDT"
. Normalising once avoids multiple validations:- if (tokenType !== 'like' && tokenType !== 'usdt') { + const normalisedType = tokenType.toLowerCase() as RechargeRequest['tokenType']; + if (normalisedType !== 'like' && normalisedType !== 'usdt') { return res.status(400).json({ error: "Invalid tokenType. Must be 'like' or 'usdt'." }); }apps/backend-mock/api/crypto/apikeys.post.ts (1)
17-38
:async
is unnecessary hereThere are no
await
calls; keepingasync
turns any thrown error into a rejected promise, hiding stack traces in DevTools.-export default async function handler( +export default function handler(apps/web-naive/src/views/crypto/Recharge.vue (1)
80-83
: Rule definition is malformed
validator
should returntrue | Error | Promise
(or callcallback
) – returning a boolean is insufficient.- Having both
type: 'number'
and a custom validator is redundant.Minimal fix:
amount: { - type: 'number', - required: true, - message: 'Please enter an amount', - trigger: ['blur', 'input'], - validator: (rule:any, value:any) => value > 0 + required: true, + trigger: ['blur','input'], + validator: (_rule, value) => + value > 0 ? true : new Error('Amount must be greater than 0'), }apps/backend-mock/api/crypto/recommendations.post.ts (2)
14-15
: Remove unused import
userTokenBalances
is never referenced and will trip eslint / tscno-unused-vars
.-import { userTokenBalances, consumeTokens } from './tokenStore'; +import { consumeTokens } from './tokenStore';
30-35
: Magic constant for token cost
const tokenCost = 800;
is duplicated in frontend store and tests. Move it to a shared constant (tokenCostPerRecommendation
) to avoid divergence.apps/web-naive/src/views/crypto/tests/Recommendations.spec.ts (3)
48-54
: Selector for button will never matchMock
<n-button>
renders plain<button>
with notype
attribute.
Change expectation to:-expect(wrapper.find('button[type="primary"]').text()).toBe('Get Recommendation'); +expect(wrapper.find('button').text()).toBe('Get Recommendation');
70-75
: AccessingformValue
viawrapper.vm
is unreliableVariables from
<script setup>
are not exposed unlessdefineExpose()
is used, sowrapper.vm.formValue
may beundefined
.
Use DOM interaction (setValue on selects) + assert via emitted payload or expose explicitly in component.
116-119
:vi.mocked(require('naive-ui').useMessage)
bypasses ESM & previous mockThis pattern is brittle and defeats earlier mocking. Prefer:
import { useMessage } from 'naive-ui'; vi.mocked(useMessage).mockReturnValue(mockMessage as any);apps/web-naive/src/views/crypto/tests/ApiKeys.spec.ts (1)
52-58
: Selector never matches – test will fail even if component works
NButton
mock does not rendertype="primary"
, so
wrapper.find('button[type="primary"]')
always returns an empty wrapper.Either add the attribute to the mock or assert by text:
-expect(wrapper.find('button[type="primary"]').text()).toBe('Save API Key'); +expect(wrapper.find('button').text()).toBe('Save API Key');apps/web-naive/src/views/crypto/tests/Portfolio.spec.ts (1)
80-121
: Spy is never exercised – test adds no value
fetchPortfolioSpy
is declared but the component never calls the action,
so the assertion is always negative. Either:
- Remove the dead test, or
- Update
Portfolio.vue
toonMounted(await store.fetchPortfolio())
.Current form only increases maintenance noise.
apps/web-naive/src/store/crypto.spec.ts (1)
18-22
: Hard-coding masked key makes the test brittleMasking logic may change; asserting the full string couples the test to an
implementation detail.-expect(store.apiKeys).toEqual([ - { id: 'binance-mock', exchange: 'Binance', apiKeyMasked: 'AB**...**YZ' }, -]); +expect(store.apiKeys[0]).toMatchObject({ exchange: 'Binance' });apps/web-naive/src/views/crypto/tests/Recharge.spec.ts (2)
50-52
: Selector mismatch – button has notype="primary"
attributeSee the ApiKeys test note; adjust selector or mock.
75-76
: Spying on unused store action inflates test noise
Recharge.vue
does not callstore.rechargeTokens
; the spy always fails
to justify itself. Remove the spy or wire the component to the store.apps/web-naive/src/views/crypto/Portfolio.vue (2)
69-75
: Currency formatting hard-codes$
and omits localeUsing
Intl.NumberFormat
improves i18n and thousands-separator handling:- render: (row) => `$${row.purchasePrice.toFixed(2)}` + render: (row) => + Intl.NumberFormat(undefined, { style: 'currency', currency: 'USD' }) + .format(row.purchasePrice)
90-98
:pnl
union type forces runtimetypeof
checksConsider normalising
pnl
tonumber | null
and deriving'N/A'
in the
render to simplify data contracts.-interface Position { ... pnl: number | string; } +interface Position { ... pnl: number | null; } ... - return row.pnl; + return row.pnl === null ? 'N/A' : row.pnl;apps/web-naive/src/views/crypto/ApiKeys.vue (1)
134-137
:maskApiKey
leaks short keys
If a key is shorter than 8 chars,substring
/slice
will overlap and may reveal the whole secret or return an empty middle. Consider early return for short inputs:- return `${key.substring(0, 4)}...${key.substring(key.length - 4)}`; + if (key.length <= 8) return '****'; + return `${key.slice(0, 4)}...${key.slice(-4)}`;apps/web-naive/src/store/crypto.ts (1)
148-149
: Masking logic fails for keys shorter than 6 chars
substring(0, 3)
+slice(-3)
can overlap or return an empty string. Guard against short inputs:- apiKeyMasked: `${payload.apiKey.substring(0, 3)}...${payload.apiKey.slice(-3)}`, + apiKeyMasked: + payload.apiKey.length <= 6 + ? '***' + : `${payload.apiKey.slice(0, 3)}...${payload.apiKey.slice(-3)}`,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
apps/backend-mock/api/crypto/apikeys.post.ts
(1 hunks)apps/backend-mock/api/crypto/portfolio.get.ts
(1 hunks)apps/backend-mock/api/crypto/recharge.post.ts
(1 hunks)apps/backend-mock/api/crypto/recommendations.post.ts
(1 hunks)apps/backend-mock/api/crypto/tokenStore.ts
(1 hunks)apps/web-naive/src/router/routes/modules/crypto.ts
(1 hunks)apps/web-naive/src/store/crypto.spec.ts
(1 hunks)apps/web-naive/src/store/crypto.ts
(1 hunks)apps/web-naive/src/views/crypto/ApiKeys.vue
(1 hunks)apps/web-naive/src/views/crypto/Portfolio.vue
(1 hunks)apps/web-naive/src/views/crypto/Recharge.vue
(1 hunks)apps/web-naive/src/views/crypto/Recommendations.vue
(1 hunks)apps/web-naive/src/views/crypto/tests/ApiKeys.spec.ts
(1 hunks)apps/web-naive/src/views/crypto/tests/Portfolio.spec.ts
(1 hunks)apps/web-naive/src/views/crypto/tests/Recharge.spec.ts
(1 hunks)apps/web-naive/src/views/crypto/tests/Recommendations.spec.ts
(1 hunks)packages/locales/src/langs/zh-CN/crypto.json
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
apps/backend-mock/api/crypto/apikeys.post.ts (1)
apps/backend-mock/api/crypto/recharge.post.ts (1)
handler
(14-43)
apps/web-naive/src/views/crypto/tests/Recommendations.spec.ts (1)
apps/web-naive/src/store/crypto.ts (1)
useCryptoStore
(55-206)
apps/web-naive/src/store/crypto.spec.ts (1)
apps/web-naive/src/store/crypto.ts (1)
useCryptoStore
(55-206)
apps/web-naive/src/views/crypto/tests/Portfolio.spec.ts (1)
apps/web-naive/src/store/crypto.ts (1)
useCryptoStore
(55-206)
apps/web-naive/src/views/crypto/tests/ApiKeys.spec.ts (1)
apps/web-naive/src/store/crypto.ts (1)
useCryptoStore
(55-206)
apps/web-naive/src/views/crypto/tests/Recharge.spec.ts (1)
apps/web-naive/src/store/crypto.ts (1)
useCryptoStore
(55-206)
🪛 Biome (1.9.4)
apps/backend-mock/api/crypto/tokenStore.ts
[error] 13-13: Do not access Object.prototype method 'hasOwnProperty' from target object.
It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.
(lint/suspicious/noPrototypeBuiltins)
🪛 Gitleaks (8.26.0)
apps/web-naive/src/views/crypto/ApiKeys.vue
101-101: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
102-102: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms (2)
- GitHub Check: post-update (windows-latest)
- GitHub Check: post-update (ubuntu-latest)
🔇 Additional comments (5)
apps/web-naive/src/router/routes/modules/crypto.ts (1)
20-20
: Let’s inspect the Vite config for theweb-naive
app to confirm if any alias or tsconfig-paths plugin is set up:#!/bin/bash # Show imports and config block from apps/web-naive/vite.config.mts sed -n '1,200p' apps/web-naive/vite.config.mtspackages/locales/src/langs/zh-CN/crypto.json (1)
1-98
: LGTM – comprehensive and well-structured translationsKeys match the routing meta and component usage; no missing commas or duplicate keys detected.
apps/backend-mock/api/crypto/portfolio.get.ts (1)
24-46
: Looks good – concise mock with proper typingHandler correctly guards HTTP method, returns typed mock data, and keeps sensitive info out. No issues spotted.
apps/web-naive/src/views/crypto/tests/ApiKeys.spec.ts (1)
86-88
:validate
signature diverges from Naive-UI contract
formRef.validate()
in Naive-UI returns a Promise; supplying a callback
risks masking real-world behaviour. Prefer:-wrapper.vm.formRef = { validate: (cb) => cb(undefined) }; +wrapper.vm.formRef = { validate: () => Promise.resolve() };apps/web-naive/src/store/crypto.spec.ts (1)
37-45
:global.Math
restore is wrong – leaks into subsequent testsYou overwrite
global.Math
but restore it with
Object.getPrototypeOf(mockMath)
, which is justObject.prototype
.-const mockMath = Object.create(global.Math); +const originalMath = global.Math; +const mockMath = { ...originalMath, random: () => 0.5 } as Math; global.Math = mockMath as any; ... -global.Math = Object.getPrototypeOf(mockMath); +global.Math = originalMath;Likely an incorrect or invalid review comment.
export const updateUserTokenBalance = (tokenType: 'like' | 'usdt', amount: number): void => { | ||
if (userTokenBalances.hasOwnProperty(tokenType)) { | ||
userTokenBalances[tokenType] += amount; | ||
} | ||
}; |
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.
🛠️ Refactor suggestion
Replace hasOwnProperty
call to satisfy lint & prototype-safety
Using obj.hasOwnProperty
is discouraged. Switch to Object.hasOwn()
(Node 16.9+) or Object.prototype.hasOwnProperty.call()
.
- if (userTokenBalances.hasOwnProperty(tokenType)) {
- userTokenBalances[tokenType] += amount;
+ if (Object.hasOwn(userTokenBalances, tokenType)) {
+ userTokenBalances[tokenType] += amount;
}
📝 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.
export const updateUserTokenBalance = (tokenType: 'like' | 'usdt', amount: number): void => { | |
if (userTokenBalances.hasOwnProperty(tokenType)) { | |
userTokenBalances[tokenType] += amount; | |
} | |
}; | |
export const updateUserTokenBalance = (tokenType: 'like' | 'usdt', amount: number): void => { | |
if (Object.hasOwn(userTokenBalances, tokenType)) { | |
userTokenBalances[tokenType] += amount; | |
} | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 13-13: Do not access Object.prototype method 'hasOwnProperty' from target object.
It's recommended using Object.hasOwn() instead of using Object.hasOwnProperty().
See MDN web docs for more details.
(lint/suspicious/noPrototypeBuiltins)
🤖 Prompt for AI Agents
In apps/backend-mock/api/crypto/tokenStore.ts between lines 12 and 16, replace
the use of userTokenBalances.hasOwnProperty(tokenType) with a safer alternative
to satisfy lint rules and avoid prototype pollution risks. Use
Object.hasOwn(userTokenBalances, tokenType) if your Node version supports it
(16.9+), or fallback to Object.prototype.hasOwnProperty.call(userTokenBalances,
tokenType) to check property existence safely.
export const consumeTokens = (amount: number): boolean => { | ||
// Assuming "like" tokens are the primary currency for service consumption for now | ||
if (userTokenBalances.like >= amount) { | ||
userTokenBalances.like -= amount; | ||
return true; | ||
} | ||
return false; | ||
}; |
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.
Guard against non-positive or NaN amounts before mutating balances
consumeTokens(-50)
currently credits the user. Add a runtime check:
export const consumeTokens = (amount: number): boolean => {
- // Assuming "like" tokens are the primary currency for service consumption for now
- if (userTokenBalances.like >= amount) {
+ if (!Number.isFinite(amount) || amount <= 0) {
+ return false;
+ }
+ // Assuming "like" tokens are the primary currency for service consumption for now
+ if (userTokenBalances.like >= amount) {
userTokenBalances.like -= amount;
return true;
}
return false;
};
📝 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.
export const consumeTokens = (amount: number): boolean => { | |
// Assuming "like" tokens are the primary currency for service consumption for now | |
if (userTokenBalances.like >= amount) { | |
userTokenBalances.like -= amount; | |
return true; | |
} | |
return false; | |
}; | |
export const consumeTokens = (amount: number): boolean => { | |
if (!Number.isFinite(amount) || amount <= 0) { | |
return false; | |
} | |
// Assuming "like" tokens are the primary currency for service consumption for now | |
if (userTokenBalances.like >= amount) { | |
userTokenBalances.like -= amount; | |
return true; | |
} | |
return false; | |
}; |
🤖 Prompt for AI Agents
In apps/backend-mock/api/crypto/tokenStore.ts between lines 18 and 25, the
consumeTokens function does not check if the amount is positive and valid,
allowing negative or NaN values to incorrectly modify userTokenBalances. Add a
guard clause at the start of the function to return false if the amount is less
than or equal to zero or is NaN, preventing unintended balance mutations.
<n-form @submit.prevent="rechargeTokens" ref="formRef"> | ||
<n-form-item-row label="Token Type" path="tokenType"> | ||
<n-radio-group v-model:value="formValue.tokenType" name="tokenTypeGroup"> | ||
<n-radio-button value="like">LIKE</n-radio-button> | ||
<n-radio-button value="usdt">USDT</n-radio-button> | ||
</n-radio-group> | ||
</n-form-item-row> | ||
|
||
<n-form-item-row label="Amount" path="amount"> | ||
<n-input-number | ||
v-model:value="formValue.amount" | ||
placeholder="Enter amount" | ||
:min="1" | ||
style="width: 100%;" | ||
/> | ||
</n-form-item-row> | ||
<n-button type="primary" attr-type="submit">Recharge Tokens</n-button> | ||
</n-form> |
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.
🛠️ Refactor suggestion
<n-form>
is missing model
and rules
props – validation will never fire
formRef.validate()
will always resolve with an empty errors
array because the form has no validation schema attached.
Attach both the model and the rules:
- <n-form @submit.prevent="rechargeTokens" ref="formRef">
+ <n-form
+ ref="formRef"
+ :model="formValue"
+ :rules="rules"
+ @submit.prevent="rechargeTokens"
+ >
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/Recharge.vue around lines 14 to 31, the
<n-form> component is missing the required model and rules props, so validation
will not trigger and formRef.validate() will always return no errors. Fix this
by binding the form's data model to the model prop and providing a validation
rules object to the rules prop on <n-form>. This will enable proper validation
when the form is submitted.
// Placeholder for token balances - in a real app, this would come from a store or API | ||
const tokenBalances = reactive<TokenBalances>({ | ||
like: 1000, | ||
usdt: 500, | ||
}); |
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.
🛠️ Refactor suggestion
Hard-coded balances break single-source-of-truth
The component duplicates token state that already lives in useCryptoStore
. This creates two independent, divergent sources of truth.
Prefer sourcing balances from – and delegating recharges to – the store:
-import { ref, reactive } from 'vue';
+import { ref, reactive, computed } from 'vue';
+import { useCryptoStore } from '@/store/crypto';
...
-const tokenBalances = reactive<TokenBalances>({
- like: 1000,
- usdt: 500,
-});
+const cryptoStore = useCryptoStore();
+const tokenBalances = computed(() => ({
+ like: cryptoStore.tokensLike,
+ usdt: cryptoStore.tokensUsdt,
+}));
and inside rechargeTokens
call await cryptoStore.rechargeTokens(formValue)
.
📝 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.
// Placeholder for token balances - in a real app, this would come from a store or API | |
const tokenBalances = reactive<TokenBalances>({ | |
like: 1000, | |
usdt: 500, | |
}); | |
import { ref, reactive, computed } from 'vue'; | |
import { useCryptoStore } from '@/store/crypto'; | |
// Placeholder for token balances - in a real app, this would come from a store or API | |
const cryptoStore = useCryptoStore(); | |
const tokenBalances = computed(() => ({ | |
like: cryptoStore.tokensLike, | |
usdt: cryptoStore.tokensUsdt, | |
})); |
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/Recharge.vue around lines 73 to 77, the
tokenBalances are hard-coded locally, causing duplication and breaking the
single-source-of-truth principle. Remove the local reactive tokenBalances object
and instead import and use the token balances from useCryptoStore. Also, update
the rechargeTokens method to call await cryptoStore.rechargeTokens(formValue) to
delegate the recharge logic to the store.
<n-h3>Your Token Balance</n-h3> | ||
<n-statistic label="Tokens" :value="10000" /> <!-- Placeholder --> | ||
|
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.
🛠️ Refactor suggestion
Static balance defeats real-time UI
<n-statistic :value="10000" />
is hard-wired; the real balance lives in useCryptoStore
. Bind to the store instead so the number updates after each recommendation (when tokens are deducted).
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/Recommendations.vue around lines 5 to 7, the
token balance is hardcoded as 10000 in the <n-statistic> component, which
prevents real-time updates. Replace the static value with a reactive binding to
the token balance from useCryptoStore so that the displayed balance updates
dynamically when tokens change after recommendations.
const currentHoldings = ref<Holding[]>([ | ||
{ id: 'btc', asset: 'Bitcoin (BTC)', amount: 2.5, currentPrice: 50000, totalValue: 125000, purchasePrice: 45000 }, | ||
{ id: 'eth', asset: 'Ethereum (ETH)', amount: 10, currentPrice: 3000, totalValue: 30000, purchasePrice: 2500 }, | ||
{ id: 'ada', asset: 'Cardano (ADA)', amount: 5000, currentPrice: 0.5, totalValue: 2500, purchasePrice: 0.4 }, | ||
]); | ||
|
||
const historicalPositions = ref<Position[]>([ | ||
{ id: 'sol-closed', asset: 'Solana (SOL)', entryPrice: 80, exitPrice: 120, amount: 100, timestamp: '2023-10-15T10:00:00Z', status: 'CLOSED', pnl: 4000 }, | ||
{ id: 'dot-open', asset: 'Polkadot (DOT)', entryPrice: 5, amount: 200, timestamp: '2024-01-20T14:30:00Z', status: 'OPEN', pnl: 'N/A' }, | ||
{ id: 'doge-closed', asset: 'Dogecoin (DOGE)', entryPrice: 0.05, exitPrice: 0.15, amount: 100000, timestamp: '2023-05-01T08:20:00Z', status: 'CLOSED', pnl: 10000 }, | ||
{ id: 'link-closed', asset: 'Chainlink (LINK)', entryPrice: 15, exitPrice: 12, amount: 50, timestamp: '2023-11-01T18:00:00Z', status: 'CLOSED', pnl: -150 }, | ||
]); |
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.
🛠️ Refactor suggestion
Static demo data – component never hits real store / API
Embedding hard-coded holdings means the UI will not update when the user
portfolio changes.
Recommend fetching via Pinia on mount:
+import { onMounted } from 'vue';
+import { useCryptoStore } from '@/store/crypto';
...
-const currentHoldings = ref<Holding[]>([ /* demo data */ ]);
-const historicalPositions = ref<Position[]>([ /* demo data */ ]);
+const store = useCryptoStore();
+const currentHoldings = ref<Holding[]>([]);
+const historicalPositions = ref<Position[]>([]);
+
+onMounted(async () => {
+ await store.fetchPortfolio();
+ currentHoldings.value = store.portfolio?.currentHoldings ?? [];
+ historicalPositions.value = store.portfolio?.historicalPositions ?? [];
+});
📝 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.
const currentHoldings = ref<Holding[]>([ | |
{ id: 'btc', asset: 'Bitcoin (BTC)', amount: 2.5, currentPrice: 50000, totalValue: 125000, purchasePrice: 45000 }, | |
{ id: 'eth', asset: 'Ethereum (ETH)', amount: 10, currentPrice: 3000, totalValue: 30000, purchasePrice: 2500 }, | |
{ id: 'ada', asset: 'Cardano (ADA)', amount: 5000, currentPrice: 0.5, totalValue: 2500, purchasePrice: 0.4 }, | |
]); | |
const historicalPositions = ref<Position[]>([ | |
{ id: 'sol-closed', asset: 'Solana (SOL)', entryPrice: 80, exitPrice: 120, amount: 100, timestamp: '2023-10-15T10:00:00Z', status: 'CLOSED', pnl: 4000 }, | |
{ id: 'dot-open', asset: 'Polkadot (DOT)', entryPrice: 5, amount: 200, timestamp: '2024-01-20T14:30:00Z', status: 'OPEN', pnl: 'N/A' }, | |
{ id: 'doge-closed', asset: 'Dogecoin (DOGE)', entryPrice: 0.05, exitPrice: 0.15, amount: 100000, timestamp: '2023-05-01T08:20:00Z', status: 'CLOSED', pnl: 10000 }, | |
{ id: 'link-closed', asset: 'Chainlink (LINK)', entryPrice: 15, exitPrice: 12, amount: 50, timestamp: '2023-11-01T18:00:00Z', status: 'CLOSED', pnl: -150 }, | |
]); | |
<script setup lang="ts"> | |
import { ref, onMounted } from 'vue'; | |
import { useCryptoStore } from '@/store/crypto'; | |
import type { Holding, Position } from '@/types'; | |
const store = useCryptoStore(); | |
// start with empty arrays | |
const currentHoldings = ref<Holding[]>([]); | |
const historicalPositions = ref<Position[]>([]); | |
onMounted(async () => { | |
// fetch from your Pinia store (or API) and populate refs | |
await store.fetchPortfolio(); | |
currentHoldings.value = store.portfolio?.currentHoldings ?? []; | |
historicalPositions.value = store.portfolio?.historicalPositions ?? []; | |
}); | |
</script> |
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/Portfolio.vue around lines 56 to 67, the
currentHoldings and historicalPositions are hard-coded static demo data, which
prevents the UI from reflecting real user portfolio changes. Replace these
static refs by fetching the holdings and positions from the Pinia store during
the component's mounted lifecycle hook, ensuring the data is reactive and
updates with the user's actual portfolio state.
interface ApiKey { | ||
id: string; | ||
exchange: string | null; | ||
apiKey: string | null; | ||
apiSecret: string | null; | ||
} |
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.
🛠️ Refactor suggestion
Raw apiSecret
(and full API keys) are persisted on the client – high-risk leakage
Storing secrets in reactive state and hard-coding mock keys triggers secret-scanners and, more importantly, normalises an unsafe pattern. Strip apiSecret
from ApiKey
, keep it only in the submit payload, and mask apiKey
before persisting.
-interface ApiKey {
- id: string;
- exchange: string | null;
- apiKey: string | null;
- apiSecret: string | null;
-}
+interface ApiKey {
+ id: string;
+ exchange: string | null;
+ apiKeyMasked: string | null;
+}
…
-const savedKeys = ref<ApiKey[]>([
- { id: 'key1', exchange: 'Binance', apiKey: 'abc123xyz789', apiSecret: 'secret1' },
- { id: 'key2', exchange: 'CoinbasePro', apiKey: 'def456uvw123', apiSecret: 'secret2' },
-]);
+const savedKeys = ref<ApiKey[]>([]);
(If mock data is really needed, use obviously fake placeholders like "********"
so Gitleaks stays green.)
Also applies to: 100-103
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/ApiKeys.vue around lines 79 to 84 and also
lines 100 to 103, the ApiKey interface and related code currently store raw
apiSecret and full API keys in the client state, which is a security risk.
Remove the apiSecret field from the ApiKey interface so it is not persisted in
the reactive state, keep apiSecret only in the submit payload when sending to
the server, and mask the apiKey value (e.g., replace with asterisks) before
storing it in the client state. Also, replace any hard-coded mock keys with
clearly fake placeholders like "********" to avoid triggering secret scanners.
const saveApiKey = () => { | ||
formRef.value?.validate(errors => { | ||
if (!errors) { | ||
// Placeholder for API call | ||
const newKey = { ...formValue.value, id: `key${Date.now()}` }; // Create a unique ID | ||
savedKeys.value.push(newKey); | ||
message.success(`${formValue.value.exchange} API Key saved (mock).`); | ||
// Reset form |
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.
reactive()
object is accessed with .value
– runtime & TS errors
formValue
is a proxy returned by reactive
, not a ref
; it has no .value
property.
- const newKey = { ...formValue.value, id: `key${Date.now()}` }; // Create a unique ID
+ const newKey = { ...formValue, id: `key${Date.now()}` }; // Create a unique ID
…
- message.success(`${formValue.value.exchange} API Key saved (mock).`);
+ message.success(`${formValue.exchange} API Key saved (mock).`);
Without this fix the code throws and compilation fails in strict-TS mode.
📝 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.
const saveApiKey = () => { | |
formRef.value?.validate(errors => { | |
if (!errors) { | |
// Placeholder for API call | |
const newKey = { ...formValue.value, id: `key${Date.now()}` }; // Create a unique ID | |
savedKeys.value.push(newKey); | |
message.success(`${formValue.value.exchange} API Key saved (mock).`); | |
// Reset form | |
const saveApiKey = () => { | |
formRef.value?.validate(errors => { | |
if (!errors) { | |
// Placeholder for API call | |
const newKey = { ...formValue, id: `key${Date.now()}` }; // Create a unique ID | |
savedKeys.value.push(newKey); | |
message.success(`${formValue.exchange} API Key saved (mock).`); | |
// Reset form |
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/ApiKeys.vue around lines 111 to 118, the code
incorrectly accesses the reactive object formValue using .value, which causes
runtime and TypeScript errors because reactive proxies do not have a .value
property. To fix this, remove all .value accesses on formValue and use it
directly as a reactive object when reading or spreading its properties.
<n-form @submit.prevent="saveApiKey" ref="formRef"> | ||
<n-form-item-row label="Exchange" path="exchange"> | ||
<n-select | ||
v-model:value="formValue.exchange" | ||
placeholder="Select Exchange" | ||
:options="exchangeOptions" | ||
/> | ||
</n-form-item-row> |
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.
Validation rules & model are not wired – validate()
will never succeed
<n-form>
misses the mandatory :model
and :rules
bindings, so every call to formRef.validate()
returns an empty error list and the component silently skips all field checks.
- <n-form @submit.prevent="saveApiKey" ref="formRef">
+ <n-form
+ :model="formValue"
+ :rules="rules"
+ @submit.prevent="saveApiKey"
+ ref="formRef"
+ >
📝 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.
<n-form @submit.prevent="saveApiKey" ref="formRef"> | |
<n-form-item-row label="Exchange" path="exchange"> | |
<n-select | |
v-model:value="formValue.exchange" | |
placeholder="Select Exchange" | |
:options="exchangeOptions" | |
/> | |
</n-form-item-row> | |
<n-form | |
:model="formValue" | |
:rules="rules" | |
@submit.prevent="saveApiKey" | |
ref="formRef" | |
> | |
<n-form-item-row label="Exchange" path="exchange"> | |
<n-select | |
v-model:value="formValue.exchange" | |
placeholder="Select Exchange" | |
:options="exchangeOptions" | |
/> | |
</n-form-item-row> |
🤖 Prompt for AI Agents
In apps/web-naive/src/views/crypto/ApiKeys.vue around lines 6 to 13, the
<n-form> component is missing the required :model and :rules bindings, causing
formRef.validate() to always succeed without actual validation. Fix this by
adding :model="formValue" and :rules="rules" props to the <n-form> element,
ensuring the form is properly wired to the data model and validation rules.
if (payload.tokenType === 'like') { | ||
tokens.value += payload.amount; | ||
} else if (payload.tokenType === 'usdt') { | ||
// If you have a separate USDT balance, update it here. | ||
// For this example, let's assume USDT recharges also affect the general 'tokens' for simplicity, | ||
// or you might convert/ignore them for the primary 'tokens' state. | ||
// tokens.value += payload.amount; // Or handle USDT separately | ||
console.log(`[CryptoStore] ${payload.amount} USDT recharged. Not directly adding to primary 'tokens' balance in this mock.`); | ||
} | ||
// Let's assume the primary `tokens.value` is for "like" tokens or a general service credit. | ||
// The backend recharge.post.ts handles specific balances; this store's `tokens` is simplified for now. | ||
// To reflect the recharge for 'like' tokens: | ||
// if (payload.tokenType === 'like') { tokens.value += payload.amount; } | ||
// For this mock, let's just add any amount to the main `tokens` state for demo purposes. | ||
tokens.value += payload.amount; |
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.
Token balance is added twice – user gets double credit
The first branch already increments tokens.value
for 'like'
, and line 181 adds the same amount again for any request.
- if (payload.tokenType === 'like') {
- tokens.value += payload.amount;
- } else if (payload.tokenType === 'usdt') {
+ if (payload.tokenType === 'like') {
+ tokens.value += payload.amount;
+ } else if (payload.tokenType === 'usdt') {
// (convert or ignore for demo)
console.log(`[CryptoStore] ${payload.amount} USDT recharged …`);
}
- …
- tokens.value += payload.amount;
+ // Remove the unconditional addition above to avoid double crediting.
This bug breaks accounting and lets users exploit recharges.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web-naive/src/store/crypto.ts between lines 167 and 181, the
tokens.value is incremented twice for 'like' tokenType: once inside the if block
and again unconditionally at line 181. To fix this, remove the unconditional
tokens.value += payload.amount at line 181 and only increment tokens.value
inside the appropriate conditional branches to avoid double counting and ensure
correct token balance updates.
This commit introduces a new set of features for AI-driven cryptocurrency analysis and portfolio management.
Key additions include:
Backend Mock API Endpoints (
apps/backend-mock/api/crypto/
):recommendations.post.ts
: Generates AI trading suggestions (mocked) based on currency, timeframe, and risk appetite. Simulates token deduction.portfolio.get.ts
: Fetches your cryptocurrency portfolio (mocked).apikeys.post.ts
: Allows binding of exchange API keys (mocked).recharge.post.ts
: Handles token recharges (mocked).tokenStore.ts
for managing mock token balances.Frontend Vue Components (
apps/web-naive/src/views/crypto/
):Recommendations.vue
: UI for getting AI recommendations, displaying results, and current token balance.Portfolio.vue
: UI for displaying current crypto holdings and historical positions.ApiKeys.vue
: UI for managing exchange API keys.Recharge.vue
: UI for recharging 'Like' or 'USDT' tokens.Routing (
apps/web-naive/src/router/routes/modules/crypto.ts
):/crypto
path for all the new feature components.State Management (
apps/web-naive/src/store/crypto.ts
):useCryptoStore
(Pinia) to manage state for token balances, portfolio data, and API keys.Localization (
packages/locales/src/langs/zh-CN/crypto.json
):Unit Tests:
apps/web-naive/src/store/crypto.spec.ts
: Tests for the Pinia store (initial state, actions, getters).apps/web-naive/src/views/crypto/tests/
: Component tests covering rendering, basic interactions, and store integration where applicable.These features provide a foundational framework for the requested cryptocurrency analysis tools within the Vben Admin structure.
Description
Type of change
Please delete options that are not relevant.
pnpm-lock.yaml
unless you introduce a new test example.Checklist
pnpm run docs:dev
command.pnpm test
.feat:
,fix:
,perf:
,docs:
, orchore:
.Summary by CodeRabbit
New Features
Tests
Documentation