feat(api): 建立 assets 的 Server Actions 防腐层与 Zod 校验

This commit is contained in:
kennethcheng 2026-04-27 19:40:27 +08:00
parent c35e2f54b5
commit 7bc234b9f6
4 changed files with 49 additions and 3 deletions

8
package-lock.json generated
View File

@ -18,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/big.js": "^6.2.2",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@ -2151,6 +2152,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/big.js": {
"version": "6.2.2",
"resolved": "https://registry.npmmirror.com/@types/big.js/-/big.js-6.2.2.tgz",
"integrity": "sha512-e2cOW9YlVzFY2iScnGBBkplKsrn2CsObHQ2Hiw4V1sSyiGbgWL8IyqE3zFi1Pt5o1pdAtYkDAIsF3KKUPjdzaA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",

View File

@ -1,4 +1,4 @@
{ {
"name": "temp-next", "name": "temp-next",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
@ -22,6 +22,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/big.js": "^6.2.2",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@ -31,4 +32,4 @@
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5" "typescript": "^5"
} }
} }

37
src/actions/asset.ts Normal file
View File

@ -0,0 +1,37 @@
'use server';
import { db } from '@/db';
import { assets, assetTypeEnum } from '@/db/schema';
import { z } from 'zod';
const createAssetSchema = z.object({
symbol: z.string().min(1, 'Symbol is required'),
type: z.enum(['STOCK', 'CRYPTO', 'CASH']),
baseCurrency: z.string().min(2).max(10),
});
export async function createAsset(params: z.infer<typeof createAssetSchema>) {
const validation = createAssetSchema.safeParse(params);
if (!validation.success) {
return { success: false, error: validation.error.issues[0].message };
}
try {
const [asset] = await db.insert(assets).values(params).returning();
return { success: true, data: asset };
} catch (error: unknown) {
if (
error &&
typeof error === 'object' &&
'code' in error &&
(error as { code: string }).code === '23505'
) {
return { success: false, error: 'Asset with this symbol already exists' };
}
throw error;
}
}
export async function getAssets() {
return db.select().from(assets);
}

View File

@ -19,7 +19,7 @@
} }
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./src/*"]
} }
}, },
"include": [ "include": [