/** * Shrimp basic-admin demo * * 用 MemoryShrimpCrud 跑一个零依赖的 admin server,展示: * - Shrimp 核心(模块注册 / RPC dispatch / 权限树) * - ShrimpTableModule + MemoryShrimpCrud 的 CRUD 自动事件 * - 通过 Express 暴露成 HTTP RPC * * 不使用 ShrimpServer/Mongoose/JWT,刻意暴露 ShrimpServer 把太多东西耦合在 * Express+Mongo+Auth 一起的事实,作为后续重构的对照样本。 */ import express from "express" import { Shrimp, ShrimpTableModule, MemoryShrimpCrud, ShrimpFieldTypes, ShrimpError, ShrimpCode, } from "shrimp-server" import type { IFieldConfig, WithId } from "shrimp-server" interface ITag { _id: string; name: string } interface IProduct { _id: string; code: string; name: string; price: number; tagIds: string[] } const tagFields: IFieldConfig[] = [ { key: "name", label: "标签名", type: ShrimpFieldTypes.String, required: true, searchable: true }, ] const productFields: IFieldConfig[] = [ { key: "code", label: "编码", type: ShrimpFieldTypes.String, required: true, editable: "addOnly" }, { key: "name", label: "名称", type: ShrimpFieldTypes.String, required: true, searchable: true }, { key: "price", label: "价格", type: ShrimpFieldTypes.Number, required: true }, { key: "tagIds", label: "标签", type: ShrimpFieldTypes.Relation, relation: { moduleKey: "tag", labelKey: "name" }, multiple: true, defaultValue: [], }, ] async function bootstrap() { const shrimp = new Shrimp() const tagCrud = new MemoryShrimpCrud>() new ShrimpTableModule(shrimp, "tag", tagCrud, tagFields, "标签管理").initCrud() const productCrud = new MemoryShrimpCrud>() new ShrimpTableModule(shrimp, "product", productCrud, productFields, "商品管理").initCrud() // 种子数据 const t1 = await tagCrud.add({ name: "热门" } as any) const t2 = await tagCrud.add({ name: "促销" } as any) await productCrud.add({ code: "P001", name: "苹果", price: 5, tagIds: [t1._id] } as any) await productCrud.add({ code: "P002", name: "香蕉", price: 3, tagIds: [t1._id, t2._id] } as any) const app = express() app.use(express.json()) // 简化的 RPC dispatch: POST /api/<...path> body 是参数数组 app.post(/^\/api\/.+/, async (req, res) => { const path = req.path.replace(/^\/api\//, "").split("/").filter(Boolean) const args = Array.isArray(req.body) ? req.body : [] try { const data = await shrimp.call(path, ...args) res.json({ code: ShrimpCode.OK, data }) } catch (e: any) { if (e instanceof ShrimpError) { res.json({ code: e.code, tip: e.tip }) } else { console.error(e) res.json({ code: ShrimpCode.SERVER_ERROR, log: String(e?.message || e) }) } } }) const port = 3100 app.listen(port, () => { console.log(`Shrimp basic-admin demo: http://localhost:${port}`) console.log("") console.log("# 试试:") console.log(` curl -s -X POST http://localhost:${port}/api/product/list -H 'content-type: application/json' -d '[{}]' | jq`) console.log(` curl -s -X POST http://localhost:${port}/api/tag/list -H 'content-type: application/json' -d '[{}]' | jq`) console.log(` curl -s -X POST http://localhost:${port}/api/product/add -H 'content-type: application/json' -d '[{"code":"P003","name":"橙子","price":4,"tagIds":[]}]' | jq`) }) } bootstrap().catch(err => { console.error("bootstrap failed:", err) process.exit(1) })