@ossy/resources 1.12.0 → 1.12.2
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 +4 -3
- package/src/CreateDocument.jsx +4 -3
- 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 +5 -2
- 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 +4 -2
- package/src/UploadResources.jsx +4 -3
- 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 +5 -5
- 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 +4 -3
- 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 +5 -5
- 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/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
|
+
}
|
package/src/en.translations.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"resources
|
|
2
|
+
"resources/home.documentTitle": "Free Resources",
|
|
3
3
|
"resources.home.title": "Free Resources",
|
|
4
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
5
|
"resources.form.nameRequired": "Name is required",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"resources/create-page-view.description": "Record a page view analytics event",
|
|
32
32
|
"resources/get-page-view-stats.label": "Get page view stats",
|
|
33
33
|
"resources/get-page-view-stats.description": "Fetch aggregated page view statistics",
|
|
34
|
-
"resources
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
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
38
|
"@resource.documentTitle": "Resource",
|
|
39
39
|
"resources.upload.title": "Upload",
|
|
40
40
|
"resources.upload.location": "Location: {location}",
|
|
@@ -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
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
|
|
1
|
+
export { metadata as CreateResource } from './create.action.js'
|
|
2
|
+
export { metadata as GetResource } from './get.action.js'
|
|
3
|
+
export { metadata as ListResources } from './list.action.js'
|
|
4
|
+
export { metadata as SearchResources } from './search.action.js'
|
|
5
|
+
export { metadata as DeleteResource } from './delete.action.js'
|
|
6
|
+
export { metadata as UpdateResourceContent } from './update-content.action.js'
|
|
7
|
+
export { metadata as UpdateResourceLocation } from './update-location.action.js'
|
|
8
|
+
export { metadata as UpdateResourceName } from './update-name.action.js'
|
|
9
|
+
export { metadata as UpdateResourceAccess } from './update-access.action.js'
|
|
10
|
+
export { metadata as UploadNamedVersion } from './upload-named-version.action.js'
|
|
11
|
+
export { metadata as CreatePageView } from './create-page-view.action.js'
|
|
12
|
+
export { metadata as GetPageViewStats } from './get-page-view-stats.action.js'
|
|
5
13
|
export * from './AudioResource.jsx'
|
|
6
14
|
export * from './CreateDirectory.jsx'
|
|
7
15
|
export * from './CreateDocument.jsx'
|
|
@@ -29,3 +37,4 @@ export * from './UploadResources.jsx'
|
|
|
29
37
|
export * from './VideoResource.jsx'
|
|
30
38
|
export * from './useActivePath.jsx'
|
|
31
39
|
export * from './useForm.js'
|
|
40
|
+
export * from './resource.helpers.js'
|
package/src/list.action.js
CHANGED
|
@@ -1,19 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const id = 'resources/list'
|
|
4
|
-
export const access = 'workspace'
|
|
5
|
-
|
|
6
|
-
export async function run({ payload, req }) {
|
|
7
|
-
const query = { ...(payload?.query ?? {}) }
|
|
8
|
-
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
-
const user = payload?.user ?? req?.user
|
|
10
|
-
let location = payload?.location ?? query.location
|
|
11
|
-
|
|
12
|
-
if (location && !location.endsWith('/')) location += '/'
|
|
13
|
-
if (location && !location.startsWith('/')) location = '/' + location
|
|
14
|
-
if (location) query.location = location
|
|
15
|
-
|
|
16
|
-
if (workspaceId) query.belongsTo = workspaceId
|
|
17
|
-
|
|
18
|
-
return ResourcesQueries.GetResources(query, user)
|
|
19
|
-
}
|
|
1
|
+
export const metadata = { id: 'resources/list', access: 'workspace' }
|
package/src/list.task.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ResourcesQueries } from './resources.queries.js'
|
|
2
|
+
import { mediaContextFromRun, withResourceMedia } from './resources.action-media.js'
|
|
3
|
+
|
|
4
|
+
export const metadata = { id: 'resources/list' }
|
|
5
|
+
|
|
6
|
+
export async function run({ payload, req }) {
|
|
7
|
+
const query = { ...(payload?.query ?? {}) }
|
|
8
|
+
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
9
|
+
const user = payload?.user ?? req?.user
|
|
10
|
+
let location = payload?.location ?? query.location
|
|
11
|
+
|
|
12
|
+
if (location && !location.endsWith('/')) location += '/'
|
|
13
|
+
if (location && !location.startsWith('/')) location = '/' + location
|
|
14
|
+
if (location) query.location = location
|
|
15
|
+
|
|
16
|
+
if (workspaceId) query.belongsTo = workspaceId
|
|
17
|
+
|
|
18
|
+
const resources = await ResourcesQueries.GetResources(query, user)
|
|
19
|
+
return withResourceMedia(resources, mediaContextFromRun({ payload, req }))
|
|
20
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { createDocument } from './resource.helpers.js'
|
|
3
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
3
4
|
import { View, useLocale } from '@ossy/design-system'
|
|
4
5
|
import { useRouter } from '@ossy/router-react'
|
|
5
6
|
import { GenericResourceForm } from './GenericResourceForm.jsx'
|
|
@@ -18,7 +19,7 @@ export default function GenericResourceCreatePage () {
|
|
|
18
19
|
const { t } = useLocale()
|
|
19
20
|
const templateId = decodeURIComponent(router.params.templateId || '')
|
|
20
21
|
const location = router.searchParams.location
|
|
21
|
-
const
|
|
22
|
+
const sdk = useSdk()
|
|
22
23
|
const [error, setError] = useState()
|
|
23
24
|
|
|
24
25
|
const onCancel = () => {
|
|
@@ -27,7 +28,7 @@ export default function GenericResourceCreatePage () {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const onSubmit = ({ name, content, type }) => {
|
|
30
|
-
createDocument({ type, location, name, content })
|
|
31
|
+
createDocument(sdk, { type, location, name, content })
|
|
31
32
|
.then(() => onCancel())
|
|
32
33
|
.catch((err) => setError(err?.message || t('resources.form.createFailed')))
|
|
33
34
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cacheKey, normalizeLocation } from '@ossy/sdk-react'
|
|
2
|
+
import { metadata as CreateResource } from './create.action.js'
|
|
3
|
+
import { metadata as DeleteResource } from './delete.action.js'
|
|
4
|
+
import { metadata as ListResources } from './list.action.js'
|
|
5
|
+
import { metadata as UpdateResourceAccess } from './update-access.action.js'
|
|
6
|
+
import { metadata as UpdateResourceContent } from './update-content.action.js'
|
|
7
|
+
import { metadata as UpdateResourceLocation } from './update-location.action.js'
|
|
8
|
+
import { metadata as UpdateResourceName } from './update-name.action.js'
|
|
9
|
+
|
|
10
|
+
function invalidateLocation(sdk, location) {
|
|
11
|
+
sdk.invalidate(cacheKey(ListResources, { location: normalizeLocation(location) }))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function createDocument(sdk, document) {
|
|
15
|
+
const resource = await sdk.invoke(CreateResource, {
|
|
16
|
+
type: document.type,
|
|
17
|
+
location: normalizeLocation(document.location),
|
|
18
|
+
name: document.name,
|
|
19
|
+
content: document.content,
|
|
20
|
+
})
|
|
21
|
+
invalidateLocation(sdk, resource.location)
|
|
22
|
+
return resource
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function createDirectory(sdk, { location, name }) {
|
|
26
|
+
const resource = await sdk.invoke(CreateResource, {
|
|
27
|
+
type: 'directory',
|
|
28
|
+
location: normalizeLocation(location),
|
|
29
|
+
name,
|
|
30
|
+
})
|
|
31
|
+
invalidateLocation(sdk, resource.location)
|
|
32
|
+
return resource
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function uploadFile(sdk, uploadLocation, file) {
|
|
36
|
+
const location = normalizeLocation(uploadLocation)
|
|
37
|
+
const resource = await sdk.invoke(CreateResource, {
|
|
38
|
+
type: file.type,
|
|
39
|
+
location,
|
|
40
|
+
name: file.name,
|
|
41
|
+
size: file.size,
|
|
42
|
+
})
|
|
43
|
+
if (resource.content?.uploadUrl) {
|
|
44
|
+
await fetch(resource.content.uploadUrl, { method: 'PUT', body: file })
|
|
45
|
+
}
|
|
46
|
+
invalidateLocation(sdk, location)
|
|
47
|
+
return resource
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function removeResource(sdk, id, location) {
|
|
51
|
+
await sdk.invoke(DeleteResource, { id })
|
|
52
|
+
if (location) invalidateLocation(sdk, location)
|
|
53
|
+
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function updateResourceContent(sdk, id, content) {
|
|
57
|
+
const resource = await sdk.invoke(UpdateResourceContent, { id, content })
|
|
58
|
+
invalidateLocation(sdk, resource.location)
|
|
59
|
+
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
60
|
+
return resource
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function moveResource(sdk, id, target, sourceLocation) {
|
|
64
|
+
const resource = await sdk.invoke(UpdateResourceLocation, { id, target })
|
|
65
|
+
invalidateLocation(sdk, resource.location)
|
|
66
|
+
if (sourceLocation) invalidateLocation(sdk, sourceLocation)
|
|
67
|
+
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
68
|
+
return resource
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function renameResource(sdk, id, name) {
|
|
72
|
+
const resource = await sdk.invoke(UpdateResourceName, { id, name })
|
|
73
|
+
invalidateLocation(sdk, resource.location)
|
|
74
|
+
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id }))
|
|
75
|
+
return resource
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function updateResourceAccess(sdk, params) {
|
|
79
|
+
const resource = await sdk.invoke(UpdateResourceAccess, params)
|
|
80
|
+
invalidateLocation(sdk, resource.location)
|
|
81
|
+
sdk.invalidate(cacheKey({ id: 'resources/get' }, { id: params.id }))
|
|
82
|
+
return resource
|
|
83
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { attachResourceMediaUrls } from './resources.attach-media-urls.js'
|
|
2
|
+
|
|
3
|
+
export function mediaContextFromRun({ payload, req }) {
|
|
4
|
+
return {
|
|
5
|
+
userId: payload?.userId ?? req?.userId,
|
|
6
|
+
workspaceId: payload?.workspaceId ?? req?.workspaceId,
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function withResourceMedia(result, context) {
|
|
11
|
+
if (Array.isArray(result)) {
|
|
12
|
+
return Promise.all(result.map(r => attachResourceMediaUrls(r, context)))
|
|
13
|
+
}
|
|
14
|
+
if (result && typeof result === 'object' && result.id) {
|
|
15
|
+
return attachResourceMediaUrls(result, context)
|
|
16
|
+
}
|
|
17
|
+
return result
|
|
18
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StorageClient } from '@ossy/platform'
|
|
2
2
|
import { Aggregate } from '@ossy/event-store'
|
|
3
|
-
import { Workspace } from '@ossy/workspaces'
|
|
3
|
+
import { Workspace } from '@ossy/workspaces/server'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Adds `content.src` (and `content.sizes[*]` URLs) for file resources, respecting
|