Import doctor reviews through Payload so production seed no longer depends on local psql.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alexey Novikov
2026-09-16 15:07:47 +03:00
co-authored by Cursor
parent cbd4299b58
commit 7415e64586
3 changed files with 43 additions and 58 deletions
+38 -55
View File
@@ -1,4 +1,6 @@
import 'dotenv/config' import 'dotenv/config'
import { getPayload } from 'payload'
import config from '../payload.config'
type ReviewSeed = { authorName: string; rating: 4 | 5; comment: string; daysAgo: number } type ReviewSeed = { authorName: string; rating: 4 | 5; comment: string; daysAgo: number }
@@ -347,30 +349,13 @@ const reviewsBySlug: Record<string, ReviewSeed[]> = {
), ),
} }
function sqlString(value: string) { export async function seedReviews(payload: Awaited<ReturnType<typeof getPayload>>) {
return `'${value.replace(/'/g, "''")}'` const doctors = await payload.find({
} collection: 'doctors',
limit: 500,
async function main() { depth: 0,
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',
}) })
.trim() const bySlug = Object.fromEntries(doctors.docs.map((doc) => [String(doc.slug), doc.id]))
.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;']
let created = 0 let created = 0
for (const [slug, items] of Object.entries(reviewsBySlug)) { for (const [slug, items] of Object.entries(reviewsBySlug)) {
@@ -379,52 +364,50 @@ async function main() {
console.log('skip missing doctor', slug) console.log('skip missing doctor', slug)
continue 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) console.log('already has reviews', slug)
continue continue
} }
for (const item of items) { for (const item of items) {
const createdAt = new Date() const createdAt = new Date()
createdAt.setDate(createdAt.getDate() - item.daysAgo) createdAt.setDate(createdAt.getDate() - item.daysAgo)
statements.push(` await payload.create({
WITH inserted AS ( collection: 'reviews',
INSERT INTO reviews (doctor_id, author_name, rating, is_published, created_at, updated_at) locale: 'ru',
VALUES (${doctorId}, ${sqlString(item.authorName)}, ${item.rating}, true, ${sqlString(createdAt.toISOString())}, now()) data: {
RETURNING id doctor: doctorId,
) authorName: item.authorName,
INSERT INTO reviews_locales (comment, _locale, _parent_id) rating: item.rating,
SELECT ${sqlString(item.comment)}, 'ru', id FROM inserted; comment: item.comment,
`) isPublished: true,
createdAt: createdAt.toISOString(),
updatedAt: createdAt.toISOString(),
},
})
created += 1 created += 1
} }
console.log(slug, items.length) 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) console.log('reviews created', created)
}
async function main() {
const payload = await getPayload({ config })
await seedReviews(payload)
process.exit(0) process.exit(0)
} }
main().catch((err) => { if (process.argv[1]?.includes('import-reviews')) {
main().catch((err) => {
console.error(err) console.error(err)
process.exit(1) process.exit(1)
}) })
}
+2 -1
View File
@@ -2,6 +2,7 @@ import 'dotenv/config'
import { getPayload } from 'payload' import { getPayload } from 'payload'
import { paidDoctors } from './paid-doctors' import { paidDoctors } from './paid-doctors'
import { seedPromotions } from './import-promotions' import { seedPromotions } from './import-promotions'
import { seedReviews } from './import-reviews'
import config from '../payload.config' import config from '../payload.config'
const translations = { const translations = {
@@ -359,7 +360,7 @@ async function seed() {
const existingReviews = await payload.find({ collection: 'reviews', limit: 1 }) const existingReviews = await payload.find({ collection: 'reviews', limit: 1 })
if (!existingReviews.docs.length) { if (!existingReviews.docs.length) {
console.log('Doctor reviews skipped') await seedReviews(payload)
} }
await seedPromotions(payload) await seedPromotions(payload)
+1
View File
@@ -700,6 +700,7 @@ export async function getReviewsPage(params?: {
page: String(page), page: String(page),
depth: "1", depth: "1",
sort: "-createdAt", sort: "-createdAt",
"where[isPublished][equals]": "true",
}), }),
params?.locale, params?.locale,
); );