const parseUrl = process.env.PARSE_URL || 'http://127.0.0.1:3000/parse'; const appId = process.env.PARSE_APP_ID; const masterKey = process.env.PARSE_MASTER_KEY; if (!appId || !masterKey) throw new Error('Missing Parse bootstrap environment variables'); const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': appId, 'X-Parse-Master-Key': masterKey, }; async function request(path, method = 'GET', body) { const response = await fetch(`${parseUrl}${path}`, { method, headers, body: body === undefined ? undefined : JSON.stringify(body), }); const result = await response.json(); if (!response.ok || result.error) throw new Error(`${method} ${path}: ${result.error || response.status}`); return result; } const StringField = { type: 'String' }; const NumberField = { type: 'Number' }; const BooleanField = { type: 'Boolean' }; const DateField = { type: 'Date' }; const ArrayField = { type: 'Array' }; const ObjectField = { type: 'Object' }; const pointer = targetClass => ({ type: 'Pointer', targetClass }); const productFields = { recordKey: StringField, asin: StringField, Asin: StringField, parentAsin: StringField, sku: StringField, sellerSku: StringField, productSku: StringField, fnsku: StringField, title: StringField, itemName: StringField, imageUrl: StringField, photo: StringField, productType: StringField, platform: StringField, currency: StringField, brand: StringField, category: StringField, categoryName: StringField, nodeId: StringField, categoryNodeId: StringField, sellerId: StringField, marketplace: StringField, marketplaceId: StringField, domain: StringField, site: StringField, country: StringField, countryCode: StringField, storeName: StringField, shopName: StringField, shopId: StringField, storeId: StringField, userAccount: StringField, designerId: StringField, designerName: StringField, operatorId: StringField, operatorName: StringField, developerId: StringField, developerName: StringField, departCode: StringField, accessUserId: StringField, fulfillmentType: StringField, onlineDate: StringField, openDateSite: StringField, listingOpenDateSite: StringField, firstOrderDateSite: StringField, onlineDays: NumberField, listingColor: StringField, listingSize: StringField, listingVariation: StringField, color: StringField, size: StringField, variation: StringField, listingImageUrl: StringField, listingSmallImageUrl: StringField, source: StringField, status: StringField, itemStatus: NumberField, price: NumberField, salesPrice: NumberField, listingPrice: NumberField, regularPrice: NumberField, rating: NumberField, ratings: NumberField, ratingsCount: NumberField, reviewCount: NumberField, rank: NumberField, monthlySales: NumberField, listingSalesVolumeOfMonth: NumberField, ListingSalesVolumeOfMonth: NumberField, listingSalesOfMonth: NumberField, inventoryQuantity: NumberField, sellerCount: NumberField, bsrCategory: ArrayField, attributes: ObjectField, rawData: ObjectField, catalogData: ObjectField, pricingData: ObjectField, inventoryData: ObjectField, shop: pointer('Shop'), }; const reviewFields = { recordKey: StringField, reviewId: StringField, asin: StringField, marketplaceId: StringField, domain: NumberField, asins: ArrayField, marketplaceIds: ArrayField, domains: ArrayField, shopIds: ArrayField, title: StringField, content: StringField, author: StringField, reviewDate: StringField, sentiment: StringField, date: StringField, createTime: StringField, source: StringField, reviewPublishedAt: DateField, capturedAt: DateField, translatedTitle: StringField, translatedContent: StringField, star: NumberField, rating: NumberField, verified: BooleanField, keywords: ArrayField, topics: ArrayField, rawData: ObjectField, shop: pointer('Shop'), }; const schemaDefinitions = { Shop: { name: StringField, shopName: StringField, storeName: StringField, platform: StringField, region: StringField, marketplaceId: StringField, domain: StringField, nodeIds: ArrayField, sync_status: StringField, syncStatus: StringField, status: StringField, disable: BooleanField, deleted: BooleanField, deletedAt: DateField, last_sync_at: DateField, lastSyncTime: DateField, config: ObjectField, }, Product: productFields, ProductDetail: productFields, SorftimeProduct: productFields, AmazonProduct: productFields, AsinSkuMapping: productFields, SorftimeReviews: reviewFields, SorftimeReview: reviewFields, Review: reviewFields, ProductReview: reviewFields, Listing: { ...productFields, createdDate: DateField, lastUpdatedDate: DateField, issues: ArrayField, mainImage: StringField, currency: StringField, parentSku: StringField, productType: StringField, quantity: NumberField, condition: StringField, }, Order: { platformOrderId: StringField, orderId: StringField, orderDate: DateField, lastUpdateDate: DateField, status: StringField, marketplaceId: StringField, totalAmount: NumberField, currency: StringField, items: ArrayField, buyerInfo: ObjectField, shippingAddress: ObjectField, customerRegion: StringField, shippingCountry: StringField, shippingState: StringField, shippingCity: StringField, fulfillmentChannel: StringField, orderType: StringField, isPrime: BooleanField, isBusinessOrder: BooleanField, earliestShipDate: DateField, latestShipDate: DateField, itemCount: NumberField, itemsSyncedAt: DateField, shop: pointer('Shop'), }, OrderMetrics: { date: StringField, orderCount: NumberField, unitCount: NumberField, revenue: NumberField, currency: StringField, shop: pointer('Shop'), }, ReturnRecord: { asin: StringField, sku: StringField, sellerSku: StringField, productSku: StringField, fnsku: StringField, orderId: StringField, order_id: StringField, amazonOrderId: StringField, returnDate: DateField, return_date: DateField, reason: StringField, reasonCode: StringField, comment: StringField, customer_comments: StringField, disposition: StringField, detailed_disposition: StringField, productName: StringField, product_name: StringField, status: StringField, site: StringField, marketplaceId: StringField, storeName: StringField, quantity: NumberField, amount: NumberField, price: NumberField, licensePlateNumber: StringField, license_plate_number: StringField, fulfillmentCenterId: StringField, fulfillment_center_id: StringField, shop: pointer('Shop'), rawData: ObjectField, }, CompetitorMonitorPrefs: { ownerId: StringField, selectedAsins: ArrayField, groupMap: ObjectField, customGroups: ArrayField, tagMap: ObjectField, allTags: ArrayField, hiddenAsins: ArrayField, socialBrands: ArrayField, }, AmazonCategory: { categoryId: StringField, nodeId: StringField, parentId: StringField, name: StringField, cnName: StringField, url: StringField, domain: NumberField, isLeaf: BooleanField, level: NumberField, children: ArrayField, rawData: ObjectField, }, SelfCategory: { categoryId: StringField, nodeId: StringField, parentId: StringField, name: StringField, cnName: StringField, url: StringField, domain: NumberField, isLeaf: BooleanField, level: NumberField, path: ArrayField, keywords: ArrayField, }, SorftimeCategory: { categoryId: StringField, nodeId: StringField, parentId: StringField, name: StringField, domain: NumberField, isLeaf: BooleanField, sales: NumberField, satisfaction: NumberField, marketShare: NumberField, source: StringField, rawData: ObjectField, }, MarketTrend: { nodeId: StringField, categoryId: StringField, domain: NumberField, trendIndex: NumberField, date: StringField, name: StringField, value: NumberField, data: ObjectField, rawData: ObjectField, }, Reports: { recordKey: StringField, region: StringField, reportId: StringField, reportDocumentId: StringField, reportType: StringField, processingStatus: StringField, isParsed: BooleanField, marketplaceIds: ArrayField, dataStartTime: DateField, dataEndTime: DateField, shop: pointer('Shop'), rawData: ObjectField, createdTime: DateField, processingStartTime: DateField, processingEndTime: DateField, parsedAt: DateField, parsedRecords: NumberField, }, TaskExecutionLog: { taskName: StringField, startTime: DateField, endTime: DateField, duration: NumberField, successCount: NumberField, errorCount: NumberField, errors: ArrayField, status: StringField, processed: NumberField, }, ReviewSentiment: { asin: StringField, sentiment: StringField, score: NumberField, data: ObjectField }, SocialMediaCache: { cacheKey: StringField, dimension: StringField, data: ObjectField, expiresAt: DateField }, AiPromptConfig: { promptKey: StringField, name: StringField, content: StringField, config: ObjectField }, TodoStatus: { todoId: StringField, sourceId: StringField, status: StringField, userId: StringField, updatedAt_ts: NumberField, data: ObjectField, }, WorkOrder: { suggestionId: StringField, productId: StringField, title: StringField, description: StringField, status: StringField, priority: StringField, dueDate: DateField, assignee: StringField, issueType: StringField, issueId: StringField, sourceId: StringField, createdBy: StringField, actionSuggestionStatus: StringField, actionSuggestionType: StringField, actionSuggestionAssignee: StringField, priorityScore: NumberField, updatedAt_ts: NumberField, aiAnalysisSnapshot: ObjectField, actionSuggestionDetailPatch: ObjectField, workOrder: ObjectField, workOrderId: StringField, workOrderOwner: StringField, nextAction: StringField, slaStatus: StringField, acceptance: ObjectField, issueTaxonomy: ObjectField, rootCause: ObjectField, impact: ObjectField, actionPlan: ArrayField, designerNames: ArrayField, operatorNames: ArrayField, productSkus: ArrayField, fulfillmentTypes: ArrayField, verificationResult: ObjectField, data: ObjectField, }, ActionSuggestionStatus: { suggestionId: StringField, status: StringField, assignee: StringField, updatedAt_ts: NumberField, verificationResult: ObjectField, detailPatch: ObjectField, data: ObjectField, }, ActionSuggestionAudit: { suggestionId: StringField, action: StringField, workOrderId: StringField, eventType: StringField, status: StringField, title: StringField, assignee: StringField, summary: StringField, detailPatch: ObjectField, verificationResult: ObjectField, createdAt_ts: NumberField, data: ObjectField, }, Notification: { type: StringField, targetType: StringField, sourceType: StringField, sourceId: StringField, suggestionId: StringField, actionStatus: StringField, eventType: StringField, channel: StringField, title: StringField, content: StringField, status: StringField, createdAt_ts: NumberField, data: ObjectField, }, RequirementStrengthScore: { asin: StringField, score: NumberField, data: ObjectField }, ReturnImprovementAction: { actionId: StringField, shopId: StringField, asin: StringField, reasonCategory: StringField, reasonDetail: StringField, action: StringField, owner: StringField, status: StringField, actionCreatedAt: StringField, completedAt: StringField, beforeRate: NumberField, afterRate: NumberField, improvement: NumberField, verificationNote: StringField, source: StringField, updatedAt_ts: NumberField, data: ObjectField, }, SizeChartKnowledge: { name: StringField, note: StringField, category: StringField, rows: ArrayField, columns: ArrayField }, Keyword: { keyword: StringField, searchVolume: NumberField, trend: NumberField, competition: NumberField, cpcAmount: NumberField, currency: StringField }, Category: { categoryId: StringField, name: StringField, parentId: StringField, productCount: NumberField }, SorftimeKeyword: { keyword: StringField, searchVolume: NumberField, trend: NumberField, growthRate: NumberField, source: StringField, rawData: ObjectField, }, SorftimeMarketCapacity: { nodeId: StringField, domain: NumberField, category: StringField, totalMarketSize: NumberField, competitionLevel: StringField, source: StringField, data: ObjectField, rawData: ObjectField, }, EmergingTrendSignal: { signalId: StringField, keyword: StringField, category: StringField, label: StringField, score: NumberField, trend: NumberField, source: StringField, fetchedAt: StringField, ttlMinutes: NumberField, data: ObjectField, }, EmergingTrendKeyword: { keyword: StringField, text: StringField, normalizedText: StringField, score: NumberField, weight: NumberField, platform: StringField, growth: NumberField, rank: NumberField, fetchedAt: StringField, lastSeenAt: StringField, cacheAgeMinutes: NumberField, freshnessLevel: StringField, sourceStatus: StringField, sourceLatencyMs: NumberField, sampleCount: NumberField, confidence: NumberField, trendVelocity: NumberField, ttlMinutes: NumberField, source: StringField, data: ObjectField, }, EmergingOpportunity: { opportunityId: StringField, title: StringField, score: NumberField, gapIndex: NumberField, description: StringField, tags: ArrayField, status: StringField, source: StringField, fetchedAt: StringField, data: ObjectField, }, EmergingSocialTrendSnapshot: { snapshotId: StringField, status: StringField, fetchedAt: StringField, nextRefreshAt: StringField, cacheAgeMinutes: NumberField, ttlMinutes: NumberField, freshnessLevel: StringField, sourceStatus: StringField, sourceLatencyMs: NumberField, totalKeywords: NumberField, realtimeCount: NumberField, freshCount: NumberField, staleCount: NumberField, platformBreakdown: ObjectField, data: ObjectField, }, KnowledgeCompetitor: { asin: StringField, title: StringField, name: StringField, brandName: StringField, category: StringField, price: NumberField, rating: NumberField, reviewCount: NumberField, monthlySales: NumberField, platform: StringField, source: StringField, data: ObjectField, }, KnowledgeInfluencer: { uniqueId: StringField, platform: StringField, name: StringField, avatar: StringField, followers: NumberField, engagementRate: NumberField, tier: StringField, mainCategory: StringField, cooperationPrice: NumberField, source: StringField, data: ObjectField, }, KnowledgeInfluencerProfile: { uniqueId: StringField, platform: StringField, nickname: StringField, avatar: StringField, signature: StringField, followerCount: NumberField, heartCount: NumberField, videoCount: NumberField, followingCount: NumberField, engagementRate: NumberField, cooperationScore: NumberField, tags: ArrayField, data: ObjectField, }, KnowledgeInfluencerPost: { postId: StringField, uniqueId: StringField, authorUniqueId: StringField, platform: StringField, description: StringField, coverUrl: StringField, playCount: NumberField, likeCount: NumberField, commentCount: NumberField, shareCount: NumberField, createTime: StringField, data: ObjectField, }, KnowledgeKeyword: { keyword: StringField, rank: NumberField, searchVolume: NumberField, competition: StringField, source: StringField, data: ObjectField, }, KeywordSummary: { keyword: StringField, rank: NumberField, data: ObjectField }, KnowledgePattern: { patternId: StringField, name: StringField, category: StringField, type: StringField, measurements: ObjectField, fitType: StringField, sizeRange: ArrayField, userFeedback: ObjectField, satisfactionScore: NumberField, problemRate: NumberField, optimizationSuggestions: ArrayField, applicableProducts: ArrayField, applicableScenarios: ArrayField, popularity: NumberField, season: StringField, referenceImages: ArrayField, source: StringField, updatedAt_custom: StringField, data: ObjectField, }, KnowledgeHotProduct: { asin: StringField, title: StringField, productName: StringField, rank: NumberField, bsrRank: NumberField, category: StringField, price: NumberField, rating: NumberField, reviewCount: NumberField, monthlySales: NumberField, keyFeatures: ArrayField, successFactors: ArrayField, source: StringField, data: ObjectField, }, CompetitorCampaign: { name: StringField, status: StringField, asins: ArrayField, data: ObjectField }, TikTokSearchHistory: { keyword: StringField, platform: StringField, region: StringField, sortType: NumberField, publishTime: NumberField, videoCount: NumberField, totalViews: NumberField, totalLikes: NumberField, totalComments: NumberField, totalShares: NumberField, avgEngagement: NumberField, searchTime: StringField, videosData: ArrayField, data: ObjectField, }, MonitoringStoreDetail: { shopId: StringField, name: StringField, totalSales: NumberField, totalAmount: NumberField, avgPrice: NumberField, healthScore: NumberField, rating: NumberField, returnRate: NumberField, positiveRate: NumberField, vocVolume: NumberField, newArrivalCount: NumberField, data: ObjectField, }, VocReviewAnnotation: { annotationId: StringField, reviewKey: StringField, asin: StringField, star: NumberField, reviewTitle: StringField, reviewDate: StringField, annotationStatus: StringField, status: StringField, sentiment: StringField, level1Category: StringField, level2Category: StringField, severity: StringField, returnRelated: BooleanField, owner: StringField, source: StringField, annotatedAt: StringField, data: ObjectField, }, RegionReturnData: { region: StringField, returnRate: NumberField, data: ObjectField }, RealTimeVoc: { data: ObjectField }, MerchantListing: productFields, SorftimeReviewProduct: { recordKey: StringField, reviewId: StringField, asin: StringField, marketplaceId: StringField, domain: NumberField, source: StringField, capturedAt: DateField, shop: pointer('Shop'), }, SpApiSellerAccount: { recordKey: StringField, sellerId: StringField, marketplaceId: StringField, businessType: StringField, region: StringField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, SpApiMarketplaceParticipation: { recordKey: StringField, marketplaceId: StringField, name: StringField, countryCode: StringField, region: StringField, participation: ObjectField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonInventorySummary: { recordKey: StringField, marketplaceId: StringField, asin: StringField, sellerSku: StringField, fnSku: StringField, condition: StringField, productName: StringField, totalQuantity: NumberField, inventoryDetails: ObjectField, lastUpdatedTime: DateField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonCatalogItem: { recordKey: StringField, marketplaceId: StringField, asin: StringField, title: StringField, brand: StringField, productType: StringField, attributes: ObjectField, dimensions: ArrayField, identifiers: ArrayField, images: ArrayField, relationships: ArrayField, salesRanks: ArrayField, summaries: ArrayField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonProductPricing: { recordKey: StringField, marketplaceId: StringField, asin: StringField, sku: StringField, status: StringField, product: ObjectField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonSalesMetric: { recordKey: StringField, marketplaceId: StringField, interval: StringField, granularity: StringField, unitCount: NumberField, orderItemCount: NumberField, orderCount: NumberField, averageUnitPrice: ObjectField, totalSales: ObjectField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonOrderItem: { recordKey: StringField, marketplaceId: StringField, amazonOrderId: StringField, orderItemId: StringField, asin: StringField, sellerSku: StringField, title: StringField, quantityOrdered: NumberField, quantityShipped: NumberField, itemPrice: ObjectField, itemTax: ObjectField, promotionDiscount: ObjectField, promotionIds: ArrayField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), order: pointer('Order'), }, AmazonFinancialEvent: { recordKey: StringField, region: StringField, eventType: StringField, amazonOrderId: StringField, sellerOrderId: StringField, marketplaceName: StringField, postedDate: DateField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonCustomerFeedback: { recordKey: StringField, marketplaceId: StringField, feedbackType: StringField, asin: StringField, browseNodeId: StringField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonInboundShipment: { recordKey: StringField, region: StringField, marketplaceId: StringField, shipmentId: StringField, shipmentName: StringField, shipmentStatus: StringField, destinationFulfillmentCenterId: StringField, labelPrepType: StringField, capturedAt: DateField, rawData: ObjectField, shop: pointer('Shop'), }, AmazonReportRow: { recordKey: StringField, rowHash: StringField, reportType: StringField, reportId: StringField, region: StringField, marketplaceIds: ArrayField, capturedAt: DateField, rowData: ObjectField, rawData: ObjectField, shop: pointer('Shop'), }, SpApiSyncAudit: { runId: StringField, dataType: StringField, endpoint: StringField, status: StringField, region: StringField, marketplaceId: StringField, records: NumberField, pages: NumberField, error: StringField, startedAt: DateField, finishedAt: DateField, details: ObjectField, shop: pointer('Shop'), }, }; const classNames = Object.keys(schemaDefinitions); const current = await request('/schemas'); const currentByName = new Map((current.results || []).map(item => [item.className, item])); const existing = new Set(currentByName.keys()); const adminOnly = { 'role:admin': true }; let created = 0; for (const className of classNames) { if (!existing.has(className)) { await request('/schemas', 'POST', { className, fields: {} }); created += 1; } const knownFields = currentByName.get(className)?.fields || {}; const fields = Object.fromEntries( Object.entries(schemaDefinitions[className] || {}).filter(([name]) => !knownFields[name]) ); await request(`/schemas/${encodeURIComponent(className)}`, 'PUT', { ...(Object.keys(fields).length ? { fields } : {}), classLevelPermissions: { find: adminOnly, count: adminOnly, get: adminOnly, create: adminOnly, update: adminOnly, delete: adminOnly, addField: {}, }, }); } console.log(JSON.stringify({ created, secured: classNames.length }));