feat(30-01): create onboarding service with batch item creation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
122
src/server/services/onboarding.service.ts
Normal file
122
src/server/services/onboarding.service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { db as prodDb } from "../../db/index.ts";
|
||||
import { categories, globalItems, items, settings } from "../../db/schema.ts";
|
||||
|
||||
type Db = typeof prodDb;
|
||||
|
||||
interface OnboardingResult {
|
||||
itemsCreated: number;
|
||||
categoriesCreated: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete onboarding by batch-creating user items from selected global catalog items.
|
||||
* Auto-creates categories based on the global items' category field.
|
||||
* Sets onboardingComplete setting to "true".
|
||||
*/
|
||||
export async function completeOnboarding(
|
||||
db: Db = prodDb,
|
||||
userId: number,
|
||||
globalItemIds: number[],
|
||||
): Promise<OnboardingResult> {
|
||||
if (globalItemIds.length === 0) {
|
||||
// No items selected — just mark complete
|
||||
await db
|
||||
.insert(settings)
|
||||
.values({ userId, key: "onboardingComplete", value: "true" })
|
||||
.onConflictDoUpdate({
|
||||
target: [settings.userId, settings.key],
|
||||
set: { value: "true" },
|
||||
});
|
||||
return { itemsCreated: 0, categoriesCreated: [] };
|
||||
}
|
||||
|
||||
// Fetch all selected global items
|
||||
const selectedItems = await db
|
||||
.select()
|
||||
.from(globalItems)
|
||||
.where(inArray(globalItems.id, globalItemIds));
|
||||
|
||||
if (selectedItems.length === 0) {
|
||||
await db
|
||||
.insert(settings)
|
||||
.values({ userId, key: "onboardingComplete", value: "true" })
|
||||
.onConflictDoUpdate({
|
||||
target: [settings.userId, settings.key],
|
||||
set: { value: "true" },
|
||||
});
|
||||
return { itemsCreated: 0, categoriesCreated: [] };
|
||||
}
|
||||
|
||||
// Collect unique category names from global items
|
||||
const categoryNames = [
|
||||
...new Set(
|
||||
selectedItems
|
||||
.map((gi) => gi.category)
|
||||
.filter((c): c is string => c !== null && c.trim() !== ""),
|
||||
),
|
||||
];
|
||||
|
||||
// Get existing user categories
|
||||
const existingCats = await db
|
||||
.select()
|
||||
.from(categories)
|
||||
.where(eq(categories.userId, userId));
|
||||
|
||||
const existingCatMap = new Map(
|
||||
existingCats.map((c) => [c.name.toLowerCase(), c.id]),
|
||||
);
|
||||
|
||||
// Create missing categories
|
||||
const newCategoryNames: string[] = [];
|
||||
for (const catName of categoryNames) {
|
||||
if (!existingCatMap.has(catName.toLowerCase())) {
|
||||
const [created] = await db
|
||||
.insert(categories)
|
||||
.values({ name: catName, userId })
|
||||
.returning();
|
||||
existingCatMap.set(catName.toLowerCase(), created.id);
|
||||
newCategoryNames.push(catName);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the "Uncategorized" category for items without a category
|
||||
let uncategorizedId = existingCatMap.get("uncategorized");
|
||||
if (!uncategorizedId) {
|
||||
const [unc] = await db
|
||||
.insert(categories)
|
||||
.values({ name: "Uncategorized", userId })
|
||||
.returning();
|
||||
uncategorizedId = unc.id;
|
||||
}
|
||||
|
||||
// Create user items linked to global items
|
||||
let itemsCreated = 0;
|
||||
for (const gi of selectedItems) {
|
||||
const catId = gi.category
|
||||
? (existingCatMap.get(gi.category.toLowerCase()) ?? uncategorizedId)
|
||||
: uncategorizedId;
|
||||
|
||||
await db.insert(items).values({
|
||||
name: gi.brand ? `${gi.brand} ${gi.model}` : gi.model,
|
||||
categoryId: catId,
|
||||
userId,
|
||||
weightGrams: gi.weightGrams,
|
||||
priceCents: gi.priceCents,
|
||||
imageFilename: gi.imageFilename,
|
||||
globalItemId: gi.id,
|
||||
});
|
||||
itemsCreated++;
|
||||
}
|
||||
|
||||
// Mark onboarding complete
|
||||
await db
|
||||
.insert(settings)
|
||||
.values({ userId, key: "onboardingComplete", value: "true" })
|
||||
.onConflictDoUpdate({
|
||||
target: [settings.userId, settings.key],
|
||||
set: { value: "true" },
|
||||
});
|
||||
|
||||
return { itemsCreated, categoriesCreated: newCategoryNames };
|
||||
}
|
||||
Reference in New Issue
Block a user