@ossy/resources 1.11.7 → 1.12.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 +10 -4
- package/src/AudioResource.jsx +4 -2
- package/src/CreateDirectory.jsx +14 -13
- package/src/CreateDocument.jsx +7 -5
- package/src/Definition.js +2 -8
- package/src/DocumentEdit.jsx +9 -6
- package/src/DocumentView.jsx +8 -4
- package/src/GenericResourceCard.jsx +4 -2
- package/src/GenericResourceDetail.jsx +9 -4
- package/src/GenericResourceForm.jsx +14 -7
- package/src/ImageResource.jsx +4 -2
- package/src/PDFResource.jsx +4 -2
- package/src/ResourceContentPage.jsx +4 -2
- package/src/ResourceDescription.jsx +4 -3
- package/src/ResourceDetails.jsx +9 -4
- package/src/ResourceDialogMove.jsx +4 -3
- package/src/ResourceFactory.jsx +6 -3
- package/src/ResourceGenericView.jsx +4 -2
- package/src/ResourceList.jsx +4 -2
- package/src/ResourcePanel.jsx +9 -12
- package/src/ResourceTags.jsx +4 -3
- package/src/ResourcesPage.jsx +8 -6
- package/src/UploadResources.jsx +9 -11
- package/src/VideoResource.jsx +4 -2
- package/src/create-page-view.action.js +1 -38
- package/src/create-page-view.task.js +38 -0
- package/src/create.action.js +1 -98
- package/src/create.task.js +100 -0
- package/src/delete.action.js +1 -31
- package/src/delete.task.js +31 -0
- package/src/en.translations.json +48 -0
- package/src/get-page-view-stats.action.js +1 -65
- package/src/get-page-view-stats.task.js +64 -0
- package/src/get.action.js +1 -14
- package/src/get.task.js +14 -0
- package/src/index.js +13 -4
- package/src/list.action.js +1 -19
- package/src/list.task.js +20 -0
- package/src/resource-create.page.jsx +7 -5
- package/src/resource-detail.page.jsx +0 -2
- package/src/resource.helpers.js +83 -0
- package/src/resources.action-media.js +18 -0
- package/src/resources.attach-media-urls.js +1 -1
- package/src/resources.events.js +1 -0
- package/src/resources.spec.js +54 -55
- package/src/search.action.js +1 -19
- package/src/search.task.js +20 -0
- package/src/server.js +4 -0
- package/src/sv.translations.json +48 -0
- package/src/update-access.action.js +1 -20
- package/src/update-access.task.js +22 -0
- package/src/update-content.action.js +1 -39
- package/src/update-content.task.js +41 -0
- package/src/update-location.action.js +1 -18
- package/src/update-location.task.js +20 -0
- package/src/update-name.action.js +1 -18
- package/src/update-name.task.js +20 -0
- package/src/upload-named-version.action.js +1 -42
- package/src/upload-named-version.task.js +46 -0
- package/src/item-access.api.js +0 -34
- package/src/item-content.api.js +0 -35
- package/src/item-location.api.js +0 -34
- package/src/item-name.api.js +0 -34
- package/src/item-version.api.js +0 -48
- package/src/item.api.js +0 -58
- package/src/list.api.js +0 -43
- package/src/page-views-stats.api.js +0 -23
- package/src/page-views.api.js +0 -23
- package/src/search.api.js +0 -27
package/src/ResourcesPage.jsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { SDK } from '@ossy/sdk'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { metadata as ListResources } from './list.action.js'
|
|
4
|
+
import { AsyncStatus, WorkspaceProvider, useSdk } from '@ossy/sdk-react'
|
|
5
|
+
import { Title, Text, Switch, View, ImageCard, Tags, useLocale } from '@ossy/design-system'
|
|
5
6
|
import { useRouter } from '@ossy/router-react'
|
|
6
|
-
import { Definition } from './Definition.js'
|
|
7
7
|
|
|
8
8
|
const sdk = SDK.of({
|
|
9
9
|
/** Ossy.se workspaceID - used to fetch free resources */
|
|
@@ -18,7 +18,9 @@ const freeResourcesGridStyles = {
|
|
|
18
18
|
|
|
19
19
|
export const ResourcesPage= () => {
|
|
20
20
|
const router = useRouter()
|
|
21
|
-
const {
|
|
21
|
+
const { t } = useLocale()
|
|
22
|
+
const sdk = useSdk()
|
|
23
|
+
const { status, data: resources = [] } = sdk.read(ListResources, { location: '/publications/images/' })
|
|
22
24
|
const tags = resources.flatMap(resource => resource?.content?.tags || [])
|
|
23
25
|
const tagsCount = tags.reduce((acc, t) => ({ ...acc, [t]: (acc?.[t] ?? 0) + 1 }), {})
|
|
24
26
|
|
|
@@ -34,8 +36,8 @@ export const ResourcesPage= () => {
|
|
|
34
36
|
<View gap="l" style={{ margin: '0 auto' }}>
|
|
35
37
|
|
|
36
38
|
<View gap="m">
|
|
37
|
-
<Title>{
|
|
38
|
-
<Text style={{ maxWidth: '900px'}}>{
|
|
39
|
+
<Title>{t('resources.home.title')}</Title>
|
|
40
|
+
<Text style={{ maxWidth: '900px'}}>{t('resources.home.description')}</Text>
|
|
39
41
|
</View>
|
|
40
42
|
|
|
41
43
|
<WorkspaceProvider sdk={sdk}>
|
package/src/UploadResources.jsx
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { uploadFile } from './resource.helpers.js'
|
|
3
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
4
|
+
import { Title, View, Text, useLocale } from '@ossy/design-system'
|
|
4
5
|
import { useRouter } from '@ossy/router-react'
|
|
5
6
|
import { Upload } from './Upload.jsx'
|
|
6
7
|
|
|
7
8
|
export const UploadResources = () => {
|
|
9
|
+
const { t } = useLocale()
|
|
8
10
|
const router = useRouter()
|
|
11
|
+
const sdk = useSdk()
|
|
9
12
|
const location = router.searchParams.location
|
|
10
|
-
const { uploadFile } = useResources()
|
|
11
13
|
|
|
12
14
|
return (
|
|
13
15
|
<View gap="s" style={{ height: '100%' }}>
|
|
14
|
-
|
|
15
16
|
<View gap="s">
|
|
16
|
-
<Title variant="tertiary" style={{ marginBottom: '0' }}>
|
|
17
|
-
Upload
|
|
18
|
-
</Title>
|
|
17
|
+
<Title variant="tertiary" style={{ marginBottom: '0' }}>{t('resources.upload.title')}</Title>
|
|
19
18
|
<View layout="row" gap="s">
|
|
20
|
-
<Text variant="small"
|
|
19
|
+
<Text variant="small">{t('resources.upload.location', { location })}</Text>
|
|
21
20
|
</View>
|
|
22
21
|
</View>
|
|
23
|
-
|
|
24
22
|
<Upload
|
|
25
|
-
onUpload={(file) => uploadFile(location, file)}
|
|
23
|
+
onUpload={(file) => uploadFile(sdk, location, file)}
|
|
26
24
|
onCancel={() => router.navigate(`@storage/home?location=${location}`)}
|
|
27
25
|
onDone={() => router.navigate(`@storage/home?location=${location}`)}
|
|
28
26
|
/>
|
|
29
27
|
</View>
|
|
30
28
|
)
|
|
31
|
-
}
|
|
29
|
+
}
|
package/src/VideoResource.jsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { metadata as GetResource } from './get.action.js'
|
|
3
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
3
4
|
import { View } from '@ossy/design-system'
|
|
4
5
|
|
|
5
6
|
export const VideoResource = ({
|
|
6
7
|
resourceId
|
|
7
8
|
}) => {
|
|
8
|
-
const
|
|
9
|
+
const sdk = useSdk()
|
|
10
|
+
const { data: resource } = sdk.read(GetResource, { id: resourceId }, { enabled: !!resourceId })
|
|
9
11
|
|
|
10
12
|
return (
|
|
11
13
|
<View stack bordered>
|
|
@@ -1,38 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Resource, ResourcesEvents } from '@ossy/resources'
|
|
3
|
-
import { Workspace } from '@ossy/workspaces'
|
|
4
|
-
import { normalizeAndValidateDocumentContent } from '@ossy/platform'
|
|
5
|
-
|
|
6
|
-
export const id = 'resources/create-page-view'
|
|
7
|
-
export const access = 'workspace'
|
|
8
|
-
|
|
9
|
-
const PAGE_VIEW_TEMPLATE_ID = '@ossy/web/page-view'
|
|
10
|
-
const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
|
|
11
|
-
|
|
12
|
-
export async function run({ payload, req }) {
|
|
13
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
14
|
-
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
15
|
-
|
|
16
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
17
|
-
|
|
18
|
-
// Page-view template is a built-in; instantiate directly without schema validation
|
|
19
|
-
const contentInput = {
|
|
20
|
-
...(payload ?? {}),
|
|
21
|
-
userAgent: payload?.userAgent ?? req?.headers?.['user-agent'],
|
|
22
|
-
eventAt: payload?.eventAt ?? Date.now(),
|
|
23
|
-
path: payload?.path ?? '/',
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const event = ResourcesEvents.Created({
|
|
27
|
-
type: PAGE_VIEW_TEMPLATE_ID,
|
|
28
|
-
createdBy: payload?.userId ?? req?.userId ?? 'public',
|
|
29
|
-
belongsTo: workspace.id,
|
|
30
|
-
location: PAGE_VIEW_LOCATION,
|
|
31
|
-
name: `${Date.now()}`,
|
|
32
|
-
content: contentInput,
|
|
33
|
-
access: 'restricted',
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
37
|
-
return { id: resource.id }
|
|
38
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/create-page-view', access: 'workspace' }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Resource } from './resource.aggregate.js'
|
|
3
|
+
import { ResourcesEvents } from './resources.events.js'
|
|
4
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
5
|
+
import { normalizeAndValidateDocumentContent } from '@ossy/platform'
|
|
6
|
+
|
|
7
|
+
export const metadata = { id: 'resources/create-page-view' }
|
|
8
|
+
|
|
9
|
+
const PAGE_VIEW_TEMPLATE_ID = '@ossy/web/page-view'
|
|
10
|
+
const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
|
|
11
|
+
|
|
12
|
+
export async function run({ payload, req }) {
|
|
13
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
14
|
+
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
15
|
+
|
|
16
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
17
|
+
|
|
18
|
+
// Page-view template is a built-in; instantiate directly without schema validation
|
|
19
|
+
const contentInput = {
|
|
20
|
+
...(payload ?? {}),
|
|
21
|
+
userAgent: payload?.userAgent ?? req?.headers?.['user-agent'],
|
|
22
|
+
eventAt: payload?.eventAt ?? Date.now(),
|
|
23
|
+
path: payload?.path ?? '/',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const event = ResourcesEvents.Created({
|
|
27
|
+
type: PAGE_VIEW_TEMPLATE_ID,
|
|
28
|
+
createdBy: payload?.userId ?? req?.userId ?? 'public',
|
|
29
|
+
belongsTo: workspace.id,
|
|
30
|
+
location: PAGE_VIEW_LOCATION,
|
|
31
|
+
name: `${Date.now()}`,
|
|
32
|
+
content: contentInput,
|
|
33
|
+
access: 'restricted',
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
37
|
+
return { id: resource.id }
|
|
38
|
+
}
|
package/src/create.action.js
CHANGED
|
@@ -1,98 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { nanoid } from 'nanoid'
|
|
3
|
-
import { Aggregate } from '@ossy/event-store'
|
|
4
|
-
import { Resource, ResourcesEvents } from '@ossy/resources'
|
|
5
|
-
import { Workspace } from '@ossy/workspaces'
|
|
6
|
-
import { getSystemResourceTemplates, normalizeAndValidateDocumentContent } from '@ossy/platform'
|
|
7
|
-
|
|
8
|
-
export const id = 'resources/create'
|
|
9
|
-
export const access = 'workspace'
|
|
10
|
-
|
|
11
|
-
const NativeResourceTypes = { DIRECTORY: 'directory' }
|
|
12
|
-
|
|
13
|
-
export async function run({ payload, integrations, req }) {
|
|
14
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
15
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
16
|
-
|
|
17
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
18
|
-
let location = payload?.location
|
|
19
|
-
const type = payload?.type
|
|
20
|
-
const name = payload?.name
|
|
21
|
-
const fileExtension = name?.split?.('.')?.pop?.()
|
|
22
|
-
const content = payload?.content
|
|
23
|
-
const size = payload?.size
|
|
24
|
-
|
|
25
|
-
if (!is(String, location)) {
|
|
26
|
-
throw Object.assign(new Error(`Not a valid location path: ${location}`), { status: 400, type: 'INVALID_PATH' })
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
location = location.endsWith('/') ? location : location + '/'
|
|
30
|
-
location = location.startsWith('/') ? location : '/' + location
|
|
31
|
-
|
|
32
|
-
const systemTemplates = getSystemResourceTemplates()
|
|
33
|
-
const allTemplates = [
|
|
34
|
-
...systemTemplates,
|
|
35
|
-
...(workspace.resourceTemplates || []).filter(t => !systemTemplates.find(s => s.id === t.id)),
|
|
36
|
-
]
|
|
37
|
-
const isResourceTemplateType = allTemplates.map(t => t.id).includes(type)
|
|
38
|
-
|
|
39
|
-
if (type === NativeResourceTypes.DIRECTORY) {
|
|
40
|
-
const event = ResourcesEvents.Created({
|
|
41
|
-
type: NativeResourceTypes.DIRECTORY,
|
|
42
|
-
createdBy,
|
|
43
|
-
belongsTo: workspace.id,
|
|
44
|
-
location,
|
|
45
|
-
name,
|
|
46
|
-
access: 'workspace',
|
|
47
|
-
})
|
|
48
|
-
return Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (isResourceTemplateType) {
|
|
52
|
-
const template = allTemplates.find(t => t.id === type)
|
|
53
|
-
const normalized = normalizeAndValidateDocumentContent(content, template)
|
|
54
|
-
if (!normalized.ok) {
|
|
55
|
-
throw Object.assign(new Error(normalized.message), { status: 400, type: normalized.code })
|
|
56
|
-
}
|
|
57
|
-
const event = ResourcesEvents.Created({
|
|
58
|
-
type,
|
|
59
|
-
createdBy,
|
|
60
|
-
belongsTo: workspace.id,
|
|
61
|
-
location,
|
|
62
|
-
name,
|
|
63
|
-
content: normalized.content,
|
|
64
|
-
})
|
|
65
|
-
return Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!fileExtension) {
|
|
69
|
-
throw Object.assign(new Error('Missing valid file extension'), { status: 400 })
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const aggregateId = nanoid()
|
|
73
|
-
const event = ResourcesEvents.Created({
|
|
74
|
-
aggregateId,
|
|
75
|
-
type,
|
|
76
|
-
createdBy,
|
|
77
|
-
belongsTo: workspace.id,
|
|
78
|
-
location,
|
|
79
|
-
name,
|
|
80
|
-
content: {
|
|
81
|
-
Key: `media/${workspace.id}/${aggregateId}/original.${fileExtension}`,
|
|
82
|
-
ContentLength: size,
|
|
83
|
-
ContentType: type,
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const storageClient = integrations?.get?.('storage')
|
|
88
|
-
const uploadHeaders = {
|
|
89
|
-
ContentLength: event.payload.content.ContentLength,
|
|
90
|
-
ContentType: event.payload.content.ContentType,
|
|
91
|
-
Key: event.payload.content.Key,
|
|
92
|
-
}
|
|
93
|
-
const uploadUrl = storageClient ? await storageClient.createUploadUrl(uploadHeaders) : null
|
|
94
|
-
const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
95
|
-
return uploadUrl
|
|
96
|
-
? { ...resource, content: { ...resource.content, uploadUrl } }
|
|
97
|
-
: resource
|
|
98
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/create', access: 'workspace' }
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { is } from 'ramda'
|
|
2
|
+
import { nanoid } from 'nanoid'
|
|
3
|
+
import { Aggregate } from '@ossy/event-store'
|
|
4
|
+
import { Resource } from './resource.aggregate.js'
|
|
5
|
+
import { ResourcesEvents } from './resources.events.js'
|
|
6
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
7
|
+
import { getSystemResourceTemplates, normalizeAndValidateDocumentContent } from '@ossy/platform'
|
|
8
|
+
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
9
|
+
|
|
10
|
+
export const metadata = { id: 'resources/create' }
|
|
11
|
+
|
|
12
|
+
const NativeResourceTypes = { DIRECTORY: 'directory' }
|
|
13
|
+
|
|
14
|
+
export async function run({ payload, integrations, req }) {
|
|
15
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
16
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
17
|
+
|
|
18
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
19
|
+
let location = payload?.location
|
|
20
|
+
const type = payload?.type
|
|
21
|
+
const name = payload?.name
|
|
22
|
+
const fileExtension = name?.split?.('.')?.pop?.()
|
|
23
|
+
const content = payload?.content
|
|
24
|
+
const size = payload?.size
|
|
25
|
+
|
|
26
|
+
if (!is(String, location)) {
|
|
27
|
+
throw Object.assign(new Error(`Not a valid location path: ${location}`), { status: 400, type: 'INVALID_PATH' })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
location = location.endsWith('/') ? location : location + '/'
|
|
31
|
+
location = location.startsWith('/') ? location : '/' + location
|
|
32
|
+
|
|
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)
|
|
39
|
+
|
|
40
|
+
if (type === NativeResourceTypes.DIRECTORY) {
|
|
41
|
+
const event = ResourcesEvents.Created({
|
|
42
|
+
type: NativeResourceTypes.DIRECTORY,
|
|
43
|
+
createdBy,
|
|
44
|
+
belongsTo: workspace.id,
|
|
45
|
+
location,
|
|
46
|
+
name,
|
|
47
|
+
access: 'workspace',
|
|
48
|
+
})
|
|
49
|
+
return Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
50
|
+
}
|
|
51
|
+
|
|
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 })
|
|
57
|
+
}
|
|
58
|
+
const event = ResourcesEvents.Created({
|
|
59
|
+
type,
|
|
60
|
+
createdBy,
|
|
61
|
+
belongsTo: workspace.id,
|
|
62
|
+
location,
|
|
63
|
+
name,
|
|
64
|
+
content: normalized.content,
|
|
65
|
+
})
|
|
66
|
+
return Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!fileExtension) {
|
|
70
|
+
throw Object.assign(new Error('Missing valid file extension'), { status: 400 })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const aggregateId = nanoid()
|
|
74
|
+
const event = ResourcesEvents.Created({
|
|
75
|
+
aggregateId,
|
|
76
|
+
type,
|
|
77
|
+
createdBy,
|
|
78
|
+
belongsTo: workspace.id,
|
|
79
|
+
location,
|
|
80
|
+
name,
|
|
81
|
+
content: {
|
|
82
|
+
Key: `media/${workspace.id}/${aggregateId}/original.${fileExtension}`,
|
|
83
|
+
ContentLength: size,
|
|
84
|
+
ContentType: type,
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const storageClient = integrations?.get?.('storage')
|
|
89
|
+
const uploadHeaders = {
|
|
90
|
+
ContentLength: event.payload.content.ContentLength,
|
|
91
|
+
ContentType: event.payload.content.ContentType,
|
|
92
|
+
Key: event.payload.content.Key,
|
|
93
|
+
}
|
|
94
|
+
const uploadUrl = storageClient ? await storageClient.createUploadUrl(uploadHeaders) : null
|
|
95
|
+
const resource = await Aggregate.Of(Resource, event).then(Aggregate.View())
|
|
96
|
+
const withUpload = uploadUrl
|
|
97
|
+
? { ...resource, content: { ...resource.content, uploadUrl } }
|
|
98
|
+
: resource
|
|
99
|
+
return withResourceMedia(withUpload, mediaContextFromRun({ payload, req }))
|
|
100
|
+
}
|
package/src/delete.action.js
CHANGED
|
@@ -1,31 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Resource, ResourcesEvents } from '@ossy/resources'
|
|
3
|
-
import { ResourcesQueries } from './resources.queries.js'
|
|
4
|
-
|
|
5
|
-
export const id = 'resources/delete'
|
|
6
|
-
export const access = 'workspace'
|
|
7
|
-
|
|
8
|
-
const DIRECTORY_TYPE = 'directory'
|
|
9
|
-
|
|
10
|
-
export async function run({ payload, req }) {
|
|
11
|
-
const createdBy = payload?.userId ?? req?.userId
|
|
12
|
-
const resourceId = payload?.resourceId ?? req?.params?.resourceId
|
|
13
|
-
|
|
14
|
-
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
15
|
-
|
|
16
|
-
const aggregate = await Aggregate.Of(Resource, resourceId)
|
|
17
|
-
const resource = Aggregate.View()(aggregate)
|
|
18
|
-
|
|
19
|
-
if (resource.type === DIRECTORY_TYPE) {
|
|
20
|
-
const nestedResources = await ResourcesQueries.GetNestedResources({
|
|
21
|
-
location: resource.location + resource.name + '/',
|
|
22
|
-
})
|
|
23
|
-
await Promise.all(nestedResources.map(nested =>
|
|
24
|
-
Aggregate.Of(Resource, nested.id)
|
|
25
|
-
.then(Aggregate.Add(ResourcesEvents.Deleted({ createdBy })))
|
|
26
|
-
))
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
await Aggregate.Add(ResourcesEvents.Deleted({ createdBy }))(aggregate)
|
|
30
|
-
return null
|
|
31
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/delete', access: 'workspace' }
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Resource } from './resource.aggregate.js'
|
|
3
|
+
import { ResourcesEvents } from './resources.events.js'
|
|
4
|
+
import { ResourcesQueries } from './resources.queries.js'
|
|
5
|
+
|
|
6
|
+
export const metadata = { id: 'resources/delete' }
|
|
7
|
+
|
|
8
|
+
const DIRECTORY_TYPE = 'directory'
|
|
9
|
+
|
|
10
|
+
export async function run({ payload, req }) {
|
|
11
|
+
const createdBy = payload?.userId ?? req?.userId
|
|
12
|
+
const resourceId = payload?.resourceId ?? req?.params?.resourceId
|
|
13
|
+
|
|
14
|
+
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
15
|
+
|
|
16
|
+
const aggregate = await Aggregate.Of(Resource, resourceId)
|
|
17
|
+
const resource = Aggregate.View()(aggregate)
|
|
18
|
+
|
|
19
|
+
if (resource.type === DIRECTORY_TYPE) {
|
|
20
|
+
const nestedResources = await ResourcesQueries.GetNestedResources({
|
|
21
|
+
location: resource.location + resource.name + '/',
|
|
22
|
+
})
|
|
23
|
+
await Promise.all(nestedResources.map(nested =>
|
|
24
|
+
Aggregate.Of(Resource, nested.id)
|
|
25
|
+
.then(Aggregate.Add(ResourcesEvents.Deleted({ createdBy })))
|
|
26
|
+
))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
await Aggregate.Add(ResourcesEvents.Deleted({ createdBy }))(aggregate)
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"resources/home.documentTitle": "Free Resources",
|
|
3
|
+
"resources.home.title": "Free Resources",
|
|
4
|
+
"resources.home.description": "We believe in providing value at every step of your journey with us. That's why we've curated a collection of free resources. Whether you're looking to enhance your creative projects or simply seeking inspiration, explore the gallery below to discover valuable assets that align with your vision. We hope you find them useful, and we're here if you have any questions!",
|
|
5
|
+
"resources.form.nameRequired": "Name is required",
|
|
6
|
+
"resources.form.untitled": "Untitled",
|
|
7
|
+
"resources.form.selectType": "Select a resource type to continue.",
|
|
8
|
+
"resources.form.createFailed": "Create failed",
|
|
9
|
+
"resources.form.createResource": "Create resource",
|
|
10
|
+
"resources/create.label": "Create resource",
|
|
11
|
+
"resources/create.description": "Create a new resource in the workspace",
|
|
12
|
+
"resources/list.label": "List resources",
|
|
13
|
+
"resources/list.description": "List resources in a workspace location",
|
|
14
|
+
"resources/get.label": "Get resource",
|
|
15
|
+
"resources/get.description": "Fetch a single resource by id",
|
|
16
|
+
"resources/search.label": "Search resources",
|
|
17
|
+
"resources/search.description": "Search resources in a workspace",
|
|
18
|
+
"resources/delete.label": "Delete resource",
|
|
19
|
+
"resources/delete.description": "Delete a resource from the workspace",
|
|
20
|
+
"resources/update-name.label": "Update resource name",
|
|
21
|
+
"resources/update-name.description": "Rename a workspace resource",
|
|
22
|
+
"resources/update-content.label": "Update resource content",
|
|
23
|
+
"resources/update-content.description": "Update the content of a workspace resource",
|
|
24
|
+
"resources/update-location.label": "Update resource location",
|
|
25
|
+
"resources/update-location.description": "Move a resource to a new location",
|
|
26
|
+
"resources/update-access.label": "Update resource access",
|
|
27
|
+
"resources/update-access.description": "Change access settings for a resource",
|
|
28
|
+
"resources/upload-named-version.label": "Upload named version",
|
|
29
|
+
"resources/upload-named-version.description": "Upload a named version of a resource file",
|
|
30
|
+
"resources/create-page-view.label": "Create page view",
|
|
31
|
+
"resources/create-page-view.description": "Record a page view analytics event",
|
|
32
|
+
"resources/get-page-view-stats.label": "Get page view stats",
|
|
33
|
+
"resources/get-page-view-stats.description": "Fetch aggregated page view statistics",
|
|
34
|
+
"resources/create/generic.documentTitle": "Create resource",
|
|
35
|
+
"create-directory.documentTitle": "Create directory",
|
|
36
|
+
"create-document.documentTitle": "Create document",
|
|
37
|
+
"upload-media.documentTitle": "Upload",
|
|
38
|
+
"@resource.documentTitle": "Resource",
|
|
39
|
+
"resources.upload.title": "Upload",
|
|
40
|
+
"resources.upload.location": "Location: {location}",
|
|
41
|
+
"resources.createDirectory.title": "Create directory",
|
|
42
|
+
"resources.createDocument.title": "Create document",
|
|
43
|
+
"resources.createDirectory.description": "Create a new directory to organize your resources. The new directory will be created in the location: {location}",
|
|
44
|
+
"resources.createDirectory.namePlaceholder": "Directory name",
|
|
45
|
+
"resources.createDirectory.submit": "Create directory",
|
|
46
|
+
"resources.createDirectory.errorEmpty": "Directory name cannot be empty",
|
|
47
|
+
"resources.createDirectory.errorOssyPrefix": "Directory name cannot start with @ossy"
|
|
48
|
+
}
|
|
@@ -1,65 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
-
|
|
4
|
-
export const id = 'resources/get-page-view-stats'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
const PAGE_VIEW_TEMPLATE_ID = '@ossy/web/page-view'
|
|
8
|
-
const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
|
|
9
|
-
|
|
10
|
-
function parseMillis(input, fallback) {
|
|
11
|
-
if (input === undefined || input === null || input === '') return fallback
|
|
12
|
-
const n = Number(input)
|
|
13
|
-
if (!Number.isFinite(n) || n <= 0) return fallback
|
|
14
|
-
return n
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function run({ payload, req }) {
|
|
18
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
19
|
-
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
20
|
-
|
|
21
|
-
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
22
|
-
|
|
23
|
-
const now = Date.now()
|
|
24
|
-
const defaultFrom = now - (30 * 24 * 60 * 60 * 1000)
|
|
25
|
-
const from = parseMillis(payload?.from ?? req?.query?.from, defaultFrom)
|
|
26
|
-
const to = parseMillis(payload?.to ?? req?.query?.to, now)
|
|
27
|
-
const topLimit = Math.min(Math.max(Number(payload?.limit ?? req?.query?.limit ?? 10), 1), 50)
|
|
28
|
-
|
|
29
|
-
const baseMatch = {
|
|
30
|
-
type: 'Resource',
|
|
31
|
-
'state.status': { $ne: 'removed' },
|
|
32
|
-
'state.belongsTo': workspace.id,
|
|
33
|
-
'state.type': PAGE_VIEW_TEMPLATE_ID,
|
|
34
|
-
'state.location': PAGE_VIEW_LOCATION,
|
|
35
|
-
'state.content.eventAt': { $gte: from, $lte: to },
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const [dailyViews, topPaths, totals] = await Promise.all([
|
|
39
|
-
Aggregate.Collection.aggregate([
|
|
40
|
-
{ $match: baseMatch },
|
|
41
|
-
{ $project: { date: { $dateToString: { format: '%Y-%m-%d', date: { $toDate: '$state.content.eventAt' } } } } },
|
|
42
|
-
{ $group: { _id: '$date', views: { $sum: 1 } } },
|
|
43
|
-
{ $sort: { _id: 1 } },
|
|
44
|
-
]).toArray(),
|
|
45
|
-
Aggregate.Collection.aggregate([
|
|
46
|
-
{ $match: baseMatch },
|
|
47
|
-
{ $group: { _id: '$state.content.path', views: { $sum: 1 } } },
|
|
48
|
-
{ $sort: { views: -1 } },
|
|
49
|
-
{ $limit: topLimit },
|
|
50
|
-
{ $project: { _id: 0, path: '$_id', views: 1 } },
|
|
51
|
-
]).toArray(),
|
|
52
|
-
Aggregate.Collection.aggregate([
|
|
53
|
-
{ $match: baseMatch },
|
|
54
|
-
{ $count: 'views' },
|
|
55
|
-
]).toArray(),
|
|
56
|
-
])
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
from,
|
|
60
|
-
to,
|
|
61
|
-
totalViews: totals?.[0]?.views || 0,
|
|
62
|
-
dailyViews: dailyViews.map(x => ({ date: x._id, views: x.views })),
|
|
63
|
-
topPaths,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/get-page-view-stats', access: 'workspace' }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'resources/get-page-view-stats' }
|
|
5
|
+
|
|
6
|
+
const PAGE_VIEW_TEMPLATE_ID = '@ossy/web/page-view'
|
|
7
|
+
const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
|
|
8
|
+
|
|
9
|
+
function parseMillis(input, fallback) {
|
|
10
|
+
if (input === undefined || input === null || input === '') return fallback
|
|
11
|
+
const n = Number(input)
|
|
12
|
+
if (!Number.isFinite(n) || n <= 0) return fallback
|
|
13
|
+
return n
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function run({ payload, req }) {
|
|
17
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
18
|
+
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
19
|
+
|
|
20
|
+
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
21
|
+
|
|
22
|
+
const now = Date.now()
|
|
23
|
+
const defaultFrom = now - (30 * 24 * 60 * 60 * 1000)
|
|
24
|
+
const from = parseMillis(payload?.from ?? req?.query?.from, defaultFrom)
|
|
25
|
+
const to = parseMillis(payload?.to ?? req?.query?.to, now)
|
|
26
|
+
const topLimit = Math.min(Math.max(Number(payload?.limit ?? req?.query?.limit ?? 10), 1), 50)
|
|
27
|
+
|
|
28
|
+
const baseMatch = {
|
|
29
|
+
type: 'Resource',
|
|
30
|
+
'state.status': { $ne: 'removed' },
|
|
31
|
+
'state.belongsTo': workspace.id,
|
|
32
|
+
'state.type': PAGE_VIEW_TEMPLATE_ID,
|
|
33
|
+
'state.location': PAGE_VIEW_LOCATION,
|
|
34
|
+
'state.content.eventAt': { $gte: from, $lte: to },
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const [dailyViews, topPaths, totals] = await Promise.all([
|
|
38
|
+
Aggregate.Collection.aggregate([
|
|
39
|
+
{ $match: baseMatch },
|
|
40
|
+
{ $project: { date: { $dateToString: { format: '%Y-%m-%d', date: { $toDate: '$state.content.eventAt' } } } } },
|
|
41
|
+
{ $group: { _id: '$date', views: { $sum: 1 } } },
|
|
42
|
+
{ $sort: { _id: 1 } },
|
|
43
|
+
]).toArray(),
|
|
44
|
+
Aggregate.Collection.aggregate([
|
|
45
|
+
{ $match: baseMatch },
|
|
46
|
+
{ $group: { _id: '$state.content.path', views: { $sum: 1 } } },
|
|
47
|
+
{ $sort: { views: -1 } },
|
|
48
|
+
{ $limit: topLimit },
|
|
49
|
+
{ $project: { _id: 0, path: '$_id', views: 1 } },
|
|
50
|
+
]).toArray(),
|
|
51
|
+
Aggregate.Collection.aggregate([
|
|
52
|
+
{ $match: baseMatch },
|
|
53
|
+
{ $count: 'views' },
|
|
54
|
+
]).toArray(),
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
from,
|
|
59
|
+
to,
|
|
60
|
+
totalViews: totals?.[0]?.views || 0,
|
|
61
|
+
dailyViews: dailyViews.map(x => ({ date: x._id, views: x.views })),
|
|
62
|
+
topPaths,
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/get.action.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Resource } from '@ossy/resources'
|
|
3
|
-
|
|
4
|
-
export const id = 'resources/get'
|
|
5
|
-
export const access = 'workspace'
|
|
6
|
-
|
|
7
|
-
export async function run({ payload, req }) {
|
|
8
|
-
const resourceId = payload?.resourceId ?? req?.params?.resourceId
|
|
9
|
-
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
10
|
-
|
|
11
|
-
const resource = await Aggregate.Of(Resource, resourceId).then(Aggregate.View())
|
|
12
|
-
if (!resource?.id) throw Object.assign(new Error('Resource not found'), { status: 404 })
|
|
13
|
-
return resource
|
|
14
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/get', access: 'workspace' }
|
package/src/get.task.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Aggregate } from '@ossy/event-store'
|
|
2
|
+
import { Resource } from './resource.aggregate.js'
|
|
3
|
+
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: 'resources/get' }
|
|
6
|
+
|
|
7
|
+
export async function run({ payload, req }) {
|
|
8
|
+
const resourceId = payload?.resourceId ?? req?.params?.resourceId
|
|
9
|
+
if (!resourceId) throw Object.assign(new Error('resourceId is required'), { status: 400 })
|
|
10
|
+
|
|
11
|
+
const resource = await Aggregate.Of(Resource, resourceId).then(Aggregate.View())
|
|
12
|
+
if (!resource?.id) throw Object.assign(new Error('Resource not found'), { status: 404 })
|
|
13
|
+
return withResourceMedia(resource, mediaContextFromRun({ payload, req }))
|
|
14
|
+
}
|