@solidxai/core 0.1.9-beta.8 → 0.1.10-beta.0

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 (26) hide show
  1. package/LICENSE +89 -0
  2. package/README.md +3 -1
  3. package/dist/passport-strategies/facebook-oauth.strategy.d.ts +5 -3
  4. package/dist/passport-strategies/facebook-oauth.strategy.d.ts.map +1 -1
  5. package/dist/passport-strategies/facebook-oauth.strategy.js +41 -18
  6. package/dist/passport-strategies/facebook-oauth.strategy.js.map +1 -1
  7. package/dist/seeders/seed-data/solid-core-metadata.json +1 -1
  8. package/dist/services/authentication.service.d.ts +12 -13
  9. package/dist/services/authentication.service.d.ts.map +1 -1
  10. package/dist/services/authentication.service.js +40 -16
  11. package/dist/services/authentication.service.js.map +1 -1
  12. package/dist/services/settings/default-settings-provider.service.d.ts +16 -0
  13. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  14. package/dist/services/settings/default-settings-provider.service.js +75 -12
  15. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  16. package/dist/services/user.service.d.ts +10 -8
  17. package/dist/services/user.service.d.ts.map +1 -1
  18. package/dist/services/user.service.js +85 -46
  19. package/dist/services/user.service.js.map +1 -1
  20. package/package.json +2 -2
  21. package/src/passport-strategies/facebook-oauth.strategy.ts +82 -31
  22. package/src/seeders/seed-data/solid-core-metadata.json +1 -1
  23. package/src/services/authentication.service.ts +217 -141
  24. package/src/services/settings/default-settings-provider.service.ts +80 -17
  25. package/src/services/user.service.ts +149 -77
  26. package/dev-grooming-docs/ozzy-prompts.txt +0 -70
@@ -1,37 +1,68 @@
1
- import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
1
+ import {
2
+ BadRequestException,
3
+ forwardRef,
4
+ Inject,
5
+ Injectable,
6
+ } from "@nestjs/common";
2
7
  import { ModuleRef } from "@nestjs/core";
3
- import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
4
- import { CRUDService } from 'src/services/crud.service';
5
- import { EntityManager, Repository } from 'typeorm';
8
+ import { InjectEntityManager, InjectRepository } from "@nestjs/typeorm";
9
+ import { CRUDService } from "src/services/crud.service";
10
+ import { EntityManager, Repository } from "typeorm";
6
11
  import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
7
12
 
8
-
9
- import { OauthUserDto } from '../dtos/oauth-user-dto';
10
- import { RoleMetadata } from '../entities/role-metadata.entity';
11
- import { User } from '../entities/user.entity';
12
- import { ActiveUserData } from '../interfaces/active-user-data.interface';
13
- import { ERROR_MESSAGES } from 'src/constants/error-messages';
14
- import { UserRepository } from 'src/repository/user.repository';
15
- import { RoleMetadataRepository } from 'src/repository/role-metadata.repository';
16
- import { HashingService } from './hashing.service';
13
+ import { OauthUserDto } from "../dtos/oauth-user-dto";
14
+ import { RoleMetadata } from "../entities/role-metadata.entity";
15
+ import { User } from "../entities/user.entity";
16
+ import { ActiveUserData } from "../interfaces/active-user-data.interface";
17
+ import { ERROR_MESSAGES } from "src/constants/error-messages";
18
+ import { UserRepository } from "src/repository/user.repository";
19
+ import { RoleMetadataRepository } from "src/repository/role-metadata.repository";
20
+ import { HashingService } from "./hashing.service";
17
21
 
18
22
  @Injectable()
19
23
  export class UserService extends CRUDService<User> {
24
+ private buildFacebookUsernameBase(name?: string): string {
25
+ const normalized = (name || "")
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9]+/g, "_")
29
+ .replace(/^_+|_+$/g, "");
30
+ return normalized || "facebook_user";
31
+ }
32
+
33
+ private async resolveUniqueUsername(
34
+ preferredUsername: string,
35
+ // fallbackUsername: string,
36
+ ): Promise<string> {
37
+ let candidate = preferredUsername;
38
+ let suffix = 0;
39
+
40
+ while (await this.repo.findOne({ where: { username: candidate } })) {
41
+ suffix += 1;
42
+ candidate = `${preferredUsername}_${suffix}`;
43
+ }
44
+
45
+ if (candidate) {
46
+ return candidate;
47
+ }
48
+
49
+ // return fallbackUsername;
50
+ }
51
+
20
52
  constructor(
21
53
  readonly hashingService: HashingService,
22
54
  @InjectEntityManager()
23
55
  readonly entityManager: EntityManager,
24
56
  // @InjectRepository(User, 'default')
25
57
  readonly repo: UserRepository,
26
- @InjectRepository(User, 'default')
58
+ @InjectRepository(User, "default")
27
59
  readonly nonSecurityRuleAwareRepo: Repository<User>,
28
60
  // @InjectRepository(RoleMetadata)
29
61
  // private readonly roleRepository: Repository<RoleMetadata>,
30
62
  private readonly roleRepository: RoleMetadataRepository,
31
63
  readonly moduleRef: ModuleRef,
32
-
33
64
  ) {
34
- super(entityManager, repo, 'user', 'solid-core', moduleRef);
65
+ super(entityManager, repo, "user", "solid-core", moduleRef);
35
66
  }
36
67
 
37
68
  override async delete(id: number, solidRequestContext: any = {}) {
@@ -44,13 +75,19 @@ export class UserService extends CRUDService<User> {
44
75
  return super.delete(id, solidRequestContext);
45
76
  }
46
77
 
47
- override async deleteMany(ids: number[], solidRequestContext: any = {}): Promise<any> {
78
+ override async deleteMany(
79
+ ids: number[],
80
+ solidRequestContext: any = {},
81
+ ): Promise<any> {
48
82
  if (!ids || ids.length === 0) {
49
83
  throw new Error(ERROR_MESSAGES.DELETE_IDS_REQUIRED);
50
84
  }
51
85
 
52
86
  // ❌ If the active user is trying to delete themselves
53
- if (solidRequestContext?.activeUser?.sub && ids.includes(solidRequestContext.activeUser.id)) {
87
+ if (
88
+ solidRequestContext?.activeUser?.sub &&
89
+ ids.includes(solidRequestContext.activeUser.id)
90
+ ) {
54
91
  throw new BadRequestException(ERROR_MESSAGES.DELETE_SELF_NOT_ALLOWED);
55
92
  }
56
93
 
@@ -60,9 +97,9 @@ export class UserService extends CRUDService<User> {
60
97
  async findOneByEmail(email: string): Promise<User> {
61
98
  return await this.repo.findOne({
62
99
  where: {
63
- email: email
100
+ email: email,
64
101
  },
65
- relations: {}
102
+ relations: {},
66
103
  });
67
104
  // if (!entity) {
68
105
  // throw new NotFoundException(`user with email #${email} not found`);
@@ -73,18 +110,18 @@ export class UserService extends CRUDService<User> {
73
110
  async findOneByAccessCode(accessCode: string): Promise<User> {
74
111
  return await this.repo.findOne({
75
112
  where: {
76
- accessCode: accessCode
113
+ accessCode: accessCode,
77
114
  },
78
- relations: {}
115
+ relations: {},
79
116
  });
80
117
  }
81
118
 
82
119
  async findOneByUsername(username: string): Promise<User> {
83
120
  return await this.repo.findOne({
84
121
  where: {
85
- username: username
122
+ username: username,
86
123
  },
87
- relations: {}
124
+ relations: {},
88
125
  });
89
126
  // if (!entity) {
90
127
  // throw new NotFoundException(`user with username ${username} not found`);
@@ -96,8 +133,8 @@ export class UserService extends CRUDService<User> {
96
133
  const user = await this.repo.findOne({
97
134
  where: { id: id },
98
135
  relations: {
99
- roles: true
100
- }
136
+ roles: true,
137
+ },
101
138
  });
102
139
  if (!user) {
103
140
  throw new Error(ERROR_MESSAGES.USER_NOT_FOUND);
@@ -113,21 +150,22 @@ export class UserService extends CRUDService<User> {
113
150
  const user = await this.repo.findOne({
114
151
  where: { username: username },
115
152
  relations: {
116
- roles: true
117
- }
153
+ roles: true,
154
+ },
118
155
  });
119
156
  if (!user) {
120
157
  throw new Error(ERROR_MESSAGES.USER_NOT_FOUND_BY_USERNAME(username));
121
158
  }
122
- const role = await this.roleRepository.findOne({ where: { name: roleName } });
159
+ const role = await this.roleRepository.findOne({
160
+ where: { name: roleName },
161
+ });
123
162
  if (!role) {
124
163
  throw new Error(ERROR_MESSAGES.ROLE_NOT_FOUND(roleName));
125
164
  }
126
165
 
127
166
  if (user.roles && user.roles.length > 0) {
128
167
  user.roles.push(role);
129
- }
130
- else {
168
+ } else {
131
169
  user.roles = [role];
132
170
  }
133
171
 
@@ -137,7 +175,7 @@ export class UserService extends CRUDService<User> {
137
175
  async addRolesToUser(username: string, roleNames: string[]): Promise<User> {
138
176
  const user = await this.nonSecurityRuleAwareRepo.findOne({
139
177
  where: { username: username },
140
- relations: { roles: true }
178
+ relations: { roles: true },
141
179
  });
142
180
 
143
181
  if (!user) {
@@ -145,43 +183,47 @@ export class UserService extends CRUDService<User> {
145
183
  }
146
184
 
147
185
  const roles = await this.roleRepository.find({
148
- where: roleNames.map(roleName => ({ name: roleName }))
186
+ where: roleNames.map((roleName) => ({ name: roleName })),
149
187
  });
150
188
 
151
189
  if (roles.length !== roleNames.length) {
152
- const foundRoleNames = roles.map(role => role.name);
153
- const missingRoles = roleNames.filter(roleName => !foundRoleNames.includes(roleName));
190
+ const foundRoleNames = roles.map((role) => role.name);
191
+ const missingRoles = roleNames.filter(
192
+ (roleName) => !foundRoleNames.includes(roleName),
193
+ );
154
194
  throw new Error(ERROR_MESSAGES.ROLES_NOT_FOUND(missingRoles));
155
195
  }
156
196
 
157
- const currentRoles = user.roles.map(role => role.name);
197
+ const currentRoles = user.roles.map((role) => role.name);
158
198
 
159
- const rolesToAdd = roles.filter(role => !currentRoles.includes(role.name));
199
+ const rolesToAdd = roles.filter(
200
+ (role) => !currentRoles.includes(role.name),
201
+ );
160
202
 
161
- const rolesToRemove = user.roles.filter(role => !roleNames.includes(role.name));
203
+ const rolesToRemove = user.roles.filter(
204
+ (role) => !roleNames.includes(role.name),
205
+ );
162
206
 
163
207
  if (rolesToAdd.length > 0) {
164
208
  user.roles.push(...rolesToAdd);
165
209
  }
166
210
 
167
211
  if (rolesToRemove.length > 0) {
168
- user.roles = user.roles.filter(role => !rolesToRemove.includes(role));
212
+ user.roles = user.roles.filter((role) => !rolesToRemove.includes(role));
169
213
  }
170
214
 
171
215
  return await this.nonSecurityRuleAwareRepo.save(user);
172
216
  }
173
217
 
174
-
175
218
  async removeRoleFromUser(username: string, roleName: string): Promise<User> {
176
-
177
219
  // load the role with the respective permissions.
178
220
  const user = await this.repo.findOne({
179
221
  where: {
180
- username: username
222
+ username: username,
181
223
  },
182
224
  relations: {
183
- roles: true
184
- }
225
+ roles: true,
226
+ },
185
227
  });
186
228
 
187
229
  if (!user) {
@@ -189,7 +231,7 @@ export class UserService extends CRUDService<User> {
189
231
  }
190
232
 
191
233
  // modify the permissions array.
192
- user.roles = user.roles.filter(role => role.name !== roleName);
234
+ user.roles = user.roles.filter((role) => role.name !== roleName);
193
235
 
194
236
  return await this.repo.save(user);
195
237
  }
@@ -201,11 +243,11 @@ export class UserService extends CRUDService<User> {
201
243
  email: oauthUserDto.email,
202
244
  },
203
245
  relations: {
204
- roles: true
205
- }
246
+ roles: true,
247
+ },
206
248
  });
207
249
 
208
- // if we are unable to find a user then we need to create one.
250
+ // if we are unable to find a user then we need to create one.
209
251
  if (!user) {
210
252
  const user = new User();
211
253
  user.username = oauthUserDto.email;
@@ -220,9 +262,12 @@ export class UserService extends CRUDService<User> {
220
262
  const savedUser = await this.repo.save(user);
221
263
 
222
264
  // Initialize the user roles
223
- await this.initializeRolesForNewUser([this.settingService.getConfigValue<SolidCoreSetting>('defaultRole')], savedUser);
265
+ await this.initializeRolesForNewUser(
266
+ [this.settingService.getConfigValue<SolidCoreSetting>("defaultRole")],
267
+ savedUser,
268
+ );
224
269
  }
225
- // else we update the user and store the generated code & access token.
270
+ // else we update the user and store the generated code & access token.
226
271
  else {
227
272
  const entity = await this.repo.preload({
228
273
  id: user.id,
@@ -240,19 +285,48 @@ export class UserService extends CRUDService<User> {
240
285
  }
241
286
 
242
287
  async resolveUserOnOauthFacebook(oauthUserDto: OauthUserDto): Promise<User> {
243
- const user = await this.repo.findOne({
244
- where: {
245
- email: oauthUserDto.email,
246
- },
247
- relations: {
248
- roles: true,
249
- },
250
- });
288
+ const normalizedEmail = oauthUserDto.email?.trim().toLowerCase() || null;
289
+ let user: User | null = null;
290
+
291
+ if (oauthUserDto.providerId) {
292
+ user = await this.repo.findOne({
293
+ where: {
294
+ facebookId: oauthUserDto.providerId,
295
+ },
296
+ relations: {
297
+ roles: true,
298
+ },
299
+ });
300
+ }
251
301
 
252
302
  if (!user) {
303
+ const facebookProviderFallback = `facebook_${oauthUserDto.providerId}`;
304
+ const facebookNameUsername = this.buildFacebookUsernameBase(
305
+ oauthUserDto.name,
306
+ );
307
+ // let username = normalizedEmail || facebookNameUsername;
308
+ let username = facebookNameUsername;
309
+
310
+ let email = normalizedEmail;
311
+
312
+ // Avoid clashing with local users that already own the same email/username.
313
+ if (normalizedEmail) {
314
+ const existingByEmail = await this.repo.findOne({
315
+ where: { email: normalizedEmail },
316
+ });
317
+ if (existingByEmail) {
318
+ username = facebookNameUsername;
319
+ email = null;
320
+ }
321
+ }
322
+ username = await this.resolveUniqueUsername(
323
+ username,
324
+ // facebookProviderFallback,
325
+ );
326
+
253
327
  const newUser = new User();
254
- newUser.username = oauthUserDto.email;
255
- newUser.email = oauthUserDto.email;
328
+ newUser.username = username;
329
+ newUser.email = email;
256
330
  newUser.fullName = oauthUserDto.name;
257
331
  newUser.lastLoginProvider = oauthUserDto.provider;
258
332
  newUser.accessCode = oauthUserDto.accessCode;
@@ -263,7 +337,7 @@ export class UserService extends CRUDService<User> {
263
337
  const savedUser = await this.repo.save(newUser);
264
338
 
265
339
  await this.initializeRolesForNewUser(
266
- [this.settingService.getConfigValue<SolidCoreSetting>('defaultRole')],
340
+ [this.settingService.getConfigValue<SolidCoreSetting>("defaultRole")],
267
341
  savedUser,
268
342
  );
269
343
  return savedUser;
@@ -276,13 +350,11 @@ export class UserService extends CRUDService<User> {
276
350
  facebookId: oauthUserDto.providerId,
277
351
  facebookProfilePicture: oauthUserDto.picture,
278
352
  });
279
-
280
353
  await this.repo.save(entity);
281
354
  return entity;
282
355
  }
283
356
  }
284
357
 
285
-
286
358
  async resolveUserOnOauthMicrosoft(oauthUserDto: OauthUserDto): Promise<User> {
287
359
  const user = await this.repo.findOne({
288
360
  where: {
@@ -307,10 +379,9 @@ export class UserService extends CRUDService<User> {
307
379
  const savedUser = await this.repo.save(newUser);
308
380
 
309
381
  await this.initializeRolesForNewUser(
310
- [this.settingService.getConfigValue<SolidCoreSetting>('defaultRole')],
382
+ [this.settingService.getConfigValue<SolidCoreSetting>("defaultRole")],
311
383
  savedUser,
312
384
  );
313
- return savedUser;
314
385
  } else {
315
386
  const entity = await this.repo.preload({
316
387
  id: user.id,
@@ -322,25 +393,29 @@ export class UserService extends CRUDService<User> {
322
393
  });
323
394
 
324
395
  await this.repo.save(entity);
325
- return entity;
326
396
  }
397
+ return user;
327
398
  }
328
399
 
329
- async findUsersByRole(roleName: string, relations: any = {}): Promise<User[]> {
400
+ async findUsersByRole(
401
+ roleName: string,
402
+ relations: any = {},
403
+ ): Promise<User[]> {
330
404
  return await this.repo.find({
331
405
  where: {
332
406
  roles: {
333
- name: roleName
334
- }
407
+ name: roleName,
408
+ },
335
409
  },
336
- relations: relations
410
+ relations: relations,
337
411
  });
338
412
  }
339
413
 
340
414
  async checkIfPermissionExists(query: any, activeUser: ActiveUserData) {
341
-
342
- const matchingPermssions = activeUser.permissions.filter((p) => query.permissionNames.includes(p));
343
- return matchingPermssions
415
+ const matchingPermssions = activeUser.permissions.filter((p) =>
416
+ query.permissionNames.includes(p),
417
+ );
418
+ return matchingPermssions;
344
419
  }
345
420
 
346
421
  async initializeRolesForNewUser(roles: string[], user: User) {
@@ -348,7 +423,7 @@ export class UserService extends CRUDService<User> {
348
423
  throw new BadRequestException(ERROR_MESSAGES.USER_MISSING_ID);
349
424
  }
350
425
  let userRoles = [];
351
- // Default Internal user role assigned
426
+ // Default Internal user role assigned
352
427
  userRoles.push("Internal User");
353
428
  if (roles) {
354
429
  userRoles = [...userRoles, ...roles];
@@ -372,7 +447,4 @@ export class UserService extends CRUDService<User> {
372
447
  passwordSchemeVersion: this.hashingService.currentVersion(),
373
448
  };
374
449
  }
375
-
376
-
377
450
  }
378
-
@@ -1,70 +0,0 @@
1
- Hi Ozzy,
2
- Can you give me instructions to create a new model in the fees portal module, you can call it address-master.
3
- This model needs to have the following fields.
4
- 1. address1 - shortText
5
- 2. address2 - shortText
6
- 3. addressType - selectionStatic with values - home, office & other.
7
- 4. city - many2one with the city model
8
- 5. state - many2one with the state model
9
- 6. pincode - many2one with the state model.
10
-
11
- After I add this model I want to start storing student address.
12
-
13
- To do this if you can also give me instructions on how to add a relation field to the student model, you can call this field studentAddress.
14
-
15
-
16
-
17
-
18
-
19
-
20
- Can you now create a new computed field provider.
21
- - You can call this provider: StateTotalCitiesComputedFieldProvider
22
- - This will be a post computation provider and will depend on the city model being inserted, deleted, updated.
23
- - The implementation simply needs to take a count of cities that belong to the same state as the city being mutated and update the state totalCities field.
24
-
25
-
26
- Can you now create a new computed field provider.
27
- - You can call this provider: StateTotalPopulationComputedFieldProvider
28
- - This will be a post computation provider and will depend on the city model being inserted, deleted, updated.
29
- - The implementation simply needs to take a sum of population of each city that belong to the same state as the city being mutated and update the state totalPopulation field.
30
-
31
-
32
- Can you now create a new computed field provider.
33
- - You can call this provider: StateTotalGdpComputedFieldProvider
34
- - This will be a post computation provider and will depend on the city model being inserted, deleted, updated.
35
- - The implementation simply needs to take a sum of gdp of each city that belong to the same state as the city being mutated and update the state totalGdp field.
36
- - I will add the totalGdp field to the state model after I have the computed provider TS code.
37
-
38
-
39
- # Testing prompt sequence
40
-
41
- ## Create Module
42
- Can you create a new module called address-master. This will be used to store address master data.
43
-
44
- ## Add Model - Country
45
- Can you add a new model to the address-master module called country.
46
- This will have name, description (richText), countryCode. You can assume name & countryCode to be unique. But only name will be used as a userkey.
47
- Also enable audit tracking on the model and all its fields.
48
- Make sure the table name is properly prefixed with the module name snake case.
49
-
50
- ## Add Model - Language
51
- Can you add a new model to the address-master module called language.
52
- This will have language, languageIsoCode. You can assume language to be unique.
53
- Also for the languageIsoCode, can you create a field with type staticSelection add a bunch (20 odd, include at-least 7-8 indian languages also) of commonly spoken languages with their respective language codes as values.
54
- Also enable audit tracking on the model and all its fields.
55
- Make sure the table name is properly prefixed with the module name snake case.
56
-
57
- ## Add Model - Country Language
58
-
59
- Can you now add a new model called CountrySpokenLanguages, this model will have 2 many-to-one fields.
60
- 1. country - many-to-one with the country model.
61
- 2. spokenLanguage - many-to-one with the language model.
62
- 3. spokenBy - int field storing data representing how many people speak that language.
63
- Also enable audit tracking on the model and all its fields.
64
- Make sure the table name is properly prefixed with the module name snake case.
65
-
66
- ## Add Model - Country Population Trend
67
- Can you add a model called CountryPopulationTrend, this model has the following fields.
68
- 1. country - many-to-one with the country model.
69
- 2. entryDate - date field.
70
- 3. populationAsOnDate - int field storing the population of that country as on that date.