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