Rocky_Mountain_Vending/convex/manuals.ts

183 lines
4.6 KiB
TypeScript

// @ts-nocheck
import { mutation, query } from "./_generated/server"
import { v } from "convex/values"
import {
canonicalizeTenantDomain,
manualVisibleForTenant,
tenantDomainVariants,
} from "../lib/manuals-tenant"
const manualInput = v.object({
filename: v.string(),
path: v.string(),
manufacturer: v.string(),
category: v.string(),
size: v.optional(v.number()),
lastModified: v.optional(v.number()),
searchTerms: v.optional(v.array(v.string())),
commonNames: v.optional(v.array(v.string())),
thumbnailUrl: v.optional(v.string()),
manualUrl: v.optional(v.string()),
hasParts: v.optional(v.boolean()),
assetSource: v.optional(v.string()),
sourcePath: v.optional(v.string()),
sourceSite: v.optional(v.string()),
sourceDomain: v.optional(v.string()),
siteVisibility: v.optional(v.array(v.string())),
importBatch: v.optional(v.string()),
})
export const list = query({
args: {
domain: v.string(),
},
handler: async (ctx, args) => {
const tenantDomain = canonicalizeTenantDomain(args.domain)
if (!tenantDomain) {
return []
}
const manuals = await ctx.db.query("manuals").collect()
return manuals
.filter((manual) => manualVisibleForTenant(manual, tenantDomain))
.sort((a, b) => a.filename.localeCompare(b.filename))
},
})
export const dashboard = query({
args: {},
handler: async (ctx) => {
const manuals = await ctx.db.query("manuals").collect()
const categories = await ctx.db.query("manualCategories").collect()
const manufacturerMap = new Map<string, number>()
const categoryMap = new Map<string, number>()
for (const manual of manuals) {
manufacturerMap.set(
manual.manufacturer,
(manufacturerMap.get(manual.manufacturer) ?? 0) + 1
)
categoryMap.set(
manual.category,
(categoryMap.get(manual.category) ?? 0) + 1
)
}
return {
missingManuals: {
summary: {
total_expected_models: manuals.length,
models_missing_all: 0,
models_partial: 0,
},
},
qaData: [],
metadata: manuals,
structuredData: [],
semanticIndex: {
total_chunks: manuals.length,
},
acquisitionList: {
total_items: 0,
high_priority: 0,
medium_priority: 0,
low_priority: 0,
acquisition_list: [],
},
nameMapping: categories,
}
},
})
export const upsertMany = mutation({
args: {
manuals: v.array(manualInput),
},
handler: async (ctx, args) => {
const now = Date.now()
const results = []
for (const manual of args.manuals) {
const existing = await ctx.db
.query("manuals")
.withIndex("by_path", (q) => q.eq("path", manual.path))
.unique()
if (existing) {
await ctx.db.patch(existing._id, {
...manual,
updatedAt: now,
})
results.push(await ctx.db.get(existing._id))
} else {
const id = await ctx.db.insert("manuals", {
...manual,
createdAt: now,
updatedAt: now,
})
results.push(await ctx.db.get(id))
}
}
return results
},
})
export const backfillTenantVisibility = mutation({
args: {
domain: v.string(),
dryRun: v.optional(v.boolean()),
},
handler: async (ctx, args) => {
const tenantDomain = canonicalizeTenantDomain(args.domain)
if (!tenantDomain) {
throw new Error("A valid tenant domain is required.")
}
const aliases = tenantDomainVariants(tenantDomain)
const dryRun = Boolean(args.dryRun)
const now = Date.now()
const manuals = await ctx.db.query("manuals").collect()
let patched = 0
let alreadyCovered = 0
for (const manual of manuals) {
const visibilitySet = new Set(
(manual.siteVisibility || [])
.map((entry) => canonicalizeTenantDomain(entry))
.filter(Boolean)
)
const sourceDomain = canonicalizeTenantDomain(manual.sourceDomain)
const hasDomain =
aliases.some((alias) => visibilitySet.has(alias)) ||
(sourceDomain ? aliases.includes(sourceDomain) : false)
if (hasDomain) {
alreadyCovered += 1
continue
}
const nextVisibility = Array.from(
new Set([...visibilitySet, ...aliases])
).sort()
if (!dryRun) {
await ctx.db.patch(manual._id, {
sourceDomain: sourceDomain || tenantDomain,
siteVisibility: nextVisibility,
updatedAt: now,
})
}
patched += 1
}
return {
domain: tenantDomain,
total: manuals.length,
patched,
alreadyCovered,
dryRun,
}
},
})