@ossy/resources 1.12.2 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/package.json +16 -4
  2. package/src/AudioResource.jsx +2 -2
  3. package/src/CreateDirectory.jsx +2 -10
  4. package/src/CreateDirectory.page.jsx +1 -1
  5. package/src/CreateDocument.jsx +2 -2
  6. package/src/CreateDocument.page.jsx +1 -1
  7. package/src/Definition.js +3 -2
  8. package/src/DocumentEdit.jsx +10 -38
  9. package/src/DocumentView.jsx +11 -22
  10. package/src/GenericResourceCard.jsx +25 -7
  11. package/src/GenericResourceDetail.jsx +6 -60
  12. package/src/GenericResourceForm.jsx +171 -37
  13. package/src/ImageResource.jsx +1 -1
  14. package/src/PDFResource.jsx +1 -1
  15. package/src/PlatformFileField.jsx +239 -0
  16. package/src/ResourceContentPage.jsx +5 -5
  17. package/src/ResourceDescription.jsx +1 -1
  18. package/src/ResourceDetails.jsx +3 -4
  19. package/src/ResourceDialogMove.jsx +3 -3
  20. package/src/ResourceFactory.jsx +11 -64
  21. package/src/ResourceGenericView.jsx +1 -1
  22. package/src/ResourceList.jsx +89 -106
  23. package/src/ResourcePanel.jsx +40 -57
  24. package/src/ResourceTags.jsx +1 -1
  25. package/src/ResourcesPage.jsx +26 -35
  26. package/src/SchemaDetailView.jsx +97 -0
  27. package/src/SchemaFormDefault.jsx +86 -0
  28. package/src/SchemaPresenter.jsx +207 -0
  29. package/src/Upload.jsx +6 -25
  30. package/src/Upload.page.jsx +1 -1
  31. package/src/UploadResources.jsx +2 -2
  32. package/src/VideoResource.jsx +1 -1
  33. package/src/access-filter.spec.js +1 -1
  34. package/src/create-page-view.action.js +1 -1
  35. package/src/create-page-view.task.js +10 -9
  36. package/src/create.action.js +1 -1
  37. package/src/create.task.js +27 -29
  38. package/src/delete.action.js +1 -1
  39. package/src/delete.task.js +9 -13
  40. package/src/en.translations.json +25 -24
  41. package/src/get-page-view-stats.action.js +1 -1
  42. package/src/get-page-view-stats.task.js +3 -4
  43. package/src/get-resource-alias.api.js +8 -0
  44. package/src/get-resource-info-alias.api.js +8 -0
  45. package/src/get-resource-info.api.js +36 -0
  46. package/src/get-resource-variant.api.js +8 -0
  47. package/src/get-resource.api.js +51 -0
  48. package/src/get.action.js +1 -1
  49. package/src/get.task.js +6 -6
  50. package/src/index.js +10 -0
  51. package/src/list.action.js +1 -1
  52. package/src/list.task.js +1 -1
  53. package/src/platform-file-field.component.jsx +5 -0
  54. package/src/platform-image-field.component.jsx +5 -0
  55. package/src/resource-create.page.jsx +5 -5
  56. package/src/resource-detail.page.jsx +1 -1
  57. package/src/resource-read.helpers.js +151 -0
  58. package/src/resource-stream.helpers.js +20 -0
  59. package/src/resource-stream.js +108 -0
  60. package/src/resource.helpers.js +9 -9
  61. package/src/resources.attach-media-urls.js +9 -3
  62. package/src/resources.events.js +42 -81
  63. package/src/{resources.spec.js → resources.integration.spec.js} +19 -19
  64. package/src/resources.queries.js +5 -2
  65. package/src/schema-detail.component.jsx +24 -0
  66. package/src/schema-form-default.component.jsx +14 -0
  67. package/src/schemaFormSlots.js +6 -0
  68. package/src/schemaViewSlots.js +6 -0
  69. package/src/search.action.js +1 -1
  70. package/src/search.task.js +1 -1
  71. package/src/server.js +2 -1
  72. package/src/sv.translations.json +25 -24
  73. package/src/update-access.action.js +1 -1
  74. package/src/update-access.task.js +9 -8
  75. package/src/update-content.action.js +1 -1
  76. package/src/update-content.task.js +25 -27
  77. package/src/update-location.action.js +1 -1
  78. package/src/update-location.task.js +9 -8
  79. package/src/update-name.action.js +1 -1
  80. package/src/update-name.task.js +9 -8
  81. package/src/upload-named-version.action.js +1 -1
  82. package/src/upload-named-version.task.js +16 -22
  83. package/src/useActivePath.jsx +3 -8
  84. package/src/useDocumentValidator.js +16 -0
  85. package/src/useSchemaEngine.js +9 -0
  86. package/src/useSchemas.js +29 -0
  87. package/src/useSelectedResourceId.jsx +36 -0
  88. package/src/ResourceContentPage.stories.jsx +0 -19
  89. package/src/resource.aggregate.js +0 -86
@@ -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. Restricted resources get short-lived S3 presigned GETs
8
- * for members only; public resources get CDN URLs for any caller.
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 allowMedia = access === 'public' || (access === 'restricted' && isMember)
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
 
@@ -1,98 +1,59 @@
1
1
  /**
2
- * Database queries related to workspace events
3
- * @class
4
- */
2
+ * ADR 0008 resource lifecycle events.
3
+ */
5
4
  export class ResourcesEvents {
6
5
 
7
- static Created({
8
- aggregateId, //optional
6
+ static Created ({
7
+ resourceId,
8
+ schemaId,
9
9
  createdBy,
10
10
  belongsTo,
11
11
  location,
12
- type,
13
12
  name,
14
- ...specificTypeProps
13
+ access,
14
+ content,
15
+ ...rest
15
16
  }) {
16
- return ({
17
- aggregateId: aggregateId,
18
- type: 'Created',
19
- aggregateType: 'Resource',
20
- createdBy: createdBy,
17
+ return {
18
+ type: schemaId,
19
+ resourceId,
20
+ event: 'Created',
21
+ createdBy,
21
22
  payload: {
22
- ...specificTypeProps,
23
- belongsTo: belongsTo,
24
- location: location,
25
- type: type,
26
- name: name
27
- }
28
- })
23
+ belongsTo,
24
+ location,
25
+ name,
26
+ access,
27
+ content: content ?? {},
28
+ ...rest,
29
+ },
30
+ }
29
31
  }
30
32
 
31
- static ContentUpdated({ createdBy, content }) {
32
- return ({
33
- type: 'ContentUpdated',
34
- createdBy: createdBy,
35
- payload: {
36
- content: content
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
- // TODO: remove named version concept
79
- static NamedVersionUploaded({
80
- createdBy,
81
- namedVersion,
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
- namedVersion,
91
- Key,
92
- ContentLength,
93
- ContentType,
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
  })
@@ -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 = { ...query, type: 'Resource' }
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()
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ import { Text, View } from '@ossy/design-system'
3
+ import { SchemaDetailView } from './SchemaDetailView.jsx'
4
+
5
+ export const metadata = { id: '@ossy/resources/view/detail' }
6
+
7
+ /** Generic schema detail view — `@ossy/resources/view/detail`. */
8
+ export default function SchemaDetailViewComponent ({ resource, schemaId, fields, projection }) {
9
+ if (!resource) return null
10
+
11
+ return (
12
+ <View inset="m">
13
+ <SchemaDetailView
14
+ schemaId={schemaId ?? resource.type}
15
+ fields={fields}
16
+ data={resource.content ?? {}}
17
+ title={resource.name}
18
+ />
19
+ {projection && (
20
+ <Text size="s" color="secondary">projection: {projection}</Text>
21
+ )}
22
+ </View>
23
+ )
24
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { SchemaFormDefault } from './SchemaFormDefault.jsx'
4
+
5
+ export const metadata = { id: '@ossy/resources/form/default' }
6
+
7
+ /** Generic schema form — `@ossy/resources/form/default`. */
8
+ export default function SchemaFormDefaultComponent (props) {
9
+ return (
10
+ <View inset="m">
11
+ <SchemaFormDefault {...props} />
12
+ </View>
13
+ )
14
+ }
@@ -0,0 +1,6 @@
1
+ import SchemaFormDefaultComponent from './schema-form-default.component.jsx'
2
+
3
+ /** Default schema form slots merged into {@link ComponentSlotsProvider} at app bootstrap. */
4
+ export const DEFAULT_SCHEMA_FORM_SLOTS = {
5
+ '@ossy/resources/form/default': SchemaFormDefaultComponent,
6
+ }
@@ -0,0 +1,6 @@
1
+ import SchemaDetailViewComponent from './schema-detail.component.jsx'
2
+
3
+ /** Default schema view slots merged into {@link ComponentSlotsProvider} at app bootstrap. */
4
+ export const DEFAULT_SCHEMA_VIEW_SLOTS = {
5
+ '@ossy/resources/view/detail': SchemaDetailViewComponent,
6
+ }
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/search', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/search', access: 'workspace' }
@@ -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.aggregate.js'
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'
@@ -7,30 +7,31 @@
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
+ "resources.form.typeRemoved": "Den här dokumenttypen har tagits bort och kan inte redigeras.",
11
+ "@ossy/resources/actions/create.label": "Skapa resurs",
12
+ "@ossy/resources/actions/create.description": "Skapa en ny resurs i workspace",
13
+ "@ossy/resources/actions/list.label": "Lista resurser",
14
+ "@ossy/resources/actions/list.description": "Lista resurser på en plats i workspace",
15
+ "@ossy/resources/actions/get.label": "Hämta resurs",
16
+ "@ossy/resources/actions/get.description": "Hämta en enskild resurs via id",
17
+ "@ossy/resources/actions/search.label": "Sök resurser",
18
+ "@ossy/resources/actions/search.description": "Sök resurser i en workspace",
19
+ "@ossy/resources/actions/delete.label": "Ta bort resurs",
20
+ "@ossy/resources/actions/delete.description": "Ta bort en resurs från workspace",
21
+ "@ossy/resources/actions/update-name.label": "Uppdatera resursnamn",
22
+ "@ossy/resources/actions/update-name.description": "Byt namn på en resurs i workspace",
23
+ "@ossy/resources/actions/update-content.label": "Uppdatera resursinnehåll",
24
+ "@ossy/resources/actions/update-content.description": "Uppdatera innehållet i en resurs",
25
+ "@ossy/resources/actions/update-location.label": "Uppdatera resursplats",
26
+ "@ossy/resources/actions/update-location.description": "Flytta en resurs till en ny plats",
27
+ "@ossy/resources/actions/update-access.label": "Uppdatera resursåtkomst",
28
+ "@ossy/resources/actions/update-access.description": "Ändra åtkomstinställningar för en resurs",
29
+ "@ossy/resources/actions/upload-named-version.label": "Ladda upp namngiven version",
30
+ "@ossy/resources/actions/upload-named-version.description": "Ladda upp en namngiven version av en resursfil",
31
+ "@ossy/resources/actions/create-page-view.label": "Skapa sidvisning",
32
+ "@ossy/resources/actions/create-page-view.description": "Registrera en sidvisningshändelse för analys",
33
+ "@ossy/resources/actions/get-page-view-stats.label": "Hämta sidvisningsstatistik",
34
+ "@ossy/resources/actions/get-page-view-stats.description": "Hämta aggregerad statistik för sidvisningar",
34
35
  "resources/create/generic.documentTitle": "Skapa resurs",
35
36
  "create-directory.documentTitle": "Skapa mapp",
36
37
  "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?.resourceId ?? req?.params?.resourceId
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 Aggregate.Of(Resource, resourceId)
19
- .then(Aggregate.Add(ResourcesEvents.AccessUpdated({ createdBy, access: resourceAccess })))
20
- .then(Aggregate.View())
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 { getSystemResourceTemplates, normalizeAndValidateDocumentContent } from '@ossy/platform'
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?.resourceId ?? req?.params?.resourceId
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
- return Aggregate.Of(Resource, resourceId)
21
- .then(aggregate => {
22
- const resource = Aggregate.View()(aggregate)
23
- const systemTemplates = getSystemResourceTemplates()
24
- const allTemplates = [
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' }
@@ -1,20 +1,21 @@
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-location' }
6
+ export const metadata = { id: '@ossy/resources/tasks/update-location' }
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?.resourceId ?? req?.params?.resourceId
10
+ const resourceId = resolveResourceId({ payload, req })
11
11
  const location = payload?.target ?? payload?.location
12
12
 
13
13
  if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
14
14
  if (!location) throw Object.assign(new Error('target location is required'), { status: 400 })
15
15
 
16
- const resource = await Aggregate.Of(Resource, resourceId)
17
- .then(Aggregate.Add(ResourcesEvents.LocationUpdated({ createdBy, location })))
18
- .then(Aggregate.View())
16
+ const resource = await mutateResource(
17
+ resourceId,
18
+ ResourcesEvents.Patched({ createdBy, location }),
19
+ )
19
20
  return withResourceMedia(resource, mediaContextFromRun({ payload, req }))
20
21
  }
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/update-name', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/update-name', access: 'workspace' }
@@ -1,20 +1,21 @@
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-name' }
6
+ export const metadata = { id: '@ossy/resources/tasks/update-name' }
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?.resourceId ?? req?.params?.resourceId
10
+ const resourceId = resolveResourceId({ payload, req })
11
11
  const name = payload?.name
12
12
 
13
13
  if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
14
14
  if (!name) throw Object.assign(new Error('name is required'), { status: 400 })
15
15
 
16
- const resource = await Aggregate.Of(Resource, resourceId)
17
- .then(Aggregate.Add(ResourcesEvents.NameUpdated({ createdBy, name })))
18
- .then(Aggregate.View())
16
+ const resource = await mutateResource(
17
+ resourceId,
18
+ ResourcesEvents.Patched({ createdBy, name }),
19
+ )
19
20
  return withResourceMedia(resource, mediaContextFromRun({ payload, req }))
20
21
  }
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/upload-named-version', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/upload-named-version', access: 'workspace' }