@loomcore/api 0.1.24 → 0.1.26

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.
@@ -28,6 +28,7 @@ export function getTestMetaOrgUser() {
28
28
  authorizations: [{
29
29
  _id: '6939c54e57a1c6576a40c590',
30
30
  _orgId: getTestMetaOrg()._id,
31
+ role: 'metaorgUser',
31
32
  feature: 'metaorgUser',
32
33
  config: {},
33
34
  _created: new Date(),
@@ -76,6 +77,7 @@ export function getTestOrgUser() {
76
77
  authorizations: [{
77
78
  _id: '6939c54e57a1c6576a40c591',
78
79
  _orgId: getTestOrg()._id,
80
+ role: 'testOrgUser',
79
81
  feature: 'testOrgUser',
80
82
  config: {},
81
83
  _created: new Date(),
@@ -2,16 +2,12 @@ import { Client } from "pg";
2
2
  import { IMigration } from "../index.js";
3
3
  export declare class CreateAdminUserMigration implements IMigration {
4
4
  private readonly client;
5
- private readonly adminEmail;
6
- private readonly adminPassword;
7
- constructor(client: Client, adminEmail: string, adminPassword: string);
5
+ constructor(client: Client);
8
6
  index: number;
9
- execute(): Promise<{
7
+ execute(adminEmail?: string, adminPassword?: string): Promise<{
10
8
  success: boolean;
11
- error: Error;
12
- } | {
13
- success: boolean;
14
- error: null;
9
+ adminUserId: string | undefined;
10
+ error: Error | null;
15
11
  }>;
16
12
  revert(): Promise<{
17
13
  success: boolean;
@@ -4,42 +4,30 @@ import { getSystemUserContext } from "@loomcore/common/models";
4
4
  import { AuthService } from "../../../services/auth.service.js";
5
5
  export class CreateAdminUserMigration {
6
6
  client;
7
- adminEmail;
8
- adminPassword;
9
- constructor(client, adminEmail, adminPassword) {
7
+ constructor(client) {
10
8
  this.client = client;
11
- this.adminEmail = adminEmail;
12
- this.adminPassword = adminPassword;
13
9
  }
14
10
  index = 6;
15
- async execute() {
11
+ async execute(adminEmail, adminPassword) {
16
12
  const _id = randomUUID().toString();
17
13
  const systemUserContext = getSystemUserContext();
14
+ let createdUser;
18
15
  try {
19
16
  const database = new PostgresDatabase(this.client);
20
17
  const authService = new AuthService(database);
21
- const adminUser = await authService.createUser(systemUserContext, {
18
+ createdUser = await authService.createUser(systemUserContext, {
22
19
  _id: _id,
23
20
  _orgId: systemUserContext._orgId,
24
- email: this.adminEmail,
25
- password: this.adminPassword,
21
+ email: adminEmail,
22
+ password: adminPassword,
26
23
  firstName: 'Admin',
27
24
  lastName: 'User',
28
25
  displayName: 'Admin User',
29
- authorizations: [{
30
- _id: randomUUID().toString(),
31
- feature: 'metaorgAdmin',
32
- config: {},
33
- _created: new Date(),
34
- _createdBy: systemUserContext.user._id,
35
- _updated: new Date(),
36
- _updatedBy: systemUserContext.user._id
37
- }],
38
26
  });
39
27
  }
40
28
  catch (error) {
41
29
  return {
42
- success: false, error: new Error(`Error creating admin user: ${error.message}`)
30
+ success: false, adminUserId: undefined, error: new Error(`Error creating admin user: ${error.message}`)
43
31
  };
44
32
  }
45
33
  try {
@@ -48,13 +36,13 @@ export class CreateAdminUserMigration {
48
36
  VALUES ('${_id}', ${this.index}, TRUE, FALSE);
49
37
  `);
50
38
  if (result.rowCount === 0) {
51
- return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
39
+ return { success: false, adminUserId: undefined, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
52
40
  }
53
41
  }
54
42
  catch (error) {
55
- return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
43
+ return { success: false, adminUserId: undefined, error: new Error(`Error inserting migration ${this.index} to migrations table: ${error.message}`) };
56
44
  }
57
- return { success: true, error: null };
45
+ return { success: true, adminUserId: createdUser?._id, error: null };
58
46
  }
59
47
  async revert() {
60
48
  throw new Error('Not implemented');
@@ -18,7 +18,13 @@ export class CreateUserRolesTableMigration {
18
18
  "_orgId" VARCHAR(255),
19
19
  "_userId" VARCHAR(255) NOT NULL,
20
20
  "_roleId" VARCHAR(255) NOT NULL,
21
- CONSTRAINT "fk_user_roles_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE
21
+ "_created" TIMESTAMP NOT NULL,
22
+ "_createdBy" VARCHAR(255) NOT NULL,
23
+ "_updated" TIMESTAMP NOT NULL,
24
+ "_updatedBy" VARCHAR(255) NOT NULL,
25
+ "_deleted" TIMESTAMP,
26
+ "_deletedBy" VARCHAR(255),
27
+ CONSTRAINT "fk_user_roles_organization" FOREIGN KEY ("_orgId") REFERENCES "organizations"("_id") ON DELETE CASCADE,
22
28
  CONSTRAINT "fk_user_roles_user" FOREIGN KEY ("_userId") REFERENCES "users"("_id") ON DELETE CASCADE,
23
29
  CONSTRAINT "fk_user_roles_role" FOREIGN KEY ("_roleId") REFERENCES "roles"("_id") ON DELETE CASCADE,
24
30
  CONSTRAINT "uk_user_roles" UNIQUE ("_orgId", "_userId", "_roleId")
@@ -0,0 +1,23 @@
1
+ import { Client } from "pg";
2
+ import { IMigration } from "./migration.interface.js";
3
+ export declare class CreateAdminAuthorizationMigration implements IMigration {
4
+ private readonly client;
5
+ private readonly adminUserId;
6
+ private readonly metaOrgId?;
7
+ constructor(client: Client, adminUserId: string, metaOrgId?: string | undefined);
8
+ index: number;
9
+ execute(): Promise<{
10
+ success: boolean;
11
+ error: Error;
12
+ } | {
13
+ success: boolean;
14
+ error: null;
15
+ }>;
16
+ revert(metaOrgId?: string): Promise<{
17
+ success: boolean;
18
+ error: Error;
19
+ } | {
20
+ success: boolean;
21
+ error: null;
22
+ }>;
23
+ }
@@ -0,0 +1,117 @@
1
+ import { randomUUID } from "crypto";
2
+ export class CreateAdminAuthorizationMigration {
3
+ client;
4
+ adminUserId;
5
+ metaOrgId;
6
+ constructor(client, adminUserId, metaOrgId) {
7
+ this.client = client;
8
+ this.adminUserId = adminUserId;
9
+ this.metaOrgId = metaOrgId;
10
+ }
11
+ index = 11;
12
+ async execute() {
13
+ const _id = randomUUID().toString();
14
+ try {
15
+ await this.client.query('BEGIN');
16
+ const roleId = randomUUID().toString();
17
+ const roleResult = await this.client.query(`
18
+ INSERT INTO "roles" ("_id", "_orgId", "name")
19
+ VALUES ($1, $2, 'admin')
20
+ `, [roleId, this.metaOrgId]);
21
+ if (roleResult.rowCount === 0) {
22
+ await this.client.query('ROLLBACK');
23
+ return { success: false, error: new Error('Failed to create admin role') };
24
+ }
25
+ const userRoleId = randomUUID().toString();
26
+ const userRoleResult = await this.client.query(`
27
+ INSERT INTO "user_roles" ("_id", "_orgId", "_userId", "_roleId", "_created", "_createdBy", "_updated", "_updatedBy")
28
+ VALUES ($1, $2, $3, $4, NOW(), 'system', NOW(), 'system')
29
+ `, [userRoleId, this.metaOrgId, this.adminUserId, roleId]);
30
+ if (userRoleResult.rowCount === 0) {
31
+ await this.client.query('ROLLBACK');
32
+ return { success: false, error: new Error('Failed to create user role') };
33
+ }
34
+ const featureId = randomUUID().toString();
35
+ const featureResult = await this.client.query(`
36
+ INSERT INTO "features" ("_id", "_orgId", "name")
37
+ VALUES ($1, $2, 'admin')
38
+ `, [featureId, this.metaOrgId]);
39
+ if (featureResult.rowCount === 0) {
40
+ await this.client.query('ROLLBACK');
41
+ return { success: false, error: new Error('Failed to create admin feature') };
42
+ }
43
+ const authorizationId = randomUUID().toString();
44
+ const authorizationResult = await this.client.query(`
45
+ INSERT INTO "authorizations" (
46
+ "_id", "_orgId", "_roleId", "_featureId",
47
+ "_created", "_createdBy", "_updated", "_updatedBy"
48
+ )
49
+ VALUES ($1, $2, $3, $4, NOW(), 'system', NOW(), 'system')
50
+ `, [authorizationId, this.metaOrgId, roleId, featureId]);
51
+ if (authorizationResult.rowCount === 0) {
52
+ await this.client.query('ROLLBACK');
53
+ return { success: false, error: new Error('Failed to create admin authorization') };
54
+ }
55
+ const migrationResult = await this.client.query(`
56
+ INSERT INTO "migrations" ("_id", "index", "hasRun", "reverted")
57
+ VALUES ($1, $2, TRUE, FALSE)
58
+ `, [_id, this.index]);
59
+ if (migrationResult.rowCount === 0) {
60
+ await this.client.query('ROLLBACK');
61
+ return { success: false, error: new Error(`Error inserting migration ${this.index} to migrations table: No row returned`) };
62
+ }
63
+ await this.client.query('COMMIT');
64
+ return { success: true, error: null };
65
+ }
66
+ catch (error) {
67
+ await this.client.query('ROLLBACK');
68
+ return { success: false, error: new Error(`Error executing migration ${this.index}: ${error.message}`) };
69
+ }
70
+ }
71
+ async revert(metaOrgId) {
72
+ try {
73
+ await this.client.query('BEGIN');
74
+ await this.client.query(`
75
+ DELETE FROM "authorizations"
76
+ WHERE "_orgId" = $1
77
+ AND "_featureId" IN (
78
+ SELECT "_id" FROM "features"
79
+ WHERE "_orgId" = $1 AND "name" = 'admin'
80
+ )
81
+ AND "_roleId" IN (
82
+ SELECT "_id" FROM "roles"
83
+ WHERE "_orgId" = $1 AND "name" = 'admin'
84
+ )
85
+ `, [metaOrgId]);
86
+ await this.client.query(`
87
+ DELETE FROM "features"
88
+ WHERE "_orgId" = $1 AND "name" = 'admin'
89
+ `, [metaOrgId]);
90
+ await this.client.query(`
91
+ DELETE FROM "user_roles"
92
+ WHERE "_orgId" = $1
93
+ AND "_roleId" IN (
94
+ SELECT "_id" FROM "roles"
95
+ WHERE "_orgId" = $1 AND "name" = 'admin'
96
+ )
97
+ `, [metaOrgId]);
98
+ await this.client.query(`
99
+ DELETE FROM "roles"
100
+ WHERE "_orgId" = $1 AND "name" = 'admin'
101
+ `, [metaOrgId]);
102
+ const updateResult = await this.client.query(`
103
+ UPDATE "migrations" SET "reverted" = TRUE WHERE "index" = $1
104
+ `, [this.index]);
105
+ if (updateResult.rowCount === 0) {
106
+ await this.client.query('ROLLBACK');
107
+ return { success: false, error: new Error(`Error updating migration record for index ${this.index}: No row returned`) };
108
+ }
109
+ await this.client.query('COMMIT');
110
+ return { success: true, error: null };
111
+ }
112
+ catch (error) {
113
+ await this.client.query('ROLLBACK');
114
+ return { success: false, error: new Error(`Error reverting migration ${this.index}: ${error.message}`) };
115
+ }
116
+ }
117
+ }
@@ -1,5 +1,5 @@
1
1
  import { Client } from "pg";
2
- export declare function setupDatabaseForAuth(client: Client, adminUsername: string, adminPassword: string): Promise<{
2
+ export declare function setupDatabaseForAuth(client: Client, adminUsername: string, adminPassword: string, metaOrgId?: string): Promise<{
3
3
  success: boolean;
4
4
  error: Error | null;
5
5
  }>;
@@ -7,7 +7,8 @@ import { CreateUserRolesTableMigration } from "./008-create-user-roles-table.mig
7
7
  import { CreateRoleTableMigration } from "./007-create-roles-table.migration.js";
8
8
  import { CreateAuthorizationsTableMigration } from "./010-create-authorizations-table.migration.js";
9
9
  import { CreateFeaturesTableMigration } from "./009-create-features-table.migration.js";
10
- export async function setupDatabaseForAuth(client, adminUsername, adminPassword) {
10
+ import { CreateAdminAuthorizationMigration } from "./011-create-admin-authorization.migration.js";
11
+ export async function setupDatabaseForAuth(client, adminUsername, adminPassword, metaOrgId) {
11
12
  let runMigrations = [];
12
13
  if (await doesTableExist(client, 'migrations')) {
13
14
  const migrations = await client.query(`
@@ -19,25 +20,79 @@ export async function setupDatabaseForAuth(client, adminUsername, adminPassword)
19
20
  return row.index;
20
21
  });
21
22
  }
22
- let migrationsToRun = [];
23
- if (!runMigrations.includes(1))
24
- migrationsToRun.push(new CreateMigrationTableMigration(client));
25
- if (!runMigrations.includes(3))
26
- migrationsToRun.push(new CreateUsersTableMigration(client));
27
- if (!runMigrations.includes(4))
28
- migrationsToRun.push(new CreateRefreshTokenTableMigration(client));
29
- if (!runMigrations.includes(6))
30
- migrationsToRun.push(new CreateAdminUserMigration(client, adminUsername, adminPassword));
31
- if (!runMigrations.includes(7))
32
- migrationsToRun.push(new CreateRoleTableMigration(client));
33
- if (!runMigrations.includes(8))
34
- migrationsToRun.push(new CreateUserRolesTableMigration(client));
35
- if (!runMigrations.includes(9))
36
- migrationsToRun.push(new CreateFeaturesTableMigration(client));
37
- if (!runMigrations.includes(10))
38
- migrationsToRun.push(new CreateAuthorizationsTableMigration(client));
39
- for (const migration of migrationsToRun) {
40
- await migration.execute();
23
+ let adminUserId;
24
+ if (!runMigrations.includes(1)) {
25
+ const migration = new CreateMigrationTableMigration(client);
26
+ const result = await migration.execute();
27
+ if (!result.success) {
28
+ console.error('setupDatabaseForAuth: error creating migrations table', result.error);
29
+ return { success: false, error: result.error };
30
+ }
31
+ }
32
+ if (!runMigrations.includes(3)) {
33
+ const migration = new CreateUsersTableMigration(client);
34
+ const result = await migration.execute();
35
+ if (!result.success) {
36
+ console.error('setupDatabaseForAuth: error creating users table', result.error);
37
+ return { success: false, error: result.error };
38
+ }
39
+ }
40
+ if (!runMigrations.includes(4)) {
41
+ const migration = new CreateRefreshTokenTableMigration(client);
42
+ const result = await migration.execute();
43
+ if (!result.success) {
44
+ console.error('setupDatabaseForAuth: error creating refresh_tokens table', result.error);
45
+ return { success: false, error: result.error };
46
+ }
47
+ }
48
+ if (!runMigrations.includes(6)) {
49
+ const migration = new CreateAdminUserMigration(client);
50
+ const result = await migration.execute(adminUsername, adminPassword);
51
+ adminUserId = result.adminUserId;
52
+ }
53
+ if (!runMigrations.includes(7)) {
54
+ const migration = new CreateRoleTableMigration(client);
55
+ const result = await migration.execute();
56
+ if (!result.success) {
57
+ console.error('setupDatabaseForAuth: error creating roles table', result.error);
58
+ return { success: false, error: result.error };
59
+ }
60
+ }
61
+ if (!runMigrations.includes(8)) {
62
+ const migration = new CreateUserRolesTableMigration(client);
63
+ const result = await migration.execute();
64
+ if (!result.success) {
65
+ console.error('setupDatabaseForAuth: error creating user_roles table', result.error);
66
+ return { success: false, error: result.error };
67
+ }
68
+ }
69
+ if (!runMigrations.includes(9)) {
70
+ const migration = new CreateFeaturesTableMigration(client);
71
+ const result = await migration.execute();
72
+ if (!result.success) {
73
+ console.error('setupDatabaseForAuth: error creating features table', result.error);
74
+ return { success: false, error: result.error };
75
+ }
76
+ }
77
+ if (!runMigrations.includes(10)) {
78
+ const migration = new CreateAuthorizationsTableMigration(client);
79
+ const result = await migration.execute();
80
+ if (!result.success) {
81
+ console.error('setupDatabaseForAuth: error creating authorizations table', result.error);
82
+ return { success: false, error: result.error };
83
+ }
84
+ }
85
+ if (!runMigrations.includes(11)) {
86
+ if (!adminUserId) {
87
+ console.error('setupDatabaseForAuth: Admin user ID is required');
88
+ return { success: true, error: null };
89
+ }
90
+ const migration = new CreateAdminAuthorizationMigration(client, adminUserId, metaOrgId);
91
+ const result = await migration.execute();
92
+ if (!result.success) {
93
+ console.error('setupDatabaseForAuth: error creating admin authorization', result.error);
94
+ return { success: false, error: result.error };
95
+ }
41
96
  }
42
97
  return { success: true, error: null };
43
98
  }
@@ -28,4 +28,5 @@ export declare class PostgresDatabase implements IDatabase {
28
28
  deleteMany(queryObject: IQueryOptions, pluralResourceName: string): Promise<DeleteResult>;
29
29
  find<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T[]>;
30
30
  findOne<T extends IEntity>(queryObject: IQueryOptions, pluralResourceName: string): Promise<T | null>;
31
+ getUserAuthorizations(userIds: string[], orgId?: string): Promise<Map<string, any[]>>;
31
32
  }
@@ -66,4 +66,63 @@ export class PostgresDatabase {
66
66
  async findOne(queryObject, pluralResourceName) {
67
67
  return findOneQuery(this.client, queryObject, pluralResourceName);
68
68
  }
69
+ async getUserAuthorizations(userIds, orgId) {
70
+ if (userIds.length === 0) {
71
+ return new Map();
72
+ }
73
+ const now = new Date();
74
+ const placeholders = userIds.map((_, i) => `$${i + 1}`).join(', ');
75
+ let query = `
76
+ SELECT DISTINCT
77
+ ur."_userId" as "userId",
78
+ r."name" as "role",
79
+ f."name" as "feature",
80
+ a."config",
81
+ a."_id",
82
+ a."_orgId",
83
+ a."_created",
84
+ a."_createdBy",
85
+ a."_updated",
86
+ a."_updatedBy"
87
+ FROM "user_roles" ur
88
+ INNER JOIN "roles" r ON ur."_roleId" = r."_id"
89
+ INNER JOIN "authorizations" a ON r."_id" = a."_roleId"
90
+ INNER JOIN "features" f ON a."_featureId" = f."_id"
91
+ WHERE ur."_userId" IN (${placeholders})
92
+ AND ur."_deleted" IS NULL
93
+ AND a."_deleted" IS NULL
94
+ AND (a."startDate" IS NULL OR a."startDate" <= $${userIds.length + 1})
95
+ AND (a."endDate" IS NULL OR a."endDate" >= $${userIds.length + 1})
96
+ `;
97
+ const values = [...userIds, now];
98
+ if (orgId) {
99
+ query += ` AND ur."_orgId" = $${userIds.length + 2} AND r."_orgId" = $${userIds.length + 2} AND a."_orgId" = $${userIds.length + 2} AND f."_orgId" = $${userIds.length + 2}`;
100
+ values.push(orgId);
101
+ }
102
+ const result = await this.client.query(query, values);
103
+ const authorizationsMap = new Map();
104
+ for (const row of result.rows) {
105
+ const userId = row.userId;
106
+ if (!authorizationsMap.has(userId)) {
107
+ authorizationsMap.set(userId, []);
108
+ }
109
+ authorizationsMap.get(userId).push({
110
+ _id: row._id,
111
+ _orgId: row._orgId,
112
+ role: row.role,
113
+ feature: row.feature,
114
+ config: row.config || undefined,
115
+ _created: row._created,
116
+ _createdBy: row._createdBy,
117
+ _updated: row._updated,
118
+ _updatedBy: row._updatedBy,
119
+ });
120
+ }
121
+ for (const userId of userIds) {
122
+ if (!authorizationsMap.has(userId)) {
123
+ authorizationsMap.set(userId, []);
124
+ }
125
+ }
126
+ return authorizationsMap;
127
+ }
69
128
  }
@@ -0,0 +1,7 @@
1
+ import { IEntity } from "@loomcore/common/models";
2
+ import { TSchema } from "@sinclair/typebox";
3
+ export interface IFeature extends IEntity {
4
+ name: string;
5
+ }
6
+ export declare const FeatureSchema: TSchema;
7
+ export declare const FeatureModelSpec: import("@loomcore/common/models").IModelSpec<TSchema>;
@@ -0,0 +1,6 @@
1
+ import { entityUtils } from "@loomcore/common/utils";
2
+ import { Type } from "@sinclair/typebox";
3
+ export const FeatureSchema = Type.Object({
4
+ name: Type.String({ minLength: 1 }),
5
+ });
6
+ export const FeatureModelSpec = entityUtils.getModelSpec(FeatureSchema);
@@ -0,0 +1,7 @@
1
+ import { IEntity } from "@loomcore/common/models";
2
+ import { TSchema } from "@sinclair/typebox";
3
+ export interface IRole extends IEntity {
4
+ name: string;
5
+ }
6
+ export declare const RoleSchema: TSchema;
7
+ export declare const RoleModelSpec: import("@loomcore/common/models").IModelSpec<TSchema>;
@@ -0,0 +1,6 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { entityUtils } from "@loomcore/common/utils";
3
+ export const RoleSchema = Type.Object({
4
+ name: Type.String({ minLength: 1 }),
5
+ });
6
+ export const RoleModelSpec = entityUtils.getModelSpec(RoleSchema);
@@ -0,0 +1,8 @@
1
+ import { IAuditable, IEntity } from "@loomcore/common/models";
2
+ import { TSchema } from "@sinclair/typebox";
3
+ export interface IUserRole extends IEntity, IAuditable {
4
+ userId: string;
5
+ roleId: string;
6
+ }
7
+ export declare const UserRoleSchema: TSchema;
8
+ export declare const UserRoleModelSpec: import("@loomcore/common/models").IModelSpec<TSchema>;
@@ -0,0 +1,7 @@
1
+ import { entityUtils } from "@loomcore/common/utils";
2
+ import { Type } from "@sinclair/typebox";
3
+ export const UserRoleSchema = Type.Object({
4
+ userId: Type.String({ minLength: 1 }),
5
+ roleId: Type.String({ minLength: 1 }),
6
+ });
7
+ export const UserRoleModelSpec = entityUtils.getModelSpec(UserRoleSchema, { isAuditable: true });
@@ -7,4 +7,4 @@ export * from './multi-tenant-api.service.js';
7
7
  export * from './organization.service.js';
8
8
  export * from './password-reset-token.service.js';
9
9
  export * from './tenant-query-decorator.js';
10
- export * from './user.service.js';
10
+ export * from './user-service/user.service.js';
@@ -7,4 +7,4 @@ export * from './multi-tenant-api.service.js';
7
7
  export * from './organization.service.js';
8
8
  export * from './password-reset-token.service.js';
9
9
  export * from './tenant-query-decorator.js';
10
- export * from './user.service.js';
10
+ export * from './user-service/user.service.js';
@@ -0,0 +1,12 @@
1
+ import { IUser, IUserContext, IPagedResult, IQueryOptions } from '@loomcore/common/models';
2
+ import { MultiTenantApiService } from '../index.js';
3
+ import { IDatabase } from '../../databases/models/index.js';
4
+ export declare class UserService extends MultiTenantApiService<IUser> {
5
+ constructor(database: IDatabase);
6
+ fullUpdateById(userContext: IUserContext, id: string, entity: IUser): Promise<IUser>;
7
+ getById(userContext: IUserContext, id: string): Promise<IUser>;
8
+ get(userContext: IUserContext, queryOptions: IQueryOptions): Promise<IPagedResult<IUser>>;
9
+ getAll(userContext: IUserContext): Promise<IUser[]>;
10
+ preprocessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, allowId?: boolean): Promise<Partial<IUser>>;
11
+ private addAuthorizationsToUsers;
12
+ }
@@ -0,0 +1,65 @@
1
+ import { Value } from '@sinclair/typebox/value';
2
+ import { UserSpec, PublicUserSchema } from '@loomcore/common/models';
3
+ import { MultiTenantApiService } from '../index.js';
4
+ import { IdNotFoundError, ServerError } from '../../errors/index.js';
5
+ import { PostgresDatabase } from '../../databases/postgres/postgres.database.js';
6
+ export class UserService extends MultiTenantApiService {
7
+ constructor(database) {
8
+ super(database, 'users', 'user', UserSpec);
9
+ }
10
+ async fullUpdateById(userContext, id, entity) {
11
+ throw new ServerError('Cannot full update a user. Either use PATCH or /auth/change-password to update password.');
12
+ }
13
+ async getById(userContext, id) {
14
+ const { operations, queryObject } = this.prepareQuery(userContext, {}, []);
15
+ const user = await this.database.getById(operations, queryObject, id, this.pluralResourceName);
16
+ if (!user) {
17
+ throw new IdNotFoundError();
18
+ }
19
+ const usersWithAuth = await this.addAuthorizationsToUsers(userContext, [user]);
20
+ return usersWithAuth[0];
21
+ }
22
+ async get(userContext, queryOptions) {
23
+ const { operations, queryObject } = this.prepareQuery(userContext, queryOptions, []);
24
+ const pagedResult = await this.database.get(operations, queryObject, this.modelSpec, this.pluralResourceName);
25
+ const transformedEntities = await this.addAuthorizationsToUsers(userContext, pagedResult.entities || []);
26
+ return {
27
+ ...pagedResult,
28
+ entities: transformedEntities
29
+ };
30
+ }
31
+ async getAll(userContext) {
32
+ const { operations } = this.prepareQuery(userContext, {}, []);
33
+ const users = await this.database.getAll(operations, this.pluralResourceName);
34
+ return this.addAuthorizationsToUsers(userContext, users);
35
+ }
36
+ async preprocessEntity(userContext, entity, isCreate, allowId = false) {
37
+ const preparedEntity = await super.preprocessEntity(userContext, entity, isCreate);
38
+ if (preparedEntity.email) {
39
+ preparedEntity.email = preparedEntity.email.toLowerCase();
40
+ }
41
+ if (!isCreate) {
42
+ return Value.Clean(PublicUserSchema, preparedEntity);
43
+ }
44
+ return preparedEntity;
45
+ }
46
+ async addAuthorizationsToUsers(userContext, users) {
47
+ if (users.length === 0) {
48
+ return users;
49
+ }
50
+ if (!(this.database instanceof PostgresDatabase)) {
51
+ return users.map(user => this.postprocessEntity(userContext, user));
52
+ }
53
+ const userIds = users.map(user => user._id);
54
+ const orgId = userContext._orgId;
55
+ const authorizationsMap = await this.database.getUserAuthorizations(userIds, orgId);
56
+ return users.map(user => {
57
+ const authorizations = authorizationsMap.get(user._id) || [];
58
+ const userWithAuth = {
59
+ ...user,
60
+ authorizations
61
+ };
62
+ return this.postprocessEntity(userContext, userWithAuth);
63
+ });
64
+ }
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb or PostgreSQL",
6
6
  "scripts": {
@@ -53,7 +53,7 @@
53
53
  "qs": "^6.14.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@loomcore/common": "^0.0.27",
56
+ "@loomcore/common": "^0.0.28",
57
57
  "@sinclair/typebox": "0.34.33",
58
58
  "cookie-parser": "^1.4.6",
59
59
  "cors": "^2.8.5",
@@ -1,8 +0,0 @@
1
- import { IUser, IUserContext } from '@loomcore/common/models';
2
- import { MultiTenantApiService } from '../services/index.js';
3
- import { IDatabase } from '../databases/models/index.js';
4
- export declare class UserService extends MultiTenantApiService<IUser> {
5
- constructor(database: IDatabase);
6
- fullUpdateById(userContext: IUserContext, id: string, entity: IUser): Promise<IUser>;
7
- preprocessEntity(userContext: IUserContext, entity: Partial<IUser>, isCreate: boolean, allowId?: boolean): Promise<Partial<IUser>>;
8
- }
@@ -1,22 +0,0 @@
1
- import { Value } from '@sinclair/typebox/value';
2
- import { UserSpec, PublicUserSchema } from '@loomcore/common/models';
3
- import { MultiTenantApiService } from '../services/index.js';
4
- import { ServerError } from '../errors/index.js';
5
- export class UserService extends MultiTenantApiService {
6
- constructor(database) {
7
- super(database, 'users', 'user', UserSpec);
8
- }
9
- async fullUpdateById(userContext, id, entity) {
10
- throw new ServerError('Cannot full update a user. Either use PATCH or /auth/change-password to update password.');
11
- }
12
- async preprocessEntity(userContext, entity, isCreate, allowId = false) {
13
- const preparedEntity = await super.preprocessEntity(userContext, entity, isCreate);
14
- if (preparedEntity.email) {
15
- preparedEntity.email = preparedEntity.email.toLowerCase();
16
- }
17
- if (!isCreate) {
18
- return Value.Clean(PublicUserSchema, preparedEntity);
19
- }
20
- return preparedEntity;
21
- }
22
- }