@loomcore/api 0.2.4 → 0.2.6

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 (39) hide show
  1. package/dist/__tests__/common-test.utils.d.ts +12 -12
  2. package/dist/__tests__/common-test.utils.js +94 -101
  3. package/dist/__tests__/postgres.test-database.d.ts +2 -2
  4. package/dist/__tests__/postgres.test-database.js +16 -15
  5. package/dist/__tests__/test-objects.d.ts +1 -6
  6. package/dist/__tests__/test-objects.js +34 -73
  7. package/dist/config/base-api-config.d.ts +2 -2
  8. package/dist/config/base-api-config.js +17 -10
  9. package/dist/controllers/auth.controller.d.ts +3 -5
  10. package/dist/controllers/auth.controller.js +22 -26
  11. package/dist/controllers/index.d.ts +10 -11
  12. package/dist/controllers/index.js +10 -11
  13. package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -2
  14. package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +185 -128
  15. package/dist/services/auth.service.d.ts +8 -9
  16. package/dist/services/auth.service.js +66 -82
  17. package/dist/services/email.service.js +5 -5
  18. package/dist/services/jwt.service.js +2 -2
  19. package/dist/services/multi-tenant-api.service.d.ts +4 -4
  20. package/dist/services/multi-tenant-api.service.js +8 -8
  21. package/dist/services/organization.service.d.ts +4 -4
  22. package/dist/services/organization.service.js +12 -8
  23. package/dist/services/password-reset-token.service.d.ts +4 -4
  24. package/dist/services/password-reset-token.service.js +13 -9
  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 +5 -2
  36. package/dist/controllers/persons.controller.d.ts +0 -8
  37. package/dist/controllers/persons.controller.js +0 -18
  38. package/dist/services/person.service.d.ts +0 -6
  39. package/dist/services/person.service.js +0 -7
@@ -1,34 +1,30 @@
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 { getUserContextAuthorizations } from "./utils/getUserContextAuthorizations.util.js";
16
14
  export class AuthService extends MultiTenantApiService {
17
15
  refreshTokenService;
18
16
  passwordResetTokenService;
19
17
  emailService;
20
18
  organizationService;
21
19
  authConfig;
22
- personService;
23
20
  constructor(database) {
24
- super(database, 'users', 'user', UserSpec);
25
- this.refreshTokenService = new GenericApiService(database, 'refresh_tokens', 'refresh_token', refreshTokenModelSpec);
21
+ super(database, "users", "user", UserSpec);
22
+ this.refreshTokenService = new MultiTenantApiService(database, "refresh_tokens", "refresh_token", refreshTokenModelSpec);
26
23
  this.passwordResetTokenService = new PasswordResetTokenService(database);
27
24
  this.emailService = new EmailService();
28
25
  this.organizationService = new OrganizationService(database);
29
- this.personService = new PersonService(database);
30
26
  if (!config.auth) {
31
- throw new ServerError('Auth configuration is not set');
27
+ throw new ServerError("Auth configuration is not set");
32
28
  }
33
29
  this.authConfig = config.auth;
34
30
  }
@@ -37,19 +33,17 @@ export class AuthService extends MultiTenantApiService {
37
33
  const user = await this.getUserByEmail(lowerCaseEmail);
38
34
  const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
39
35
  if (!user) {
40
- throw new BadRequestError('Invalid Credentials');
36
+ throw new BadRequestError("Invalid Credentials");
41
37
  }
42
38
  const passwordsMatch = await passwordUtils.comparePasswords(user.password, password);
43
39
  if (!passwordsMatch) {
44
- throw new BadRequestError('Invalid Credentials');
40
+ throw new BadRequestError("Invalid Credentials");
45
41
  }
46
- const person = await this.personService.findOne(EmptyUserContext, { filters: { _id: { eq: user.personId } } });
47
42
  const authorizations = await getUserContextAuthorizations(this.database, user);
48
43
  const userContext = {
49
44
  user: user,
50
- person: person ?? undefined,
51
45
  organization: organization ?? undefined,
52
- authorizations: authorizations
46
+ authorizations: authorizations,
53
47
  };
54
48
  const deviceId = this.getAndSetDeviceIdCookie(req, res);
55
49
  const loginResponse = await this.logUserIn(userContext, deviceId);
@@ -65,10 +59,9 @@ export class AuthService extends MultiTenantApiService {
65
59
  const tokenResponse = {
66
60
  accessToken,
67
61
  refreshToken: refreshTokenObject.token,
68
- expiresOn: accessTokenExpiresOn
62
+ expiresOn: accessTokenExpiresOn,
69
63
  };
70
- this.updateLastLoggedIn(userContext.user._id)
71
- .catch(err => console.log(`Error updating lastLoggedIn: ${err}`));
64
+ this.updateLastLoggedIn(userContext.user._id).catch((err) => console.log(`Error updating lastLoggedIn: ${err}`));
72
65
  userContext.user = this.postProcessEntity(userContext, userContext.user);
73
66
  loginResponse = { tokens: tokenResponse, userContext };
74
67
  }
@@ -76,44 +69,33 @@ export class AuthService extends MultiTenantApiService {
76
69
  }
77
70
  async getUserByEmail(email) {
78
71
  const queryOptions = { filters: { email: { eq: email.toLowerCase() } } };
79
- const rawUser = await this.database.findOne(queryOptions, 'users');
72
+ const rawUser = await this.database.findOne(queryOptions, "users");
80
73
  if (!rawUser) {
81
74
  return null;
82
75
  }
83
76
  return this.database.postProcessEntity(rawUser, this.modelSpec.fullSchema);
84
77
  }
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');
78
+ async createUser(userContext, user) {
79
+ if (userContext.user._id === "system") {
80
+ if (config.app.isMultiTenant &&
81
+ userContext.organization?._id !== user._orgId) {
82
+ throw new BadRequestError("User is not authorized to create a user in this organization");
89
83
  }
90
84
  if (user._orgId) {
91
- const org = await this.organizationService.findOne(userContext, { filters: { _id: { eq: user._orgId } } });
85
+ const org = await this.organizationService.findOne(userContext, {
86
+ filters: { _id: { eq: user._orgId } },
87
+ });
92
88
  if (!org) {
93
- throw new BadRequestError('The specified organization does not exist');
89
+ throw new BadRequestError("The specified organization does not exist");
94
90
  }
95
91
  }
96
92
  }
97
93
  if (user.email) {
98
94
  const existingUser = await this.getUserByEmail(user.email);
99
95
  if (existingUser) {
100
- throw new BadRequestError('A user with this email address already exists');
96
+ throw new BadRequestError("A user with this email address already exists");
101
97
  }
102
98
  }
103
- let personId = user.personId;
104
- if (personId && person) {
105
- await this.personService.partialUpdateById(userContext, personId, person);
106
- }
107
- if (!personId && person) {
108
- const newPerson = await this.personService.create(userContext, person);
109
- if (newPerson) {
110
- personId = newPerson._id;
111
- }
112
- else {
113
- throw new ServerError('authService.createUser: Failed to create person');
114
- }
115
- }
116
- user.personId = personId;
117
99
  return await this.create(userContext, user);
118
100
  }
119
101
  async requestTokenUsingRefreshToken(refreshToken, deviceId) {
@@ -122,21 +104,13 @@ export class AuthService extends MultiTenantApiService {
122
104
  if (activeRefreshToken) {
123
105
  const systemUserContext = getSystemUserContext();
124
106
  const user = await this.getById(systemUserContext, activeRefreshToken.userId);
125
- const person = await this.personService.findOne(EmptyUserContext, { filters: { _id: { eq: user?.personId } } });
126
107
  const organization = await this.organizationService.findOne(EmptyUserContext, { filters: { _id: { eq: user?._orgId } } });
127
108
  const authorizations = await getUserContextAuthorizations(this.database, user);
128
- let userContext = {
109
+ const userContext = {
129
110
  user: user,
130
- person: person ?? undefined,
131
111
  organization: organization ?? undefined,
132
- authorizations: authorizations
112
+ authorizations: authorizations,
133
113
  };
134
- if (user.personId) {
135
- const person = await this.personService.getById(EmptyUserContext, user.personId);
136
- if (person) {
137
- userContext.person = person;
138
- }
139
- }
140
114
  tokens = await this.createNewTokens(userContext, activeRefreshToken);
141
115
  }
142
116
  return tokens;
@@ -147,7 +121,10 @@ export class AuthService extends MultiTenantApiService {
147
121
  return result;
148
122
  }
149
123
  async changePassword(userContext, queryObject, password) {
150
- const updates = { password: password, _lastPasswordChange: moment().utc().toDate() };
124
+ const updates = {
125
+ password: password,
126
+ _lastPasswordChange: moment().utc().toDate(),
127
+ };
151
128
  const updatedUsers = await super.update(userContext, queryObject, updates);
152
129
  const result = {
153
130
  success: true,
@@ -162,12 +139,12 @@ export class AuthService extends MultiTenantApiService {
162
139
  const tokenResponse = {
163
140
  accessToken,
164
141
  refreshToken: activeRefreshToken.token,
165
- expiresOn: accessTokenExpiresOn
142
+ expiresOn: accessTokenExpiresOn,
166
143
  };
167
144
  return tokenResponse;
168
145
  }
169
146
  async getActiveRefreshToken(refreshToken, deviceId) {
170
- const refreshTokenResult = await this.refreshTokenService.findOne(EmptyUserContext, { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
147
+ const refreshTokenResult = await this.refreshTokenService.findOne(getSystemUserContext(), { filters: { token: { eq: refreshToken }, deviceId: { eq: deviceId } } });
171
148
  let activeRefreshToken = null;
172
149
  if (refreshTokenResult) {
173
150
  const now = Date.now();
@@ -187,10 +164,10 @@ export class AuthService extends MultiTenantApiService {
187
164
  userId,
188
165
  expiresOn: expiresOn,
189
166
  created: moment().utc().toDate(),
190
- createdBy: userId
167
+ createdBy: userId,
191
168
  };
192
- const deleteResult = await this.deleteRefreshTokensForDevice(deviceId);
193
- const insertResult = await this.refreshTokenService.create(EmptyUserContext, newRefreshToken);
169
+ await this.deleteRefreshTokensForDevice(deviceId);
170
+ const insertResult = await this.refreshTokenService.create(getSystemUserContext(), newRefreshToken);
194
171
  return insertResult;
195
172
  }
196
173
  async sendResetPasswordEmail(emailAddress, clientBaseUrl) {
@@ -209,38 +186,42 @@ export class AuthService extends MultiTenantApiService {
209
186
  if (!retrievedPasswordResetToken) {
210
187
  throw new ServerError(`Unable to retrieve password reset token for email: ${lowerCaseEmail}`);
211
188
  }
212
- if (retrievedPasswordResetToken.token !== passwordResetToken || retrievedPasswordResetToken.expiresOn < Date.now()) {
213
- throw new BadRequestError('Invalid password reset token');
189
+ if (retrievedPasswordResetToken.token !== passwordResetToken ||
190
+ retrievedPasswordResetToken.expiresOn < Date.now()) {
191
+ throw new BadRequestError("Invalid password reset token");
214
192
  }
215
193
  const validationErrors = entityUtils.validate(UserSpec, { password: password }, true, passwordValidator);
216
- entityUtils.handleValidationResult(validationErrors, 'AuthService.resetPassword');
194
+ entityUtils.handleValidationResult(validationErrors, "AuthService.resetPassword");
217
195
  const result = await this.changePassword(getSystemUserContext(), { email: lowerCaseEmail }, password);
218
196
  console.log(`password changed using forgot-password for email: ${lowerCaseEmail}`);
219
- await this.passwordResetTokenService.deleteById(EmptyUserContext, retrievedPasswordResetToken._id.toString());
197
+ await this.passwordResetTokenService.deleteById(getSystemUserContext(), retrievedPasswordResetToken._id.toString());
220
198
  console.log(`passwordResetToken deleted for email: ${lowerCaseEmail}`);
221
199
  return result;
222
200
  }
223
201
  deleteRefreshTokensForDevice(deviceId) {
224
- return this.refreshTokenService.deleteMany(EmptyUserContext, { filters: { deviceId: { eq: deviceId } } });
202
+ return this.refreshTokenService.deleteMany(getSystemUserContext(), {
203
+ filters: { deviceId: { eq: deviceId } },
204
+ });
225
205
  }
226
206
  generateJwt(userContext) {
227
207
  const jwtExpiryConfig = this.authConfig.jwtExpirationInSeconds;
228
- const jwtExpirationInSeconds = (typeof jwtExpiryConfig === 'string') ? parseInt(jwtExpiryConfig) : jwtExpiryConfig;
208
+ const jwtExpirationInSeconds = typeof jwtExpiryConfig === "string"
209
+ ? parseInt(jwtExpiryConfig)
210
+ : jwtExpiryConfig;
229
211
  const accessToken = JwtService.sign(userContext, this.authConfig.clientSecret, {
230
- expiresIn: jwtExpirationInSeconds
212
+ expiresIn: jwtExpirationInSeconds,
231
213
  });
232
214
  return accessToken;
233
215
  }
234
- ;
235
216
  generateRefreshToken() {
236
- return crypto.randomBytes(40).toString('hex');
217
+ return crypto.randomBytes(40).toString("hex");
237
218
  }
238
219
  generateDeviceId() {
239
- return crypto.randomBytes(40).toString('hex');
220
+ return crypto.randomBytes(40).toString("hex");
240
221
  }
241
222
  getAndSetDeviceIdCookie(req, res) {
242
223
  let isNewDeviceId = false;
243
- let deviceId = '';
224
+ let deviceId = "";
244
225
  const deviceIdFromCookie = this.getDeviceIdFromCookie(req);
245
226
  if (deviceIdFromCookie) {
246
227
  deviceId = deviceIdFromCookie;
@@ -252,14 +233,14 @@ export class AuthService extends MultiTenantApiService {
252
233
  if (isNewDeviceId) {
253
234
  const cookieOptions = {
254
235
  maxAge: this.authConfig.deviceIdCookieMaxAgeInDays * 24 * 60 * 60 * 1000,
255
- httpOnly: true
236
+ httpOnly: true,
256
237
  };
257
- res.cookie('deviceId', deviceId, cookieOptions);
238
+ res.cookie("deviceId", deviceId, cookieOptions);
258
239
  }
259
240
  return deviceId;
260
241
  }
261
242
  getDeviceIdFromCookie(req) {
262
- return req.cookies['deviceId'];
243
+ return req.cookies.deviceId;
263
244
  }
264
245
  getExpiresOnFromSeconds(expiresInSeconds) {
265
246
  return Date.now() + expiresInSeconds * 1000;
@@ -272,7 +253,7 @@ export class AuthService extends MultiTenantApiService {
272
253
  }
273
254
  async preProcessEntity(userContext, entity, isCreate, allowId) {
274
255
  if (entity.email) {
275
- entity.email = entity.email.toLowerCase();
256
+ entity.email = entity.email?.toLowerCase();
276
257
  }
277
258
  if (entity.password) {
278
259
  const hash = await passwordUtils.hashPassword(entity.password);
@@ -283,11 +264,14 @@ export class AuthService extends MultiTenantApiService {
283
264
  }
284
265
  async updateLastLoggedIn(userId) {
285
266
  try {
286
- const updates = { _lastLoggedIn: moment().utc().toDate() };
267
+ const updates = {
268
+ _lastLoggedIn: moment().utc().toDate(),
269
+ };
287
270
  const systemUserContext = getSystemUserContext();
288
271
  await this.partialUpdateById(systemUserContext, userId, updates);
289
272
  }
290
273
  catch (error) {
274
+ console.error(`Error updating lastLoggedIn: ${error}`);
291
275
  }
292
276
  }
293
277
  }
@@ -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,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();
@@ -1,2 +1,2 @@
1
- import { IUserContext } from "@loomcore/common/models";
1
+ import type { IUserContext } from "@loomcore/common/models";
2
2
  export declare function auditForCreate(userContext: IUserContext, doc: any): void;
@@ -1,2 +1,2 @@
1
- import { IUserContext } from "@loomcore/common/models";
1
+ import type { IUserContext } from "@loomcore/common/models";
2
2
  export declare function auditForUpdate(userContext: IUserContext, doc: any): void;