Playlist server API
[oweals/peertube.git] / server / models / video / video-playlist-element.ts
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
new file mode 100644 (file)
index 0000000..d76149d
--- /dev/null
@@ -0,0 +1,231 @@
+import {
+  AllowNull,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  DataType,
+  Default,
+  ForeignKey,
+  Is,
+  IsInt,
+  Min,
+  Model,
+  Table,
+  UpdatedAt
+} from 'sequelize-typescript'
+import { VideoModel } from './video'
+import { VideoPlaylistModel } from './video-playlist'
+import * as Sequelize from 'sequelize'
+import { getSort, throwIfNotValid } from '../utils'
+import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
+import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
+
+@Table({
+  tableName: 'videoPlaylistElement',
+  indexes: [
+    {
+      fields: [ 'videoPlaylistId' ]
+    },
+    {
+      fields: [ 'videoId' ]
+    },
+    {
+      fields: [ 'videoPlaylistId', 'videoId' ],
+      unique: true
+    },
+    {
+      fields: [ 'videoPlaylistId', 'position' ],
+      unique: true
+    },
+    {
+      fields: [ 'url' ],
+      unique: true
+    }
+  ]
+})
+export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> {
+  @CreatedAt
+  createdAt: Date
+
+  @UpdatedAt
+  updatedAt: Date
+
+  @AllowNull(false)
+  @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max))
+  url: string
+
+  @AllowNull(false)
+  @Default(1)
+  @IsInt
+  @Min(1)
+  @Column
+  position: number
+
+  @AllowNull(true)
+  @IsInt
+  @Min(0)
+  @Column
+  startTimestamp: number
+
+  @AllowNull(true)
+  @IsInt
+  @Min(0)
+  @Column
+  stopTimestamp: number
+
+  @ForeignKey(() => VideoPlaylistModel)
+  @Column
+  videoPlaylistId: number
+
+  @BelongsTo(() => VideoPlaylistModel, {
+    foreignKey: {
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+  VideoPlaylist: VideoPlaylistModel
+
+  @ForeignKey(() => VideoModel)
+  @Column
+  videoId: number
+
+  @BelongsTo(() => VideoModel, {
+    foreignKey: {
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+  Video: VideoModel
+
+  static deleteAllOf (videoPlaylistId: number, transaction?: Sequelize.Transaction) {
+    const query = {
+      where: {
+        videoPlaylistId
+      },
+      transaction
+    }
+
+    return VideoPlaylistElementModel.destroy(query)
+  }
+
+  static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) {
+    const query = {
+      where: {
+        videoPlaylistId,
+        videoId
+      }
+    }
+
+    return VideoPlaylistElementModel.findOne(query)
+  }
+
+  static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) {
+    const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
+    const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
+
+    const query = {
+      include: [
+        {
+          attributes: [ 'privacy' ],
+          model: VideoPlaylistModel.unscoped(),
+          where: playlistWhere
+        },
+        {
+          attributes: [ 'url' ],
+          model: VideoModel.unscoped(),
+          where: videoWhere
+        }
+      ]
+    }
+
+    return VideoPlaylistElementModel.findOne(query)
+  }
+
+  static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number) {
+    const query = {
+      attributes: [ 'url' ],
+      offset: start,
+      limit: count,
+      order: getSort('position'),
+      where: {
+        videoPlaylistId
+      }
+    }
+
+    return VideoPlaylistElementModel
+      .findAndCountAll(query)
+      .then(({ rows, count }) => {
+        return { total: count, data: rows.map(e => e.url) }
+      })
+  }
+
+  static getNextPositionOf (videoPlaylistId: number, transaction?: Sequelize.Transaction) {
+    const query = {
+      where: {
+        videoPlaylistId
+      },
+      transaction
+    }
+
+    return VideoPlaylistElementModel.max('position', query)
+      .then(position => position ? position + 1 : 1)
+  }
+
+  static reassignPositionOf (
+    videoPlaylistId: number,
+    firstPosition: number,
+    endPosition: number,
+    newPosition: number,
+    transaction?: Sequelize.Transaction
+  ) {
+    const query = {
+      where: {
+        videoPlaylistId,
+        position: {
+          [Sequelize.Op.gte]: firstPosition,
+          [Sequelize.Op.lte]: endPosition
+        }
+      },
+      transaction
+    }
+
+    return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query)
+  }
+
+  static increasePositionOf (
+    videoPlaylistId: number,
+    fromPosition: number,
+    toPosition?: number,
+    by = 1,
+    transaction?: Sequelize.Transaction
+  ) {
+    const query = {
+      where: {
+        videoPlaylistId,
+        position: {
+          [Sequelize.Op.gte]: fromPosition
+        }
+      },
+      transaction
+    }
+
+    return VideoPlaylistElementModel.increment({ position: by }, query)
+  }
+
+  toActivityPubObject (): PlaylistElementObject {
+    const base: PlaylistElementObject = {
+      id: this.url,
+      type: 'PlaylistElement',
+
+      url: this.Video.url,
+      position: this.position
+    }
+
+    if (this.startTimestamp) base.startTimestamp = this.startTimestamp
+    if (this.stopTimestamp) base.stopTimestamp = this.stopTimestamp
+
+    return base
+  }
+}