@lilaquadrat/studio 10.0.0-beta.8 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/eslint.config.js +146 -0
  2. package/lib/fastify-plugins.d.ts +6 -0
  3. package/lib/fastify-plugins.js +7 -0
  4. package/lib/fastify-plugins.js.map +1 -0
  5. package/lib/helpers.d.ts +4 -2
  6. package/lib/helpers.js +13 -2
  7. package/lib/helpers.js.map +1 -1
  8. package/lib/main.d.ts +1 -3
  9. package/lib/main.js +7 -3
  10. package/lib/main.js.map +1 -1
  11. package/lib/models.d.ts +4 -4
  12. package/lib/models.js +4 -4
  13. package/lib/models.js.map +1 -1
  14. package/lib/services.d.ts +5 -5
  15. package/lib/services.js +5 -13
  16. package/lib/services.js.map +1 -1
  17. package/lib/src/Immutable.class.d.ts +8 -1
  18. package/lib/src/Immutable.class.js +52 -8
  19. package/lib/src/Immutable.class.js.map +1 -1
  20. package/lib/src/ShareClientFactory.class.d.ts +1 -3
  21. package/lib/src/ShareClientFactory.class.js +1 -9
  22. package/lib/src/ShareClientFactory.class.js.map +1 -1
  23. package/lib/src/classes/models.class.js.map +1 -1
  24. package/lib/src/classes/modelv2.class.d.ts +2 -0
  25. package/lib/src/classes/modelv2.class.js +1 -1
  26. package/lib/src/classes/modelv2.class.js.map +1 -1
  27. package/lib/src/classes/mongo.class.js +4 -14
  28. package/lib/src/classes/mongo.class.js.map +1 -1
  29. package/lib/src/functions/handleError.d.ts +2 -3
  30. package/lib/src/functions/handleError.js +3 -16
  31. package/lib/src/functions/handleError.js.map +1 -1
  32. package/lib/src/functions/optionsHelper.d.ts +4 -4
  33. package/lib/src/functions/optionsHelper.js +5 -4
  34. package/lib/src/functions/optionsHelper.js.map +1 -1
  35. package/lib/src/functions/respondCode.d.ts +2 -1
  36. package/lib/src/functions/respondCode.js +1 -1
  37. package/lib/src/functions/respondCode.js.map +1 -1
  38. package/lib/src/helpers/ControllerHelper.d.ts +73 -0
  39. package/lib/src/helpers/ControllerHelper.js +242 -0
  40. package/lib/src/helpers/ControllerHelper.js.map +1 -0
  41. package/lib/src/helpers/EnvMapper.js +1 -0
  42. package/lib/src/helpers/EnvMapper.js.map +1 -0
  43. package/lib/src/helpers/auth0config.d.ts +6 -0
  44. package/lib/src/helpers/auth0config.js +23 -0
  45. package/lib/src/helpers/auth0config.js.map +1 -0
  46. package/lib/src/helpers/authPlugin.d.ts +29 -0
  47. package/lib/src/helpers/authPlugin.js +77 -0
  48. package/lib/src/helpers/authPlugin.js.map +1 -0
  49. package/lib/src/helpers/cacheHelper.d.ts +69 -0
  50. package/lib/src/helpers/cacheHelper.js +235 -0
  51. package/lib/src/helpers/cacheHelper.js.map +1 -0
  52. package/lib/src/helpers/createSasToken.d.ts +0 -2
  53. package/lib/src/helpers/createSasToken.js +35 -32
  54. package/lib/src/helpers/createSasToken.js.map +1 -1
  55. package/lib/src/helpers/getSecrets.d.ts +1 -1
  56. package/lib/src/helpers/getSecrets.js +10 -12
  57. package/lib/src/helpers/getSecrets.js.map +1 -1
  58. package/lib/src/helpers/limiterPlugin.d.ts +9 -0
  59. package/lib/src/helpers/limiterPlugin.js +72 -0
  60. package/lib/src/helpers/limiterPlugin.js.map +1 -0
  61. package/lib/src/helpers/loggingPlugin.d.ts +30 -0
  62. package/lib/src/helpers/loggingPlugin.js +87 -0
  63. package/lib/src/helpers/loggingPlugin.js.map +1 -0
  64. package/lib/src/helpers/queryAssertionPlugin.d.ts +3 -0
  65. package/lib/src/helpers/queryAssertionPlugin.js +20 -0
  66. package/lib/src/helpers/queryAssertionPlugin.js.map +1 -0
  67. package/lib/src/helpers/safeObjectId.d.ts +1 -1
  68. package/lib/src/helpers/safeObjectId.js +5 -1
  69. package/lib/src/helpers/safeObjectId.js.map +1 -1
  70. package/lib/src/helpers/storageSdkFactory.d.ts +2 -0
  71. package/lib/src/helpers/storageSdkFactory.js +11 -0
  72. package/lib/src/helpers/storageSdkFactory.js.map +1 -0
  73. package/lib/src/helpers/studioAppPlugin.d.ts +3 -0
  74. package/lib/src/helpers/studioAppPlugin.js +16 -0
  75. package/lib/src/helpers/studioAppPlugin.js.map +1 -0
  76. package/lib/src/logger.js +57 -8
  77. package/lib/src/logger.js.map +1 -1
  78. package/lib/src/models/access.model.d.ts +14 -3
  79. package/lib/src/models/access.model.js +7 -9
  80. package/lib/src/models/access.model.js.map +1 -1
  81. package/lib/src/models/customers.model.js +14 -4
  82. package/lib/src/models/customers.model.js.map +1 -1
  83. package/lib/src/models/design.model.d.ts +4 -0
  84. package/lib/src/models/design.model.js +58 -0
  85. package/lib/src/models/design.model.js.map +1 -0
  86. package/lib/src/models/domain.model.js +1 -1
  87. package/lib/src/models/domain.model.js.map +1 -1
  88. package/lib/src/models/editor.model.js +7 -0
  89. package/lib/src/models/editor.model.js.map +1 -1
  90. package/lib/src/models/emailLimit.model.d.ts +4 -0
  91. package/lib/src/models/emailLimit.model.js +31 -0
  92. package/lib/src/models/emailLimit.model.js.map +1 -0
  93. package/lib/src/models/hosting.model.js +1 -3
  94. package/lib/src/models/hosting.model.js.map +1 -1
  95. package/lib/src/models/hostingSettings.model.js +6 -4
  96. package/lib/src/models/hostingSettings.model.js.map +1 -1
  97. package/lib/src/models/invoice.model.d.ts +4 -0
  98. package/lib/src/models/invoice.model.js +235 -0
  99. package/lib/src/models/invoice.model.js.map +1 -0
  100. package/lib/src/models/mailFrom.model.js +51 -10
  101. package/lib/src/models/mailFrom.model.js.map +1 -1
  102. package/lib/src/models/project.model.js +2 -4
  103. package/lib/src/models/project.model.js.map +1 -1
  104. package/lib/src/models/publish-method.model.js +79 -430
  105. package/lib/src/models/publish-method.model.js.map +1 -1
  106. package/lib/src/models/publish.model.js +6 -0
  107. package/lib/src/models/publish.model.js.map +1 -1
  108. package/lib/src/models/storage.model.js +23 -5
  109. package/lib/src/models/storage.model.js.map +1 -1
  110. package/lib/src/models/structure.model.js +40 -0
  111. package/lib/src/models/structure.model.js.map +1 -1
  112. package/lib/src/models/upload.model.js +38 -2
  113. package/lib/src/models/upload.model.js.map +1 -1
  114. package/lib/src/prompts/textGeneration.js +88 -0
  115. package/lib/src/prompts/textGeneration.js.map +1 -1
  116. package/lib/src/prompts/textGenerationMulti.js +78 -44
  117. package/lib/src/prompts/textGenerationMulti.js.map +1 -1
  118. package/lib/src/services/access.service.d.ts +132 -33
  119. package/lib/src/services/access.service.js +270 -92
  120. package/lib/src/services/access.service.js.map +1 -1
  121. package/lib/src/services/ai.service.d.ts +4 -3
  122. package/lib/src/services/ai.service.js +22 -29
  123. package/lib/src/services/ai.service.js.map +1 -1
  124. package/lib/src/services/auth.service.d.ts +11 -0
  125. package/lib/src/services/auth.service.js +70 -0
  126. package/lib/src/services/auth.service.js.map +1 -0
  127. package/lib/src/services/conf.service.d.ts +3 -31
  128. package/lib/src/services/conf.service.js +58 -167
  129. package/lib/src/services/conf.service.js.map +1 -1
  130. package/lib/src/services/customers.service.d.ts +8 -4
  131. package/lib/src/services/customers.service.js +34 -7
  132. package/lib/src/services/customers.service.js.map +1 -1
  133. package/lib/src/services/designs.service.d.ts +7 -0
  134. package/lib/src/services/designs.service.js +10 -0
  135. package/lib/src/services/designs.service.js.map +1 -0
  136. package/lib/src/services/domains.service.d.ts +18 -84
  137. package/lib/src/services/domains.service.js +91 -583
  138. package/lib/src/services/domains.service.js.map +1 -1
  139. package/lib/src/services/editor.service.d.ts +4 -0
  140. package/lib/src/services/editor.service.js +28 -0
  141. package/lib/src/services/editor.service.js.map +1 -1
  142. package/lib/src/services/emailLimit.service.d.ts +21 -0
  143. package/lib/src/services/emailLimit.service.js +51 -0
  144. package/lib/src/services/emailLimit.service.js.map +1 -0
  145. package/lib/src/services/hosting.service.d.ts +12 -24
  146. package/lib/src/services/hosting.service.js +32 -122
  147. package/lib/src/services/hosting.service.js.map +1 -1
  148. package/lib/src/services/hostingAdmin.service.d.ts +1 -1
  149. package/lib/src/services/hostingAdmin.service.js +2 -2
  150. package/lib/src/services/hostingAdmin.service.js.map +1 -1
  151. package/lib/src/services/import.service.d.ts +6 -22
  152. package/lib/src/services/import.service.js +63 -65
  153. package/lib/src/services/import.service.js.map +1 -1
  154. package/lib/src/services/invoices.service.d.ts +30 -0
  155. package/lib/src/services/invoices.service.js +265 -0
  156. package/lib/src/services/invoices.service.js.map +1 -0
  157. package/lib/src/services/jetstream.service.d.ts +5 -3
  158. package/lib/src/services/jetstream.service.js +63 -7
  159. package/lib/src/services/jetstream.service.js.map +1 -1
  160. package/lib/src/services/listParticipants.service.d.ts +3 -5
  161. package/lib/src/services/listParticipants.service.js +76 -16
  162. package/lib/src/services/listParticipants.service.js.map +1 -1
  163. package/lib/src/services/mailFrom.service.d.ts +14 -1
  164. package/lib/src/services/mailFrom.service.js +59 -0
  165. package/lib/src/services/mailFrom.service.js.map +1 -1
  166. package/lib/src/services/me.service.d.ts +23 -12
  167. package/lib/src/services/me.service.js +65 -88
  168. package/lib/src/services/me.service.js.map +1 -1
  169. package/lib/src/services/publish.service.d.ts +6 -8
  170. package/lib/src/services/publish.service.js +34 -32
  171. package/lib/src/services/publish.service.js.map +1 -1
  172. package/lib/src/services/publishData.service.d.ts +10 -7
  173. package/lib/src/services/publishData.service.js +32 -75
  174. package/lib/src/services/publishData.service.js.map +1 -1
  175. package/lib/src/services/spamAnalasys.service.d.ts +4 -4
  176. package/lib/src/services/spamAnalasys.service.js +36 -44
  177. package/lib/src/services/spamAnalasys.service.js.map +1 -1
  178. package/lib/src/services/storage.service.d.ts +68 -39
  179. package/lib/src/services/storage.service.js +378 -209
  180. package/lib/src/services/storage.service.js.map +1 -1
  181. package/lib/src/services/structures.service.d.ts +8 -1
  182. package/lib/src/services/structures.service.js +26 -1
  183. package/lib/src/services/structures.service.js.map +1 -1
  184. package/lib/src/services/upload.service.d.ts +8 -1
  185. package/lib/src/services/upload.service.js +76 -3
  186. package/lib/src/services/upload.service.js.map +1 -1
  187. package/lib/tests/groupStructuresByModel.spec.d.ts +1 -0
  188. package/lib/tests/groupStructuresByModel.spec.js +33 -0
  189. package/lib/tests/groupStructuresByModel.spec.js.map +1 -0
  190. package/lib/tests/listParticipantsServiceJoin.spec.d.ts +1 -0
  191. package/lib/tests/listParticipantsServiceJoin.spec.js +151 -0
  192. package/lib/tests/listParticipantsServiceJoin.spec.js.map +1 -0
  193. package/lib/tests/storageServiceHandleFile.spec.d.ts +1 -0
  194. package/lib/tests/storageServiceHandleFile.spec.js +94 -0
  195. package/lib/tests/storageServiceHandleFile.spec.js.map +1 -0
  196. package/lib/tests/storageServiceToken.spec.d.ts +1 -0
  197. package/lib/tests/storageServiceToken.spec.js +104 -0
  198. package/lib/tests/storageServiceToken.spec.js.map +1 -0
  199. package/lib/tests/uploadServiceCreate.spec.d.ts +1 -0
  200. package/lib/tests/uploadServiceCreate.spec.js +81 -0
  201. package/lib/tests/uploadServiceCreate.spec.js.map +1 -0
  202. package/package.json +30 -26
  203. package/lib/src/AzureBlobStorage.share.d.ts +0 -19
  204. package/lib/src/AzureBlobStorage.share.js +0 -162
  205. package/lib/src/AzureBlobStorage.share.js.map +0 -1
  206. package/lib/src/AzureFileStorage.share.d.ts +0 -22
  207. package/lib/src/AzureFileStorage.share.js +0 -139
  208. package/lib/src/AzureFileStorage.share.js.map +0 -1
  209. package/lib/src/AzureVault.d.ts +0 -14
  210. package/lib/src/AzureVault.js +0 -28
  211. package/lib/src/AzureVault.js.map +0 -1
  212. package/lib/src/dns.challenge.class.d.ts +0 -17
  213. package/lib/src/dns.challenge.class.js +0 -41
  214. package/lib/src/dns.challenge.class.js.map +0 -1
  215. package/lib/src/http.challenge.class.d.ts +0 -33
  216. package/lib/src/http.challenge.class.js +0 -58
  217. package/lib/src/http.challenge.class.js.map +0 -1
  218. package/lib/src/models/certificate-action.model.d.ts +0 -5
  219. package/lib/src/models/certificate-action.model.js +0 -230
  220. package/lib/src/models/certificate-action.model.js.map +0 -1
  221. package/lib/src/models/certificate.model.d.ts +0 -4
  222. package/lib/src/models/certificate.model.js +0 -96
  223. package/lib/src/models/certificate.model.js.map +0 -1
  224. package/lib/src/models/editorBase.model.d.ts +0 -4
  225. package/lib/src/models/editorBase.model.js +0 -39
  226. package/lib/src/models/editorBase.model.js.map +0 -1
  227. package/lib/src/services/certificates.service.js +0 -199
  228. package/lib/src/services/certificates.service.js.map +0 -1
  229. package/lib/src/services/certificatesAction.service.d.ts +0 -0
  230. package/lib/src/services/certificatesAction.service.js +0 -237
  231. package/lib/src/services/certificatesAction.service.js.map +0 -1
  232. package/lib/src/services/editorBase.service.d.ts +0 -46
  233. package/lib/src/services/editorBase.service.js +0 -161
  234. package/lib/src/services/editorBase.service.js.map +0 -1
  235. package/lib/src/services/handleFile.service.d.ts +0 -9
  236. package/lib/src/services/handleFile.service.js +0 -45
  237. package/lib/src/services/handleFile.service.js.map +0 -1
  238. package/lib/src/services/media.service.d.ts +0 -35
  239. package/lib/src/services/media.service.js +0 -418
  240. package/lib/src/services/media.service.js.map +0 -1
  241. package/lib/src/services/share.service.d.ts +0 -6
  242. package/lib/src/services/share.service.js +0 -4
  243. package/lib/src/services/share.service.js.map +0 -1
  244. /package/lib/src/{services/certificates.service.d.ts → helpers/EnvMapper.d.ts} +0 -0
@@ -1,109 +1,287 @@
1
- import dayjs from 'dayjs';
2
- import Timeseries from '../Timeseries.class.js';
1
+ /**
2
+ * AccessService — distributed rate limiter (Mongo-only, multi-pod safe).
3
+ *
4
+ * ============================================================================
5
+ * OVERVIEW
6
+ * ============================================================================
7
+ * Counts requests per key (IP or user id) over fixed time buckets and reports
8
+ * when a limit is exceeded. Designed to be shared across multiple Fastify APIs
9
+ * pointing at the same MongoDB. No Redis required.
10
+ *
11
+ * Storage = a single Mongo collection (`accessLimits`) of counter documents,
12
+ * one per unique key. Each document holds the current minute bucket count and
13
+ * current hour bucket count. A TTL index reaps stale docs automatically.
14
+ *
15
+ * Two buckets only by design: minute (short burst) and hour (sustained load).
16
+ * Second/quarter buckets dropped — sec is noisy under network jitter, quarter
17
+ * is redundant with minute+hour.
18
+ *
19
+ * ============================================================================
20
+ * PERFORMANCE STRATEGY ("Tier 1")
21
+ * ============================================================================
22
+ * Three layered optimizations to minimize Mongo writes while keeping the
23
+ * limit decision correct:
24
+ *
25
+ * 1. Single atomic upsert per DB hit
26
+ * Pipeline update with `$cond` either increments the existing bucket
27
+ * count or resets it (when the window rolled over). Both minute and
28
+ * hour intervals updated in one round-trip, no read-then-write race.
29
+ *
30
+ * 2. Per-pod LRU cache (in-process Map)
31
+ * Caches the last known counts for ~10k hottest keys. If a request
32
+ * arrives for a cached key that is well under its limit and the cache
33
+ * entry is fresh, the DB is skipped entirely. The local counter is
34
+ * bumped instead. Trade-off described below.
35
+ *
36
+ * 3. Threshold-based flush
37
+ * Cache is trusted only while projected count < 70% of limit. Above
38
+ * that, every request forces a DB sync so the real count is known
39
+ * before a 429 is issued.
40
+ *
41
+ * Result: under normal traffic, most requests do zero Mongo work. Under
42
+ * abuse, every request near/over the limit syncs to Mongo and gets
43
+ * blocked accurately.
44
+ *
45
+ * ============================================================================
46
+ * CACHE TRADE-OFF (read this if Mongo counts look low)
47
+ * ============================================================================
48
+ * Mongo count = lagging snapshot. Real count = `mongoCount + sum(per-pod
49
+ * cache deltas)`. A burst of 10 requests in < 2s may only produce ~1–2
50
+ * Mongo writes; the rest are absorbed by the cache and never sync.
51
+ *
52
+ * This is correct behavior — the limit is still enforced because the cache
53
+ * holds the projected count locally and returns `LimiterBreach` from RAM.
54
+ *
55
+ * If exact Mongo numbers are required (debugging, audit), set
56
+ * CACHE_SOFT_SYNC_MS = 0 and CACHE_SAFE_RATIO = 0 (every request hits DB).
57
+ * Multi-pod note: each pod has its own cache. Across N pods, true count can
58
+ * exceed limit by up to N × (CACHE_SAFE_RATIO × limit) before any pod
59
+ * sees a breach. For strict global enforcement on tiny limits, tighten
60
+ * CACHE_SAFE_RATIO or set to 0.
61
+ *
62
+ * ============================================================================
63
+ * DOCUMENT SHAPE (in `accessLimits` collection)
64
+ * ============================================================================
65
+ * {
66
+ * _id: "studio:ip:1.2.3.4", // or "studio:user:<sub>"
67
+ * minute: { bucket: 29347521, count: 12 },
68
+ * hour: { bucket: 489125, count: 230 },
69
+ * expiresAt: ISODate("...") // TTL target, ~2h ahead
70
+ * }
71
+ *
72
+ * Bucket values = Math.floor(now_ms / windowMs). Same value across all pods
73
+ * means same window. expiresAt set 2 hours past current hour start so the
74
+ * doc survives just long enough to be relevant.
75
+ *
76
+ * ============================================================================
77
+ * USAGE
78
+ * ============================================================================
79
+ * const breach = await AccessService.hit(key, { minute: 60, hour: 1000 });
80
+ * if (breach) return reply.code(429).send({ retryAfter: breach.retryAfter });
81
+ *
82
+ * Normally wired via `limiterPlugin` (Fastify) which composes the key from
83
+ * namespace + IP/user and resolves the tier from URL prefix. See
84
+ * `src/helpers/limiterPlugin.ts`.
85
+ */
3
86
  import AccessModel from '../models/access.model.js';
4
- import { HttpStatusCode } from '../helpers/HttpStatusCode.enum.js';
5
- export default class AccessService extends Timeseries {
6
- constructor(settings) {
7
- super();
8
- this.model = AccessModel;
9
- this.settings = settings;
10
- }
11
- count(after) {
12
- return this.model.db.countDocuments({ date: { $gte: after } });
87
+ /** 60 seconds in ms — minute bucket width. */
88
+ const MINUTE_MS = 60000;
89
+ /** 1 hour in ms — hour bucket width and TTL stride. */
90
+ const HOUR_MS = 3600000;
91
+ /** Maximum LRU cache entries per pod. Oldest evicted on overflow. */
92
+ const CACHE_MAX = 10000;
93
+ /**
94
+ * Maximum age (ms) a cache entry can be trusted without re-syncing to Mongo.
95
+ * Lower = more accurate Mongo numbers, more writes.
96
+ * Higher = fewer writes, larger cross-pod drift in real-time count.
97
+ */
98
+ const CACHE_SOFT_SYNC_MS = 2000;
99
+ /**
100
+ * Fraction of the limit below which the cache is trusted without DB sync.
101
+ * Example: limit=100, ratio=0.7 → cache trusted until projected count hits 70.
102
+ * Above this, every request hits Mongo so a 429 decision is based on real data.
103
+ */
104
+ const CACHE_SAFE_RATIO = 0.7;
105
+ /**
106
+ * Module-level LRU cache. Map preserves insertion order so re-inserting on
107
+ * access moves the entry to the "most recent" end; oldest can then be
108
+ * evicted from the head when CACHE_MAX is exceeded.
109
+ *
110
+ * Lifetime = process lifetime. No persistence intended; the LRU is purely a
111
+ * per-pod write-reduction optimization, not the source of truth.
112
+ */
113
+ const cache = new Map();
114
+ /**
115
+ * LRU read: fetch entry and mark it as most-recently-used by re-inserting.
116
+ * Returns undefined if not present.
117
+ */
118
+ function cacheGet(key) {
119
+ const entry = cache.get(key);
120
+ if (!entry)
121
+ return;
122
+ cache.delete(key);
123
+ cache.set(key, entry);
124
+ return entry;
125
+ }
126
+ /**
127
+ * LRU write: insert/update entry, then evict oldest if over CACHE_MAX.
128
+ * Eviction is O(1) because Map iteration starts at the oldest key.
129
+ */
130
+ function cacheSet(key, entry) {
131
+ if (cache.has(key))
132
+ cache.delete(key);
133
+ cache.set(key, entry);
134
+ if (cache.size > CACHE_MAX) {
135
+ const oldest = cache.keys().next().value;
136
+ if (oldest)
137
+ cache.delete(oldest);
13
138
  }
14
- async lastHour(ipOrUser, options) {
15
- const secondAgo = dayjs().subtract(1, 'second').toDate();
16
- const minuteAgo = dayjs().subtract(1, 'minute').toDate();
17
- const hourAgo = dayjs().subtract(1, 'hour').toDate();
18
- const quarterAgo = dayjs().subtract(15, 'minutes').toDate();
19
- const match = {
20
- $match: {
21
- date: { $gte: hourAgo },
22
- },
23
- };
24
- if (options?.user) {
25
- match.$match['metadata.user'] = ipOrUser;
26
- }
27
- if (!options?.user) {
28
- match.$match['metadata.ip'] = ipOrUser;
29
- }
30
- const count = await this.model.db.aggregate([
31
- match,
139
+ }
140
+ export default class AccessService {
141
+ /**
142
+ * Atomic upsert of minute + hour buckets for a single key.
143
+ *
144
+ * Uses Mongo 4.2+ pipeline update with `$cond`:
145
+ * if stored bucket === current bucket → increment count
146
+ * else → reset count to 1
147
+ *
148
+ * Both intervals handled in one document, one round-trip. `upsert: true`
149
+ * creates the doc on first ever hit. `returnDocument: 'after'` gives the
150
+ * post-increment values so we can compare against the limit immediately.
151
+ *
152
+ * @param key Composed key (`{namespace}:ip:...` or `:user:...`).
153
+ * @param minuteBucket floor(Date.now() / MINUTE_MS) — same on all pods.
154
+ * @param hourBucket floor(Date.now() / HOUR_MS).
155
+ * @returns Post-increment counts for minute and hour.
156
+ */
157
+ static async incr(key, minuteBucket, hourBucket) {
158
+ // TTL target = 2 full hours past the current hour boundary. Long enough
159
+ // that an active key survives a full sustained-load window, short enough
160
+ // that Mongo doesn't accumulate cold data indefinitely.
161
+ const expiresAt = new Date((hourBucket + 2) * HOUR_MS);
162
+ const result = await AccessModel.db.findOneAndUpdate({ _id: key }, [
32
163
  {
33
- $group: {
34
- _id: null,
35
- second: {
36
- $sum: {
37
- $cond: [{ $gte: ['$date', secondAgo] }, 1, 0],
38
- },
39
- },
164
+ $set: {
40
165
  minute: {
41
- $sum: {
42
- $cond: [{ $gte: ['$date', minuteAgo] }, 1, 0],
43
- },
44
- },
45
- quarter: {
46
- $sum: {
47
- $cond: [{ $gte: ['$date', quarterAgo] }, 1, 0],
48
- },
166
+ $cond: [
167
+ { $eq: ['$minute.bucket', minuteBucket] },
168
+ // Same minute window — bump count. $ifNull guards the upsert
169
+ // path where the doc doesn't exist yet.
170
+ { bucket: minuteBucket, count: { $add: [{ $ifNull: ['$minute.count', 0] }, 1] } },
171
+ // Window rolled over (or new doc) — reset.
172
+ { bucket: minuteBucket, count: 1 },
173
+ ],
49
174
  },
50
175
  hour: {
51
- $sum: 1,
176
+ $cond: [
177
+ { $eq: ['$hour.bucket', hourBucket] },
178
+ { bucket: hourBucket, count: { $add: [{ $ifNull: ['$hour.count', 0] }, 1] } },
179
+ { bucket: hourBucket, count: 1 },
180
+ ],
52
181
  },
182
+ expiresAt,
53
183
  },
54
184
  },
55
- ]).toArray();
56
- if (count.length) {
57
- return count[0];
58
- }
59
- return;
185
+ ], { upsert: true, returnDocument: 'after' });
186
+ // On upsert path with returnDocument:'after', result is always populated
187
+ // — the `?? 1` fallback is defensive for the impossible-null case.
188
+ return {
189
+ minute: result?.minute.count ?? 1,
190
+ hour: result?.hour.count ?? 1,
191
+ };
60
192
  }
61
193
  /**
62
- applies the provided settings that limit the requests per second, minute, 15 minutes and hour.
63
- responds with code 429 if the limits are exceeded
64
- ```TS
65
- callLimits: {
66
- second: number,
67
- minute: number,
68
- quarter: number
69
- hour: number
70
- }
71
- ```
72
- */
73
- async applyLimits(req, res, next, options) {
74
- // Retrieve the last hour's access log for the requesting IP address
75
- const accessLog = await this.lastHour(options?.user && req.auth?.sub ? req.auth.sub : req.ip, options);
76
- // Check if accessLog is available to proceed with rate limiting checks
77
- if (accessLog) {
78
- // Check if the number of requests in the last second exceeds the limit
79
- if (accessLog.second >= this.settings.callLimits.second) {
80
- this.sendResponse(this.settings.callLimits.second.toString(), 'per second', res);
81
- return false;
82
- }
83
- // Check if the number of requests in the last minute exceeds the limit
84
- if (accessLog.minute >= this.settings.callLimits.minute) {
85
- this.sendResponse(this.settings.callLimits.minute.toString(), 'per minute', res);
86
- return false;
87
- }
88
- // Check if the number of requests in the last quarter-hour exceeds the limit
89
- if (accessLog.quarter >= this.settings.callLimits.quarter) {
90
- this.sendResponse(this.settings.callLimits.quarter.toString(), 'per 15 minutes', res);
91
- return false;
92
- }
93
- // Check if the number of requests in the last hour exceeds the limit
94
- if (accessLog.hour >= this.settings.callLimits.hour) {
95
- this.sendResponse(this.settings.callLimits.hour.toString(), 'per hour', res);
96
- return false;
194
+ * Main entry point. Records a request and reports whether it exceeded any
195
+ * configured limit.
196
+ *
197
+ * Decision path:
198
+ * 1. If neither minute nor hour limit set → no-op.
199
+ * 2. Look up cache. If a fresh entry exists, the request keeps the same
200
+ * bucket, AND the projected count stays under CACHE_SAFE_RATIO of the
201
+ * configured limit → bump locally, skip Mongo, return allowed.
202
+ * 3. Otherwise → atomic Mongo upsert, refresh cache from the result.
203
+ * 4. Compare post-increment counts to tier limits. If exceeded, return
204
+ * a LimiterBreach with the offending interval, observed count, and
205
+ * a Retry-After value computed from the time remaining in the window.
206
+ *
207
+ * Important: a breach is only returned when count *exceeds* the limit
208
+ * (`>`), not when it equals it. A limit of 60/minute permits exactly 60
209
+ * requests; the 61st returns the breach.
210
+ *
211
+ * @param key Pre-composed cache+DB key. Caller controls namespacing.
212
+ * @param tier Configured per-interval limits. Missing field = no limit.
213
+ * @returns undefined if allowed, LimiterBreach if denied.
214
+ */
215
+ static async hit(key, tier) {
216
+ if (!tier.minute && !tier.hour)
217
+ return;
218
+ const now = Date.now();
219
+ // Bucket = monotonic integer derived from wall clock. All pods compute
220
+ // the same bucket value at the same instant (subject to NTP skew).
221
+ const minuteBucket = Math.floor(now / MINUTE_MS);
222
+ const hourBucket = Math.floor(now / HOUR_MS);
223
+ const cached = cacheGet(key);
224
+ // Cached entry only relevant if it was recorded inside the *same*
225
+ // window. If either bucket rolled over, we must hit Mongo to learn the
226
+ // new bucket's count (which may already be non-zero from another pod).
227
+ const sameWindow = cached
228
+ && cached.minuteBucket === minuteBucket
229
+ && cached.hourBucket === hourBucket;
230
+ if (sameWindow) {
231
+ // Project what the count would be after this request, then check
232
+ // whether we're still in the "safe zone" where cache absorption is
233
+ // OK. If yes, do not touch Mongo.
234
+ const projMinute = cached.minute + 1;
235
+ const projHour = cached.hour + 1;
236
+ const minuteSafe = !tier.minute || projMinute < tier.minute * CACHE_SAFE_RATIO;
237
+ const hourSafe = !tier.hour || projHour < tier.hour * CACHE_SAFE_RATIO;
238
+ const fresh = now - cached.syncedAt < CACHE_SOFT_SYNC_MS;
239
+ if (minuteSafe && hourSafe && fresh) {
240
+ cached.minute = projMinute;
241
+ cached.hour = projHour;
242
+ // Re-insert to mark MRU; do NOT update syncedAt — it tracks the
243
+ // last *Mongo* sync, not the last access.
244
+ cacheSet(key, cached);
245
+ return;
97
246
  }
98
247
  }
99
- // If the request does not exceed any rate limits, continue to the next middleware
100
- next();
101
- return true;
248
+ // Cache cold, stale, near-limit, or in a new bucket authoritative
249
+ // Mongo write and refresh the cache from the result.
250
+ const counts = await AccessService.incr(key, minuteBucket, hourBucket);
251
+ cacheSet(key, {
252
+ minuteBucket,
253
+ hourBucket,
254
+ minute: counts.minute,
255
+ hour: counts.hour,
256
+ syncedAt: now,
257
+ });
258
+ // Minute checked first because it always trips before hour for steady
259
+ // overload. retryAfter = ms remaining in the current window, rounded
260
+ // up to whole seconds (Retry-After header is integer seconds).
261
+ if (tier.minute && counts.minute > tier.minute) {
262
+ return {
263
+ interval: 'minute',
264
+ limit: tier.minute,
265
+ count: counts.minute,
266
+ retryAfter: Math.ceil((MINUTE_MS - (now % MINUTE_MS)) / 1000),
267
+ };
268
+ }
269
+ if (tier.hour && counts.hour > tier.hour) {
270
+ return {
271
+ interval: 'hour',
272
+ limit: tier.hour,
273
+ count: counts.hour,
274
+ retryAfter: Math.ceil((HOUR_MS - (now % HOUR_MS)) / 1000),
275
+ };
276
+ }
277
+ return;
102
278
  }
103
- sendResponse(limit, interval, res) {
104
- res.set('X-RateLimit-Limit', limit);
105
- res.set('X-RateLimit-Interval', interval);
106
- res.status(HttpStatusCode.TOO_MANY_REQUESTS).end();
279
+ /**
280
+ * Drops the entire in-process cache. Intended for tests and ops tooling
281
+ * (e.g. after manually editing accessLimits docs). Does not touch Mongo.
282
+ */
283
+ static clearCache() {
284
+ cache.clear();
107
285
  }
108
286
  }
109
287
  //# sourceMappingURL=access.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"access.service.js","sourceRoot":"","sources":["../../../src/services/access.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"access.service.js","sourceRoot":"","sources":["../../../src/services/access.service.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
1
  import { UserAppOptionsRequired } from '@lilaquadrat/interfaces';
2
- import OpenAI from 'openai';
2
+ import { GoogleGenAI } from '@google/genai';
3
3
  export declare class Ai {
4
- provider: OpenAI;
5
- constructor(provider: string, key?: string);
4
+ provider: GoogleGenAI;
5
+ model: string;
6
+ constructor(key: string, model: string);
6
7
  tokenUsage(date: string | undefined, options: UserAppOptionsRequired): Promise<import("bson").Document>;
7
8
  generateTextForProject(input: Object, instructions: string, element: string | undefined, context: {
8
9
  project?: string;
@@ -1,19 +1,18 @@
1
- import OpenAI from 'openai';
2
1
  import textGeneration from '../prompts/textGeneration.js';
3
2
  import textGenerationMulti from '../prompts/textGenerationMulti.js';
4
3
  import ProjectModel from '../models/project.model.js';
5
4
  import LoggingService from './logging.service.js';
6
5
  import LoggingModel from '../models/logging.model.js';
7
6
  import dayjs from 'dayjs';
7
+ import { GoogleGenAI, ThinkingLevel } from '@google/genai';
8
8
  export class Ai {
9
- constructor(provider, key) {
9
+ constructor(key, model) {
10
10
  if (key) {
11
- if (provider === 'openai') {
12
- this.provider = new OpenAI({
13
- apiKey: key,
14
- });
15
- }
11
+ this.provider = new GoogleGenAI({
12
+ apiKey: key,
13
+ });
16
14
  }
15
+ this.model = model;
17
16
  }
18
17
  async tokenUsage(date, options) {
19
18
  if (date) {
@@ -65,36 +64,30 @@ export class Ai {
65
64
  return { input: 0, output: 0, calls: 0 };
66
65
  }
67
66
  async generateTextForProject(input, instructions, element, context, options) {
68
- const projectContext = await ProjectModel.db.findOne({ id: options.project }, { projection: { aiDescription: 1, aiTone: 1 } });
67
+ const projectContext = await ProjectModel.db.findOne({ company: options.company, id: options.project }, { projection: { aiDescription: 1, aiTone: 1 } });
69
68
  return this.generateText(input, instructions, element, { ...context, project: projectContext?.aiDescription, tone: projectContext?.aiTone }, options);
70
69
  }
71
70
  async generateText(input, instructions, element, context, options) {
72
71
  const content = options.type === 'single' ? textGeneration(input, element, context) : textGenerationMulti(input, context);
73
- const response = await this.provider.chat.completions.create({
74
- model: 'gpt-4.1-mini',
75
- response_format: { type: 'json_object' },
76
- store: true,
77
- messages: [
78
- {
79
- role: 'system',
80
- content,
72
+ const response = await this.provider.models.generateContent({
73
+ model: this.model,
74
+ config: {
75
+ systemInstruction: content,
76
+ thinkingConfig: {
77
+ thinkingLevel: ThinkingLevel.LOW,
81
78
  },
82
- {
83
- role: 'user',
84
- content: JSON.stringify({
85
- input,
86
- instructions,
87
- context,
88
- }),
89
- },
90
- ],
91
- }, {
92
- timeout: 30000,
79
+ responseMimeType: 'application/json'
80
+ },
81
+ contents: JSON.stringify({
82
+ input,
83
+ instructions,
84
+ context,
85
+ }),
93
86
  });
94
87
  await LoggingService.add({ input: { input, instructions, context }, response }, { type: 'ai', company: options.company, project: options.project, app: options.app, user: options.user });
95
- const responseData = JSON.parse(response.choices[0].message?.content);
88
+ const responseData = JSON.parse(response.text);
96
89
  return { output: responseData.input };
97
90
  }
98
91
  }
99
- export default new Ai('openai', process.env.OPENAI_API_KEY);
92
+ export default new Ai(process.env.GOOGLE_API_KEY, process.env.AI_MODEL);
100
93
  //# sourceMappingURL=ai.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai.service.js","sourceRoot":"","sources":["../../../src/services/ai.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai.service.js","sourceRoot":"","sources":["../../../src/services/ai.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ declare class AuthService {
2
+ private tokenCache;
3
+ private inflight;
4
+ url: string;
5
+ constructor(url: string);
6
+ token(clientId: string, clientSecret: string): Promise<string>;
7
+ getToken(clientId: string, clientSecret: string): Promise<string>;
8
+ private buildTokenUrl;
9
+ }
10
+ declare const _default: AuthService;
11
+ export default _default;
@@ -0,0 +1,70 @@
1
+ import logger from "../logger.js";
2
+ import querystring from 'querystring';
3
+ class AuthService {
4
+ constructor(url) {
5
+ this.tokenCache = null;
6
+ this.inflight = null;
7
+ this.url = url;
8
+ }
9
+ async token(clientId, clientSecret) {
10
+ // Check if we have a valid cached token
11
+ if (this.tokenCache && Date.now() < this.tokenCache.expiresAt) {
12
+ logger.debug('authservice.token.cached');
13
+ return this.tokenCache.accessToken;
14
+ }
15
+ if (this.inflight) {
16
+ logger.debug('authservice.token.inflight');
17
+ return this.inflight;
18
+ }
19
+ logger.debug('authservice.token.fetching');
20
+ this.inflight = this.getToken(clientId, clientSecret).finally(() => {
21
+ this.inflight = null;
22
+ });
23
+ return this.inflight;
24
+ }
25
+ async getToken(clientId, clientSecret) {
26
+ if (!this.url)
27
+ throw new Error('OAUTH_URL is not configured');
28
+ const tokenUrl = this.buildTokenUrl();
29
+ const body = querystring.stringify({
30
+ client_id: clientId,
31
+ client_secret: clientSecret,
32
+ audience: 'https://editor.lilaquadrat.de/api',
33
+ grant_type: 'client_credentials',
34
+ });
35
+ const response = await fetch(tokenUrl, {
36
+ method: 'POST',
37
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
38
+ body,
39
+ });
40
+ if (!response.ok) {
41
+ const errorData = await response.json().catch(() => ({}));
42
+ logger.error({ message: 'Token request failed', status: response.status, errorData });
43
+ throw new Error(`Token request failed with status ${response.status}`);
44
+ }
45
+ const token = await response.json();
46
+ if (!token.access_token || !token.expires_in) {
47
+ logger.error({ message: 'Invalid token response', token });
48
+ throw new Error('Invalid token response from OAuth server');
49
+ }
50
+ this.tokenCache = {
51
+ accessToken: token.access_token,
52
+ expiresAt: Date.now() + (token.expires_in - 60) * 1000,
53
+ };
54
+ logger.debug('authservice.token.get.success');
55
+ return token.access_token;
56
+ }
57
+ buildTokenUrl() {
58
+ try {
59
+ const url = new URL(`https://${this.url}/oauth/token`);
60
+ logger.debug(`authservice.token.url: ${url.toString()}`);
61
+ return url.toString();
62
+ }
63
+ catch (error) {
64
+ logger.error({ message: 'Invalid OAUTH_URL', url: this.url, error });
65
+ throw new Error(`Invalid OAUTH_URL: ${this.url}`);
66
+ }
67
+ }
68
+ }
69
+ export default new AuthService(process.env.OAUTH_URL);
70
+ //# sourceMappingURL=auth.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../../src/services/auth.service.ts"],"names":[],"mappings":"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"}