@ossy/platform 3.0.1 → 3.0.3
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 +13 -12
- package/src/accept-invitation.api.js +46 -0
- package/src/audit/list-task-runs.task.js +2 -2
- package/src/config.service.js +1 -48
- package/src/mcp/create-ossy-mcp-server.js +1 -1
- package/src/mcp/mount-platform-mcp.js +1 -1
- package/src/resources/schema.registry.js +1 -26
- package/src/resources/schema.validation.js +6 -90
- package/src/runtime.js +1 -1
- package/src/select-workspace.api.js +50 -0
- package/src/server.js +1 -1
- package/src/site-loader.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,16 +44,17 @@
|
|
|
44
44
|
"@aws-sdk/util-create-request": "^3.972.26",
|
|
45
45
|
"@aws-sdk/util-format-url": "^3.972.17",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
|
-
"@ossy/
|
|
48
|
-
"@ossy/event-store": "^3.0.
|
|
49
|
-
"@ossy/locale": "^3.0.
|
|
50
|
-
"@ossy/
|
|
51
|
-
"@ossy/
|
|
52
|
-
"@ossy/
|
|
53
|
-
"@ossy/
|
|
54
|
-
"@ossy/
|
|
55
|
-
"@ossy/
|
|
56
|
-
"@ossy/
|
|
47
|
+
"@ossy/config": "^3.0.3",
|
|
48
|
+
"@ossy/event-store": "^3.0.3",
|
|
49
|
+
"@ossy/locale": "^3.0.3",
|
|
50
|
+
"@ossy/manifest": "^3.0.3",
|
|
51
|
+
"@ossy/observability": "^3.0.3",
|
|
52
|
+
"@ossy/policies": "^3.0.3",
|
|
53
|
+
"@ossy/schema": "^3.0.3",
|
|
54
|
+
"@ossy/sdk": "^3.0.3",
|
|
55
|
+
"@ossy/tokens": "^3.0.3",
|
|
56
|
+
"@ossy/users": "^3.0.3",
|
|
57
|
+
"@ossy/workspaces": "^3.0.3",
|
|
57
58
|
"cookie-parser": "^1.4.7",
|
|
58
59
|
"dotenv": ">=16.0.0 <18.0.0",
|
|
59
60
|
"express": ">=5.0.0 <6.0.0",
|
|
@@ -73,5 +74,5 @@
|
|
|
73
74
|
"src",
|
|
74
75
|
"Dockerfile"
|
|
75
76
|
],
|
|
76
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "8d098a04b595b2d1f54788c5c7ed7f310e9cd7e1"
|
|
77
78
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionService,
|
|
3
|
+
mergeUserAppSettingsCookie,
|
|
4
|
+
setAuthCookie,
|
|
5
|
+
} from '@ossy/platform'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
id: 'users.accept-invitation',
|
|
9
|
+
path: '/api/v0/users/accept-invitation',
|
|
10
|
+
action: '@ossy/workspaces/actions/accept-invitation',
|
|
11
|
+
method: 'GET',
|
|
12
|
+
query: ['token', 'workspaceId', 'redirect'],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default async function handle (req, res) {
|
|
16
|
+
if (req.method !== 'GET') {
|
|
17
|
+
res.setHeader('Allow', 'GET')
|
|
18
|
+
res.status(405).json({ error: 'Method Not Allowed' })
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const { authToken, workspaceId } = await ActionService.invoke('@ossy/workspaces/actions/accept-invitation', {
|
|
24
|
+
payload: {
|
|
25
|
+
token: req.query.token,
|
|
26
|
+
workspaceId: req.query.workspaceId,
|
|
27
|
+
},
|
|
28
|
+
req,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
setAuthCookie(res, authToken)
|
|
32
|
+
if (workspaceId) {
|
|
33
|
+
mergeUserAppSettingsCookie(req, res, { workspaceId })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const redirect = req.query.redirect
|
|
37
|
+
if (redirect && typeof redirect === 'string') {
|
|
38
|
+
res.redirect(302, redirect)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
res.status(200).json('')
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(err.status || 401).json('')
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -12,6 +12,6 @@ export async function run ({ payload, req }) {
|
|
|
12
12
|
throw Object.assign(new Error('workspaceId required'), { status: 400 })
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const projection = await getProjection(TaskRunListProjection.ProjectionId
|
|
16
|
-
return projection
|
|
15
|
+
const projection = await getProjection(workspaceId, TaskRunListProjection.ProjectionId)
|
|
16
|
+
return projection ?? TaskRunListProjection.initialState(workspaceId)
|
|
17
17
|
}
|
package/src/config.service.js
CHANGED
|
@@ -1,48 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* API Configuration
|
|
3
|
-
* @class
|
|
4
|
-
*/
|
|
5
|
-
export class ConfigService {
|
|
6
|
-
|
|
7
|
-
static Port = process.env.BE_PORT || 3000
|
|
8
|
-
static Domain = process.env.DOMAIN || `localhost:${ConfigService.Port}`
|
|
9
|
-
static WebClientDomain = process.env.WEB_CLIENT_DOMAIN || `http://localhost:3000`
|
|
10
|
-
static MongoUrl = process.env.DB_URL || 'mongodb://mongodb:27017/'
|
|
11
|
-
static DbName = process.env.DB_NAME || 'test'
|
|
12
|
-
static TokenSecret = process.env.TOKEN_SECRET || 'testsecret2'
|
|
13
|
-
static TokenValidity = process.env.TOKEN_VALIDITY || 60 * 60 * 24 * 14
|
|
14
|
-
static LimitedAccessCode = process.env.LIMITED_ACCESS_CODE || 'test'
|
|
15
|
-
static BuildEnvironment = process.env.BUILD_ENVIRONMENT
|
|
16
|
-
static BotUserEmail = process.env.BOT_USER_EMAIL || 'ossybot@ossy.se'
|
|
17
|
-
static BotUserId = process.env.BOT_USER_ID || 'Mil5qAL7jDFCTyuKD_BKb'
|
|
18
|
-
/** Set on `req.userId` when there is no valid session; role checks can treat this as unauthenticated. */
|
|
19
|
-
static AnonymousUserId = process.env.ANONYMOUS_USER_ID || 'anonymous'
|
|
20
|
-
static MediaRepository = process.env.MEDIA_REPOSITORY
|
|
21
|
-
static MediaCdnDomainName = process.env.MEDIA_CDN_DOMAIN_NAME
|
|
22
|
-
static awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID
|
|
23
|
-
static awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY
|
|
24
|
-
static SesRegion = process.env.SES_REGION || 'eu-north-1'
|
|
25
|
-
static Debug = process.env.DEBUG
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Derives the web client base URL from the incoming request's Origin or Referer
|
|
29
|
-
* header so that email links point back to whichever domain the user came from
|
|
30
|
-
* (e.g. http://ossy.local, http://localhost:3002). Falls back to the
|
|
31
|
-
* WEB_CLIENT_DOMAIN env var / default when no header is present.
|
|
32
|
-
*/
|
|
33
|
-
static getWebClientBaseUrl(req) {
|
|
34
|
-
const origin = req?.headers?.origin
|
|
35
|
-
if (origin && origin !== 'null') return origin
|
|
36
|
-
|
|
37
|
-
const referer = req?.headers?.referer
|
|
38
|
-
if (referer) {
|
|
39
|
-
try {
|
|
40
|
-
const { origin: refOrigin } = new URL(referer)
|
|
41
|
-
if (refOrigin && refOrigin !== 'null') return refOrigin
|
|
42
|
-
} catch {}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return ConfigService.WebClientDomain
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
}
|
|
1
|
+
export { ConfigService } from '@ossy/config'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildCapabilities } from '@ossy/
|
|
1
|
+
import { buildCapabilities } from '@ossy/manifest/build-capabilities'
|
|
2
2
|
import { mountOssyMcp } from './mount-ossy-mcp.js'
|
|
3
3
|
import { uploadFileToolHandler, UPLOAD_FILE_TOOL } from './upload-file-tool.js'
|
|
4
4
|
import { ActionService } from '../actions/action.service.js'
|
|
@@ -1,26 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const log = createLogger('platform')
|
|
4
|
-
|
|
5
|
-
/** @type {object[]} */
|
|
6
|
-
const systemSchemas = []
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Register a single schema POJO (as inlined in `build/manifest.json`).
|
|
10
|
-
*
|
|
11
|
-
* @param {object} schema
|
|
12
|
-
*/
|
|
13
|
-
export function registerSchema (schema) {
|
|
14
|
-
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return
|
|
15
|
-
if (typeof schema.id !== 'string' || schema.id.trim() === '') return
|
|
16
|
-
if (systemSchemas.find(s => s.id === schema.id)) return
|
|
17
|
-
systemSchemas.push(schema)
|
|
18
|
-
log.info(`[SchemaRegistry] Registered system schema: ${schema.id}`)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @returns {object[]}
|
|
23
|
-
*/
|
|
24
|
-
export function getSystemSchemas () {
|
|
25
|
-
return systemSchemas
|
|
26
|
-
}
|
|
1
|
+
export { registerSchema, getSystemSchemas } from '@ossy/schema'
|
|
@@ -1,90 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'textarea',
|
|
8
|
-
'richtext',
|
|
9
|
-
'number',
|
|
10
|
-
'select',
|
|
11
|
-
'multiselect',
|
|
12
|
-
'file',
|
|
13
|
-
'image',
|
|
14
|
-
'boolean',
|
|
15
|
-
'date',
|
|
16
|
-
'timestamp',
|
|
17
|
-
'date-range',
|
|
18
|
-
'date-ranges',
|
|
19
|
-
'id',
|
|
20
|
-
'path',
|
|
21
|
-
])
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Normalize template field `type` to the canonical editor type.
|
|
25
|
-
* `image` is an alias for `file` with default `accept: 'image/*'` when accept is omitted.
|
|
26
|
-
*
|
|
27
|
-
* @param {string | undefined} type
|
|
28
|
-
* @returns {{ type: string, defaultAccept?: string }}
|
|
29
|
-
*/
|
|
30
|
-
export function normalizeFieldType (type) {
|
|
31
|
-
const raw = typeof type === 'string' ? type.trim() : ''
|
|
32
|
-
if (raw === 'image') return { type: 'file', defaultAccept: 'image/*' }
|
|
33
|
-
return { type: raw }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @param {{ type?: string, accept?: string, min?: number, max?: number } & Record<string, unknown>} field
|
|
38
|
-
*/
|
|
39
|
-
export function resolveFieldDef (field) {
|
|
40
|
-
const { type: canonical, defaultAccept } = normalizeFieldType(field?.type)
|
|
41
|
-
return {
|
|
42
|
-
...field,
|
|
43
|
-
type: canonical,
|
|
44
|
-
accept: field?.accept ?? defaultAccept,
|
|
45
|
-
max: field?.max ?? 1,
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Validates a batch import of workspace schemas.
|
|
51
|
-
*
|
|
52
|
-
* @param {unknown} templates
|
|
53
|
-
* @param {Set<string>} reservedIds - System schema ids that must not be redefined
|
|
54
|
-
* @returns {{ ok: true } | { ok: false, code: string, message: string }}
|
|
55
|
-
*/
|
|
56
|
-
export function validateSchemasForImport (templates, reservedIds) {
|
|
57
|
-
if (!Array.isArray(templates)) {
|
|
58
|
-
return { ok: false, code: 'INVALID_PAYLOAD', message: 'Body must be a JSON array of schemas' }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const engine = Schema.of({ schemas: templates })
|
|
62
|
-
const seenIds = new Set()
|
|
63
|
-
|
|
64
|
-
for (const template of templates) {
|
|
65
|
-
if (reservedIds?.has(template?.id)) {
|
|
66
|
-
return {
|
|
67
|
-
ok: false,
|
|
68
|
-
code: 'RESERVED_SCHEMA_ID',
|
|
69
|
-
message: `Template id "${template.id}" is reserved by a system template`,
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = engine.validate(template)
|
|
74
|
-
if (!result.ok) {
|
|
75
|
-
const first = result.errors[0]
|
|
76
|
-
return { ok: false, code: first.code, message: first.message }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (seenIds.has(template.id)) {
|
|
80
|
-
return {
|
|
81
|
-
ok: false,
|
|
82
|
-
code: 'DUPLICATE_SCHEMA_ID',
|
|
83
|
-
message: `Duplicate schema id "${template.id}" in import payload`,
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
seenIds.add(template.id)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { ok: true }
|
|
90
|
-
}
|
|
1
|
+
export {
|
|
2
|
+
validateSchemasForImport,
|
|
3
|
+
ALLOWED_FIELD_TYPES,
|
|
4
|
+
normalizeFieldType,
|
|
5
|
+
resolveFieldDef,
|
|
6
|
+
} from '@ossy/schema'
|
package/src/runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ import morgan from 'morgan'
|
|
|
6
6
|
import { Router as OssyRouter } from '@ossy/router'
|
|
7
7
|
import { loadManifest, resolveEntryUrl, loadLayoutsById, resolvePageLayoutRender } from './server.js'
|
|
8
8
|
import { resolveRequestLocale } from './locale.js'
|
|
9
|
-
import { buildManifestSummary } from '@ossy/
|
|
9
|
+
import { buildManifestSummary } from '@ossy/manifest/build-manifest-summary'
|
|
10
10
|
import { ProxyInternal } from './proxy-internal.js'
|
|
11
11
|
import { loadSite, invalidateSite } from './site-loader.js'
|
|
12
12
|
import { createLogger } from '@ossy/observability'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionService,
|
|
3
|
+
mergeUserAppSettingsCookie,
|
|
4
|
+
} from '@ossy/platform'
|
|
5
|
+
|
|
6
|
+
export const metadata = {
|
|
7
|
+
id: 'users.select-workspace',
|
|
8
|
+
path: '/api/v0/users/select-workspace',
|
|
9
|
+
method: 'GET',
|
|
10
|
+
query: ['workspaceId', 'redirect'],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async function handle (req, res) {
|
|
14
|
+
if (req.method !== 'GET') {
|
|
15
|
+
res.setHeader('Allow', 'GET')
|
|
16
|
+
res.status(405).json({ error: 'Method Not Allowed' })
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!req.userId || req.userId === 'anonymous') {
|
|
21
|
+
res.status(401).json({ error: 'Unauthorized' })
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const workspaceId = req.query.workspaceId
|
|
26
|
+
if (!workspaceId || typeof workspaceId !== 'string') {
|
|
27
|
+
res.status(400).json({ message: 'No workspaceId provided' })
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const workspaces = await ActionService.invoke('@ossy/workspaces/actions/list', { req })
|
|
33
|
+
if (!workspaces.some((w) => w.id === workspaceId)) {
|
|
34
|
+
res.status(403).json({ message: 'Forbidden' })
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
mergeUserAppSettingsCookie(req, res, { workspaceId })
|
|
39
|
+
|
|
40
|
+
const redirect = req.query.redirect
|
|
41
|
+
if (redirect && typeof redirect === 'string') {
|
|
42
|
+
res.redirect(302, redirect)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
res.status(200).json('')
|
|
47
|
+
} catch (err) {
|
|
48
|
+
res.status(err.status || 500).json({ message: err.message || 'Internal Server Error' })
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/server.js
CHANGED
|
@@ -10,7 +10,7 @@ import { SDK, resolveActionId } from '@ossy/sdk'
|
|
|
10
10
|
import { AggregateRebuild, ensureEventStoreIndexes, Mongo, resolveMongoUrl } from '@ossy/event-store'
|
|
11
11
|
import { TaskService } from './tasks/task-service.js'
|
|
12
12
|
import { ChangeStream } from './tasks/change-stream.js'
|
|
13
|
-
import { buildManifestSummary } from '@ossy/
|
|
13
|
+
import { buildManifestSummary } from '@ossy/manifest/build-manifest-summary'
|
|
14
14
|
import { registerSchema } from './resources/schema.registry.js'
|
|
15
15
|
import { initPlatformSchema } from './resources/schema.service.js'
|
|
16
16
|
import { IntegrationService } from './integration.service.js'
|
package/src/site-loader.js
CHANGED
|
@@ -42,7 +42,7 @@ async function resolveWorkspaceId (domain) {
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* BFS walk of CMS resources under `location`, collecting all file resources recursively.
|
|
45
|
-
* Directories have `type === 'directory'`; files have `content.src`.
|
|
45
|
+
* Directories have `type === 'directory'` or `@ossy/platform/schema/directory`; files have `content.src`.
|
|
46
46
|
*
|
|
47
47
|
* @param {ReturnType<typeof SDK.of>} sdk
|
|
48
48
|
* @param {string} location e.g. `/@ossy/apps/ossy.se`
|
|
@@ -62,7 +62,7 @@ async function collectFiles (sdk, location) {
|
|
|
62
62
|
|
|
63
63
|
for (const r of resources) {
|
|
64
64
|
const relPath = prefix ? `${prefix}/${r.name}` : r.name
|
|
65
|
-
if (r.type === 'directory') {
|
|
65
|
+
if (r.type === '@ossy/platform/schema/directory' || r.type === 'directory') {
|
|
66
66
|
queue.push({ location: `${loc}/${r.name}`, prefix: relPath })
|
|
67
67
|
} else if (r.content?.src) {
|
|
68
68
|
results.push({ resource: r, relPath })
|