@churchsoln/dbms 1.0.61 → 1.0.63
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 +9 -0
- package/constants/index.js +1 -0
- package/db-setup.js +5 -0
- package/package.json +1 -1
- package/utils/seedTracking.js +95 -0
- package/workers/seed.js +52 -32
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
|
};
|
package/constants/index.js
CHANGED
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
|
@@ -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 ${
|
|
13
|
+
`Applying seed for ${kind} db: ${!isCommonDb ? name : ""}`
|
|
12
14
|
);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
57
|
+
{ seedStatus: ONBOARD_STATUS.SEED_COMPLETED },
|
|
34
58
|
{ where: { id } }
|
|
35
59
|
);
|
|
36
60
|
}
|
|
37
|
-
|
|
61
|
+
resolve();
|
|
38
62
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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(
|