From 7415e645863287464f92e5b72c16cb1c183c116d Mon Sep 17 00:00:00 2001 From: Alexey Novikov Date: Wed, 16 Sep 2026 15:07:47 +0300 Subject: [PATCH] Import doctor reviews through Payload so production seed no longer depends on local psql. Co-authored-by: Cursor --- apps/cms/src/seed/import-reviews.ts | 97 ++++++++++++----------------- apps/cms/src/seed/index.ts | 3 +- apps/web/src/lib/api.ts | 1 + 3 files changed, 43 insertions(+), 58 deletions(-) diff --git a/apps/cms/src/seed/import-reviews.ts b/apps/cms/src/seed/import-reviews.ts index a02ad62..0e0881f 100644 --- a/apps/cms/src/seed/import-reviews.ts +++ b/apps/cms/src/seed/import-reviews.ts @@ -1,4 +1,6 @@ import 'dotenv/config' +import { getPayload } from 'payload' +import config from '../payload.config' type ReviewSeed = { authorName: string; rating: 4 | 5; comment: string; daysAgo: number } @@ -347,30 +349,13 @@ const reviewsBySlug: Record = { ), } -function sqlString(value: string) { - return `'${value.replace(/'/g, "''")}'` -} - -async function main() { - const { execFileSync } = await import('node:child_process') - const rows = execFileSync('psql', ['-d', 'moniki', '-t', '-A', '-c', "SELECT slug || E'\\t' || id FROM doctors"], { - encoding: 'utf8', +export async function seedReviews(payload: Awaited>) { + const doctors = await payload.find({ + collection: 'doctors', + limit: 500, + depth: 0, }) - .trim() - .split('\n') - .filter(Boolean) - const bySlug = Object.fromEntries(rows.map((line) => line.split('\t'))) - const existingRows = execFileSync( - 'psql', - ['-d', 'moniki', '-t', '-A', '-c', 'SELECT doctor_id::text FROM reviews GROUP BY doctor_id'], - { encoding: 'utf8' }, - ) - .trim() - .split('\n') - .filter(Boolean) - const doctorsWithReviews = new Set(existingRows) - - const statements: string[] = ['BEGIN;'] + const bySlug = Object.fromEntries(doctors.docs.map((doc) => [String(doc.slug), doc.id])) let created = 0 for (const [slug, items] of Object.entries(reviewsBySlug)) { @@ -379,52 +364,50 @@ async function main() { console.log('skip missing doctor', slug) continue } - if (doctorsWithReviews.has(doctorId)) { + + const existing = await payload.find({ + collection: 'reviews', + where: { doctor: { equals: doctorId } }, + limit: 1, + }) + if (existing.docs.length) { console.log('already has reviews', slug) continue } + for (const item of items) { const createdAt = new Date() createdAt.setDate(createdAt.getDate() - item.daysAgo) - statements.push(` -WITH inserted AS ( - INSERT INTO reviews (doctor_id, author_name, rating, is_published, created_at, updated_at) - VALUES (${doctorId}, ${sqlString(item.authorName)}, ${item.rating}, true, ${sqlString(createdAt.toISOString())}, now()) - RETURNING id -) -INSERT INTO reviews_locales (comment, _locale, _parent_id) -SELECT ${sqlString(item.comment)}, 'ru', id FROM inserted; -`) + await payload.create({ + collection: 'reviews', + locale: 'ru', + data: { + doctor: doctorId, + authorName: item.authorName, + rating: item.rating, + comment: item.comment, + isPublished: true, + createdAt: createdAt.toISOString(), + updatedAt: createdAt.toISOString(), + }, + }) created += 1 } console.log(slug, items.length) } - statements.push(` -UPDATE doctors d SET - reviews_count = stats.cnt, - rating = stats.avg -FROM ( - SELECT doctor_id, COUNT(*)::numeric AS cnt, ROUND(AVG(rating)::numeric, 1) AS avg - FROM reviews - WHERE is_published = true - GROUP BY doctor_id -) stats -WHERE d.id = stats.doctor_id; -COMMIT; -`) - - execFileSync('psql', ['-d', 'moniki', '-v', 'ON_ERROR_STOP=1'], { - input: statements.join('\n'), - encoding: 'utf8', - stdio: ['pipe', 'inherit', 'inherit'], - }) - console.log('reviews created', created) +} + +async function main() { + const payload = await getPayload({ config }) + await seedReviews(payload) process.exit(0) } -main().catch((err) => { - console.error(err) - process.exit(1) -}) +if (process.argv[1]?.includes('import-reviews')) { + main().catch((err) => { + console.error(err) + process.exit(1) + }) +} diff --git a/apps/cms/src/seed/index.ts b/apps/cms/src/seed/index.ts index c24a689..53e8bc9 100644 --- a/apps/cms/src/seed/index.ts +++ b/apps/cms/src/seed/index.ts @@ -2,6 +2,7 @@ import 'dotenv/config' import { getPayload } from 'payload' import { paidDoctors } from './paid-doctors' import { seedPromotions } from './import-promotions' +import { seedReviews } from './import-reviews' import config from '../payload.config' const translations = { @@ -359,7 +360,7 @@ async function seed() { const existingReviews = await payload.find({ collection: 'reviews', limit: 1 }) if (!existingReviews.docs.length) { - console.log('Doctor reviews skipped') + await seedReviews(payload) } await seedPromotions(payload) diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts index 3c0d1ab..6bccf87 100644 --- a/apps/web/src/lib/api.ts +++ b/apps/web/src/lib/api.ts @@ -700,6 +700,7 @@ export async function getReviewsPage(params?: { page: String(page), depth: "1", sort: "-createdAt", + "where[isPublished][equals]": "true", }), params?.locale, );