feat(11-01): add tag-assignment migration and validation scripts (OFFER-13/D-02)
- scripts/migrate-tags.ts: idempotently assigns the "Offerta" tag to every services row where migrated_from = 'offer_services' (D-02) - scripts/validate-tags-migration.ts: row-count + orphan checks for the tags consolidation dimension of OFFER-13 NOTE: Not yet executed against the dev/staging DB (178.104.27.55:54321) — that host is firewalled and only reachable via SSH+docker exec per project memory, which is out of scope for this agent's authorization. Execution of this script, scripts/migrate-services.ts, scripts/validate-services-migration.ts, and scripts/push-11-tags-migration.ts is documented as a pending access gate in 11-01-SUMMARY.md.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { db } from "@/db";
|
||||
import { services, tags } from "@/db/schema";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
|
||||
async function migrate() {
|
||||
console.log("Starting tags migration: assigning 'Offerta' tag to services migrated from offer_services...\n");
|
||||
|
||||
const offerServices = await db
|
||||
.select({ id: services.id })
|
||||
.from(services)
|
||||
.where(eq(services.migrated_from, "offer_services"));
|
||||
|
||||
let taggedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const service of offerServices) {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(tags)
|
||||
.where(
|
||||
and(
|
||||
eq(tags.entity_type, "services"),
|
||||
eq(tags.entity_id, service.id),
|
||||
eq(tags.name, "Offerta")
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.insert(tags).values({
|
||||
entity_type: "services",
|
||||
entity_id: service.id,
|
||||
name: "Offerta",
|
||||
});
|
||||
taggedCount++;
|
||||
}
|
||||
|
||||
console.log(`Assigned 'Offerta' tag: ${taggedCount} services, ${skippedCount} already tagged`);
|
||||
console.log("\nMigration complete. Run scripts/validate-tags-migration.ts next.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
migrate().catch((err) => {
|
||||
console.error("Migration failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { db } from "@/db";
|
||||
import { services, tags } from "@/db/schema";
|
||||
import { eq, and, sql } from "drizzle-orm";
|
||||
|
||||
async function validate() {
|
||||
let failures = 0;
|
||||
|
||||
const [offerServicesCount] = await db
|
||||
.select({ n: sql<number>`count(*)::int` })
|
||||
.from(services)
|
||||
.where(eq(services.migrated_from, "offer_services"));
|
||||
|
||||
const [offerTagCount] = await db
|
||||
.select({ n: sql<number>`count(*)::int` })
|
||||
.from(tags)
|
||||
.where(and(eq(tags.entity_type, "services"), eq(tags.name, "Offerta")));
|
||||
|
||||
console.log(`offer_services-derived services: ${offerServicesCount.n} | Offerta tags: ${offerTagCount.n}`);
|
||||
|
||||
if (offerServicesCount.n !== offerTagCount.n) {
|
||||
console.log("FAIL: Offerta tag count does not match offer_services-derived services count");
|
||||
failures++;
|
||||
} else {
|
||||
console.log("PASS: all offer_services-derived services have the Offerta tag");
|
||||
}
|
||||
|
||||
const orphanedTags = await db.execute(sql`
|
||||
SELECT COUNT(*)::int AS n FROM tags t
|
||||
WHERE t.entity_type = 'services'
|
||||
AND NOT EXISTS (SELECT 1 FROM services s WHERE s.id = t.entity_id)
|
||||
`);
|
||||
const orphanedTagsCount = (orphanedTags as unknown as Array<{ n: number }>)[0]?.n ?? 0;
|
||||
if (orphanedTagsCount > 0) {
|
||||
console.log(`FAIL: ${orphanedTagsCount} tags reference non-existent services`);
|
||||
failures++;
|
||||
} else {
|
||||
console.log("PASS: no orphaned tag references");
|
||||
}
|
||||
|
||||
const tagCounts = await db.execute(sql`
|
||||
SELECT entity_type, COUNT(*)::int AS n FROM tags GROUP BY entity_type
|
||||
`);
|
||||
const counts = tagCounts as unknown as Array<{ entity_type: string; n: number }>;
|
||||
for (const row of counts) {
|
||||
console.log(`INFO: ${row.n} tags for entity_type=${row.entity_type}`);
|
||||
}
|
||||
|
||||
console.log(`\n${failures === 0 ? "ALL CHECKS PASSED" : `${failures} CHECK(S) FAILED`}`);
|
||||
process.exit(failures === 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
validate().catch((err) => {
|
||||
console.error("Validation failed:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user