@loomcore/api 0.1.9 → 0.1.11

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.
@@ -29,15 +29,15 @@ export class TestPostgresDatabase {
29
29
  const testDatabase = new PostgresDatabase(postgresClient);
30
30
  this.database = testDatabase;
31
31
  this.postgresClient = postgresClient;
32
- let success = await setupDatabaseForMultitenant(postgresClient, 'Test Org', 'test-org');
32
+ let success = (await setupDatabaseForMultitenant(postgresClient, 'Test Org', 'test-org')).success;
33
33
  if (!success) {
34
34
  throw new Error('Failed to setup for multitenant');
35
35
  }
36
- success = await setupDatabaseForAuth(postgresClient, testOrg._id);
36
+ success = (await setupDatabaseForAuth(postgresClient, testOrg._id)).success;
37
37
  if (!success) {
38
38
  throw new Error('Failed to setup for auth');
39
39
  }
40
- success = await runTestMigrations(postgresClient, testOrg._id);
40
+ success = (await runTestMigrations(postgresClient, testOrg._id)).success;
41
41
  if (!success) {
42
42
  throw new Error('Failed to run test migrations');
43
43
  }
@@ -6,10 +6,10 @@ export declare class CreateMigrationTableMigration implements IMigration {
6
6
  index: number;
7
7
  execute(_orgId?: string): Promise<{
8
8
  success: boolean;
9
- error: null;
9
+ error: Error;
10
10
  } | {
11
11
  success: boolean;
12
- error: Error;
12
+ error: null;
13
13
  }>;
14
14
  revert(_orgId?: string): Promise<{
15
15
  success: boolean;
@@ -20,7 +20,7 @@ export class CreateMigrationTableMigration {
20
20
  }
21
21
  catch (error) {
22
22
  if (error.code === '42P07' || error.data?.error?.includes('already exists')) {
23
- return { success: true, error: null };
23
+ console.log(`Migrations table already exists`);
24
24
  }
25
25
  else {
26
26
  return { success: false, error: new Error(`Error creating migrations table: ${error.message}`) };
@@ -28,15 +28,21 @@ export class CreateMigrationTableMigration {
28
28
  }
29
29
  try {
30
30
  if (_orgId) {
31
- await this.client.query(`
31
+ const result = await this.client.query(`
32
32
  INSERT INTO "migrations" ("_id", "_orgId", "index", "hasRun", "reverted")
33
33
  VALUES ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);`);
34
+ if (result.rowCount === 0) {
35
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
36
+ }
34
37
  }
35
38
  else {
36
- await this.client.query(`
39
+ const result = await this.client.query(`
37
40
  INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
38
41
  VALUES ('${_id}', ${this.index}, TRUE, FALSE);
39
42
  `);
43
+ if (result.rowCount === 0) {
44
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
45
+ }
40
46
  }
41
47
  }
42
48
  catch (error) {
@@ -46,9 +52,12 @@ export class CreateMigrationTableMigration {
46
52
  }
47
53
  async revert(_orgId) {
48
54
  try {
49
- await this.client.query(`
55
+ const result = await this.client.query(`
50
56
  DROP TABLE "migrations";
51
57
  `);
58
+ if (result.rowCount === 0) {
59
+ return { success: false, error: new Error(`Error dropping migrations table: No row returned`) };
60
+ }
52
61
  }
53
62
  catch (error) {
54
63
  return { success: false, error: new Error(`Error reverting migration ${this.index} from migrations table: ${error.message}`) };
@@ -1,6 +1,6 @@
1
1
  import { Client } from "pg";
2
2
  import { IMigration } from "./migration.interface.js";
3
- export declare class CreateOrganizationTableMigration implements IMigration {
3
+ export declare class CreateOrganizationsTableMigration implements IMigration {
4
4
  private readonly client;
5
5
  private readonly orgName;
6
6
  private readonly orgCode;
@@ -8,10 +8,10 @@ export declare class CreateOrganizationTableMigration implements IMigration {
8
8
  index: number;
9
9
  execute(_orgId?: string): Promise<{
10
10
  success: boolean;
11
- error: null;
11
+ error: Error;
12
12
  } | {
13
13
  success: boolean;
14
- error: Error;
14
+ error: null;
15
15
  }>;
16
16
  revert(_orgId?: string): Promise<{
17
17
  success: boolean;
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "crypto";
2
- export class CreateOrganizationTableMigration {
2
+ export class CreateOrganizationsTableMigration {
3
3
  client;
4
4
  orgName;
5
5
  orgCode;
@@ -11,7 +11,6 @@ export class CreateOrganizationTableMigration {
11
11
  index = 2;
12
12
  async execute(_orgId) {
13
13
  const _id = randomUUID().toString();
14
- const _orgIdToUse = _orgId || randomUUID().toString();
15
14
  try {
16
15
  await this.client.query(`
17
16
  CREATE TABLE "organizations" (
@@ -33,26 +32,20 @@ export class CreateOrganizationTableMigration {
33
32
  }
34
33
  catch (error) {
35
34
  if (error.code === '42P07' || error.data?.error?.includes('already exists')) {
36
- return { success: true, error: null };
35
+ console.log(`Organization table already exists`);
37
36
  }
38
37
  else {
39
38
  return { success: false, error: new Error(`Error creating organization table: ${error.message}`) };
40
39
  }
41
40
  }
42
41
  try {
43
- await this.client.query(`
44
- INSERT INTO "organizations" ("_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
45
- VALUES ('${_orgIdToUse}', '${this.orgName}', '${this.orgCode}', 1, true, NOW(), 'system', NOW(), 'system');
46
- `);
47
- }
48
- catch (error) {
49
- return { success: false, error: new Error(`Error creating meta organization: ${error.message}`) };
50
- }
51
- try {
52
- await this.client.query(`
42
+ const result = await this.client.query(`
53
43
  INSERT INTO "migrations" ("_id", "_orgId", "index", "hasRun", "reverted")
54
- VALUES ('${_id}', '${_orgIdToUse}', ${this.index}, TRUE, FALSE);
44
+ VALUES ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);
55
45
  `);
46
+ if (result.rowCount === 0) {
47
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
48
+ }
56
49
  }
57
50
  catch (error) {
58
51
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
@@ -61,12 +54,15 @@ export class CreateOrganizationTableMigration {
61
54
  }
62
55
  async revert(_orgId) {
63
56
  try {
64
- await this.client.query(`
65
- DROP TABLE "organizations";
66
- `);
57
+ const result = await this.client.query(`
58
+ DROP TABLE "organizations";
59
+ `);
60
+ if (result.rowCount === 0) {
61
+ return { success: false, error: new Error(`Error dropping organizations table: No row returned`) };
62
+ }
67
63
  }
68
64
  catch (error) {
69
- return { success: false, error: new Error(`Error dropping organizations table: ${error.message}`) };
65
+ return { success: false, error: new Error(`Error dropping organizations table for orgId ${_orgId}: ${error.message}`) };
70
66
  }
71
67
  try {
72
68
  const result = await this.client.query(`
@@ -74,13 +70,13 @@ export class CreateOrganizationTableMigration {
74
70
  `);
75
71
  if (result.rowCount === 0) {
76
72
  return {
77
- success: false, error: new Error(`Error updating migration record: Migration record not found.
73
+ success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: Migration record not found.
78
74
  Migration index: ${this.index}, _orgId: ${_orgId}`)
79
75
  };
80
76
  }
81
77
  }
82
78
  catch (error) {
83
- return { success: false, error: new Error(`Error updating migration record: ${error.message}`) };
79
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: ${error.message}`) };
84
80
  }
85
81
  return { success: true, error: null };
86
82
  }
@@ -6,10 +6,10 @@ export declare class CreateUsersTableMigration implements IMigration {
6
6
  index: number;
7
7
  execute(_orgId?: string): Promise<{
8
8
  success: boolean;
9
- error: null;
9
+ error: Error;
10
10
  } | {
11
11
  success: boolean;
12
- error: Error;
12
+ error: null;
13
13
  }>;
14
14
  revert(_orgId?: string): Promise<{
15
15
  success: boolean;
@@ -31,7 +31,7 @@ export class CreateUsersTableMigration {
31
31
  }
32
32
  catch (error) {
33
33
  if (error.code === '42P07' || error.data?.error?.includes('already exists')) {
34
- return { success: true, error: null };
34
+ console.log(`Users table already exists`);
35
35
  }
36
36
  else {
37
37
  return { success: false, error: new Error(`Error creating users table: ${error.message}`) };
@@ -39,10 +39,13 @@ export class CreateUsersTableMigration {
39
39
  }
40
40
  if (_orgId) {
41
41
  try {
42
- await this.client.query(`
42
+ const result = await this.client.query(`
43
43
  INSERT INTO "migrations" ("_id", "_orgId", "index", "hasRun", "reverted")
44
44
  VALUES ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);
45
45
  `);
46
+ if (result.rowCount === 0) {
47
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
48
+ }
46
49
  }
47
50
  catch (error) {
48
51
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
@@ -50,10 +53,13 @@ export class CreateUsersTableMigration {
50
53
  }
51
54
  else {
52
55
  try {
53
- await this.client.query(`
56
+ const result = await this.client.query(`
54
57
  INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
55
58
  VALUES ('${_id}', ${this.index}, TRUE, FALSE);
56
59
  `);
60
+ if (result.rowCount === 0) {
61
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
62
+ }
57
63
  }
58
64
  catch (error) {
59
65
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
@@ -63,20 +69,26 @@ export class CreateUsersTableMigration {
63
69
  }
64
70
  async revert(_orgId) {
65
71
  try {
66
- await this.client.query(`
72
+ const result = await this.client.query(`
67
73
  DROP TABLE "users";
68
74
  `);
75
+ if (result.rowCount === 0) {
76
+ return { success: false, error: new Error(`Error dropping users table: No row returned`) };
77
+ }
69
78
  }
70
79
  catch (error) {
71
80
  return { success: false, error: new Error(`Error dropping users table: ${error.message}`) };
72
81
  }
73
82
  try {
74
- await this.client.query(`
83
+ const result = await this.client.query(`
75
84
  UPDATE "migrations" SET "reverted" = TRUE WHERE "index" = '${this.index}' AND "_orgId" = '${_orgId}';
76
85
  `);
86
+ if (result.rowCount === 0) {
87
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: No row returned`) };
88
+ }
77
89
  }
78
90
  catch (error) {
79
- return { success: false, error: new Error(`Error updating migration record: ${error.message}`) };
91
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: ${error.message}`) };
80
92
  }
81
93
  return { success: true, error: null };
82
94
  }
@@ -23,7 +23,7 @@ export class CreateRefreshTokenTableMigration {
23
23
  }
24
24
  catch (error) {
25
25
  if (error.code === '42P07' || error.data?.error?.includes('already exists')) {
26
- return { success: true, error: null };
26
+ console.log(`Refresh token table already exists`);
27
27
  }
28
28
  else {
29
29
  return { success: false, error: new Error(`Error creating refresh token table: ${error.message}`) };
@@ -31,10 +31,13 @@ export class CreateRefreshTokenTableMigration {
31
31
  }
32
32
  if (_orgId) {
33
33
  try {
34
- await this.client.query(`
34
+ const result = await this.client.query(`
35
35
  INSERT INTO "migrations" ("_id", "_orgId", "index", "hasRun", "reverted")
36
36
  VALUES ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);
37
37
  `);
38
+ if (result.rowCount === 0) {
39
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
40
+ }
38
41
  }
39
42
  catch (error) {
40
43
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
@@ -42,10 +45,13 @@ export class CreateRefreshTokenTableMigration {
42
45
  }
43
46
  else {
44
47
  try {
45
- await this.client.query(`
48
+ const result = await this.client.query(`
46
49
  INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
47
50
  VALUES ('${_id}', ${this.index}, TRUE, FALSE);
48
51
  `);
52
+ if (result.rowCount === 0) {
53
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
54
+ }
49
55
  }
50
56
  catch (error) {
51
57
  return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
@@ -55,17 +61,23 @@ export class CreateRefreshTokenTableMigration {
55
61
  }
56
62
  async revert(_orgId) {
57
63
  try {
58
- await this.client.query(`
64
+ const result = await this.client.query(`
59
65
  DROP TABLE "refreshTokens";
60
66
  `);
67
+ if (result.rowCount === 0) {
68
+ return { success: false, error: new Error(`Error dropping refresh token table: No row returned`) };
69
+ }
61
70
  }
62
71
  catch (error) {
63
72
  return { success: false, error: new Error(`Error dropping refresh token table: ${error.message}`) };
64
73
  }
65
74
  try {
66
- await this.client.query(`
75
+ const result = await this.client.query(`
67
76
  UPDATE "migrations" SET "reverted" = TRUE WHERE "index" = '${this.index}' AND "_orgId" = '${_orgId}';
68
77
  `);
78
+ if (result.rowCount === 0) {
79
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: No row returned`) };
80
+ }
69
81
  }
70
82
  catch (error) {
71
83
  return { success: false, error: new Error(`Error updating migration record: ${error.message}`) };
@@ -0,0 +1,26 @@
1
+ import { Client } from "pg";
2
+ import { IMigration } from "./migration.interface.js";
3
+ export declare class CreateMetaOrgMigration implements IMigration {
4
+ private readonly client;
5
+ private readonly orgName;
6
+ private readonly orgCode;
7
+ constructor(client: Client, orgName: string, orgCode: string);
8
+ index: number;
9
+ execute(_orgId?: string): Promise<{
10
+ success: boolean;
11
+ error: Error;
12
+ metaOrgId?: undefined;
13
+ } | {
14
+ success: boolean;
15
+ metaOrgId: string;
16
+ error: null;
17
+ }>;
18
+ revert(_orgId?: string): Promise<{
19
+ success: boolean;
20
+ error: Error;
21
+ } | {
22
+ success: boolean;
23
+ error: null;
24
+ }>;
25
+ }
26
+ export default CreateMetaOrgMigration;
@@ -0,0 +1,63 @@
1
+ import { randomUUID } from "crypto";
2
+ export class CreateMetaOrgMigration {
3
+ client;
4
+ orgName;
5
+ orgCode;
6
+ constructor(client, orgName, orgCode) {
7
+ this.client = client;
8
+ this.orgName = orgName;
9
+ this.orgCode = orgCode;
10
+ }
11
+ index = 5;
12
+ async execute(_orgId) {
13
+ const _id = randomUUID().toString();
14
+ try {
15
+ const result = await this.client.query(`
16
+ INSERT INTO "organizations" ("_id", "name", "code", "status", "isMetaOrg", "_created", "_createdBy", "_updated", "_updatedBy")
17
+ VALUES ('${_id}', '${this.orgName}', '${this.orgCode}', 1, true, NOW(), 'system', NOW(), 'system');`);
18
+ if (result.rowCount === 0) {
19
+ return { success: false, error: new Error(`Error creating meta org: No row returned`) };
20
+ }
21
+ }
22
+ catch (error) {
23
+ return { success: false, error: new Error(`Error creating meta org: ${error.message}`) };
24
+ }
25
+ try {
26
+ const result = await this.client.query(`
27
+ INSERT INTO "migrations" ("_id", "_orgId", "index", "hasRun", "reverted")
28
+ VALUES ('${_id}', '${_orgId}', ${this.index}, TRUE, FALSE);
29
+ `);
30
+ if (result.rowCount === 0) {
31
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
32
+ }
33
+ }
34
+ catch (error) {
35
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
36
+ }
37
+ return { success: true, metaOrgId: _id, error: null };
38
+ }
39
+ async revert(_orgId) {
40
+ try {
41
+ const result = await this.client.query(`DELETE FROM "organizations" WHERE "_id" = ${_orgId} AND "isMetaOrg" = TRUE;`);
42
+ if (result.rowCount === 0) {
43
+ return { success: false, error: new Error(`Error reverting meta org: No row returned`) };
44
+ }
45
+ }
46
+ catch (error) {
47
+ return { success: false, error: new Error(`Error reverting meta org: ${error.message}`) };
48
+ }
49
+ try {
50
+ const result = await this.client.query(`
51
+ UPDATE "migrations" SET "reverted" = TRUE WHERE "index" = '${this.index}' AND "_orgId" = '${_orgId}';
52
+ `);
53
+ if (result.rowCount === 0) {
54
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: No row returned`) };
55
+ }
56
+ }
57
+ catch (error) {
58
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index} and orgId ${_orgId}: ${error.message}`) };
59
+ }
60
+ return { success: true, error: null };
61
+ }
62
+ }
63
+ export default CreateMetaOrgMigration;
@@ -1,4 +1,4 @@
1
- import { CreateRefreshTokenTableMigration } from "./004-create-refresh-token-table.migration.js";
1
+ import { CreateRefreshTokenTableMigration } from "./004-create-refresh-tokens-table.migration.js";
2
2
  import { CreateMigrationTableMigration } from "./001-create-migrations-table.migration.js";
3
3
  import { CreateUsersTableMigration } from "./003-create-users-table.migration.js";
4
4
  import { doesTableExist } from "../utils/does-table-exist.util.js";
@@ -1,5 +1,6 @@
1
1
  import { Client } from "pg";
2
2
  export declare function setupDatabaseForMultitenant(client: Client, orgName: string, orgCode: string): Promise<{
3
3
  success: boolean;
4
+ metaOrgId: string | undefined;
4
5
  error: Error | null;
5
6
  }>;
@@ -1,30 +1,44 @@
1
1
  import { CreateMigrationTableMigration } from "./001-create-migrations-table.migration.js";
2
- import { CreateOrganizationTableMigration } from "./002-create-organizations-table.migration.js";
2
+ import { CreateOrganizationsTableMigration } from "./002-create-organizations-table.migration.js";
3
3
  import { doesTableExist } from "../utils/does-table-exist.util.js";
4
+ import { CreateMetaOrgMigration } from "./005-create-meta-org.migration.js";
5
+ import { randomUUID } from 'crypto';
4
6
  export async function setupDatabaseForMultitenant(client, orgName, orgCode) {
5
7
  let runMigrations = [];
8
+ const metaOrgId = randomUUID().toString();
6
9
  if (await doesTableExist(client, 'migrations')) {
7
10
  const migrations = await client.query(`
8
11
  SELECT "_id", "index"
9
12
  FROM migrations
10
- WHERE "hasRun" = TRUE AND "reverted" = FALSE AND "_orgId" IS NULL
13
+ WHERE "hasRun" = TRUE AND "reverted" = FALSE
11
14
  `);
12
15
  runMigrations = migrations.rows.map((row) => {
13
16
  return row.index;
14
17
  });
15
18
  }
16
- let migrationsToRun = [];
17
- if (!runMigrations.includes(1))
18
- migrationsToRun.push(new CreateMigrationTableMigration(client));
19
- if (!runMigrations.includes(2))
20
- migrationsToRun.push(new CreateOrganizationTableMigration(client, orgName, orgCode));
21
- try {
22
- for (const migration of migrationsToRun) {
23
- await migration.execute();
19
+ if (!runMigrations.includes(1)) {
20
+ const createMigrationTableMigration = new CreateMigrationTableMigration(client);
21
+ const result = await createMigrationTableMigration.execute(metaOrgId);
22
+ if (!result.success) {
23
+ console.log('setupDatabaseForMultitenant: error creating migration table', result.error);
24
+ return { success: false, metaOrgId: metaOrgId, error: result.error };
24
25
  }
25
26
  }
26
- catch (error) {
27
- return { success: false, error: error };
27
+ if (!runMigrations.includes(2)) {
28
+ const createOrganizationTableMigration = new CreateOrganizationsTableMigration(client, orgName, orgCode);
29
+ const result = await createOrganizationTableMigration.execute(metaOrgId);
30
+ if (!result.success) {
31
+ console.log('setupDatabaseForMultitenant: error creating organizations table', result.error);
32
+ return { success: false, metaOrgId: metaOrgId, error: result.error };
33
+ }
34
+ }
35
+ if (!runMigrations.includes(5)) {
36
+ const createMetaOrgMigration = new CreateMetaOrgMigration(client, orgName, orgCode);
37
+ const result = await createMetaOrgMigration.execute(metaOrgId);
38
+ if (!result.success || !result.metaOrgId) {
39
+ console.log('setupDatabaseForMultitenant: error creating meta org', result.error);
40
+ return { success: false, metaOrgId: metaOrgId, error: result.error };
41
+ }
28
42
  }
29
- return { success: true, error: null };
43
+ return { success: true, metaOrgId: metaOrgId, error: null };
30
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {
@@ -6,10 +6,10 @@ export declare class CreateRefreshTokenTableMigration implements IMigration {
6
6
  index: number;
7
7
  execute(_orgId?: string): Promise<{
8
8
  success: boolean;
9
- error: null;
9
+ error: Error;
10
10
  } | {
11
11
  success: boolean;
12
- error: Error;
12
+ error: null;
13
13
  }>;
14
14
  revert(_orgId?: string): Promise<{
15
15
  success: boolean;