Skip to content

Commit d9f0790

Browse files
committed
fix: ...duplicate records?
1 parent 8f321d0 commit d9f0790

1 file changed

Lines changed: 77 additions & 52 deletions

File tree

background/src/index.ts

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -50,66 +50,91 @@ class AirtableSyncWorker {
5050
console.log(`Deleted ${deleteResult.count} records no longer in Airtable`);
5151
}
5252

53-
for (const record of records) {
54-
const slug = record.get('slug') as string;
55-
const websiteJson = record.get('website_json') as string;
56-
const websiteActive = record.get('website_active') === true;
57-
58-
if (!slug) {
59-
console.warn(`Skipping record ${record.id}: missing slug`);
60-
continue;
61-
}
53+
// Parse all records first
54+
const parsedRecords = records
55+
.map(record => {
56+
const slug = record.get('slug') as string;
57+
const websiteJson = record.get('website_json') as string;
58+
const websiteActive = record.get('website_active') === true;
59+
60+
if (!slug) {
61+
console.warn(`Skipping record ${record.id}: missing slug`);
62+
return null;
63+
}
6264

63-
let data: any;
64-
try {
65-
data = websiteJson ? JSON.parse(websiteJson) : {};
66-
if (websiteActive && data.version !== VERSION) {
67-
data = {
68-
error: "Your JSON is outdated. Please update it!"
65+
let data: any;
66+
try {
67+
data = websiteJson ? JSON.parse(websiteJson) : {};
68+
if (websiteActive && data.version !== VERSION) {
69+
data = { error: "Your JSON is outdated. Please update it!" };
6970
}
71+
} catch {
72+
console.error(`Error parsing JSON for slug ${slug}`);
73+
data = { error: "Error parsing JSON. Make sure the JSON is valid!" };
7074
}
71-
} catch (error) {
72-
console.error(`Error parsing JSON for slug ${slug}:`, error);
73-
data = {
74-
error: "Error parsing JSON. Make sure the JSON is valid!"
75-
};
75+
76+
return { recordId: record.id, slug, data, active: websiteActive };
77+
})
78+
.filter((r): r is NonNullable<typeof r> => r !== null);
79+
80+
// Check for duplicate slugs in Airtable data and skip them
81+
const slugCounts = new Map<string, string[]>();
82+
for (const r of parsedRecords) {
83+
const existing = slugCounts.get(r.slug) || [];
84+
existing.push(r.recordId);
85+
slugCounts.set(r.slug, existing);
86+
}
87+
const duplicateSlugs = new Set<string>();
88+
for (const [slug, recordIds] of slugCounts) {
89+
if (recordIds.length > 1) {
90+
console.error(`Duplicate slug "${slug}" found in Airtable for records: ${recordIds.join(', ')} - skipping all`);
91+
duplicateSlugs.add(slug);
7692
}
93+
}
94+
const deduplicatedRecords = parsedRecords.filter(r => !duplicateSlugs.has(r.slug));
7795

78-
try {
79-
await prisma.satellite.upsert({
80-
where: { recordId: record.id },
81-
update: {
82-
slug,
83-
data,
84-
active: websiteActive,
85-
updatedAt: new Date(),
86-
},
87-
create: {
88-
recordId: record.id,
89-
slug,
90-
data,
91-
active: websiteActive,
92-
},
93-
});
94-
console.log(`✓ Synced: ${slug} - Active: ${websiteActive}`);
95-
} catch (upsertError: any) {
96-
if (upsertError?.code === 'P2002') {
97-
const field = upsertError.meta?.target?.[0] || 'unknown';
98-
const existing = await prisma.satellite.findFirst({
99-
where: field === 'slug' ? { slug } : { recordId: record.id },
100-
});
101-
console.error(
102-
`Unique constraint violation on "${field}" for record ${record.id} (slug: ${slug}).`,
103-
existing
104-
? `Conflicting record: id=${existing.id}, recordId=${existing.recordId}, slug=${existing.slug}`
105-
: 'Could not find conflicting record.'
106-
);
107-
} else {
108-
console.error(`Failed to upsert record ${record.id} (slug: ${slug}):`, upsertError);
109-
}
96+
// Fetch existing records by recordId
97+
const existingRecords = await prisma.satellite.findMany({
98+
where: { recordId: { in: deduplicatedRecords.map(r => r.recordId) } },
99+
});
100+
const existingByRecordId = new Map(existingRecords.map(r => [r.recordId, r]));
101+
102+
const toCreate: typeof deduplicatedRecords = [];
103+
const toUpdate: { id: number; data: typeof deduplicatedRecords[0] }[] = [];
104+
105+
for (const record of deduplicatedRecords) {
106+
const existing = existingByRecordId.get(record.recordId);
107+
if (existing) {
108+
toUpdate.push({ id: existing.id, data: record });
109+
} else {
110+
toCreate.push(record);
110111
}
111112
}
112113

114+
// Batch create
115+
if (toCreate.length > 0) {
116+
await prisma.satellite.createMany({ data: toCreate });
117+
console.log(`✓ Created ${toCreate.length} new records`);
118+
}
119+
120+
// Batch update (Prisma doesn't support batch update, so we do it in parallel)
121+
if (toUpdate.length > 0) {
122+
await Promise.all(
123+
toUpdate.map(({ id, data }) =>
124+
prisma.satellite.update({
125+
where: { id },
126+
data: {
127+
slug: data.slug,
128+
data: data.data,
129+
active: data.active,
130+
updatedAt: new Date(),
131+
},
132+
})
133+
)
134+
);
135+
console.log(`✓ Updated ${toUpdate.length} existing records`);
136+
}
137+
113138
console.log(`[${new Date().toISOString()}] Sync completed successfully`);
114139
} catch (error) {
115140
console.error(`[${new Date().toISOString()}] Sync failed:`, error);

0 commit comments

Comments
 (0)