1242 lines
27 KiB
JavaScript
1242 lines
27 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import vm from 'node:vm';
|
|
import ts from 'typescript';
|
|
const contracts = [
|
|
{
|
|
"domain": "gas",
|
|
"name": "gas_basic",
|
|
"resource": "/gas/gas_basic",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "credit_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "principal",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "address",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "longitude",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "latitude",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "gas",
|
|
"name": "gas_account",
|
|
"resource": "/gas/gas_account",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "username",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "password",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "display_name",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "role_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "gas_basic_identity",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "delivery",
|
|
"name": "delivery_basic",
|
|
"resource": "/delivery/delivery_basic",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "delivery_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "gas_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "principal",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "address",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "delivery",
|
|
"name": "delivery_account",
|
|
"resource": "/delivery/delivery_account",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "username",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "password",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "display_name",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "role_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "delivery_basic_identity",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "delivery",
|
|
"name": "delivery_task",
|
|
"resource": "/delivery/delivery_task",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_order_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "delivery_basic_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "staff_account_identity",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "delivery",
|
|
"name": "delivery_track",
|
|
"resource": "/delivery/delivery_track",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "delivery_task_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "started_at",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "completed_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "delivery",
|
|
"name": "delivery_track_point",
|
|
"resource": "/delivery/delivery_track_point",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "delivery_track_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "point_type",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "occurred_at",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "longitude",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "latitude",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "staff",
|
|
"name": "staff_account",
|
|
"resource": "/staff/account",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "username",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "password",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "phone",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "avatar",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "role_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "gas_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "delivery_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "work_status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "staff",
|
|
"name": "staff_credential",
|
|
"resource": "/staff/credential",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "staff_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "credential_type",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "credential_no",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "expired_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "user",
|
|
"name": "user_account",
|
|
"resource": "/user/account",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "username",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "password",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "phone",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "avatar",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "real_name",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "user",
|
|
"name": "user_address",
|
|
"resource": "/user/address",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "address",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "longitude",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "latitude",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "is_default",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "user",
|
|
"name": "user_service_relation",
|
|
"resource": "/user/service_relation",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "gas_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "delivery_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "staff_account_identity",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "device",
|
|
"name": "dev_smart_cylinder_valve",
|
|
"resource": "/device/dev_smart_cylinder_valve",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "device_no",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "model",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "online_status",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "owner_identity",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "device",
|
|
"name": "dev_device_binding",
|
|
"resource": "/device/dev_device_binding",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "smart_cylinder_valve_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "effective_at",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "expired_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "device",
|
|
"name": "dev_telemetry",
|
|
"resource": "/device/dev_telemetry",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "smart_cylinder_valve_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "recorded_at",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "payload",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "safety",
|
|
"name": "saf_rule",
|
|
"resource": "/safety/saf_rule",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "rule_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "version_no",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "threshold",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "action",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "gray_scope",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "safety",
|
|
"name": "saf_event",
|
|
"resource": "/safety/saf_event",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "event_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "level",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "title",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "smart_cylinder_valve_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "sla_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "safety",
|
|
"name": "saf_inspection",
|
|
"resource": "/safety/saf_inspection",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "staff_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "result",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "evidence_uri",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "safety",
|
|
"name": "saf_event_disposal",
|
|
"resource": "/safety/saf_event/:identity/disposals",
|
|
"mode": "append_only",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "action",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "reason",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_category",
|
|
"resource": "/ec/ec_category",
|
|
"mode": "writable",
|
|
"pageKind": "tree",
|
|
"fields": [
|
|
{
|
|
"key": "parent_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "sort_no",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_product",
|
|
"resource": "/ec/ec_product",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_category_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "product_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "price_amount",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "stock_quantity",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_product_attribute",
|
|
"resource": "/ec/ec_product_attribute",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_product_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "value",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "sort_no",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_product_image",
|
|
"resource": "/ec/ec_product_image",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_product_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "image_uri",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "sort_no",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "is_cover",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_cart",
|
|
"resource": "/ec/ec_cart",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "ec_product_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "quantity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "selected",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_order",
|
|
"resource": "/ec/ec_order",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "gas_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "delivery_basic_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "order_no",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "total_amount",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_order_item",
|
|
"resource": "/ec/ec_order_item",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_order_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "ec_product_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "product_snapshot",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "quantity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "sale_amount",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "ec",
|
|
"name": "ec_review",
|
|
"resource": "/ec/ec_review",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_order_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "ec_product_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "score",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "content",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "finance",
|
|
"name": "fin_payment",
|
|
"resource": "/finance/fin_payment",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "ec_order_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "channel",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "amount",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "paid_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "finance",
|
|
"name": "fin_settlement",
|
|
"resource": "/finance/fin_settlement",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "settlement_no",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "subject_type",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "subject_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "period_start",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "period_end",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "finance",
|
|
"name": "fin_reconciliation",
|
|
"resource": "/finance/fin_reconciliation",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "channel",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "bill_date",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "difference_amount",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "content",
|
|
"name": "cnt_content",
|
|
"resource": "/content/cnt_content",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "content_type",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "title",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "body",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "version_no",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "publish_status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "notification",
|
|
"name": "ntf_template",
|
|
"resource": "/notification/ntf_template",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "template_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "channel",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "content",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "customer_service",
|
|
"name": "cs_ticket",
|
|
"resource": "/customer_service/cs_ticket",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "user_account_identity",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "ticket_no",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "category",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "priority",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "platform",
|
|
"name": "platfrom_account",
|
|
"resource": "/platform/platfrom_account",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "username",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "password",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "display_name",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "avatar",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "platform_role_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "phone",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "platform",
|
|
"name": "platform_role",
|
|
"resource": "/platform/platform_role",
|
|
"mode": "writable",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "role_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "data_scope",
|
|
"required": true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "platform",
|
|
"name": "platform_menu",
|
|
"resource": "/platform/platform_menu",
|
|
"mode": "writable",
|
|
"pageKind": "tree",
|
|
"fields": [
|
|
{
|
|
"key": "parent_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "menu_code",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": true
|
|
},
|
|
{
|
|
"key": "icon",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "path",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "sort_no",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "wallet",
|
|
"name": "wallet",
|
|
"resource": "/wallet/wallet",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "owner_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "balance_amount",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "wallet",
|
|
"name": "wallet_ledger",
|
|
"resource": "/wallet/wallet_ledger",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "wallet_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "change_amount",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "balance_after",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "wallet",
|
|
"name": "wallet_recharge",
|
|
"resource": "/wallet/wallet_recharge",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "wallet_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "amount",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "wallet",
|
|
"name": "wallet_withdrawal",
|
|
"resource": "/wallet/wallet_withdrawal",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "wallet_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "amount",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "report",
|
|
"name": "report",
|
|
"resource": "/report/report",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "report_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "name",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "status",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "report",
|
|
"name": "report_item",
|
|
"resource": "/report/report_item",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "report_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "metric_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "value",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "report",
|
|
"name": "report_metric_snapshot",
|
|
"resource": "/report/report_metric_snapshot",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "report_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "metric_code",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "value",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "captured_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "audit",
|
|
"name": "aud_operation_log",
|
|
"resource": "/audit/aud_operation_log",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "operator_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "action",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "object_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "created_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "audit",
|
|
"name": "aud_export_log",
|
|
"resource": "/audit/aud_export_log",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "operator_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "resource_type",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "created_at",
|
|
"required": false
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"domain": "audit",
|
|
"name": "aud_approval",
|
|
"resource": "/audit/aud_approval",
|
|
"mode": "readonly",
|
|
"pageKind": "list",
|
|
"fields": [
|
|
{
|
|
"key": "operator_identity",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "status",
|
|
"required": false
|
|
},
|
|
{
|
|
"key": "handled_at",
|
|
"required": false
|
|
}
|
|
]
|
|
}
|
|
];
|
|
const source = fs.readFileSync('src/api/resources.ts', 'utf8');
|
|
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
|
|
const resourceModule = { exports: {} };
|
|
vm.runInNewContext(compiled, { module: resourceModule, exports: resourceModule.exports });
|
|
const resources = resourceModule.exports.resources;
|
|
const files = (dir) => fs.existsSync(dir) ? fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? files(path.join(dir, entry.name)) : [path.join(dir, entry.name)]) : [];
|
|
const sameFields = (actual, expected) => Array.isArray(actual) && actual.length === expected.length && actual.every((field, index) => field.key === expected[index].key && Boolean(field.required) === Boolean(expected[index].required));
|
|
function checkContracts() {
|
|
const failures = [];
|
|
if (!Array.isArray(resources) || resources.length !== contracts.length) failures.push(`catalogue: expected ${contracts.length} resources`);
|
|
for (const contract of contracts) {
|
|
const definition = resources.find((resource) => resource.name === contract.name);
|
|
const label = `${contract.domain}/${contract.name}`;
|
|
if (!definition || definition.resource !== contract.resource || definition.mode !== contract.mode || definition.pageKind !== contract.pageKind || !sameFields(definition.fields, contract.fields)) failures.push(`${label}: mismatched contract or allowlist`);
|
|
const requiredIdentity = contract.fields.filter((field) => field.required && field.key.endsWith('_identity')).map((field) => field.key);
|
|
if (definition && JSON.stringify(definition.requiredIdentities ?? []) !== JSON.stringify(requiredIdentity)) failures.push(`${label}: required identity fields do not match allowlist`);
|
|
if (contract.name === 'saf_event_disposal') continue;
|
|
const route = path.join('src/router/routes/modules', contract.domain + '.ts');
|
|
if (!fs.existsSync(route) || !fs.readFileSync(route, 'utf8').includes(contract.name)) failures.push(`${label}: missing route`);
|
|
const page = path.join('src/views', contract.domain, contract.name, contract.pageKind === 'tree' ? 'TreePage.vue' : 'ListPage.vue');
|
|
if (!fs.existsSync(page)) failures.push(`${label}: missing view`);
|
|
if (contract.mode === 'readonly' && (!fs.existsSync(page) || !fs.readFileSync(page, 'utf8').includes('ReadOnlyListPage'))) failures.push(`${label}: readonly page exposes write surface`);
|
|
}
|
|
const names = resources.map((resource) => resource.name).sort();
|
|
const expectedNames = contracts.map((contract) => contract.name).sort();
|
|
if (JSON.stringify(names) !== JSON.stringify(expectedNames)) failures.push('catalogue: resource names do not equal ExpectedResources');
|
|
if (resources.some((resource) => resource.fields.some((field) => field.key === 'id' || field.key.endsWith('_id')))) failures.push('catalogue: internal identifier leaked through fields');
|
|
const readonlyPage = fs.readFileSync('src/views/shared/ReadOnlyListPage.vue', 'utf8');
|
|
if (/(CrudListPage|resourceApi\\.(create|update|archive|updateStatus))/.test(readonlyPage)) failures.push('readonly shared page: contains write surface');
|
|
const treePage = fs.readFileSync('src/views/shared/TreePage.vue', 'utf8');
|
|
if (!treePage.includes('<a-tree') || !/parent_identity|parent_id/.test(treePage) || treePage.includes('CrudListPage')) failures.push('tree shared page: missing tree semantics');
|
|
for (const name of ['ec_category', 'platform_menu']) { const view = path.join('src/views', name === 'ec_category' ? 'ec' : 'platform', name, 'TreePage.vue'); if (!fs.existsSync(view)) failures.push(`${name}: must use TreePage`); }
|
|
const safetyEvent = resources.find((resource) => resource.name === 'saf_event');
|
|
if (!safetyEvent?.detailActions?.some((item) => item.name === 'saf_event_disposal' && item.resource === '/safety/saf_event/:identity/disposals')) failures.push('safety event: missing disposal detail action');
|
|
if (files('src/router/routes/modules').some((file) => fs.readFileSync(file, 'utf8').includes('saf_event_disposal')) || fs.existsSync('src/views/safety/saf_event_disposal')) failures.push('saf_event_disposal: must not have independent menu or page');
|
|
const crudSource = fs.readFileSync('src/views/shared/CrudListPage.vue', 'utf8');
|
|
if (!crudSource.includes("key !== 'id'") || !crudSource.includes("key.endsWith('_id')")) failures.push('detail view: does not remove internal identifiers');
|
|
const leakedUiFiles = files('src/api').concat(files('src/views')).filter((file) => /\.(ts|vue)$/.test(file) && !file.endsWith('CrudListPage.vue') && !file.endsWith('ReadOnlyListPage.vue') && !file.endsWith('TreePage.vue')).filter((file) => /\b[A-Za-z][A-Za-z0-9]*_id\b|\bid\b/.test(fs.readFileSync(file, 'utf8')));
|
|
if (leakedUiFiles.length) failures.push(`identity leak: ${leakedUiFiles.join(', ')}`);
|
|
if (/data-index="(?:id|[^" ]+_id)"|\{\{\s*[^}]*\b(?:id|[A-Za-z][A-Za-z0-9]*_id)\b/.test(treePage)) failures.push('tree page: displays internal identifier');
|
|
return failures;
|
|
}
|
|
const failures = checkContracts();
|
|
for (const failure of failures) console.log(failure);
|
|
if (failures.length) process.exitCode = 1;
|