feat: 添加购物车相关迁移和索引功能
- 在 package.json 中添加迁移脚本 `migrate:cart-active-unique-index`。 - 修改 `pocketbase.cart-order.js` 文件,更新 `cart_id` 和 `cart_product_id` 字段的必填属性,并添加唯一索引 `idx_tbl_cart_owner_product_active_unique`。 - 在 `pocketbase.ensure-cart-order-autogen-id.js` 中,调整 `cart_id` 字段的必填属性为可选,并确保 `order_id` 字段为必填。 - 在 `pocketbase.product-list.js` 中,新增 `prod_list_barcode` 字段。 - 新增 `make-openapi-standalone.cjs` 脚本,用于处理 OpenAPI 文档。 - 新增 `pocketbase.cart-active-unique-index.js` 脚本,处理购物车的唯一索引和去重逻辑。
This commit is contained in:
132
.tmp-openapi-validate/make-openapi-standalone.cjs
Normal file
132
.tmp-openapi-validate/make-openapi-standalone.cjs
Normal file
@@ -0,0 +1,132 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const YAML = require('yaml');
|
||||
|
||||
const repoRoot = path.resolve(process.cwd(), '..');
|
||||
const specRoot = path.join(repoRoot, 'pocket-base', 'spec');
|
||||
const folders = ['openapi-wx', 'openapi-manage'];
|
||||
|
||||
const allFiles = [];
|
||||
for (const folder of folders) {
|
||||
const dir = path.join(specRoot, folder);
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
if (name.endsWith('.yaml')) allFiles.push(path.join(dir, name));
|
||||
}
|
||||
}
|
||||
|
||||
const docCache = new Map();
|
||||
function loadDoc(filePath) {
|
||||
if (!docCache.has(filePath)) {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
docCache.set(filePath, YAML.parse(text));
|
||||
}
|
||||
return docCache.get(filePath);
|
||||
}
|
||||
|
||||
function deepClone(v) {
|
||||
return v == null ? v : JSON.parse(JSON.stringify(v));
|
||||
}
|
||||
|
||||
function decodePointer(seg) {
|
||||
return seg.replace(/~1/g, '/').replace(/~0/g, '~');
|
||||
}
|
||||
|
||||
function getByPointer(doc, pointer) {
|
||||
if (!pointer || pointer === '#' || pointer === '') return doc;
|
||||
const parts = pointer.replace(/^#/, '').split('/').filter(Boolean).map(decodePointer);
|
||||
let cur = doc;
|
||||
for (const p of parts) {
|
||||
if (cur == null) return undefined;
|
||||
cur = cur[p];
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
function mergeObjects(base, extra) {
|
||||
if (base && typeof base === 'object' && !Array.isArray(base) && extra && typeof extra === 'object' && !Array.isArray(extra)) {
|
||||
return { ...base, ...extra };
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function isExternalRef(ref) {
|
||||
if (typeof ref !== 'string') return false;
|
||||
if (ref.startsWith('#')) return false;
|
||||
if (/^https?:\/\//i.test(ref)) return false;
|
||||
const [filePart] = ref.split('#');
|
||||
return !!filePart;
|
||||
}
|
||||
|
||||
function resolveExternalRef(ref, baseFile) {
|
||||
const [filePart, hashPart = ''] = ref.split('#');
|
||||
const targetFile = path.resolve(path.dirname(baseFile), filePart);
|
||||
const targetDoc = loadDoc(targetFile);
|
||||
const pointer = hashPart ? `#${hashPart}` : '#';
|
||||
const targetNode = getByPointer(targetDoc, pointer);
|
||||
return { targetFile, targetNode: deepClone(targetNode) };
|
||||
}
|
||||
|
||||
function derefExternals(node, baseFile, seen = new Set()) {
|
||||
if (Array.isArray(node)) {
|
||||
return node.map((item) => derefExternals(item, baseFile, seen));
|
||||
}
|
||||
if (!node || typeof node !== 'object') {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (typeof node.$ref === 'string' && isExternalRef(node.$ref)) {
|
||||
const key = `${baseFile}::${node.$ref}`;
|
||||
if (seen.has(key)) {
|
||||
return node;
|
||||
}
|
||||
seen.add(key);
|
||||
|
||||
const { targetFile, targetNode } = resolveExternalRef(node.$ref, baseFile);
|
||||
let inlined = derefExternals(targetNode, targetFile, seen);
|
||||
|
||||
const siblings = { ...node };
|
||||
delete siblings.$ref;
|
||||
if (Object.keys(siblings).length > 0) {
|
||||
inlined = mergeObjects(inlined, derefExternals(siblings, baseFile, seen));
|
||||
}
|
||||
return inlined;
|
||||
}
|
||||
|
||||
const out = {};
|
||||
for (const [k, v] of Object.entries(node)) {
|
||||
out[k] = derefExternals(v, baseFile, seen);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function ensureTopLevel(doc, filePath) {
|
||||
if (!doc.openapi) doc.openapi = '3.1.0';
|
||||
if (!doc.info || typeof doc.info !== 'object') {
|
||||
const base = path.basename(filePath, '.yaml');
|
||||
const group = filePath.includes('openapi-wx') ? 'WX Native' : 'Manage Hooks';
|
||||
doc.info = {
|
||||
title: `BAI ${group} API - ${base}`,
|
||||
version: '1.0.0'
|
||||
};
|
||||
} else {
|
||||
if (!doc.info.title) doc.info.title = `BAI API - ${path.basename(filePath, '.yaml')}`;
|
||||
if (!doc.info.version) doc.info.version = '1.0.0';
|
||||
}
|
||||
if (!Array.isArray(doc.servers)) {
|
||||
doc.servers = [
|
||||
{ url: 'https://bai-api.blv-oa.com', description: '生产环境' },
|
||||
{ url: 'http://localhost:8090', description: 'PocketBase 本地环境' }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
for (const filePath of allFiles) {
|
||||
const original = loadDoc(filePath);
|
||||
const transformed = derefExternals(deepClone(original), filePath);
|
||||
ensureTopLevel(transformed, filePath);
|
||||
const outText = YAML.stringify(transformed, { lineWidth: 0 });
|
||||
fs.writeFileSync(filePath, outText, 'utf8');
|
||||
docCache.set(filePath, transformed);
|
||||
}
|
||||
|
||||
console.log(`Processed ${allFiles.length} yaml files under openapi-wx/openapi-manage.`);
|
||||
Reference in New Issue
Block a user