diff options
author | Chocobozzz <me@florianbigard.com> | 2018-08-16 15:25:20 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-08-27 09:41:54 +0200 |
commit | 06a05d5f4784a7cbb27aa1188385b5679845dad8 (patch) | |
tree | ac197f3ed0768529456225ad76c912f22bc55e29 /server/models | |
parent | 4bda2e47bbc937c401ddcf14c1be53c70481a294 (diff) | |
download | PeerTube-06a05d5f4784a7cbb27aa1188385b5679845dad8.tar.gz PeerTube-06a05d5f4784a7cbb27aa1188385b5679845dad8.tar.zst PeerTube-06a05d5f4784a7cbb27aa1188385b5679845dad8.zip |
Add subscriptions endpoints to REST API
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/activitypub/actor-follow.ts | 93 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 41 | ||||
-rw-r--r-- | server/models/video/video.ts | 31 |
3 files changed, 132 insertions, 33 deletions
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index adec5e92b..90a8ac43c 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -2,8 +2,21 @@ import * as Bluebird from 'bluebird' | |||
2 | import { values } from 'lodash' | 2 | import { values } from 'lodash' |
3 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
4 | import { | 4 | import { |
5 | AfterCreate, AfterDestroy, AfterUpdate, AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, IsInt, Max, Model, | 5 | AfterCreate, |
6 | Table, UpdatedAt | 6 | AfterDestroy, |
7 | AfterUpdate, | ||
8 | AllowNull, | ||
9 | BelongsTo, | ||
10 | Column, | ||
11 | CreatedAt, | ||
12 | DataType, | ||
13 | Default, | ||
14 | ForeignKey, | ||
15 | IsInt, | ||
16 | Max, | ||
17 | Model, | ||
18 | Table, | ||
19 | UpdatedAt | ||
7 | } from 'sequelize-typescript' | 20 | } from 'sequelize-typescript' |
8 | import { FollowState } from '../../../shared/models/actors' | 21 | import { FollowState } from '../../../shared/models/actors' |
9 | import { AccountFollow } from '../../../shared/models/actors/follow.model' | 22 | import { AccountFollow } from '../../../shared/models/actors/follow.model' |
@@ -14,6 +27,7 @@ import { FOLLOW_STATES } from '../../initializers/constants' | |||
14 | import { ServerModel } from '../server/server' | 27 | import { ServerModel } from '../server/server' |
15 | import { getSort } from '../utils' | 28 | import { getSort } from '../utils' |
16 | import { ActorModel } from './actor' | 29 | import { ActorModel } from './actor' |
30 | import { VideoChannelModel } from '../video/video-channel' | ||
17 | 31 | ||
18 | @Table({ | 32 | @Table({ |
19 | tableName: 'actorFollow', | 33 | tableName: 'actorFollow', |
@@ -151,7 +165,32 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
151 | return ActorFollowModel.findOne(query) | 165 | return ActorFollowModel.findOne(query) |
152 | } | 166 | } |
153 | 167 | ||
154 | static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) { | 168 | static loadByActorAndTargetNameAndHost (actorId: number, targetName: string, targetHost: string, t?: Sequelize.Transaction) { |
169 | const actorFollowingPartInclude = { | ||
170 | model: ActorModel, | ||
171 | required: true, | ||
172 | as: 'ActorFollowing', | ||
173 | where: { | ||
174 | preferredUsername: targetName | ||
175 | } | ||
176 | } | ||
177 | |||
178 | if (targetHost === null) { | ||
179 | actorFollowingPartInclude.where['serverId'] = null | ||
180 | } else { | ||
181 | Object.assign(actorFollowingPartInclude, { | ||
182 | include: [ | ||
183 | { | ||
184 | model: ServerModel, | ||
185 | required: true, | ||
186 | where: { | ||
187 | host: targetHost | ||
188 | } | ||
189 | } | ||
190 | ] | ||
191 | }) | ||
192 | } | ||
193 | |||
155 | const query = { | 194 | const query = { |
156 | where: { | 195 | where: { |
157 | actorId | 196 | actorId |
@@ -162,20 +201,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
162 | required: true, | 201 | required: true, |
163 | as: 'ActorFollower' | 202 | as: 'ActorFollower' |
164 | }, | 203 | }, |
165 | { | 204 | actorFollowingPartInclude |
166 | model: ActorModel, | ||
167 | required: true, | ||
168 | as: 'ActorFollowing', | ||
169 | include: [ | ||
170 | { | ||
171 | model: ServerModel, | ||
172 | required: true, | ||
173 | where: { | ||
174 | host: targetHost | ||
175 | } | ||
176 | } | ||
177 | ] | ||
178 | } | ||
179 | ], | 205 | ], |
180 | transaction: t | 206 | transaction: t |
181 | } | 207 | } |
@@ -216,6 +242,39 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
216 | }) | 242 | }) |
217 | } | 243 | } |
218 | 244 | ||
245 | static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) { | ||
246 | const query = { | ||
247 | distinct: true, | ||
248 | offset: start, | ||
249 | limit: count, | ||
250 | order: getSort(sort), | ||
251 | where: { | ||
252 | actorId: id | ||
253 | }, | ||
254 | include: [ | ||
255 | { | ||
256 | model: ActorModel, | ||
257 | as: 'ActorFollowing', | ||
258 | required: true, | ||
259 | include: [ | ||
260 | { | ||
261 | model: VideoChannelModel, | ||
262 | required: true | ||
263 | } | ||
264 | ] | ||
265 | } | ||
266 | ] | ||
267 | } | ||
268 | |||
269 | return ActorFollowModel.findAndCountAll(query) | ||
270 | .then(({ rows, count }) => { | ||
271 | return { | ||
272 | data: rows.map(r => r.ActorFollowing.VideoChannel), | ||
273 | total: count | ||
274 | } | ||
275 | }) | ||
276 | } | ||
277 | |||
219 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { | 278 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { |
220 | const query = { | 279 | const query = { |
221 | distinct: true, | 280 | distinct: true, |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index d0dba18d5..0273fab13 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -1,14 +1,27 @@ | |||
1 | import { | 1 | import { |
2 | AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Is, Model, Scopes, Table, | 2 | AllowNull, |
3 | UpdatedAt, Default, DataType | 3 | BeforeDestroy, |
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | Default, | ||
9 | DefaultScope, | ||
10 | ForeignKey, | ||
11 | HasMany, | ||
12 | Is, | ||
13 | Model, | ||
14 | Scopes, | ||
15 | Table, | ||
16 | UpdatedAt | ||
4 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
5 | import { ActivityPubActor } from '../../../shared/models/activitypub' | 18 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
6 | import { VideoChannel } from '../../../shared/models/videos' | 19 | import { VideoChannel } from '../../../shared/models/videos' |
7 | import { | 20 | import { |
8 | isVideoChannelDescriptionValid, isVideoChannelNameValid, | 21 | isVideoChannelDescriptionValid, |
22 | isVideoChannelNameValid, | ||
9 | isVideoChannelSupportValid | 23 | isVideoChannelSupportValid |
10 | } from '../../helpers/custom-validators/video-channels' | 24 | } from '../../helpers/custom-validators/video-channels' |
11 | import { logger } from '../../helpers/logger' | ||
12 | import { sendDeleteActor } from '../../lib/activitypub/send' | 25 | import { sendDeleteActor } from '../../lib/activitypub/send' |
13 | import { AccountModel } from '../account/account' | 26 | import { AccountModel } from '../account/account' |
14 | import { ActorModel } from '../activitypub/actor' | 27 | import { ActorModel } from '../activitypub/actor' |
@@ -241,6 +254,23 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
241 | .findById(id, options) | 254 | .findById(id, options) |
242 | } | 255 | } |
243 | 256 | ||
257 | static loadLocalByName (name: string) { | ||
258 | const query = { | ||
259 | include: [ | ||
260 | { | ||
261 | model: ActorModel, | ||
262 | required: true, | ||
263 | where: { | ||
264 | preferredUsername: name, | ||
265 | serverId: null | ||
266 | } | ||
267 | } | ||
268 | ] | ||
269 | } | ||
270 | |||
271 | return VideoChannelModel.findOne(query) | ||
272 | } | ||
273 | |||
244 | toFormattedJSON (): VideoChannel { | 274 | toFormattedJSON (): VideoChannel { |
245 | const actor = this.Actor.toFormattedJSON() | 275 | const actor = this.Actor.toFormattedJSON() |
246 | const videoChannel = { | 276 | const videoChannel = { |
@@ -251,8 +281,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
251 | isLocal: this.Actor.isOwned(), | 281 | isLocal: this.Actor.isOwned(), |
252 | createdAt: this.createdAt, | 282 | createdAt: this.createdAt, |
253 | updatedAt: this.updatedAt, | 283 | updatedAt: this.updatedAt, |
254 | ownerAccount: undefined, | 284 | ownerAccount: undefined |
255 | videos: undefined | ||
256 | } | 285 | } |
257 | 286 | ||
258 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() | 287 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b13dee403..5db718061 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -133,6 +133,7 @@ export enum ScopeNames { | |||
133 | 133 | ||
134 | type AvailableForListOptions = { | 134 | type AvailableForListOptions = { |
135 | actorId: number, | 135 | actorId: number, |
136 | includeLocalVideos: boolean, | ||
136 | filter?: VideoFilter, | 137 | filter?: VideoFilter, |
137 | categoryOneOf?: number[], | 138 | categoryOneOf?: number[], |
138 | nsfw?: boolean, | 139 | nsfw?: boolean, |
@@ -201,6 +202,15 @@ type AvailableForListOptions = { | |||
201 | 202 | ||
202 | // Force actorId to be a number to avoid SQL injections | 203 | // Force actorId to be a number to avoid SQL injections |
203 | const actorIdNumber = parseInt(options.actorId.toString(), 10) | 204 | const actorIdNumber = parseInt(options.actorId.toString(), 10) |
205 | let localVideosReq = '' | ||
206 | if (options.includeLocalVideos === true) { | ||
207 | localVideosReq = ' UNION ALL ' + | ||
208 | 'SELECT "video"."id" AS "id" FROM "video" ' + | ||
209 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
210 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
211 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + | ||
212 | 'WHERE "actor"."serverId" IS NULL' | ||
213 | } | ||
204 | 214 | ||
205 | // FIXME: It would be more efficient to use a CTE so we join AFTER the filters, but sequelize does not support it... | 215 | // FIXME: It would be more efficient to use a CTE so we join AFTER the filters, but sequelize does not support it... |
206 | const query: IFindOptions<VideoModel> = { | 216 | const query: IFindOptions<VideoModel> = { |
@@ -214,12 +224,6 @@ type AvailableForListOptions = { | |||
214 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | 224 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + |
215 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 225 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
216 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | 226 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + |
217 | ' UNION ' + | ||
218 | 'SELECT "video"."id" AS "id" FROM "video" ' + | ||
219 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
220 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
221 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + | ||
222 | 'WHERE "actor"."serverId" IS NULL ' + | ||
223 | ' UNION ALL ' + | 227 | ' UNION ALL ' + |
224 | 'SELECT "video"."id" AS "id" FROM "video" ' + | 228 | 'SELECT "video"."id" AS "id" FROM "video" ' + |
225 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 229 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
@@ -227,6 +231,7 @@ type AvailableForListOptions = { | |||
227 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + | 231 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + |
228 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + | 232 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + |
229 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | 233 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + |
234 | localVideosReq + | ||
230 | ')' | 235 | ')' |
231 | ) | 236 | ) |
232 | }, | 237 | }, |
@@ -825,6 +830,7 @@ export class VideoModel extends Model<VideoModel> { | |||
825 | count: number, | 830 | count: number, |
826 | sort: string, | 831 | sort: string, |
827 | nsfw: boolean, | 832 | nsfw: boolean, |
833 | includeLocalVideos: boolean, | ||
828 | withFiles: boolean, | 834 | withFiles: boolean, |
829 | categoryOneOf?: number[], | 835 | categoryOneOf?: number[], |
830 | licenceOneOf?: number[], | 836 | licenceOneOf?: number[], |
@@ -833,7 +839,8 @@ export class VideoModel extends Model<VideoModel> { | |||
833 | tagsAllOf?: string[], | 839 | tagsAllOf?: string[], |
834 | filter?: VideoFilter, | 840 | filter?: VideoFilter, |
835 | accountId?: number, | 841 | accountId?: number, |
836 | videoChannelId?: number | 842 | videoChannelId?: number, |
843 | actorId?: number | ||
837 | }) { | 844 | }) { |
838 | const query = { | 845 | const query = { |
839 | offset: options.start, | 846 | offset: options.start, |
@@ -841,11 +848,12 @@ export class VideoModel extends Model<VideoModel> { | |||
841 | order: getSort(options.sort) | 848 | order: getSort(options.sort) |
842 | } | 849 | } |
843 | 850 | ||
844 | const serverActor = await getServerActor() | 851 | const actorId = options.actorId || (await getServerActor()).id |
852 | |||
845 | const scopes = { | 853 | const scopes = { |
846 | method: [ | 854 | method: [ |
847 | ScopeNames.AVAILABLE_FOR_LIST, { | 855 | ScopeNames.AVAILABLE_FOR_LIST, { |
848 | actorId: serverActor.id, | 856 | actorId, |
849 | nsfw: options.nsfw, | 857 | nsfw: options.nsfw, |
850 | categoryOneOf: options.categoryOneOf, | 858 | categoryOneOf: options.categoryOneOf, |
851 | licenceOneOf: options.licenceOneOf, | 859 | licenceOneOf: options.licenceOneOf, |
@@ -855,7 +863,8 @@ export class VideoModel extends Model<VideoModel> { | |||
855 | filter: options.filter, | 863 | filter: options.filter, |
856 | withFiles: options.withFiles, | 864 | withFiles: options.withFiles, |
857 | accountId: options.accountId, | 865 | accountId: options.accountId, |
858 | videoChannelId: options.videoChannelId | 866 | videoChannelId: options.videoChannelId, |
867 | includeLocalVideos: options.includeLocalVideos | ||
859 | } as AvailableForListOptions | 868 | } as AvailableForListOptions |
860 | ] | 869 | ] |
861 | } | 870 | } |
@@ -871,6 +880,7 @@ export class VideoModel extends Model<VideoModel> { | |||
871 | } | 880 | } |
872 | 881 | ||
873 | static async searchAndPopulateAccountAndServer (options: { | 882 | static async searchAndPopulateAccountAndServer (options: { |
883 | includeLocalVideos: boolean | ||
874 | search?: string | 884 | search?: string |
875 | start?: number | 885 | start?: number |
876 | count?: number | 886 | count?: number |
@@ -955,6 +965,7 @@ export class VideoModel extends Model<VideoModel> { | |||
955 | method: [ | 965 | method: [ |
956 | ScopeNames.AVAILABLE_FOR_LIST, { | 966 | ScopeNames.AVAILABLE_FOR_LIST, { |
957 | actorId: serverActor.id, | 967 | actorId: serverActor.id, |
968 | includeLocalVideos: options.includeLocalVideos, | ||
958 | nsfw: options.nsfw, | 969 | nsfw: options.nsfw, |
959 | categoryOneOf: options.categoryOneOf, | 970 | categoryOneOf: options.categoryOneOf, |
960 | licenceOneOf: options.licenceOneOf, | 971 | licenceOneOf: options.licenceOneOf, |