Skip to content

Commit d923c36

Browse files
chore: create Node.js data seeding script
1 parent c4841dc commit d923c36

File tree

4 files changed

+149
-11
lines changed

4 files changed

+149
-11
lines changed

cypress/plugins/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ import volunteer from 'cypress/fixtures/users/volunteer.json';
3131
// Follow the Next.js convention for loading `.env` files.
3232
// @see {@link https://nextjs.org/docs/basic-features/environment-variables}
3333
[
34-
path.resolve(__dirname, '../../.env'),
35-
path.resolve(__dirname, `../../.env.${process.env.NODE_ENV || 'test'}`),
36-
path.resolve(__dirname, '../../.env.local'),
3734
path.resolve(__dirname, `../../.env.${process.env.NODE_ENV || 'test'}.local`),
35+
path.resolve(__dirname, '../../.env.local'),
36+
path.resolve(__dirname, `../../.env.${process.env.NODE_ENV || 'test'}`),
37+
path.resolve(__dirname, '../../.env'),
3838
].forEach((dotfile: string) => dotenv.config({ path: dotfile }));
3939

4040
const clientCredentials = {

cypress/tests/seed.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"dev:next": "next dev -p 3000",
1515
"dev:cy": "cross-env NODE_ENV=development PERCY_BRANCH=local percy exec -- cypress open",
1616
"dev": "cross-env NODE_ENV=development concurrently yarn:dev:db yarn:dev:next yarn:dev:cy",
17-
"seed": "cross-env NODE_ENV=development NO_COLOR=1 cypress run --spec cypress/tests/seed.ts",
17+
"seed": "node scripts/seed.js",
1818
"build:sw": "cd sw && webpack",
1919
"build:next": "next build",
2020
"build:analyze": "cross-env ANALYZE=true next build",

scripts/seed.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
const path = require('path');
2+
3+
const algoliasearch = require('algoliasearch');
4+
const axios = require('axios');
5+
const dotenv = require('dotenv');
6+
const firebaseAdminLib = require('firebase-admin');
7+
const firebaseClient = require('firebase/app');
8+
require('firebase/auth');
9+
10+
const admin = require('../cypress/fixtures/users/admin.json');
11+
const match = require('../cypress/fixtures/match.json');
12+
const meeting = require('../cypress/fixtures/meeting.json');
13+
const org = require('../cypress/fixtures/orgs/default.json');
14+
const school = require('../cypress/fixtures/orgs/school.json');
15+
const student = require('../cypress/fixtures/users/student.json');
16+
const volunteer = require('../cypress/fixtures/users/volunteer.json');
17+
18+
// Follow the Next.js convention for loading `.env` files.
19+
// @see {@link https://nextjs.org/docs/basic-features/environment-variables}
20+
const env = process.env.NODE_ENV || 'development';
21+
[
22+
path.resolve(__dirname, `../.env.${env}.local`),
23+
path.resolve(__dirname, '../.env.local'),
24+
path.resolve(__dirname, `../.env.${env}`),
25+
path.resolve(__dirname, '../.env'),
26+
].forEach((dotfile) => {
27+
console.log(`Loaded env from ${dotfile}`);
28+
dotenv.config({ path: dotfile });
29+
});
30+
31+
const credentials = {
32+
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
33+
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
34+
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
35+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
36+
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
37+
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
38+
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
39+
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
40+
};
41+
if (!firebaseClient.apps.length) firebaseClient.initializeApp(credentials);
42+
const clientAuth = firebaseClient.auth();
43+
44+
const firebaseAdmin = firebaseAdminLib.initializeApp({
45+
credential: firebaseAdminLib.credential.cert({
46+
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
47+
privateKey: (process.env.FIREBASE_ADMIN_KEY || '').replace(/\\n/g, '\n'),
48+
clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
49+
}),
50+
serviceAccountId: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
51+
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
52+
databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
53+
databaseAuthVariableOverride: { uid: 'server' },
54+
});
55+
const adminAuth = firebaseAdmin.auth();
56+
57+
async function getHeaders(uid) {
58+
const token = await adminAuth.createCustomToken(uid);
59+
await clientAuth.signInWithCustomToken(token);
60+
const jwt = await clientAuth.currentUser.getIdToken(true);
61+
await clientAuth.signOut();
62+
return { authorization: `Bearer ${jwt || ''}` };
63+
}
64+
65+
const algoliaId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID;
66+
const algoliaKey = process.env.ALGOLIA_ADMIN_KEY;
67+
const search = algoliasearch(algoliaId, algoliaKey);
68+
69+
const usersIdx = search.initIndex(`${env}-users`);
70+
const matchesIdx = search.initIndex(`${env}-matches`);
71+
const meetingsIdx = search.initIndex(`${env}-meetings`);
72+
73+
async function clear() {
74+
console.log('Clearing data...');
75+
const clearFirestoreEndpoint =
76+
`http://${process.env.FIRESTORE_EMULATOR_HOST}/emulator/v1/projects/` +
77+
`${process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID}/databases/(default)/` +
78+
`documents`;
79+
await Promise.all([
80+
usersIdx.clearObjects(),
81+
matchesIdx.clearObjects(),
82+
meetingsIdx.clearObjects(),
83+
axios.delete(clearFirestoreEndpoint),
84+
]);
85+
}
86+
87+
async function seed(overrides = {}) {
88+
let matches = [];
89+
matches.push({ ...match, ...overrides.match });
90+
if (overrides.match === null) delete matches[0];
91+
matches = matches.filter(Boolean);
92+
93+
let orgs = [];
94+
orgs.push({ ...org, ...overrides.org });
95+
orgs.push({ ...school, ...overrides.school });
96+
if (overrides.org === null) delete orgs[0];
97+
if (overrides.school === null) delete orgs[1];
98+
orgs = orgs.filter(Boolean);
99+
100+
let users = [];
101+
users.push({ ...volunteer, ...overrides.volunteer });
102+
users.push({ ...student, ...overrides.student });
103+
users.push({ ...admin, ...overrides.admin });
104+
if (overrides.volunteer === null) delete users[0];
105+
if (overrides.student === null) delete users[1];
106+
if (overrides.admin === null) delete users[2];
107+
users = users.filter(Boolean);
108+
109+
let meetings = [];
110+
meetings.push({ ...meeting, ...overrides.meeting });
111+
if (overrides.meeting === null) delete meetings[0];
112+
meetings = meetings.filter(Boolean);
113+
114+
const rconfig = { headers: await getHeaders(admin.id) };
115+
116+
async function create(route, data) {
117+
console.log(`Creating ${data.length} ${route}...`);
118+
const endpoint = `http://localhost:3000/api/${route}`;
119+
await Promise.all(data.map((d) => axios.post(endpoint, d, rconfig)));
120+
}
121+
122+
await create('orgs', orgs);
123+
124+
// We have to create the admin first because TB's back-end will try to
125+
// fetch his data when sending user creation notification emails.
126+
await create(
127+
'users',
128+
users.filter((u) => u.id === 'admin')
129+
);
130+
await create(
131+
'users',
132+
users.filter((u) => u.id !== 'admin')
133+
);
134+
135+
await create('matches', matches);
136+
137+
await create('meetings', meetings);
138+
}
139+
140+
async function main(overrides) {
141+
await clear();
142+
await seed(overrides);
143+
}
144+
145+
if (require.main === module) main();

0 commit comments

Comments
 (0)