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