Add playlist check param tests
[oweals/peertube.git] / server / models / video / video-playlist-element.ts
1 import {
2   AllowNull,
3   BelongsTo,
4   Column,
5   CreatedAt,
6   DataType,
7   Default,
8   ForeignKey,
9   Is,
10   IsInt,
11   Min,
12   Model,
13   Table,
14   UpdatedAt
15 } from 'sequelize-typescript'
16 import { VideoModel } from './video'
17 import { VideoPlaylistModel } from './video-playlist'
18 import * as Sequelize from 'sequelize'
19 import { getSort, throwIfNotValid } from '../utils'
20 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
21 import { CONSTRAINTS_FIELDS } from '../../initializers'
22 import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
23
24 @Table({
25   tableName: 'videoPlaylistElement',
26   indexes: [
27     {
28       fields: [ 'videoPlaylistId' ]
29     },
30     {
31       fields: [ 'videoId' ]
32     },
33     {
34       fields: [ 'videoPlaylistId', 'videoId' ],
35       unique: true
36     },
37     {
38       fields: [ 'videoPlaylistId', 'position' ],
39       unique: true
40     },
41     {
42       fields: [ 'url' ],
43       unique: true
44     }
45   ]
46 })
47 export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> {
48   @CreatedAt
49   createdAt: Date
50
51   @UpdatedAt
52   updatedAt: Date
53
54   @AllowNull(false)
55   @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
56   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max))
57   url: string
58
59   @AllowNull(false)
60   @Default(1)
61   @IsInt
62   @Min(1)
63   @Column
64   position: number
65
66   @AllowNull(true)
67   @IsInt
68   @Min(0)
69   @Column
70   startTimestamp: number
71
72   @AllowNull(true)
73   @IsInt
74   @Min(0)
75   @Column
76   stopTimestamp: number
77
78   @ForeignKey(() => VideoPlaylistModel)
79   @Column
80   videoPlaylistId: number
81
82   @BelongsTo(() => VideoPlaylistModel, {
83     foreignKey: {
84       allowNull: false
85     },
86     onDelete: 'CASCADE'
87   })
88   VideoPlaylist: VideoPlaylistModel
89
90   @ForeignKey(() => VideoModel)
91   @Column
92   videoId: number
93
94   @BelongsTo(() => VideoModel, {
95     foreignKey: {
96       allowNull: false
97     },
98     onDelete: 'CASCADE'
99   })
100   Video: VideoModel
101
102   static deleteAllOf (videoPlaylistId: number, transaction?: Sequelize.Transaction) {
103     const query = {
104       where: {
105         videoPlaylistId
106       },
107       transaction
108     }
109
110     return VideoPlaylistElementModel.destroy(query)
111   }
112
113   static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) {
114     const query = {
115       where: {
116         videoPlaylistId,
117         videoId
118       }
119     }
120
121     return VideoPlaylistElementModel.findOne(query)
122   }
123
124   static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) {
125     const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
126     const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
127
128     const query = {
129       include: [
130         {
131           attributes: [ 'privacy' ],
132           model: VideoPlaylistModel.unscoped(),
133           where: playlistWhere
134         },
135         {
136           attributes: [ 'url' ],
137           model: VideoModel.unscoped(),
138           where: videoWhere
139         }
140       ]
141     }
142
143     return VideoPlaylistElementModel.findOne(query)
144   }
145
146   static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number) {
147     const query = {
148       attributes: [ 'url' ],
149       offset: start,
150       limit: count,
151       order: getSort('position'),
152       where: {
153         videoPlaylistId
154       }
155     }
156
157     return VideoPlaylistElementModel
158       .findAndCountAll(query)
159       .then(({ rows, count }) => {
160         return { total: count, data: rows.map(e => e.url) }
161       })
162   }
163
164   static getNextPositionOf (videoPlaylistId: number, transaction?: Sequelize.Transaction) {
165     const query = {
166       where: {
167         videoPlaylistId
168       },
169       transaction
170     }
171
172     return VideoPlaylistElementModel.max('position', query)
173       .then(position => position ? position + 1 : 1)
174   }
175
176   static reassignPositionOf (
177     videoPlaylistId: number,
178     firstPosition: number,
179     endPosition: number,
180     newPosition: number,
181     transaction?: Sequelize.Transaction
182   ) {
183     const query = {
184       where: {
185         videoPlaylistId,
186         position: {
187           [Sequelize.Op.gte]: firstPosition,
188           [Sequelize.Op.lte]: endPosition
189         }
190       },
191       transaction,
192       validate: false // We use a literal to update the position
193     }
194
195     return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query)
196   }
197
198   static increasePositionOf (
199     videoPlaylistId: number,
200     fromPosition: number,
201     toPosition?: number,
202     by = 1,
203     transaction?: Sequelize.Transaction
204   ) {
205     const query = {
206       where: {
207         videoPlaylistId,
208         position: {
209           [Sequelize.Op.gte]: fromPosition
210         }
211       },
212       transaction
213     }
214
215     return VideoPlaylistElementModel.increment({ position: by }, query)
216   }
217
218   toActivityPubObject (): PlaylistElementObject {
219     const base: PlaylistElementObject = {
220       id: this.url,
221       type: 'PlaylistElement',
222
223       url: this.Video.url,
224       position: this.position
225     }
226
227     if (this.startTimestamp) base.startTimestamp = this.startTimestamp
228     if (this.stopTimestamp) base.stopTimestamp = this.stopTimestamp
229
230     return base
231   }
232 }