@loomcore/api 0.2.3 → 0.2.5

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 (35) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +77 -77
  3. package/dist/__tests__/common-test.utils.d.ts +12 -12
  4. package/dist/__tests__/common-test.utils.js +96 -90
  5. package/dist/__tests__/postgres.test-database.d.ts +2 -2
  6. package/dist/__tests__/postgres.test-database.js +16 -15
  7. package/dist/config/base-api-config.d.ts +2 -2
  8. package/dist/config/base-api-config.js +17 -10
  9. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -2
  10. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +185 -128
  11. package/dist/databases/postgres/migrations/postgres-initial-schema.d.ts +2 -2
  12. package/dist/databases/postgres/migrations/postgres-initial-schema.js +75 -64
  13. package/dist/services/auth.service.d.ts +7 -7
  14. package/dist/services/auth.service.js +71 -58
  15. package/dist/services/email.service.js +5 -5
  16. package/dist/services/jwt.service.js +2 -2
  17. package/dist/services/multi-tenant-api.service.d.ts +4 -4
  18. package/dist/services/multi-tenant-api.service.js +8 -8
  19. package/dist/services/organization.service.d.ts +4 -4
  20. package/dist/services/organization.service.js +12 -8
  21. package/dist/services/password-reset-token.service.d.ts +4 -4
  22. package/dist/services/password-reset-token.service.js +13 -9
  23. package/dist/services/person.service.d.ts +4 -4
  24. package/dist/services/person.service.js +3 -3
  25. package/dist/services/tenant-query-decorator.d.ts +1 -1
  26. package/dist/services/tenant-query-decorator.js +17 -11
  27. package/dist/services/user.service.d.ts +6 -6
  28. package/dist/services/user.service.js +7 -7
  29. package/dist/services/utils/audit-for-create.util.d.ts +1 -1
  30. package/dist/services/utils/audit-for-update.util.d.ts +1 -1
  31. package/dist/services/utils/audit-for-update.util.js +0 -1
  32. package/dist/services/utils/getUserContextAuthorizations.util.d.ts +2 -2
  33. package/dist/services/utils/strip-sender-provided-system-properties.util.d.ts +1 -1
  34. package/dist/services/utils/strip-sender-provided-system-properties.util.js +5 -3
  35. package/package.json +1 -1
@@ -1,18 +1,17 @@
1
- import moment from 'moment';
2
- import crypto from 'crypto';
3
- import { EmptyUserContext, passwordValidator, UserSpec, getSystemUserContext } from '@loomcore/common/models';
4
- import { entityUtils } from '@loomcore/common/utils';
5
- import { BadRequestError, ServerError } from '../errors/index.js';
6
- import { JwtService, EmailService } from './index.js';
7
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
8
- import { MultiTenantApiService } from './multi-tenant-api.service.js';
9
- import { PasswordResetTokenService } from './password-reset-token.service.js';
10
- import { OrganizationService } from './organization.service.js';
11
- import { passwordUtils } from '../utils/index.js';
12
- import { config } from '../config/index.js';
13
- import { refreshTokenModelSpec } from '../models/refresh-token.model.js';
14
- import { getUserContextAuthorizations } from './utils/getUserContextAuthorizations.util.js';
15
- import { PersonService } from './person.service.js';
1
+ import crypto from "node:crypto";
2
+ import { EmptyUserContext, getSystemUserContext, passwordValidator, UserSpec, } from "@loomcore/common/models";
3
+ import { entityUtils } from "@loomcore/common/utils";
4
+ import moment from "moment";
5
+ import { config } from "../config/index.js";
6
+ import { BadRequestError, ServerError } from "../errors/index.js";
7
+ import { refreshTokenModelSpec, } from "../models/refresh-token.model.js";
8
+ import { passwordUtils } from "../utils/index.js";
9
+ import { EmailService, JwtService } from "./index.js";
10
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
11
+ import { OrganizationService } from "./organization.service.js";
12
+ import { PasswordResetTokenService } from "./password-reset-token.service.js";
13
+ import { PersonService } from "./person.service.js";
14
+ import { getUserContextAuthorizations } from "./utils/getUserContextAuthorizations.util.js";
16
15
  export class AuthService extends MultiTenantApiService {
17
16
  refreshTokenService;
18
17
  passwordResetTokenService;
@@ -21,14 +20,14 @@ export class AuthService extends MultiTenantApiService {
21
20
  authConfig;
22
21
  personService;
23
22
  constructor(database) {
24
- super(database, 'users', 'user', UserSpec);
25
- this.refreshTokenService = new GenericApiService(database, 'refresh_tokens', 'refresh_token', refreshTokenModelSpec);
23
+ super(database, "users", "user", UserSpec);
24
+ this.refreshTokenService = new MultiTenantApiService(database, "refresh_tokens", "refresh_token", refreshTokenModelSpec);
26
25
  this.passwordResetTokenService = new PasswordResetTokenService(database);
27
26
  this.emailService = new EmailService();
28
27
  this.organizationService = new OrganizationService(database);
29
28
  this.personService = new PersonService(database);
30
29
  if (!config.auth) {
31
- throw new ServerError('Auth configuration is not set');
30
+ throw new ServerError("Auth configuration is not set");
32
31
  }
33
32
  this.authConfig = config.auth;
34
33
  }
@@ -37,19 +36,21 @@ export class AuthService extends MultiTenantApiService {
37
36
  const user = await this.getUserByEmail(lowerCaseEmail);
38
37
  const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
39
38
  if (!user) {
40
- throw new BadRequestError('Invalid Credentials');
39
+ throw new BadRequestError("Invalid Credentials");
41
40
  }
42
41
  const passwordsMatch = await passwordUtils.comparePasswords(user.password, password);
43
42
  if (!passwordsMatch) {
44
- throw new BadRequestError('Invalid Credentials');
43
+ throw new BadRequestError("Invalid Credentials");
45
44
  }
46
- const person = await this.personService.findOne(EmptyUserContext, { filters: { _id: { eq: user.personId } } });
45
+ const person = await this.personService.findOne(getSystemUserContext(), {
46
+ filters: { _id: { eq: user.personId } },
47
+ });
47
48
  const authorizations = await getUserContextAuthorizations(this.database, user);
48
49
  const userContext = {
49
50
  user: user,
50
51
  person: person ?? undefined,
51
52
  organization: organization ?? undefined,
52
- authorizations: authorizations
53
+ authorizations: authorizations,
53
54
  };
54
55
  const deviceId = this.getAndSetDeviceIdCookie(req, res);
55
56
  const loginResponse = await this.logUserIn(userContext, deviceId);
@@ -65,10 +66,9 @@ export class AuthService extends MultiTenantApiService {
65
66
  const tokenResponse = {
66
67
  accessToken,
67
68
  refreshToken: refreshTokenObject.token,
68
- expiresOn: accessTokenExpiresOn
69
+ expiresOn: accessTokenExpiresOn,
69
70
  };
70
- this.updateLastLoggedIn(userContext.user._id)
71
- .catch(err => console.log(`Error updating lastLoggedIn: ${err}`));
71
+ this.updateLastLoggedIn(userContext.user._id).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
72
72
  userContext.user = this.postProcessEntity(userContext, userContext.user);
73
73
  loginResponse = { tokens: tokenResponse, userContext };
74
74
  }
@@ -76,28 +76,31 @@ export class AuthService extends MultiTenantApiService {
76
76
  }
77
77
  async getUserByEmail(email) {
78
78
  const queryOptions = { filters: { email: { eq: email.toLowerCase() } } };
79
- const rawUser = await this.database.findOne(queryOptions, 'users');
79
+ const rawUser = await this.database.findOne(queryOptions, "users");
80
80
  if (!rawUser) {
81
81
  return null;
82
82
  }
83
83
  return this.database.postProcessEntity(rawUser, this.modelSpec.fullSchema);
84
84
  }
85
85
  async createUser(userContext, user, person) {
86
- if (userContext.user._id === 'system') {
87
- if (config.app.isMultiTenant && userContext.organization?._id !== user._orgId) {
88
- throw new BadRequestError('User is not authorized to create a user in this organization');
86
+ if (userContext.user._id === "system") {
87
+ if (config.app.isMultiTenant &&
88
+ userContext.organization?._id !== user._orgId) {
89
+ throw new BadRequestError("User is not authorized to create a user in this organization");
89
90
  }
90
91
  if (user._orgId) {
91
- const org = await this.organizationService.findOne(userContext, { filters: { _id: { eq: user._orgId } } });
92
+ const org = await this.organizationService.findOne(userContext, {
93
+ filters: { _id: { eq: user._orgId } },
94
+ });
92
95
  if (!org) {
93
- throw new BadRequestError('The specified organization does not exist');
96
+ throw new BadRequestError("The specified organization does not exist");
94
97
  }
95
98
  }
96
99
  }
97
100
  if (user.email) {
98
101
  const existingUser = await this.getUserByEmail(user.email);
99
102
  if (existingUser) {
100
- throw new BadRequestError('A user with this email address already exists');
103
+ throw new BadRequestError("A user with this email address already exists");
101
104
  }
102
105
  }
103
106
  let personId = user.personId;
@@ -110,7 +113,7 @@ export class AuthService extends MultiTenantApiService {
110
113
  personId = newPerson._id;
111
114
  }
112
115
  else {
113
- throw new ServerError('authService.createUser: Failed to create person');
116
+ throw new ServerError("authService.createUser: Failed to create person");
114
117
  }
115
118
  }
116
119
  user.personId = personId;
@@ -122,17 +125,19 @@ export class AuthService extends MultiTenantApiService {
122
125
  if (activeRefreshToken) {
123
126
  const systemUserContext = getSystemUserContext();
124
127
  const user = await this.getById(systemUserContext, activeRefreshToken.userId);
125
- const person = await this.personService.findOne(EmptyUserContext, { filters: { _id: { eq: user?.personId } } });
128
+ const person = await this.personService.findOne(systemUserContext, {
129
+ filters: { _id: { eq: user?.personId } },
130
+ });
126
131
  const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
127
132
  const authorizations = await getUserContextAuthorizations(this.database, user);
128
133
  let userContext = {
129
134
  user: user,
130
135
  person: person ?? undefined,
131
136
  organization: organization ?? undefined,
132
- authorizations: authorizations
137
+ authorizations: authorizations,
133
138
  };
134
139
  if (user.personId) {
135
- const person = await this.personService.getById(EmptyUserContext, user.personId);
140
+ const person = await this.personService.getById(systemUserContext, user.personId);
136
141
  if (person) {
137
142
  userContext.person = person;
138
143
  }
@@ -147,7 +152,10 @@ export class AuthService extends MultiTenantApiService {
147
152
  return result;
148
153
  }
149
154
  async changePassword(userContext, queryObject, password) {
150
- const updates = { password: password, _lastPasswordChange: moment().utc().toDate() };
155
+ const updates = {
156
+ password: password,
157
+ _lastPasswordChange: moment().utc().toDate(),
158
+ };
151
159
  const updatedUsers = await super.update(userContext, queryObject, updates);
152
160
  const result = {
153
161
  success: true,
@@ -162,12 +170,12 @@ export class AuthService extends MultiTenantApiService {
162
170
  const tokenResponse = {
163
171
  accessToken,
164
172
  refreshToken: activeRefreshToken.token,
165
- expiresOn: accessTokenExpiresOn
173
+ expiresOn: accessTokenExpiresOn,
166
174
  };
167
175
  return tokenResponse;
168
176
  }
169
177
  async getActiveRefreshToken(refreshToken, deviceId) {
170
- const refreshTokenResult = await this.refreshTokenService.findOne(EmptyUserContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
178
+ const refreshTokenResult = await this.refreshTokenService.findOne(getSystemUserContext(), { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
171
179
  let activeRefreshToken = null;
172
180
  if (refreshTokenResult) {
173
181
  const now = Date.now();
@@ -187,10 +195,10 @@ export class AuthService extends MultiTenantApiService {
187
195
  userId,
188
196
  expiresOn: expiresOn,
189
197
  created: moment().utc().toDate(),
190
- createdBy: userId
198
+ createdBy: userId,
191
199
  };
192
200
  const deleteResult = await this.deleteRefreshTokensForDevice(deviceId);
193
- const insertResult = await this.refreshTokenService.create(EmptyUserContext, newRefreshToken);
201
+ const insertResult = await this.refreshTokenService.create(getSystemUserContext(), newRefreshToken);
194
202
  return insertResult;
195
203
  }
196
204
  async sendResetPasswordEmail(emailAddress, clientBaseUrl) {
@@ -209,38 +217,42 @@ export class AuthService extends MultiTenantApiService {
209
217
  if (!retrievedPasswordResetToken) {
210
218
  throw new ServerError(`Unable to retrieve password reset token for email: ${lowerCaseEmail}`);
211
219
  }
212
- if (retrievedPasswordResetToken.token !== passwordResetToken || retrievedPasswordResetToken.expiresOn < Date.now()) {
213
- throw new BadRequestError('Invalid password reset token');
220
+ if (retrievedPasswordResetToken.token !== passwordResetToken ||
221
+ retrievedPasswordResetToken.expiresOn < Date.now()) {
222
+ throw new BadRequestError("Invalid password reset token");
214
223
  }
215
224
  const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
216
- entityUtils.handleValidationResult(validationErrors, 'AuthService.resetPassword');
225
+ entityUtils.handleValidationResult(validationErrors, "AuthService.resetPassword");
217
226
  const result = await this.changePassword(getSystemUserContext(), { email: lowerCaseEmail }, password);
218
227
  console.log(`password changed using forgot-password for email: ${lowerCaseEmail}`);
219
- await this.passwordResetTokenService.deleteById(EmptyUserContext, retrievedPasswordResetToken._id.toString());
228
+ await this.passwordResetTokenService.deleteById(getSystemUserContext(), retrievedPasswordResetToken._id.toString());
220
229
  console.log(`passwordResetToken deleted for email: ${lowerCaseEmail}`);
221
230
  return result;
222
231
  }
223
232
  deleteRefreshTokensForDevice(deviceId) {
224
- return this.refreshTokenService.deleteMany(EmptyUserContext, { filters: { deviceId: { eq: deviceId } } });
233
+ return this.refreshTokenService.deleteMany(getSystemUserContext(), {
234
+ filters: { deviceId: { eq: deviceId } },
235
+ });
225
236
  }
226
237
  generateJwt(userContext) {
227
238
  const jwtExpiryConfig = this.authConfig.jwtExpirationInSeconds;
228
- const jwtExpirationInSeconds = (typeof jwtExpiryConfig === 'string') ? parseInt(jwtExpiryConfig) : jwtExpiryConfig;
239
+ const jwtExpirationInSeconds = typeof jwtExpiryConfig === "string"
240
+ ? parseInt(jwtExpiryConfig)
241
+ : jwtExpiryConfig;
229
242
  const accessToken = JwtService.sign(userContext, this.authConfig.clientSecret, {
230
- expiresIn: jwtExpirationInSeconds
243
+ expiresIn: jwtExpirationInSeconds,
231
244
  });
232
245
  return accessToken;
233
246
  }
234
- ;
235
247
  generateRefreshToken() {
236
- return crypto.randomBytes(40).toString('hex');
248
+ return crypto.randomBytes(40).toString("hex");
237
249
  }
238
250
  generateDeviceId() {
239
- return crypto.randomBytes(40).toString('hex');
251
+ return crypto.randomBytes(40).toString("hex");
240
252
  }
241
253
  getAndSetDeviceIdCookie(req, res) {
242
254
  let isNewDeviceId = false;
243
- let deviceId = '';
255
+ let deviceId = "";
244
256
  const deviceIdFromCookie = this.getDeviceIdFromCookie(req);
245
257
  if (deviceIdFromCookie) {
246
258
  deviceId = deviceIdFromCookie;
@@ -252,14 +264,14 @@ export class AuthService extends MultiTenantApiService {
252
264
  if (isNewDeviceId) {
253
265
  const cookieOptions = {
254
266
  maxAge: this.authConfig.deviceIdCookieMaxAgeInDays * 24 * 60 * 60 * 1000,
255
- httpOnly: true
267
+ httpOnly: true,
256
268
  };
257
- res.cookie('deviceId', deviceId, cookieOptions);
269
+ res.cookie("deviceId", deviceId, cookieOptions);
258
270
  }
259
271
  return deviceId;
260
272
  }
261
273
  getDeviceIdFromCookie(req) {
262
- return req.cookies['deviceId'];
274
+ return req.cookies["deviceId"];
263
275
  }
264
276
  getExpiresOnFromSeconds(expiresInSeconds) {
265
277
  return Date.now() + expiresInSeconds * 1000;
@@ -283,11 +295,12 @@ export class AuthService extends MultiTenantApiService {
283
295
  }
284
296
  async updateLastLoggedIn(userId) {
285
297
  try {
286
- const updates = { _lastLoggedIn: moment().utc().toDate() };
298
+ const updates = {
299
+ _lastLoggedIn: moment().utc().toDate(),
300
+ };
287
301
  const systemUserContext = getSystemUserContext();
288
302
  await this.partialUpdateById(systemUserContext, userId, updates);
289
303
  }
290
- catch (error) {
291
- }
304
+ catch (error) { }
292
305
  }
293
306
  }
@@ -1,5 +1,5 @@
1
- import { ServerError } from '../errors/index.js';
2
- import { config } from '../config/index.js';
1
+ import { config } from "../config/index.js";
2
+ import { ServerError } from "../errors/index.js";
3
3
  export class EmailService {
4
4
  emailClient;
5
5
  constructor() {
@@ -7,7 +7,7 @@ export class EmailService {
7
7
  this.emailClient = config.thirdPartyClients.emailClient;
8
8
  }
9
9
  else {
10
- throw new ServerError('Email client is not available. Email client is not set in the config.');
10
+ throw new ServerError("Email client is not available. Email client is not set in the config.");
11
11
  }
12
12
  }
13
13
  async sendResetPasswordEmail(emailAddress, resetPasswordLink) {
@@ -16,8 +16,8 @@ export class EmailService {
16
16
  console.log(`Reset password email sent to ${emailAddress} with reset password link ${resetPasswordLink}`);
17
17
  }
18
18
  catch (error) {
19
- console.error('Error sending reset password email:', error);
20
- throw new ServerError('Error sending reset password email');
19
+ console.error("Error sending reset password email:", error);
20
+ throw new ServerError("Error sending reset password email");
21
21
  }
22
22
  }
23
23
  }
@@ -1,11 +1,11 @@
1
- import jwt from 'jsonwebtoken';
1
+ import jwt from "jsonwebtoken";
2
2
  export class JwtService {
3
3
  static sign(payload, secret, options) {
4
4
  return jwt.sign(payload, secret, options);
5
5
  }
6
6
  static verify(token, secret) {
7
7
  if (!secret) {
8
- throw new Error('JWT secret is required for verification');
8
+ throw new Error("JWT secret is required for verification");
9
9
  }
10
10
  try {
11
11
  const decoded = jwt.verify(token, secret);
@@ -1,7 +1,7 @@
1
- import { IUserContext, IEntity, IQueryOptions, IModelSpec } from '@loomcore/common/models';
2
- import { Operation } from '../databases/operations/operation.js';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
4
- import { IDatabase } from '../databases/models/index.js';
1
+ import type { IEntity, IModelSpec, IQueryOptions, IUserContext } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../databases/models/index.js";
3
+ import type { Operation } from "../databases/operations/operation.js";
4
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
5
5
  export declare class MultiTenantApiService<T extends IEntity> extends GenericApiService<T> {
6
6
  private tenantDecorator?;
7
7
  constructor(database: IDatabase, pluralResourceName: string, singularResourceName: string, modelSpec: IModelSpec);
@@ -1,7 +1,7 @@
1
- import { TenantQueryDecorator } from './tenant-query-decorator.js';
2
- import { BadRequestError } from '../errors/bad-request.error.js';
3
- import { config } from '../config/base-api-config.js';
4
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
1
+ import { config } from "../config/base-api-config.js";
2
+ import { BadRequestError } from "../errors/bad-request.error.js";
3
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
4
+ import { TenantQueryDecorator } from "./tenant-query-decorator.js";
5
5
  export class MultiTenantApiService extends GenericApiService {
6
6
  tenantDecorator;
7
7
  constructor(database, pluralResourceName, singularResourceName, modelSpec) {
@@ -11,11 +11,11 @@ export class MultiTenantApiService extends GenericApiService {
11
11
  }
12
12
  }
13
13
  prepareQuery(userContext, queryOptions, operations) {
14
- if (!config?.app?.isMultiTenant || userContext?.user?._id === 'system') {
14
+ if (!config?.app?.isMultiTenant || userContext?.user?._id === "system") {
15
15
  return super.prepareQuery(userContext, queryOptions, operations);
16
16
  }
17
17
  if (!userContext || !userContext.organization?._id) {
18
- throw new BadRequestError('A valid userContext was not provided to MultiTenantApiService.prepareQuery');
18
+ throw new BadRequestError("A valid userContext was not provided to MultiTenantApiService.prepareQuery");
19
19
  }
20
20
  const queryObject = this.tenantDecorator.applyTenantToQuery(userContext, queryOptions, this.pluralResourceName);
21
21
  return { queryObject, operations };
@@ -25,10 +25,10 @@ export class MultiTenantApiService extends GenericApiService {
25
25
  return super.preProcessEntity(userContext, entity, isCreate, allowId);
26
26
  }
27
27
  if (!userContext || !userContext.organization?._id) {
28
- throw new BadRequestError('A valid userContext was not provided to MultiTenantApiService.prepareEntity');
28
+ throw new BadRequestError("A valid userContext was not provided to MultiTenantApiService.prepareEntity");
29
29
  }
30
30
  const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate, allowId);
31
- if (isCreate && userContext.user._id !== 'system') {
31
+ if (isCreate && userContext.user._id !== "system") {
32
32
  preparedEntity._orgId = userContext.organization?._id;
33
33
  }
34
34
  return preparedEntity;
@@ -1,7 +1,7 @@
1
- import { IOrganization, IUserContext } from '@loomcore/common/models';
2
- import { IDatabase } from '../databases/models/database.interface.js';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
4
- import { AppIdType } from '@loomcore/common/types';
1
+ import { type IOrganization, type IUserContext } from "@loomcore/common/models";
2
+ import type { AppIdType } from "@loomcore/common/types";
3
+ import type { IDatabase } from "../databases/models/index.js";
4
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
5
5
  export declare class OrganizationService extends GenericApiService<IOrganization> {
6
6
  constructor(database: IDatabase);
7
7
  preProcessEntity(userContext: IUserContext, entity: Partial<IOrganization>, isCreate: boolean, allowId?: boolean): Promise<Partial<IOrganization>>;
@@ -1,18 +1,18 @@
1
- import { OrganizationSpec } from '@loomcore/common/models';
2
- import { BadRequestError } from '../errors/index.js';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
1
+ import { OrganizationSpec, } from "@loomcore/common/models";
2
+ import { BadRequestError } from "../errors/index.js";
3
+ import { GenericApiService } from "./generic-api-service/generic-api.service.js";
4
4
  export class OrganizationService extends GenericApiService {
5
5
  constructor(database) {
6
- super(database, 'organizations', 'organization', OrganizationSpec);
6
+ super(database, "organizations", "organization", OrganizationSpec);
7
7
  }
8
8
  async preProcessEntity(userContext, entity, isCreate, allowId = true) {
9
9
  if (isCreate) {
10
10
  const metaOrg = await this.getMetaOrg(userContext);
11
11
  if (metaOrg && entity.isMetaOrg) {
12
- throw new BadRequestError('Meta organization already exists');
12
+ throw new BadRequestError("Meta organization already exists");
13
13
  }
14
14
  if (metaOrg && userContext.organization?._id !== metaOrg._id) {
15
- throw new BadRequestError('User is not authorized to create an organization');
15
+ throw new BadRequestError("User is not authorized to create an organization");
16
16
  }
17
17
  }
18
18
  const result = await super.preProcessEntity(userContext, entity, isCreate, allowId);
@@ -23,7 +23,9 @@ export class OrganizationService extends GenericApiService {
23
23
  return org?.authToken ?? null;
24
24
  }
25
25
  async validateRepoAuthToken(userContext, orgCode, authToken) {
26
- const org = await this.findOne(userContext, { filters: { code: { eq: orgCode } } });
26
+ const org = await this.findOne(userContext, {
27
+ filters: { code: { eq: orgCode } },
28
+ });
27
29
  if (!org) {
28
30
  return null;
29
31
  }
@@ -31,7 +33,9 @@ export class OrganizationService extends GenericApiService {
31
33
  return orgId;
32
34
  }
33
35
  async getMetaOrg(userContext) {
34
- const org = await this.findOne(userContext, { filters: { isMetaOrg: { eq: true } } });
36
+ const org = await this.findOne(userContext, {
37
+ filters: { isMetaOrg: { eq: true } },
38
+ });
35
39
  return org;
36
40
  }
37
41
  }
@@ -1,7 +1,7 @@
1
- import { IPasswordResetToken } from '@loomcore/common/models';
2
- import { IDatabase } from '../databases/models/index.js';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
4
- export declare class PasswordResetTokenService extends GenericApiService<IPasswordResetToken> {
1
+ import { type IPasswordResetToken } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../databases/models/index.js";
3
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
4
+ export declare class PasswordResetTokenService extends MultiTenantApiService<IPasswordResetToken> {
5
5
  constructor(database: IDatabase);
6
6
  createPasswordResetToken(email: string, expiresOn: number): Promise<IPasswordResetToken | null>;
7
7
  getByEmail(email: string): Promise<IPasswordResetToken | null>;
@@ -1,21 +1,25 @@
1
- import crypto from 'crypto';
2
- import { EmptyUserContext, PasswordResetTokenSpec } from '@loomcore/common/models';
3
- import { GenericApiService } from './generic-api-service/generic-api.service.js';
4
- export class PasswordResetTokenService extends GenericApiService {
1
+ import crypto from "node:crypto";
2
+ import { getSystemUserContext, PasswordResetTokenSpec, } from "@loomcore/common/models";
3
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
4
+ export class PasswordResetTokenService extends MultiTenantApiService {
5
5
  constructor(database) {
6
- super(database, 'password_reset_tokens', 'password_reset_token', PasswordResetTokenSpec);
6
+ super(database, "password_reset_tokens", "password_reset_token", PasswordResetTokenSpec);
7
7
  }
8
8
  async createPasswordResetToken(email, expiresOn) {
9
9
  const lowerCaseEmail = email.toLowerCase();
10
- await this.deleteMany(EmptyUserContext, { filters: { email: { eq: lowerCaseEmail } } });
10
+ await this.deleteMany(getSystemUserContext(), {
11
+ filters: { email: { eq: lowerCaseEmail } },
12
+ });
11
13
  const passwordResetToken = {
12
14
  email: lowerCaseEmail,
13
- token: crypto.randomBytes(40).toString('hex'),
15
+ token: crypto.randomBytes(40).toString("hex"),
14
16
  expiresOn: expiresOn,
15
17
  };
16
- return super.create(EmptyUserContext, passwordResetToken);
18
+ return super.create(getSystemUserContext(), passwordResetToken);
17
19
  }
18
20
  async getByEmail(email) {
19
- return await super.findOne(EmptyUserContext, { filters: { email: { eq: email.toLowerCase() } } });
21
+ return await super.findOne(getSystemUserContext(), {
22
+ filters: { email: { eq: email.toLowerCase() } },
23
+ });
20
24
  }
21
25
  }
@@ -1,6 +1,6 @@
1
- import { IPersonModel } from "@loomcore/common/models";
2
- import { IDatabase } from "../databases/index.js";
3
- import { GenericApiService } from "./generic-api-service/generic-api.service.js";
4
- export declare class PersonService extends GenericApiService<IPersonModel> {
1
+ import { type IPersonModel } from "@loomcore/common/models";
2
+ import type { IDatabase } from "../databases/models/index.js";
3
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
4
+ export declare class PersonService extends MultiTenantApiService<IPersonModel> {
5
5
  constructor(database: IDatabase);
6
6
  }
@@ -1,7 +1,7 @@
1
1
  import { personModelSpec } from "@loomcore/common/models";
2
- import { GenericApiService } from "./generic-api-service/generic-api.service.js";
3
- export class PersonService extends GenericApiService {
2
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
3
+ export class PersonService extends MultiTenantApiService {
4
4
  constructor(database) {
5
- super(database, 'persons', 'person', personModelSpec);
5
+ super(database, "persons", "person", personModelSpec);
6
6
  }
7
7
  }
@@ -1,4 +1,4 @@
1
- import { IUserContext, IQueryOptions, IEntity } from '@loomcore/common/models';
1
+ import type { IEntity, IQueryOptions, IUserContext } from "@loomcore/common/models";
2
2
  export interface ITenantQueryOptions {
3
3
  orgIdField?: string;
4
4
  excludedCollections?: string[];
@@ -1,7 +1,7 @@
1
- import { ServerError } from '../errors/index.js';
1
+ import { ServerError } from "../errors/index.js";
2
2
  export const DEFAULT_TENANT_OPTIONS = {
3
- orgIdField: '_orgId',
4
- excludedCollections: []
3
+ orgIdField: "_orgId",
4
+ excludedCollections: [],
5
5
  };
6
6
  export class TenantQueryDecorator {
7
7
  options;
@@ -13,12 +13,18 @@ export class TenantQueryDecorator {
13
13
  const shouldApplyTenantFilter = !this.options.excludedCollections?.includes(collectionName) &&
14
14
  userContext?.organization?._id;
15
15
  if (shouldApplyTenantFilter) {
16
- const orgIdField = this.options.orgIdField || '_orgId';
17
- result = { ...queryObject, filters: { ...queryObject.filters, [orgIdField]: { eq: userContext.organization?._id } } };
16
+ const orgIdField = this.options.orgIdField || "_orgId";
17
+ result = {
18
+ ...queryObject,
19
+ filters: {
20
+ ...queryObject.filters,
21
+ [orgIdField]: { eq: userContext.organization?._id },
22
+ },
23
+ };
18
24
  }
19
25
  else if (!userContext?.organization?._id) {
20
26
  if (!this.options.excludedCollections?.includes(collectionName)) {
21
- throw new ServerError('No _orgId found in userContext');
27
+ throw new ServerError("No _orgId found in userContext");
22
28
  }
23
29
  }
24
30
  return result;
@@ -28,7 +34,7 @@ export class TenantQueryDecorator {
28
34
  const shouldApplyTenantFilter = !this.options.excludedCollections?.includes(collectionName);
29
35
  if (shouldApplyTenantFilter) {
30
36
  if (!userContext?.organization?._id) {
31
- throw new ServerError('userContext must have an _orgId property to apply tenant filtering');
37
+ throw new ServerError("userContext must have an _orgId property to apply tenant filtering");
32
38
  }
33
39
  if (!result.filters) {
34
40
  result.filters = {};
@@ -43,20 +49,20 @@ export class TenantQueryDecorator {
43
49
  const shouldApplyTenantFilter = !this.options.excludedCollections?.includes(collectionName) &&
44
50
  userContext?.organization?._id;
45
51
  if (shouldApplyTenantFilter) {
46
- const orgIdField = this.options.orgIdField || '_orgId';
52
+ const orgIdField = this.options.orgIdField || "_orgId";
47
53
  result = {
48
54
  ...entity,
49
- [orgIdField]: userContext.organization?._id
55
+ [orgIdField]: userContext.organization?._id,
50
56
  };
51
57
  }
52
58
  else if (!userContext?.organization?._id) {
53
59
  if (!this.options.excludedCollections?.includes(collectionName)) {
54
- throw new ServerError('No _orgId found in userContext');
60
+ throw new ServerError("No _orgId found in userContext");
55
61
  }
56
62
  }
57
63
  return result;
58
64
  }
59
65
  getOrgIdField() {
60
- return this.options.orgIdField || '_orgId';
66
+ return this.options.orgIdField || "_orgId";
61
67
  }
62
68
  }
@@ -1,9 +1,9 @@
1
- import { IUser, IUserContext } from '@loomcore/common/models';
2
- import type { AppIdType } from '@loomcore/common/types';
3
- import { MultiTenantApiService } from './index.js';
4
- import { IDatabase } from '../databases/models/index.js';
1
+ import { type IUser, type IUserContext } from "@loomcore/common/models";
2
+ import type { AppIdType } from "@loomcore/common/types";
3
+ import type { IDatabase } from "../databases/models/index.js";
4
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
5
5
  export declare class UserService extends MultiTenantApiService<IUser> {
6
6
  constructor(database: IDatabase);
7
- fullUpdateById(userContext: IUserContext, id: AppIdType, entity: IUser): Promise<IUser>;
8
- preProcessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, allowId?: boolean): Promise<Partial<IUser>>;
7
+ fullUpdateById(_userContext: IUserContext, _id: AppIdType, _entity: IUser): Promise<IUser>;
8
+ preProcessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, _allowId?: boolean): Promise<Partial<IUser>>;
9
9
  }
@@ -1,14 +1,14 @@
1
- import { UserSpec } from '@loomcore/common/models';
2
- import { MultiTenantApiService } from './index.js';
3
- import { ServerError } from '../errors/index.js';
1
+ import { UserSpec, } from "@loomcore/common/models";
2
+ import { ServerError } from "../errors/index.js";
3
+ import { MultiTenantApiService } from "./multi-tenant-api.service.js";
4
4
  export class UserService extends MultiTenantApiService {
5
5
  constructor(database) {
6
- super(database, 'users', 'user', UserSpec);
6
+ super(database, "users", "user", UserSpec);
7
7
  }
8
- async fullUpdateById(userContext, id, entity) {
9
- throw new ServerError('Cannot full update a user. Either use PATCH or /auth/change-password to update password.');
8
+ async fullUpdateById(_userContext, _id, _entity) {
9
+ throw new ServerError("Cannot full update a user. Either use PATCH or /auth/change-password to update password.");
10
10
  }
11
- async preProcessEntity(userContext, entity, isCreate, allowId = false) {
11
+ async preProcessEntity(userContext, entity, isCreate, _allowId = false) {
12
12
  const preparedEntity = await super.preProcessEntity(userContext, entity, isCreate);
13
13
  if (preparedEntity.email) {
14
14
  preparedEntity.email = preparedEntity.email.toLowerCase();