@getstrata/bootstrap 0.2.66 → 0.4.2

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.
@@ -4,37 +4,61 @@ var __jsonParse = (a) => JSON.parse(a);
4
4
  // ../../src/bootstrap/createSpaRoutes.ts
5
5
  import { join } from "path";
6
6
  import { jsonResponse } from "@getstrata/core/http/response";
7
- import { isSpaEnabled } from "@getstrata/core/runtime/frontendMode";
7
+ import { isSpaEnabled, isViewsEnabled, readSpaPrefix } from "@getstrata/core/runtime/frontendMode";
8
8
  var SPA_DIST_DIRECTORY = join(process.cwd(), "frontend/dist");
9
- var SPA_INDEX_FILE = join(SPA_DIST_DIRECTORY, "index.html");
10
- function createSpaRoutes(_dependencies) {
11
- return {
12
- "/app/*": async (request) => {
13
- const pathname = new URL(request.url).pathname;
14
- const relativePath = pathname.replace(/^\/app\//, "");
15
- const assetFile = Bun.file(join(SPA_DIST_DIRECTORY, relativePath));
16
- if (relativePath.length > 0 && await assetFile.exists()) {
17
- return new Response(assetFile);
18
- }
19
- const indexFile = Bun.file(SPA_INDEX_FILE);
20
- if (await indexFile.exists()) {
21
- return new Response(indexFile, {
22
- headers: { "Content-Type": "text/html; charset=utf-8" }
23
- });
24
- }
25
- return jsonResponse({
26
- error: "SPA build not found. Run `bun run build:frontend`."
27
- }, { status: 503 });
28
- },
29
- "/": async () => Response.redirect("/app/", 302)
9
+ function relativeSpaPath(pathname, prefix) {
10
+ if (pathname === prefix || pathname === `${prefix}/`) {
11
+ return "";
12
+ }
13
+ if (pathname.startsWith(`${prefix}/`)) {
14
+ return pathname.slice(prefix.length + 1);
15
+ }
16
+ return "";
17
+ }
18
+ function createSpaDocumentHandler(prefix, distDirectory) {
19
+ const indexFilePath = join(distDirectory, "index.html");
20
+ return async (request) => {
21
+ const pathname = new URL(request.url).pathname;
22
+ if (pathname.startsWith("/api/")) {
23
+ return new Response("Not found", { status: 404 });
24
+ }
25
+ const relativePath = relativeSpaPath(pathname, prefix);
26
+ const assetFile = Bun.file(join(distDirectory, relativePath));
27
+ if (relativePath.length > 0 && await assetFile.exists()) {
28
+ return new Response(assetFile);
29
+ }
30
+ const indexFile = Bun.file(indexFilePath);
31
+ if (await indexFile.exists()) {
32
+ return new Response(indexFile, {
33
+ headers: { "Content-Type": "text/html; charset=utf-8" }
34
+ });
35
+ }
36
+ return jsonResponse({
37
+ error: "SPA build not found. Run `bun run frontend:build` in your app."
38
+ }, { status: 503 });
30
39
  };
31
40
  }
32
- function mergeSpaRoutes(dependencies, routes) {
41
+ function createSpaRoutes(_dependencies, options = {}) {
42
+ const prefix = options.prefix ?? readSpaPrefix();
43
+ const distDirectory = options.distDirectory ?? SPA_DIST_DIRECTORY;
44
+ const wrap = options.wrap ?? ((handler2) => handler2);
45
+ const handler = wrap(createSpaDocumentHandler(prefix, distDirectory));
46
+ const routes = {
47
+ [prefix]: handler,
48
+ [`${prefix}/`]: handler,
49
+ [`${prefix}/*`]: handler
50
+ };
51
+ if (!isViewsEnabled()) {
52
+ routes["/"] = async () => Response.redirect(`${prefix}/`, 302);
53
+ }
54
+ return routes;
55
+ }
56
+ function mergeSpaRoutes(dependencies, routes, options) {
33
57
  if (!isSpaEnabled()) {
34
58
  return routes;
35
59
  }
36
60
  return {
37
- ...createSpaRoutes(dependencies),
61
+ ...createSpaRoutes(dependencies, options),
38
62
  ...routes
39
63
  };
40
64
  }
@@ -36,7 +36,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
36
36
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
37
37
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
38
38
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
39
- import { withErrorHandling } from "@getstrata/core/http/response";
39
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
40
40
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
41
41
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
42
42
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -184,7 +184,7 @@ class HttpKernel {
184
184
  return withMiddleware(...middleware)(handler);
185
185
  }
186
186
  wrapApi(handler) {
187
- return this.wrap(["api", "authenticated"], handler);
187
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
188
188
  }
189
189
  wrapWeb(handler) {
190
190
  return withErrorHandling(this.wrap("web", handler));
@@ -349,7 +349,12 @@ function readDiscoverModulesState() {
349
349
  return state;
350
350
  }
351
351
  function configureModulesDirectory(modulesDir) {
352
- readDiscoverModulesState().configuredModulesDir = modulesDir;
352
+ const state = readDiscoverModulesState();
353
+ if (state.configuredModulesDir !== modulesDir) {
354
+ state.appModules.length = 0;
355
+ state.modulesReady = undefined;
356
+ }
357
+ state.configuredModulesDir = modulesDir;
353
358
  }
354
359
  function resolveModulesDirectory(options) {
355
360
  const state = readDiscoverModulesState();
@@ -506,9 +511,10 @@ function buildWebModuleRoutes(dependencies, options = {}) {
506
511
  function registerRoute(method, path, middleware) {
507
512
  routeRegistry.register({ method, path, middleware });
508
513
  }
509
- function createWebRoutes(dependencies) {
514
+ function createWebRoutes(dependencies, options = {}) {
510
515
  const wrappedRoutes = buildWebModuleRoutes(dependencies, {
511
- clearRegistry: false
516
+ clearRegistry: false,
517
+ modules: options.modules
512
518
  });
513
519
  registerRoute("GET", "/", ["global", "web"]);
514
520
  wrappedRoutes["/assets/*"] = async (request) => {
@@ -524,12 +530,12 @@ function createWebRoutes(dependencies) {
524
530
  registerRoute("GET", "/assets/*", ["global", "web"]);
525
531
  return wrappedRoutes;
526
532
  }
527
- function mergeWebRoutes(dependencies, routes) {
533
+ function mergeWebRoutes(dependencies, routes, options = {}) {
528
534
  if (!isViewsEnabled2()) {
529
535
  return routes;
530
536
  }
531
537
  return {
532
- ...createWebRoutes(dependencies),
538
+ ...createWebRoutes(dependencies, options),
533
539
  ...routes
534
540
  };
535
541
  }
@@ -40,7 +40,12 @@ function readDiscoverModulesState() {
40
40
  return state;
41
41
  }
42
42
  function configureModulesDirectory(modulesDir) {
43
- readDiscoverModulesState().configuredModulesDir = modulesDir;
43
+ const state = readDiscoverModulesState();
44
+ if (state.configuredModulesDir !== modulesDir) {
45
+ state.appModules.length = 0;
46
+ state.modulesReady = undefined;
47
+ }
48
+ state.configuredModulesDir = modulesDir;
44
49
  }
45
50
  function resolveModulesDirectory(options) {
46
51
  const state = readDiscoverModulesState();
@@ -91,17 +96,19 @@ function resetDiscoverModulesForTests() {
91
96
  state.modulesReady = undefined;
92
97
  }
93
98
  // ../../src/bootstrap/providers/auth.ts
99
+ import { BasicAuthGuard } from "@getstrata/core/auth/basicAuthGuard";
94
100
  import {
95
101
  AuthManager,
96
102
  CompositeGuard,
97
103
  DatabaseTokenGuard,
98
104
  GuestGuard
99
105
  } from "@getstrata/core/auth/guard";
106
+ import { JwtGuard } from "@getstrata/core/auth/jwtGuard";
100
107
  import { SessionGuard } from "@getstrata/core/auth/sessionGuard";
101
108
 
102
109
  // ../../src/config/auth.ts
103
110
  var authConfig = {
104
- allowDevHeaders: (process.env.AUTH_DEV_HEADERS ?? "true") !== "false",
111
+ allowDevHeaders: process.env.AUTH_DEV_HEADERS === "true",
105
112
  tokenDefaultAbilities: ["*"]
106
113
  };
107
114
 
@@ -136,11 +143,22 @@ var authProvider = {
136
143
  name: "core.auth",
137
144
  register({ container, config }) {
138
145
  config.set("auth.allowDevHeaders", authConfig.allowDevHeaders);
139
- const guards = [new DatabaseTokenGuard(container), new SessionGuard(container)];
146
+ const apiGuard = new DatabaseTokenGuard(container);
147
+ const sessionGuard = new SessionGuard(container);
148
+ const jwtGuard = new JwtGuard;
149
+ const basicGuard = new BasicAuthGuard(container);
150
+ const guards = [apiGuard, jwtGuard, basicGuard, sessionGuard];
140
151
  if (authConfig.allowDevHeaders) {
141
152
  guards.push(new GuestGuard);
142
153
  }
143
- container.set(CORE_AUTH_TOKEN, new AuthManager(new CompositeGuard(guards)));
154
+ const auth = new AuthManager(new CompositeGuard(guards));
155
+ auth.registerGuard("api", apiGuard);
156
+ auth.registerGuard("access_token", apiGuard);
157
+ auth.registerGuard("jwt", jwtGuard);
158
+ auth.registerGuard("basic", basicGuard);
159
+ auth.registerGuard("web", sessionGuard);
160
+ auth.registerGuard("session", sessionGuard);
161
+ container.set(CORE_AUTH_TOKEN, auth);
144
162
  }
145
163
  };
146
164
  var auth_default = authProvider;
@@ -201,8 +219,12 @@ var queueConfig = {
201
219
  };
202
220
  // ../../src/bootstrap/env.ts
203
221
  import { defineEnvSchema } from "@getstrata/core/config/envSchema";
222
+ import { DEFAULT_SPA_PREFIX, FRONTEND_MODE_PATTERN } from "@getstrata/core/runtime/frontendMode";
204
223
  var appEnvSchema = defineEnvSchema({
205
- DATABASE_URL: { required: true, pattern: /^postgres(ql)?:\/\// },
224
+ DATABASE_URL: {
225
+ required: true,
226
+ pattern: /^(postgres(ql)?|mysql|sqlite):\/\//i
227
+ },
206
228
  PORT: {
207
229
  integer: true,
208
230
  minimum: 1,
@@ -230,9 +252,21 @@ var appEnvSchema = defineEnvSchema({
230
252
  pattern: /^(sync|async|redis)$/
231
253
  },
232
254
  AUTH_DEV_HEADERS: {
233
- default: "true",
255
+ default: "false",
234
256
  pattern: /^(true|false|0|1)$/
235
257
  },
258
+ DB_CONNECTION: {
259
+ default: "",
260
+ pattern: /^(pgsql|postgres|postgresql|mysql|mariadb|sqlite)?$/i
261
+ },
262
+ AUTH_DEFAULT_GUARD: {
263
+ default: "web"
264
+ },
265
+ JWT_TTL_SECONDS: {
266
+ integer: true,
267
+ minimum: 60,
268
+ default: "3600"
269
+ },
236
270
  APP_ENV: {
237
271
  default: "local"
238
272
  },
@@ -242,6 +276,13 @@ var appEnvSchema = defineEnvSchema({
242
276
  APP_URL: {
243
277
  default: "http://localhost:3000"
244
278
  },
279
+ FRONTEND_MODE: {
280
+ default: "api",
281
+ pattern: FRONTEND_MODE_PATTERN
282
+ },
283
+ SPA_PREFIX: {
284
+ default: DEFAULT_SPA_PREFIX
285
+ },
245
286
  API_PREFIX: {
246
287
  default: "/api/v1"
247
288
  },
@@ -480,7 +521,6 @@ var storageProvider = {
480
521
  var storage_default = storageProvider;
481
522
 
482
523
  // ../../src/bootstrap/providers/view.ts
483
- import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
484
524
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
485
525
  import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
486
526
  import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
@@ -492,148 +532,6 @@ import {
492
532
  errorTemplateName,
493
533
  resolveWebLayoutData
494
534
  } from "@getstrata/core/view";
495
-
496
- // ../../src/modules/organization/repository.ts
497
- import { BaseRepository } from "@getstrata/core/database/baseRepository";
498
- import { NotFoundError } from "@getstrata/core/errors/http";
499
- import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
500
-
501
- // ../../src/modules/organization/table.ts
502
- import { defineTable } from "@getstrata/core/database/table";
503
-
504
- // ../../src/domain/workhub.ts
505
- var ORGANIZATION_TABLE = "organization";
506
-
507
- // ../../src/modules/organization/table.ts
508
- var organizationTable = defineTable({
509
- name: ORGANIZATION_TABLE,
510
- primaryKey: "id",
511
- columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
512
- softDeletes: true,
513
- defaultOrderBy: { column: "id", direction: "ASC" }
514
- });
515
-
516
- // ../../src/modules/organization/repository.ts
517
- class OrganizationRepository extends BaseRepository {
518
- constructor() {
519
- super(organizationTable);
520
- }
521
- async findBySlug(slug) {
522
- return await this.firstOrNull({ slug });
523
- }
524
- async listForTenant(options) {
525
- return await this.findAll({
526
- limit: options.limit,
527
- offset: options.offset,
528
- where: { tenant_id: options.tenantId ?? currentTenantId() }
529
- });
530
- }
531
- async countForTenant(tenantId = currentTenantId()) {
532
- return await this.countWhere({ tenant_id: tenantId });
533
- }
534
- async findForTenantOrThrow(id, tenantId = currentTenantId()) {
535
- const organization = await this.findById(id);
536
- if (!organization || organization.tenant_id !== tenantId) {
537
- throw new NotFoundError(`SCIM group ${id} not found.`);
538
- }
539
- return organization;
540
- }
541
- }
542
- var repository_default = OrganizationRepository;
543
-
544
- // ../../src/modules/user/repository.ts
545
- import {
546
- emailLookupForQuery,
547
- protectEmail,
548
- revealEmail
549
- } from "@getstrata/core/crypto/fieldEncryption";
550
- import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
551
- import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
552
- import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
553
-
554
- // ../../src/modules/user/table.ts
555
- import { defineTable as defineTable2 } from "@getstrata/core/database/table";
556
- var userTable = defineTable2({
557
- name: "users",
558
- primaryKey: "id",
559
- columns: [
560
- "id",
561
- "name",
562
- "email",
563
- "email_lookup",
564
- "role",
565
- "tenant_id",
566
- "password_hash",
567
- "email_verified_at",
568
- "mfa_secret",
569
- "mfa_enabled",
570
- "mfa_recovery_codes",
571
- "profile_photo_path",
572
- "session_valid_after",
573
- "current_organization_id",
574
- "created_at",
575
- "updated_at"
576
- ],
577
- defaultOrderBy: { column: "id", direction: "ASC" }
578
- });
579
-
580
- // ../../src/modules/user/repository.ts
581
- class UserRepository extends BaseRepository2 {
582
- constructor() {
583
- super(userTable);
584
- }
585
- decode(record) {
586
- return {
587
- ...record,
588
- email: revealEmail(record.email),
589
- mfa_secret: revealMfaSecret(record.mfa_secret)
590
- };
591
- }
592
- async findById(id) {
593
- const record = await super.findById(id);
594
- return record ? this.decode(record) : null;
595
- }
596
- async findAll(options = {}) {
597
- const records = await super.findAll(options);
598
- return records.map((record) => this.decode(record));
599
- }
600
- async create(values) {
601
- const email = values.email;
602
- if (!email) {
603
- throw new Error("Email is required.");
604
- }
605
- const protectedEmail = protectEmail(email);
606
- const record = await super.create({
607
- ...values,
608
- tenant_id: values.tenant_id ?? currentTenantId2(),
609
- email: protectedEmail.storedEmail,
610
- email_lookup: protectedEmail.emailLookup,
611
- password_hash: values.password_hash ?? ""
612
- });
613
- return this.decode(record);
614
- }
615
- async updateByIdOrThrow(id, values, errorFactory) {
616
- const changes = { ...values };
617
- if (values.email !== undefined) {
618
- const protectedEmail = protectEmail(values.email);
619
- changes.email = protectedEmail.storedEmail;
620
- changes.email_lookup = protectedEmail.emailLookup;
621
- }
622
- const record = await super.updateByIdOrThrow(id, changes, errorFactory);
623
- return this.decode(record);
624
- }
625
- async findByEmail(email) {
626
- const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
627
- const record = records[0];
628
- return record ? this.decode(record) : null;
629
- }
630
- async countForTenant(tenantId = currentTenantId2()) {
631
- return await this.countWhere({ tenant_id: tenantId });
632
- }
633
- }
634
- var repository_default2 = UserRepository;
635
-
636
- // ../../src/bootstrap/providers/view.ts
637
535
  var CORE_VIEW_TOKEN = "core.view";
638
536
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
639
537
  var viewProvider = {
@@ -647,23 +545,11 @@ var viewProvider = {
647
545
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
648
546
  container.set(CORE_VIEW_TOKEN, engine);
649
547
  configureWebLayoutData({
650
- extra: async (user) => {
651
- const appName = appDisplayName();
652
- if (!user || typeof user.id !== "number") {
653
- return { appName, currentOrganization: null, organizations: [] };
654
- }
655
- try {
656
- const record = await new repository_default2().findByIdOrThrow(user.id);
657
- const memberships = await resolveMembershipLookup().listForUser(record.id);
658
- const organizationsRepo = new repository_default;
659
- const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
660
- const currentId = record.current_organization_id ?? null;
661
- const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
662
- return { appName, currentOrganization, organizations };
663
- } catch {
664
- return { appName, currentOrganization: null, organizations: [] };
665
- }
666
- }
548
+ extra: async () => ({
549
+ appName: appDisplayName(),
550
+ currentOrganization: null,
551
+ organizations: []
552
+ })
667
553
  });
668
554
  configureWebErrorView({
669
555
  render: async (input) => engine.render(errorTemplateName(input.status), {
@@ -16,7 +16,12 @@ function readDiscoverModulesState() {
16
16
  return state;
17
17
  }
18
18
  function configureModulesDirectory(modulesDir) {
19
- readDiscoverModulesState().configuredModulesDir = modulesDir;
19
+ const state = readDiscoverModulesState();
20
+ if (state.configuredModulesDir !== modulesDir) {
21
+ state.appModules.length = 0;
22
+ state.modulesReady = undefined;
23
+ }
24
+ state.configuredModulesDir = modulesDir;
20
25
  }
21
26
  function resolveModulesDirectory(options) {
22
27
  const state = readDiscoverModulesState();
@@ -24,7 +24,7 @@ import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/require
24
24
  import { createRequirePasswordConfirmMiddleware } from "@getstrata/core/http/requirePasswordConfirmMiddleware";
25
25
  import { createRequireVerifiedMiddleware } from "@getstrata/core/http/requireVerifiedMiddleware";
26
26
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
27
- import { withErrorHandling } from "@getstrata/core/http/response";
27
+ import { withErrorHandling, withJsonErrorHandling } from "@getstrata/core/http/response";
28
28
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
29
29
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
30
30
  import { createValidateSignatureMiddleware } from "@getstrata/core/http/signedUrl";
@@ -172,7 +172,7 @@ class HttpKernel {
172
172
  return withMiddleware(...middleware)(handler);
173
173
  }
174
174
  wrapApi(handler) {
175
- return this.wrap(["api", "authenticated"], handler);
175
+ return withJsonErrorHandling(this.wrap(["api", "authenticated"], handler));
176
176
  }
177
177
  wrapWeb(handler) {
178
178
  return withErrorHandling(this.wrap("web", handler));
@@ -34,7 +34,12 @@ function readDiscoverModulesState() {
34
34
  return state;
35
35
  }
36
36
  function configureModulesDirectory(modulesDir) {
37
- readDiscoverModulesState().configuredModulesDir = modulesDir;
37
+ const state = readDiscoverModulesState();
38
+ if (state.configuredModulesDir !== modulesDir) {
39
+ state.appModules.length = 0;
40
+ state.modulesReady = undefined;
41
+ }
42
+ state.configuredModulesDir = modulesDir;
38
43
  }
39
44
  function resolveModulesDirectory(options) {
40
45
  const state = readDiscoverModulesState();
@@ -2,7 +2,6 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/providers/view.ts
5
- import { resolveMembershipLookup } from "@getstrata/core/auth/membershipContext";
6
5
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
7
6
  import { appDisplayName } from "@getstrata/core/runtime/appKeyPrefix";
8
7
  import { isViewsEnabled } from "@getstrata/core/runtime/frontendMode";
@@ -14,148 +13,6 @@ import {
14
13
  errorTemplateName,
15
14
  resolveWebLayoutData
16
15
  } from "@getstrata/core/view";
17
-
18
- // ../../src/modules/organization/repository.ts
19
- import { BaseRepository } from "@getstrata/core/database/baseRepository";
20
- import { NotFoundError } from "@getstrata/core/errors/http";
21
- import { currentTenantId } from "@getstrata/core/tenant/tenantContext";
22
-
23
- // ../../src/modules/organization/table.ts
24
- import { defineTable } from "@getstrata/core/database/table";
25
-
26
- // ../../src/domain/workhub.ts
27
- var ORGANIZATION_TABLE = "organization";
28
-
29
- // ../../src/modules/organization/table.ts
30
- var organizationTable = defineTable({
31
- name: ORGANIZATION_TABLE,
32
- primaryKey: "id",
33
- columns: ["id", "tenant_id", "name", "slug", "created_at", "updated_at", "deleted_at"],
34
- softDeletes: true,
35
- defaultOrderBy: { column: "id", direction: "ASC" }
36
- });
37
-
38
- // ../../src/modules/organization/repository.ts
39
- class OrganizationRepository extends BaseRepository {
40
- constructor() {
41
- super(organizationTable);
42
- }
43
- async findBySlug(slug) {
44
- return await this.firstOrNull({ slug });
45
- }
46
- async listForTenant(options) {
47
- return await this.findAll({
48
- limit: options.limit,
49
- offset: options.offset,
50
- where: { tenant_id: options.tenantId ?? currentTenantId() }
51
- });
52
- }
53
- async countForTenant(tenantId = currentTenantId()) {
54
- return await this.countWhere({ tenant_id: tenantId });
55
- }
56
- async findForTenantOrThrow(id, tenantId = currentTenantId()) {
57
- const organization = await this.findById(id);
58
- if (!organization || organization.tenant_id !== tenantId) {
59
- throw new NotFoundError(`SCIM group ${id} not found.`);
60
- }
61
- return organization;
62
- }
63
- }
64
- var repository_default = OrganizationRepository;
65
-
66
- // ../../src/modules/user/repository.ts
67
- import {
68
- emailLookupForQuery,
69
- protectEmail,
70
- revealEmail
71
- } from "@getstrata/core/crypto/fieldEncryption";
72
- import { revealMfaSecret } from "@getstrata/core/crypto/mfaSecret";
73
- import { BaseRepository as BaseRepository2 } from "@getstrata/core/database/baseRepository";
74
- import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
75
-
76
- // ../../src/modules/user/table.ts
77
- import { defineTable as defineTable2 } from "@getstrata/core/database/table";
78
- var userTable = defineTable2({
79
- name: "users",
80
- primaryKey: "id",
81
- columns: [
82
- "id",
83
- "name",
84
- "email",
85
- "email_lookup",
86
- "role",
87
- "tenant_id",
88
- "password_hash",
89
- "email_verified_at",
90
- "mfa_secret",
91
- "mfa_enabled",
92
- "mfa_recovery_codes",
93
- "profile_photo_path",
94
- "session_valid_after",
95
- "current_organization_id",
96
- "created_at",
97
- "updated_at"
98
- ],
99
- defaultOrderBy: { column: "id", direction: "ASC" }
100
- });
101
-
102
- // ../../src/modules/user/repository.ts
103
- class UserRepository extends BaseRepository2 {
104
- constructor() {
105
- super(userTable);
106
- }
107
- decode(record) {
108
- return {
109
- ...record,
110
- email: revealEmail(record.email),
111
- mfa_secret: revealMfaSecret(record.mfa_secret)
112
- };
113
- }
114
- async findById(id) {
115
- const record = await super.findById(id);
116
- return record ? this.decode(record) : null;
117
- }
118
- async findAll(options = {}) {
119
- const records = await super.findAll(options);
120
- return records.map((record) => this.decode(record));
121
- }
122
- async create(values) {
123
- const email = values.email;
124
- if (!email) {
125
- throw new Error("Email is required.");
126
- }
127
- const protectedEmail = protectEmail(email);
128
- const record = await super.create({
129
- ...values,
130
- tenant_id: values.tenant_id ?? currentTenantId2(),
131
- email: protectedEmail.storedEmail,
132
- email_lookup: protectedEmail.emailLookup,
133
- password_hash: values.password_hash ?? ""
134
- });
135
- return this.decode(record);
136
- }
137
- async updateByIdOrThrow(id, values, errorFactory) {
138
- const changes = { ...values };
139
- if (values.email !== undefined) {
140
- const protectedEmail = protectEmail(values.email);
141
- changes.email = protectedEmail.storedEmail;
142
- changes.email_lookup = protectedEmail.emailLookup;
143
- }
144
- const record = await super.updateByIdOrThrow(id, changes, errorFactory);
145
- return this.decode(record);
146
- }
147
- async findByEmail(email) {
148
- const records = await this.findWhere({ email_lookup: emailLookupForQuery(email) }, { limit: 1 });
149
- const record = records[0];
150
- return record ? this.decode(record) : null;
151
- }
152
- async countForTenant(tenantId = currentTenantId2()) {
153
- return await this.countWhere({ tenant_id: tenantId });
154
- }
155
- }
156
- var repository_default2 = UserRepository;
157
-
158
- // ../../src/bootstrap/providers/view.ts
159
16
  var CORE_VIEW_TOKEN = "core.view";
160
17
  var VIEW_DIRECTORY_CONFIG_KEY = "view.directory";
161
18
  var viewProvider = {
@@ -169,23 +26,11 @@ var viewProvider = {
169
26
  const engine = new EtaViewEngine(viewsDirectory, (request) => resolveWebLayoutData(container, request ?? currentRequestMeta().request));
170
27
  container.set(CORE_VIEW_TOKEN, engine);
171
28
  configureWebLayoutData({
172
- extra: async (user) => {
173
- const appName = appDisplayName();
174
- if (!user || typeof user.id !== "number") {
175
- return { appName, currentOrganization: null, organizations: [] };
176
- }
177
- try {
178
- const record = await new repository_default2().findByIdOrThrow(user.id);
179
- const memberships = await resolveMembershipLookup().listForUser(record.id);
180
- const organizationsRepo = new repository_default;
181
- const organizations = (await Promise.all(memberships.map((membership) => organizationsRepo.findById(membership.organization_id)))).filter((organization) => Boolean(organization && !organization.deleted_at));
182
- const currentId = record.current_organization_id ?? null;
183
- const currentOrganization = organizations.find((organization) => organization.id === currentId) ?? organizations[0] ?? null;
184
- return { appName, currentOrganization, organizations };
185
- } catch {
186
- return { appName, currentOrganization: null, organizations: [] };
187
- }
188
- }
29
+ extra: async () => ({
30
+ appName: appDisplayName(),
31
+ currentOrganization: null,
32
+ organizations: []
33
+ })
189
34
  });
190
35
  configureWebErrorView({
191
36
  render: async (input) => engine.render(errorTemplateName(input.status), {