diff options
-rw-r--r-- | server/controllers/activitypub/client.ts | 6 | ||||
-rw-r--r-- | server/controllers/activitypub/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/activitypub/outbox.ts | 60 | ||||
-rw-r--r-- | server/helpers/activitypub.ts | 35 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-announce.ts | 25 | ||||
-rw-r--r-- | server/models/account/account-follow.ts | 4 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 8 | ||||
-rw-r--r-- | server/models/video/video.ts | 58 |
8 files changed, 170 insertions, 28 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 7b3921770..24c8665a5 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -6,7 +6,7 @@ import { executeIfActivityPub, localAccountValidator } from '../../middlewares' | |||
6 | import { pageToStartAndCount } from '../../helpers' | 6 | import { pageToStartAndCount } from '../../helpers' |
7 | import { AccountInstance, VideoChannelInstance } from '../../models' | 7 | import { AccountInstance, VideoChannelInstance } from '../../models' |
8 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | 8 | import { activityPubCollectionPagination } from '../../helpers/activitypub' |
9 | import { ACTIVITY_PUB } from '../../initializers/constants' | 9 | import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants' |
10 | import { asyncMiddleware } from '../../middlewares/async' | 10 | import { asyncMiddleware } from '../../middlewares/async' |
11 | import { videosGetValidator } from '../../middlewares/validators/videos' | 11 | import { videosGetValidator } from '../../middlewares/validators/videos' |
12 | import { VideoInstance } from '../../models/video/video-interface' | 12 | import { VideoInstance } from '../../models/video/video-interface' |
@@ -60,7 +60,7 @@ async function accountFollowersController (req: express.Request, res: express.Re | |||
60 | const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) | 60 | const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) |
61 | 61 | ||
62 | const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count) | 62 | const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], start, count) |
63 | const activityPubResult = activityPubCollectionPagination(req.url, page, result) | 63 | const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) |
64 | 64 | ||
65 | return res.json(activityPubResult) | 65 | return res.json(activityPubResult) |
66 | } | 66 | } |
@@ -72,7 +72,7 @@ async function accountFollowingController (req: express.Request, res: express.Re | |||
72 | const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) | 72 | const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) |
73 | 73 | ||
74 | const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count) | 74 | const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], start, count) |
75 | const activityPubResult = activityPubCollectionPagination(req.url, page, result) | 75 | const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) |
76 | 76 | ||
77 | return res.json(activityPubResult) | 77 | return res.json(activityPubResult) |
78 | } | 78 | } |
diff --git a/server/controllers/activitypub/index.ts b/server/controllers/activitypub/index.ts index c5bec6448..7e81902af 100644 --- a/server/controllers/activitypub/index.ts +++ b/server/controllers/activitypub/index.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { activityPubClientRouter } from './client' | 2 | import { activityPubClientRouter } from './client' |
3 | import { inboxRouter } from './inbox' | 3 | import { inboxRouter } from './inbox' |
4 | import { outboxRouter } from './outbox' | ||
4 | 5 | ||
5 | const activityPubRouter = express.Router() | 6 | const activityPubRouter = express.Router() |
6 | 7 | ||
7 | activityPubRouter.use('/', inboxRouter) | 8 | activityPubRouter.use('/', inboxRouter) |
9 | activityPubRouter.use('/', outboxRouter) | ||
8 | activityPubRouter.use('/', activityPubClientRouter) | 10 | activityPubRouter.use('/', activityPubClientRouter) |
9 | 11 | ||
10 | // --------------------------------------------------------------------------- | 12 | // --------------------------------------------------------------------------- |
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts new file mode 100644 index 000000000..396fa2db5 --- /dev/null +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -0,0 +1,60 @@ | |||
1 | import * as express from 'express' | ||
2 | import { Activity, ActivityAdd } from '../../../shared/models/activitypub/activity' | ||
3 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' | ||
4 | import { database as db } from '../../initializers' | ||
5 | import { addActivityData } from '../../lib/activitypub/send/send-add' | ||
6 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | ||
7 | import { announceActivityData } from '../../lib/index' | ||
8 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' | ||
9 | import { AccountInstance } from '../../models/account/account-interface' | ||
10 | import { pageToStartAndCount } from '../../helpers/core-utils' | ||
11 | import { ACTIVITY_PUB } from '../../initializers/constants' | ||
12 | |||
13 | const outboxRouter = express.Router() | ||
14 | |||
15 | outboxRouter.get('/account/:name/outbox', | ||
16 | localAccountValidator, | ||
17 | asyncMiddleware(outboxController) | ||
18 | ) | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | outboxRouter | ||
24 | } | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
29 | const account: AccountInstance = res.locals.account | ||
30 | |||
31 | const page = req.params.page || 1 | ||
32 | const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) | ||
33 | |||
34 | const data = await db.Video.listAllAndSharedByAccountForOutbox(account.id, start, count) | ||
35 | const activities: Activity[] = [] | ||
36 | |||
37 | console.log(account.url) | ||
38 | |||
39 | for (const video of data.data) { | ||
40 | const videoObject = video.toActivityPubObject() | ||
41 | let addActivity: ActivityAdd = await addActivityData(video.url, account, video, video.VideoChannel.url, videoObject) | ||
42 | |||
43 | // This is a shared video | ||
44 | if (video.VideoShare !== undefined) { | ||
45 | const url = getAnnounceActivityPubUrl(video.url, account) | ||
46 | const announceActivity = await announceActivityData(url, account, addActivity) | ||
47 | activities.push(announceActivity) | ||
48 | } else { | ||
49 | activities.push(addActivity) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | const newResult = { | ||
54 | data: activities, | ||
55 | total: data.total | ||
56 | } | ||
57 | const json = activityPubCollectionPagination(account.url + '/outbox', page, newResult) | ||
58 | |||
59 | return res.json(json).end() | ||
60 | } | ||
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 5c577bb61..04d85b8e6 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -2,6 +2,7 @@ import { Activity } from '../../shared/models/activitypub/activity' | |||
2 | import { ResultList } from '../../shared/models/result-list.model' | 2 | import { ResultList } from '../../shared/models/result-list.model' |
3 | import { AccountInstance } from '../models/account/account-interface' | 3 | import { AccountInstance } from '../models/account/account-interface' |
4 | import { signObject } from './peertube-crypto' | 4 | import { signObject } from './peertube-crypto' |
5 | import { ACTIVITY_PUB } from '../initializers/constants' | ||
5 | 6 | ||
6 | function activityPubContextify <T> (data: T) { | 7 | function activityPubContextify <T> (data: T) { |
7 | return Object.assign(data,{ | 8 | return Object.assign(data,{ |
@@ -24,20 +25,32 @@ function activityPubContextify <T> (data: T) { | |||
24 | } | 25 | } |
25 | 26 | ||
26 | function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) { | 27 | function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) { |
27 | const baseUrl = url.split('?').shift | 28 | let next: string |
29 | let prev: string | ||
30 | |||
31 | // There are more results | ||
32 | if (result.total > ((page + 1) * ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)) { | ||
33 | next = url + '?page=' + (page + 1) | ||
34 | } | ||
35 | |||
36 | if (page > 1) { | ||
37 | prev = url + '?page=' + (page - 1) | ||
38 | } | ||
39 | |||
40 | const orderedCollectionPagination = { | ||
41 | id: url + '?page=' + page, | ||
42 | type: 'OrderedCollectionPage', | ||
43 | prev, | ||
44 | next, | ||
45 | partOf: url, | ||
46 | orderedItems: result.data | ||
47 | } | ||
28 | 48 | ||
29 | const obj = { | 49 | const obj = { |
30 | id: baseUrl, | 50 | id: url, |
31 | type: 'Collection', | 51 | type: 'OrderedCollection', |
32 | totalItems: result.total, | 52 | totalItems: result.total, |
33 | first: { | 53 | orderedItems: orderedCollectionPagination |
34 | id: baseUrl + '?page=' + page, | ||
35 | type: 'CollectionPage', | ||
36 | totalItems: result.total, | ||
37 | next: baseUrl + '?page=' + (page + 1), | ||
38 | partOf: baseUrl, | ||
39 | items: result.data | ||
40 | } | ||
41 | } | 54 | } |
42 | 55 | ||
43 | return activityPubContextify(obj) | 56 | return activityPubContextify(obj) |
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index 4b3a4ef75..b8ea51bc0 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAdd } from '../../../../shared/index' | ||
3 | import { ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' | ||
2 | import { AccountInstance, VideoInstance } from '../../../models' | 4 | import { AccountInstance, VideoInstance } from '../../../models' |
3 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 5 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' |
6 | import { getAnnounceActivityPubUrl } from '../url' | ||
4 | import { broadcastToFollowers } from './misc' | 7 | import { broadcastToFollowers } from './misc' |
5 | import { addActivityData } from './send-add' | 8 | import { addActivityData } from './send-add' |
6 | import { createActivityData } from './send-create' | 9 | import { createActivityData } from './send-create' |
7 | import { getAnnounceActivityPubUrl } from '../url' | ||
8 | 10 | ||
9 | async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 11 | async function sendVideoAnnounce (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { |
10 | const url = getAnnounceActivityPubUrl(video.url, byAccount) | 12 | const url = getAnnounceActivityPubUrl(video.url, byAccount) |
@@ -24,17 +26,8 @@ async function sendVideoChannelAnnounce (byAccount: AccountInstance, videoChanne | |||
24 | return broadcastToFollowers(data, byAccount, [ byAccount ], t) | 26 | return broadcastToFollowers(data, byAccount, [ byAccount ], t) |
25 | } | 27 | } |
26 | 28 | ||
27 | // --------------------------------------------------------------------------- | 29 | async function announceActivityData (url: string, byAccount: AccountInstance, object: ActivityCreate | ActivityAdd) { |
28 | 30 | const activity: ActivityAnnounce = { | |
29 | export { | ||
30 | sendVideoAnnounce, | ||
31 | sendVideoChannelAnnounce | ||
32 | } | ||
33 | |||
34 | // --------------------------------------------------------------------------- | ||
35 | |||
36 | async function announceActivityData (url: string, byAccount: AccountInstance, object: any) { | ||
37 | const activity = { | ||
38 | type: 'Announce', | 31 | type: 'Announce', |
39 | id: url, | 32 | id: url, |
40 | actor: byAccount.url, | 33 | actor: byAccount.url, |
@@ -43,3 +36,11 @@ async function announceActivityData (url: string, byAccount: AccountInstance, ob | |||
43 | 36 | ||
44 | return activity | 37 | return activity |
45 | } | 38 | } |
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | export { | ||
43 | sendVideoAnnounce, | ||
44 | sendVideoChannelAnnounce, | ||
45 | announceActivityData | ||
46 | } | ||
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts index 34ba3f8db..578bcda39 100644 --- a/server/models/account/account-follow.ts +++ b/server/models/account/account-follow.ts | |||
@@ -221,8 +221,8 @@ async function createListAcceptedFollowForApiQuery ( | |||
221 | 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + | 221 | 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + |
222 | 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' ' | 222 | 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' ' |
223 | 223 | ||
224 | if (start !== undefined) query += 'LIMIT ' + start | 224 | if (count !== undefined) query += 'LIMIT ' + count |
225 | if (count !== undefined) query += ', ' + count | 225 | if (start !== undefined) query += ' OFFSET ' + start |
226 | 226 | ||
227 | const options = { | 227 | const options = { |
228 | bind: { accountIds }, | 228 | bind: { accountIds }, |
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 9f29c842c..391ecff43 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -7,6 +7,7 @@ import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from ' | |||
7 | import { TagAttributes, TagInstance } from './tag-interface' | 7 | import { TagAttributes, TagInstance } from './tag-interface' |
8 | import { VideoChannelInstance } from './video-channel-interface' | 8 | import { VideoChannelInstance } from './video-channel-interface' |
9 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | 9 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' |
10 | import { VideoShareInstance } from './video-share-interface' | ||
10 | 11 | ||
11 | export namespace VideoMethods { | 12 | export namespace VideoMethods { |
12 | export type GetThumbnailName = (this: VideoInstance) => string | 13 | export type GetThumbnailName = (this: VideoInstance) => string |
@@ -44,6 +45,11 @@ export namespace VideoMethods { | |||
44 | export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]> | 45 | export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]> |
45 | export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]> | 46 | export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]> |
46 | 47 | ||
48 | export type ListAllAndSharedByAccountForOutbox = ( | ||
49 | accountId: number, | ||
50 | start: number, | ||
51 | count: number | ||
52 | ) => Bluebird< ResultList<VideoInstance> > | ||
47 | export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > | 53 | export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > |
48 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > | 54 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > |
49 | export type SearchAndPopulateAccountAndServerAndTags = ( | 55 | export type SearchAndPopulateAccountAndServerAndTags = ( |
@@ -73,6 +79,7 @@ export namespace VideoMethods { | |||
73 | export interface VideoClass { | 79 | export interface VideoClass { |
74 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 80 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
75 | list: VideoMethods.List | 81 | list: VideoMethods.List |
82 | listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox | ||
76 | listForApi: VideoMethods.ListForApi | 83 | listForApi: VideoMethods.ListForApi |
77 | listUserVideosForApi: VideoMethods.ListUserVideosForApi | 84 | listUserVideosForApi: VideoMethods.ListUserVideosForApi |
78 | listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags | 85 | listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags |
@@ -115,6 +122,7 @@ export interface VideoAttributes { | |||
115 | VideoChannel?: VideoChannelInstance | 122 | VideoChannel?: VideoChannelInstance |
116 | Tags?: TagInstance[] | 123 | Tags?: TagInstance[] |
117 | VideoFiles?: VideoFileInstance[] | 124 | VideoFiles?: VideoFileInstance[] |
125 | VideoShare?: VideoShareInstance | ||
118 | } | 126 | } |
119 | 127 | ||
120 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { | 128 | export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e2069eb0c..3b7e83779 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -78,6 +78,7 @@ let getLanguageLabel: VideoMethods.GetLanguageLabel | |||
78 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 78 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
79 | let list: VideoMethods.List | 79 | let list: VideoMethods.List |
80 | let listForApi: VideoMethods.ListForApi | 80 | let listForApi: VideoMethods.ListForApi |
81 | let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox | ||
81 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi | 82 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi |
82 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | 83 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID |
83 | let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags | 84 | let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags |
@@ -266,6 +267,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
266 | 267 | ||
267 | generateThumbnailFromData, | 268 | generateThumbnailFromData, |
268 | list, | 269 | list, |
270 | listAllAndSharedByAccountForOutbox, | ||
269 | listForApi, | 271 | listForApi, |
270 | listUserVideosForApi, | 272 | listUserVideosForApi, |
271 | listOwnedAndPopulateAccountAndTags, | 273 | listOwnedAndPopulateAccountAndTags, |
@@ -348,6 +350,14 @@ function associate (models) { | |||
348 | }, | 350 | }, |
349 | onDelete: 'cascade' | 351 | onDelete: 'cascade' |
350 | }) | 352 | }) |
353 | |||
354 | Video.hasMany(models.VideoShare, { | ||
355 | foreignKey: { | ||
356 | name: 'videoId', | ||
357 | allowNull: false | ||
358 | }, | ||
359 | onDelete: 'cascade' | ||
360 | }) | ||
351 | } | 361 | } |
352 | 362 | ||
353 | function afterDestroy (video: VideoInstance) { | 363 | function afterDestroy (video: VideoInstance) { |
@@ -775,6 +785,54 @@ list = function () { | |||
775 | return Video.findAll(query) | 785 | return Video.findAll(query) |
776 | } | 786 | } |
777 | 787 | ||
788 | listAllAndSharedByAccountForOutbox = function (accountId: number, start: number, count: number) { | ||
789 | const queryVideo = 'SELECT "Video"."id" FROM "Videos" AS "Video" ' + | ||
790 | 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + | ||
791 | 'WHERE "VideoChannel"."accountId" = ' + accountId | ||
792 | const queryVideoShare = 'SELECT "Video"."id" FROM "VideoShares" AS "VideoShare" ' + | ||
793 | 'INNER JOIN "Videos" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + | ||
794 | 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + | ||
795 | 'WHERE "VideoShare"."accountId" = ' + accountId | ||
796 | const rawQuery = `(${queryVideo}) UNION (${queryVideoShare}) LIMIT ${count} OFFSET ${start}` | ||
797 | |||
798 | const query = { | ||
799 | distinct: true, | ||
800 | offset: start, | ||
801 | limit: count, | ||
802 | order: [ getSort('createdAt'), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], | ||
803 | where: { | ||
804 | id: { | ||
805 | [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')') | ||
806 | } | ||
807 | }, | ||
808 | include: [ | ||
809 | { | ||
810 | model: Video['sequelize'].models.VideoShare, | ||
811 | required: false | ||
812 | }, | ||
813 | { | ||
814 | model: Video['sequelize'].models.VideoChannel, | ||
815 | required: true, | ||
816 | include: [ | ||
817 | { | ||
818 | model: Video['sequelize'].models.Account, | ||
819 | required: true | ||
820 | } | ||
821 | ] | ||
822 | }, | ||
823 | Video['sequelize'].models.Tag, | ||
824 | Video['sequelize'].models.VideoFile | ||
825 | ] | ||
826 | } | ||
827 | |||
828 | return Video.findAndCountAll(query).then(({ rows, count }) => { | ||
829 | return { | ||
830 | data: rows, | ||
831 | total: count | ||
832 | } | ||
833 | }) | ||
834 | } | ||
835 | |||
778 | listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { | 836 | listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { |
779 | const query = { | 837 | const query = { |
780 | distinct: true, | 838 | distinct: true, |