@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.
Files changed (67) hide show
  1. package/package.json +10 -4
  2. package/src/AudioResource.jsx +4 -2
  3. package/src/CreateDirectory.jsx +4 -3
  4. package/src/CreateDocument.jsx +4 -3
  5. package/src/DocumentEdit.jsx +9 -6
  6. package/src/DocumentView.jsx +8 -4
  7. package/src/GenericResourceCard.jsx +4 -2
  8. package/src/GenericResourceDetail.jsx +9 -4
  9. package/src/GenericResourceForm.jsx +5 -2
  10. package/src/ImageResource.jsx +4 -2
  11. package/src/PDFResource.jsx +4 -2
  12. package/src/ResourceContentPage.jsx +4 -2
  13. package/src/ResourceDescription.jsx +4 -3
  14. package/src/ResourceDetails.jsx +9 -4
  15. package/src/ResourceDialogMove.jsx +4 -3
  16. package/src/ResourceFactory.jsx +6 -3
  17. package/src/ResourceGenericView.jsx +4 -2
  18. package/src/ResourceList.jsx +4 -2
  19. package/src/ResourcePanel.jsx +9 -12
  20. package/src/ResourceTags.jsx +4 -3
  21. package/src/ResourcesPage.jsx +4 -2
  22. package/src/UploadResources.jsx +4 -3
  23. package/src/VideoResource.jsx +4 -2
  24. package/src/create-page-view.action.js +1 -38
  25. package/src/create-page-view.task.js +38 -0
  26. package/src/create.action.js +1 -98
  27. package/src/create.task.js +100 -0
  28. package/src/delete.action.js +1 -31
  29. package/src/delete.task.js +31 -0
  30. package/src/en.translations.json +5 -5
  31. package/src/get-page-view-stats.action.js +1 -65
  32. package/src/get-page-view-stats.task.js +64 -0
  33. package/src/get.action.js +1 -14
  34. package/src/get.task.js +14 -0
  35. package/src/index.js +13 -4
  36. package/src/list.action.js +1 -19
  37. package/src/list.task.js +20 -0
  38. package/src/resource-create.page.jsx +4 -3
  39. package/src/resource.helpers.js +83 -0
  40. package/src/resources.action-media.js +18 -0
  41. package/src/resources.attach-media-urls.js +1 -1
  42. package/src/resources.events.js +1 -0
  43. package/src/resources.spec.js +54 -55
  44. package/src/search.action.js +1 -19
  45. package/src/search.task.js +20 -0
  46. package/src/server.js +4 -0
  47. package/src/sv.translations.json +5 -5
  48. package/src/update-access.action.js +1 -20
  49. package/src/update-access.task.js +22 -0
  50. package/src/update-content.action.js +1 -39
  51. package/src/update-content.task.js +41 -0
  52. package/src/update-location.action.js +1 -18
  53. package/src/update-location.task.js +20 -0
  54. package/src/update-name.action.js +1 -18
  55. package/src/update-name.task.js +20 -0
  56. package/src/upload-named-version.action.js +1 -42
  57. package/src/upload-named-version.task.js +46 -0
  58. package/src/item-access.api.js +0 -34
  59. package/src/item-content.api.js +0 -35
  60. package/src/item-location.api.js +0 -34
  61. package/src/item-name.api.js +0 -34
  62. package/src/item-version.api.js +0 -48
  63. package/src/item.api.js +0 -58
  64. package/src/list.api.js +0 -43
  65. package/src/page-views-stats.api.js +0 -23
  66. package/src/page-views.api.js +0 -23
  67. package/src/search.api.js +0 -27
@@ -1,98 +1 @@
1
- import { is } from 'ramda'
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
+ }
@@ -1,31 +1 @@
1
- import { Aggregate } from '@ossy/event-store'
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
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "resources.home.documentTitle": "Free 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.create.generic.documentTitle": "Create resource",
35
- "createDirectory.documentTitle": "Create directory",
36
- "createDocument.documentTitle": "Create document",
37
- "uploadMedia.documentTitle": "Upload",
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
- import { Aggregate } from '@ossy/event-store'
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
- import { Aggregate } from '@ossy/event-store'
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' }
@@ -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 * from './resource.aggregate.js'
2
- export * from './resources.events.js'
3
- export * from './resources.queries.js'
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'
@@ -1,19 +1 @@
1
- import { ResourcesQueries } from './resources.queries.js'
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' }
@@ -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 { useResources } from '@ossy/sdk-react'
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 { createDocument } = useResources()
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
@@ -75,6 +75,7 @@ export class ResourcesEvents {
75
75
  })
76
76
  }
77
77
 
78
+ // TODO: remove named version concept
78
79
  static NamedVersionUploaded({
79
80
  createdBy,
80
81
  namedVersion,