| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- 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 }));
|