@loomcore/api 0.2.7 → 0.2.9

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.
Files changed (90) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +8 -6
  2. package/dist/__tests__/common-test.utils.js +61 -37
  3. package/dist/__tests__/test-express-app.d.ts +20 -19
  4. package/dist/__tests__/test-express-app.js +72 -68
  5. package/dist/__tests__/test-objects.d.ts +5 -0
  6. package/dist/__tests__/test-objects.js +16 -0
  7. package/dist/config/base-api-config.d.ts +2 -2
  8. package/dist/config/base-api-config.js +2 -2
  9. package/dist/controllers/auth.controller.d.ts +9 -8
  10. package/dist/controllers/auth.controller.js +76 -29
  11. package/dist/controllers/authorizations.controller.d.ts +3 -3
  12. package/dist/controllers/authorizations.controller.js +4 -4
  13. package/dist/controllers/roles.controller.d.ts +1 -1
  14. package/dist/controllers/roles.controller.js +3 -3
  15. package/dist/controllers/users.controller.d.ts +5 -6
  16. package/dist/controllers/users.controller.js +4 -15
  17. package/dist/databases/postgres/commands/postgres-create.command.d.ts +2 -2
  18. package/dist/databases/postgres/commands/postgres-create.command.js +5 -5
  19. package/dist/databases/postgres/migrations/__tests__/test-migration-helper.js +2 -1
  20. package/dist/databases/postgres/migrations/postgres-initial-schema.js +7 -5
  21. package/dist/databases/postgres/postgres.database.d.ts +2 -2
  22. package/dist/databases/postgres/postgres.database.js +6 -8
  23. package/dist/middleware/index.d.ts +4 -4
  24. package/dist/middleware/index.js +4 -4
  25. package/dist/middleware/is-authorized.d.ts +2 -2
  26. package/dist/middleware/is-authorized.js +13 -11
  27. package/dist/models/app-config.interface.d.ts +1 -1
  28. package/dist/models/base-api-config.interface.d.ts +5 -5
  29. package/dist/models/initial-database-config.interface.d.ts +4 -3
  30. package/dist/models/refresh-token.model.d.ts +1 -1
  31. package/dist/models/refresh-token.model.js +3 -4
  32. package/dist/services/generic-api-service/generic-api-service.interface.d.ts +6 -6
  33. package/dist/services/generic-api-service/generic-api.service.d.ts +8 -8
  34. package/dist/services/generic-api-service/generic-api.service.js +16 -16
  35. package/dist/services/index.d.ts +11 -12
  36. package/dist/services/index.js +11 -12
  37. package/dist/services/multi-tenant-api.service.js +6 -2
  38. package/dist/services/password-reset-token.service.d.ts +2 -3
  39. package/dist/services/password-reset-token.service.js +4 -9
  40. package/dist/services/refresh-token.service.d.ts +6 -0
  41. package/dist/services/refresh-token.service.js +7 -0
  42. package/dist/services/user.service.js +4 -4
  43. package/dist/utils/auth/attempt-login.util.d.ts +3 -0
  44. package/dist/utils/auth/attempt-login.util.js +37 -0
  45. package/dist/utils/auth/change-password.util.d.ts +4 -0
  46. package/dist/utils/auth/change-password.util.js +14 -0
  47. package/dist/utils/auth/create-new-refresh-token.util.d.ts +4 -0
  48. package/dist/utils/auth/create-new-refresh-token.util.js +29 -0
  49. package/dist/utils/auth/create-new-tokens.util.d.ts +3 -0
  50. package/dist/utils/auth/create-new-tokens.util.js +13 -0
  51. package/dist/utils/auth/generate-device-id.util.d.ts +1 -0
  52. package/dist/utils/auth/generate-device-id.util.js +4 -0
  53. package/dist/utils/auth/generate-refresh-token.util.d.ts +1 -0
  54. package/dist/utils/auth/generate-refresh-token.util.js +4 -0
  55. package/dist/utils/auth/get-active-refresh-token.util.d.ts +4 -0
  56. package/dist/utils/auth/get-active-refresh-token.util.js +19 -0
  57. package/dist/utils/auth/get-and-set-device-id-cookie.util.d.ts +2 -0
  58. package/dist/utils/auth/get-and-set-device-id-cookie.util.js +23 -0
  59. package/dist/utils/auth/get-auth-config.util.d.ts +2 -0
  60. package/dist/utils/auth/get-auth-config.util.js +8 -0
  61. package/dist/utils/auth/get-device-id-from-cookie.util.d.ts +2 -0
  62. package/dist/utils/auth/get-device-id-from-cookie.util.js +3 -0
  63. package/dist/utils/auth/get-expires-on-from-days.util.d.ts +1 -0
  64. package/dist/utils/auth/get-expires-on-from-days.util.js +3 -0
  65. package/dist/utils/auth/get-expires-on-from-minutes.util.d.ts +1 -0
  66. package/dist/utils/auth/get-expires-on-from-minutes.util.js +3 -0
  67. package/dist/utils/auth/get-expires-on-from-seconds.util.d.ts +1 -0
  68. package/dist/utils/auth/get-expires-on-from-seconds.util.js +3 -0
  69. package/dist/utils/auth/index.d.ts +18 -0
  70. package/dist/utils/auth/index.js +18 -0
  71. package/dist/utils/auth/log-user-in.util.d.ts +3 -0
  72. package/dist/utils/auth/log-user-in.util.js +18 -0
  73. package/dist/utils/auth/request-token-using-refresh-token.util.d.ts +3 -0
  74. package/dist/utils/auth/request-token-using-refresh-token.util.js +9 -0
  75. package/dist/utils/auth/reset-password.util.d.ts +4 -0
  76. package/dist/utils/auth/reset-password.util.js +39 -0
  77. package/dist/utils/auth/send-reset-password-email.util.d.ts +3 -0
  78. package/dist/utils/auth/send-reset-password-email.util.js +23 -0
  79. package/dist/utils/auth/update-last-logged-in.util.d.ts +3 -0
  80. package/dist/utils/auth/update-last-logged-in.util.js +15 -0
  81. package/dist/utils/index.d.ts +8 -6
  82. package/dist/utils/index.js +8 -6
  83. package/dist/utils/jwt.utils.d.ts +2 -0
  84. package/dist/utils/jwt.utils.js +12 -0
  85. package/dist/utils/password.utils.js +8 -8
  86. package/package.json +3 -3
  87. package/dist/services/auth.service.d.ts +0 -49
  88. package/dist/services/auth.service.js +0 -277
  89. package/dist/services/jwt.service.d.ts +0 -4
  90. package/dist/services/jwt.service.js +0 -18
@@ -1,60 +1,76 @@
1
- import { LoginResponseSpec, PublicUserContextSpec, PublicUserSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
1
+ import { EmptyUserContext, LoginResponseSpec, PublicUserContextSpec, passwordValidator, TokenResponseSpec, UserSpec, } from "@loomcore/common/models";
2
2
  import { entityUtils } from "@loomcore/common/utils";
3
+ import { config } from "../config/base-api-config.js";
3
4
  import { BadRequestError, UnauthenticatedError } from "../errors/index.js";
4
5
  import { isAuthorized } from "../middleware/index.js";
5
- import { AuthService } from "../services/index.js";
6
+ import { OrganizationService, UserService } from "../services/index.js";
7
+ import { attemptLogin, changePassword, getAndSetDeviceIdCookie, getDeviceIdFromCookie, requestTokenUsingRefreshToken, resetPassword, sendResetPasswordEmail, } from "../utils/auth/index.js";
6
8
  import { apiUtils } from "../utils/index.js";
7
9
  export class AuthController {
8
- authService;
10
+ database;
11
+ userService;
12
+ organizationService;
9
13
  constructor(app, database) {
10
- this.authService = new AuthService(database);
14
+ this.database = database;
15
+ this.userService = new UserService(database);
16
+ this.organizationService = new OrganizationService(database);
11
17
  this.mapRoutes(app);
12
18
  }
13
19
  mapRoutes(app) {
14
20
  app.post(`/api/auth/login`, this.login.bind(this), this.afterAuth.bind(this));
15
- app.post(`/api/auth/register`, isAuthorized(), this.registerUser.bind(this));
16
21
  app.get(`/api/auth/refresh`, this.requestTokenUsingRefreshToken.bind(this));
17
22
  app.get(`/api/auth/get-user-context`, isAuthorized(), this.getUserContext.bind(this));
18
23
  app.patch(`/api/auth/change-password`, isAuthorized(), this.changePassword.bind(this));
19
24
  app.post(`/api/auth/forgot-password`, this.forgotPassword.bind(this));
20
25
  app.post(`/api/auth/reset-password`, this.resetPassword.bind(this));
21
26
  }
22
- async login(req, res, next) {
27
+ async login(req, res) {
23
28
  const { email, password } = req.body;
29
+ if (!email || typeof email !== "string") {
30
+ throw new BadRequestError("Missing required fields: email is required.");
31
+ }
32
+ if (!password || typeof password !== "string") {
33
+ throw new BadRequestError("Missing required fields: password is required.");
34
+ }
35
+ let organization = null;
36
+ if (config.app.isMultiTenant) {
37
+ const referer = req.get("referer") || req.headers.referer;
38
+ if (!referer) {
39
+ throw new BadRequestError("Missing required fields: referer is required.");
40
+ }
41
+ organization = await this.organizationService.findOne(EmptyUserContext, {
42
+ filters: { domain: { eq: referer.split("/")[2] } },
43
+ });
44
+ if (!organization) {
45
+ throw new BadRequestError("Missing required fields: organization is required.");
46
+ }
47
+ }
24
48
  res.set("Content-Type", "application/json");
25
- const loginResponse = await this.authService.attemptLogin(req, res, email, password);
49
+ const deviceId = getAndSetDeviceIdCookie(req, res);
50
+ const loginResponse = await attemptLogin(this.database, email, password, deviceId, organization);
26
51
  apiUtils.apiResponse(res, 200, { data: loginResponse }, LoginResponseSpec);
27
52
  }
28
- async registerUser(req, res) {
53
+ async requestTokenUsingRefreshToken(req, res) {
29
54
  const userContext = req.userContext;
30
55
  if (!userContext) {
31
56
  throw new BadRequestError("Missing required fields: userContext is required.");
32
57
  }
33
- const body = req.body;
34
- const validationErrors = this.authService.validate(body.user);
35
- entityUtils.handleValidationResult(validationErrors, "AuthController.registerUser");
36
- const user = await this.authService.createUser(userContext, body.user);
37
- apiUtils.apiResponse(res, 201, { data: user || undefined }, UserSpec, PublicUserSpec);
38
- }
39
- async requestTokenUsingRefreshToken(req, res, next) {
40
58
  const refreshToken = req.query.refreshToken;
41
59
  if (!refreshToken || typeof refreshToken !== "string") {
42
60
  throw new BadRequestError("Missing required fields: refreshToken is required.");
43
61
  }
44
- const deviceId = this.authService.getDeviceIdFromCookie(req);
45
- const tokens = await this.authService.requestTokenUsingRefreshToken(refreshToken, deviceId);
46
- if (tokens) {
47
- apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
48
- }
49
- else {
62
+ const deviceId = getDeviceIdFromCookie(req);
63
+ const tokens = await requestTokenUsingRefreshToken(this.database, userContext, refreshToken, deviceId);
64
+ if (!tokens) {
50
65
  throw new UnauthenticatedError();
51
66
  }
67
+ apiUtils.apiResponse(res, 200, { data: tokens }, TokenResponseSpec);
52
68
  }
53
- async getUserContext(req, res, next) {
69
+ async getUserContext(req, res) {
54
70
  const userContext = req.userContext;
55
71
  apiUtils.apiResponse(res, 200, { data: userContext }, PublicUserContextSpec);
56
72
  }
57
- afterAuth(req, res, loginResponse) {
73
+ afterAuth(_req, _res, _loginResponse) {
58
74
  console.log("in afterAuth");
59
75
  }
60
76
  async changePassword(req, res) {
@@ -62,22 +78,40 @@ export class AuthController {
62
78
  if (!userContext) {
63
79
  throw new BadRequestError("Missing required fields: userContext is required.");
64
80
  }
65
- const body = req.body;
66
- const validationErrors = entityUtils.validate(UserSpec, { password: body.password }, true, passwordValidator);
81
+ const password = req.body?.password;
82
+ const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
67
83
  entityUtils.handleValidationResult(validationErrors, "AuthController.changePassword");
68
- const updateResult = await this.authService.changeLoggedInUsersPassword(userContext, body);
84
+ const updateResult = await changePassword(this.database, userContext, password);
69
85
  apiUtils.apiResponse(res, 200, { data: updateResult });
70
86
  }
71
87
  async forgotPassword(req, res) {
72
88
  const email = req.body?.email;
89
+ if (!email || typeof email !== "string") {
90
+ throw new BadRequestError("Missing required fields: email is required.");
91
+ }
73
92
  let referer = req.get("referer") || req.headers.referer;
74
93
  if (!referer) {
75
94
  throw new BadRequestError("Missing required fields: referer is required.");
76
95
  }
77
96
  referer = referer.replace(/\/$/, "");
78
- const user = await this.authService.getUserByEmail(email);
97
+ let organization = null;
98
+ if (config.app.isMultiTenant) {
99
+ organization = await this.organizationService.findOne(EmptyUserContext, {
100
+ filters: { domain: { eq: referer.split("/")[2] } },
101
+ });
102
+ if (!organization) {
103
+ throw new BadRequestError("Missing required fields: organization is required.");
104
+ }
105
+ }
106
+ const userContext = {
107
+ ...EmptyUserContext,
108
+ organization: organization || undefined,
109
+ };
110
+ const user = await this.userService.findOne(userContext, {
111
+ filters: { email: { eq: email.toLowerCase() } },
112
+ });
79
113
  if (user) {
80
- await this.authService.sendResetPasswordEmail(email, referer);
114
+ await sendResetPasswordEmail(this.database, email, referer);
81
115
  }
82
116
  apiUtils.apiResponse(res, 200);
83
117
  }
@@ -86,7 +120,20 @@ export class AuthController {
86
120
  if (!email || !token || !password) {
87
121
  throw new BadRequestError("Missing required fields: email, token, and password are required.");
88
122
  }
89
- const response = await this.authService.resetPassword(email, token, password);
123
+ let organization = null;
124
+ if (config.app.isMultiTenant) {
125
+ const referer = req.get("referer") || req.headers.referer;
126
+ if (!referer) {
127
+ throw new BadRequestError("Missing required fields: referer is required.");
128
+ }
129
+ organization = await this.organizationService.findOne(EmptyUserContext, {
130
+ filters: { domain: { eq: referer.split("/")[2] } },
131
+ });
132
+ if (!organization) {
133
+ throw new BadRequestError("Missing required fields: organization is required.");
134
+ }
135
+ }
136
+ const response = await resetPassword(this.database, email, token, password, organization);
90
137
  apiUtils.apiResponse(res, 200, { data: response });
91
138
  }
92
139
  }
@@ -1,7 +1,7 @@
1
- import { IAuthorization } from "../models/authorization.model.js";
1
+ import type { Application } from "express";
2
+ import type { IDatabase } from "../databases/models/index.js";
3
+ import { type IAuthorization } from "../models/authorization.model.js";
2
4
  import { ApiController } from "./api.controller.js";
3
- import { Application } from "express";
4
- import { IDatabase } from "../databases/models/index.js";
5
5
  export declare class AuthorizationsController extends ApiController<IAuthorization> {
6
6
  constructor(app: Application, database: IDatabase);
7
7
  }
@@ -1,9 +1,9 @@
1
- import { AuthorizationModelSpec } from "../models/authorization.model.js";
2
- import { ApiController } from "./api.controller.js";
1
+ import { AuthorizationModelSpec, } from "../models/authorization.model.js";
3
2
  import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
3
+ import { ApiController } from "./api.controller.js";
4
4
  export class AuthorizationsController extends ApiController {
5
5
  constructor(app, database) {
6
- const authorizationService = new MultiTenantApiService(database, 'authorizations', 'authorization', AuthorizationModelSpec);
7
- super('authorizations', app, authorizationService, 'authorization', AuthorizationModelSpec);
6
+ const authorizationService = new MultiTenantApiService(database, "authorizations", "authorization", AuthorizationModelSpec);
7
+ super("authorizations", app, authorizationService, "authorization", AuthorizationModelSpec);
8
8
  }
9
9
  }
@@ -1,7 +1,7 @@
1
+ import { Application } from "express";
1
2
  import { IDatabase } from "../databases/models/index.js";
2
3
  import { IRole } from "../models/role.model.js";
3
4
  import { ApiController } from "./api.controller.js";
4
- import { Application } from "express";
5
5
  export declare class RolesController extends ApiController<IRole> {
6
6
  constructor(app: Application, database: IDatabase);
7
7
  }
@@ -1,9 +1,9 @@
1
1
  import { RoleModelSpec } from "../models/role.model.js";
2
- import { ApiController } from "./api.controller.js";
3
2
  import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
3
+ import { ApiController } from "./api.controller.js";
4
4
  export class RolesController extends ApiController {
5
5
  constructor(app, database) {
6
- const roleService = new MultiTenantApiService(database, 'roles', 'role', RoleModelSpec);
7
- super('roles', app, roleService, 'role', RoleModelSpec, RoleModelSpec);
6
+ const roleService = new MultiTenantApiService(database, "roles", "role", RoleModelSpec);
7
+ super("roles", app, roleService, "role", RoleModelSpec, RoleModelSpec);
8
8
  }
9
9
  }
@@ -1,10 +1,9 @@
1
- import { Application } from 'express';
2
- import { IUser } from '@loomcore/common/models';
3
- import { ApiController } from './api.controller.js';
4
- import { UserService } from '../services/index.js';
5
- import { IDatabase } from '../databases/models/index.js';
1
+ import { type IUser } from "@loomcore/common/models";
2
+ import type { Application } from "express";
3
+ import type { IDatabase } from "../databases/models/index.js";
4
+ import { UserService } from "../services/index.js";
5
+ import { ApiController } from "./api.controller.js";
6
6
  export declare class UsersController extends ApiController<IUser> {
7
7
  userService: UserService;
8
8
  constructor(app: Application, database: IDatabase);
9
- mapRoutes(app: Application): void;
10
9
  }
@@ -1,22 +1,11 @@
1
- import { UserSpec, PublicUserSpec } from '@loomcore/common/models';
2
- import { ApiController } from './api.controller.js';
3
- import { isAuthorized } from '../middleware/index.js';
4
- import { UserService } from '../services/index.js';
1
+ import { PublicUserSpec, UserSpec } from "@loomcore/common/models";
2
+ import { UserService } from "../services/index.js";
3
+ import { ApiController } from "./api.controller.js";
5
4
  export class UsersController extends ApiController {
6
5
  userService;
7
6
  constructor(app, database) {
8
7
  const userService = new UserService(database);
9
- super('users', app, userService, 'user', UserSpec, PublicUserSpec);
8
+ super("users", app, userService, "user", UserSpec, PublicUserSpec);
10
9
  this.userService = userService;
11
10
  }
12
- mapRoutes(app) {
13
- app.get(`/api/${this.slug}`, isAuthorized(), this.get.bind(this));
14
- app.get(`/api/${this.slug}/all`, isAuthorized(), this.getAll.bind(this));
15
- app.get(`/api/${this.slug}/find`, isAuthorized(), this.get.bind(this));
16
- app.get(`/api/${this.slug}/count`, isAuthorized(), this.getCount.bind(this));
17
- app.get(`/api/${this.slug}/:id`, isAuthorized(), this.getById.bind(this));
18
- app.post(`/api/${this.slug}`, isAuthorized(), this.create.bind(this));
19
- app.patch(`/api/${this.slug}/:id`, isAuthorized(), this.partialUpdateById.bind(this));
20
- app.delete(`/api/${this.slug}/:id`, isAuthorized(), this.deleteById.bind(this));
21
- }
22
11
  }
@@ -1,6 +1,6 @@
1
- import type { PostgresConnection } from '../postgres-connection.js';
2
- import { IEntity } from "@loomcore/common/models";
1
+ import type { IEntity } from "@loomcore/common/models";
3
2
  import type { AppIdType } from "@loomcore/common/types";
3
+ import type { PostgresConnection } from "../postgres-connection.js";
4
4
  export declare function create<T extends IEntity>(client: PostgresConnection, pluralResourceName: string, entity: Partial<T>): Promise<{
5
5
  insertedId: AppIdType;
6
6
  entity: T;
@@ -1,12 +1,12 @@
1
1
  import { BadRequestError, DuplicateKeyError } from "../../../errors/index.js";
2
- import { columnsAndValuesFromEntity } from '../utils/columns-and-values-from-entity.js';
2
+ import { columnsAndValuesFromEntity } from "../utils/columns-and-values-from-entity.js";
3
3
  export async function create(client, pluralResourceName, entity) {
4
4
  try {
5
5
  delete entity._id;
6
6
  const { columns, values } = columnsAndValuesFromEntity(entity);
7
- const placeholders = columns.map((_, index) => `$${index + 1}`).join(', ');
7
+ const placeholders = columns.map((_, index) => `$${index + 1}`).join(", ");
8
8
  const query = `
9
- INSERT INTO "${pluralResourceName}" (${columns.join(', ')})
9
+ INSERT INTO "${pluralResourceName}" (${columns.join(", ")})
10
10
  VALUES (${placeholders})
11
11
  RETURNING *
12
12
  `;
@@ -18,11 +18,11 @@ export async function create(client, pluralResourceName, entity) {
18
18
  const insertedId = insertedRow._id;
19
19
  return {
20
20
  insertedId: insertedId,
21
- entity: insertedRow
21
+ entity: insertedRow,
22
22
  };
23
23
  }
24
24
  catch (err) {
25
- if (err.code === '23505') {
25
+ if (err.code === "23505") {
26
26
  throw new DuplicateKeyError(`${pluralResourceName} already exists`);
27
27
  }
28
28
  throw new BadRequestError(`Error creating ${pluralResourceName}: ${err.message}`);
@@ -1,4 +1,5 @@
1
1
  import { Umzug } from 'umzug';
2
+ import { TEST_META_ORG_DOMAIN } from '../../../../__tests__/test-objects.js';
2
3
  import { getPostgresInitialSchema } from '../postgres-initial-schema.js';
3
4
  import { getPostgresTestSchema } from '../../../../__tests__/postgres-test-migrations/postgres-test-schema.js';
4
5
  export async function runInitialSchemaMigrations(pool, config) {
@@ -7,7 +8,7 @@ export async function runInitialSchemaMigrations(pool, config) {
7
8
  app: config.app,
8
9
  database: config.database,
9
10
  adminUser: config.adminUser ?? { email: 'admin@test.com', password: 'admin-password' },
10
- multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG' } : { metaOrgName: '', metaOrgCode: '' }),
11
+ multiTenant: config.multiTenant ?? (config.app.isMultiTenant ? { metaOrgName: 'Test Meta Organization', metaOrgCode: 'TEST_META_ORG', metaOrgDomain: TEST_META_ORG_DOMAIN } : { metaOrgName: '', metaOrgCode: '', metaOrgDomain: '' }),
11
12
  email: config.email,
12
13
  };
13
14
  const initialSchema = getPostgresInitialSchema(migrationConfig);
@@ -33,7 +33,8 @@ export const getPostgresInitialSchema = (dbConfig) => {
33
33
  CREATE TABLE IF NOT EXISTS "organizations" (
34
34
  "_id" INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
35
35
  "name" VARCHAR(255) NOT NULL UNIQUE,
36
- "code" VARCHAR(255) NOT NULL UNIQUE,
36
+ "code" VARCHAR(255) UNIQUE,
37
+ "domain" VARCHAR(255) UNIQUE,
37
38
  "description" TEXT,
38
39
  "status" INTEGER NOT NULL,
39
40
  "is_meta_org" BOOLEAN NOT NULL,
@@ -297,12 +298,13 @@ export const getPostgresInitialSchema = (dbConfig) => {
297
298
  name: "00000000000011_data-meta-org",
298
299
  up: async ({ context: pool }) => {
299
300
  const result = await pool.query(`
300
- INSERT INTO "organizations"("name", "code", "status", "is_meta_org", "_created", "_createdBy")
301
- VALUES($1, $2, 1, true, NOW(), 0)
302
- RETURNING "_id", "name", "code", "status", "is_meta_org", "_created", "_createdBy"
303
- `, [
301
+ INSERT INTO "organizations"("name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy")
302
+ VALUES($1, $2, $3, 1, true, NOW(), 0)
303
+ RETURNING "_id", "name", "code", "domain", "status", "is_meta_org", "_created", "_createdBy"
304
+ `, [
304
305
  dbConfig.multiTenant?.metaOrgName,
305
306
  dbConfig.multiTenant?.metaOrgCode,
307
+ dbConfig.multiTenant?.metaOrgDomain,
306
308
  ]);
307
309
  if (result.rowCount === 0) {
308
310
  throw new Error("Failed to create meta organization");
@@ -1,9 +1,9 @@
1
- import { IQueryOptions, IModelSpec, IPagedResult, IEntity, IUserContextAuthorization } from "@loomcore/common/models";
1
+ import { IEntity, IModelSpec, IPagedResult, IQueryOptions, IUserContextAuthorization } from "@loomcore/common/models";
2
2
  import type { AppIdType } from "@loomcore/common/types";
3
3
  import { TSchema } from "@sinclair/typebox";
4
4
  import { DeleteResult, IDatabase } from "../models/index.js";
5
5
  import { Operation } from "../operations/operation.js";
6
- import type { PostgresConnection } from './postgres-connection.js';
6
+ import type { PostgresConnection } from "./postgres-connection.js";
7
7
  export declare class PostgresDatabase implements IDatabase {
8
8
  private connection;
9
9
  constructor(connection: PostgresConnection);
@@ -1,19 +1,19 @@
1
+ import { batchUpdate as batchUpdateCommand } from "./commands/postgres-batch-update.command.js";
1
2
  import { create as createCommand } from "./commands/postgres-create.command.js";
2
3
  import { createMany as createManyCommand } from "./commands/postgres-create-many.command.js";
3
- import { batchUpdate as batchUpdateCommand } from "./commands/postgres-batch-update.command.js";
4
+ import { deleteById as deleteByIdCommand } from "./commands/postgres-delete-by-id.command.js";
5
+ import { deleteMany as deleteManyCommand } from "./commands/postgres-delete-many.command.js";
4
6
  import { fullUpdateById as fullUpdateByIdCommand } from "./commands/postgres-full-update-by-id.command.js";
5
7
  import { partialUpdateById as partialUpdateByIdCommand } from "./commands/postgres-partial-update-by-id.command.js";
6
8
  import { update as updateCommand } from "./commands/postgres-update.command.js";
7
- import { deleteById as deleteByIdCommand } from "./commands/postgres-delete-by-id.command.js";
8
- import { deleteMany as deleteManyCommand } from "./commands/postgres-delete-many.command.js";
9
- import { findOne as findOneQuery } from "./queries/postgres-find-one.query.js";
10
9
  import { find as findQuery } from "./queries/postgres-find.query.js";
11
- import { getAll as getAllQuery } from "./queries/postgres-get-all.query.js";
10
+ import { findOne as findOneQuery } from "./queries/postgres-find-one.query.js";
12
11
  import { get as getQuery } from "./queries/postgres-get.query.js";
12
+ import { getAll as getAllQuery } from "./queries/postgres-get-all.query.js";
13
13
  import { getById as getByIdQuery } from "./queries/postgres-get-by-id.query.js";
14
14
  import { getCount as getCountQuery } from "./queries/postgres-get-count.query.js";
15
+ import { convertKeysToCamelCase, convertKeysToSnakeCase, } from "./utils/convert-keys.util.js";
15
16
  import { convertNullToUndefined } from "./utils/convert-null-to-undefined.util.js";
16
- import { convertKeysToSnakeCase, convertKeysToCamelCase } from "./utils/convert-keys.util.js";
17
17
  export class PostgresDatabase {
18
18
  connection;
19
19
  constructor(connection) {
@@ -72,7 +72,6 @@ export class PostgresDatabase {
72
72
  const now = new Date();
73
73
  let query = `
74
74
  SELECT DISTINCT
75
- ur."user_id" as "userId",
76
75
  r."name" as "role",
77
76
  f."name" as "feature",
78
77
  a."config",
@@ -96,7 +95,6 @@ export class PostgresDatabase {
96
95
  const result = await this.connection.query(query, values);
97
96
  const authorizations = [];
98
97
  for (const row of result.rows) {
99
- const userId = row.userId;
100
98
  authorizations.push({
101
99
  _id: row._id,
102
100
  _orgId: row._orgId,
@@ -1,4 +1,4 @@
1
- export * from './error-handler.js';
2
- export * from './is-authorized.js';
3
- export * from './ensure-user-context.js';
4
- export * from './request-lifecycle.js';
1
+ export * from "./ensure-user-context.js";
2
+ export * from "./error-handler.js";
3
+ export * from "./is-authorized.js";
4
+ export * from "./request-lifecycle.js";
@@ -1,4 +1,4 @@
1
- export * from './error-handler.js';
2
- export * from './is-authorized.js';
3
- export * from './ensure-user-context.js';
4
- export * from './request-lifecycle.js';
1
+ export * from "./ensure-user-context.js";
2
+ export * from "./error-handler.js";
3
+ export * from "./is-authorized.js";
4
+ export * from "./request-lifecycle.js";
@@ -1,3 +1,3 @@
1
- import { Request, Response, NextFunction } from 'express';
2
- declare const isAuthorized: (allowedFeatures?: string[]) => (req: Request, res: Response, next: NextFunction) => void;
1
+ import type { NextFunction, Request, Response } from "express";
2
+ declare const isAuthorized: (allowedFeatures?: string[]) => (req: Request, _res: Response, next: NextFunction) => void;
3
3
  export { isAuthorized };
@@ -1,13 +1,13 @@
1
- import { UserContextSpec } from '@loomcore/common/models';
2
- import { UnauthenticatedError, UnauthorizedError } from '../errors/index.js';
3
- import { JwtService } from '../services/index.js';
4
- import { config } from '../config/index.js';
1
+ import { UserContextSpec } from "@loomcore/common/models";
2
+ import jwt from "jsonwebtoken";
3
+ import { UnauthenticatedError, UnauthorizedError } from "../errors/index.js";
4
+ import { getAuthConfig } from "../utils/auth/get-auth-config.util.js";
5
5
  const isAuthorized = (allowedFeatures) => {
6
- return (req, res, next) => {
6
+ return (req, _res, next) => {
7
7
  let token = null;
8
8
  if (req.headers?.authorization) {
9
- let authHeader = req.headers.authorization;
10
- const authHeaderArray = authHeader.split('Bearer ');
9
+ const authHeader = req.headers.authorization;
10
+ const authHeaderArray = authHeader.split("Bearer ");
11
11
  if (authHeaderArray?.length > 1) {
12
12
  token = authHeaderArray[1];
13
13
  }
@@ -15,15 +15,16 @@ const isAuthorized = (allowedFeatures) => {
15
15
  if (!token) {
16
16
  throw new UnauthenticatedError();
17
17
  }
18
+ const authConfig = getAuthConfig();
18
19
  try {
19
- const rawPayload = JwtService.verify(token, config.auth?.clientSecret || '');
20
+ const rawPayload = jwt.verify(token, authConfig.clientSecret);
20
21
  const userContext = UserContextSpec.decode(rawPayload);
21
22
  req.userContext = userContext;
22
- if (userContext.authorizations.some(authorization => authorization.feature === 'admin')) {
23
+ if (userContext.authorizations.some((authorization) => authorization.feature === "admin")) {
23
24
  next();
24
25
  }
25
- else if (allowedFeatures && allowedFeatures.length) {
26
- if (!userContext.authorizations.some(authorization => allowedFeatures?.includes(authorization.feature))) {
26
+ else if (allowedFeatures?.length) {
27
+ if (!userContext.authorizations.some((authorization) => allowedFeatures.includes(authorization.feature))) {
27
28
  throw new UnauthorizedError();
28
29
  }
29
30
  next();
@@ -33,6 +34,7 @@ const isAuthorized = (allowedFeatures) => {
33
34
  }
34
35
  }
35
36
  catch (err) {
37
+ console.error(err);
36
38
  throw new UnauthenticatedError();
37
39
  }
38
40
  };
@@ -1,4 +1,4 @@
1
- import { DbType } from "../databases/db-type.type.js";
1
+ import type { DbType } from "../databases/db-type.type.js";
2
2
  export interface IAppConfig {
3
3
  dbType: DbType;
4
4
  isMultiTenant: boolean;
@@ -1,8 +1,8 @@
1
- import { IAppConfig } from "./app-config.interface.js";
2
- import { IAuthConfig } from "./auth-config.interface.js";
3
- import { IDatabaseConfig } from "./database-config.interface.js";
4
- import { IEmailConfig } from "./email-config.interface.js";
5
- import { IEmailClient } from "./email-client.interface.js";
1
+ import type { IAppConfig } from "./app-config.interface.js";
2
+ import type { IAuthConfig } from "./auth-config.interface.js";
3
+ import type { IDatabaseConfig } from "./database-config.interface.js";
4
+ import type { IEmailClient } from "./email-client.interface.js";
5
+ import type { IEmailConfig } from "./email-config.interface.js";
6
6
  export interface IBaseApiConfig {
7
7
  app: IAppConfig;
8
8
  auth?: IAuthConfig;
@@ -1,6 +1,6 @@
1
- import { IAppConfig } from "./app-config.interface.js";
2
- import { IDatabaseConfig } from "./database-config.interface.js";
3
- import { IEmailConfig } from "./email-config.interface.js";
1
+ import type { IAppConfig } from "./app-config.interface.js";
2
+ import type { IDatabaseConfig } from "./database-config.interface.js";
3
+ import type { IEmailConfig } from "./email-config.interface.js";
4
4
  export interface IInitialDbMigrationConfig {
5
5
  env: string;
6
6
  app: IAppConfig;
@@ -12,6 +12,7 @@ export interface IInitialDbMigrationConfig {
12
12
  multiTenant: {
13
13
  metaOrgName: string;
14
14
  metaOrgCode: string;
15
+ metaOrgDomain: string;
15
16
  };
16
17
  email?: IEmailConfig;
17
18
  }
@@ -1,4 +1,4 @@
1
- import { IEntity } from "@loomcore/common/models";
1
+ import type { IEntity } from "@loomcore/common/models";
2
2
  import type { AppIdType } from "@loomcore/common/types";
3
3
  export interface IRefreshToken extends IEntity {
4
4
  token: string;
@@ -1,13 +1,12 @@
1
1
  import { entityUtils } from "@loomcore/common/utils";
2
- import { TypeboxIsoDate, getIdSchema } from "@loomcore/common/validation";
2
+ import { getIdSchema, TypeboxIsoDate } from "@loomcore/common/validation";
3
3
  import { Type } from "@sinclair/typebox";
4
- ;
5
4
  export const refreshTokenSchema = Type.Object({
6
5
  token: Type.String({ minLength: 1 }),
7
6
  deviceId: Type.String({ minLength: 1 }),
8
7
  userId: getIdSchema(),
9
8
  expiresOn: Type.Number(),
10
- created: TypeboxIsoDate({ title: 'Created Date' }),
11
- createdBy: getIdSchema()
9
+ created: TypeboxIsoDate({ title: "Created Date" }),
10
+ createdBy: getIdSchema(),
12
11
  });
13
12
  export const refreshTokenModelSpec = entityUtils.getModelSpec(refreshTokenSchema);
@@ -1,9 +1,9 @@
1
- import { ValueError } from '@sinclair/typebox/errors';
2
- import { IUserContext, IEntity, IPagedResult, IQueryOptions } from '@loomcore/common/models';
3
- import type { AppIdType } from '@loomcore/common/types';
4
- import { DeleteResult } from '../../databases/models/delete-result.js';
5
- import { Operation } from '../../databases/operations/operation.js';
6
- import { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from '../../controllers/types.js';
1
+ import type { IEntity, IPagedResult, IQueryOptions, IUserContext } from "@loomcore/common/models";
2
+ import type { AppIdType } from "@loomcore/common/types";
3
+ import type { ValueError } from "@sinclair/typebox/errors";
4
+ import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
5
+ import type { DeleteResult } from "../../databases/models/delete-result.js";
6
+ import type { Operation } from "../../databases/operations/operation.js";
7
7
  export interface IGenericApiService<T extends IEntity> {
8
8
  validate(doc: any, isPartial?: boolean): ValueError[] | null;
9
9
  validateMany(docs: any[], isPartial?: boolean): ValueError[] | null;
@@ -1,11 +1,11 @@
1
- import { ValueError } from '@sinclair/typebox/errors';
2
- import { IUserContext, IEntity, IQueryOptions, IPagedResult, IModelSpec } from '@loomcore/common/models';
3
- import type { AppIdType } from '@loomcore/common/types';
4
- import { IGenericApiService } from './generic-api-service.interface.js';
5
- import { Operation } from '../../databases/operations/operation.js';
6
- import { DeleteResult } from '../../databases/models/delete-result.js';
7
- import { IDatabase } from '../../databases/models/index.js';
8
- import { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from '../../controllers/types.js';
1
+ import { type IEntity, type IModelSpec, type IPagedResult, type IQueryOptions, type IUserContext } from "@loomcore/common/models";
2
+ import type { AppIdType } from "@loomcore/common/types";
3
+ import type { ValueError } from "@sinclair/typebox/errors";
4
+ import type { PostProcessEntityCustomFunction, PrepareQueryCustomFunction } from "../../controllers/types.js";
5
+ import type { DeleteResult } from "../../databases/models/delete-result.js";
6
+ import type { IDatabase } from "../../databases/models/index.js";
7
+ import type { Operation } from "../../databases/operations/operation.js";
8
+ import type { IGenericApiService } from "./generic-api-service.interface.js";
9
9
  export declare class GenericApiService<T extends IEntity> implements IGenericApiService<T> {
10
10
  protected database: IDatabase;
11
11
  protected pluralResourceName: string;