122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
import { PrismaClient } from '@prisma/client';
|
||
import bcrypt from 'bcryptjs';
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
async function main() {
|
||
// -- Settings singleton --
|
||
await prisma.settings.upsert({
|
||
where: { id: 'singleton' },
|
||
update: {},
|
||
create: { id: 'singleton', pickupEnabled: true },
|
||
});
|
||
console.log('✓ Settings singleton ensured');
|
||
|
||
// -- Initial admin --
|
||
const email = (process.env.SEED_ADMIN_EMAIL ?? 'admin@example.com').toLowerCase();
|
||
const password = process.env.SEED_ADMIN_PASSWORD ?? 'change-me';
|
||
const name = process.env.SEED_ADMIN_NAME ?? 'Admin';
|
||
|
||
const passwordHash = await bcrypt.hash(password, 12);
|
||
await prisma.admin.upsert({
|
||
where: { email },
|
||
update: {},
|
||
create: { email, name, passwordHash },
|
||
});
|
||
console.log(`✓ Admin ensured: ${email}`);
|
||
|
||
// -- Products (example LPG cylinders) --
|
||
const products = [
|
||
{
|
||
sku: 'P6',
|
||
nameSv: 'Gasoltub P6',
|
||
nameEn: 'LPG cylinder P6',
|
||
descriptionSv: '6 kg gasol — för mindre kök och campingkök.',
|
||
descriptionEn: '6 kg LPG — for smaller stoves and camping cooktops.',
|
||
priceOre: 39900, // 399 kr
|
||
sortOrder: 10,
|
||
},
|
||
{
|
||
sku: 'P11',
|
||
nameSv: 'Gasoltub P11',
|
||
nameEn: 'LPG cylinder P11',
|
||
descriptionSv: '11 kg gasol — vanligaste storleken för matlagning på läger.',
|
||
descriptionEn: '11 kg LPG — most common size for cooking at camps.',
|
||
priceOre: 64900, // 649 kr
|
||
sortOrder: 20,
|
||
},
|
||
{
|
||
sku: 'P19',
|
||
nameSv: 'Gasoltub P19',
|
||
nameEn: 'LPG cylinder P19',
|
||
descriptionSv: '19 kg gasol — för större kök eller längre vistelse.',
|
||
descriptionEn: '19 kg LPG — for larger kitchens or longer stays.',
|
||
priceOre: 99900, // 999 kr
|
||
sortOrder: 30,
|
||
},
|
||
];
|
||
|
||
for (const p of products) {
|
||
await prisma.product.upsert({
|
||
where: { sku: p.sku },
|
||
update: {
|
||
nameSv: p.nameSv,
|
||
nameEn: p.nameEn,
|
||
descriptionSv: p.descriptionSv,
|
||
descriptionEn: p.descriptionEn,
|
||
priceOre: p.priceOre,
|
||
sortOrder: p.sortOrder,
|
||
},
|
||
create: { ...p, active: true, vatBp: 2500 },
|
||
});
|
||
}
|
||
console.log(`✓ Seeded ${products.length} products`);
|
||
|
||
// -- Pickup slots (example: 3 days × 2 slots) --
|
||
const existing = await prisma.pickupSlot.count();
|
||
if (existing === 0) {
|
||
const baseDay = new Date();
|
||
baseDay.setUTCHours(8, 0, 0, 0);
|
||
// Place slots ~6 months out so they don't expire immediately.
|
||
baseDay.setUTCMonth(baseDay.getUTCMonth() + 6);
|
||
|
||
const slots = [
|
||
{ offsetDays: 0, startH: 9, endH: 12, labelSv: 'Dag 1 — Förmiddag', labelEn: 'Day 1 — Morning' },
|
||
{ offsetDays: 0, startH: 13, endH: 17, labelSv: 'Dag 1 — Eftermiddag', labelEn: 'Day 1 — Afternoon' },
|
||
{ offsetDays: 1, startH: 9, endH: 12, labelSv: 'Dag 2 — Förmiddag', labelEn: 'Day 2 — Morning' },
|
||
{ offsetDays: 1, startH: 13, endH: 17, labelSv: 'Dag 2 — Eftermiddag', labelEn: 'Day 2 — Afternoon' },
|
||
{ offsetDays: 2, startH: 9, endH: 12, labelSv: 'Dag 3 — Förmiddag', labelEn: 'Day 3 — Morning' },
|
||
];
|
||
for (const s of slots) {
|
||
const startsAt = new Date(baseDay);
|
||
startsAt.setUTCDate(baseDay.getUTCDate() + s.offsetDays);
|
||
startsAt.setUTCHours(s.startH, 0, 0, 0);
|
||
const endsAt = new Date(startsAt);
|
||
endsAt.setUTCHours(s.endH, 0, 0, 0);
|
||
|
||
await prisma.pickupSlot.create({
|
||
data: {
|
||
labelSv: s.labelSv,
|
||
labelEn: s.labelEn,
|
||
startsAt,
|
||
endsAt,
|
||
capacity: 50,
|
||
active: true,
|
||
},
|
||
});
|
||
}
|
||
console.log(`✓ Seeded ${slots.length} pickup slots`);
|
||
} else {
|
||
console.log(`• Pickup slots already exist (${existing}) — skipping`);
|
||
}
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error(e);
|
||
process.exit(1);
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
});
|