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