Initial release of Moniki paid services site and CMS (v1.0.0).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
import 'dotenv/config'
|
||||
import { getPayload } from 'payload'
|
||||
import { paidDoctors } from './paid-doctors'
|
||||
import { seedPromotions } from './import-promotions'
|
||||
import config from '../payload.config'
|
||||
|
||||
const translations = {
|
||||
ru: {
|
||||
branchMain: 'КДЦ МОНИКИ (основной)',
|
||||
branchReutov: 'Филиал КДЦ МОНИКИ в г. Реутов',
|
||||
addrMain:
|
||||
'г. Москва, ул. Щепкина, 61/2. Станция метро «Проспект Мира», выход к спорткомплексу «Олимпийский».',
|
||||
addrReutov: 'Московская область, г. Реутов, проспект Мира, д. 53',
|
||||
howMain: 'От метро «Проспект Мира» пешком по улице Щепкина до корпуса института.',
|
||||
howReutov:
|
||||
'Московская область, г. Реутов, проспект Мира, д. 53. Удобно доехать наземным транспортом от метро «Новокосино» и от станции Реутов.',
|
||||
},
|
||||
en: {
|
||||
branchMain: 'MONIKI CDC (main)',
|
||||
branchReutov: 'MONIKI CDC branch in Reutov',
|
||||
addrMain: 'Moscow, Shchepkina St., 61/2. Prospect Mira metro station.',
|
||||
addrReutov: 'Moscow Region, Reutov, Prospekt Mira, 53',
|
||||
howMain: 'From Prospect Mira metro walk along Shchepkina Street to the institute campus.',
|
||||
howReutov:
|
||||
'Moscow Region, Reutov, Prospekt Mira, 53. Convenient by surface transport from Novokosino metro and Reutov station.',
|
||||
},
|
||||
}
|
||||
|
||||
async function findOne(payload: Awaited<ReturnType<typeof getPayload>>, collection: 'branches' | 'specialties' | 'services' | 'doctors' | 'users' | 'posts', slugOrEmail: string) {
|
||||
const field = collection === 'users' ? 'email' : 'slug'
|
||||
const res = await payload.find({
|
||||
collection,
|
||||
where: { [field]: { equals: slugOrEmail } },
|
||||
limit: 1,
|
||||
})
|
||||
return res.docs[0]
|
||||
}
|
||||
|
||||
async function ensureBranchesSchema(payload: Awaited<ReturnType<typeof getPayload>>) {
|
||||
const adapter = payload.db as {
|
||||
drizzle: { execute: (query: unknown) => Promise<unknown> }
|
||||
}
|
||||
const { sql } = await import('@payloadcms/db-postgres')
|
||||
await adapter.drizzle.execute(sql`
|
||||
ALTER TABLE branches ADD COLUMN IF NOT EXISTS sort_order numeric DEFAULT 100
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches
|
||||
SET sort_order = 10, is_main = true, lat = 55.786637, lng = 37.627265
|
||||
WHERE slug = 'kdc-moniki'
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches
|
||||
SET sort_order = 20, is_main = false, lat = 55.771049, lng = 37.862507
|
||||
WHERE slug = 'kdc-reutov'
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches_locales
|
||||
SET
|
||||
name = 'КДЦ МОНИКИ (основной)',
|
||||
address = 'г. Москва, ул. Щепкина, 61/2. Станция метро «Проспект Мира», выход к спорткомплексу «Олимпийский».',
|
||||
city = 'Москва',
|
||||
metro = 'Проспект Мира',
|
||||
how_to_get = 'От метро «Проспект Мира» пешком по улице Щепкина до корпуса института.'
|
||||
WHERE _locale = 'ru'
|
||||
AND _parent_id IN (SELECT id FROM branches WHERE slug = 'kdc-moniki')
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches_locales
|
||||
SET
|
||||
name = 'Филиал КДЦ МОНИКИ в г. Реутов',
|
||||
address = 'Московская область, г. Реутов, проспект Мира, д. 53',
|
||||
city = 'Реутов',
|
||||
how_to_get = 'Московская область, г. Реутов, проспект Мира, д. 53. Удобно доехать наземным транспортом от метро «Новокосино» и от станции Реутов.'
|
||||
WHERE _locale = 'ru'
|
||||
AND _parent_id IN (SELECT id FROM branches WHERE slug = 'kdc-reutov')
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches_locales
|
||||
SET
|
||||
name = 'MONIKI CDC (main)',
|
||||
address = 'Moscow, Shchepkina St., 61/2. Prospect Mira metro station.',
|
||||
city = 'Moscow',
|
||||
metro = 'Prospect Mira',
|
||||
how_to_get = 'From Prospect Mira metro walk along Shchepkina Street to the institute campus.'
|
||||
WHERE _locale = 'en'
|
||||
AND _parent_id IN (SELECT id FROM branches WHERE slug = 'kdc-moniki')
|
||||
`)
|
||||
await adapter.drizzle.execute(sql`
|
||||
UPDATE branches_locales
|
||||
SET
|
||||
name = 'MONIKI CDC branch in Reutov',
|
||||
address = 'Moscow Region, Reutov, Prospekt Mira, 53',
|
||||
city = 'Reutov',
|
||||
how_to_get = 'Moscow Region, Reutov, Prospekt Mira, 53. Convenient by surface transport from Novokosino metro and Reutov station.'
|
||||
WHERE _locale = 'en'
|
||||
AND _parent_id IN (SELECT id FROM branches WHERE slug = 'kdc-reutov')
|
||||
`)
|
||||
}
|
||||
|
||||
async function seed() {
|
||||
const payload = await getPayload({ config })
|
||||
await ensureBranchesSchema(payload)
|
||||
|
||||
console.log('Seeding MONIKI demo data...')
|
||||
|
||||
const adminEmail = 'admin@moniki.local'
|
||||
const existingAdmin = await payload.find({
|
||||
collection: 'users',
|
||||
where: { email: { equals: adminEmail } },
|
||||
limit: 1,
|
||||
})
|
||||
if (!existingAdmin.docs.length) {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: adminEmail,
|
||||
password: 'Admin123!',
|
||||
fullName: 'Администратор МОНИКИ',
|
||||
role: 'admin',
|
||||
phone: '+7 (499) 674-07-09',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const patientEmail = 'patient@demo.local'
|
||||
const existingPatient = await payload.find({
|
||||
collection: 'users',
|
||||
where: { email: { equals: patientEmail } },
|
||||
limit: 1,
|
||||
})
|
||||
if (!existingPatient.docs.length) {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: patientEmail,
|
||||
password: 'Patient123!',
|
||||
fullName: 'Иванова Анна Сергеевна',
|
||||
role: 'patient',
|
||||
phone: '+7 (900) 123-45-67',
|
||||
smsOptIn: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let branchMain = await findOne(payload, 'branches', 'kdc-moniki')
|
||||
if (!branchMain) {
|
||||
branchMain = await payload.create({
|
||||
collection: 'branches',
|
||||
locale: 'ru',
|
||||
data: {
|
||||
name: translations.ru.branchMain,
|
||||
slug: 'kdc-moniki',
|
||||
address: translations.ru.addrMain,
|
||||
city: 'Москва',
|
||||
phone: '+7 (499) 674-07-09',
|
||||
email: 'mail@monikiweb.ru',
|
||||
metro: 'Проспект Мира',
|
||||
howToGet: translations.ru.howMain,
|
||||
isMain: true,
|
||||
sortOrder: 10,
|
||||
lat: 55.786637,
|
||||
lng: 37.627265,
|
||||
workHours: [
|
||||
{ day: 'Пн–Сб', hours: '08:00–20:00' },
|
||||
{ day: 'Вс', hours: '09:00–16:00' },
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let branchReutov = await findOne(payload, 'branches', 'kdc-reutov')
|
||||
if (!branchReutov) {
|
||||
branchReutov = await payload.create({
|
||||
collection: 'branches',
|
||||
locale: 'ru',
|
||||
data: {
|
||||
name: translations.ru.branchReutov,
|
||||
slug: 'kdc-reutov',
|
||||
address: translations.ru.addrReutov,
|
||||
city: 'Реутов',
|
||||
phone: '+7 (499) 674-07-09',
|
||||
howToGet: translations.ru.howReutov,
|
||||
isMain: false,
|
||||
sortOrder: 20,
|
||||
lat: 55.771049,
|
||||
lng: 37.862507,
|
||||
workHours: [{ day: 'Пн–Сб', hours: '08:00–20:00' }],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const specialtyDefs = [
|
||||
{ title: 'Терапия', slug: 'therapy', icon: 'heart' },
|
||||
{ title: 'Кардиология', slug: 'cardiology', icon: 'activity' },
|
||||
{ title: 'Неврология', slug: 'neurology', icon: 'cpu-chip' },
|
||||
{ title: 'Гастроэнтерология', slug: 'gastro', icon: 'beaker' },
|
||||
{ title: 'Эндокринология', slug: 'endo', icon: 'flash' },
|
||||
{ title: 'Диагностика', slug: 'diagnostics', icon: 'scan' },
|
||||
]
|
||||
|
||||
const specialties: Record<string, string | number> = {}
|
||||
for (const [i, s] of specialtyDefs.entries()) {
|
||||
let doc = await findOne(payload, 'specialties', s.slug)
|
||||
if (!doc) {
|
||||
doc = await payload.create({
|
||||
collection: 'specialties',
|
||||
locale: 'ru',
|
||||
data: {
|
||||
title: s.title,
|
||||
slug: s.slug,
|
||||
description: `Платные услуги направления «${s.title}» в МОНИКИ`,
|
||||
icon: s.icon,
|
||||
sortOrder: i,
|
||||
},
|
||||
})
|
||||
}
|
||||
specialties[s.slug] = doc.id
|
||||
await payload.update({
|
||||
collection: 'specialties',
|
||||
id: doc.id,
|
||||
locale: 'en',
|
||||
data: {
|
||||
title: s.slug.charAt(0).toUpperCase() + s.slug.slice(1),
|
||||
description: `Paid ${s.slug} services at MONIKI`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const serviceDefs = [
|
||||
{
|
||||
title: 'Консультация терапевта',
|
||||
slug: 'consult-therapy',
|
||||
price: 3500,
|
||||
durationMinutes: 30,
|
||||
specialty: 'therapy',
|
||||
category: 'consultation' as const,
|
||||
isPopular: true,
|
||||
},
|
||||
{
|
||||
title: 'Консультация кардиолога',
|
||||
slug: 'consult-cardio',
|
||||
price: 4500,
|
||||
durationMinutes: 40,
|
||||
specialty: 'cardiology',
|
||||
category: 'consultation' as const,
|
||||
isPopular: true,
|
||||
},
|
||||
{
|
||||
title: 'Консультация невролога',
|
||||
slug: 'consult-neuro',
|
||||
price: 4200,
|
||||
durationMinutes: 40,
|
||||
specialty: 'neurology',
|
||||
category: 'consultation' as const,
|
||||
isPopular: false,
|
||||
},
|
||||
{
|
||||
title: 'УЗИ органов брюшной полости',
|
||||
slug: 'uzi-abdomen',
|
||||
price: 3200,
|
||||
durationMinutes: 30,
|
||||
specialty: 'diagnostics',
|
||||
category: 'diagnostics' as const,
|
||||
isPopular: true,
|
||||
},
|
||||
{
|
||||
title: 'ЭКГ с расшифровкой',
|
||||
slug: 'ecg',
|
||||
price: 1800,
|
||||
durationMinutes: 20,
|
||||
specialty: 'cardiology',
|
||||
category: 'diagnostics' as const,
|
||||
isPopular: true,
|
||||
},
|
||||
{
|
||||
title: 'Комплексное обследование «Чек-ап»',
|
||||
slug: 'checkup',
|
||||
price: 18900,
|
||||
durationMinutes: 120,
|
||||
specialty: 'diagnostics',
|
||||
category: 'package' as const,
|
||||
isPopular: true,
|
||||
},
|
||||
]
|
||||
|
||||
const services: Record<string, string | number> = {}
|
||||
for (const s of serviceDefs) {
|
||||
let doc = await findOne(payload, 'services', s.slug)
|
||||
if (!doc) {
|
||||
doc = await payload.create({
|
||||
collection: 'services',
|
||||
locale: 'ru',
|
||||
data: {
|
||||
title: s.title,
|
||||
slug: s.slug,
|
||||
description: `${s.title} в платных отделениях МОНИКИ. Полная оплата через СБП.`,
|
||||
price: s.price,
|
||||
durationMinutes: s.durationMinutes,
|
||||
specialty: specialties[s.specialty],
|
||||
category: s.category,
|
||||
branches: [branchMain.id, branchReutov.id],
|
||||
isPopular: s.isPopular,
|
||||
isPublished: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
services[s.slug] = doc.id
|
||||
}
|
||||
|
||||
const doctors: Record<string, string | number> = {}
|
||||
for (const d of paidDoctors) {
|
||||
let doc = await findOne(payload, 'doctors', d.slug)
|
||||
if (!doc) {
|
||||
doc = await payload.create({
|
||||
collection: 'doctors',
|
||||
locale: 'ru',
|
||||
data: {
|
||||
fullName: d.fullName,
|
||||
slug: d.slug,
|
||||
position: [d.position, d.specialty]
|
||||
.filter((value, index, arr) => value && arr.indexOf(value) === index)
|
||||
.join(' · '),
|
||||
degree: d.degree || undefined,
|
||||
experienceYears: d.experienceYears,
|
||||
bio: d.bio,
|
||||
photoUrl: d.photo,
|
||||
photoPending: d.photoPending,
|
||||
specialties: d.specialtySlugs.map((s) => specialties[s]).filter(Boolean),
|
||||
branches: [branchMain.id],
|
||||
education: d.education.map((item) => ({ item })),
|
||||
defaultSlotMinutes: d.defaultSlotMinutes,
|
||||
isPublished: true,
|
||||
rating: 0,
|
||||
reviewsCount: 0,
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'schedules',
|
||||
data: {
|
||||
title: `Расписание: ${d.fullName}`,
|
||||
doctor: doc.id,
|
||||
branch: branchMain.id,
|
||||
slotMinutes: d.defaultSlotMinutes,
|
||||
isActive: true,
|
||||
weekdays: [
|
||||
{ day: '1', startTime: '09:00', endTime: '14:00' },
|
||||
{ day: '2', startTime: '09:00', endTime: '14:00' },
|
||||
{ day: '3', startTime: '14:00', endTime: '19:00' },
|
||||
{ day: '4', startTime: '09:00', endTime: '14:00' },
|
||||
{ day: '5', startTime: '09:00', endTime: '13:00' },
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
doctors[d.slug] = doc.id
|
||||
}
|
||||
|
||||
const existingReviews = await payload.find({ collection: 'reviews', limit: 1 })
|
||||
if (!existingReviews.docs.length) {
|
||||
console.log('Doctor reviews skipped')
|
||||
}
|
||||
|
||||
await seedPromotions(payload)
|
||||
|
||||
console.log('Seed complete.')
|
||||
console.log('Admin: admin@moniki.local / Admin123!')
|
||||
console.log('Patient: patient@demo.local / Patient123!')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
seed().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user