@ossy/resources 1.12.2 → 1.12.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 +11 -3
- package/src/AudioResource.jsx +1 -1
- package/src/CreateDirectory.jsx +2 -10
- package/src/CreateDocument.jsx +2 -2
- package/src/Definition.js +2 -1
- package/src/DocumentEdit.jsx +10 -38
- package/src/DocumentView.jsx +3 -4
- package/src/GenericResourceCard.jsx +25 -7
- package/src/GenericResourceDetail.jsx +6 -60
- package/src/GenericResourceForm.jsx +66 -31
- package/src/ImageResource.jsx +1 -1
- package/src/PDFResource.jsx +1 -1
- package/src/PlatformFileField.jsx +239 -0
- package/src/ResourceContentPage.jsx +3 -3
- package/src/ResourceDescription.jsx +1 -1
- package/src/ResourceDetails.jsx +3 -4
- package/src/ResourceDialogMove.jsx +2 -2
- package/src/ResourceFactory.jsx +11 -64
- package/src/ResourceGenericView.jsx +1 -1
- package/src/ResourceList.jsx +89 -106
- package/src/ResourcePanel.jsx +40 -57
- package/src/ResourceTags.jsx +1 -1
- package/src/ResourcesPage.jsx +26 -35
- package/src/SchemaPresenter.jsx +215 -0
- package/src/Upload.jsx +6 -25
- package/src/UploadResources.jsx +2 -2
- package/src/VideoResource.jsx +1 -1
- package/src/access-filter.spec.js +1 -1
- package/src/create-page-view.action.js +1 -1
- package/src/create-page-view.task.js +10 -9
- package/src/create.action.js +1 -1
- package/src/create.task.js +28 -29
- package/src/delete.action.js +1 -1
- package/src/delete.task.js +10 -13
- package/src/en.translations.json +24 -24
- package/src/get-page-view-stats.action.js +1 -1
- package/src/get-page-view-stats.task.js +3 -4
- package/src/get-resource-alias.api.js +8 -0
- package/src/get-resource-info-alias.api.js +8 -0
- package/src/get-resource-info.api.js +36 -0
- package/src/get-resource-variant.api.js +8 -0
- package/src/get-resource.api.js +51 -0
- package/src/get.action.js +1 -1
- package/src/get.task.js +6 -6
- package/src/index.js +6 -0
- package/src/list.action.js +1 -1
- package/src/list.task.js +1 -1
- package/src/platform-file-field.component.jsx +5 -0
- package/src/platform-image-field.component.jsx +5 -0
- package/src/resource-create.page.jsx +4 -4
- package/src/resource-read.helpers.js +151 -0
- package/src/resource-stream.helpers.js +20 -0
- package/src/resource-stream.js +108 -0
- package/src/resource.helpers.js +9 -9
- package/src/resources.attach-media-urls.js +9 -3
- package/src/resources.events.js +42 -81
- package/src/{resources.spec.js → resources.integration.spec.js} +19 -19
- package/src/resources.queries.js +5 -2
- package/src/search.action.js +1 -1
- package/src/search.task.js +1 -1
- package/src/server.js +2 -1
- package/src/sv.translations.json +24 -24
- package/src/update-access.action.js +1 -1
- package/src/update-access.task.js +9 -8
- package/src/update-content.action.js +1 -1
- package/src/update-content.task.js +25 -27
- package/src/update-location.action.js +1 -1
- package/src/update-location.task.js +9 -8
- package/src/update-name.action.js +1 -1
- package/src/update-name.task.js +9 -8
- package/src/upload-named-version.action.js +1 -1
- package/src/upload-named-version.task.js +16 -22
- package/src/useActivePath.jsx +3 -8
- package/src/useDocumentValidator.js +16 -0
- package/src/useSchemaEngine.js +9 -0
- package/src/useSchemas.js +29 -0
- package/src/useSelectedResourceId.jsx +36 -0
- package/src/ResourceContentPage.stories.jsx +0 -19
- package/src/resource.aggregate.js +0 -86
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
2
|
+
import { EventStore, Mongo, withMongoReconnect } from '@ossy/event-store'
|
|
3
|
+
import { createDefaultReducer } from '@ossy/fold'
|
|
4
|
+
import { createLogger } from '@ossy/observability'
|
|
5
|
+
|
|
6
|
+
const log = createLogger('@ossy/resources')
|
|
7
|
+
|
|
8
|
+
const reduce = createDefaultReducer()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ADR 0008 resource stream — keyed by `resourceId`, events use `{ type, event, version, payload }`.
|
|
12
|
+
*/
|
|
13
|
+
export class ResourceStream {
|
|
14
|
+
|
|
15
|
+
static get Collection () {
|
|
16
|
+
return Mongo.db.collection('aggregates')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string | object} identifier resourceId or a Created event partial
|
|
21
|
+
*/
|
|
22
|
+
static Of (identifier) {
|
|
23
|
+
if (typeof identifier === 'object' && identifier !== null) {
|
|
24
|
+
const resourceId = identifier.resourceId ?? nanoid()
|
|
25
|
+
return Promise.resolve(new ResourceStream(resourceId, [], null))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ResourceStream.Find(identifier)
|
|
29
|
+
.then(snapshot =>
|
|
30
|
+
EventStore.GetResourceStream({ resourceId: identifier, fromVersion: snapshot?.version ?? 0 })
|
|
31
|
+
.then(events => new ResourceStream(identifier, events, snapshot)),
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static Find (resourceId) {
|
|
36
|
+
return withMongoReconnect(() => ResourceStream.Collection.findOne({ id: resourceId }))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} event partial resource event (without id, created, version)
|
|
41
|
+
*/
|
|
42
|
+
static Add (event) {
|
|
43
|
+
return stream => {
|
|
44
|
+
const version = stream.version + 1
|
|
45
|
+
const saved = {
|
|
46
|
+
...event,
|
|
47
|
+
type: event.type ?? stream.events[0]?.type ?? stream.state?.type,
|
|
48
|
+
id: nanoid(),
|
|
49
|
+
created: Date.now(),
|
|
50
|
+
resourceId: stream.id,
|
|
51
|
+
version,
|
|
52
|
+
}
|
|
53
|
+
return EventStore.AppendResourceEvent(saved)
|
|
54
|
+
.then(() => {
|
|
55
|
+
stream.version = version
|
|
56
|
+
stream.events = [...stream.events, saved]
|
|
57
|
+
return stream
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static Save () {
|
|
63
|
+
return stream => {
|
|
64
|
+
if (!stream.events?.length) return Promise.resolve()
|
|
65
|
+
|
|
66
|
+
const latestVersion = stream.events[stream.events.length - 1]?.version ?? stream.version
|
|
67
|
+
const state = reduce(stream.events, stream.state)
|
|
68
|
+
const schemaId = state.type ?? stream.events[0]?.type
|
|
69
|
+
|
|
70
|
+
return withMongoReconnect(() =>
|
|
71
|
+
ResourceStream.Collection.updateOne(
|
|
72
|
+
{ id: stream.id },
|
|
73
|
+
{
|
|
74
|
+
$set: {
|
|
75
|
+
id: stream.id,
|
|
76
|
+
version: latestVersion,
|
|
77
|
+
type: schemaId,
|
|
78
|
+
state,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{ upsert: true },
|
|
82
|
+
),
|
|
83
|
+
).catch(err => {
|
|
84
|
+
log.error(`[ResourceStream] save failed for ${stream.id}`, undefined, err)
|
|
85
|
+
throw err
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static View () {
|
|
91
|
+
return stream => reduce(stream.events, stream.state)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {string} id resourceId
|
|
96
|
+
* @param {object[]} events
|
|
97
|
+
* @param {object} [snapshot]
|
|
98
|
+
*/
|
|
99
|
+
constructor (id, events, snapshot) {
|
|
100
|
+
this.id = id
|
|
101
|
+
this.version = snapshot?.version ?? 0
|
|
102
|
+
this.state = snapshot?.state
|
|
103
|
+
this.events = events ?? []
|
|
104
|
+
this.View = reduce
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { ResourceStream as Aggregate }
|
package/src/resource.helpers.js
CHANGED
|
@@ -48,36 +48,36 @@ export async function uploadFile(sdk, uploadLocation, file) {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export async function removeResource(sdk, id, location) {
|
|
51
|
-
await sdk.invoke(DeleteResource, { id })
|
|
51
|
+
await sdk.invoke(DeleteResource, { resourceId: id })
|
|
52
52
|
if (location) invalidateLocation(sdk, location)
|
|
53
|
-
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
53
|
+
sdk.invalidate(cacheKey({ id: '@ossy/resources/actions/get' }, { resourceId: id }))
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export async function updateResourceContent(sdk, id, content) {
|
|
57
|
-
const resource = await sdk.invoke(UpdateResourceContent, { id, content })
|
|
57
|
+
const resource = await sdk.invoke(UpdateResourceContent, { resourceId: id, content })
|
|
58
58
|
invalidateLocation(sdk, resource.location)
|
|
59
|
-
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
59
|
+
sdk.invalidate(cacheKey({ id: '@ossy/resources/actions/get' }, { resourceId: id }))
|
|
60
60
|
return resource
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
export async function moveResource(sdk, id, target, sourceLocation) {
|
|
64
|
-
const resource = await sdk.invoke(UpdateResourceLocation, { id, target })
|
|
64
|
+
const resource = await sdk.invoke(UpdateResourceLocation, { resourceId: id, target })
|
|
65
65
|
invalidateLocation(sdk, resource.location)
|
|
66
66
|
if (sourceLocation) invalidateLocation(sdk, sourceLocation)
|
|
67
|
-
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
67
|
+
sdk.invalidate(cacheKey({ id: '@ossy/resources/actions/get' }, { resourceId: id }))
|
|
68
68
|
return resource
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export async function renameResource(sdk, id, name) {
|
|
72
|
-
const resource = await sdk.invoke(UpdateResourceName, { id, name })
|
|
72
|
+
const resource = await sdk.invoke(UpdateResourceName, { resourceId: id, name })
|
|
73
73
|
invalidateLocation(sdk, resource.location)
|
|
74
|
-
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
74
|
+
sdk.invalidate(cacheKey({ id: '@ossy/resources/actions/get' }, { resourceId: id }))
|
|
75
75
|
return resource
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export async function updateResourceAccess(sdk, params) {
|
|
79
79
|
const resource = await sdk.invoke(UpdateResourceAccess, params)
|
|
80
80
|
invalidateLocation(sdk, resource.location)
|
|
81
|
-
sdk.invalidate(cacheKey({ id: 'resources/get' }, {
|
|
81
|
+
sdk.invalidate(cacheKey({ id: '@ossy/resources/actions/get' }, { resourceId: params.resourceId ?? params.id }))
|
|
82
82
|
return resource
|
|
83
83
|
}
|
|
@@ -4,8 +4,8 @@ import { Workspace } from '@ossy/workspaces/server'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Adds `content.src` (and `content.sizes[*]` URLs) for file resources, respecting
|
|
7
|
-
* `access` and workspace membership.
|
|
8
|
-
*
|
|
7
|
+
* `access` and workspace membership. All backends expose the same read URL shape:
|
|
8
|
+
* `/r/{resourceId}` (derivatives: `/r/{resourceId}/{variant}`).
|
|
9
9
|
*
|
|
10
10
|
* @param {object} resource - Resource view
|
|
11
11
|
* @param {{ userId?: string, workspaceId?: string }} context
|
|
@@ -22,7 +22,13 @@ export async function attachResourceMediaUrls(resource, { userId, workspaceId }
|
|
|
22
22
|
|
|
23
23
|
const access = resource.access || 'restricted'
|
|
24
24
|
const isMember = Boolean(userId && workspace?.users?.includes(userId))
|
|
25
|
-
const
|
|
25
|
+
const inWorkspaceContext = Boolean(
|
|
26
|
+
userId && workspaceId && resource.belongsTo === workspaceId,
|
|
27
|
+
)
|
|
28
|
+
const allowMedia =
|
|
29
|
+
access === 'public' ||
|
|
30
|
+
(access === 'workspace' && inWorkspaceContext) ||
|
|
31
|
+
(access === 'restricted' && isMember)
|
|
26
32
|
|
|
27
33
|
const nextContent = { ...resource.content }
|
|
28
34
|
|
package/src/resources.events.js
CHANGED
|
@@ -1,98 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
2
|
+
* ADR 0008 resource lifecycle events.
|
|
3
|
+
*/
|
|
5
4
|
export class ResourcesEvents {
|
|
6
5
|
|
|
7
|
-
static Created({
|
|
8
|
-
|
|
6
|
+
static Created ({
|
|
7
|
+
resourceId,
|
|
8
|
+
schemaId,
|
|
9
9
|
createdBy,
|
|
10
10
|
belongsTo,
|
|
11
11
|
location,
|
|
12
|
-
type,
|
|
13
12
|
name,
|
|
14
|
-
|
|
13
|
+
access,
|
|
14
|
+
content,
|
|
15
|
+
...rest
|
|
15
16
|
}) {
|
|
16
|
-
return
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
createdBy
|
|
17
|
+
return {
|
|
18
|
+
type: schemaId,
|
|
19
|
+
resourceId,
|
|
20
|
+
event: 'Created',
|
|
21
|
+
createdBy,
|
|
21
22
|
payload: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
belongsTo,
|
|
24
|
+
location,
|
|
25
|
+
name,
|
|
26
|
+
access,
|
|
27
|
+
content: content ?? {},
|
|
28
|
+
...rest,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
static
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
createdBy
|
|
35
|
-
payload:
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
static NameUpdated({ createdBy, name }) {
|
|
42
|
-
return ({
|
|
43
|
-
type: 'NameUpdated',
|
|
44
|
-
createdBy: createdBy,
|
|
45
|
-
payload: {
|
|
46
|
-
name: name
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
static AccessUpdated({ createdBy, access }) {
|
|
52
|
-
return ({
|
|
53
|
-
type: 'AccessUpdated',
|
|
54
|
-
createdBy: createdBy,
|
|
55
|
-
payload: {
|
|
56
|
-
access: access
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
static LocationUpdated({ createdBy, location }) {
|
|
62
|
-
return ({
|
|
63
|
-
type: 'LocationUpdated',
|
|
64
|
-
createdBy: createdBy,
|
|
65
|
-
payload: {
|
|
66
|
-
location: location
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static Deleted({ createdBy }) {
|
|
72
|
-
return ({
|
|
73
|
-
type: 'Deleted',
|
|
74
|
-
createdBy: createdBy
|
|
75
|
-
})
|
|
33
|
+
static Patched ({ createdBy, ...partial }) {
|
|
34
|
+
return {
|
|
35
|
+
event: 'Patched',
|
|
36
|
+
createdBy,
|
|
37
|
+
payload: partial,
|
|
38
|
+
}
|
|
76
39
|
}
|
|
77
40
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
ContentLength,
|
|
83
|
-
ContentType,
|
|
84
|
-
Key
|
|
85
|
-
}) {
|
|
86
|
-
return ({
|
|
87
|
-
type: 'NamedVersionUploaded',
|
|
88
|
-
createdBy: createdBy,
|
|
41
|
+
static Updated ({ createdBy, content, name }) {
|
|
42
|
+
return {
|
|
43
|
+
event: 'Updated',
|
|
44
|
+
createdBy,
|
|
89
45
|
payload: {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
})
|
|
46
|
+
...(content !== undefined ? { content } : {}),
|
|
47
|
+
...(name !== undefined ? { name } : {}),
|
|
48
|
+
},
|
|
49
|
+
}
|
|
96
50
|
}
|
|
97
51
|
|
|
52
|
+
static Deleted ({ createdBy }) {
|
|
53
|
+
return {
|
|
54
|
+
event: 'Deleted',
|
|
55
|
+
createdBy,
|
|
56
|
+
payload: {},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
98
59
|
}
|
|
@@ -9,58 +9,58 @@ function workspaceHeaders(userToken, workspaceId) {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
describe('[resources/create]', () => {
|
|
12
|
+
describe('[resources/actions/create]', () => {
|
|
13
13
|
|
|
14
14
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
15
|
-
actionId: 'resources/create',
|
|
15
|
+
actionId: '@ossy/resources/actions/create',
|
|
16
16
|
payload: { type: 'directory', location: '/', name: 'test' },
|
|
17
17
|
headers: { workspaceId: 'ws-test' },
|
|
18
18
|
})
|
|
19
19
|
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
-
describe('[resources/list]', () => {
|
|
22
|
+
describe('[resources/actions/list]', () => {
|
|
23
23
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
24
|
-
actionId: 'resources/list',
|
|
24
|
+
actionId: '@ossy/resources/actions/list',
|
|
25
25
|
headers: { workspaceId: 'ws-test' },
|
|
26
26
|
})
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
describe('[resources/get]', () => {
|
|
29
|
+
describe('[resources/actions/get]', () => {
|
|
30
30
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
31
|
-
actionId: 'resources/get',
|
|
31
|
+
actionId: '@ossy/resources/actions/get',
|
|
32
32
|
payload: { resourceId: 'r1' },
|
|
33
33
|
headers: { workspaceId: 'ws-test' },
|
|
34
34
|
})
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
describe('[resources/update-name]', () => {
|
|
37
|
+
describe('[resources/actions/update-name]', () => {
|
|
38
38
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
39
|
-
actionId: 'resources/update-name',
|
|
39
|
+
actionId: '@ossy/resources/actions/update-name',
|
|
40
40
|
payload: { resourceId: 'r1', name: 'x' },
|
|
41
41
|
headers: { workspaceId: 'ws-test' },
|
|
42
42
|
})
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
describe('[resources/update-location]', () => {
|
|
45
|
+
describe('[resources/actions/update-location]', () => {
|
|
46
46
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
47
|
-
actionId: 'resources/update-location',
|
|
47
|
+
actionId: '@ossy/resources/actions/update-location',
|
|
48
48
|
payload: { resourceId: 'r1', target: '/other/' },
|
|
49
49
|
headers: { workspaceId: 'ws-test' },
|
|
50
50
|
})
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
describe('[resources/update-content]', () => {
|
|
53
|
+
describe('[resources/actions/update-content]', () => {
|
|
54
54
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
55
|
-
actionId: 'resources/update-content',
|
|
55
|
+
actionId: '@ossy/resources/actions/update-content',
|
|
56
56
|
payload: { resourceId: 'r1', content: {} },
|
|
57
57
|
headers: { workspaceId: 'ws-test' },
|
|
58
58
|
})
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
describe('[resources/delete]', () => {
|
|
61
|
+
describe('[resources/actions/delete]', () => {
|
|
62
62
|
TestUtil.AssertWorkspaceActionAuthenticationNeeded({
|
|
63
|
-
actionId: 'resources/delete',
|
|
63
|
+
actionId: '@ossy/resources/actions/delete',
|
|
64
64
|
payload: { resourceId: 'r1' },
|
|
65
65
|
headers: { workspaceId: 'ws-test' },
|
|
66
66
|
})
|
|
@@ -70,7 +70,7 @@ describe('E2E: template-backed resource (workspaceId header)', () => {
|
|
|
70
70
|
it('create → update content → delete', async () => {
|
|
71
71
|
const user = await TestUtil.GetAuthenticatedTestUser()
|
|
72
72
|
const workspace = await TestUtil.InvokeAction({
|
|
73
|
-
actionId: 'workspaces/create',
|
|
73
|
+
actionId: '@ossy/workspaces/actions/create',
|
|
74
74
|
headers: { 'Content-Type': 'application/json', Authorization: user.token },
|
|
75
75
|
payload: { name: casual.word },
|
|
76
76
|
}).then(r => r.json())
|
|
@@ -88,7 +88,7 @@ describe('E2E: template-backed resource (workspaceId header)', () => {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
const createRes = await TestUtil.InvokeAction({
|
|
91
|
-
actionId: 'resources/create',
|
|
91
|
+
actionId: '@ossy/resources/actions/create',
|
|
92
92
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
93
93
|
payload: createBody,
|
|
94
94
|
})
|
|
@@ -103,7 +103,7 @@ describe('E2E: template-backed resource (workspaceId header)', () => {
|
|
|
103
103
|
})
|
|
104
104
|
|
|
105
105
|
const updateRes = await TestUtil.InvokeAction({
|
|
106
|
-
actionId: 'resources/update-content',
|
|
106
|
+
actionId: '@ossy/resources/actions/update-content',
|
|
107
107
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
108
108
|
payload: {
|
|
109
109
|
resourceId: created.id,
|
|
@@ -124,7 +124,7 @@ describe('E2E: template-backed resource (workspaceId header)', () => {
|
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
const deleteRes = await TestUtil.InvokeAction({
|
|
127
|
-
actionId: 'resources/delete',
|
|
127
|
+
actionId: '@ossy/resources/actions/delete',
|
|
128
128
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
129
129
|
payload: { resourceId: created.id },
|
|
130
130
|
})
|
|
@@ -134,7 +134,7 @@ describe('E2E: template-backed resource (workspaceId header)', () => {
|
|
|
134
134
|
expect(deleteBody).toEqual({ ok: true })
|
|
135
135
|
|
|
136
136
|
const listRes = await TestUtil.InvokeAction({
|
|
137
|
-
actionId: 'resources/list',
|
|
137
|
+
actionId: '@ossy/resources/actions/list',
|
|
138
138
|
headers: workspaceHeaders(user.token, workspace.id),
|
|
139
139
|
payload: { query: { location: '/' } },
|
|
140
140
|
})
|
package/src/resources.queries.js
CHANGED
|
@@ -36,7 +36,7 @@ export function accessFilter(user) {
|
|
|
36
36
|
if (!user?.anonymous) {
|
|
37
37
|
if (user?.workspaces?.length) {
|
|
38
38
|
conditions.push({
|
|
39
|
-
'state.access': { $in: ['public', 'workspace'] },
|
|
39
|
+
'state.access': { $in: ['public', 'workspace', 'restricted'] },
|
|
40
40
|
'state.belongsTo': { $in: user.workspaces },
|
|
41
41
|
})
|
|
42
42
|
}
|
|
@@ -73,7 +73,10 @@ export class ResourcesQueries {
|
|
|
73
73
|
const workspaceScope = query['state.belongsTo'] ?? '(no workspace scope)'
|
|
74
74
|
log.info(`[ResourcesQueries][GetResources] Fetching resources for ${workspaceScope}`)
|
|
75
75
|
|
|
76
|
-
const baseQuery = {
|
|
76
|
+
const baseQuery = {
|
|
77
|
+
...query,
|
|
78
|
+
type: { $regex: /^@[^/]+\/[^/]+\/schema\// },
|
|
79
|
+
}
|
|
77
80
|
const mongoQuery = user ? { $and: [baseQuery, accessFilter(user)] } : baseQuery
|
|
78
81
|
|
|
79
82
|
return Aggregate.Collection.find(mongoQuery, { state: true }).toArray()
|
package/src/search.action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/search', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/search', access: 'workspace' }
|
package/src/search.task.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ResourcesQueries } from './resources.queries.js'
|
|
2
2
|
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
3
3
|
|
|
4
|
-
export const metadata = { id: 'resources/search' }
|
|
4
|
+
export const metadata = { id: '@ossy/resources/tasks/search' }
|
|
5
5
|
|
|
6
6
|
export async function run({ payload, req }) {
|
|
7
7
|
const query = { ...(payload ?? {}) }
|
package/src/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './resource.
|
|
1
|
+
export * from './resource-stream.js'
|
|
2
|
+
export * from './resource-stream.helpers.js'
|
|
2
3
|
export * from './resources.events.js'
|
|
3
4
|
export * from './resources.queries.js'
|
|
4
5
|
export { attachResourceMediaUrls } from './resources.attach-media-urls.js'
|
package/src/sv.translations.json
CHANGED
|
@@ -7,30 +7,30 @@
|
|
|
7
7
|
"resources.form.selectType": "Välj en resurstyp för att fortsätta.",
|
|
8
8
|
"resources.form.createFailed": "Skapandet misslyckades",
|
|
9
9
|
"resources.form.createResource": "Skapa resurs",
|
|
10
|
-
"resources/create.label": "Skapa resurs",
|
|
11
|
-
"resources/create.description": "Skapa en ny resurs i workspace",
|
|
12
|
-
"resources/list.label": "Lista resurser",
|
|
13
|
-
"resources/list.description": "Lista resurser på en plats i workspace",
|
|
14
|
-
"resources/get.label": "Hämta resurs",
|
|
15
|
-
"resources/get.description": "Hämta en enskild resurs via id",
|
|
16
|
-
"resources/search.label": "Sök resurser",
|
|
17
|
-
"resources/search.description": "Sök resurser i en workspace",
|
|
18
|
-
"resources/delete.label": "Ta bort resurs",
|
|
19
|
-
"resources/delete.description": "Ta bort en resurs från workspace",
|
|
20
|
-
"resources/update-name.label": "Uppdatera resursnamn",
|
|
21
|
-
"resources/update-name.description": "Byt namn på en resurs i workspace",
|
|
22
|
-
"resources/update-content.label": "Uppdatera resursinnehåll",
|
|
23
|
-
"resources/update-content.description": "Uppdatera innehållet i en resurs",
|
|
24
|
-
"resources/update-location.label": "Uppdatera resursplats",
|
|
25
|
-
"resources/update-location.description": "Flytta en resurs till en ny plats",
|
|
26
|
-
"resources/update-access.label": "Uppdatera resursåtkomst",
|
|
27
|
-
"resources/update-access.description": "Ändra åtkomstinställningar för en resurs",
|
|
28
|
-
"resources/upload-named-version.label": "Ladda upp namngiven version",
|
|
29
|
-
"resources/upload-named-version.description": "Ladda upp en namngiven version av en resursfil",
|
|
30
|
-
"resources/create-page-view.label": "Skapa sidvisning",
|
|
31
|
-
"resources/create-page-view.description": "Registrera en sidvisningshändelse för analys",
|
|
32
|
-
"resources/get-page-view-stats.label": "Hämta sidvisningsstatistik",
|
|
33
|
-
"resources/get-page-view-stats.description": "Hämta aggregerad statistik för sidvisningar",
|
|
10
|
+
"@ossy/resources/actions/create.label": "Skapa resurs",
|
|
11
|
+
"@ossy/resources/actions/create.description": "Skapa en ny resurs i workspace",
|
|
12
|
+
"@ossy/resources/actions/list.label": "Lista resurser",
|
|
13
|
+
"@ossy/resources/actions/list.description": "Lista resurser på en plats i workspace",
|
|
14
|
+
"@ossy/resources/actions/get.label": "Hämta resurs",
|
|
15
|
+
"@ossy/resources/actions/get.description": "Hämta en enskild resurs via id",
|
|
16
|
+
"@ossy/resources/actions/search.label": "Sök resurser",
|
|
17
|
+
"@ossy/resources/actions/search.description": "Sök resurser i en workspace",
|
|
18
|
+
"@ossy/resources/actions/delete.label": "Ta bort resurs",
|
|
19
|
+
"@ossy/resources/actions/delete.description": "Ta bort en resurs från workspace",
|
|
20
|
+
"@ossy/resources/actions/update-name.label": "Uppdatera resursnamn",
|
|
21
|
+
"@ossy/resources/actions/update-name.description": "Byt namn på en resurs i workspace",
|
|
22
|
+
"@ossy/resources/actions/update-content.label": "Uppdatera resursinnehåll",
|
|
23
|
+
"@ossy/resources/actions/update-content.description": "Uppdatera innehållet i en resurs",
|
|
24
|
+
"@ossy/resources/actions/update-location.label": "Uppdatera resursplats",
|
|
25
|
+
"@ossy/resources/actions/update-location.description": "Flytta en resurs till en ny plats",
|
|
26
|
+
"@ossy/resources/actions/update-access.label": "Uppdatera resursåtkomst",
|
|
27
|
+
"@ossy/resources/actions/update-access.description": "Ändra åtkomstinställningar för en resurs",
|
|
28
|
+
"@ossy/resources/actions/upload-named-version.label": "Ladda upp namngiven version",
|
|
29
|
+
"@ossy/resources/actions/upload-named-version.description": "Ladda upp en namngiven version av en resursfil",
|
|
30
|
+
"@ossy/resources/actions/create-page-view.label": "Skapa sidvisning",
|
|
31
|
+
"@ossy/resources/actions/create-page-view.description": "Registrera en sidvisningshändelse för analys",
|
|
32
|
+
"@ossy/resources/actions/get-page-view-stats.label": "Hämta sidvisningsstatistik",
|
|
33
|
+
"@ossy/resources/actions/get-page-view-stats.description": "Hämta aggregerad statistik för sidvisningar",
|
|
34
34
|
"resources/create/generic.documentTitle": "Skapa resurs",
|
|
35
35
|
"create-directory.documentTitle": "Skapa mapp",
|
|
36
36
|
"create-document.documentTitle": "Skapa dokument",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/update-access', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/update-access', access: 'workspace' }
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { Aggregate } from '@ossy/event-store'
|
|
2
|
-
import { Resource } from './resource.aggregate.js'
|
|
3
1
|
import { ResourcesEvents } from './resources.events.js'
|
|
2
|
+
import { mutateResource } from './resource-stream.helpers.js'
|
|
3
|
+
import { resolveResourceId } from './resource-read.helpers.js'
|
|
4
4
|
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
5
5
|
|
|
6
|
-
export const metadata = { id: 'resources/update-access' }
|
|
6
|
+
export const metadata = { id: '@ossy/resources/tasks/update-access' }
|
|
7
7
|
|
|
8
|
-
export async function run({ payload, req }) {
|
|
8
|
+
export async function run ({ payload, req }) {
|
|
9
9
|
const createdBy = payload?.userId ?? req?.userId
|
|
10
|
-
const resourceId = payload
|
|
10
|
+
const resourceId = resolveResourceId({ payload, req })
|
|
11
11
|
const resourceAccess = payload?.access
|
|
12
12
|
|
|
13
13
|
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
@@ -15,8 +15,9 @@ export async function run({ payload, req }) {
|
|
|
15
15
|
throw Object.assign(new Error(`Invalid access value: ${resourceAccess}`), { status: 400 })
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const resource = await
|
|
19
|
-
|
|
20
|
-
.
|
|
18
|
+
const resource = await mutateResource(
|
|
19
|
+
resourceId,
|
|
20
|
+
ResourcesEvents.Patched({ createdBy, access: resourceAccess }),
|
|
21
|
+
)
|
|
21
22
|
return withResourceMedia(resource, mediaContextFromRun({ payload, req }))
|
|
22
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/update-content', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/update-content', access: 'workspace' }
|
|
@@ -1,41 +1,39 @@
|
|
|
1
1
|
import { Aggregate } from '@ossy/event-store'
|
|
2
|
-
import { Resource } from './resource.aggregate.js'
|
|
3
|
-
import { ResourcesEvents } from './resources.events.js'
|
|
4
2
|
import { Workspace } from '@ossy/workspaces/server'
|
|
5
|
-
import {
|
|
3
|
+
import { schemaForWorkspace } from '@ossy/platform'
|
|
4
|
+
import { ResourcesEvents } from './resources.events.js'
|
|
5
|
+
import { mutateResource, viewResource } from './resource-stream.helpers.js'
|
|
6
6
|
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
7
|
+
import { resolveResourceId } from './resource-read.helpers.js'
|
|
7
8
|
|
|
8
|
-
export const metadata = { id: 'resources/update-content' }
|
|
9
|
+
export const metadata = { id: '@ossy/resources/tasks/update-content' }
|
|
9
10
|
|
|
10
|
-
export async function run({ payload, req }) {
|
|
11
|
+
export async function run ({ payload, req }) {
|
|
11
12
|
const createdBy = payload?.userId ?? req?.userId
|
|
12
|
-
const resourceId = payload
|
|
13
|
+
const resourceId = resolveResourceId({ payload, req })
|
|
13
14
|
const content = payload?.content
|
|
14
15
|
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
15
16
|
|
|
16
17
|
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
17
18
|
|
|
18
19
|
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
20
|
+
const current = await viewResource(resourceId)
|
|
21
|
+
|
|
22
|
+
const engine = schemaForWorkspace(workspace.schemas)
|
|
23
|
+
const schema = engine.has(current.type) ? engine.resolve(current.type) : null
|
|
24
|
+
let finalContent = content
|
|
25
|
+
if (schema) {
|
|
26
|
+
const result = engine.validate(schema, content)
|
|
27
|
+
if (!result.ok) {
|
|
28
|
+
const first = result.errors[0]
|
|
29
|
+
throw Object.assign(new Error(first.message), { status: 400, type: first.code })
|
|
30
|
+
}
|
|
31
|
+
finalContent = result.data
|
|
32
|
+
}
|
|
19
33
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
...systemTemplates,
|
|
26
|
-
...(workspace.resourceTemplates || []).filter(t => !systemTemplates.find(s => s.id === t.id)),
|
|
27
|
-
]
|
|
28
|
-
const template = allTemplates.find(t => t.id === resource.type)
|
|
29
|
-
let finalContent = content
|
|
30
|
-
if (template) {
|
|
31
|
-
const normalized = normalizeAndValidateDocumentContent(content, template)
|
|
32
|
-
if (!normalized.ok) {
|
|
33
|
-
throw Object.assign(new Error(normalized.message), { status: 400, type: normalized.code })
|
|
34
|
-
}
|
|
35
|
-
finalContent = normalized.content
|
|
36
|
-
}
|
|
37
|
-
return Aggregate.Add(ResourcesEvents.ContentUpdated({ createdBy, content: finalContent }))(aggregate)
|
|
38
|
-
})
|
|
39
|
-
.then(Aggregate.View())
|
|
40
|
-
.then(resource => withResourceMedia(resource, mediaContextFromRun({ payload, req })))
|
|
34
|
+
const updated = await mutateResource(
|
|
35
|
+
resourceId,
|
|
36
|
+
ResourcesEvents.Updated({ createdBy, content: finalContent }),
|
|
37
|
+
)
|
|
38
|
+
return withResourceMedia(updated, mediaContextFromRun({ payload, req }))
|
|
41
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/update-location', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/update-location', access: 'workspace' }
|