-
Notifications
You must be signed in to change notification settings - Fork 3
Seeder and Emulator Setup #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Smeet-Shah
wants to merge
10
commits into
main
Choose a base branch
from
F25/smeet/seeder_setup
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
9a170ec
Added emulator for scripting
Smeet-Shah 3ddbf6e
Linting fixes
Smeet-Shah 2e9b667
Unique migration and documentation
Smeet-Shah f8e0b07
Minor change to git ignore
Smeet-Shah 6cde37d
Removing package-lock
Smeet-Shah 314708b
Editing git ignore
Smeet-Shah 0900dd5
Removed tasks.json
Smeet-Shah 9cb29bf
Fixed mocks
Smeet-Shah 2b85cee
Merge branch 'main' into F25/smeet/seeder_setup
zyrephus b09876f
Remove hardcoded pet names from seeder undo function
zyrephus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,5 @@ | |
| .vscode | ||
| **/*.cache | ||
| **/*.egg-info | ||
| **/package-lock.json | ||
| **/seed-auth-map.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* eslint-disable */ | ||
|
|
||
| /* | ||
| Sequelize CLI config | ||
| - We're using this to tell sequelize which DB to talk to for each environment (dev/test/prod) | ||
| - Values mostly come from the docker .env so it works inside the same containers | ||
| */ | ||
|
|
||
| require("dotenv").config(); | ||
|
|
||
| module.exports = { | ||
| // Local | ||
| development: { | ||
| username: process.env.POSTGRES_USER || "postgres", | ||
| password: process.env.POSTGRES_PASSWORD || "password", | ||
| database: process.env.POSTGRES_DB_NAME || "humane_society_dev", | ||
| host: process.env.DB_HOST || "db", | ||
| port: process.env.DB_PORT || 5432, | ||
| dialect: "postgres", | ||
| logging: false, | ||
| }, | ||
| // CI / local tests | ||
| test: { | ||
| username: process.env.POSTGRES_USER || "postgres", | ||
| password: process.env.POSTGRES_PASSWORD || "password", | ||
| database: process.env.POSTGRES_DB_TEST || "humane_society_test", | ||
| host: process.env.DB_HOST || "db", | ||
| port: process.env.DB_PORT || 5432, | ||
| dialect: "postgres", | ||
| logging: false, | ||
| }, | ||
| // Prod | ||
| production: { | ||
| use_env_variable: "DATABASE_URL", | ||
| dialect: "postgres", | ||
| logging: false, | ||
| }, | ||
| }; |
33 changes: 33 additions & 0 deletions
33
backend/typescript/migrations/2025.10.27T03.30.00.add-unique-constraints-users.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /* eslint-disable */ | ||
| import type { QueryInterface, Sequelize } from "sequelize"; | ||
|
|
||
| const TABLE = "users"; | ||
| const UQ_EMAIL = "users_email_unique"; | ||
| const UQ_AUTH_ID = "users_auth_id_unique"; | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface: QueryInterface, _sequelize: Sequelize) => { | ||
| await queryInterface.sequelize.transaction(async (t) => { | ||
| // adding unique constraints | ||
| await queryInterface.addConstraint(TABLE, { | ||
| fields: ["email"], | ||
| type: "unique", | ||
| name: UQ_EMAIL, | ||
| transaction: t, | ||
| }); | ||
| await queryInterface.addConstraint(TABLE, { | ||
| fields: ["auth_id"], | ||
| type: "unique", | ||
| name: UQ_AUTH_ID, | ||
| transaction: t, | ||
| }); | ||
| }); | ||
| }, | ||
|
|
||
| down: async (queryInterface: QueryInterface, _sequelize: Sequelize) => { | ||
| await queryInterface.sequelize.transaction(async (t) => { | ||
| await queryInterface.removeConstraint(TABLE, UQ_AUTH_ID, { transaction: t }); | ||
| await queryInterface.removeConstraint(TABLE, UQ_EMAIL, { transaction: t }); | ||
| }); | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document code blocks for clarity |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| /* eslint-disable */ | ||
| import * as fs from "fs"; | ||
| import * as path from "path"; | ||
| import admin from "firebase-admin"; | ||
| import "dotenv/config"; | ||
|
|
||
| if (!process.env.FIREBASE_AUTH_EMULATOR_HOST) { | ||
| console.error( | ||
| "Set FIREBASE_AUTH_EMULATOR_HOST (e.g., host.docker.internal:9099).", | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| if (!admin.apps.length) { | ||
| admin.initializeApp({ | ||
| projectId: process.env.FIREBASE_PROJECT_ID || "uw-blueprint-starter-code", | ||
| }); | ||
| } | ||
|
|
||
| const auth = admin.auth(); | ||
|
|
||
| const USERS: Array<{ label: string; email: string; password: string; displayName: string }> = require( | ||
| path.resolve(__dirname, "../seeders/mockData/auth.json") | ||
| ); | ||
|
|
||
| (async () => { | ||
| // label -> uid map | ||
| const map: Record<string, string> = {}; | ||
| for (const u of USERS) { | ||
| try { | ||
| // reuse user if in emulator already | ||
| const ex = await auth.getUserByEmail(u.email); | ||
| map[u.label] = ex.uid; | ||
| } catch { | ||
| const created = await auth.createUser({ | ||
| email: u.email, | ||
| password: u.password, | ||
| displayName: u.displayName, | ||
| emailVerified: true, | ||
| }); | ||
| map[u.label] = created.uid; | ||
| } | ||
| } | ||
|
|
||
| // Seeder files will consume this to map something like "admin_001" -> actual UID | ||
| const outPath = path.resolve(__dirname, "../seeders/seed-auth-map.json"); | ||
| fs.writeFileSync(outPath, JSON.stringify(map, null, 2)); | ||
| console.log("Wrote UID map:", outPath, map); | ||
| process.exit(0); | ||
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* eslint-disable */ | ||
| import type { QueryInterface } from "sequelize"; | ||
| import { resolveTable, tsKeys, withTS, Rec } from "../utilities/_utils"; | ||
|
|
||
| // Pull real UIDs created by `yarn seed:auth` | ||
| let uidMap: Record<string, string>; | ||
| try { | ||
| uidMap = require("./seed-auth-map.json"); | ||
| } catch { | ||
| throw new Error( | ||
| "seed-auth-map.json not found. Run `docker compose exec ts-backend yarn seed:auth` first.", | ||
| ); | ||
| } | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface: QueryInterface) => { | ||
| const Users = await resolveTable(queryInterface, ["Users", "users"]); | ||
| const uTS = await tsKeys(queryInterface, Users); | ||
|
|
||
| // Load user fixtures | ||
| const FIXTURES: Array<{ | ||
| label: string; | ||
| first_name: string; | ||
| last_name: string; | ||
| role: string; | ||
| email: string; | ||
| color_level: number; | ||
| animal_tags: string; | ||
| status: string; | ||
| can_see_all_logs: boolean; | ||
| can_assign_users_to_tasks: boolean; | ||
| phone_number: string; | ||
| }> = require("./mockData/users.json"); | ||
|
|
||
| const users: Rec[] = FIXTURES.map((f) => ({ | ||
| ...f, | ||
| auth_id: uidMap[f.label], | ||
| })) | ||
| .map((r) => { | ||
| const { label, ...rest } = r; | ||
| return rest; | ||
| }) | ||
| .map((r) => withTS(r, uTS.createdKey, uTS.updatedKey)); | ||
|
|
||
| await queryInterface.bulkInsert(Users, users); | ||
| }, | ||
|
|
||
| down: async (queryInterface: QueryInterface) => { | ||
| const Users = await resolveTable(queryInterface, ["Users", "users"]); | ||
| const FIXTURES: Array<{ label: string }> = require("./mockData/users.json"); | ||
| await queryInterface.bulkDelete(Users, { | ||
| auth_id: FIXTURES.map((f) => uidMap[f.label]), | ||
| } as any); | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| /* eslint-disable */ | ||
| import type { QueryInterface } from "sequelize"; | ||
| import { resolveTable, tsKeys, withTS, Rec } from "../utilities/_utils"; | ||
|
|
||
| const PET_FIXTURES: Rec[] = require("./mockData/pets.json") | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface: QueryInterface) => { | ||
| const Pets = await resolveTable(queryInterface, ["Pets", "pets"]); | ||
| const pTS = await tsKeys(queryInterface, Pets); | ||
|
|
||
| const pets: Rec[] = PET_FIXTURES.map((r) => withTS(r, pTS.createdKey, pTS.updatedKey)); | ||
| await queryInterface.bulkInsert(Pets, pets); | ||
| }, | ||
|
|
||
| down: async (queryInterface: QueryInterface) => { | ||
| const Pets = await resolveTable(queryInterface, ["Pets", "pets"]); | ||
| await queryInterface.bulkDelete(Pets, {}, {}); | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| /* eslint-disable */ | ||
| import type { QueryInterface } from "sequelize"; | ||
| import { resolveTable, tsKeys, withTS, Rec } from "../utilities/_utils"; | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface: QueryInterface) => { | ||
| const Templates = await resolveTable(queryInterface, [ | ||
| "task_templates", | ||
| "TaskTemplates", | ||
| "Task_Templates", | ||
| "taskTemplates", | ||
| ]); | ||
| const ttTS = await tsKeys(queryInterface, Templates); | ||
|
|
||
| // Load template fixtures | ||
| const FIXTURES: Array<{ | ||
| task_name: string; | ||
| category: string; | ||
| instruction: string; | ||
| }> = require("./mockData/templates.json"); | ||
|
|
||
| const templates: Rec[] = FIXTURES.map((r) => | ||
| withTS(r, ttTS.createdKey, ttTS.updatedKey) | ||
| ); | ||
|
|
||
| await queryInterface.bulkInsert(Templates, templates); | ||
| }, | ||
|
|
||
| down: async (queryInterface: QueryInterface) => { | ||
| const Templates = await resolveTable(queryInterface, [ | ||
| "task_templates", | ||
| "TaskTemplates", | ||
| "Task_Templates", | ||
| "taskTemplates", | ||
| ]); | ||
| const FIXTURES: Array<{ task_name: string }> = require("./mockData/templates.json"); | ||
| await queryInterface.bulkDelete(Templates, { | ||
| task_name: FIXTURES.map((f) => f.task_name), | ||
| } as any); | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| /* eslint-disable */ | ||
| import type { QueryInterface } from "sequelize"; | ||
| import { resolveTable, tsKeys, withTS, Rec } from "../utilities/_utils"; | ||
|
|
||
| let uidMap: Record<string, string>; | ||
| try { | ||
| uidMap = require("./seed-auth-map.json"); | ||
| } catch { | ||
| throw new Error( | ||
| "seed-auth-map.json not found. Run `docker compose exec ts-backend yarn seed:auth` first.", | ||
| ); | ||
| } | ||
|
|
||
| module.exports = { | ||
| up: async (queryInterface: QueryInterface) => { | ||
| const Users = await resolveTable(queryInterface, ["Users", "users"]); | ||
| const Pets = await resolveTable(queryInterface, ["Pets", "pets"]); | ||
| const Templates = await resolveTable(queryInterface, [ | ||
| "task_templates", | ||
| "TaskTemplates", | ||
| "Task_Templates", | ||
| "taskTemplates", | ||
| ]); | ||
| const Tasks = await resolveTable(queryInterface, ["tasks", "Tasks"]); | ||
| const tkTS = await tsKeys(queryInterface, Tasks); | ||
|
|
||
| // Load task fixtures | ||
| const FIXTURES: Array<{ | ||
| userLabel: string | null; | ||
| petName: string; | ||
| templateName: string; | ||
| offsetHours?: number; | ||
| startNow?: boolean; | ||
| notes: string; | ||
| }> = require("./mockData/tasks.json"); | ||
|
|
||
| // Look up FK IDs dynamically (no hardcoded numbers) | ||
| const userAuthIds: string[] = Array.from( | ||
| new Set( | ||
| FIXTURES | ||
| .map((f) => f.userLabel) | ||
| .filter((x): x is string => Boolean(x)) | ||
| .map((label) => uidMap[label]) | ||
| ) | ||
| ); | ||
| const petNames: string[] = Array.from( | ||
| new Set(FIXTURES.map((f) => f.petName)) | ||
| ); | ||
|
|
||
| const [userRows]: any = await queryInterface.sequelize.query( | ||
| `SELECT id, auth_id FROM "${Users}" WHERE auth_id IN (:ids)`, | ||
| { replacements: { ids: userAuthIds } }, | ||
| ); | ||
| const [petRows]: any = await queryInterface.sequelize.query( | ||
| `SELECT id, name FROM "${Pets}" WHERE name IN (:names)`, | ||
| { replacements: { names: petNames } }, | ||
| ); | ||
| const [tmplRows]: any = await queryInterface.sequelize.query( | ||
| `SELECT id, task_name FROM "${Templates}"`, | ||
| ); | ||
|
|
||
| const idOf = (arr: any[], key: string, val: string) => | ||
| (arr.find((x) => x[key] === val) || {}).id ?? null; | ||
|
|
||
| const tasks: Rec[] = FIXTURES.map((f) => { | ||
| const scheduled = new Date( | ||
| Date.now() + ((f.offsetHours ?? 0) * 60 * 60 * 1000) | ||
| ); | ||
| const base: any = { | ||
| user_id: f.userLabel ? idOf(userRows, "auth_id", uidMap[f.userLabel]) : null, | ||
| pet_id: idOf(petRows, "name", f.petName), | ||
| task_template_id: idOf(tmplRows, "task_name", f.templateName), | ||
| scheduled_start_time: scheduled, | ||
| notes: f.notes, | ||
| }; | ||
| if (f.startNow) base.start_time = new Date(); | ||
| return base; | ||
| }).map((r) => withTS(r, tkTS.createdKey, tkTS.updatedKey)); | ||
|
|
||
| await queryInterface.bulkInsert(Tasks, tasks); | ||
| }, | ||
|
|
||
| down: async (queryInterface: QueryInterface) => { | ||
| const Tasks = await resolveTable(queryInterface, ["tasks", "Tasks"]); | ||
| await queryInterface.sequelize.query( | ||
| `DELETE FROM "${Tasks}" WHERE notes LIKE 'seed_%'`, | ||
| ); | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [ | ||
| { "label": "admin_001", "email": "john.smith@humanesociety.com", "password": "Passw0rd!", "displayName": "John Smith" }, | ||
| { "label": "admin_002", "email": "sarah.johnson@humanesociety.com", "password": "Passw0rd!", "displayName": "Sarah Johnson" }, | ||
| { "label": "behaviourist_001", "email": "emily.wilson@humanesociety.com", "password": "Passw0rd!", "displayName": "Emily Wilson" }, | ||
| { "label": "behaviourist_002", "email": "michael.brown@humanesociety.com", "password": "Passw0rd!", "displayName": "Michael Brown" }, | ||
| { "label": "staff_001", "email": "lisa.davis@humanesociety.com", "password": "Passw0rd!", "displayName": "Lisa Davis" }, | ||
| { "label": "staff_002", "email": "robert.miller@humanesociety.com", "password": "Passw0rd!", "displayName": "Robert Miller" }, | ||
| { "label": "volunteer_001", "email": "amanda.garcia@volunteer.com", "password": "Passw0rd!", "displayName": "Amanda Garcia" }, | ||
| { "label": "volunteer_002", "email": "kevin.martinez@volunteer.com", "password": "Passw0rd!", "displayName": "Kevin Martinez" } | ||
| ] | ||
|
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.