183 lines
4.6 KiB
TypeScript
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,
|
|
}
|
|
},
|
|
})
|