aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-08-16 15:25:20 +0200
committerChocobozzz <me@florianbigard.com>2018-08-27 09:41:54 +0200
commit06a05d5f4784a7cbb27aa1188385b5679845dad8 (patch)
treeac197f3ed0768529456225ad76c912f22bc55e29 /server/models
parent4bda2e47bbc937c401ddcf14c1be53c70481a294 (diff)
downloadPeerTube-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.ts93
-rw-r--r--server/models/video/video-channel.ts41
-rw-r--r--server/models/video/video.ts31
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'
2import { values } from 'lodash' 2import { values } from 'lodash'
3import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
4import { 4import {
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'
8import { FollowState } from '../../../shared/models/actors' 21import { FollowState } from '../../../shared/models/actors'
9import { AccountFollow } from '../../../shared/models/actors/follow.model' 22import { AccountFollow } from '../../../shared/models/actors/follow.model'
@@ -14,6 +27,7 @@ import { FOLLOW_STATES } from '../../initializers/constants'
14import { ServerModel } from '../server/server' 27import { ServerModel } from '../server/server'
15import { getSort } from '../utils' 28import { getSort } from '../utils'
16import { ActorModel } from './actor' 29import { ActorModel } from './actor'
30import { 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 @@
1import { 1import {
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'
5import { ActivityPubActor } from '../../../shared/models/activitypub' 18import { ActivityPubActor } from '../../../shared/models/activitypub'
6import { VideoChannel } from '../../../shared/models/videos' 19import { VideoChannel } from '../../../shared/models/videos'
7import { 20import {
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'
11import { logger } from '../../helpers/logger'
12import { sendDeleteActor } from '../../lib/activitypub/send' 25import { sendDeleteActor } from '../../lib/activitypub/send'
13import { AccountModel } from '../account/account' 26import { AccountModel } from '../account/account'
14import { ActorModel } from '../activitypub/actor' 27import { 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
134type AvailableForListOptions = { 134type 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,