hazo_auth 4.2.0 → 4.4.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.
- package/bin/hazo_auth.mjs +35 -0
- package/cli-src/assets/images/forgot_password_default.jpg +0 -0
- package/cli-src/assets/images/login_default.jpg +0 -0
- package/cli-src/assets/images/register_default.jpg +0 -0
- package/cli-src/assets/images/reset_password_default.jpg +0 -0
- package/cli-src/assets/images/verify_email_default.jpg +0 -0
- package/cli-src/cli/generate.ts +276 -0
- package/cli-src/cli/index.ts +207 -0
- package/cli-src/cli/init.ts +254 -0
- package/cli-src/cli/init_users.ts +376 -0
- package/cli-src/cli/validate.ts +581 -0
- package/cli-src/lib/already_logged_in_config.server.ts +46 -0
- package/cli-src/lib/app_logger.ts +24 -0
- package/cli-src/lib/auth/auth_cache.ts +220 -0
- package/cli-src/lib/auth/auth_rate_limiter.ts +121 -0
- package/cli-src/lib/auth/auth_types.ts +117 -0
- package/cli-src/lib/auth/auth_utils.server.ts +196 -0
- package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +583 -0
- package/cli-src/lib/auth/index.ts +23 -0
- package/cli-src/lib/auth/nextauth_config.ts +227 -0
- package/cli-src/lib/auth/org_cache.ts +148 -0
- package/cli-src/lib/auth/scope_cache.ts +233 -0
- package/cli-src/lib/auth/server_auth.ts +88 -0
- package/cli-src/lib/auth/session_token_validator.edge.ts +92 -0
- package/cli-src/lib/auth_utility_config.server.ts +136 -0
- package/cli-src/lib/config/config_loader.server.ts +164 -0
- package/cli-src/lib/config/default_config.ts +243 -0
- package/cli-src/lib/dev_lock_config.server.ts +148 -0
- package/cli-src/lib/email_verification_config.server.ts +63 -0
- package/cli-src/lib/file_types_config.server.ts +25 -0
- package/cli-src/lib/forgot_password_config.server.ts +63 -0
- package/cli-src/lib/hazo_connect_instance.server.ts +101 -0
- package/cli-src/lib/hazo_connect_setup.server.ts +194 -0
- package/cli-src/lib/hazo_connect_setup.ts +54 -0
- package/cli-src/lib/index.ts +46 -0
- package/cli-src/lib/login_config.server.ts +106 -0
- package/cli-src/lib/messages_config.server.ts +45 -0
- package/cli-src/lib/migrations/apply_migration.ts +105 -0
- package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
- package/cli-src/lib/my_settings_config.server.ts +135 -0
- package/cli-src/lib/oauth_config.server.ts +87 -0
- package/cli-src/lib/password_requirements_config.server.ts +40 -0
- package/cli-src/lib/profile_pic_menu_config.server.ts +138 -0
- package/cli-src/lib/profile_picture_config.server.ts +56 -0
- package/cli-src/lib/register_config.server.ts +101 -0
- package/cli-src/lib/reset_password_config.server.ts +103 -0
- package/cli-src/lib/scope_hierarchy_config.server.ts +151 -0
- package/cli-src/lib/services/email_service.ts +587 -0
- package/cli-src/lib/services/email_verification_service.ts +270 -0
- package/cli-src/lib/services/index.ts +16 -0
- package/cli-src/lib/services/login_service.ts +150 -0
- package/cli-src/lib/services/oauth_service.ts +494 -0
- package/cli-src/lib/services/org_service.ts +965 -0
- package/cli-src/lib/services/password_change_service.ts +154 -0
- package/cli-src/lib/services/password_reset_service.ts +418 -0
- package/cli-src/lib/services/profile_picture_remove_service.ts +120 -0
- package/cli-src/lib/services/profile_picture_service.ts +451 -0
- package/cli-src/lib/services/profile_picture_source_mapper.ts +62 -0
- package/cli-src/lib/services/registration_service.ts +185 -0
- package/cli-src/lib/services/scope_labels_service.ts +348 -0
- package/cli-src/lib/services/scope_service.ts +778 -0
- package/cli-src/lib/services/session_token_service.ts +178 -0
- package/cli-src/lib/services/token_service.ts +240 -0
- package/cli-src/lib/services/user_profiles_cache.ts +189 -0
- package/cli-src/lib/services/user_profiles_service.ts +264 -0
- package/cli-src/lib/services/user_scope_service.ts +554 -0
- package/cli-src/lib/services/user_update_service.ts +141 -0
- package/cli-src/lib/ui_shell_config.server.ts +73 -0
- package/cli-src/lib/ui_sizes_config.server.ts +37 -0
- package/cli-src/lib/user_fields_config.server.ts +31 -0
- package/cli-src/lib/user_management_config.server.ts +39 -0
- package/cli-src/lib/user_profiles_config.server.ts +55 -0
- package/cli-src/lib/utils/api_route_helpers.ts +60 -0
- package/cli-src/lib/utils/error_sanitizer.ts +75 -0
- package/cli-src/lib/utils/password_validator.ts +65 -0
- package/cli-src/lib/utils.ts +11 -0
- package/cli-src/server/logging/logger_service.ts +56 -0
- package/cli-src/server/types/app_types.ts +74 -0
- package/cli-src/server/types/express.d.ts +16 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/init_users.d.ts +17 -0
- package/dist/cli/init_users.d.ts.map +1 -0
- package/dist/cli/init_users.js +307 -0
- package/dist/components/layouts/dev_lock/index.d.ts +29 -0
- package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
- package/dist/components/layouts/dev_lock/index.js +60 -0
- package/dist/components/layouts/index.d.ts +2 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +1 -0
- package/dist/components/layouts/org_management/index.d.ts +26 -0
- package/dist/components/layouts/org_management/index.d.ts.map +1 -0
- package/dist/components/layouts/org_management/index.js +75 -0
- package/dist/components/layouts/shared/config/layout_customization.d.ts +2 -7
- package/dist/components/layouts/shared/config/layout_customization.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
- package/dist/components/layouts/user_management/index.d.ts +3 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +10 -4
- package/dist/lib/auth/auth_types.d.ts +6 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
- package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
- package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +61 -1
- package/dist/lib/auth/org_cache.d.ts +65 -0
- package/dist/lib/auth/org_cache.d.ts.map +1 -0
- package/dist/lib/auth/org_cache.js +103 -0
- package/dist/lib/config/default_config.d.ts +76 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +42 -0
- package/dist/lib/dev_lock_config.server.d.ts +41 -0
- package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
- package/dist/lib/dev_lock_config.server.js +50 -0
- package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
- package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
- package/dist/lib/multi_tenancy_config.server.js +41 -0
- package/dist/lib/services/org_service.d.ts +191 -0
- package/dist/lib/services/org_service.d.ts.map +1 -0
- package/dist/lib/services/org_service.js +746 -0
- package/dist/lib/utils/password_validator.d.ts +7 -1
- package/dist/lib/utils/password_validator.d.ts.map +1 -1
- package/dist/page_components/dev_lock.d.ts +11 -0
- package/dist/page_components/dev_lock.d.ts.map +1 -0
- package/dist/page_components/dev_lock.js +17 -0
- package/dist/page_components/index.d.ts +1 -0
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +1 -0
- package/dist/page_components/org_management.d.ts +27 -0
- package/dist/page_components/org_management.d.ts.map +1 -0
- package/dist/page_components/org_management.js +18 -0
- package/hazo_auth_config.example.ini +30 -0
- package/package.json +27 -3
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
// file_description: service for HRBAC scope operations using hazo_connect
|
|
2
|
+
// section: imports
|
|
3
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
4
|
+
import { createCrudService } from "hazo_connect/server";
|
|
5
|
+
import { create_app_logger } from "../app_logger.js";
|
|
6
|
+
import { sanitize_error_for_user } from "../utils/error_sanitizer.js";
|
|
7
|
+
|
|
8
|
+
// section: types
|
|
9
|
+
export type ScopeLevel =
|
|
10
|
+
| "hazo_scopes_l1"
|
|
11
|
+
| "hazo_scopes_l2"
|
|
12
|
+
| "hazo_scopes_l3"
|
|
13
|
+
| "hazo_scopes_l4"
|
|
14
|
+
| "hazo_scopes_l5"
|
|
15
|
+
| "hazo_scopes_l6"
|
|
16
|
+
| "hazo_scopes_l7";
|
|
17
|
+
|
|
18
|
+
export type ScopeRecord = {
|
|
19
|
+
id: string;
|
|
20
|
+
seq: string;
|
|
21
|
+
org: string;
|
|
22
|
+
name: string;
|
|
23
|
+
parent_scope_id?: string | null;
|
|
24
|
+
created_at: string;
|
|
25
|
+
changed_at: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ScopeServiceResult = {
|
|
29
|
+
success: boolean;
|
|
30
|
+
scope?: ScopeRecord;
|
|
31
|
+
scopes?: ScopeRecord[];
|
|
32
|
+
error?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type CreateScopeData = {
|
|
36
|
+
org: string;
|
|
37
|
+
name: string;
|
|
38
|
+
parent_scope_id?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type UpdateScopeData = {
|
|
42
|
+
name?: string;
|
|
43
|
+
parent_scope_id?: string | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// section: constants
|
|
47
|
+
export const SCOPE_LEVELS: ScopeLevel[] = [
|
|
48
|
+
"hazo_scopes_l1",
|
|
49
|
+
"hazo_scopes_l2",
|
|
50
|
+
"hazo_scopes_l3",
|
|
51
|
+
"hazo_scopes_l4",
|
|
52
|
+
"hazo_scopes_l5",
|
|
53
|
+
"hazo_scopes_l6",
|
|
54
|
+
"hazo_scopes_l7",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export const SCOPE_LEVEL_NUMBERS: Record<ScopeLevel, number> = {
|
|
58
|
+
hazo_scopes_l1: 1,
|
|
59
|
+
hazo_scopes_l2: 2,
|
|
60
|
+
hazo_scopes_l3: 3,
|
|
61
|
+
hazo_scopes_l4: 4,
|
|
62
|
+
hazo_scopes_l5: 5,
|
|
63
|
+
hazo_scopes_l6: 6,
|
|
64
|
+
hazo_scopes_l7: 7,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// section: helpers
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validates that the provided string is a valid scope level
|
|
71
|
+
*/
|
|
72
|
+
export function is_valid_scope_level(level: string): level is ScopeLevel {
|
|
73
|
+
return SCOPE_LEVELS.includes(level as ScopeLevel);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Gets the parent level for a given scope level
|
|
78
|
+
* Returns undefined for L1 (root level)
|
|
79
|
+
*/
|
|
80
|
+
export function get_parent_level(level: ScopeLevel): ScopeLevel | undefined {
|
|
81
|
+
const level_num = SCOPE_LEVEL_NUMBERS[level];
|
|
82
|
+
if (level_num === 1) return undefined;
|
|
83
|
+
return `hazo_scopes_l${level_num - 1}` as ScopeLevel;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Gets the child level for a given scope level
|
|
88
|
+
* Returns undefined for L7 (leaf level)
|
|
89
|
+
*/
|
|
90
|
+
export function get_child_level(level: ScopeLevel): ScopeLevel | undefined {
|
|
91
|
+
const level_num = SCOPE_LEVEL_NUMBERS[level];
|
|
92
|
+
if (level_num === 7) return undefined;
|
|
93
|
+
return `hazo_scopes_l${level_num + 1}` as ScopeLevel;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Gets all scopes for a given level, optionally filtered by organization
|
|
98
|
+
*/
|
|
99
|
+
export async function get_scopes_by_level(
|
|
100
|
+
adapter: HazoConnectAdapter,
|
|
101
|
+
level: ScopeLevel,
|
|
102
|
+
org?: string,
|
|
103
|
+
): Promise<ScopeServiceResult> {
|
|
104
|
+
try {
|
|
105
|
+
const scope_service = createCrudService(adapter, level);
|
|
106
|
+
|
|
107
|
+
let scopes: unknown[];
|
|
108
|
+
if (org) {
|
|
109
|
+
scopes = await scope_service.findBy({ org });
|
|
110
|
+
} else {
|
|
111
|
+
scopes = await scope_service.findBy({});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!Array.isArray(scopes)) {
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
scopes: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
scopes: scopes as ScopeRecord[],
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const logger = create_app_logger();
|
|
127
|
+
const error_message = sanitize_error_for_user(error, {
|
|
128
|
+
logToConsole: true,
|
|
129
|
+
logToLogger: true,
|
|
130
|
+
logger,
|
|
131
|
+
context: {
|
|
132
|
+
filename: "scope_service.ts",
|
|
133
|
+
line_number: 0,
|
|
134
|
+
operation: "get_scopes_by_level",
|
|
135
|
+
level,
|
|
136
|
+
org,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: false,
|
|
142
|
+
error: error_message,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets a single scope by ID
|
|
149
|
+
*/
|
|
150
|
+
export async function get_scope_by_id(
|
|
151
|
+
adapter: HazoConnectAdapter,
|
|
152
|
+
level: ScopeLevel,
|
|
153
|
+
scope_id: string,
|
|
154
|
+
): Promise<ScopeServiceResult> {
|
|
155
|
+
try {
|
|
156
|
+
const scope_service = createCrudService(adapter, level);
|
|
157
|
+
const scopes = await scope_service.findBy({ id: scope_id });
|
|
158
|
+
|
|
159
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: "Scope not found",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
scope: scopes[0] as ScopeRecord,
|
|
169
|
+
};
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const logger = create_app_logger();
|
|
172
|
+
const error_message = sanitize_error_for_user(error, {
|
|
173
|
+
logToConsole: true,
|
|
174
|
+
logToLogger: true,
|
|
175
|
+
logger,
|
|
176
|
+
context: {
|
|
177
|
+
filename: "scope_service.ts",
|
|
178
|
+
line_number: 0,
|
|
179
|
+
operation: "get_scope_by_id",
|
|
180
|
+
level,
|
|
181
|
+
scope_id,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
error: error_message,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Gets a single scope by seq (friendly ID)
|
|
194
|
+
*/
|
|
195
|
+
export async function get_scope_by_seq(
|
|
196
|
+
adapter: HazoConnectAdapter,
|
|
197
|
+
level: ScopeLevel,
|
|
198
|
+
seq: string,
|
|
199
|
+
): Promise<ScopeServiceResult> {
|
|
200
|
+
try {
|
|
201
|
+
const scope_service = createCrudService(adapter, level);
|
|
202
|
+
const scopes = await scope_service.findBy({ seq });
|
|
203
|
+
|
|
204
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: "Scope not found",
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: true,
|
|
213
|
+
scope: scopes[0] as ScopeRecord,
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const logger = create_app_logger();
|
|
217
|
+
const error_message = sanitize_error_for_user(error, {
|
|
218
|
+
logToConsole: true,
|
|
219
|
+
logToLogger: true,
|
|
220
|
+
logger,
|
|
221
|
+
context: {
|
|
222
|
+
filename: "scope_service.ts",
|
|
223
|
+
line_number: 0,
|
|
224
|
+
operation: "get_scope_by_seq",
|
|
225
|
+
level,
|
|
226
|
+
seq,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
success: false,
|
|
232
|
+
error: error_message,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Creates a new scope
|
|
239
|
+
* Note: The seq field is auto-generated by the database via hazo_scope_id_generator function
|
|
240
|
+
*/
|
|
241
|
+
export async function create_scope(
|
|
242
|
+
adapter: HazoConnectAdapter,
|
|
243
|
+
level: ScopeLevel,
|
|
244
|
+
data: CreateScopeData,
|
|
245
|
+
): Promise<ScopeServiceResult> {
|
|
246
|
+
try {
|
|
247
|
+
const scope_service = createCrudService(adapter, level);
|
|
248
|
+
const now = new Date().toISOString();
|
|
249
|
+
|
|
250
|
+
// Validate parent_scope_id is required for L2-L7
|
|
251
|
+
const parent_level = get_parent_level(level);
|
|
252
|
+
if (parent_level && !data.parent_scope_id) {
|
|
253
|
+
return {
|
|
254
|
+
success: false,
|
|
255
|
+
error: `parent_scope_id is required for ${level}`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Validate parent exists if provided
|
|
260
|
+
if (data.parent_scope_id && parent_level) {
|
|
261
|
+
const parent_result = await get_scope_by_id(
|
|
262
|
+
adapter,
|
|
263
|
+
parent_level,
|
|
264
|
+
data.parent_scope_id,
|
|
265
|
+
);
|
|
266
|
+
if (!parent_result.success) {
|
|
267
|
+
return {
|
|
268
|
+
success: false,
|
|
269
|
+
error: "Parent scope not found",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const insert_data: Record<string, unknown> = {
|
|
275
|
+
org: data.org,
|
|
276
|
+
name: data.name,
|
|
277
|
+
created_at: now,
|
|
278
|
+
changed_at: now,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if (data.parent_scope_id) {
|
|
282
|
+
insert_data.parent_scope_id = data.parent_scope_id;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const inserted = await scope_service.insert(insert_data);
|
|
286
|
+
|
|
287
|
+
if (!Array.isArray(inserted) || inserted.length === 0) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
error: "Failed to create scope",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
scope: inserted[0] as ScopeRecord,
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const logger = create_app_logger();
|
|
300
|
+
const error_message = sanitize_error_for_user(error, {
|
|
301
|
+
logToConsole: true,
|
|
302
|
+
logToLogger: true,
|
|
303
|
+
logger,
|
|
304
|
+
context: {
|
|
305
|
+
filename: "scope_service.ts",
|
|
306
|
+
line_number: 0,
|
|
307
|
+
operation: "create_scope",
|
|
308
|
+
level,
|
|
309
|
+
data,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
error: error_message,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Updates an existing scope
|
|
322
|
+
*/
|
|
323
|
+
export async function update_scope(
|
|
324
|
+
adapter: HazoConnectAdapter,
|
|
325
|
+
level: ScopeLevel,
|
|
326
|
+
scope_id: string,
|
|
327
|
+
data: UpdateScopeData,
|
|
328
|
+
): Promise<ScopeServiceResult> {
|
|
329
|
+
try {
|
|
330
|
+
const scope_service = createCrudService(adapter, level);
|
|
331
|
+
const now = new Date().toISOString();
|
|
332
|
+
|
|
333
|
+
// Check scope exists
|
|
334
|
+
const existing = await get_scope_by_id(adapter, level, scope_id);
|
|
335
|
+
if (!existing.success) {
|
|
336
|
+
return existing;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Validate parent if being changed
|
|
340
|
+
if (data.parent_scope_id !== undefined) {
|
|
341
|
+
const parent_level = get_parent_level(level);
|
|
342
|
+
if (parent_level && data.parent_scope_id) {
|
|
343
|
+
const parent_result = await get_scope_by_id(
|
|
344
|
+
adapter,
|
|
345
|
+
parent_level,
|
|
346
|
+
data.parent_scope_id,
|
|
347
|
+
);
|
|
348
|
+
if (!parent_result.success) {
|
|
349
|
+
return {
|
|
350
|
+
success: false,
|
|
351
|
+
error: "Parent scope not found",
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const update_data: Record<string, unknown> = {
|
|
358
|
+
changed_at: now,
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
if (data.name !== undefined) {
|
|
362
|
+
update_data.name = data.name;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (data.parent_scope_id !== undefined) {
|
|
366
|
+
update_data.parent_scope_id = data.parent_scope_id;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const updated = await scope_service.updateById(scope_id, update_data);
|
|
370
|
+
|
|
371
|
+
if (!Array.isArray(updated) || updated.length === 0) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: "Failed to update scope",
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
scope: updated[0] as ScopeRecord,
|
|
381
|
+
};
|
|
382
|
+
} catch (error) {
|
|
383
|
+
const logger = create_app_logger();
|
|
384
|
+
const error_message = sanitize_error_for_user(error, {
|
|
385
|
+
logToConsole: true,
|
|
386
|
+
logToLogger: true,
|
|
387
|
+
logger,
|
|
388
|
+
context: {
|
|
389
|
+
filename: "scope_service.ts",
|
|
390
|
+
line_number: 0,
|
|
391
|
+
operation: "update_scope",
|
|
392
|
+
level,
|
|
393
|
+
scope_id,
|
|
394
|
+
data,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error: error_message,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Deletes a scope (cascades to children via database FK constraints)
|
|
407
|
+
*/
|
|
408
|
+
export async function delete_scope(
|
|
409
|
+
adapter: HazoConnectAdapter,
|
|
410
|
+
level: ScopeLevel,
|
|
411
|
+
scope_id: string,
|
|
412
|
+
): Promise<ScopeServiceResult> {
|
|
413
|
+
try {
|
|
414
|
+
const scope_service = createCrudService(adapter, level);
|
|
415
|
+
|
|
416
|
+
// Check scope exists
|
|
417
|
+
const existing = await get_scope_by_id(adapter, level, scope_id);
|
|
418
|
+
if (!existing.success) {
|
|
419
|
+
return existing;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
await scope_service.deleteById(scope_id);
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
success: true,
|
|
426
|
+
scope: existing.scope,
|
|
427
|
+
};
|
|
428
|
+
} catch (error) {
|
|
429
|
+
const logger = create_app_logger();
|
|
430
|
+
const error_message = sanitize_error_for_user(error, {
|
|
431
|
+
logToConsole: true,
|
|
432
|
+
logToLogger: true,
|
|
433
|
+
logger,
|
|
434
|
+
context: {
|
|
435
|
+
filename: "scope_service.ts",
|
|
436
|
+
line_number: 0,
|
|
437
|
+
operation: "delete_scope",
|
|
438
|
+
level,
|
|
439
|
+
scope_id,
|
|
440
|
+
},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: error_message,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Gets immediate children of a scope
|
|
452
|
+
*/
|
|
453
|
+
export async function get_scope_children(
|
|
454
|
+
adapter: HazoConnectAdapter,
|
|
455
|
+
level: ScopeLevel,
|
|
456
|
+
scope_id: string,
|
|
457
|
+
): Promise<ScopeServiceResult> {
|
|
458
|
+
try {
|
|
459
|
+
const child_level = get_child_level(level);
|
|
460
|
+
if (!child_level) {
|
|
461
|
+
return {
|
|
462
|
+
success: true,
|
|
463
|
+
scopes: [], // L7 has no children
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const child_service = createCrudService(adapter, child_level);
|
|
468
|
+
const children = await child_service.findBy({ parent_scope_id: scope_id });
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
success: true,
|
|
472
|
+
scopes: Array.isArray(children) ? (children as ScopeRecord[]) : [],
|
|
473
|
+
};
|
|
474
|
+
} catch (error) {
|
|
475
|
+
const logger = create_app_logger();
|
|
476
|
+
const error_message = sanitize_error_for_user(error, {
|
|
477
|
+
logToConsole: true,
|
|
478
|
+
logToLogger: true,
|
|
479
|
+
logger,
|
|
480
|
+
context: {
|
|
481
|
+
filename: "scope_service.ts",
|
|
482
|
+
line_number: 0,
|
|
483
|
+
operation: "get_scope_children",
|
|
484
|
+
level,
|
|
485
|
+
scope_id,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
error: error_message,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Gets all ancestors of a scope up to L1 (root)
|
|
498
|
+
* Returns array ordered from immediate parent to root (L1)
|
|
499
|
+
*/
|
|
500
|
+
export async function get_scope_ancestors(
|
|
501
|
+
adapter: HazoConnectAdapter,
|
|
502
|
+
level: ScopeLevel,
|
|
503
|
+
scope_id: string,
|
|
504
|
+
): Promise<ScopeServiceResult> {
|
|
505
|
+
try {
|
|
506
|
+
const ancestors: ScopeRecord[] = [];
|
|
507
|
+
|
|
508
|
+
// Get the scope first
|
|
509
|
+
const scope_result = await get_scope_by_id(adapter, level, scope_id);
|
|
510
|
+
if (!scope_result.success || !scope_result.scope) {
|
|
511
|
+
return scope_result;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
let current_scope = scope_result.scope;
|
|
515
|
+
let current_level = level;
|
|
516
|
+
|
|
517
|
+
// Walk up the hierarchy
|
|
518
|
+
while (current_scope.parent_scope_id) {
|
|
519
|
+
const parent_level = get_parent_level(current_level);
|
|
520
|
+
if (!parent_level) break;
|
|
521
|
+
|
|
522
|
+
const parent_result = await get_scope_by_id(
|
|
523
|
+
adapter,
|
|
524
|
+
parent_level,
|
|
525
|
+
current_scope.parent_scope_id,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (!parent_result.success || !parent_result.scope) break;
|
|
529
|
+
|
|
530
|
+
ancestors.push(parent_result.scope);
|
|
531
|
+
current_scope = parent_result.scope;
|
|
532
|
+
current_level = parent_level;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
return {
|
|
536
|
+
success: true,
|
|
537
|
+
scopes: ancestors,
|
|
538
|
+
};
|
|
539
|
+
} catch (error) {
|
|
540
|
+
const logger = create_app_logger();
|
|
541
|
+
const error_message = sanitize_error_for_user(error, {
|
|
542
|
+
logToConsole: true,
|
|
543
|
+
logToLogger: true,
|
|
544
|
+
logger,
|
|
545
|
+
context: {
|
|
546
|
+
filename: "scope_service.ts",
|
|
547
|
+
line_number: 0,
|
|
548
|
+
operation: "get_scope_ancestors",
|
|
549
|
+
level,
|
|
550
|
+
scope_id,
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
success: false,
|
|
556
|
+
error: error_message,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Gets all descendants of a scope down to L7 (leaves)
|
|
563
|
+
* Returns flat array of all descendant scopes
|
|
564
|
+
*/
|
|
565
|
+
export async function get_scope_descendants(
|
|
566
|
+
adapter: HazoConnectAdapter,
|
|
567
|
+
level: ScopeLevel,
|
|
568
|
+
scope_id: string,
|
|
569
|
+
): Promise<ScopeServiceResult> {
|
|
570
|
+
try {
|
|
571
|
+
const descendants: ScopeRecord[] = [];
|
|
572
|
+
|
|
573
|
+
// Recursive function to get all children
|
|
574
|
+
async function collect_descendants(
|
|
575
|
+
current_level: ScopeLevel,
|
|
576
|
+
current_id: string,
|
|
577
|
+
): Promise<void> {
|
|
578
|
+
const children_result = await get_scope_children(
|
|
579
|
+
adapter,
|
|
580
|
+
current_level,
|
|
581
|
+
current_id,
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
if (children_result.success && children_result.scopes) {
|
|
585
|
+
for (const child of children_result.scopes) {
|
|
586
|
+
descendants.push(child);
|
|
587
|
+
const child_level = get_child_level(current_level);
|
|
588
|
+
if (child_level) {
|
|
589
|
+
await collect_descendants(child_level, child.id);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
await collect_descendants(level, scope_id);
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
success: true,
|
|
599
|
+
scopes: descendants,
|
|
600
|
+
};
|
|
601
|
+
} catch (error) {
|
|
602
|
+
const logger = create_app_logger();
|
|
603
|
+
const error_message = sanitize_error_for_user(error, {
|
|
604
|
+
logToConsole: true,
|
|
605
|
+
logToLogger: true,
|
|
606
|
+
logger,
|
|
607
|
+
context: {
|
|
608
|
+
filename: "scope_service.ts",
|
|
609
|
+
line_number: 0,
|
|
610
|
+
operation: "get_scope_descendants",
|
|
611
|
+
level,
|
|
612
|
+
scope_id,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
return {
|
|
617
|
+
success: false,
|
|
618
|
+
error: error_message,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Gets scope hierarchy tree for a given organization
|
|
625
|
+
* Returns nested structure starting from L1
|
|
626
|
+
*/
|
|
627
|
+
export type ScopeTreeNode = ScopeRecord & {
|
|
628
|
+
children?: ScopeTreeNode[];
|
|
629
|
+
level: ScopeLevel;
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Organization tree node that groups scopes by organization
|
|
634
|
+
*/
|
|
635
|
+
export type OrgScopeTreeNode = {
|
|
636
|
+
id: string;
|
|
637
|
+
name: string;
|
|
638
|
+
org: string;
|
|
639
|
+
isOrgNode: true;
|
|
640
|
+
children: ScopeTreeNode[];
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
export async function get_scope_tree(
|
|
644
|
+
adapter: HazoConnectAdapter,
|
|
645
|
+
org: string,
|
|
646
|
+
): Promise<{ success: boolean; tree?: ScopeTreeNode[]; error?: string }> {
|
|
647
|
+
try {
|
|
648
|
+
// Get all L1 scopes for this org
|
|
649
|
+
const l1_result = await get_scopes_by_level(adapter, "hazo_scopes_l1", org);
|
|
650
|
+
if (!l1_result.success || !l1_result.scopes) {
|
|
651
|
+
return l1_result;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Build tree recursively
|
|
655
|
+
async function build_tree(
|
|
656
|
+
scope: ScopeRecord,
|
|
657
|
+
level: ScopeLevel,
|
|
658
|
+
): Promise<ScopeTreeNode> {
|
|
659
|
+
const node: ScopeTreeNode = {
|
|
660
|
+
...scope,
|
|
661
|
+
level,
|
|
662
|
+
children: [],
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const children_result = await get_scope_children(adapter, level, scope.id);
|
|
666
|
+
if (children_result.success && children_result.scopes) {
|
|
667
|
+
const child_level = get_child_level(level);
|
|
668
|
+
if (child_level) {
|
|
669
|
+
for (const child of children_result.scopes) {
|
|
670
|
+
const child_node = await build_tree(child, child_level);
|
|
671
|
+
node.children!.push(child_node);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
return node;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const tree: ScopeTreeNode[] = [];
|
|
680
|
+
for (const l1_scope of l1_result.scopes) {
|
|
681
|
+
const node = await build_tree(l1_scope, "hazo_scopes_l1");
|
|
682
|
+
tree.push(node);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
success: true,
|
|
687
|
+
tree,
|
|
688
|
+
};
|
|
689
|
+
} catch (error) {
|
|
690
|
+
const logger = create_app_logger();
|
|
691
|
+
const error_message = sanitize_error_for_user(error, {
|
|
692
|
+
logToConsole: true,
|
|
693
|
+
logToLogger: true,
|
|
694
|
+
logger,
|
|
695
|
+
context: {
|
|
696
|
+
filename: "scope_service.ts",
|
|
697
|
+
line_number: 0,
|
|
698
|
+
operation: "get_scope_tree",
|
|
699
|
+
org,
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
success: false,
|
|
705
|
+
error: error_message,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Gets all scope trees across all organizations
|
|
712
|
+
* Returns trees for all L1 scopes (top level)
|
|
713
|
+
*/
|
|
714
|
+
export async function get_all_scope_trees(
|
|
715
|
+
adapter: HazoConnectAdapter,
|
|
716
|
+
): Promise<{ success: boolean; trees?: ScopeTreeNode[]; error?: string }> {
|
|
717
|
+
try {
|
|
718
|
+
// Get all L1 scopes (no org filter)
|
|
719
|
+
const l1_result = await get_scopes_by_level(adapter, "hazo_scopes_l1");
|
|
720
|
+
if (!l1_result.success || !l1_result.scopes) {
|
|
721
|
+
return { success: true, trees: [] };
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Build tree recursively
|
|
725
|
+
async function build_tree(
|
|
726
|
+
scope: ScopeRecord,
|
|
727
|
+
level: ScopeLevel,
|
|
728
|
+
): Promise<ScopeTreeNode> {
|
|
729
|
+
const node: ScopeTreeNode = {
|
|
730
|
+
...scope,
|
|
731
|
+
level,
|
|
732
|
+
children: [],
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
const children_result = await get_scope_children(adapter, level, scope.id);
|
|
736
|
+
if (children_result.success && children_result.scopes) {
|
|
737
|
+
const child_level = get_child_level(level);
|
|
738
|
+
if (child_level) {
|
|
739
|
+
for (const child of children_result.scopes) {
|
|
740
|
+
const child_node = await build_tree(child, child_level);
|
|
741
|
+
node.children!.push(child_node);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
return node;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Build trees for all L1 scopes
|
|
750
|
+
const trees: ScopeTreeNode[] = [];
|
|
751
|
+
for (const scope of l1_result.scopes) {
|
|
752
|
+
const scopeTree = await build_tree(scope, "hazo_scopes_l1");
|
|
753
|
+
trees.push(scopeTree);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
success: true,
|
|
758
|
+
trees,
|
|
759
|
+
};
|
|
760
|
+
} catch (error) {
|
|
761
|
+
const logger = create_app_logger();
|
|
762
|
+
const error_message = sanitize_error_for_user(error, {
|
|
763
|
+
logToConsole: true,
|
|
764
|
+
logToLogger: true,
|
|
765
|
+
logger,
|
|
766
|
+
context: {
|
|
767
|
+
filename: "scope_service.ts",
|
|
768
|
+
line_number: 0,
|
|
769
|
+
operation: "get_all_scope_trees",
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
success: false,
|
|
775
|
+
error: error_message,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|