musora-content-services 1.2.5 → 1.3.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/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +11 -0
- package/README.md +0 -0
- package/babel.config.cjs +1 -1
- package/docs/config.js.html +0 -0
- package/docs/index.html +0 -0
- package/docs/module-Config.html +0 -0
- package/docs/module-Railcontent-Services.html +0 -0
- package/docs/module-Sanity-Services.html +0 -0
- package/docs/railcontent.js.html +0 -0
- package/docs/sanity.js.html +0 -0
- package/jest.config.js +9 -10
- package/jsdoc.json +17 -12
- package/package.json +2 -1
- package/src/contentMetaData.js +1190 -1131
- package/src/contentTypeConfig.js +492 -387
- package/src/filterBuilder.js +163 -145
- package/src/index.d.ts +227 -237
- package/src/index.js +226 -236
- package/src/services/config.js +12 -12
- package/src/services/contentLikes.js +33 -32
- package/src/services/contentProgress.js +233 -200
- package/src/services/dataContext.js +99 -93
- package/src/services/lastUpdated.js +7 -7
- package/src/services/railcontent.js +368 -364
- package/src/services/sanity.js +983 -955
- package/src/services/userPermissions.js +12 -14
- package/test/contentLikes.test.js +89 -86
- package/test/contentProgress.test.js +229 -236
- package/test/initializeTests.js +54 -51
- package/test/lastUpdated.test.js +20 -18
- package/test/live/contentProgressLive.test.js +135 -137
- package/test/live/railcontentLive.test.js +12 -14
- package/test/localStorageMock.js +16 -16
- package/test/log.js +5 -5
- package/test/sanityQueryService.test.js +857 -821
- package/test/userPermissions.test.js +15 -15
- package/tools/generate-index.cjs +108 -111
package/src/filterBuilder.js
CHANGED
|
@@ -1,159 +1,177 @@
|
|
|
1
|
-
import {fetchUserPermissions} from
|
|
2
|
-
import {
|
|
3
|
-
songAccessMembership
|
|
4
|
-
} from "./contentTypeConfig.js";
|
|
1
|
+
import { fetchUserPermissions } from './services/userPermissions.js'
|
|
2
|
+
import { songAccessMembership } from './contentTypeConfig.js'
|
|
5
3
|
|
|
6
4
|
export class FilterBuilder {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
statuses.splice(statuses.indexOf(this.STATUS_SCHEDULED), 1);
|
|
86
|
-
this._andWhere(`(${this.prefix}status in ${arrayToStringRepresentation(statuses)} || (${this.prefix}status == '${this.STATUS_SCHEDULED}' && defined(${this.prefix}published_on) && ${this.prefix}published_on >= '${now}'))`)
|
|
87
|
-
|
|
88
|
-
} else {
|
|
89
|
-
this._andWhere(`${this.prefix}status in ${arrayToStringRepresentation(this.availableContentStatuses)}`);
|
|
90
|
-
}
|
|
91
|
-
return this;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
_applyPermissions() {
|
|
95
|
-
if (this.bypassPermissions || this.userData.isAdmin) return this;
|
|
96
|
-
let requiredPermissions = this._getUserPermissions();
|
|
97
|
-
if(this.userData.isABasicMember && this.allowsPullSongsContent){
|
|
98
|
-
requiredPermissions = [...requiredPermissions, songAccessMembership];
|
|
99
|
-
}
|
|
100
|
-
this._andWhere(`(!defined(permission) || references(*[_type == 'permission' && railcontent_id in ${arrayToRawRepresentation(requiredPermissions)}]._id))`);
|
|
101
|
-
return this;
|
|
5
|
+
STATUS_SCHEDULED = 'scheduled'
|
|
6
|
+
STATUS_PUBLISHED = 'published'
|
|
7
|
+
STATUS_DRAFT = 'draft'
|
|
8
|
+
STATUS_ARCHIVED = 'archived'
|
|
9
|
+
STATUS_UNLISTED = 'unlisted'
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
filter = '',
|
|
13
|
+
{
|
|
14
|
+
availableContentStatuses = [],
|
|
15
|
+
bypassPermissions = false,
|
|
16
|
+
pullFutureContent = false,
|
|
17
|
+
getFutureContentOnly = false,
|
|
18
|
+
getFutureScheduledContentsOnly = false,
|
|
19
|
+
bypassStatuses = false,
|
|
20
|
+
bypassPublishedDateRestriction = false,
|
|
21
|
+
isSingle = false,
|
|
22
|
+
allowsPullSongsContent = true,
|
|
23
|
+
isChildrenFilter = false,
|
|
24
|
+
} = {}
|
|
25
|
+
) {
|
|
26
|
+
this.availableContentStatuses = availableContentStatuses
|
|
27
|
+
this.bypassPermissions = bypassPermissions
|
|
28
|
+
this.bypassStatuses = bypassStatuses
|
|
29
|
+
this.bypassPublishedDateRestriction = bypassPublishedDateRestriction
|
|
30
|
+
this.pullFutureContent = pullFutureContent
|
|
31
|
+
this.getFutureContentOnly = getFutureContentOnly
|
|
32
|
+
this.getFutureScheduledContentsOnly = getFutureScheduledContentsOnly
|
|
33
|
+
this.isSingle = isSingle
|
|
34
|
+
this.allowsPullSongsContent = allowsPullSongsContent
|
|
35
|
+
this.filter = filter
|
|
36
|
+
// this.debug = process.env.DEBUG === 'true' || false;
|
|
37
|
+
this.debug = false
|
|
38
|
+
this.prefix = isChildrenFilter ? '@->' : ''
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static withOnlyFilterAvailableStatuses(filter, availableContentStatuses, bypassPermissions) {
|
|
42
|
+
return new FilterBuilder(filter, {
|
|
43
|
+
availableContentStatuses,
|
|
44
|
+
bypassPermissions,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async buildFilter() {
|
|
49
|
+
this.userData = await fetchUserPermissions()
|
|
50
|
+
if (this.debug) console.log('baseFilter', this.filter)
|
|
51
|
+
const filter = this._applyContentStatuses()
|
|
52
|
+
._applyPermissions()
|
|
53
|
+
._applyPublishingDateRestrictions()
|
|
54
|
+
._trimAmpersands().filter // just in case
|
|
55
|
+
if (this.debug) console.log('finalFilter', filter)
|
|
56
|
+
return filter
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_applyContentStatuses() {
|
|
60
|
+
// This must be run before _applyPublishDateRestrictions()
|
|
61
|
+
if (this.bypassStatuses) return this
|
|
62
|
+
if (this.availableContentStatuses.length === 0) {
|
|
63
|
+
if (this.userData.isAdmin) {
|
|
64
|
+
this.availableContentStatuses = [
|
|
65
|
+
this.STATUS_DRAFT,
|
|
66
|
+
this.STATUS_SCHEDULED,
|
|
67
|
+
this.STATUS_PUBLISHED,
|
|
68
|
+
this.STATUS_ARCHIVED,
|
|
69
|
+
this.STATUS_UNLISTED,
|
|
70
|
+
]
|
|
71
|
+
this.getFutureScheduledContentsOnly = true
|
|
72
|
+
} else if (this.isSingle) {
|
|
73
|
+
this.availableContentStatuses = [
|
|
74
|
+
this.STATUS_SCHEDULED,
|
|
75
|
+
this.STATUS_PUBLISHED,
|
|
76
|
+
this.STATUS_UNLISTED,
|
|
77
|
+
this.STATUS_ARCHIVED,
|
|
78
|
+
]
|
|
79
|
+
} else {
|
|
80
|
+
this.availableContentStatuses = [this.STATUS_SCHEDULED, this.STATUS_PUBLISHED]
|
|
81
|
+
this.getFutureScheduledContentsOnly = true
|
|
82
|
+
}
|
|
102
83
|
}
|
|
103
84
|
|
|
104
|
-
|
|
105
|
-
|
|
85
|
+
// I'm not sure if I'm 100% on this logic, but this is my intepretation of the ContentRepository logic
|
|
86
|
+
if (
|
|
87
|
+
this.getFutureScheduledContentsOnly &&
|
|
88
|
+
this.availableContentStatuses.includes(this.STATUS_SCHEDULED)
|
|
89
|
+
) {
|
|
90
|
+
// we must pull in future content here, otherwise we'll restrict on content this is published in the past and remove any scheduled content
|
|
91
|
+
this.pullFutureContent = true
|
|
92
|
+
const now = new Date().toISOString()
|
|
93
|
+
let statuses = [...this.availableContentStatuses]
|
|
94
|
+
statuses.splice(statuses.indexOf(this.STATUS_SCHEDULED), 1)
|
|
95
|
+
this._andWhere(
|
|
96
|
+
`(${this.prefix}status in ${arrayToStringRepresentation(statuses)} || (${this.prefix}status == '${this.STATUS_SCHEDULED}' && defined(${this.prefix}published_on) && ${this.prefix}published_on >= '${now}'))`
|
|
97
|
+
)
|
|
98
|
+
} else {
|
|
99
|
+
this._andWhere(
|
|
100
|
+
`${this.prefix}status in ${arrayToStringRepresentation(this.availableContentStatuses)}`
|
|
101
|
+
)
|
|
106
102
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
// when the new content is available.
|
|
116
|
-
// Round to the start of the current hour
|
|
117
|
-
const roundedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours());
|
|
118
|
-
|
|
119
|
-
now = roundedDate.toISOString();
|
|
120
|
-
|
|
121
|
-
if (this.getFutureContentOnly) {
|
|
122
|
-
this._andWhere(`${this.prefix}published_on >= '${now}'`);
|
|
123
|
-
} else if (!this.pullFutureContent) {
|
|
124
|
-
this._andWhere(`${this.prefix}published_on <= '${now}'`);
|
|
125
|
-
} else {
|
|
126
|
-
// const date = new Date();
|
|
127
|
-
// const theFuture = new Date(date.setMonth(date.getMonth() + 18));
|
|
128
|
-
// this._andWhere(`published_on <= '${theFuture}'`);
|
|
129
|
-
}
|
|
130
|
-
return this;
|
|
103
|
+
return this
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_applyPermissions() {
|
|
107
|
+
if (this.bypassPermissions || this.userData.isAdmin) return this
|
|
108
|
+
let requiredPermissions = this._getUserPermissions()
|
|
109
|
+
if (this.userData.isABasicMember && this.allowsPullSongsContent) {
|
|
110
|
+
requiredPermissions = [...requiredPermissions, songAccessMembership]
|
|
131
111
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
112
|
+
this._andWhere(
|
|
113
|
+
`(!defined(permission) || references(*[_type == 'permission' && railcontent_id in ${arrayToRawRepresentation(requiredPermissions)}]._id))`
|
|
114
|
+
)
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_getUserPermissions() {
|
|
119
|
+
return this.userData.permissions
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_applyPublishingDateRestrictions() {
|
|
123
|
+
if (this.bypassPublishedDateRestriction) return this
|
|
124
|
+
let now = new Date()
|
|
125
|
+
|
|
126
|
+
// We need to set the published on filter date to be a round time so that it doesn't bypass the query cache
|
|
127
|
+
// with every request by changing the filter date every second. I've set it to one minute past the current hour
|
|
128
|
+
// because publishing usually publishes content on the hour exactly which means it should still skip the cache
|
|
129
|
+
// when the new content is available.
|
|
130
|
+
// Round to the start of the current hour
|
|
131
|
+
const roundedDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours())
|
|
132
|
+
|
|
133
|
+
now = roundedDate.toISOString()
|
|
134
|
+
|
|
135
|
+
if (this.getFutureContentOnly) {
|
|
136
|
+
this._andWhere(`${this.prefix}published_on >= '${now}'`)
|
|
137
|
+
} else if (!this.pullFutureContent) {
|
|
138
|
+
this._andWhere(`${this.prefix}published_on <= '${now}'`)
|
|
139
|
+
} else {
|
|
140
|
+
// const date = new Date();
|
|
141
|
+
// const theFuture = new Date(date.setMonth(date.getMonth() + 18));
|
|
142
|
+
// this._andWhere(`published_on <= '${theFuture}'`);
|
|
141
143
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
return this
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_andWhere(query) {
|
|
148
|
+
const leadingAmpersand = this.filter ? ' && ' : ''
|
|
149
|
+
this.filter += leadingAmpersand + query
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_orWhere(query) {
|
|
153
|
+
if (!this.filter)
|
|
154
|
+
throw new Error('invalid query, _orWhere needs to be called on an existing query')
|
|
155
|
+
this.filter += ` || (${query})`
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_trimAmpersands() {
|
|
159
|
+
this.filter = this.filter.trim()
|
|
160
|
+
while (this.filter.charAt(0) === '&' || this.filter.charAt(0) === ' ')
|
|
161
|
+
this.filter = this.filter.substring(1)
|
|
162
|
+
while (
|
|
163
|
+
this.filter.charAt(this.filter.length) === '&' ||
|
|
164
|
+
this.filter.charAt(this.filter.length) === ' '
|
|
165
|
+
)
|
|
166
|
+
this.filter = this.filter.slice(-1)
|
|
167
|
+
return this
|
|
168
|
+
}
|
|
151
169
|
}
|
|
152
170
|
|
|
153
171
|
export function arrayToStringRepresentation(arr) {
|
|
154
|
-
|
|
172
|
+
return '[' + arr.map((item) => `'${item}'`).join(',') + ']'
|
|
155
173
|
}
|
|
156
174
|
|
|
157
175
|
export function arrayToRawRepresentation(arr) {
|
|
158
|
-
|
|
176
|
+
return '[' + arr.map((item) => `${item}`).join(',') + ']'
|
|
159
177
|
}
|