Split types and typings
[oweals/peertube.git] / server / models / video / video-share.ts
1 import * as Bluebird from 'bluebird'
2 import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
4 import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5 import { ActorModel } from '../activitypub/actor'
6 import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
7 import { VideoModel } from './video'
8 import { literal, Op, Transaction } from 'sequelize'
9 import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
10 import { MActorDefault } from '../../types/models'
11
12 enum ScopeNames {
13   FULL = 'FULL',
14   WITH_ACTOR = 'WITH_ACTOR'
15 }
16
17 @Scopes(() => ({
18   [ScopeNames.FULL]: {
19     include: [
20       {
21         model: ActorModel,
22         required: true
23       },
24       {
25         model: VideoModel,
26         required: true
27       }
28     ]
29   },
30   [ScopeNames.WITH_ACTOR]: {
31     include: [
32       {
33         model: ActorModel,
34         required: true
35       }
36     ]
37   }
38 }))
39 @Table({
40   tableName: 'videoShare',
41   indexes: [
42     {
43       fields: [ 'actorId' ]
44     },
45     {
46       fields: [ 'videoId' ]
47     },
48     {
49       fields: [ 'url' ],
50       unique: true
51     }
52   ]
53 })
54 export class VideoShareModel extends Model<VideoShareModel> {
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
61   @CreatedAt
62   createdAt: Date
63
64   @UpdatedAt
65   updatedAt: Date
66
67   @ForeignKey(() => ActorModel)
68   @Column
69   actorId: number
70
71   @BelongsTo(() => ActorModel, {
72     foreignKey: {
73       allowNull: false
74     },
75     onDelete: 'cascade'
76   })
77   Actor: ActorModel
78
79   @ForeignKey(() => VideoModel)
80   @Column
81   videoId: number
82
83   @BelongsTo(() => VideoModel, {
84     foreignKey: {
85       allowNull: false
86     },
87     onDelete: 'cascade'
88   })
89   Video: VideoModel
90
91   static load (actorId: number | string, videoId: number | string, t?: Transaction): Bluebird<MVideoShareActor> {
92     return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
93       where: {
94         actorId,
95         videoId
96       },
97       transaction: t
98     })
99   }
100
101   static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> {
102     return VideoShareModel.scope(ScopeNames.FULL).findOne({
103       where: {
104         url
105       },
106       transaction: t
107     })
108   }
109
110   static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> {
111     const query = {
112       where: {
113         videoId
114       },
115       include: [
116         {
117           model: ActorModel,
118           required: true
119         }
120       ],
121       transaction: t
122     }
123
124     return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
125                           .then((res: MVideoShareFull[]) => res.map(r => r.Actor))
126   }
127
128   static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> {
129     const safeOwnerId = parseInt(actorOwnerId + '', 10)
130
131     // /!\ On actor model
132     const query = {
133       where: {
134         [Op.and]: [
135           literal(
136             `EXISTS (` +
137             `  SELECT 1 FROM "videoShare" ` +
138             `  INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
139             `  INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ` +
140             `  INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ` +
141             `  WHERE "videoShare"."actorId" = "ActorModel"."id" AND "account"."actorId" = ${safeOwnerId} ` +
142             `  LIMIT 1` +
143             `)`
144           )
145         ]
146       },
147       transaction: t
148     }
149
150     return ActorModel.findAll(query)
151   }
152
153   static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> {
154     const safeChannelId = parseInt(videoChannelId + '', 10)
155
156     // /!\ On actor model
157     const query = {
158       where: {
159         [Op.and]: [
160           literal(
161             `EXISTS (` +
162             `  SELECT 1 FROM "videoShare" ` +
163             `  INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` +
164             `  WHERE "videoShare"."actorId" = "ActorModel"."id" AND "video"."channelId" = ${safeChannelId} ` +
165             `  LIMIT 1` +
166             `)`
167           )
168         ]
169       },
170       transaction: t
171     }
172
173     return ActorModel.findAll(query)
174   }
175
176   static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) {
177     const query = {
178       offset: start,
179       limit: count,
180       where: {
181         videoId
182       },
183       transaction: t
184     }
185
186     return VideoShareModel.findAndCountAll(query)
187   }
188
189   static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) {
190     const query = {
191       where: {
192         updatedAt: {
193           [Op.lt]: beforeUpdatedAt
194         },
195         videoId,
196         actorId: {
197           [Op.notIn]: buildLocalActorIdsIn()
198         }
199       }
200     }
201
202     return VideoShareModel.destroy(query)
203   }
204 }