@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.
- package/package.json +16 -4
- package/src/AudioResource.jsx +2 -2
- package/src/CreateDirectory.jsx +2 -10
- package/src/CreateDirectory.page.jsx +1 -1
- package/src/CreateDocument.jsx +2 -2
- package/src/CreateDocument.page.jsx +1 -1
- package/src/Definition.js +3 -2
- package/src/DocumentEdit.jsx +10 -38
- package/src/DocumentView.jsx +11 -22
- package/src/GenericResourceCard.jsx +25 -7
- package/src/GenericResourceDetail.jsx +6 -60
- package/src/GenericResourceForm.jsx +171 -37
- package/src/ImageResource.jsx +1 -1
- package/src/PDFResource.jsx +1 -1
- package/src/PlatformFileField.jsx +239 -0
- package/src/ResourceContentPage.jsx +5 -5
- package/src/ResourceDescription.jsx +1 -1
- package/src/ResourceDetails.jsx +3 -4
- package/src/ResourceDialogMove.jsx +3 -3
- 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/SchemaDetailView.jsx +97 -0
- package/src/SchemaFormDefault.jsx +86 -0
- package/src/SchemaPresenter.jsx +207 -0
- package/src/Upload.jsx +6 -25
- package/src/Upload.page.jsx +1 -1
- 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 +27 -29
- package/src/delete.action.js +1 -1
- package/src/delete.task.js +9 -13
- package/src/en.translations.json +25 -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 +10 -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 +5 -5
- package/src/resource-detail.page.jsx +1 -1
- 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/schema-detail.component.jsx +24 -0
- package/src/schema-form-default.component.jsx +14 -0
- package/src/schemaFormSlots.js +6 -0
- package/src/schemaViewSlots.js +6 -0
- 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 +25 -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,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
|
|
2
|
-
import { Button, View, Text,
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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" />
|
package/src/Upload.page.jsx
CHANGED
|
@@ -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
|
|
17
|
+
<View data-region="content" surface="primary" roundness="m" inset="m">
|
|
18
18
|
<UploadResources/>
|
|
19
19
|
</View>
|
|
20
20
|
</View>
|
package/src/UploadResources.jsx
CHANGED
|
@@ -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 {
|
|
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
|
-
<
|
|
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>
|
package/src/VideoResource.jsx
CHANGED
|
@@ -7,7 +7,7 @@ export const VideoResource = ({
|
|
|
7
7
|
resourceId
|
|
8
8
|
}) => {
|
|
9
9
|
const sdk = useSdk()
|
|
10
|
-
const { data: resource } = sdk.read(GetResource, {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
37
|
+
const resource = await commitResource(event)
|
|
37
38
|
return { id: resource.id }
|
|
38
39
|
}
|
package/src/create.action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/create', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/create', access: 'workspace' }
|
package/src/create.task.js
CHANGED
|
@@ -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 {
|
|
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
|
|
34
|
-
const
|
|
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 ===
|
|
35
|
+
if (type === 'directory' || type === '@ossy/platform/schema/directory') {
|
|
41
36
|
const event = ResourcesEvents.Created({
|
|
42
|
-
|
|
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
|
|
44
|
+
return commitResource(event)
|
|
50
45
|
}
|
|
51
46
|
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
if (!
|
|
56
|
-
|
|
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:
|
|
60
|
+
content: result.data,
|
|
61
|
+
access: payload?.access ?? 'workspace',
|
|
65
62
|
})
|
|
66
|
-
return
|
|
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
|
|
70
|
+
const resourceId = nanoid()
|
|
74
71
|
const event = ResourcesEvents.Created({
|
|
75
|
-
|
|
76
|
-
|
|
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:
|
|
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
|
|
93
|
+
const resource = await commitResource(event)
|
|
96
94
|
const withUpload = uploadUrl
|
|
97
95
|
? { ...resource, content: { ...resource.content, uploadUrl } }
|
|
98
96
|
: resource
|
package/src/delete.action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/delete', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/delete', access: 'workspace' }
|
package/src/delete.task.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
17
|
-
const resource = Aggregate.View()(aggregate)
|
|
14
|
+
const resource = await viewResource(resourceId)
|
|
18
15
|
|
|
19
|
-
if (resource.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
|
-
|
|
25
|
-
.then(Aggregate.Add(ResourcesEvents.Deleted({ createdBy })))
|
|
21
|
+
mutateResource(nested.id, ResourcesEvents.Deleted({ createdBy })),
|
|
26
22
|
))
|
|
27
23
|
}
|
|
28
24
|
|
|
29
|
-
await
|
|
25
|
+
await mutateResource(resourceId, ResourcesEvents.Deleted({ createdBy }))
|
|
30
26
|
return null
|
|
31
27
|
}
|