alepha 0.13.5 → 0.13.7

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 (136) hide show
  1. package/dist/api-audits/index.browser.js +116 -0
  2. package/dist/api-audits/index.browser.js.map +1 -0
  3. package/dist/api-audits/index.d.ts +1194 -0
  4. package/dist/api-audits/index.js +674 -0
  5. package/dist/api-audits/index.js.map +1 -0
  6. package/dist/api-notifications/index.d.ts +147 -147
  7. package/dist/api-parameters/index.browser.js +36 -5
  8. package/dist/api-parameters/index.browser.js.map +1 -1
  9. package/dist/api-parameters/index.d.ts +711 -33
  10. package/dist/api-parameters/index.js +831 -17
  11. package/dist/api-parameters/index.js.map +1 -1
  12. package/dist/api-users/index.d.ts +16 -3
  13. package/dist/api-users/index.js +699 -19
  14. package/dist/api-users/index.js.map +1 -1
  15. package/dist/api-verifications/index.js +2 -1
  16. package/dist/api-verifications/index.js.map +1 -1
  17. package/dist/bin/index.js +1 -0
  18. package/dist/bin/index.js.map +1 -1
  19. package/dist/cli/index.d.ts +85 -31
  20. package/dist/cli/index.js +205 -33
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/command/index.d.ts +67 -6
  23. package/dist/command/index.js +30 -3
  24. package/dist/command/index.js.map +1 -1
  25. package/dist/core/index.browser.js +241 -61
  26. package/dist/core/index.browser.js.map +1 -1
  27. package/dist/core/index.d.ts +170 -90
  28. package/dist/core/index.js +264 -67
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/core/index.native.js +248 -65
  31. package/dist/core/index.native.js.map +1 -1
  32. package/dist/email/index.js +15 -10554
  33. package/dist/email/index.js.map +1 -1
  34. package/dist/logger/index.d.ts +4 -4
  35. package/dist/logger/index.js +77 -72
  36. package/dist/logger/index.js.map +1 -1
  37. package/dist/orm/index.d.ts +5 -1
  38. package/dist/orm/index.js +24 -7
  39. package/dist/orm/index.js.map +1 -1
  40. package/dist/queue/index.d.ts +4 -4
  41. package/dist/redis/index.d.ts +10 -10
  42. package/dist/security/index.d.ts +28 -28
  43. package/dist/server/index.d.ts +10 -1
  44. package/dist/server/index.js +20 -6
  45. package/dist/server/index.js.map +1 -1
  46. package/dist/server-auth/index.d.ts +163 -152
  47. package/dist/server-auth/index.js +40 -10
  48. package/dist/server-auth/index.js.map +1 -1
  49. package/dist/server-cookies/index.js +5 -1
  50. package/dist/server-cookies/index.js.map +1 -1
  51. package/dist/server-links/index.d.ts +33 -33
  52. package/dist/server-security/index.d.ts +9 -9
  53. package/dist/thread/index.js +2 -2
  54. package/dist/thread/index.js.map +1 -1
  55. package/dist/vite/index.d.ts +2 -2
  56. package/dist/vite/index.js +102 -45
  57. package/dist/vite/index.js.map +1 -1
  58. package/dist/websocket/index.browser.js +3 -3
  59. package/dist/websocket/index.browser.js.map +1 -1
  60. package/dist/websocket/index.d.ts +7 -7
  61. package/dist/websocket/index.js +4 -4
  62. package/dist/websocket/index.js.map +1 -1
  63. package/package.json +14 -9
  64. package/src/api-audits/controllers/AuditController.ts +186 -0
  65. package/src/api-audits/entities/audits.ts +132 -0
  66. package/src/api-audits/index.browser.ts +18 -0
  67. package/src/api-audits/index.ts +58 -0
  68. package/src/api-audits/primitives/$audit.ts +159 -0
  69. package/src/api-audits/schemas/auditQuerySchema.ts +23 -0
  70. package/src/api-audits/schemas/auditResourceSchema.ts +9 -0
  71. package/src/api-audits/schemas/createAuditSchema.ts +27 -0
  72. package/src/api-audits/services/AuditService.ts +412 -0
  73. package/src/api-parameters/controllers/ConfigController.ts +324 -0
  74. package/src/api-parameters/entities/parameters.ts +93 -10
  75. package/src/api-parameters/index.ts +43 -4
  76. package/src/api-parameters/primitives/$config.ts +291 -19
  77. package/src/api-parameters/schedulers/ConfigActivationScheduler.ts +30 -0
  78. package/src/api-parameters/services/ConfigStore.ts +491 -0
  79. package/src/api-users/atoms/realmAuthSettingsAtom.ts +19 -0
  80. package/src/api-users/controllers/UserRealmController.ts +0 -2
  81. package/src/api-users/index.ts +2 -0
  82. package/src/api-users/primitives/$userRealm.ts +18 -3
  83. package/src/api-users/providers/UserRealmProvider.ts +6 -3
  84. package/src/api-users/services/RegistrationService.ts +2 -1
  85. package/src/api-users/services/SessionService.ts +4 -0
  86. package/src/api-users/services/UserService.ts +3 -0
  87. package/src/api-verifications/index.ts +7 -1
  88. package/src/bin/index.ts +1 -0
  89. package/src/cli/assets/biomeJson.ts +1 -1
  90. package/src/cli/assets/dummySpecTs.ts +7 -0
  91. package/src/cli/assets/editorconfig.ts +13 -0
  92. package/src/cli/assets/mainTs.ts +14 -0
  93. package/src/cli/commands/BiomeCommands.ts +2 -0
  94. package/src/cli/commands/CoreCommands.ts +28 -9
  95. package/src/cli/commands/VerifyCommands.ts +2 -1
  96. package/src/cli/commands/ViteCommands.ts +8 -9
  97. package/src/cli/services/AlephaCliUtils.ts +214 -23
  98. package/src/command/helpers/Asker.ts +0 -1
  99. package/src/command/primitives/$command.ts +67 -0
  100. package/src/command/providers/CliProvider.ts +39 -8
  101. package/src/core/Alepha.ts +40 -30
  102. package/src/core/helpers/jsonSchemaToTypeBox.ts +307 -0
  103. package/src/core/index.shared.ts +1 -0
  104. package/src/core/index.ts +30 -3
  105. package/src/core/providers/EventManager.ts +1 -1
  106. package/src/core/providers/StateManager.ts +23 -12
  107. package/src/core/providers/TypeProvider.ts +26 -34
  108. package/src/logger/index.ts +8 -6
  109. package/src/logger/primitives/$logger.ts +1 -1
  110. package/src/logger/providers/{SimpleFormatterProvider.ts → PrettyFormatterProvider.ts} +10 -1
  111. package/src/orm/index.ts +6 -0
  112. package/src/orm/services/PgRelationManager.ts +2 -2
  113. package/src/orm/services/PostgresModelBuilder.ts +11 -7
  114. package/src/orm/services/Repository.ts +16 -7
  115. package/src/orm/services/SqliteModelBuilder.ts +10 -0
  116. package/src/server/index.ts +6 -0
  117. package/src/server/primitives/$action.ts +10 -1
  118. package/src/server/providers/ServerBodyParserProvider.ts +11 -5
  119. package/src/server/providers/ServerRouterProvider.ts +13 -7
  120. package/src/server-auth/primitives/$auth.ts +7 -0
  121. package/src/server-auth/providers/ServerAuthProvider.ts +51 -8
  122. package/src/server-cookies/index.ts +2 -1
  123. package/src/thread/primitives/$thread.ts +2 -2
  124. package/src/vite/index.ts +0 -2
  125. package/src/vite/tasks/buildServer.ts +3 -4
  126. package/src/vite/tasks/generateCloudflare.ts +35 -19
  127. package/src/vite/tasks/generateDocker.ts +18 -4
  128. package/src/vite/tasks/generateSitemap.ts +5 -7
  129. package/src/vite/tasks/generateVercel.ts +76 -41
  130. package/src/vite/tasks/runAlepha.ts +16 -1
  131. package/src/websocket/providers/NodeWebSocketServerProvider.ts +3 -11
  132. package/src/websocket/services/WebSocketClient.ts +3 -3
  133. package/dist/cli/dist-BlfFtOk2.js +0 -2770
  134. package/dist/cli/dist-BlfFtOk2.js.map +0 -1
  135. package/src/api-parameters/controllers/ParameterController.ts +0 -45
  136. package/src/api-parameters/services/ParameterStore.ts +0 -23
@@ -1,4 +1,4 @@
1
- import { $atom, $context, $hook, $inject, $module, Alepha, AlephaError, t } from "alepha";
1
+ import { $atom, $context, $hook, $inject, $module, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
2
2
  import { $notification, AlephaApiNotifications } from "alepha/api/notifications";
3
3
  import { AlephaApiVerification } from "alepha/api/verifications";
4
4
  import { AlephaEmail } from "alepha/email";
@@ -15,6 +15,7 @@ import { $client } from "alepha/server/links";
15
15
  import { $authCredentials, $authGithub, $authGoogle, ServerAuthProvider, authenticationProviderSchema } from "alepha/server/auth";
16
16
  import { FileSystemProvider } from "alepha/file";
17
17
  import { AlephaApiFiles } from "alepha/api/files";
18
+ import { AlephaApiJobs } from "alepha/api/jobs";
18
19
 
19
20
  //#region ../../src/api-users/schemas/identityQuerySchema.ts
20
21
  const identityQuerySchema = t.extend(pageQuerySchema, {
@@ -160,10 +161,12 @@ var UserRealmProvider = class {
160
161
  */
161
162
  getRealm(userRealmName = DEFAULT_USER_REALM_NAME$1) {
162
163
  let realm = this.realms.get(userRealmName);
163
- if (!realm) if (userRealmName === DEFAULT_USER_REALM_NAME$1) {
164
- this.register(userRealmName);
165
- realm = this.realms.get(userRealmName);
166
- } else throw new AlephaError(`Missing user realm '${userRealmName}', please declare $userRealm in your application.`);
164
+ if (!realm) {
165
+ const firstRealm = Array.from(this.realms.values())[0];
166
+ if (userRealmName === DEFAULT_USER_REALM_NAME$1 && firstRealm) realm = firstRealm;
167
+ else if (this.alepha.isTest()) realm = this.register(userRealmName);
168
+ else throw new AlephaError(`Missing user realm '${userRealmName}', please declare $userRealm in your application.`);
169
+ }
167
170
  return realm;
168
171
  }
169
172
  identityRepository(userRealmName = DEFAULT_USER_REALM_NAME$1) {
@@ -990,6 +993,7 @@ var RegistrationService = class {
990
993
  }, userRealmName);
991
994
  await this.intentCache.invalidate(body.intentId);
992
995
  const user = await userRepository.create({
996
+ realm: userRealmName,
993
997
  username: intent.data.username,
994
998
  email: intent.data.email,
995
999
  phoneNumber: intent.data.phoneNumber,
@@ -1056,10 +1060,7 @@ var RegistrationService = class {
1056
1060
  });
1057
1061
  this.log.debug("Email verification code sent", { email });
1058
1062
  } catch (error) {
1059
- this.log.warn("Failed to send email verification code", {
1060
- email,
1061
- error
1062
- });
1063
+ this.log.warn("Failed to send email verification code", error);
1063
1064
  }
1064
1065
  }
1065
1066
  /**
@@ -1290,6 +1291,7 @@ var UserService = class {
1290
1291
  email: data.email,
1291
1292
  userRealmName
1292
1293
  });
1294
+ const realm = this.userRealmProvider.getRealm(userRealmName);
1293
1295
  if (data.username) {
1294
1296
  if (await this.users(userRealmName).findOne({ where: { username: { eq: data.username } } }).catch(() => void 0)) {
1295
1297
  this.log.debug("Username already taken", { username: data.username });
@@ -1310,7 +1312,8 @@ var UserService = class {
1310
1312
  }
1311
1313
  const user = await this.users(userRealmName).create({
1312
1314
  ...data,
1313
- roles: data.roles ?? ["user"]
1315
+ roles: data.roles ?? ["user"],
1316
+ realm: realm.name
1314
1317
  });
1315
1318
  this.log.info("User created", {
1316
1319
  userId: user.id,
@@ -1656,6 +1659,9 @@ var UserController = class {
1656
1659
  const realmAuthSettingsAtom = $atom({
1657
1660
  name: "alepha.api.users.realmAuthSettings",
1658
1661
  schema: t.object({
1662
+ displayName: t.optional(t.string({ description: "Display name shown on auth pages (e.g., 'Customer Portal')" })),
1663
+ description: t.optional(t.string({ description: "Description shown on auth pages" })),
1664
+ logoUrl: t.optional(t.string({ description: "Logo URL for auth pages" })),
1659
1665
  registrationAllowed: t.boolean({ description: "Enable user self-registration" }),
1660
1666
  emailEnabled: t.boolean({ description: "Enable email address as a login/registration credential" }),
1661
1667
  emailRequired: t.boolean({ description: "Require email address for user accounts" }),
@@ -1721,7 +1727,6 @@ var UserRealmController = class {
1721
1727
  url = "/realms";
1722
1728
  userRealmProvider = $inject(UserRealmProvider);
1723
1729
  serverAuthProvider = $inject(ServerAuthProvider);
1724
- cryptoProvider = $inject(CryptoProvider);
1725
1730
  /**
1726
1731
  * Get realm configuration settings.
1727
1732
  * This endpoint is not exposed in the API documentation.
@@ -1808,7 +1813,8 @@ var SessionService = class {
1808
1813
  else {
1809
1814
  this.log.warn("Invalid login identifier format", {
1810
1815
  provider,
1811
- username
1816
+ username,
1817
+ realm: name
1812
1818
  });
1813
1819
  throw new InvalidCredentialsError();
1814
1820
  }
@@ -1816,7 +1822,8 @@ var SessionService = class {
1816
1822
  if (!user) {
1817
1823
  this.log.warn("User not found during login attempt", {
1818
1824
  provider,
1819
- username
1825
+ username,
1826
+ realm: name
1820
1827
  });
1821
1828
  throw new InvalidCredentialsError();
1822
1829
  }
@@ -1829,14 +1836,16 @@ var SessionService = class {
1829
1836
  this.log.error("Identity has no password configured", {
1830
1837
  provider,
1831
1838
  username,
1832
- identityId: identity.id
1839
+ identityId: identity.id,
1840
+ realm: name
1833
1841
  });
1834
1842
  throw new InvalidCredentialsError();
1835
1843
  }
1836
1844
  if (!await this.cryptoProvider.verifyPassword(password, storedPassword)) {
1837
1845
  this.log.warn("Invalid password during login attempt", {
1838
1846
  provider,
1839
- username
1847
+ username,
1848
+ realm: name
1840
1849
  });
1841
1850
  throw new InvalidCredentialsError();
1842
1851
  }
@@ -1985,6 +1994,673 @@ var SessionService = class {
1985
1994
  }
1986
1995
  };
1987
1996
 
1997
+ //#endregion
1998
+ //#region ../../src/api-audits/entities/audits.ts
1999
+ /**
2000
+ * Audit severity levels for categorizing events.
2001
+ */
2002
+ const auditSeveritySchema = t.enum([
2003
+ "info",
2004
+ "warning",
2005
+ "critical"
2006
+ ], {
2007
+ default: "info",
2008
+ description: "Severity level of the audit event"
2009
+ });
2010
+ /**
2011
+ * Audit log entity for tracking important system events.
2012
+ *
2013
+ * Stores comprehensive audit information including:
2014
+ * - Who performed the action (userId, userRealm)
2015
+ * - What happened (type, action, resource)
2016
+ * - When it happened (createdAt)
2017
+ * - Context and details (metadata, ipAddress, userAgent)
2018
+ */
2019
+ const audits = $entity({
2020
+ name: "audits",
2021
+ schema: t.object({
2022
+ id: pg.primaryKey(t.bigint()),
2023
+ createdAt: pg.createdAt(),
2024
+ type: t.text({ description: "Audit event type (e.g., auth, user, payment, system)" }),
2025
+ action: t.text({ description: "Specific action performed (e.g., login, create, update)" }),
2026
+ severity: pg.default(auditSeveritySchema, "info"),
2027
+ userId: t.optional(t.uuid()),
2028
+ userRealm: t.optional(t.text()),
2029
+ userEmail: t.optional(t.email()),
2030
+ resourceType: t.optional(t.text()),
2031
+ resourceId: t.optional(t.text()),
2032
+ description: t.optional(t.text()),
2033
+ metadata: t.optional(t.json()),
2034
+ ipAddress: t.optional(t.text()),
2035
+ userAgent: t.optional(t.text()),
2036
+ sessionId: t.optional(t.uuid()),
2037
+ requestId: t.optional(t.text()),
2038
+ success: pg.default(t.boolean(), true),
2039
+ errorMessage: t.optional(t.text())
2040
+ }),
2041
+ indexes: [
2042
+ "createdAt",
2043
+ "type",
2044
+ "action",
2045
+ "userId",
2046
+ "userRealm",
2047
+ "resourceType",
2048
+ "resourceId",
2049
+ "severity",
2050
+ { columns: ["type", "action"] },
2051
+ { columns: ["userId", "createdAt"] },
2052
+ { columns: ["userRealm", "createdAt"] }
2053
+ ]
2054
+ });
2055
+ const auditEntitySchema = audits.schema;
2056
+ const auditEntityInsertSchema = audits.insertSchema;
2057
+
2058
+ //#endregion
2059
+ //#region ../../src/api-audits/schemas/auditQuerySchema.ts
2060
+ /**
2061
+ * Query schema for searching and filtering audit logs.
2062
+ */
2063
+ const auditQuerySchema = t.extend(pageQuerySchema, {
2064
+ type: t.optional(t.text({ description: "Filter by audit type" })),
2065
+ action: t.optional(t.text({ description: "Filter by action" })),
2066
+ severity: t.optional(auditSeveritySchema),
2067
+ userId: t.optional(t.uuid({ description: "Filter by user ID" })),
2068
+ userRealm: t.optional(t.text({ description: "Filter by user realm" })),
2069
+ resourceType: t.optional(t.text({ description: "Filter by resource type" })),
2070
+ resourceId: t.optional(t.text({ description: "Filter by resource ID" })),
2071
+ success: t.optional(t.boolean({ description: "Filter by success status" })),
2072
+ from: t.optional(t.datetime({ description: "Start date filter" })),
2073
+ to: t.optional(t.datetime({ description: "End date filter" })),
2074
+ search: t.optional(t.text({ description: "Search in description" }))
2075
+ });
2076
+
2077
+ //#endregion
2078
+ //#region ../../src/api-audits/schemas/auditResourceSchema.ts
2079
+ /**
2080
+ * Resource schema for audit log responses.
2081
+ */
2082
+ const auditResourceSchema = audits.schema;
2083
+
2084
+ //#endregion
2085
+ //#region ../../src/api-audits/schemas/createAuditSchema.ts
2086
+ /**
2087
+ * Schema for creating a new audit log entry.
2088
+ */
2089
+ const createAuditSchema = t.object({
2090
+ type: t.text({ description: "Audit event type" }),
2091
+ action: t.text({ description: "Specific action performed" }),
2092
+ severity: t.optional(auditSeveritySchema),
2093
+ userId: t.optional(t.uuid()),
2094
+ userRealm: t.optional(t.text()),
2095
+ userEmail: t.optional(t.email()),
2096
+ resourceType: t.optional(t.text()),
2097
+ resourceId: t.optional(t.text()),
2098
+ description: t.optional(t.text()),
2099
+ metadata: t.optional(t.json()),
2100
+ ipAddress: t.optional(t.text()),
2101
+ userAgent: t.optional(t.text()),
2102
+ sessionId: t.optional(t.uuid()),
2103
+ requestId: t.optional(t.text()),
2104
+ success: t.optional(t.boolean()),
2105
+ errorMessage: t.optional(t.text())
2106
+ });
2107
+
2108
+ //#endregion
2109
+ //#region ../../src/api-audits/services/AuditService.ts
2110
+ /**
2111
+ * Service for managing audit logs.
2112
+ *
2113
+ * Provides methods for:
2114
+ * - Creating audit entries
2115
+ * - Querying audit history
2116
+ * - Aggregating audit statistics
2117
+ * - Managing registered audit types
2118
+ */
2119
+ var AuditService = class {
2120
+ alepha = $inject(Alepha);
2121
+ log = $logger();
2122
+ repo = $repository(audits);
2123
+ /**
2124
+ * Registry of audit types and their allowed actions.
2125
+ */
2126
+ auditTypes = /* @__PURE__ */ new Map();
2127
+ /**
2128
+ * Register an audit type with its allowed actions.
2129
+ */
2130
+ registerType(definition) {
2131
+ this.auditTypes.set(definition.type, definition);
2132
+ this.log.debug("Audit type registered", {
2133
+ type: definition.type,
2134
+ actions: definition.actions
2135
+ });
2136
+ }
2137
+ /**
2138
+ * Get all registered audit types.
2139
+ */
2140
+ getRegisteredTypes() {
2141
+ return Array.from(this.auditTypes.values());
2142
+ }
2143
+ /**
2144
+ * Get current request context if available.
2145
+ */
2146
+ getRequestContext() {
2147
+ return this.alepha.context.get("request");
2148
+ }
2149
+ /**
2150
+ * Create a new audit log entry.
2151
+ * Automatically populates ipAddress, userAgent, and requestId from the current request context.
2152
+ */
2153
+ async create(data) {
2154
+ const request = this.getRequestContext();
2155
+ const contextData = {};
2156
+ if (request) {
2157
+ if (!data.ipAddress && request.ip) contextData.ipAddress = request.ip;
2158
+ if (!data.userAgent && request.headers["user-agent"]) contextData.userAgent = request.headers["user-agent"];
2159
+ if (!data.requestId && request.requestId) contextData.requestId = request.requestId;
2160
+ if (!data.sessionId && request.metadata?.sessionId) contextData.sessionId = request.metadata.sessionId;
2161
+ const user = request.user;
2162
+ if (user) {
2163
+ if (!data.userId && user.id) contextData.userId = user.id;
2164
+ if (!data.userEmail && user.email) contextData.userEmail = user.email;
2165
+ if (!data.userRealm && user.realm) contextData.userRealm = user.realm;
2166
+ }
2167
+ }
2168
+ this.log.trace("Creating audit entry", {
2169
+ type: data.type,
2170
+ action: data.action,
2171
+ userId: data.userId ?? contextData.userId
2172
+ });
2173
+ const entry = await this.repo.create({
2174
+ ...contextData,
2175
+ ...data,
2176
+ severity: data.severity ?? "info",
2177
+ success: data.success ?? true
2178
+ });
2179
+ this.log.debug("Audit entry created", {
2180
+ id: entry.id,
2181
+ type: data.type,
2182
+ action: data.action
2183
+ });
2184
+ return entry;
2185
+ }
2186
+ /**
2187
+ * Record an audit event (convenience method).
2188
+ */
2189
+ async record(type, action, options = {}) {
2190
+ return this.create({
2191
+ type,
2192
+ action,
2193
+ ...options
2194
+ });
2195
+ }
2196
+ /**
2197
+ * Record an authentication event.
2198
+ */
2199
+ async recordAuth(action, options = {}) {
2200
+ return this.create({
2201
+ type: "auth",
2202
+ action,
2203
+ severity: action === "login_failed" ? "warning" : "info",
2204
+ ...options
2205
+ });
2206
+ }
2207
+ /**
2208
+ * Record a user management event.
2209
+ */
2210
+ async recordUser(action, options = {}) {
2211
+ return this.create({
2212
+ type: "user",
2213
+ action,
2214
+ resourceType: "user",
2215
+ ...options
2216
+ });
2217
+ }
2218
+ /**
2219
+ * Record a data access event.
2220
+ */
2221
+ async recordAccess(action, options = {}) {
2222
+ return this.create({
2223
+ type: "access",
2224
+ action,
2225
+ ...options
2226
+ });
2227
+ }
2228
+ /**
2229
+ * Record a security event.
2230
+ */
2231
+ async recordSecurity(action, options = {}) {
2232
+ return this.create({
2233
+ type: "security",
2234
+ action,
2235
+ severity: "warning",
2236
+ ...options
2237
+ });
2238
+ }
2239
+ /**
2240
+ * Record a system event.
2241
+ */
2242
+ async recordSystem(action, options = {}) {
2243
+ return this.create({
2244
+ type: "system",
2245
+ action,
2246
+ severity: action === "error" ? "critical" : "info",
2247
+ ...options
2248
+ });
2249
+ }
2250
+ /**
2251
+ * Find audit entries with filtering and pagination.
2252
+ */
2253
+ async find(query = {}) {
2254
+ this.log.trace("Finding audit entries", { query });
2255
+ query.sort ??= "-createdAt";
2256
+ const where = this.repo.createQueryWhere();
2257
+ if (query.type) where.type = { eq: query.type };
2258
+ if (query.action) where.action = { eq: query.action };
2259
+ if (query.severity) where.severity = { eq: query.severity };
2260
+ if (query.userId) where.userId = { eq: query.userId };
2261
+ if (query.userRealm) where.userRealm = { eq: query.userRealm };
2262
+ if (query.resourceType) where.resourceType = { eq: query.resourceType };
2263
+ if (query.resourceId) where.resourceId = { eq: query.resourceId };
2264
+ if (query.success !== void 0) where.success = { eq: query.success };
2265
+ if (query.from) where.createdAt = {
2266
+ ...where.createdAt,
2267
+ gte: query.from
2268
+ };
2269
+ if (query.to) where.createdAt = {
2270
+ ...where.createdAt,
2271
+ lte: query.to
2272
+ };
2273
+ if (query.search) where.description = { like: `%${query.search}%` };
2274
+ const result = await this.repo.paginate(query, { where }, { count: true });
2275
+ this.log.debug("Audit entries found", {
2276
+ count: result.content.length,
2277
+ total: result.page.totalElements
2278
+ });
2279
+ return result;
2280
+ }
2281
+ /**
2282
+ * Get audit entry by ID.
2283
+ */
2284
+ async getById(id) {
2285
+ return this.repo.findById(id);
2286
+ }
2287
+ /**
2288
+ * Get audit entries for a specific user.
2289
+ */
2290
+ async findByUser(userId, query = {}) {
2291
+ return this.find({
2292
+ ...query,
2293
+ userId
2294
+ });
2295
+ }
2296
+ /**
2297
+ * Get audit entries for a specific resource.
2298
+ */
2299
+ async findByResource(resourceType, resourceId, query = {}) {
2300
+ return this.find({
2301
+ ...query,
2302
+ resourceType,
2303
+ resourceId
2304
+ });
2305
+ }
2306
+ /**
2307
+ * Get audit statistics for a time period.
2308
+ */
2309
+ async getStats(options = {}) {
2310
+ this.log.trace("Getting audit stats", options);
2311
+ const where = this.repo.createQueryWhere();
2312
+ if (options.from) where.createdAt = { gte: options.from.toISOString() };
2313
+ if (options.to) where.createdAt = {
2314
+ ...where.createdAt,
2315
+ lte: options.to.toISOString()
2316
+ };
2317
+ if (options.userRealm) where.userRealm = { eq: options.userRealm };
2318
+ const all = await this.repo.findMany({ where });
2319
+ const stats = {
2320
+ total: all.length,
2321
+ byType: {},
2322
+ bySeverity: {
2323
+ info: 0,
2324
+ warning: 0,
2325
+ critical: 0
2326
+ },
2327
+ successRate: 0,
2328
+ recentFailures: []
2329
+ };
2330
+ let successCount = 0;
2331
+ for (const entry of all) {
2332
+ stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
2333
+ const severity = entry.severity;
2334
+ stats.bySeverity[severity]++;
2335
+ if (entry.success) successCount++;
2336
+ }
2337
+ stats.successRate = stats.total > 0 ? successCount / stats.total : 1;
2338
+ stats.recentFailures = all.filter((e) => !e.success).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()).slice(0, 10);
2339
+ return stats;
2340
+ }
2341
+ /**
2342
+ * Delete old audit entries (for retention policy).
2343
+ */
2344
+ async deleteOlderThan(date) {
2345
+ this.log.info("Deleting old audit entries", { olderThan: date });
2346
+ const old = await this.repo.findMany({ where: { createdAt: { lt: date.toISOString() } } });
2347
+ for (const entry of old) await this.repo.deleteById(entry.id);
2348
+ this.log.info("Old audit entries deleted", { count: old.length });
2349
+ return old.length;
2350
+ }
2351
+ };
2352
+
2353
+ //#endregion
2354
+ //#region ../../src/api-audits/controllers/AuditController.ts
2355
+ /**
2356
+ * REST API controller for audit log management.
2357
+ *
2358
+ * Provides endpoints for:
2359
+ * - Querying audit logs with filtering
2360
+ * - Creating audit entries
2361
+ * - Getting audit statistics
2362
+ * - Viewing registered audit types
2363
+ */
2364
+ var AuditController = class {
2365
+ url = "/audits";
2366
+ group = "audits";
2367
+ auditService = $inject(AuditService);
2368
+ /**
2369
+ * Find audit entries with filtering and pagination.
2370
+ */
2371
+ findAudits = $action({
2372
+ path: this.url,
2373
+ group: this.group,
2374
+ description: "Find audit entries with filtering and pagination",
2375
+ schema: {
2376
+ query: auditQuerySchema,
2377
+ response: pg.page(auditResourceSchema)
2378
+ },
2379
+ handler: ({ query }) => this.auditService.find(query)
2380
+ });
2381
+ /**
2382
+ * Get a single audit entry by ID.
2383
+ */
2384
+ getAudit = $action({
2385
+ path: `${this.url}/:id`,
2386
+ group: this.group,
2387
+ description: "Get a single audit entry by ID",
2388
+ schema: {
2389
+ params: t.object({ id: t.text() }),
2390
+ response: auditResourceSchema
2391
+ },
2392
+ handler: ({ params }) => this.auditService.getById(params.id)
2393
+ });
2394
+ /**
2395
+ * Create a new audit entry.
2396
+ */
2397
+ createAudit = $action({
2398
+ method: "POST",
2399
+ path: this.url,
2400
+ group: this.group,
2401
+ description: "Create a new audit entry",
2402
+ schema: {
2403
+ body: createAuditSchema,
2404
+ response: auditResourceSchema
2405
+ },
2406
+ handler: ({ body }) => this.auditService.create(body)
2407
+ });
2408
+ /**
2409
+ * Get audit entries for a specific user.
2410
+ */
2411
+ findByUser = $action({
2412
+ path: `${this.url}/user/:userId`,
2413
+ group: this.group,
2414
+ description: "Get audit entries for a specific user",
2415
+ schema: {
2416
+ params: t.object({ userId: t.uuid() }),
2417
+ query: t.omit(auditQuerySchema, ["userId"]),
2418
+ response: pg.page(auditResourceSchema)
2419
+ },
2420
+ handler: ({ params, query }) => this.auditService.findByUser(params.userId, query)
2421
+ });
2422
+ /**
2423
+ * Get audit entries for a specific resource.
2424
+ */
2425
+ findByResource = $action({
2426
+ path: `${this.url}/resource/:resourceType/:resourceId`,
2427
+ group: this.group,
2428
+ description: "Get audit entries for a specific resource",
2429
+ schema: {
2430
+ params: t.object({
2431
+ resourceType: t.text(),
2432
+ resourceId: t.text()
2433
+ }),
2434
+ query: t.omit(auditQuerySchema, ["resourceType", "resourceId"]),
2435
+ response: pg.page(auditResourceSchema)
2436
+ },
2437
+ handler: ({ params, query }) => this.auditService.findByResource(params.resourceType, params.resourceId, query)
2438
+ });
2439
+ /**
2440
+ * Get audit statistics.
2441
+ */
2442
+ getStats = $action({
2443
+ path: `${this.url}/stats`,
2444
+ group: this.group,
2445
+ description: "Get audit statistics for a time period",
2446
+ schema: {
2447
+ query: t.object({
2448
+ from: t.optional(t.datetime()),
2449
+ to: t.optional(t.datetime()),
2450
+ userRealm: t.optional(t.text())
2451
+ }),
2452
+ response: t.object({
2453
+ total: t.integer(),
2454
+ byType: t.record(t.text(), t.integer()),
2455
+ bySeverity: t.object({
2456
+ info: t.integer(),
2457
+ warning: t.integer(),
2458
+ critical: t.integer()
2459
+ }),
2460
+ successRate: t.number(),
2461
+ recentFailures: t.array(auditResourceSchema)
2462
+ })
2463
+ },
2464
+ handler: ({ query }) => this.auditService.getStats({
2465
+ from: query.from ? new Date(query.from) : void 0,
2466
+ to: query.to ? new Date(query.to) : void 0,
2467
+ userRealm: query.userRealm
2468
+ })
2469
+ });
2470
+ /**
2471
+ * Get registered audit types.
2472
+ */
2473
+ getTypes = $action({
2474
+ path: `${this.url}/types`,
2475
+ group: this.group,
2476
+ description: "Get all registered audit types",
2477
+ schema: { response: t.array(t.object({
2478
+ type: t.text(),
2479
+ description: t.optional(t.text()),
2480
+ actions: t.array(t.text())
2481
+ })) },
2482
+ handler: () => this.auditService.getRegisteredTypes()
2483
+ });
2484
+ /**
2485
+ * Get distinct values for filters.
2486
+ */
2487
+ getFilterOptions = $action({
2488
+ path: `${this.url}/filters`,
2489
+ group: this.group,
2490
+ description: "Get distinct values for audit filters",
2491
+ schema: { response: t.object({
2492
+ types: t.array(t.text()),
2493
+ actions: t.array(t.text()),
2494
+ resourceTypes: t.array(t.text()),
2495
+ userRealms: t.array(t.text())
2496
+ }) },
2497
+ handler: async () => {
2498
+ const types = this.auditService.getRegisteredTypes();
2499
+ return {
2500
+ types: types.map((t$1) => t$1.type),
2501
+ actions: types.flatMap((t$1) => t$1.actions),
2502
+ resourceTypes: [
2503
+ "user",
2504
+ "session",
2505
+ "file",
2506
+ "order",
2507
+ "payment"
2508
+ ],
2509
+ userRealms: ["default"]
2510
+ };
2511
+ }
2512
+ });
2513
+ };
2514
+
2515
+ //#endregion
2516
+ //#region ../../src/api-audits/primitives/$audit.ts
2517
+ /**
2518
+ * Audit type primitive for registering domain-specific audit events.
2519
+ *
2520
+ * Provides a type-safe way to define and log audit events within a specific domain.
2521
+ *
2522
+ * @example
2523
+ * ```ts
2524
+ * class PaymentAudits {
2525
+ * audit = $audit({
2526
+ * type: "payment",
2527
+ * description: "Payment-related audit events",
2528
+ * actions: ["create", "refund", "cancel", "dispute"],
2529
+ * });
2530
+ *
2531
+ * async logPaymentCreated(paymentId: string, userId: string, amount: number) {
2532
+ * await this.audit.log("create", {
2533
+ * userId,
2534
+ * resourceType: "payment",
2535
+ * resourceId: paymentId,
2536
+ * description: `Payment of ${amount} created`,
2537
+ * metadata: { amount },
2538
+ * });
2539
+ * }
2540
+ * }
2541
+ * ```
2542
+ */
2543
+ var AuditPrimitive = class extends Primitive {
2544
+ auditService = $inject(AuditService);
2545
+ /**
2546
+ * The audit type identifier.
2547
+ */
2548
+ get type() {
2549
+ return this.options.type;
2550
+ }
2551
+ /**
2552
+ * The audit type description.
2553
+ */
2554
+ get description() {
2555
+ return this.options.description;
2556
+ }
2557
+ /**
2558
+ * The allowed actions for this audit type.
2559
+ */
2560
+ get actions() {
2561
+ return this.options.actions;
2562
+ }
2563
+ /**
2564
+ * Log an audit event for this type.
2565
+ */
2566
+ async log(action, options = {}) {
2567
+ await this.auditService.record(this.options.type, action, options);
2568
+ }
2569
+ /**
2570
+ * Log a successful audit event.
2571
+ */
2572
+ async logSuccess(action, options = {}) {
2573
+ await this.log(action, {
2574
+ ...options,
2575
+ success: true
2576
+ });
2577
+ }
2578
+ /**
2579
+ * Log a failed audit event.
2580
+ */
2581
+ async logFailure(action, errorMessage, options = {}) {
2582
+ await this.log(action, {
2583
+ ...options,
2584
+ success: false,
2585
+ errorMessage
2586
+ });
2587
+ }
2588
+ /**
2589
+ * Called during initialization to register this audit type.
2590
+ */
2591
+ onInit() {
2592
+ const definition = {
2593
+ type: this.options.type,
2594
+ description: this.options.description,
2595
+ actions: this.options.actions
2596
+ };
2597
+ this.auditService.registerType(definition);
2598
+ }
2599
+ };
2600
+ /**
2601
+ * Create an audit type primitive.
2602
+ *
2603
+ * @example
2604
+ * ```ts
2605
+ * class OrderAudits {
2606
+ * audit = $audit({
2607
+ * type: "order",
2608
+ * description: "Order management events",
2609
+ * actions: ["create", "update", "cancel", "fulfill", "ship"],
2610
+ * });
2611
+ * }
2612
+ * ```
2613
+ */
2614
+ const $audit = (options) => {
2615
+ return createPrimitive(AuditPrimitive, options);
2616
+ };
2617
+ $audit[KIND] = AuditPrimitive;
2618
+
2619
+ //#endregion
2620
+ //#region ../../src/api-audits/index.ts
2621
+ /**
2622
+ * Provides audit logging API endpoints for Alepha applications.
2623
+ *
2624
+ * This module includes:
2625
+ * - Audit log CRUD operations
2626
+ * - Filtering and searching audit events
2627
+ * - Audit statistics and analytics
2628
+ * - `$audit` primitive for domain-specific audit types
2629
+ *
2630
+ * @module alepha.api.audits
2631
+ *
2632
+ * @example
2633
+ * ```ts
2634
+ * // In your app module
2635
+ * import { AlephaApiAudits } from "alepha/api/audits";
2636
+ *
2637
+ * const App = $module({
2638
+ * name: "app",
2639
+ * services: [AlephaApiAudits, ...],
2640
+ * });
2641
+ *
2642
+ * // Create domain-specific audit types
2643
+ * class PaymentAudits {
2644
+ * audit = $audit({
2645
+ * type: "payment",
2646
+ * actions: ["create", "refund", "cancel"],
2647
+ * });
2648
+ *
2649
+ * async onPaymentCreated(paymentId: string, userId: string) {
2650
+ * await this.audit.log("create", {
2651
+ * userId,
2652
+ * resourceType: "payment",
2653
+ * resourceId: paymentId,
2654
+ * });
2655
+ * }
2656
+ * }
2657
+ * ```
2658
+ */
2659
+ const AlephaApiAudits = $module({
2660
+ name: "alepha.api.audits",
2661
+ services: [AuditService, AuditController]
2662
+ });
2663
+
1988
2664
  //#endregion
1989
2665
  //#region ../../src/api-users/primitives/$userRealm.ts
1990
2666
  /**
@@ -2007,8 +2683,9 @@ const $userRealm = (options = {}) => {
2007
2683
  const userRealmProvider = alepha.inject(UserRealmProvider);
2008
2684
  const name = options.realm?.name ?? DEFAULT_USER_REALM_NAME;
2009
2685
  const userRealm = userRealmProvider.register(name, options);
2010
- if (options.modules?.audits) {}
2686
+ if (options.modules?.audits) alepha.with(AlephaApiAudits);
2011
2687
  if (options.modules?.files) alepha.with(AlephaApiFiles);
2688
+ if (options.modules?.jobs) alepha.with(AlephaApiJobs);
2012
2689
  const realm = $realm({
2013
2690
  ...options.realm,
2014
2691
  name,
@@ -2040,10 +2717,12 @@ const $userRealm = (options = {}) => {
2040
2717
  }
2041
2718
  });
2042
2719
  realm.link = (name$1) => {
2043
- return (ctx) => sessionService.link(name$1, ctx.user);
2720
+ return (ctx) => sessionService.link(name$1, ctx.user, realm.name);
2044
2721
  };
2045
2722
  realm.login = (name$1) => {
2046
- return (credentials) => sessionService.login(name$1, credentials.username, credentials.password);
2723
+ return (credentials) => {
2724
+ return sessionService.login(name$1, credentials.username, credentials.password, realm.name);
2725
+ };
2047
2726
  };
2048
2727
  const identities$1 = options.identities ?? { credentials: true };
2049
2728
  if (identities$1) {
@@ -2140,7 +2819,8 @@ const AlephaApiUsers = $module({
2140
2819
  UserController,
2141
2820
  SessionController,
2142
2821
  IdentityController,
2143
- UserRealmController
2822
+ UserRealmController,
2823
+ UserNotifications
2144
2824
  ]
2145
2825
  });
2146
2826