@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.
- package/eslint.config.js +146 -0
- package/lib/fastify-plugins.d.ts +6 -0
- package/lib/fastify-plugins.js +7 -0
- package/lib/fastify-plugins.js.map +1 -0
- package/lib/helpers.d.ts +4 -2
- package/lib/helpers.js +13 -2
- package/lib/helpers.js.map +1 -1
- package/lib/main.d.ts +1 -3
- package/lib/main.js +7 -3
- package/lib/main.js.map +1 -1
- package/lib/models.d.ts +4 -4
- package/lib/models.js +4 -4
- package/lib/models.js.map +1 -1
- package/lib/services.d.ts +5 -5
- package/lib/services.js +5 -13
- package/lib/services.js.map +1 -1
- package/lib/src/Immutable.class.d.ts +8 -1
- package/lib/src/Immutable.class.js +52 -8
- package/lib/src/Immutable.class.js.map +1 -1
- package/lib/src/ShareClientFactory.class.d.ts +1 -3
- package/lib/src/ShareClientFactory.class.js +1 -9
- package/lib/src/ShareClientFactory.class.js.map +1 -1
- package/lib/src/classes/models.class.js.map +1 -1
- package/lib/src/classes/modelv2.class.d.ts +2 -0
- package/lib/src/classes/modelv2.class.js +1 -1
- package/lib/src/classes/modelv2.class.js.map +1 -1
- package/lib/src/classes/mongo.class.js +4 -14
- package/lib/src/classes/mongo.class.js.map +1 -1
- package/lib/src/functions/handleError.d.ts +2 -3
- package/lib/src/functions/handleError.js +3 -16
- package/lib/src/functions/handleError.js.map +1 -1
- package/lib/src/functions/optionsHelper.d.ts +4 -4
- package/lib/src/functions/optionsHelper.js +5 -4
- package/lib/src/functions/optionsHelper.js.map +1 -1
- package/lib/src/functions/respondCode.d.ts +2 -1
- package/lib/src/functions/respondCode.js +1 -1
- package/lib/src/functions/respondCode.js.map +1 -1
- package/lib/src/helpers/ControllerHelper.d.ts +73 -0
- package/lib/src/helpers/ControllerHelper.js +242 -0
- package/lib/src/helpers/ControllerHelper.js.map +1 -0
- package/lib/src/helpers/EnvMapper.js +1 -0
- package/lib/src/helpers/EnvMapper.js.map +1 -0
- package/lib/src/helpers/auth0config.d.ts +6 -0
- package/lib/src/helpers/auth0config.js +23 -0
- package/lib/src/helpers/auth0config.js.map +1 -0
- package/lib/src/helpers/authPlugin.d.ts +29 -0
- package/lib/src/helpers/authPlugin.js +77 -0
- package/lib/src/helpers/authPlugin.js.map +1 -0
- package/lib/src/helpers/cacheHelper.d.ts +69 -0
- package/lib/src/helpers/cacheHelper.js +235 -0
- package/lib/src/helpers/cacheHelper.js.map +1 -0
- package/lib/src/helpers/createSasToken.d.ts +0 -2
- package/lib/src/helpers/createSasToken.js +35 -32
- package/lib/src/helpers/createSasToken.js.map +1 -1
- package/lib/src/helpers/getSecrets.d.ts +1 -1
- package/lib/src/helpers/getSecrets.js +10 -12
- package/lib/src/helpers/getSecrets.js.map +1 -1
- package/lib/src/helpers/limiterPlugin.d.ts +9 -0
- package/lib/src/helpers/limiterPlugin.js +72 -0
- package/lib/src/helpers/limiterPlugin.js.map +1 -0
- package/lib/src/helpers/loggingPlugin.d.ts +30 -0
- package/lib/src/helpers/loggingPlugin.js +87 -0
- package/lib/src/helpers/loggingPlugin.js.map +1 -0
- package/lib/src/helpers/queryAssertionPlugin.d.ts +3 -0
- package/lib/src/helpers/queryAssertionPlugin.js +20 -0
- package/lib/src/helpers/queryAssertionPlugin.js.map +1 -0
- package/lib/src/helpers/safeObjectId.d.ts +1 -1
- package/lib/src/helpers/safeObjectId.js +5 -1
- package/lib/src/helpers/safeObjectId.js.map +1 -1
- package/lib/src/helpers/storageSdkFactory.d.ts +2 -0
- package/lib/src/helpers/storageSdkFactory.js +11 -0
- package/lib/src/helpers/storageSdkFactory.js.map +1 -0
- package/lib/src/helpers/studioAppPlugin.d.ts +3 -0
- package/lib/src/helpers/studioAppPlugin.js +16 -0
- package/lib/src/helpers/studioAppPlugin.js.map +1 -0
- package/lib/src/logger.js +57 -8
- package/lib/src/logger.js.map +1 -1
- package/lib/src/models/access.model.d.ts +14 -3
- package/lib/src/models/access.model.js +7 -9
- package/lib/src/models/access.model.js.map +1 -1
- package/lib/src/models/customers.model.js +14 -4
- package/lib/src/models/customers.model.js.map +1 -1
- package/lib/src/models/design.model.d.ts +4 -0
- package/lib/src/models/design.model.js +58 -0
- package/lib/src/models/design.model.js.map +1 -0
- package/lib/src/models/domain.model.js +1 -1
- package/lib/src/models/domain.model.js.map +1 -1
- package/lib/src/models/editor.model.js +7 -0
- package/lib/src/models/editor.model.js.map +1 -1
- package/lib/src/models/emailLimit.model.d.ts +4 -0
- package/lib/src/models/emailLimit.model.js +31 -0
- package/lib/src/models/emailLimit.model.js.map +1 -0
- package/lib/src/models/hosting.model.js +1 -3
- package/lib/src/models/hosting.model.js.map +1 -1
- package/lib/src/models/hostingSettings.model.js +6 -4
- package/lib/src/models/hostingSettings.model.js.map +1 -1
- package/lib/src/models/invoice.model.d.ts +4 -0
- package/lib/src/models/invoice.model.js +235 -0
- package/lib/src/models/invoice.model.js.map +1 -0
- package/lib/src/models/mailFrom.model.js +51 -10
- package/lib/src/models/mailFrom.model.js.map +1 -1
- package/lib/src/models/project.model.js +2 -4
- package/lib/src/models/project.model.js.map +1 -1
- package/lib/src/models/publish-method.model.js +79 -430
- package/lib/src/models/publish-method.model.js.map +1 -1
- package/lib/src/models/publish.model.js +6 -0
- package/lib/src/models/publish.model.js.map +1 -1
- package/lib/src/models/storage.model.js +23 -5
- package/lib/src/models/storage.model.js.map +1 -1
- package/lib/src/models/structure.model.js +40 -0
- package/lib/src/models/structure.model.js.map +1 -1
- package/lib/src/models/upload.model.js +38 -2
- package/lib/src/models/upload.model.js.map +1 -1
- package/lib/src/prompts/textGeneration.js +88 -0
- package/lib/src/prompts/textGeneration.js.map +1 -1
- package/lib/src/prompts/textGenerationMulti.js +78 -44
- package/lib/src/prompts/textGenerationMulti.js.map +1 -1
- package/lib/src/services/access.service.d.ts +132 -33
- package/lib/src/services/access.service.js +270 -92
- package/lib/src/services/access.service.js.map +1 -1
- package/lib/src/services/ai.service.d.ts +4 -3
- package/lib/src/services/ai.service.js +22 -29
- package/lib/src/services/ai.service.js.map +1 -1
- package/lib/src/services/auth.service.d.ts +11 -0
- package/lib/src/services/auth.service.js +70 -0
- package/lib/src/services/auth.service.js.map +1 -0
- package/lib/src/services/conf.service.d.ts +3 -31
- package/lib/src/services/conf.service.js +58 -167
- package/lib/src/services/conf.service.js.map +1 -1
- package/lib/src/services/customers.service.d.ts +8 -4
- package/lib/src/services/customers.service.js +34 -7
- package/lib/src/services/customers.service.js.map +1 -1
- package/lib/src/services/designs.service.d.ts +7 -0
- package/lib/src/services/designs.service.js +10 -0
- package/lib/src/services/designs.service.js.map +1 -0
- package/lib/src/services/domains.service.d.ts +18 -84
- package/lib/src/services/domains.service.js +91 -583
- package/lib/src/services/domains.service.js.map +1 -1
- package/lib/src/services/editor.service.d.ts +4 -0
- package/lib/src/services/editor.service.js +28 -0
- package/lib/src/services/editor.service.js.map +1 -1
- package/lib/src/services/emailLimit.service.d.ts +21 -0
- package/lib/src/services/emailLimit.service.js +51 -0
- package/lib/src/services/emailLimit.service.js.map +1 -0
- package/lib/src/services/hosting.service.d.ts +12 -24
- package/lib/src/services/hosting.service.js +32 -122
- package/lib/src/services/hosting.service.js.map +1 -1
- package/lib/src/services/hostingAdmin.service.d.ts +1 -1
- package/lib/src/services/hostingAdmin.service.js +2 -2
- package/lib/src/services/hostingAdmin.service.js.map +1 -1
- package/lib/src/services/import.service.d.ts +6 -22
- package/lib/src/services/import.service.js +63 -65
- package/lib/src/services/import.service.js.map +1 -1
- package/lib/src/services/invoices.service.d.ts +30 -0
- package/lib/src/services/invoices.service.js +265 -0
- package/lib/src/services/invoices.service.js.map +1 -0
- package/lib/src/services/jetstream.service.d.ts +5 -3
- package/lib/src/services/jetstream.service.js +63 -7
- package/lib/src/services/jetstream.service.js.map +1 -1
- package/lib/src/services/listParticipants.service.d.ts +3 -5
- package/lib/src/services/listParticipants.service.js +76 -16
- package/lib/src/services/listParticipants.service.js.map +1 -1
- package/lib/src/services/mailFrom.service.d.ts +14 -1
- package/lib/src/services/mailFrom.service.js +59 -0
- package/lib/src/services/mailFrom.service.js.map +1 -1
- package/lib/src/services/me.service.d.ts +23 -12
- package/lib/src/services/me.service.js +65 -88
- package/lib/src/services/me.service.js.map +1 -1
- package/lib/src/services/publish.service.d.ts +6 -8
- package/lib/src/services/publish.service.js +34 -32
- package/lib/src/services/publish.service.js.map +1 -1
- package/lib/src/services/publishData.service.d.ts +10 -7
- package/lib/src/services/publishData.service.js +32 -75
- package/lib/src/services/publishData.service.js.map +1 -1
- package/lib/src/services/spamAnalasys.service.d.ts +4 -4
- package/lib/src/services/spamAnalasys.service.js +36 -44
- package/lib/src/services/spamAnalasys.service.js.map +1 -1
- package/lib/src/services/storage.service.d.ts +68 -39
- package/lib/src/services/storage.service.js +378 -209
- package/lib/src/services/storage.service.js.map +1 -1
- package/lib/src/services/structures.service.d.ts +8 -1
- package/lib/src/services/structures.service.js +26 -1
- package/lib/src/services/structures.service.js.map +1 -1
- package/lib/src/services/upload.service.d.ts +8 -1
- package/lib/src/services/upload.service.js +76 -3
- package/lib/src/services/upload.service.js.map +1 -1
- package/lib/tests/groupStructuresByModel.spec.d.ts +1 -0
- package/lib/tests/groupStructuresByModel.spec.js +33 -0
- package/lib/tests/groupStructuresByModel.spec.js.map +1 -0
- package/lib/tests/listParticipantsServiceJoin.spec.d.ts +1 -0
- package/lib/tests/listParticipantsServiceJoin.spec.js +151 -0
- package/lib/tests/listParticipantsServiceJoin.spec.js.map +1 -0
- package/lib/tests/storageServiceHandleFile.spec.d.ts +1 -0
- package/lib/tests/storageServiceHandleFile.spec.js +94 -0
- package/lib/tests/storageServiceHandleFile.spec.js.map +1 -0
- package/lib/tests/storageServiceToken.spec.d.ts +1 -0
- package/lib/tests/storageServiceToken.spec.js +104 -0
- package/lib/tests/storageServiceToken.spec.js.map +1 -0
- package/lib/tests/uploadServiceCreate.spec.d.ts +1 -0
- package/lib/tests/uploadServiceCreate.spec.js +81 -0
- package/lib/tests/uploadServiceCreate.spec.js.map +1 -0
- package/package.json +30 -26
- package/lib/src/AzureBlobStorage.share.d.ts +0 -19
- package/lib/src/AzureBlobStorage.share.js +0 -162
- package/lib/src/AzureBlobStorage.share.js.map +0 -1
- package/lib/src/AzureFileStorage.share.d.ts +0 -22
- package/lib/src/AzureFileStorage.share.js +0 -139
- package/lib/src/AzureFileStorage.share.js.map +0 -1
- package/lib/src/AzureVault.d.ts +0 -14
- package/lib/src/AzureVault.js +0 -28
- package/lib/src/AzureVault.js.map +0 -1
- package/lib/src/dns.challenge.class.d.ts +0 -17
- package/lib/src/dns.challenge.class.js +0 -41
- package/lib/src/dns.challenge.class.js.map +0 -1
- package/lib/src/http.challenge.class.d.ts +0 -33
- package/lib/src/http.challenge.class.js +0 -58
- package/lib/src/http.challenge.class.js.map +0 -1
- package/lib/src/models/certificate-action.model.d.ts +0 -5
- package/lib/src/models/certificate-action.model.js +0 -230
- package/lib/src/models/certificate-action.model.js.map +0 -1
- package/lib/src/models/certificate.model.d.ts +0 -4
- package/lib/src/models/certificate.model.js +0 -96
- package/lib/src/models/certificate.model.js.map +0 -1
- package/lib/src/models/editorBase.model.d.ts +0 -4
- package/lib/src/models/editorBase.model.js +0 -39
- package/lib/src/models/editorBase.model.js.map +0 -1
- package/lib/src/services/certificates.service.js +0 -199
- package/lib/src/services/certificates.service.js.map +0 -1
- package/lib/src/services/certificatesAction.service.d.ts +0 -0
- package/lib/src/services/certificatesAction.service.js +0 -237
- package/lib/src/services/certificatesAction.service.js.map +0 -1
- package/lib/src/services/editorBase.service.d.ts +0 -46
- package/lib/src/services/editorBase.service.js +0 -161
- package/lib/src/services/editorBase.service.js.map +0 -1
- package/lib/src/services/handleFile.service.d.ts +0 -9
- package/lib/src/services/handleFile.service.js +0 -45
- package/lib/src/services/handleFile.service.js.map +0 -1
- package/lib/src/services/media.service.d.ts +0 -35
- package/lib/src/services/media.service.js +0 -418
- package/lib/src/services/media.service.js.map +0 -1
- package/lib/src/services/share.service.d.ts +0 -6
- package/lib/src/services/share.service.js +0 -4
- package/lib/src/services/share.service.js.map +0 -1
- /package/lib/src/{services/certificates.service.d.ts → helpers/EnvMapper.d.ts} +0 -0
|
@@ -1,109 +1,287 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
$
|
|
34
|
-
_id: null,
|
|
35
|
-
second: {
|
|
36
|
-
$sum: {
|
|
37
|
-
$cond: [{ $gte: ['$date', secondAgo] }, 1, 0],
|
|
38
|
-
},
|
|
39
|
-
},
|
|
164
|
+
$set: {
|
|
40
165
|
minute: {
|
|
41
|
-
$
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
$
|
|
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
|
-
])
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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":"AAAA,OAAO,
|
|
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
|
|
2
|
+
import { GoogleGenAI } from '@google/genai';
|
|
3
3
|
export declare class Ai {
|
|
4
|
-
provider:
|
|
5
|
-
|
|
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(
|
|
9
|
+
constructor(key, model) {
|
|
10
10
|
if (key) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
74
|
-
model:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.
|
|
88
|
+
const responseData = JSON.parse(response.text);
|
|
96
89
|
return { output: responseData.input };
|
|
97
90
|
}
|
|
98
91
|
}
|
|
99
|
-
export default new Ai(
|
|
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":"AACA,OAAO,
|
|
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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"}
|