meteor3: re-enable fixtures and sitemaps with async/Meteor-3 implementations

fixtures: drop @cleverbeagle/seeder (sync Mongo, unmaintained) for a small
idempotent async seeder (same dev accounts). sitemaps: drop dead
gadicohen:sitemaps for a WebApp.connectHandlers /sitemap.xml with the same
static page list (the per-fire section was already disabled). Verified on
dev boot: 6 users ensured, sitemap serving; REST smoke byte-identical.
This commit is contained in:
vjrj 2026-07-14 07:06:07 +02:00
parent 66bab7a43b
commit 0ff9848cd4
8 changed files with 20493 additions and 15709 deletions

View file

@ -1,54 +1,71 @@
import seeder from '@cleverbeagle/seeder';
/* eslint-disable no-await-in-loop */
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
import Documents from '../../api/Documents/Documents';
const documentsSeed = userId => ({
collection: Documents,
environments: ['development', 'staging'],
noLimit: true,
modelCount: 5,
model(dataIndex) {
return {
owner: userId,
title: `Document #${dataIndex + 1}`,
body: `This is the body of document #${dataIndex + 1}`,
};
},
});
/*
* Dev/staging fixtures. Replaces @cleverbeagle/seeder (sync Mongo, no Meteor 3
* support) with a small async seeder: idempotent (find-before-create), only
* runs outside production.
*/
seeder(Meteor.users, {
environments: ['development', 'staging'],
noLimit: true,
data: [{
const USERS = [
{
email: 'admin@admin.com',
password: 'password',
profile: {
name: {
first: 'Andy',
last: 'Warhol',
},
},
roles: ['admin'],
data(userId) {
return documentsSeed(userId);
},
}],
modelCount: 5,
model(index, faker) {
const userCount = index + 1;
return {
email: `user+${userCount}@test.com`,
password: 'password',
profile: {
name: {
first: faker.name.firstName(),
last: faker.name.lastName(),
},
},
roles: ['user'],
data(userId) {
return documentsSeed(userId);
},
};
profile: { name: { first: 'Andy', last: 'Warhol' } },
roles: ['admin']
},
...[
['Frida', 'Kahlo'],
['Joaquín', 'Sorolla'],
['Maruja', 'Mallo'],
['Remedios', 'Varo'],
['Pablo', 'Picasso']
].map(([first, last], i) => ({
email: `user+${i + 1}@test.com`,
password: 'password',
profile: { name: { first, last } },
roles: ['user']
}))
];
const DOCUMENTS_PER_USER = 5;
async function ensureUser({ email, password, profile, roles }) {
const existing = await Meteor.users.findOneAsync({ 'emails.address': email });
const userId = existing
? existing._id
: await Accounts.createUserAsync({ email, password, profile });
if (!existing && roles) {
await Meteor.users.updateAsync(userId, { $set: { roles } });
}
return userId;
}
async function ensureDocuments(userId) {
const count = await Documents.find({ owner: userId }).countAsync();
if (count > 0) return;
for (let i = 0; i < DOCUMENTS_PER_USER; i += 1) {
await Documents.insertAsync({
owner: userId,
title: `Document #${i + 1}`,
body: `This is the body of document #${i + 1}`
});
}
}
Meteor.startup(async () => {
// dev always; staging opts in with TCEF_SEED=1 (the old seeder's
// 'staging' environment had no reliable detection either).
if (!Meteor.isDevelopment && process.env.TCEF_SEED !== '1') return;
try {
for (const user of USERS) {
const userId = await ensureUser(user);
await ensureDocuments(userId);
}
console.log(`fixtures: ${USERS.length} dev users ensured`);
} catch (e) {
console.warn(`fixtures: seeding failed: ${e}`);
}
});

View file

@ -3,19 +3,13 @@ import './catchExceptions';
import './i18n';
import './accounts';
import './api';
// Meteor 3 debt: '@cleverbeagle/seeder' (dev/staging fixtures) uses sync Mongo
// (findOne) and has no Meteor 3 async support. Disabled — not needed for the
// REST smoke test. Re-implement dev fixtures with *Async if desired.
// import './fixtures';
import './fixtures';
import './email';
import '/imports/api/Comments/server';
import './IPGeocoder';
import './migrations';
import './facts';
// Meteor 3 debt: gadicohen:sitemaps@0.0.17 uses the pre-0.9 package API and no
// longer exports the `sitemaps` global on Meteor 3. Disabled (SEO sitemap, not
// on the REST path). Replace with a maintained sitemap approach if needed.
// import './sitemaps';
import './sitemaps';
import './subsUnion';
import './prerender';
import './feedback';

View file

@ -1,44 +1,37 @@
/* global sitemaps */
/* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import Fires from '/imports/api/Fires/Fires';
import Comments from '/imports/api/Comments/Comments';
/*
* /sitemap.xml own implementation replacing gadicohen:sitemaps (pre-0.9
* package API, dead on Meteor 3). Static pages only: the per-fire section of
* the old handler was behind `firesMapEnabled = false` (dead code) and was
* dropped; see git history if it's ever wanted back.
*/
const firesMapEnabled = false;
const PAGES = [
'/fires',
'/login',
'/signup',
'/recover-password',
'/credits',
'/terms',
'/license',
'/privacy',
'/about'
];
sitemaps.add('/sitemap.xml', () => {
const today = new Date();
function xmlEscape(s) {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
const out = [];
out.push({ page: '/fires', lastmod: today });
out.push({ page: '/login', lastmod: today });
out.push({ page: '/signup', lastmod: today });
out.push({ page: '/recover-password', lastmod: today });
out.push({ page: '/credits', lastmod: today });
out.push({ page: '/terms', lastmod: today });
out.push({ page: '/license', lastmod: today });
out.push({ page: '/privacy', lastmod: today });
out.push({ page: '/about', lastmod: today });
// When user has public page
/* const users = Meteor.users.find().fetch();
_.each(users, function(user) {
out.push({
page: '/persona/' + user.username,
lastmod: user.updatedAt
});
}); */
if (firesMapEnabled) {
Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => {
// Search the last comment of tha fire
const lastComment = Comments.findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } });
out.push({
page: `/fire/archive/${fire._id._str}`,
lastmod: lastComment ? lastComment.createdAt : fire.updatedAt
});
});
}
return out;
WebApp.connectHandlers.use('/sitemap.xml', (req, res) => {
const lastmod = new Date().toISOString().slice(0, 10);
const urls = PAGES.map((page) => {
const loc = xmlEscape(Meteor.absoluteUrl(page.replace(/^\//, '')));
return ` <url>\n <loc>${loc}</loc>\n <lastmod>${lastmod}</lastmod>\n </url>`;
}).join('\n');
const body = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls}\n</urlset>\n`;
res.writeHead(200, { 'Content-Type': 'application/xml; charset=utf-8' });
res.end(body);
});