X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-share.ts;h=99a24dbdf5df717dbe0e6b6290ce1e750e2d85ee;hb=7d9ba5c08999c6482f0bc5e0c09c6f55b7724090;hp=37e405fa9810dd519dbcd02ea5d1d31961cf9fc7;hpb=25ed141c7c7631ef21d8764c1163fbf8a6591391;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index 37e405fa9..99a24dbdf 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -1,84 +1,214 @@ -import * as Sequelize from 'sequelize' +import { literal, Op, QueryTypes, Transaction } from 'sequelize' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' +import { CONSTRAINTS_FIELDS } from '../../initializers/constants' +import { MActorDefault } from '../../types/models' +import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' +import { ActorModel } from '../actor/actor' +import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' +import { VideoModel } from './video' -import { addMethodsToModel } from '../utils' -import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './video-share-interface' - -let VideoShare: Sequelize.Model -let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare -let load: VideoShareMethods.Load +enum ScopeNames { + FULL = 'FULL', + WITH_ACTOR = 'WITH_ACTOR' +} -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoShare = sequelize.define('VideoShare', - { }, +@Scopes(() => ({ + [ScopeNames.FULL]: { + include: [ + { + model: ActorModel, + required: true + }, + { + model: VideoModel, + required: true + } + ] + }, + [ScopeNames.WITH_ACTOR]: { + include: [ + { + model: ActorModel, + required: true + } + ] + } +})) +@Table({ + tableName: 'videoShare', + indexes: [ { - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'videoId' ] - } - ] + fields: [ 'actorId' ] + }, + { + fields: [ 'videoId' ] + }, + { + fields: [ 'url' ], + unique: true } - ) - - const classMethods = [ - associate, - loadAccountsByShare, - load ] - addMethodsToModel(VideoShare, classMethods) +}) +export class VideoShareModel extends Model { - return VideoShare -} + @AllowNull(false) + @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_SHARE.URL.max)) + url: string + + @CreatedAt + createdAt: Date -// ------------------------------ METHODS ------------------------------ + @UpdatedAt + updatedAt: Date -function associate (models) { - VideoShare.belongsTo(models.Account, { + @ForeignKey(() => ActorModel) + @Column + actorId: number + + @BelongsTo(() => ActorModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'cascade' }) + Actor: ActorModel + + @ForeignKey(() => VideoModel) + @Column + videoId: number - VideoShare.belongsTo(models.Video, { + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', - allowNull: true + allowNull: false }, onDelete: 'cascade' }) -} + Video: VideoModel -load = function (accountId: number, videoId: number, t: Sequelize.Transaction) { - return VideoShare.findOne({ - where: { - accountId, - videoId - }, - include: [ - VideoShare['sequelize'].models.Account - ], - transaction: t - }) -} + static load (actorId: number | string, videoId: number | string, t?: Transaction): Promise { + return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ + where: { + actorId, + videoId + }, + transaction: t + }) + } -loadAccountsByShare = function (videoId: number, t: Sequelize.Transaction) { - const query = { - where: { - videoId - }, - include: [ - { - model: VideoShare['sequelize'].models.Account, - required: true - } - ], - transaction: t + static loadByUrl (url: string, t: Transaction): Promise { + return VideoShareModel.scope(ScopeNames.FULL).findOne({ + where: { + url + }, + transaction: t + }) } - return VideoShare.findAll(query) - .then(res => res.map(r => r.Account)) + static loadActorsByShare (videoId: number, t: Transaction): Promise { + const query = { + where: { + videoId + }, + include: [ + { + model: ActorModel, + required: true + } + ], + transaction: t + } + + return VideoShareModel.scope(ScopeNames.FULL).findAll(query) + .then((res: MVideoShareFull[]) => res.map(r => r.Actor)) + } + + static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise { + const safeOwnerId = parseInt(actorOwnerId + '', 10) + + // /!\ On actor model + const query = { + where: { + [Op.and]: [ + literal( + `EXISTS (` + + ` SELECT 1 FROM "videoShare" ` + + ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` + + ` INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ` + + ` INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ` + + ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "account"."actorId" = ${safeOwnerId} ` + + ` LIMIT 1` + + `)` + ) + ] + }, + transaction: t + } + + return ActorModel.findAll(query) + } + + static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Promise { + const safeChannelId = parseInt(videoChannelId + '', 10) + + // /!\ On actor model + const query = { + where: { + [Op.and]: [ + literal( + `EXISTS (` + + ` SELECT 1 FROM "videoShare" ` + + ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` + + ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "video"."channelId" = ${safeChannelId} ` + + ` LIMIT 1` + + `)` + ) + ] + }, + transaction: t + } + + return ActorModel.findAll(query) + } + + static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) { + const query = { + offset: start, + limit: count, + where: { + videoId + }, + transaction: t + } + + return VideoShareModel.findAndCountAll(query) + } + + static listRemoteShareUrlsOfLocalVideos () { + const query = `SELECT "videoShare".url FROM "videoShare" ` + + `INNER JOIN actor ON actor.id = "videoShare"."actorId" AND actor."serverId" IS NOT NULL ` + + `INNER JOIN video ON video.id = "videoShare"."videoId" AND video.remote IS FALSE` + + return VideoShareModel.sequelize.query<{ url: string }>(query, { + type: QueryTypes.SELECT, + raw: true + }).then(rows => rows.map(r => r.url)) + } + + static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) { + const query = { + where: { + updatedAt: { + [Op.lt]: beforeUpdatedAt + }, + videoId, + actorId: { + [Op.notIn]: buildLocalActorIdsIn() + } + } + } + + return VideoShareModel.destroy(query) + } }