@churchsoln/dbms 1.0.61 → 1.0.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config/config.js CHANGED
@@ -1,4 +1,10 @@
1
1
  require("dotenv").config();
2
+
3
+ const seedTracking = {
4
+ seederStorage: "sequelize",
5
+ seederStorageTableName: "SequelizeData",
6
+ };
7
+
2
8
  module.exports = {
3
9
  localdev: {
4
10
  user: "root",
@@ -9,6 +15,7 @@ module.exports = {
9
15
  port: "5432",
10
16
  dialect: "postgres",
11
17
  url: "postgres://root:root@localhost:5432/dbms",
18
+ ...seedTracking,
12
19
  },
13
20
  development: {
14
21
  user: process.env.POSTGRES_USER,
@@ -19,6 +26,7 @@ module.exports = {
19
26
  port: process.env.POSTGRES_PORT,
20
27
  dialect: "postgres",
21
28
  url: `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DB}`,
29
+ ...seedTracking,
22
30
  },
23
31
  test: {
24
32
  user: process.env.POSTGRES_USER,
@@ -29,5 +37,6 @@ module.exports = {
29
37
  port: process.env.POSTGRES_PORT,
30
38
  dialect: "postgres",
31
39
  url: `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DB}`,
40
+ ...seedTracking,
32
41
  },
33
42
  };
@@ -81,6 +81,7 @@ const JOURNAL_ENTRY_SOURCE_TYPE = {
81
81
 
82
82
  const JOURNAL_ENTRY_STATUS = {
83
83
  POSTED: "posted",
84
+ DRAFT: "draft",
84
85
  };
85
86
 
86
87
  const ACCOUNTING_ACCOUNT_TYPES = {
package/db-setup.js CHANGED
@@ -2,6 +2,7 @@ const { exec } = require("child_process");
2
2
  const { Sequelize } = require("sequelize");
3
3
  const env = process.env.NODE_ENV || "development";
4
4
  const config = require("./config/config")[env];
5
+ const { backfillSeedTracking } = require("./utils/seedTracking");
5
6
  const rootSequelize = new Sequelize("postgres", config.username, "root", {
6
7
  host: config.host,
7
8
  dialect: config.dialect,
@@ -78,6 +79,10 @@ function runSeed() {
78
79
  try {
79
80
  await ensureDatabaseExists();
80
81
  await runMigrations();
82
+ await backfillSeedTracking({
83
+ connectionUrl: config.url,
84
+ kind: "common",
85
+ });
81
86
  await runSeed();
82
87
  console.log("Database setup and migrations complete.");
83
88
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@churchsoln/dbms",
3
- "version": "1.0.61",
3
+ "version": "1.0.62",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "prestart": "node db-setup.js",
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Seed tracking (migration-style via SequelizeData) for common and church DBs:
5
+ * - Fresh DB: no backfill → db:seed:all runs all seeders once → recorded in SequelizeData
6
+ * - Re-seed: skip applied files; only new seed files run
7
+ * - Tenant/common-deleted seeded rows stay deleted (old insert seeds never re-run)
8
+ * - Global data changes: add a new seed file; do not edit old seed files
9
+ */
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const Sequelize = require("sequelize");
14
+ const { ONBOARD_STATUS } = require("../constants");
15
+
16
+ const SEQUELIZE_DATA_TABLE = "SequelizeData";
17
+ const SEEDERS_ROOT = path.join(__dirname, "..", "seeders");
18
+
19
+ const listSeederFilenames = (seedersPath) =>
20
+ fs
21
+ .readdirSync(seedersPath)
22
+ .filter((file) => file.endsWith(".js") && !file.startsWith("."));
23
+
24
+ const resolveSeedersPath = (kind) => path.join(SEEDERS_ROOT, kind === "common" ? "common" : "church");
25
+
26
+ /**
27
+ * Ensure SequelizeData exists. When the DB was already seeded but has no tracking
28
+ * rows, mark all current seeder files as applied so the next db:seed:all only
29
+ * runs newly added seed files.
30
+ *
31
+ * @param {object} opts
32
+ * @param {string} opts.connectionUrl
33
+ * @param {"common"|"church"} opts.kind
34
+ * @param {string} [opts.seedStatus] church seedStatus (used when kind=church)
35
+ */
36
+ const backfillSeedTracking = async ({ connectionUrl, kind, seedStatus }) => {
37
+ if (!connectionUrl) return;
38
+
39
+ const sequelize = new Sequelize(connectionUrl, {
40
+ dialectOptions: { multipleStatements: true },
41
+ logging: false,
42
+ });
43
+
44
+ try {
45
+ await sequelize.authenticate();
46
+ await sequelize.query(`
47
+ CREATE TABLE IF NOT EXISTS "${SEQUELIZE_DATA_TABLE}" (
48
+ "name" VARCHAR(255) NOT NULL PRIMARY KEY
49
+ );
50
+ `);
51
+
52
+ const [rows] = await sequelize.query(
53
+ `SELECT COUNT(*)::int AS count FROM "${SEQUELIZE_DATA_TABLE}"`
54
+ );
55
+ const count = rows?.[0]?.count ?? 0;
56
+ if (count > 0) return;
57
+
58
+ let shouldBackfill = false;
59
+ if (kind === "church") {
60
+ shouldBackfill = seedStatus === ONBOARD_STATUS.SEED_COMPLETED;
61
+ } else {
62
+ // Common has no seedStatus: backfill only if Role data already exists
63
+ try {
64
+ const [roleRows] = await sequelize.query(
65
+ `SELECT COUNT(*)::int AS count FROM "Role"`
66
+ );
67
+ shouldBackfill = (roleRows?.[0]?.count ?? 0) > 0;
68
+ } catch {
69
+ shouldBackfill = false;
70
+ }
71
+ }
72
+ if (!shouldBackfill) return;
73
+
74
+ const seedersPath = resolveSeedersPath(kind);
75
+ const filenames = listSeederFilenames(seedersPath);
76
+ if (!filenames.length) return;
77
+
78
+ const values = filenames.map((name) => `('${name.replace(/'/g, "''")}')`).join(", ");
79
+ await sequelize.query(
80
+ `INSERT INTO "${SEQUELIZE_DATA_TABLE}" ("name") VALUES ${values} ON CONFLICT ("name") DO NOTHING`
81
+ );
82
+ console.log(
83
+ `Backfilled ${filenames.length} ${kind} seed file(s) into ${SEQUELIZE_DATA_TABLE}`
84
+ );
85
+ } finally {
86
+ await sequelize.close();
87
+ }
88
+ };
89
+
90
+ module.exports = {
91
+ backfillSeedTracking,
92
+ listSeederFilenames,
93
+ resolveSeedersPath,
94
+ SEQUELIZE_DATA_TABLE,
95
+ };
package/workers/seed.js CHANGED
@@ -3,53 +3,73 @@ const redisConfig = require("../redis/config");
3
3
  const commonDb = require("../models/common");
4
4
  const { exec } = require("child_process");
5
5
  const { WORKERS, ONBOARD_STATUS } = require("../constants");
6
+ const { backfillSeedTracking } = require("../utils/seedTracking");
6
7
 
7
8
  const seedDb = async (seedData) => {
8
9
  return new Promise(async (resolve, reject) => {
9
- const { id, connectionUrl, name, isCommonDb = false } = seedData;
10
+ const { id, connectionUrl, name, isCommonDb = false, seedStatus } = seedData;
11
+ const kind = isCommonDb ? "common" : "church";
10
12
  console.log(
11
- `Applying seed for ${isCommonDb ? "common" : "church"} db: ${!isCommonDb ? name : ""}`
13
+ `Applying seed for ${kind} db: ${!isCommonDb ? name : ""}`
12
14
  );
13
- if (!isCommonDb) {
14
- await commonDb.church.update(
15
- { seedStatus: ONBOARD_STATUS.SEED_IN_PROGRESS },
16
- { where: { id } }
17
- );
18
- }
19
- exec(
20
- `DEBUG=sequelize:* npx sequelize-cli db:seed:all --url ${connectionUrl} --seeders-path ${
21
- isCommonDb ? "./seeders/common" : "./seeders/church"
22
- }`,
23
- async (error, stdout, stderr) => {
24
- if (error) {
25
- console.error(
26
- `Error applying seed for ${isCommonDb ? "common" : "church"} db: ${
15
+ try {
16
+ // Use original church seedStatus / common Role check before running
17
+ // so already-seeded DBs get SequelizeData backfilled and only new seeds run.
18
+ await backfillSeedTracking({
19
+ connectionUrl,
20
+ kind,
21
+ seedStatus,
22
+ });
23
+ if (!isCommonDb) {
24
+ await commonDb.church.update(
25
+ { seedStatus: ONBOARD_STATUS.SEED_IN_PROGRESS },
26
+ { where: { id } }
27
+ );
28
+ }
29
+ exec(
30
+ `DEBUG=sequelize:* npx sequelize-cli db:seed:all --url ${connectionUrl} --seeders-path ${
31
+ isCommonDb ? "./seeders/common" : "./seeders/church"
32
+ }`,
33
+ async (error, stdout, stderr) => {
34
+ if (error) {
35
+ console.error(
36
+ `Error applying seed for ${kind} db: ${
37
+ !isCommonDb ? name : ""
38
+ }`,
39
+ error.message
40
+ );
41
+ if (!isCommonDb) {
42
+ await commonDb.church.update(
43
+ { seedStatus: ONBOARD_STATUS.SEED_FAILED },
44
+ { where: { id } }
45
+ );
46
+ }
47
+ reject(error);
48
+ return;
49
+ }
50
+ console.log(
51
+ `Seed applied for ${kind} db: ${
27
52
  !isCommonDb ? name : ""
28
- }`,
29
- error.message
53
+ }`
30
54
  );
31
55
  if (!isCommonDb) {
32
56
  await commonDb.church.update(
33
- { seedStatus: ONBOARD_STATUS.SEED_FAILED },
57
+ { seedStatus: ONBOARD_STATUS.SEED_COMPLETED },
34
58
  { where: { id } }
35
59
  );
36
60
  }
37
- reject(error);
61
+ resolve();
38
62
  }
39
- console.log(
40
- `Seed applied for ${isCommonDb ? "common" : "church"} db: ${
41
- !isCommonDb ? name : ""
42
- }`
63
+ );
64
+ } catch (err) {
65
+ if (!isCommonDb && id) {
66
+ await commonDb.church.update(
67
+ { seedStatus: ONBOARD_STATUS.SEED_FAILED },
68
+ { where: { id } }
43
69
  );
44
- if (!isCommonDb) {
45
- await commonDb.church.update(
46
- { seedStatus: ONBOARD_STATUS.SEED_COMPLETED },
47
- { where: { id } }
48
- );
49
- }
50
- resolve();
51
70
  }
52
- );
71
+ reject(err);
72
+ }
53
73
  });
54
74
  };
55
75
  new Worker(