@ossy/platform 3.24.0 → 3.25.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/package.json +10 -10
- package/src/proxy-internal.js +3 -3
- package/src/runtime.js +2 -1
- package/src/server.js +2 -1
- package/src/ses/poll-inbound-mail.js +8 -4
- package/src/site-loader.js +3 -3
- package/src/storage/s3-storage.client.js +2 -2
- package/src/storage/s3.client.js +1 -1
- package/src/tasks/change-stream.js +1 -1
- package/src/tasks/task-service.js +20 -13
- package/src/token.service.js +1 -1
- package/src/users.middleware.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.25.0",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -49,18 +49,18 @@
|
|
|
49
49
|
"@aws-sdk/util-format-url": "^3.972.17",
|
|
50
50
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
51
51
|
"@ossy/config": "^3.0.9",
|
|
52
|
-
"@ossy/email": "^3.
|
|
53
|
-
"@ossy/event-store": "^3.
|
|
52
|
+
"@ossy/email": "^3.25.0",
|
|
53
|
+
"@ossy/event-store": "^3.24.1",
|
|
54
54
|
"@ossy/locale": "^3.4.0",
|
|
55
55
|
"@ossy/manifest": "^3.24.0",
|
|
56
|
-
"@ossy/observability": "^3.
|
|
57
|
-
"@ossy/policies": "^3.
|
|
58
|
-
"@ossy/router": "^3.0
|
|
56
|
+
"@ossy/observability": "^3.24.1",
|
|
57
|
+
"@ossy/policies": "^3.24.1",
|
|
58
|
+
"@ossy/router": "^3.25.0",
|
|
59
59
|
"@ossy/schema": "^3.24.0",
|
|
60
60
|
"@ossy/sdk": "^3.24.0",
|
|
61
|
-
"@ossy/tokens": "^3.
|
|
62
|
-
"@ossy/users": "^3.24.
|
|
63
|
-
"@ossy/workspaces": "^3.
|
|
61
|
+
"@ossy/tokens": "^3.24.1",
|
|
62
|
+
"@ossy/users": "^3.24.1",
|
|
63
|
+
"@ossy/workspaces": "^3.25.0",
|
|
64
64
|
"cookie-parser": "^1.4.7",
|
|
65
65
|
"dotenv": ">=16.0.0 <18.0.0",
|
|
66
66
|
"express": ">=5.0.0 <6.0.0",
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"Dockerfile",
|
|
82
82
|
"docker-healthcheck.js"
|
|
83
83
|
],
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "00dd4b51a94de7e0399c9dfa59380a53e58c8523"
|
|
85
85
|
}
|
package/src/proxy-internal.js
CHANGED
|
@@ -58,7 +58,7 @@ export function ProxyInternal () {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'GET') {
|
|
61
|
-
log.
|
|
61
|
+
log.debug('[@ossy/platform][proxy] GET /@ossy/users/me/app-settings')
|
|
62
62
|
const userSettings = readUserAppSettings(req)
|
|
63
63
|
const workspaceId = readWorkspaceIdFromCookies(req)
|
|
64
64
|
res.status(200)
|
|
@@ -68,13 +68,13 @@ export function ProxyInternal () {
|
|
|
68
68
|
|
|
69
69
|
if (!isRemoteOssyApiUrl()) {
|
|
70
70
|
const dest = ossyProxyLocalPath(req.originalUrl)
|
|
71
|
-
log.
|
|
71
|
+
log.debug(`[@ossy/platform][proxy] ${req.method} ${req.originalUrl} → ${dest} (same origin)`)
|
|
72
72
|
req.url = dest
|
|
73
73
|
req.originalUrl = dest
|
|
74
74
|
return next()
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
log.
|
|
77
|
+
log.debug(`[@ossy/platform][proxy] ${req.method} ${req.originalUrl}`)
|
|
78
78
|
|
|
79
79
|
const domain = String(process.env.OSSY_API_URL).trim()
|
|
80
80
|
.replace(/\/api\/v0\/?$/, '')
|
package/src/runtime.js
CHANGED
|
@@ -187,7 +187,8 @@ export async function startRuntime ({ port } = {}) {
|
|
|
187
187
|
const { language, messages, fallbackMessages } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
188
188
|
if (typeof pageEntry.redirect === 'string' && pageEntry.redirect.trim()) {
|
|
189
189
|
const targetId = pageEntry.redirect.trim().replace(/^@/, '')
|
|
190
|
-
const
|
|
190
|
+
const params = pageRouter.getParamsFromUrl(requestUrl)
|
|
191
|
+
const targetPath = pageRouter.getPathname({ id: targetId, language, params })
|
|
191
192
|
if (targetPath) {
|
|
192
193
|
const search = new URL(requestUrl, 'http://localhost').search || ''
|
|
193
194
|
res.redirect(302, `${targetPath}${search}`)
|
package/src/server.js
CHANGED
|
@@ -530,7 +530,8 @@ export async function startServer (options = {}) {
|
|
|
530
530
|
if (typeof pageEntry.redirect === 'string' && pageEntry.redirect.trim()) {
|
|
531
531
|
const { language } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
532
532
|
const targetId = pageEntry.redirect.trim().replace(/^@/, '')
|
|
533
|
-
const
|
|
533
|
+
const params = pageRouter.getParamsFromUrl(requestUrl)
|
|
534
|
+
const targetPath = pageRouter.getPathname({ id: targetId, language, params })
|
|
534
535
|
if (targetPath) {
|
|
535
536
|
const search = new URL(requestUrl, 'http://localhost').search || ''
|
|
536
537
|
res.redirect(302, `${targetPath}${search}`)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DeleteMessageCommand, ReceiveMessageCommand, SQSClient } from '@aws-sdk/client-sqs'
|
|
2
2
|
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
|
|
3
3
|
import { fileSesReceipt, sesReceiptFromBody } from '@ossy/email/ses-inbound'
|
|
4
|
+
import { canonicalWorkspaceId } from '@ossy/workspaces/server'
|
|
4
5
|
import { createInProcessSdk } from '../tasks/in-process-sdk.js'
|
|
5
6
|
import { inboundAwsClientConfig } from './inbound-aws-credentials.js'
|
|
6
7
|
|
|
@@ -21,7 +22,7 @@ export async function ackInboundMessages (messages, { file, deleteMessage, log }
|
|
|
21
22
|
for (const message of messages ?? []) {
|
|
22
23
|
try {
|
|
23
24
|
const result = await file(message.Body)
|
|
24
|
-
if (result?.skipped) log?.
|
|
25
|
+
if (result?.skipped) log?.debug('[ses/inbound] skipped receipt', result)
|
|
25
26
|
await deleteMessage(message)
|
|
26
27
|
} catch (error) {
|
|
27
28
|
if (error?.name === 'NoSuchKey') {
|
|
@@ -47,9 +48,12 @@ export function startInboundMailPoller ({ queueUrl, bucket, log } = {}) {
|
|
|
47
48
|
const file = (body) => fileSesReceipt(sesReceiptFromBody(body), {
|
|
48
49
|
bucket,
|
|
49
50
|
getObject: (location) => readObject(objects, location),
|
|
50
|
-
invoke: ({ mime, workspaceId }) =>
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
invoke: async ({ mime, workspaceId }) => {
|
|
52
|
+
const canonical = await canonicalWorkspaceId(workspaceId)
|
|
53
|
+
return createInProcessSdk()
|
|
54
|
+
.forWorkspace(canonical)
|
|
55
|
+
.invoke(ReceiveInbound, { mime, workspaceId: canonical })
|
|
56
|
+
},
|
|
53
57
|
})
|
|
54
58
|
|
|
55
59
|
const run = (async () => {
|
package/src/site-loader.js
CHANGED
|
@@ -128,7 +128,7 @@ export async function loadSite (domain) {
|
|
|
128
128
|
const buildDir = path.join(BASE_DIR, domain)
|
|
129
129
|
const cmsLocation = `/@ossy/apps/${domain}`
|
|
130
130
|
|
|
131
|
-
log.
|
|
131
|
+
log.debug(`[@ossy/platform] Loading site for ${domain} (workspace ${workspaceId}) from CMS ${cmsLocation}…`)
|
|
132
132
|
|
|
133
133
|
const sdk = SDK.of({
|
|
134
134
|
apiUrl: API_URL,
|
|
@@ -149,7 +149,7 @@ export async function loadSite (domain) {
|
|
|
149
149
|
|
|
150
150
|
await runWithConcurrency(tasks, DOWNLOAD_CONCURRENCY)
|
|
151
151
|
|
|
152
|
-
log.
|
|
152
|
+
log.debug(`[@ossy/platform] Site ${domain} ready — ${files.length} file(s) in ${buildDir}`)
|
|
153
153
|
|
|
154
154
|
cache.set(domain, { buildDir, workspaceId, loadedAt: Date.now() })
|
|
155
155
|
return buildDir
|
|
@@ -161,5 +161,5 @@ export async function loadSite (domain) {
|
|
|
161
161
|
*/
|
|
162
162
|
export function invalidateSite (domain) {
|
|
163
163
|
cache.delete(domain)
|
|
164
|
-
log.
|
|
164
|
+
log.debug(`[@ossy/platform] Cache invalidated for ${domain}`)
|
|
165
165
|
}
|
|
@@ -57,7 +57,7 @@ export function createS3StorageClient ({
|
|
|
57
57
|
},
|
|
58
58
|
|
|
59
59
|
createUploadUrl ({ ContentType, ContentLength, Key, expiresIn = 60 * 60 * 24 }) {
|
|
60
|
-
log.
|
|
60
|
+
log.debug('[S3Storage][createUploadUrl] Creating upload URL')
|
|
61
61
|
|
|
62
62
|
const clientParams = {
|
|
63
63
|
Bucket: bucket,
|
|
@@ -88,7 +88,7 @@ export function createS3StorageClient ({
|
|
|
88
88
|
const Key = toS3Key(logicalKey)
|
|
89
89
|
const s3Client = createAwsClient()
|
|
90
90
|
const Body = Buffer.isBuffer(body) ? body : Buffer.from(body)
|
|
91
|
-
log.
|
|
91
|
+
log.debug('[S3Storage][save] Putting object', { key: logicalKey, bytes: Body.length })
|
|
92
92
|
await s3Client.send(new PutObjectCommand({
|
|
93
93
|
Bucket: bucket,
|
|
94
94
|
Key,
|
package/src/storage/s3.client.js
CHANGED
|
@@ -46,7 +46,7 @@ export class S3Client {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
static createUploadUrl({ ContentType, ContentLength, Key, Bucket, expiresIn = 60 * 60 * 24 }) {
|
|
49
|
-
log.
|
|
49
|
+
log.debug('[S3Client][createUploadUrl()] Creating upload URL')
|
|
50
50
|
|
|
51
51
|
const bucket = Bucket ?? ConfigService.MediaRepository
|
|
52
52
|
if (!bucket) {
|
|
@@ -157,7 +157,7 @@ export class ChangeStream {
|
|
|
157
157
|
})
|
|
158
158
|
|
|
159
159
|
changeStream.on('change', (change) => {
|
|
160
|
-
log.
|
|
160
|
+
log.debug('[ChangeStream] Change detected')
|
|
161
161
|
const event = change.fullDocument
|
|
162
162
|
if (event) {
|
|
163
163
|
ProjectionRebuild.dispatch(event).catch((error) => {
|
|
@@ -370,20 +370,27 @@ export class TaskService {
|
|
|
370
370
|
if (!event) return
|
|
371
371
|
if (AUDIT_DISPATCH_TYPES.has(event.type)) return
|
|
372
372
|
|
|
373
|
-
const baseSdk = sdk ?? await sdkBoundTo()
|
|
374
|
-
const effectiveSdk = typeof baseSdk?.withEventContext === 'function'
|
|
375
|
-
? baseSdk.withEventContext(event)
|
|
376
|
-
: baseSdk
|
|
377
|
-
|
|
378
373
|
const workspaceId = event.payload?.belongsTo ?? event.belongsTo ?? null
|
|
374
|
+
const matches = []
|
|
379
375
|
|
|
380
|
-
for (const
|
|
381
|
-
const { triggers, armed } = await TaskService._eventTriggersForDispatch(metadata, workspaceId)
|
|
376
|
+
for (const task of TaskService._tasks) {
|
|
377
|
+
const { triggers, armed } = await TaskService._eventTriggersForDispatch(task.metadata, workspaceId)
|
|
382
378
|
if (!armed) continue
|
|
383
379
|
|
|
384
380
|
const matchedTrigger = triggers.find((trigger) => TaskService._matchesEventTrigger(trigger, event))
|
|
385
381
|
if (!matchedTrigger) continue
|
|
382
|
+
matches.push({ ...task, matchedTrigger })
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Loading the bot reads the user aggregate. Do that only when a task will run.
|
|
386
|
+
if (matches.length === 0) return
|
|
387
|
+
|
|
388
|
+
const baseSdk = sdk ?? await sdkBoundTo()
|
|
389
|
+
const effectiveSdk = typeof baseSdk?.withEventContext === 'function'
|
|
390
|
+
? baseSdk.withEventContext(event)
|
|
391
|
+
: baseSdk
|
|
386
392
|
|
|
393
|
+
for (const { metadata, handler, matchedTrigger } of matches) {
|
|
387
394
|
const _taskId = metadata.id
|
|
388
395
|
const claimKey = buildEventClaimKey(_taskId, event.id)
|
|
389
396
|
|
|
@@ -391,11 +398,11 @@ export class TaskService {
|
|
|
391
398
|
void (async () => {
|
|
392
399
|
const won = await tryClaimDispatch(claimKey)
|
|
393
400
|
if (!won) {
|
|
394
|
-
_serviceLog.
|
|
401
|
+
_serviceLog.debug(`Skipping task "${_taskId}" — dispatch claim lost`)
|
|
395
402
|
return
|
|
396
403
|
}
|
|
397
404
|
|
|
398
|
-
_serviceLog.
|
|
405
|
+
_serviceLog.debug(`Dispatching task "${_taskId}"`)
|
|
399
406
|
|
|
400
407
|
const taskContext = buildTaskContext(
|
|
401
408
|
{ sdk: effectiveSdk, integrations: IntegrationService, log: createLogger(_taskId) },
|
|
@@ -470,11 +477,11 @@ export class TaskService {
|
|
|
470
477
|
void (async () => {
|
|
471
478
|
const won = await tryClaimDispatch(claimKey)
|
|
472
479
|
if (!won) {
|
|
473
|
-
_serviceLog.
|
|
480
|
+
_serviceLog.debug(`Skipping on_action task "${_taskId}" — dispatch claim lost`)
|
|
474
481
|
return
|
|
475
482
|
}
|
|
476
483
|
|
|
477
|
-
_serviceLog.
|
|
484
|
+
_serviceLog.debug(`Dispatching on_action task "${_taskId}" for action "${actionId}"`)
|
|
478
485
|
|
|
479
486
|
const taskContext = buildTaskContext(
|
|
480
487
|
{
|
|
@@ -587,14 +594,14 @@ export class TaskService {
|
|
|
587
594
|
void (async () => {
|
|
588
595
|
const won = await tryClaimDispatch(claimKey)
|
|
589
596
|
if (!won) {
|
|
590
|
-
_serviceLog.
|
|
597
|
+
_serviceLog.debug(
|
|
591
598
|
`Skipping schedule for task "${metadata.id}" — dispatch claim lost`
|
|
592
599
|
+ (job.workspaceId ? ` (workspace ${job.workspaceId})` : ''),
|
|
593
600
|
)
|
|
594
601
|
return
|
|
595
602
|
}
|
|
596
603
|
|
|
597
|
-
_serviceLog.
|
|
604
|
+
_serviceLog.debug(
|
|
598
605
|
`Schedule fired for task "${metadata.id}"`
|
|
599
606
|
+ (job.workspaceId ? ` (workspace ${job.workspaceId})` : ''),
|
|
600
607
|
)
|
package/src/token.service.js
CHANGED
package/src/users.middleware.js
CHANGED
|
@@ -71,7 +71,7 @@ export class UsersMiddleware {
|
|
|
71
71
|
return asAnonymous()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
log.
|
|
74
|
+
log.debug('[UsersMiddleware] Authenticating')
|
|
75
75
|
log.debug('[UsersMiddleware] authToken', { authToken })
|
|
76
76
|
|
|
77
77
|
const authStarted = performance.now()
|
|
@@ -98,7 +98,7 @@ export class UsersMiddleware {
|
|
|
98
98
|
return PoliciesQueries.GetPoliciesForUser(user.id)
|
|
99
99
|
.then(policies => {
|
|
100
100
|
finishAuth(() => {
|
|
101
|
-
log.
|
|
101
|
+
log.debug('[UsersMiddleware] Resolved user', {
|
|
102
102
|
elapsedMs: Math.round(performance.now() - authStarted),
|
|
103
103
|
})
|
|
104
104
|
req.userId = user.id
|