@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
@@ -0,0 +1,86 @@
1
+ import React from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ Button,
6
+ Fields,
7
+ DelayedRender,
8
+ useLocale,
9
+ Form,
10
+ FieldFactory,
11
+ } from '@ossy/design-system'
12
+
13
+ /**
14
+ * Generic schema form — {@link Form} + {@link FieldFactory} (platform fallback).
15
+ *
16
+ * @param {{
17
+ * schemaId?: string,
18
+ * fields?: { name: string, type?: string, label?: string, required?: boolean }[],
19
+ * mode?: 'create' | 'edit',
20
+ * form?: { data?: Record<string, unknown>, onChange?: (detail: unknown) => void },
21
+ * initialContent?: Record<string, unknown>,
22
+ * resource?: { content?: Record<string, unknown> },
23
+ * onSubmit?: (data: Record<string, unknown>) => void,
24
+ * onCancel?: () => void,
25
+ * submitLabel?: string,
26
+ * }} props
27
+ */
28
+ export function SchemaFormDefault ({
29
+ schemaId,
30
+ fields: fieldsProp,
31
+ mode = 'create',
32
+ form,
33
+ initialContent = {},
34
+ resource,
35
+ onSubmit,
36
+ onCancel,
37
+ submitLabel,
38
+ }) {
39
+ const { t } = useLocale()
40
+ const fields = fieldsProp ?? []
41
+ const defaultData = Object.keys(initialContent).length
42
+ ? initialContent
43
+ : (resource?.content ?? {})
44
+
45
+ if (form && mode === 'edit') {
46
+ if (fields.length && form.data) {
47
+ return (
48
+ <Fields
49
+ schemaId={schemaId}
50
+ data={form.data}
51
+ onChange={form.onChange}
52
+ fields={fields}
53
+ />
54
+ )
55
+ }
56
+
57
+ if (fields.length && !form.data) {
58
+ return (
59
+ <DelayedRender>
60
+ <Text>Loading...</Text>
61
+ </DelayedRender>
62
+ )
63
+ }
64
+
65
+ return null
66
+ }
67
+
68
+ return (
69
+ <Form
70
+ id={schemaId ? `${schemaId}/default` : 'schema-form-default'}
71
+ schemaId={schemaId}
72
+ fields={fields}
73
+ defaultData={defaultData}
74
+ onSubmit={onSubmit}
75
+ gap="m"
76
+ >
77
+ <FieldFactory />
78
+ <View layout="row" gap="s" justifyContent="flex-end">
79
+ {onCancel && <Button variant="link" onClick={onCancel} label="design-system.cancel" />}
80
+ <Button type="submit" variant="cta">
81
+ {submitLabel || (mode === 'edit' ? t('design-system.save') : 'Submit')}
82
+ </Button>
83
+ </View>
84
+ </Form>
85
+ )
86
+ }
@@ -0,0 +1,207 @@
1
+ import React, { useMemo } from 'react'
2
+ import { metadata as GetResource } from './get.action.js'
3
+ import { AsyncStatus, useSdk } from '@ossy/sdk-react'
4
+ import { useSchemas } from './useSchemas.js'
5
+ import {
6
+ isMimeType,
7
+ isSchemaDocumentType,
8
+ resolveFormComponentId,
9
+ resolveMimeViewChain,
10
+ resolveViewComponentChain,
11
+ } from '@ossy/schema/resolve'
12
+ import { Guide, Text, DelayedRender, Slot, View } from '@ossy/design-system'
13
+ import { SchemaDetailView } from './SchemaDetailView.jsx'
14
+ import { useSchema } from './useSchemas.js'
15
+ import { ImageResource } from './ImageResource.jsx'
16
+ import { AudioResource } from './AudioResource.jsx'
17
+ import { VideoResource } from './VideoResource.jsx'
18
+ import { PDFResource } from './PDFResource.jsx'
19
+ import { ResourceGenericView } from './ResourceGenericView.jsx'
20
+ import { GenericResourceForm } from './GenericResourceForm.jsx'
21
+
22
+ /** MIME type for rendering — file envelope stores MIME in content. */
23
+ export function resolveMediaType (resource) {
24
+ if (resource?.content?.ContentType) return resource.content.ContentType
25
+ return resource?.type
26
+ }
27
+
28
+ function SchemaViewFallback ({ resource, projection, resourceId, fields }) {
29
+ if (fields?.length || resource?.content) {
30
+ return (
31
+ <View inset="m">
32
+ <SchemaDetailView
33
+ schemaId={resource?.type}
34
+ fields={fields}
35
+ data={resource?.content ?? {}}
36
+ title={resource?.name}
37
+ />
38
+ {projection && (
39
+ <Text size="s" color="secondary">projection: {projection}</Text>
40
+ )}
41
+ </View>
42
+ )
43
+ }
44
+
45
+ return <ResourceGenericView resourceId={resourceId} />
46
+ }
47
+
48
+ function SchemaViewPresenter ({ resource, projection, resourceId }) {
49
+ const template = useSchema(resource?.type)
50
+ const chain = useMemo(
51
+ () => resolveViewComponentChain(resource.type, projection),
52
+ [resource.type, projection],
53
+ )
54
+ const [primary, secondary] = chain
55
+ const slotProps = {
56
+ resourceId,
57
+ resource,
58
+ schemaId: resource.type,
59
+ projection,
60
+ fields: template?.fields,
61
+ }
62
+
63
+ return (
64
+ <Slot
65
+ view={primary}
66
+ {...slotProps}
67
+ fallback={
68
+ <Slot
69
+ view={secondary}
70
+ {...slotProps}
71
+ fallback={(
72
+ <SchemaViewFallback
73
+ resource={resource}
74
+ projection={projection}
75
+ resourceId={resourceId}
76
+ fields={template?.fields}
77
+ />
78
+ )}
79
+ />
80
+ }
81
+ />
82
+ )
83
+ }
84
+
85
+ function MimeViewPresenter ({ resource, projection, resourceId, onClose }) {
86
+ const mime = resolveMediaType(resource)
87
+ if (['image/jpeg', 'image/png', 'image/webp'].includes(mime)) {
88
+ return <ImageResource resourceId={resourceId} onClose={onClose} />
89
+ }
90
+ if (mime?.startsWith('video/')) {
91
+ return <VideoResource resourceId={resourceId} onClose={onClose} />
92
+ }
93
+ if (mime?.startsWith('audio/')) {
94
+ return <AudioResource resourceId={resourceId} onClose={onClose} />
95
+ }
96
+ if (mime === 'application/pdf') {
97
+ return <PDFResource resourceId={resourceId} onClose={onClose} />
98
+ }
99
+
100
+ const chain = resolveMimeViewChain(mime, projection)
101
+ const [primary, secondary] = chain
102
+ return (
103
+ <Slot
104
+ view={primary}
105
+ resourceId={resourceId}
106
+ resource={resource}
107
+ projection={projection}
108
+ fallback={
109
+ <Slot
110
+ view={secondary}
111
+ resourceId={resourceId}
112
+ resource={resource}
113
+ projection={projection}
114
+ fallback={<ResourceGenericView resourceId={resourceId} onClose={onClose} />}
115
+ />
116
+ }
117
+ />
118
+ )
119
+ }
120
+
121
+ /**
122
+ * ADR 0006 host: `resourceId` → fetch → `resource.type` → view/form component chain.
123
+ */
124
+ export function SchemaPresenter ({
125
+ resourceId,
126
+ resource: resourceProp,
127
+ flow = 'View',
128
+ projection,
129
+ form,
130
+ onClose,
131
+ onSubmit,
132
+ onCancel,
133
+ mode,
134
+ initialName,
135
+ initialContent,
136
+ submitLabel,
137
+ }) {
138
+ const sdk = useSdk()
139
+ const schemas = useSchemas()
140
+ const { data: loaded, status } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId && !resourceProp })
141
+ const resource = resourceProp ?? loaded
142
+
143
+ if (!resourceProp && AsyncStatus.Error === status) {
144
+ return (
145
+ <Guide
146
+ title="Not found"
147
+ text="We can't find the resource you are looking for"
148
+ style={{ padding: '40px 16px' }}
149
+ />
150
+ )
151
+ }
152
+
153
+ if (!resource && [AsyncStatus.Loading, AsyncStatus.NotInitialized].includes(status)) {
154
+ return (
155
+ <DelayedRender>
156
+ <Text>loading...</Text>
157
+ </DelayedRender>
158
+ )
159
+ }
160
+
161
+ if (!resource) return null
162
+
163
+ const isDocumentSchema =
164
+ isSchemaDocumentType(resource.type) || schemas.some(s => s.id === resource.type)
165
+
166
+ if (flow === 'Edit' || flow === 'edit') {
167
+ if (isDocumentSchema) {
168
+ return (
169
+ <GenericResourceForm
170
+ schemaId={resource.type}
171
+ mode="edit"
172
+ resourceId={resourceId}
173
+ resource={resource}
174
+ form={form}
175
+ onSubmit={onSubmit}
176
+ onCancel={onCancel}
177
+ submitLabel={submitLabel}
178
+ />
179
+ )
180
+ }
181
+ return <ResourceGenericView resourceId={resourceId} onClose={onClose} />
182
+ }
183
+
184
+ if (resource.type === '@ossy/platform/schema/directory' || resource.type === 'directory') {
185
+ return <Text>directory</Text>
186
+ }
187
+
188
+ if (isDocumentSchema) {
189
+ return <SchemaViewPresenter resource={resource} projection={projection} resourceId={resourceId} />
190
+ }
191
+
192
+ const mime = resolveMediaType(resource)
193
+ if (isMimeType(mime) || resource.type === '@ossy/platform/schema/file') {
194
+ return <MimeViewPresenter resource={resource} projection={projection} resourceId={resourceId} onClose={onClose} />
195
+ }
196
+
197
+ return <ResourceGenericView resourceId={resourceId} onClose={onClose} />
198
+ }
199
+
200
+ /**
201
+ * Create flow — resolves form component for a schema id.
202
+ */
203
+ export function SchemaFormPresenter (props) {
204
+ return <GenericResourceForm {...props} />
205
+ }
206
+
207
+ export { resolveFormComponentId, resolveFormComponentChain } from '@ossy/schema/resolve'
package/src/Upload.jsx CHANGED
@@ -1,5 +1,5 @@
1
- import React, { useState, useEffect } from 'react'
2
- import { Button, View, Text, Upload as _Upload, Icon } from '@ossy/design-system'
1
+ import React, { useState } from 'react'
2
+ import { Button, View, Text, UploadInput, Icon, LocalFilePreview } from '@ossy/design-system'
3
3
  import { useRouter } from '@ossy/router-react'
4
4
 
5
5
  const FlowStage = {
@@ -53,8 +53,8 @@ export const Upload = ({
53
53
  <View gap="s" style={{ flexGrow: '1', overflow: 'auto', padding: 'var(--space-m) 0' }}>
54
54
  {
55
55
  files.map(({ status, file }) => (
56
- <View layout="row" gap="m" alignItems="center">
57
- <UploadImgPreview file={file} />
56
+ <View key={file.name} layout="row" gap="m" alignItems="center">
57
+ <LocalFilePreview file={file} />
58
58
  <Text style={{ margin: '0', lineHeight: '1', textWrap: 'nowrap', textOverflow: 'ellipsis', flexGrow: '1' }}>{file?.name || 'Untitled'}</Text>
59
59
  <View layout="row" gap="xs">
60
60
  <UploadStatus status={status} size="8px" />
@@ -68,7 +68,8 @@ export const Upload = ({
68
68
  }
69
69
 
70
70
  {files.length === 0 && (
71
- <_Upload
71
+ <UploadInput
72
+ id="upload-resources"
72
73
  type="file"
73
74
  multiple
74
75
  onChange={onUserInput}
@@ -133,26 +134,6 @@ export const Upload = ({
133
134
 
134
135
 
135
136
 
136
- function UploadImgPreview({ file, size = "32px" }) {
137
- const [src, setSrc] = useState()
138
-
139
- useEffect(() => {
140
- const reader = new FileReader()
141
- reader.onload = (e) => setSrc(e.target.result)
142
- reader.readAsDataURL(file)
143
- }, [file])
144
-
145
- return (
146
- <View
147
- as="img"
148
- src={src}
149
- width={size}
150
- height={size}
151
- style={{ backgroundSize: 'cover', borderRadius: 'var(--space-s)' }}
152
- />
153
- )
154
- }
155
-
156
137
  function UploadStatus({ status }) {
157
138
  if (status === 'preview') return <></>
158
139
  if (status === 'uploading') return <Icon name="spinner" size="s" animation="rotate" />
@@ -14,7 +14,7 @@ export const metadata = {
14
14
  export const UploadPage = () => {
15
15
  return (
16
16
  <View layout="off-center-s" style={{ height: '100%' }}>
17
- <View slot="content" surface="primary" roundness="m" inset="m">
17
+ <View data-region="content" surface="primary" roundness="m" inset="m">
18
18
  <UploadResources/>
19
19
  </View>
20
20
  </View>
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { uploadFile } from './resource.helpers.js'
3
3
  import { useSdk } from '@ossy/sdk-react'
4
- import { Title, View, Text, useLocale } from '@ossy/design-system'
4
+ import { View, Text, useLocale } from '@ossy/design-system'
5
5
  import { useRouter } from '@ossy/router-react'
6
6
  import { Upload } from './Upload.jsx'
7
7
 
@@ -14,7 +14,7 @@ export const UploadResources = () => {
14
14
  return (
15
15
  <View gap="s" style={{ height: '100%' }}>
16
16
  <View gap="s">
17
- <Title variant="tertiary" style={{ marginBottom: '0' }}>{t('resources.upload.title')}</Title>
17
+ <Text variant="heading-tertiary" style={{ marginBottom: '0' }} as="h3" text="resources.upload.title" />
18
18
  <View layout="row" gap="s">
19
19
  <Text variant="small">{t('resources.upload.location', { location })}</Text>
20
20
  </View>
@@ -7,7 +7,7 @@ export const VideoResource = ({
7
7
  resourceId
8
8
  }) => {
9
9
  const sdk = useSdk()
10
- const { data: resource } = sdk.read(GetResource, { id: resourceId }, { enabled: !!resourceId })
10
+ const { data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
11
11
 
12
12
  return (
13
13
  <View stack bordered>
@@ -50,7 +50,7 @@ describe('accessFilter', () => {
50
50
  const user = { id: 'u-1', workspaces: ['ws-1', 'ws-2'], policies: [] }
51
51
  const filter = accessFilter(user)
52
52
  expect(filter.$or).toContainEqual({
53
- 'state.access': { $in: ['public', 'workspace'] },
53
+ 'state.access': { $in: ['public', 'workspace', 'restricted'] },
54
54
  'state.belongsTo': { $in: ['ws-1', 'ws-2'] },
55
55
  })
56
56
  })
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/create-page-view', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/create-page-view', access: 'workspace' }
@@ -1,21 +1,20 @@
1
+ import { nanoid } from 'nanoid'
1
2
  import { Aggregate } from '@ossy/event-store'
2
- import { Resource } from './resource.aggregate.js'
3
- import { ResourcesEvents } from './resources.events.js'
4
3
  import { Workspace } from '@ossy/workspaces/server'
5
- import { normalizeAndValidateDocumentContent } from '@ossy/platform'
4
+ import { ResourcesEvents } from './resources.events.js'
5
+ import { commitResource } from './resource-stream.helpers.js'
6
6
 
7
- export const metadata = { id: 'resources/create-page-view' }
7
+ export const metadata = { id: '@ossy/resources/tasks/create-page-view' }
8
8
 
9
- const PAGE_VIEW_TEMPLATE_ID = '@ossy/web/page-view'
9
+ const PAGE_VIEW_SCHEMA_ID = '@ossy/web/schema/page-view'
10
10
  const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
11
11
 
12
- export async function run({ payload, req }) {
12
+ export async function run ({ payload, req }) {
13
13
  const workspaceId = payload?.workspaceId ?? req?.workspaceId
14
14
  if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
15
15
 
16
16
  const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
17
17
 
18
- // Page-view template is a built-in; instantiate directly without schema validation
19
18
  const contentInput = {
20
19
  ...(payload ?? {}),
21
20
  userAgent: payload?.userAgent ?? req?.headers?.['user-agent'],
@@ -23,8 +22,10 @@ export async function run({ payload, req }) {
23
22
  path: payload?.path ?? '/',
24
23
  }
25
24
 
25
+ const resourceId = nanoid()
26
26
  const event = ResourcesEvents.Created({
27
- type: PAGE_VIEW_TEMPLATE_ID,
27
+ resourceId,
28
+ schemaId: PAGE_VIEW_SCHEMA_ID,
28
29
  createdBy: payload?.userId ?? req?.userId ?? 'public',
29
30
  belongsTo: workspace.id,
30
31
  location: PAGE_VIEW_LOCATION,
@@ -33,6 +34,6 @@ export async function run({ payload, req }) {
33
34
  access: 'restricted',
34
35
  })
35
36
 
36
- const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
37
+ const resource = await commitResource(event)
37
38
  return { id: resource.id }
38
39
  }
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/create', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/create', access: 'workspace' }
@@ -1,17 +1,16 @@
1
1
  import { is } from 'ramda'
2
2
  import { nanoid } from 'nanoid'
3
3
  import { Aggregate } from '@ossy/event-store'
4
- import { Resource } from './resource.aggregate.js'
5
- import { ResourcesEvents } from './resources.events.js'
6
4
  import { Workspace } from '@ossy/workspaces/server'
7
- import { getSystemResourceTemplates, normalizeAndValidateDocumentContent } from '@ossy/platform'
5
+ import { schemaForWorkspace } from '@ossy/platform'
6
+ import { originalObjectKey } from '@ossy/platform/storage-keys'
7
+ import { commitResource } from './resource-stream.helpers.js'
8
+ import { ResourcesEvents } from './resources.events.js'
8
9
  import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
9
10
 
10
- export const metadata = { id: 'resources/create' }
11
-
12
- const NativeResourceTypes = { DIRECTORY: 'directory' }
11
+ export const metadata = { id: '@ossy/resources/tasks/create' }
13
12
 
14
- export async function run({ payload, integrations, req }) {
13
+ export async function run ({ payload, integrations, req }) {
15
14
  const createdBy = payload?.userId ?? req?.userId
16
15
  const workspaceId = payload?.workspaceId ?? req?.workspaceId
17
16
 
@@ -30,56 +29,55 @@ export async function run({ payload, integrations, req }) {
30
29
  location = location.endsWith('/') ? location : location + '/'
31
30
  location = location.startsWith('/') ? location : '/' + location
32
31
 
33
- const systemTemplates = getSystemResourceTemplates()
34
- const allTemplates = [
35
- ...systemTemplates,
36
- ...(workspace.resourceTemplates || []).filter(t => !systemTemplates.find(s => s.id === t.id)),
37
- ]
38
- const isResourceTemplateType = allTemplates.map(t => t.id).includes(type)
32
+ const engine = schemaForWorkspace(workspace.schemas)
33
+ const isDocumentSchema = engine.has(type)
39
34
 
40
- if (type === NativeResourceTypes.DIRECTORY) {
35
+ if (type === 'directory' || type === '@ossy/platform/schema/directory') {
41
36
  const event = ResourcesEvents.Created({
42
- type: NativeResourceTypes.DIRECTORY,
37
+ schemaId: '@ossy/platform/schema/directory',
43
38
  createdBy,
44
39
  belongsTo: workspace.id,
45
40
  location,
46
41
  name,
47
42
  access: 'workspace',
48
43
  })
49
- return Aggregate.Of(Resource, event).then(Aggregate.View())
44
+ return commitResource(event)
50
45
  }
51
46
 
52
- if (isResourceTemplateType) {
53
- const template = allTemplates.find(t => t.id === type)
54
- const normalized = normalizeAndValidateDocumentContent(content, template)
55
- if (!normalized.ok) {
56
- throw Object.assign(new Error(normalized.message), { status: 400, type: normalized.code })
47
+ if (isDocumentSchema) {
48
+ const schema = engine.resolve(type)
49
+ const result = engine.validate(schema, content)
50
+ if (!result.ok) {
51
+ const first = result.errors[0]
52
+ throw Object.assign(new Error(first.message), { status: 400, type: first.code })
57
53
  }
58
54
  const event = ResourcesEvents.Created({
59
- type,
55
+ schemaId: type,
60
56
  createdBy,
61
57
  belongsTo: workspace.id,
62
58
  location,
63
59
  name,
64
- content: normalized.content,
60
+ content: result.data,
61
+ access: payload?.access ?? 'workspace',
65
62
  })
66
- return Aggregate.Of(Resource, event).then(Aggregate.View())
63
+ return commitResource(event)
67
64
  }
68
65
 
69
66
  if (!fileExtension) {
70
67
  throw Object.assign(new Error('Missing valid file extension'), { status: 400 })
71
68
  }
72
69
 
73
- const aggregateId = nanoid()
70
+ const resourceId = nanoid()
74
71
  const event = ResourcesEvents.Created({
75
- aggregateId,
76
- type,
72
+ resourceId,
73
+ schemaId: '@ossy/platform/schema/file',
77
74
  createdBy,
78
75
  belongsTo: workspace.id,
79
76
  location,
80
77
  name,
78
+ access: payload?.access ?? 'workspace',
81
79
  content: {
82
- Key: `media/${workspace.id}/${aggregateId}/original.${fileExtension}`,
80
+ Key: originalObjectKey(resourceId),
83
81
  ContentLength: size,
84
82
  ContentType: type,
85
83
  },
@@ -92,7 +90,7 @@ export async function run({ payload, integrations, req }) {
92
90
  Key: event.payload.content.Key,
93
91
  }
94
92
  const uploadUrl = storageClient ? await storageClient.createUploadUrl(uploadHeaders) : null
95
- const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
93
+ const resource = await commitResource(event)
96
94
  const withUpload = uploadUrl
97
95
  ? { ...resource, content: { ...resource.content, uploadUrl } }
98
96
  : resource
@@ -1 +1 @@
1
- export const metadata = { id: 'resources/delete', access: 'workspace' }
1
+ export const metadata = { id: '@ossy/resources/actions/delete', access: 'workspace' }
@@ -1,31 +1,27 @@
1
- import { Aggregate } from '@ossy/event-store'
2
- import { Resource } from './resource.aggregate.js'
3
1
  import { ResourcesEvents } from './resources.events.js'
4
2
  import { ResourcesQueries } from './resources.queries.js'
3
+ import { mutateResource, viewResource } from './resource-stream.helpers.js'
4
+ import { resolveResourceId } from './resource-read.helpers.js'
5
5
 
6
- export const metadata = { id: 'resources/delete' }
6
+ export const metadata = { id: '@ossy/resources/tasks/delete' }
7
7
 
8
- const DIRECTORY_TYPE = 'directory'
9
-
10
- export async function run({ payload, req }) {
8
+ export async function run ({ payload, req }) {
11
9
  const createdBy = payload?.userId ?? req?.userId
12
- const resourceId = payload?.resourceId ?? req?.params?.resourceId
10
+ const resourceId = resolveResourceId({ payload, req })
13
11
 
14
12
  if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
15
13
 
16
- const aggregate = await Aggregate.Of(Resource, resourceId)
17
- const resource = Aggregate.View()(aggregate)
14
+ const resource = await viewResource(resourceId)
18
15
 
19
- if (resource.type === DIRECTORY_TYPE) {
16
+ if (resource.type === '@ossy/platform/schema/directory' || resource.type === 'directory') {
20
17
  const nestedResources = await ResourcesQueries.GetNestedResources({
21
18
  location: resource.location + resource.name + '/',
22
19
  })
23
20
  await Promise.all(nestedResources.map(nested =>
24
- Aggregate.Of(Resource, nested.id)
25
- .then(Aggregate.Add(ResourcesEvents.Deleted({ createdBy })))
21
+ mutateResource(nested.id, ResourcesEvents.Deleted({ createdBy })),
26
22
  ))
27
23
  }
28
24
 
29
- await Aggregate.Add(ResourcesEvents.Deleted({ createdBy }))(aggregate)
25
+ await mutateResource(resourceId, ResourcesEvents.Deleted({ createdBy }))
30
26
  return null
31
27
  }