]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/models/video/video-share.ts
Optimize broadcast job creation
[github/Chocobozzz/PeerTube.git] / server / models / video / video-share.ts
CommitLineData
74d249bc 1import { literal, Op, QueryTypes, Transaction } from 'sequelize'
4ba3b8ea 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
6b5f72be 3import { AttributesOnly } from '@shared/typescript-utils'
4ba3b8ea 4import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
74dc3bca 5import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
3396e653 6import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models'
b49f22d8 7import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
7d9ba5c0 8import { ActorModel } from '../actor/actor'
6b9c966f 9import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
3fd3ab2d 10import { VideoModel } from './video'
d8465018 11
d48ff09d
C
12enum ScopeNames {
13 FULL = 'FULL',
50d6de9c 14 WITH_ACTOR = 'WITH_ACTOR'
d48ff09d
C
15}
16
3acc5084 17@Scopes(() => ({
d48ff09d
C
18 [ScopeNames.FULL]: {
19 include: [
20 {
3acc5084 21 model: ActorModel,
d48ff09d
C
22 required: true
23 },
24 {
3acc5084 25 model: VideoModel,
d48ff09d
C
26 required: true
27 }
28 ]
29 },
50d6de9c 30 [ScopeNames.WITH_ACTOR]: {
d48ff09d
C
31 include: [
32 {
3acc5084 33 model: ActorModel,
d48ff09d
C
34 required: true
35 }
36 ]
37 }
3acc5084 38}))
3fd3ab2d
C
39@Table({
40 tableName: 'videoShare',
41 indexes: [
d8465018 42 {
50d6de9c 43 fields: [ 'actorId' ]
3fd3ab2d
C
44 },
45 {
46 fields: [ 'videoId' ]
4ba3b8ea
C
47 },
48 {
49 fields: [ 'url' ],
50 unique: true
d8465018 51 }
d8465018 52 ]
3fd3ab2d 53})
16c016e8 54export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareModel>>> {
4ba3b8ea
C
55
56 @AllowNull(false)
57 @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
58 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_SHARE.URL.max))
59 url: string
60
3fd3ab2d
C
61 @CreatedAt
62 createdAt: Date
d8465018 63
3fd3ab2d
C
64 @UpdatedAt
65 updatedAt: Date
d8465018 66
50d6de9c 67 @ForeignKey(() => ActorModel)
3fd3ab2d 68 @Column
50d6de9c 69 actorId: number
d8465018 70
50d6de9c 71 @BelongsTo(() => ActorModel, {
d8465018 72 foreignKey: {
d8465018
C
73 allowNull: false
74 },
75 onDelete: 'cascade'
76 })
50d6de9c 77 Actor: ActorModel
d8465018 78
3fd3ab2d
C
79 @ForeignKey(() => VideoModel)
80 @Column
81 videoId: number
82
83 @BelongsTo(() => VideoModel, {
d8465018 84 foreignKey: {
3fd3ab2d 85 allowNull: false
d8465018
C
86 },
87 onDelete: 'cascade'
88 })
3fd3ab2d 89 Video: VideoModel
4e50b6a1 90
b49f22d8 91 static load (actorId: number | string, videoId: number | string, t?: Transaction): Promise<MVideoShareActor> {
50d6de9c 92 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
3fd3ab2d 93 where: {
50d6de9c 94 actorId,
3fd3ab2d
C
95 videoId
96 },
3fd3ab2d
C
97 transaction: t
98 })
d7d5611c
C
99 }
100
b49f22d8 101 static loadByUrl (url: string, t: Transaction): Promise<MVideoShareFull> {
9588d4f4 102 return VideoShareModel.scope(ScopeNames.FULL).findOne({
0f320037
C
103 where: {
104 url
105 },
106 transaction: t
107 })
108 }
109
3396e653
C
110 static listActorIdsAndFollowerUrlsByShare (videoId: number, t: Transaction) {
111 const query = `SELECT "actor"."id" AS "id", "actor"."followersUrl" AS "followersUrl" ` +
112 `FROM "videoShare" ` +
113 `INNER JOIN "actor" ON "actor"."id" = "videoShare"."actorId" ` +
114 `WHERE "videoShare"."videoId" = :videoId`
115
116 const options = {
117 type: QueryTypes.SELECT as QueryTypes.SELECT,
118 replacements: { videoId },
3fd3ab2d
C
119 transaction: t
120 }
121
3396e653 122 return VideoShareModel.sequelize.query<MActorId & MActorFollowersUrl>(query, options)
3fd3ab2d 123 }
265ba139 124
b49f22d8 125 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Promise<MActorDefault[]> {
891a8196
C
126 const safeOwnerId = parseInt(actorOwnerId + '', 10)
127
128 // /!\ On actor model
265ba139 129 const query = {
891a8196
C
130 where: {
131 [Op.and]: [
132 literal(
133 `EXISTS (` +
134 ` SELECT 1 FROM "videoShare" ` +
135 ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
136 ` INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ` +
137 ` INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ` +
138 ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "account"."actorId" = ${safeOwnerId} ` +
139 ` LIMIT 1` +
140 `)`
141 )
142 ]
143 },
265ba139
C
144 transaction: t
145 }
146
891a8196 147 return ActorModel.findAll(query)
265ba139 148 }
2422c46b 149
b49f22d8 150 static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Promise<MActorDefault[]> {
891a8196
C
151 const safeChannelId = parseInt(videoChannelId + '', 10)
152
153 // /!\ On actor model
2422c46b 154 const query = {
891a8196
C
155 where: {
156 [Op.and]: [
157 literal(
158 `EXISTS (` +
159 ` SELECT 1 FROM "videoShare" ` +
160 ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
161 ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "video"."channelId" = ${safeChannelId} ` +
162 ` LIMIT 1` +
163 `)`
164 )
165 ]
166 },
2422c46b
C
167 transaction: t
168 }
169
891a8196 170 return ActorModel.findAll(query)
2422c46b 171 }
8fffe21a 172
970ceac0 173 static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) {
8fffe21a 174 const query = {
9a4a9b6c
C
175 offset: start,
176 limit: count,
8fffe21a
C
177 where: {
178 videoId
179 },
180 transaction: t
181 }
182
d0800f76 183 return Promise.all([
184 VideoShareModel.count(query),
185 VideoShareModel.findAll(query)
186 ]).then(([ total, data ]) => ({ total, data }))
8fffe21a 187 }
2ba92871 188
74d249bc
C
189 static listRemoteShareUrlsOfLocalVideos () {
190 const query = `SELECT "videoShare".url FROM "videoShare" ` +
191 `INNER JOIN actor ON actor.id = "videoShare"."actorId" AND actor."serverId" IS NOT NULL ` +
192 `INNER JOIN video ON video.id = "videoShare"."videoId" AND video.remote IS FALSE`
193
194 return VideoShareModel.sequelize.query<{ url: string }>(query, {
195 type: QueryTypes.SELECT,
196 raw: true
197 }).then(rows => rows.map(r => r.url))
198 }
199
2ba92871
C
200 static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) {
201 const query = {
202 where: {
203 updatedAt: {
970ceac0 204 [Op.lt]: beforeUpdatedAt
2ba92871 205 },
6b9c966f
C
206 videoId,
207 actorId: {
208 [Op.notIn]: buildLocalActorIdsIn()
970ceac0 209 }
6b9c966f 210 }
2ba92871
C
211 }
212
213 return VideoShareModel.destroy(query)
214 }
d7d5611c 215}