1
+ const Path = require ( 'path' )
1
2
const { DataTypes, Model } = require ( 'sequelize' )
2
3
const oldFeed = require ( '../objects/Feed' )
3
4
const areEquivalent = require ( '../utils/areEquivalent' )
4
5
6
+ /**
7
+ * @typedef FeedOptions
8
+ * @property {boolean } preventIndexing
9
+ * @property {string } ownerName
10
+ * @property {string } ownerEmail
11
+ */
12
+
13
+ /**
14
+ * @typedef FeedExpandedProperties
15
+ * @property {import('./FeedEpisode') } feedEpisodes
16
+ *
17
+ * @typedef {Feed & FeedExpandedProperties } FeedExpanded
18
+ */
19
+
5
20
class Feed extends Model {
6
21
constructor ( values , options ) {
7
22
super ( values , options )
@@ -50,6 +65,9 @@ class Feed extends Model {
50
65
this . createdAt
51
66
/** @type {Date } */
52
67
this . updatedAt
68
+
69
+ /** @type {import('./FeedEpisode')[] } - only set if expanded */
70
+ this . feedEpisodes
53
71
}
54
72
55
73
static async getOldFeeds ( ) {
@@ -67,7 +85,15 @@ class Feed extends Model {
67
85
* @returns {oldFeed }
68
86
*/
69
87
static getOldFeed ( feedExpanded ) {
70
- const episodes = feedExpanded . feedEpisodes ?. map ( ( feedEpisode ) => feedEpisode . getOldEpisode ( ) )
88
+ const episodes = feedExpanded . feedEpisodes ?. map ( ( feedEpisode ) => feedEpisode . getOldEpisode ( ) ) || [ ]
89
+
90
+ // Sort episodes by pubDate. Newest to oldest for episodic, oldest to newest for serial
91
+ if ( feedExpanded . podcastType === 'episodic' ) {
92
+ episodes . sort ( ( a , b ) => new Date ( b . pubDate ) - new Date ( a . pubDate ) )
93
+ } else {
94
+ episodes . sort ( ( a , b ) => new Date ( a . pubDate ) - new Date ( b . pubDate ) )
95
+ }
96
+
71
97
return new oldFeed ( {
72
98
id : feedExpanded . id ,
73
99
slug : feedExpanded . slug ,
@@ -92,7 +118,7 @@ class Feed extends Model {
92
118
} ,
93
119
serverAddress : feedExpanded . serverAddress ,
94
120
feedUrl : feedExpanded . feedURL ,
95
- episodes : episodes || [ ] ,
121
+ episodes,
96
122
createdAt : feedExpanded . createdAt . valueOf ( ) ,
97
123
updatedAt : feedExpanded . updatedAt . valueOf ( )
98
124
} )
@@ -250,10 +276,62 @@ class Feed extends Model {
250
276
}
251
277
}
252
278
253
- getEntity ( options ) {
254
- if ( ! this . entityType ) return Promise . resolve ( null )
255
- const mixinMethodName = `get${ this . sequelize . uppercaseFirst ( this . entityType ) } `
256
- return this [ mixinMethodName ] ( options )
279
+ /**
280
+ *
281
+ * @param {string } userId
282
+ * @param {string } slug
283
+ * @param {import('./LibraryItem').LibraryItemExpanded } libraryItem
284
+ * @param {string } serverAddress
285
+ * @param {FeedOptions } feedOptions
286
+ *
287
+ * @returns {Promise<FeedExpanded> }
288
+ */
289
+ static async createFeedForLibraryItem ( userId , libraryItem , slug , serverAddress , feedOptions ) {
290
+ const media = libraryItem . media
291
+
292
+ const feedObj = {
293
+ slug,
294
+ entityType : 'libraryItem' ,
295
+ entityId : libraryItem . id ,
296
+ entityUpdatedAt : libraryItem . updatedAt ,
297
+ serverAddress,
298
+ feedURL : `/feed/${ slug } ` ,
299
+ imageURL : media . coverPath ? `/feed/${ slug } /cover${ Path . extname ( media . coverPath ) } ` : `/Logo.png` ,
300
+ siteURL : `/item/${ libraryItem . id } ` ,
301
+ title : media . title ,
302
+ description : media . description ,
303
+ author : libraryItem . mediaType === 'podcast' ? media . author : media . authorName ,
304
+ podcastType : libraryItem . mediaType === 'podcast' ? media . podcastType : 'serial' ,
305
+ language : media . language ,
306
+ preventIndexing : feedOptions . preventIndexing ,
307
+ ownerName : feedOptions . ownerName ,
308
+ ownerEmail : feedOptions . ownerEmail ,
309
+ explicit : media . explicit ,
310
+ coverPath : media . coverPath ,
311
+ userId
312
+ }
313
+
314
+ /** @type {typeof import('./FeedEpisode') } */
315
+ const feedEpisodeModel = this . sequelize . models . feedEpisode
316
+
317
+ const transaction = await this . sequelize . transaction ( )
318
+ try {
319
+ const feed = await this . create ( feedObj , { transaction } )
320
+
321
+ if ( libraryItem . mediaType === 'podcast' ) {
322
+ feed . feedEpisodes = await feedEpisodeModel . createFromPodcastEpisodes ( libraryItem , feed , slug , transaction )
323
+ } else {
324
+ feed . feedEpisodes = await feedEpisodeModel . createFromAudiobookTracks ( libraryItem , feed , slug , transaction )
325
+ }
326
+
327
+ await transaction . commit ( )
328
+
329
+ return feed
330
+ } catch ( error ) {
331
+ Logger . error ( `[Feed] Error creating feed for library item ${ libraryItem . id } ` , error )
332
+ await transaction . rollback ( )
333
+ return null
334
+ }
257
335
}
258
336
259
337
/**
@@ -369,6 +447,60 @@ class Feed extends Model {
369
447
}
370
448
} )
371
449
}
450
+
451
+ getEntity ( options ) {
452
+ if ( ! this . entityType ) return Promise . resolve ( null )
453
+ const mixinMethodName = `get${ this . sequelize . uppercaseFirst ( this . entityType ) } `
454
+ return this [ mixinMethodName ] ( options )
455
+ }
456
+
457
+ toOldJSON ( ) {
458
+ const episodes = this . feedEpisodes ?. map ( ( feedEpisode ) => feedEpisode . getOldEpisode ( ) )
459
+ return {
460
+ id : this . id ,
461
+ slug : this . slug ,
462
+ userId : this . userId ,
463
+ entityType : this . entityType ,
464
+ entityId : this . entityId ,
465
+ entityUpdatedAt : this . entityUpdatedAt ?. valueOf ( ) || null ,
466
+ coverPath : this . coverPath || null ,
467
+ meta : {
468
+ title : this . title ,
469
+ description : this . description ,
470
+ author : this . author ,
471
+ imageUrl : this . imageURL ,
472
+ feedUrl : this . feedURL ,
473
+ link : this . siteURL ,
474
+ explicit : this . explicit ,
475
+ type : this . podcastType ,
476
+ language : this . language ,
477
+ preventIndexing : this . preventIndexing ,
478
+ ownerName : this . ownerName ,
479
+ ownerEmail : this . ownerEmail
480
+ } ,
481
+ serverAddress : this . serverAddress ,
482
+ feedUrl : this . feedURL ,
483
+ episodes : episodes || [ ] ,
484
+ createdAt : this . createdAt . valueOf ( ) ,
485
+ updatedAt : this . updatedAt . valueOf ( )
486
+ }
487
+ }
488
+
489
+ toOldJSONMinified ( ) {
490
+ return {
491
+ id : this . id ,
492
+ entityType : this . entityType ,
493
+ entityId : this . entityId ,
494
+ feedUrl : this . feedURL ,
495
+ meta : {
496
+ title : this . title ,
497
+ description : this . description ,
498
+ preventIndexing : this . preventIndexing ,
499
+ ownerName : this . ownerName ,
500
+ ownerEmail : this . ownerEmail
501
+ }
502
+ }
503
+ }
372
504
}
373
505
374
506
module . exports = Feed
0 commit comments