aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts48
-rw-r--r--server/controllers/activitypub/outbox.ts8
-rw-r--r--server/controllers/api/jobs.ts14
-rw-r--r--server/controllers/api/oauth-clients.ts4
-rw-r--r--server/controllers/api/server/follows.ts54
-rw-r--r--server/controllers/api/users.ts28
-rw-r--r--server/controllers/api/videos/abuse.ts17
-rw-r--r--server/controllers/api/videos/blacklist.ts12
-rw-r--r--server/controllers/api/videos/channel.ts27
-rw-r--r--server/controllers/api/videos/index.ts53
-rw-r--r--server/controllers/api/videos/rate.ts20
-rw-r--r--server/controllers/client.ts12
-rw-r--r--server/controllers/services.ts8
-rw-r--r--server/controllers/static.ts1
-rw-r--r--server/controllers/webfinger.ts8
-rw-r--r--server/helpers/activitypub.ts10
-rw-r--r--server/helpers/custom-validators/accounts.ts13
-rw-r--r--server/helpers/custom-validators/activitypub/account.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/activity.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/misc.ts4
-rw-r--r--server/helpers/custom-validators/index.ts9
-rw-r--r--server/helpers/custom-validators/video-channels.ts10
-rw-r--r--server/helpers/custom-validators/videos.ts19
-rw-r--r--server/helpers/custom-validators/webfinger.ts8
-rw-r--r--server/helpers/ffmpeg-utils.ts2
-rw-r--r--server/helpers/index.ts1
-rw-r--r--server/helpers/logger.ts2
-rw-r--r--server/helpers/peertube-crypto.ts6
-rw-r--r--server/helpers/utils.ts17
-rw-r--r--server/helpers/webfinger.ts4
-rw-r--r--server/initializers/checker.ts20
-rw-r--r--server/initializers/constants.ts14
-rw-r--r--server/initializers/database.ts186
-rw-r--r--server/initializers/installer.ts25
-rw-r--r--server/initializers/migrations/0065-video-file-size.ts5
-rw-r--r--server/initializers/migrations/0100-activitypub.ts6
-rw-r--r--server/initializers/migrations/0105-server-mail.ts3
-rw-r--r--server/initializers/migrations/0110-server-key.ts3
-rw-r--r--server/initializers/migrations/0115-account-avatar.ts3
-rw-r--r--server/initializers/migrations/0120-video-null.ts3
-rw-r--r--server/initializers/migrator.ts21
-rw-r--r--server/lib/activitypub/account.ts28
-rw-r--r--server/lib/activitypub/fetch.ts6
-rw-r--r--server/lib/activitypub/process/misc.ts38
-rw-r--r--server/lib/activitypub/process/process-accept.ts14
-rw-r--r--server/lib/activitypub/process/process-add.ts42
-rw-r--r--server/lib/activitypub/process/process-announce.ts35
-rw-r--r--server/lib/activitypub/process/process-create.ts48
-rw-r--r--server/lib/activitypub/process/process-delete.ts35
-rw-r--r--server/lib/activitypub/process/process-follow.ts22
-rw-r--r--server/lib/activitypub/process/process-like.ts20
-rw-r--r--server/lib/activitypub/process/process-undo.ts37
-rw-r--r--server/lib/activitypub/process/process-update.ts39
-rw-r--r--server/lib/activitypub/process/process.ts10
-rw-r--r--server/lib/activitypub/send/misc.ts56
-rw-r--r--server/lib/activitypub/send/send-accept.ts12
-rw-r--r--server/lib/activitypub/send/send-add.ts20
-rw-r--r--server/lib/activitypub/send/send-announce.ts35
-rw-r--r--server/lib/activitypub/send/send-create.ts46
-rw-r--r--server/lib/activitypub/send/send-delete.ts25
-rw-r--r--server/lib/activitypub/send/send-follow.ts14
-rw-r--r--server/lib/activitypub/send/send-like.ts19
-rw-r--r--server/lib/activitypub/send/send-undo.ts26
-rw-r--r--server/lib/activitypub/send/send-update.ts23
-rw-r--r--server/lib/activitypub/share.ts19
-rw-r--r--server/lib/activitypub/url.ts30
-rw-r--r--server/lib/activitypub/video-channels.ts21
-rw-r--r--server/lib/activitypub/videos.ts31
-rw-r--r--server/lib/cache/videos-preview-cache.ts13
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts3
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts7
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts9
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts3
-rw-r--r--server/lib/jobs/job-scheduler.ts20
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts5
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts16
-rw-r--r--server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts11
-rw-r--r--server/lib/oauth-model.ts19
-rw-r--r--server/lib/user.ts17
-rw-r--r--server/lib/video-channel.ts10
-rw-r--r--server/middlewares/activitypub.ts8
-rw-r--r--server/middlewares/sort.ts6
-rw-r--r--server/middlewares/user-right.ts7
-rw-r--r--server/middlewares/validators/account.ts4
-rw-r--r--server/middlewares/validators/activitypub/activity.ts3
-rw-r--r--server/middlewares/validators/activitypub/signature.ts4
-rw-r--r--server/middlewares/validators/follows.ts11
-rw-r--r--server/middlewares/validators/oembed.ts5
-rw-r--r--server/middlewares/validators/sort.ts1
-rw-r--r--server/middlewares/validators/users.ts15
-rw-r--r--server/middlewares/validators/utils.ts1
-rw-r--r--server/middlewares/validators/video-blacklist.ts13
-rw-r--r--server/middlewares/validators/video-channels.ts19
-rw-r--r--server/middlewares/validators/videos.ts17
-rw-r--r--server/middlewares/validators/webfinger.ts6
-rw-r--r--server/models/account/account-follow-interface.ts60
-rw-r--r--server/models/account/account-follow.ts364
-rw-r--r--server/models/account/account-interface.ts76
-rw-r--r--server/models/account/account-video-rate-interface.ts31
-rw-r--r--server/models/account/account-video-rate.ts97
-rw-r--r--server/models/account/account.ts673
-rw-r--r--server/models/account/index.ts4
-rw-r--r--server/models/account/user-interface.ts67
-rw-r--r--server/models/account/user.ts460
-rw-r--r--server/models/application/application-interface.ts31
-rw-r--r--server/models/application/application.ts80
-rw-r--r--server/models/application/index.ts1
-rw-r--r--server/models/avatar/avatar-interface.ts16
-rw-r--r--server/models/avatar/avatar.ts31
-rw-r--r--server/models/avatar/index.ts1
-rw-r--r--server/models/index.ts7
-rw-r--r--server/models/job/index.ts1
-rw-r--r--server/models/job/job-interface.ts33
-rw-r--r--server/models/job/job.ts137
-rw-r--r--server/models/oauth/index.ts2
-rw-r--r--server/models/oauth/oauth-client-interface.ts31
-rw-r--r--server/models/oauth/oauth-client.ts112
-rw-r--r--server/models/oauth/oauth-token-interface.ts46
-rw-r--r--server/models/oauth/oauth-token.ts259
-rw-r--r--server/models/server/index.ts1
-rw-r--r--server/models/server/server-interface.ts24
-rw-r--r--server/models/server/server.ts183
-rw-r--r--server/models/utils.ts17
-rw-r--r--server/models/video/index.ts9
-rw-r--r--server/models/video/tag-interface.ts20
-rw-r--r--server/models/video/tag.ts105
-rw-r--r--server/models/video/video-abuse-interface.ts41
-rw-r--r--server/models/video/video-abuse.ts210
-rw-r--r--server/models/video/video-blacklist-interface.ts39
-rw-r--r--server/models/video/video-blacklist.ts142
-rw-r--r--server/models/video/video-channel-interface.ts64
-rw-r--r--server/models/video/video-channel-share-interface.ts32
-rw-r--r--server/models/video/video-channel-share.ts120
-rw-r--r--server/models/video/video-channel.ts572
-rw-r--r--server/models/video/video-file-interface.ts24
-rw-r--r--server/models/video/video-file.ts109
-rw-r--r--server/models/video/video-interface.ts150
-rw-r--r--server/models/video/video-share-interface.ts30
-rw-r--r--server/models/video/video-share.ts118
-rw-r--r--server/models/video/video-tag-interface.ts18
-rw-r--r--server/models/video/video-tag.ts43
-rw-r--r--server/models/video/video.ts1845
142 files changed, 3412 insertions, 4842 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index a478acd14..72b216254 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -1,20 +1,22 @@
1// Intercept ActivityPub client requests 1// Intercept ActivityPub client requests
2import * as express from 'express' 2import * as express from 'express'
3import { pageToStartAndCount } from '../../helpers' 3import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers'
4import { activityPubCollectionPagination } from '../../helpers/activitypub' 4import { ACTIVITY_PUB, CONFIG } from '../../initializers'
5 5import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send'
6import { database as db } from '../../initializers'
7import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants'
8import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send/send-announce'
9import { buildVideoAnnounceToFollowers } from '../../lib/index' 6import { buildVideoAnnounceToFollowers } from '../../lib/index'
10import { executeIfActivityPub, localAccountValidator } from '../../middlewares' 7import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares'
11import { asyncMiddleware } from '../../middlewares/async' 8import {
12import { videoChannelsGetValidator, videoChannelsShareValidator } from '../../middlewares/validators/video-channels' 9 videoChannelsGetValidator,
13import { videosGetValidator, videosShareValidator } from '../../middlewares/validators/videos' 10 videoChannelsShareValidator,
14import { AccountInstance, VideoChannelInstance } from '../../models' 11 videosGetValidator,
15import { VideoChannelShareInstance } from '../../models/video/video-channel-share-interface' 12 videosShareValidator
16import { VideoInstance } from '../../models/video/video-interface' 13} from '../../middlewares/validators'
17import { VideoShareInstance } from '../../models/video/video-share-interface' 14import { AccountModel } from '../../models/account/account'
15import { AccountFollowModel } from '../../models/account/account-follow'
16import { VideoModel } from '../../models/video/video'
17import { VideoChannelModel } from '../../models/video/video-channel'
18import { VideoChannelShareModel } from '../../models/video/video-channel-share'
19import { VideoShareModel } from '../../models/video/video-share'
18 20
19const activityPubClientRouter = express.Router() 21const activityPubClientRouter = express.Router()
20 22
@@ -62,57 +64,57 @@ export {
62// --------------------------------------------------------------------------- 64// ---------------------------------------------------------------------------
63 65
64function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { 66function accountController (req: express.Request, res: express.Response, next: express.NextFunction) {
65 const account: AccountInstance = res.locals.account 67 const account: AccountModel = res.locals.account
66 68
67 return res.json(account.toActivityPubObject()).end() 69 return res.json(account.toActivityPubObject()).end()
68} 70}
69 71
70async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { 72async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) {
71 const account: AccountInstance = res.locals.account 73 const account: AccountModel = res.locals.account
72 74
73 const page = req.query.page || 1 75 const page = req.query.page || 1
74 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 76 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
75 77
76 const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count) 78 const result = await AccountFollowModel.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count)
77 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) 79 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
78 80
79 return res.json(activityPubResult) 81 return res.json(activityPubResult)
80} 82}
81 83
82async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { 84async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) {
83 const account: AccountInstance = res.locals.account 85 const account: AccountModel = res.locals.account
84 86
85 const page = req.query.page || 1 87 const page = req.query.page || 1
86 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 88 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
87 89
88 const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count) 90 const result = await AccountFollowModel.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count)
89 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) 91 const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result)
90 92
91 return res.json(activityPubResult) 93 return res.json(activityPubResult)
92} 94}
93 95
94function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { 96function videoController (req: express.Request, res: express.Response, next: express.NextFunction) {
95 const video: VideoInstance = res.locals.video 97 const video: VideoModel = res.locals.video
96 98
97 return res.json(video.toActivityPubObject()) 99 return res.json(video.toActivityPubObject())
98} 100}
99 101
100async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { 102async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
101 const share = res.locals.videoShare as VideoShareInstance 103 const share = res.locals.videoShare as VideoShareModel
102 const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined) 104 const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined)
103 105
104 return res.json(object) 106 return res.json(object)
105} 107}
106 108
107async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { 109async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) {
108 const share = res.locals.videoChannelShare as VideoChannelShareInstance 110 const share = res.locals.videoChannelShare as VideoChannelShareModel
109 const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined) 111 const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined)
110 112
111 return res.json(object) 113 return res.json(object)
112} 114}
113 115
114async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) { 116async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) {
115 const videoChannel: VideoChannelInstance = res.locals.videoChannel 117 const videoChannel: VideoChannelModel = res.locals.videoChannel
116 118
117 return res.json(videoChannel.toActivityPubObject()) 119 return res.json(videoChannel.toActivityPubObject())
118} 120}
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index 5a2d43f3d..dc6b72a6e 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -2,13 +2,13 @@ import * as express from 'express'
2import { Activity } from '../../../shared/models/activitypub/activity' 2import { Activity } from '../../../shared/models/activitypub/activity'
3import { activityPubCollectionPagination } from '../../helpers/activitypub' 3import { activityPubCollectionPagination } from '../../helpers/activitypub'
4import { pageToStartAndCount } from '../../helpers/core-utils' 4import { pageToStartAndCount } from '../../helpers/core-utils'
5import { database as db } from '../../initializers'
6import { ACTIVITY_PUB } from '../../initializers/constants' 5import { ACTIVITY_PUB } from '../../initializers/constants'
7import { addActivityData } from '../../lib/activitypub/send/send-add' 6import { addActivityData } from '../../lib/activitypub/send/send-add'
8import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' 7import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url'
9import { announceActivityData } from '../../lib/index' 8import { announceActivityData } from '../../lib/index'
10import { asyncMiddleware, localAccountValidator } from '../../middlewares' 9import { asyncMiddleware, localAccountValidator } from '../../middlewares'
11import { AccountInstance } from '../../models/account/account-interface' 10import { AccountModel } from '../../models/account/account'
11import { VideoModel } from '../../models/video/video'
12 12
13const outboxRouter = express.Router() 13const outboxRouter = express.Router()
14 14
@@ -26,12 +26,12 @@ export {
26// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
27 27
28async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { 28async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) {
29 const account: AccountInstance = res.locals.account 29 const account: AccountModel = res.locals.account
30 30
31 const page = req.query.page || 1 31 const page = req.query.page || 1
32 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) 32 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
33 33
34 const data = await db.Video.listAllAndSharedByAccountForOutbox(account.id, start, count) 34 const data = await VideoModel.listAllAndSharedByAccountForOutbox(account.id, start, count)
35 const activities: Activity[] = [] 35 const activities: Activity[] = []
36 36
37 for (const video of data.data) { 37 for (const video of data.data) {
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index f6fbff369..4e7cd1ee3 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -1,11 +1,9 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' 2import { UserRight } from '../../../shared/models/users'
3import { paginationValidator } from '../../middlewares/validators/pagination' 3import { getFormattedObjects } from '../../helpers'
4import { database as db } from '../../initializers' 4import { asyncMiddleware, authenticate, ensureUserHasRight, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares'
5import { getFormattedObjects } from '../../helpers/utils' 5import { paginationValidator } from '../../middlewares/validators'
6import { authenticate } from '../../middlewares/oauth' 6import { JobModel } from '../../models/job/job'
7import { ensureUserHasRight } from '../../middlewares/user-right'
8import { UserRight } from '../../../shared/models/users/user-right.enum'
9 7
10const jobsRouter = express.Router() 8const jobsRouter = express.Router()
11 9
@@ -28,7 +26,7 @@ export {
28// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
29 27
30async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { 28async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) {
31 const resultList = await db.Job.listForApi(req.query.start, req.query.count, req.query.sort) 29 const resultList = await JobModel.listForApi(req.query.start, req.query.count, req.query.sort)
32 30
33 return res.json(getFormattedObjects(resultList.data, resultList.total)) 31 return res.json(getFormattedObjects(resultList.data, resultList.total))
34} 32}
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index ac1ee9e36..bc02fce90 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -3,8 +3,8 @@ import * as express from 'express'
3import { CONFIG } from '../../initializers' 3import { CONFIG } from '../../initializers'
4import { logger } from '../../helpers' 4import { logger } from '../../helpers'
5import { asyncMiddleware } from '../../middlewares' 5import { asyncMiddleware } from '../../middlewares'
6import { database as db } from '../../initializers/database'
7import { OAuthClientLocal } from '../../../shared' 6import { OAuthClientLocal } from '../../../shared'
7import { OAuthClientModel } from '../../models/oauth/oauth-client'
8 8
9const oauthClientsRouter = express.Router() 9const oauthClientsRouter = express.Router()
10 10
@@ -27,7 +27,7 @@ async function getLocalClient (req: express.Request, res: express.Response, next
27 return res.type('json').status(403).end() 27 return res.type('json').status(403).end()
28 } 28 }
29 29
30 const client = await db.OAuthClient.loadFirstClient() 30 const client = await OAuthClientModel.loadFirstClient()
31 if (!client) throw new Error('No client available.') 31 if (!client) throw new Error('No client available.')
32 32
33 const json: OAuthClientLocal = { 33 const json: OAuthClientLocal = {
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index c2fb37c39..913998e3a 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -1,24 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users/user-right.enum' 2import { UserRight } from '../../../../shared/models/users'
3import { getFormattedObjects } from '../../../helpers' 3import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers'
4import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers'
5import { logger } from '../../../helpers/logger' 5import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub'
6import { getServerAccount } from '../../../helpers/utils' 6import { sendUndoFollow } from '../../../lib/activitypub/send'
7import { getAccountFromWebfinger } from '../../../helpers/webfinger'
8import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants'
9import { database as db } from '../../../initializers/database'
10import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub/account'
11import { sendUndoFollow } from '../../../lib/activitypub/send/send-undo'
12import { sendFollow } from '../../../lib/index' 7import { sendFollow } from '../../../lib/index'
13import { asyncMiddleware, paginationValidator, removeFollowingValidator, setFollowersSort, setPagination } from '../../../middlewares' 8import {
14import { authenticate } from '../../../middlewares/oauth' 9 asyncMiddleware,
15import { setBodyHostsPort } from '../../../middlewares/servers' 10 authenticate,
16import { setFollowingSort } from '../../../middlewares/sort' 11 ensureUserHasRight,
17import { ensureUserHasRight } from '../../../middlewares/user-right' 12 paginationValidator,
18import { followValidator } from '../../../middlewares/validators/follows' 13 removeFollowingValidator,
19import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort' 14 setBodyHostsPort,
20import { AccountInstance } from '../../../models/account/account-interface' 15 setFollowersSort,
21import { AccountFollowInstance } from '../../../models/index' 16 setFollowingSort,
17 setPagination
18} from '../../../middlewares'
19import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators'
20import { AccountModel } from '../../../models/account/account'
21import { AccountFollowModel } from '../../../models/account/account-follow'
22 22
23const serverFollowsRouter = express.Router() 23const serverFollowsRouter = express.Router()
24 24
@@ -63,14 +63,14 @@ export {
63 63
64async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 64async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
65 const serverAccount = await getServerAccount() 65 const serverAccount = await getServerAccount()
66 const resultList = await db.AccountFollow.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) 66 const resultList = await AccountFollowModel.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort)
67 67
68 return res.json(getFormattedObjects(resultList.data, resultList.total)) 68 return res.json(getFormattedObjects(resultList.data, resultList.total))
69} 69}
70 70
71async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 71async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
72 const serverAccount = await getServerAccount() 72 const serverAccount = await getServerAccount()
73 const resultList = await db.AccountFollow.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) 73 const resultList = await AccountFollowModel.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort)
74 74
75 return res.json(getFormattedObjects(resultList.data, resultList.total)) 75 return res.json(getFormattedObjects(resultList.data, resultList.total))
76} 76}
@@ -110,14 +110,14 @@ async function followRetry (req: express.Request, res: express.Response, next: e
110 return res.status(204).end() 110 return res.status(204).end()
111} 111}
112 112
113async function follow (fromAccount: AccountInstance, targetAccount: AccountInstance, targetAlreadyInDB: boolean) { 113async function follow (fromAccount: AccountModel, targetAccount: AccountModel, targetAlreadyInDB: boolean) {
114 try { 114 try {
115 await db.sequelize.transaction(async t => { 115 await sequelizeTypescript.transaction(async t => {
116 if (targetAlreadyInDB === false) { 116 if (targetAlreadyInDB === false) {
117 await saveAccountAndServerIfNotExist(targetAccount, t) 117 await saveAccountAndServerIfNotExist(targetAccount, t)
118 } 118 }
119 119
120 const [ accountFollow ] = await db.AccountFollow.findOrCreate({ 120 const [ accountFollow ] = await AccountFollowModel.findOrCreate({
121 where: { 121 where: {
122 accountId: fromAccount.id, 122 accountId: fromAccount.id,
123 targetAccountId: targetAccount.id 123 targetAccountId: targetAccount.id
@@ -145,9 +145,9 @@ async function follow (fromAccount: AccountInstance, targetAccount: AccountInsta
145} 145}
146 146
147async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { 147async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) {
148 const follow: AccountFollowInstance = res.locals.follow 148 const follow: AccountFollowModel = res.locals.follow
149 149
150 await db.sequelize.transaction(async t => { 150 await sequelizeTypescript.transaction(async t => {
151 if (follow.state === 'accepted') await sendUndoFollow(follow, t) 151 if (follow.state === 'accepted') await sendUndoFollow(follow, t)
152 152
153 await follow.destroy({ transaction: t }) 153 await follow.destroy({ transaction: t })
@@ -164,7 +164,7 @@ async function removeFollow (req: express.Request, res: express.Response, next:
164 164
165async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) { 165async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) {
166 let loadedFromDB = true 166 let loadedFromDB = true
167 let account = await db.Account.loadByNameAndHost(name, host) 167 let account = await AccountModel.loadByNameAndHost(name, host)
168 168
169 if (!account) { 169 if (!account) {
170 const nameWithDomain = name + '@' + host 170 const nameWithDomain = name + '@' + host
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index f9b871724..d6c0e67f9 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' 2import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared'
3import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' 3import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers'
4import { CONFIG, database as db } from '../../initializers' 4import { CONFIG } from '../../initializers'
5import { createUserAccountAndChannel } from '../../lib' 5import { createUserAccountAndChannel } from '../../lib'
6import { 6import {
7 asyncMiddleware, 7 asyncMiddleware,
@@ -11,6 +11,7 @@ import {
11 paginationValidator, 11 paginationValidator,
12 setPagination, 12 setPagination,
13 setUsersSort, 13 setUsersSort,
14 setVideosSort,
14 token, 15 token,
15 usersAddValidator, 16 usersAddValidator,
16 usersGetValidator, 17 usersGetValidator,
@@ -21,9 +22,10 @@ import {
21 usersUpdateValidator, 22 usersUpdateValidator,
22 usersVideoRatingValidator 23 usersVideoRatingValidator
23} from '../../middlewares' 24} from '../../middlewares'
24import { setVideosSort } from '../../middlewares/sort' 25import { videosSortValidator } from '../../middlewares/validators'
25import { videosSortValidator } from '../../middlewares/validators/sort' 26import { AccountVideoRateModel } from '../../models/account/account-video-rate'
26import { UserInstance } from '../../models' 27import { UserModel } from '../../models/account/user'
28import { VideoModel } from '../../models/video/video'
27 29
28const usersRouter = express.Router() 30const usersRouter = express.Router()
29 31
@@ -107,8 +109,8 @@ export {
107// --------------------------------------------------------------------------- 109// ---------------------------------------------------------------------------
108 110
109async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 111async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
110 const user = res.locals.oauth.token.User 112 const user = res.locals.oauth.token.User as UserModel
111 const resultList = await db.Video.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort) 113 const resultList = await VideoModel.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort)
112 114
113 return res.json(getFormattedObjects(resultList.data, resultList.total)) 115 return res.json(getFormattedObjects(resultList.data, resultList.total))
114} 116}
@@ -127,7 +129,7 @@ async function createUserRetryWrapper (req: express.Request, res: express.Respon
127 129
128async function createUser (req: express.Request) { 130async function createUser (req: express.Request) {
129 const body: UserCreate = req.body 131 const body: UserCreate = req.body
130 const user = db.User.build({ 132 const user = new UserModel({
131 username: body.username, 133 username: body.username,
132 password: body.password, 134 password: body.password,
133 email: body.email, 135 email: body.email,
@@ -155,7 +157,7 @@ async function registerUserRetryWrapper (req: express.Request, res: express.Resp
155async function registerUser (req: express.Request) { 157async function registerUser (req: express.Request) {
156 const body: UserCreate = req.body 158 const body: UserCreate = req.body
157 159
158 const user = db.User.build({ 160 const user = new UserModel({
159 username: body.username, 161 username: body.username,
160 password: body.password, 162 password: body.password,
161 email: body.email, 163 email: body.email,
@@ -171,7 +173,7 @@ async function registerUser (req: express.Request) {
171 173
172async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 174async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) {
173 // We did not load channels in res.locals.user 175 // We did not load channels in res.locals.user
174 const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 176 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
175 177
176 return res.json(user.toFormattedJSON()) 178 return res.json(user.toFormattedJSON())
177} 179}
@@ -184,7 +186,7 @@ async function getUserVideoRating (req: express.Request, res: express.Response,
184 const videoId = +req.params.videoId 186 const videoId = +req.params.videoId
185 const accountId = +res.locals.oauth.token.User.Account.id 187 const accountId = +res.locals.oauth.token.User.Account.id
186 188
187 const ratingObj = await db.AccountVideoRate.load(accountId, videoId, null) 189 const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
188 const rating = ratingObj ? ratingObj.type : 'none' 190 const rating = ratingObj ? ratingObj.type : 'none'
189 191
190 const json: FormattedUserVideoRate = { 192 const json: FormattedUserVideoRate = {
@@ -195,13 +197,13 @@ async function getUserVideoRating (req: express.Request, res: express.Response,
195} 197}
196 198
197async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 199async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) {
198 const resultList = await db.User.listForApi(req.query.start, req.query.count, req.query.sort) 200 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort)
199 201
200 return res.json(getFormattedObjects(resultList.data, resultList.total)) 202 return res.json(getFormattedObjects(resultList.data, resultList.total))
201} 203}
202 204
203async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 205async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) {
204 const user = await db.User.loadById(req.params.id) 206 const user = await UserModel.loadById(req.params.id)
205 207
206 await user.destroy() 208 await user.destroy()
207 209
@@ -225,7 +227,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
225 227
226async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 228async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
227 const body: UserUpdate = req.body 229 const body: UserUpdate = req.body
228 const user: UserInstance = res.locals.user 230 const user = res.locals.user as UserModel
229 231
230 if (body.email !== undefined) user.email = body.email 232 if (body.email !== undefined) user.email = body.email
231 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota 233 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index 29e1175c5..08cc4d0b4 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -1,11 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2
3import { database as db } from '../../../initializers/database'
4import { 2import {
5 logger, 3 logger,
6 getFormattedObjects, 4 getFormattedObjects,
7 retryTransactionWrapper 5 retryTransactionWrapper
8} from '../../../helpers' 6} from '../../../helpers'
7import { sequelizeTypescript } from '../../../initializers'
9import { 8import {
10 authenticate, 9 authenticate,
11 ensureUserHasRight, 10 ensureUserHasRight,
@@ -16,9 +15,11 @@ import {
16 setPagination, 15 setPagination,
17 asyncMiddleware 16 asyncMiddleware
18} from '../../../middlewares' 17} from '../../../middlewares'
19import { VideoInstance } from '../../../models'
20import { VideoAbuseCreate, UserRight } from '../../../../shared' 18import { VideoAbuseCreate, UserRight } from '../../../../shared'
21import { sendVideoAbuse } from '../../../lib/index' 19import { sendVideoAbuse } from '../../../lib/index'
20import { AccountModel } from '../../../models/account/account'
21import { VideoModel } from '../../../models/video/video'
22import { VideoAbuseModel } from '../../../models/video/video-abuse'
22 23
23const abuseVideoRouter = express.Router() 24const abuseVideoRouter = express.Router()
24 25
@@ -46,7 +47,7 @@ export {
46// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
47 48
48async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { 49async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
49 const resultList = await db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) 50 const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort)
50 51
51 return res.json(getFormattedObjects(resultList.data, resultList.total)) 52 return res.json(getFormattedObjects(resultList.data, resultList.total))
52} 53}
@@ -63,8 +64,8 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express.
63} 64}
64 65
65async function reportVideoAbuse (req: express.Request, res: express.Response) { 66async function reportVideoAbuse (req: express.Request, res: express.Response) {
66 const videoInstance = res.locals.video as VideoInstance 67 const videoInstance = res.locals.video as VideoModel
67 const reporterAccount = res.locals.oauth.token.User.Account 68 const reporterAccount = res.locals.oauth.token.User.Account as AccountModel
68 const body: VideoAbuseCreate = req.body 69 const body: VideoAbuseCreate = req.body
69 70
70 const abuseToCreate = { 71 const abuseToCreate = {
@@ -73,8 +74,8 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
73 videoId: videoInstance.id 74 videoId: videoInstance.id
74 } 75 }
75 76
76 await db.sequelize.transaction(async t => { 77 await sequelizeTypescript.transaction(async t => {
77 const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t }) 78 const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
78 videoAbuseInstance.Video = videoInstance 79 videoAbuseInstance.Video = videoInstance
79 80
80 // We send the video abuse to the origin server 81 // We send the video abuse to the origin server
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 06333c271..d08c6e13f 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,6 +1,4 @@
1import * as express from 'express' 1import * as express from 'express'
2
3import { database as db } from '../../../initializers'
4import { logger, getFormattedObjects } from '../../../helpers' 2import { logger, getFormattedObjects } from '../../../helpers'
5import { 3import {
6 authenticate, 4 authenticate,
@@ -13,8 +11,8 @@ import {
13 setPagination, 11 setPagination,
14 asyncMiddleware 12 asyncMiddleware
15} from '../../../middlewares' 13} from '../../../middlewares'
16import { BlacklistedVideoInstance } from '../../../models'
17import { BlacklistedVideo, UserRight } from '../../../../shared' 14import { BlacklistedVideo, UserRight } from '../../../../shared'
15import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
18 16
19const blacklistRouter = express.Router() 17const blacklistRouter = express.Router()
20 18
@@ -57,18 +55,18 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response,
57 videoId: videoInstance.id 55 videoId: videoInstance.id
58 } 56 }
59 57
60 await db.BlacklistedVideo.create(toCreate) 58 await VideoBlacklistModel.create(toCreate)
61 return res.type('json').status(204).end() 59 return res.type('json').status(204).end()
62} 60}
63 61
64async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 62async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
65 const resultList = await db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort) 63 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort)
66 64
67 return res.json(getFormattedObjects<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total)) 65 return res.json(getFormattedObjects<BlacklistedVideo, VideoBlacklistModel>(resultList.data, resultList.total))
68} 66}
69 67
70async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 68async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
71 const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance 69 const blacklistedVideo = res.locals.blacklistedVideo as VideoBlacklistModel
72 70
73 try { 71 try {
74 await blacklistedVideo.destroy() 72 await blacklistedVideo.destroy()
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
index d99f47c32..683b0448d 100644
--- a/server/controllers/api/videos/channel.ts
+++ b/server/controllers/api/videos/channel.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' 2import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
3import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' 3import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
4import { database as db } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { createVideoChannel } from '../../../lib' 5import { createVideoChannel } from '../../../lib'
6import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' 6import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update'
7import { 7import {
@@ -17,7 +17,8 @@ import {
17 videoChannelsSortValidator, 17 videoChannelsSortValidator,
18 videoChannelsUpdateValidator 18 videoChannelsUpdateValidator
19} from '../../../middlewares' 19} from '../../../middlewares'
20import { AccountInstance, VideoChannelInstance } from '../../../models' 20import { AccountModel } from '../../../models/account/account'
21import { VideoChannelModel } from '../../../models/video/video-channel'
21 22
22const videoChannelRouter = express.Router() 23const videoChannelRouter = express.Router()
23 24
@@ -66,13 +67,13 @@ export {
66// --------------------------------------------------------------------------- 67// ---------------------------------------------------------------------------
67 68
68async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 69async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
69 const resultList = await db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort) 70 const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort)
70 71
71 return res.json(getFormattedObjects(resultList.data, resultList.total)) 72 return res.json(getFormattedObjects(resultList.data, resultList.total))
72} 73}
73 74
74async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 75async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
75 const resultList = await db.VideoChannel.listByAccount(res.locals.account.id) 76 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
76 77
77 return res.json(getFormattedObjects(resultList.data, resultList.total)) 78 return res.json(getFormattedObjects(resultList.data, resultList.total))
78} 79}
@@ -93,10 +94,10 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R
93 94
94async function addVideoChannel (req: express.Request, res: express.Response) { 95async function addVideoChannel (req: express.Request, res: express.Response) {
95 const videoChannelInfo: VideoChannelCreate = req.body 96 const videoChannelInfo: VideoChannelCreate = req.body
96 const account: AccountInstance = res.locals.oauth.token.User.Account 97 const account: AccountModel = res.locals.oauth.token.User.Account
97 let videoChannelCreated: VideoChannelInstance 98 let videoChannelCreated: VideoChannelModel
98 99
99 await db.sequelize.transaction(async t => { 100 await sequelizeTypescript.transaction(async t => {
100 videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t) 101 videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t)
101 }) 102 })
102 103
@@ -115,12 +116,12 @@ async function updateVideoChannelRetryWrapper (req: express.Request, res: expres
115} 116}
116 117
117async function updateVideoChannel (req: express.Request, res: express.Response) { 118async function updateVideoChannel (req: express.Request, res: express.Response) {
118 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel 119 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
119 const videoChannelFieldsSave = videoChannelInstance.toJSON() 120 const videoChannelFieldsSave = videoChannelInstance.toJSON()
120 const videoChannelInfoToUpdate: VideoChannelUpdate = req.body 121 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
121 122
122 try { 123 try {
123 await db.sequelize.transaction(async t => { 124 await sequelizeTypescript.transaction(async t => {
124 const sequelizeOptions = { 125 const sequelizeOptions = {
125 transaction: t 126 transaction: t
126 } 127 }
@@ -158,9 +159,9 @@ async function removeVideoChannelRetryWrapper (req: express.Request, res: expres
158} 159}
159 160
160async function removeVideoChannel (req: express.Request, res: express.Response) { 161async function removeVideoChannel (req: express.Request, res: express.Response) {
161 const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel 162 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
162 163
163 await db.sequelize.transaction(async t => { 164 await sequelizeTypescript.transaction(async t => {
164 await videoChannelInstance.destroy({ transaction: t }) 165 await videoChannelInstance.destroy({ transaction: t })
165 }) 166 })
166 167
@@ -168,7 +169,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
168} 169}
169 170
170async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 171async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
171 const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) 172 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
172 173
173 return res.json(videoChannelWithVideos.toFormattedJSON()) 174 return res.json(videoChannelWithVideos.toFormattedJSON())
174} 175}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 63de662a7..91ab8c66a 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -12,16 +12,19 @@ import {
12 retryTransactionWrapper 12 retryTransactionWrapper
13} from '../../../helpers' 13} from '../../../helpers'
14import { getServerAccount } from '../../../helpers/utils' 14import { getServerAccount } from '../../../helpers/utils'
15import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' 15import {
16import { database as db } from '../../../initializers/database' 16 CONFIG,
17import { sendAddVideo } from '../../../lib/activitypub/send/send-add' 17 sequelizeTypescript,
18import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' 18 VIDEO_CATEGORIES,
19import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' 19 VIDEO_LANGUAGES,
20import { shareVideoByServer } from '../../../lib/activitypub/share' 20 VIDEO_LICENCES,
21import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' 21 VIDEO_MIMETYPE_EXT,
22import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' 22 VIDEO_PRIVACIES
23} from '../../../initializers'
24import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub'
25import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send'
23import { sendCreateViewToVideoFollowers } from '../../../lib/index' 26import { sendCreateViewToVideoFollowers } from '../../../lib/index'
24import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' 27import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler'
25import { 28import {
26 asyncMiddleware, 29 asyncMiddleware,
27 authenticate, 30 authenticate,
@@ -35,7 +38,9 @@ import {
35 videosSortValidator, 38 videosSortValidator,
36 videosUpdateValidator 39 videosUpdateValidator
37} from '../../../middlewares' 40} from '../../../middlewares'
38import { VideoInstance } from '../../../models' 41import { TagModel } from '../../../models/video/tag'
42import { VideoModel } from '../../../models/video/video'
43import { VideoFileModel } from '../../../models/video/video-file'
39import { abuseVideoRouter } from './abuse' 44import { abuseVideoRouter } from './abuse'
40import { blacklistRouter } from './blacklist' 45import { blacklistRouter } from './blacklist'
41import { videoChannelRouter } from './channel' 46import { videoChannelRouter } from './channel'
@@ -99,7 +104,7 @@ videosRouter.put('/:id',
99videosRouter.post('/upload', 104videosRouter.post('/upload',
100 authenticate, 105 authenticate,
101 reqFiles, 106 reqFiles,
102 videosAddValidator, 107 asyncMiddleware(videosAddValidator),
103 asyncMiddleware(addVideoRetryWrapper) 108 asyncMiddleware(addVideoRetryWrapper)
104) 109)
105 110
@@ -181,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
181 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware 186 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
182 channelId: res.locals.videoChannel.id 187 channelId: res.locals.videoChannel.id
183 } 188 }
184 const video = db.Video.build(videoData) 189 const video = new VideoModel(videoData)
185 video.url = getVideoActivityPubUrl(video) 190 video.url = getVideoActivityPubUrl(video)
186 191
187 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 192 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
@@ -192,7 +197,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
192 resolution: videoFileHeight, 197 resolution: videoFileHeight,
193 size: videoPhysicalFile.size 198 size: videoPhysicalFile.size
194 } 199 }
195 const videoFile = db.VideoFile.build(videoFileData) 200 const videoFile = new VideoFileModel(videoFileData)
196 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 201 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
197 const source = join(videoDir, videoPhysicalFile.filename) 202 const source = join(videoDir, videoPhysicalFile.filename)
198 const destination = join(videoDir, video.getVideoFilename(videoFile)) 203 const destination = join(videoDir, video.getVideoFilename(videoFile))
@@ -210,7 +215,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
210 ) 215 )
211 await Promise.all(tasks) 216 await Promise.all(tasks)
212 217
213 return db.sequelize.transaction(async t => { 218 return sequelizeTypescript.transaction(async t => {
214 const sequelizeOptions = { transaction: t } 219 const sequelizeOptions = { transaction: t }
215 220
216 if (CONFIG.TRANSCODING.ENABLED === true) { 221 if (CONFIG.TRANSCODING.ENABLED === true) {
@@ -232,9 +237,9 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
232 video.VideoFiles = [ videoFile ] 237 video.VideoFiles = [ videoFile ]
233 238
234 if (videoInfo.tags) { 239 if (videoInfo.tags) {
235 const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t) 240 const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t)
236 241
237 await video.setTags(tagInstances, sequelizeOptions) 242 await video.$set('Tags', tagInstances, sequelizeOptions)
238 video.Tags = tagInstances 243 video.Tags = tagInstances
239 } 244 }
240 245
@@ -264,13 +269,13 @@ async function updateVideoRetryWrapper (req: express.Request, res: express.Respo
264} 269}
265 270
266async function updateVideo (req: express.Request, res: express.Response) { 271async function updateVideo (req: express.Request, res: express.Response) {
267 const videoInstance: VideoInstance = res.locals.video 272 const videoInstance: VideoModel = res.locals.video
268 const videoFieldsSave = videoInstance.toJSON() 273 const videoFieldsSave = videoInstance.toJSON()
269 const videoInfoToUpdate: VideoUpdate = req.body 274 const videoInfoToUpdate: VideoUpdate = req.body
270 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 275 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
271 276
272 try { 277 try {
273 await db.sequelize.transaction(async t => { 278 await sequelizeTypescript.transaction(async t => {
274 const sequelizeOptions = { 279 const sequelizeOptions = {
275 transaction: t 280 transaction: t
276 } 281 }
@@ -286,9 +291,9 @@ async function updateVideo (req: express.Request, res: express.Response) {
286 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 291 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
287 292
288 if (videoInfoToUpdate.tags) { 293 if (videoInfoToUpdate.tags) {
289 const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t) 294 const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
290 295
291 await videoInstance.setTags(tagInstances, sequelizeOptions) 296 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
292 videoInstance.Tags = tagInstances 297 videoInstance.Tags = tagInstances
293 } 298 }
294 299
@@ -350,7 +355,7 @@ async function getVideoDescription (req: express.Request, res: express.Response)
350} 355}
351 356
352async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 357async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
353 const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort) 358 const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort)
354 359
355 return res.json(getFormattedObjects(resultList.data, resultList.total)) 360 return res.json(getFormattedObjects(resultList.data, resultList.total))
356} 361}
@@ -367,9 +372,9 @@ async function removeVideoRetryWrapper (req: express.Request, res: express.Respo
367} 372}
368 373
369async function removeVideo (req: express.Request, res: express.Response) { 374async function removeVideo (req: express.Request, res: express.Response) {
370 const videoInstance: VideoInstance = res.locals.video 375 const videoInstance: VideoModel = res.locals.video
371 376
372 await db.sequelize.transaction(async t => { 377 await sequelizeTypescript.transaction(async t => {
373 await videoInstance.destroy({ transaction: t }) 378 await videoInstance.destroy({ transaction: t })
374 }) 379 })
375 380
@@ -377,7 +382,7 @@ async function removeVideo (req: express.Request, res: express.Response) {
377} 382}
378 383
379async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 384async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
380 const resultList = await db.Video.searchAndPopulateAccountAndServerAndTags( 385 const resultList = await VideoModel.searchAndPopulateAccountAndServerAndTags(
381 req.query.search, 386 req.query.search,
382 req.query.start, 387 req.query.start,
383 req.query.count, 388 req.query.count,
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index c27c61116..48b744b0c 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,12 +1,12 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { UserVideoRateUpdate } from '../../../../shared'
3import { logger, retryTransactionWrapper } from '../../../helpers' 3import { logger, retryTransactionWrapper } from '../../../helpers'
4import { VIDEO_RATE_TYPES } from '../../../initializers' 4import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers'
5import { database as db } from '../../../initializers/database' 5import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub'
6import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub/videos'
7import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares' 6import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares'
8import { AccountInstance } from '../../../models/account/account-interface' 7import { AccountModel } from '../../../models/account/account'
9import { VideoInstance } from '../../../models/video/video-interface' 8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
9import { VideoModel } from '../../../models/video/video'
10 10
11const rateVideoRouter = express.Router() 11const rateVideoRouter = express.Router()
12 12
@@ -38,12 +38,12 @@ async function rateVideoRetryWrapper (req: express.Request, res: express.Respons
38async function rateVideo (req: express.Request, res: express.Response) { 38async function rateVideo (req: express.Request, res: express.Response) {
39 const body: UserVideoRateUpdate = req.body 39 const body: UserVideoRateUpdate = req.body
40 const rateType = body.rating 40 const rateType = body.rating
41 const videoInstance: VideoInstance = res.locals.video 41 const videoInstance: VideoModel = res.locals.video
42 const accountInstance: AccountInstance = res.locals.oauth.token.User.Account 42 const accountInstance: AccountModel = res.locals.oauth.token.User.Account
43 43
44 await db.sequelize.transaction(async t => { 44 await sequelizeTypescript.transaction(async t => {
45 const sequelizeOptions = { transaction: t } 45 const sequelizeOptions = { transaction: t }
46 const previousRate = await db.AccountVideoRate.load(accountInstance.id, videoInstance.id, t) 46 const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t)
47 47
48 let likesToIncrement = 0 48 let likesToIncrement = 0
49 let dislikesToIncrement = 0 49 let dislikesToIncrement = 0
@@ -71,7 +71,7 @@ async function rateVideo (req: express.Request, res: express.Response) {
71 type: rateType 71 type: rateType
72 } 72 }
73 73
74 await db.AccountVideoRate.create(query, sequelizeOptions) 74 await AccountVideoRateModel.create(query, sequelizeOptions)
75 } 75 }
76 76
77 const incrementQuery = { 77 const incrementQuery = {
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 1b140b14a..9a72fe8e0 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -2,8 +2,6 @@ import * as express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import * as validator from 'validator' 3import * as validator from 'validator'
4import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
5
6import { database as db } from '../initializers/database'
7import { 5import {
8 CONFIG, 6 CONFIG,
9 STATIC_PATHS, 7 STATIC_PATHS,
@@ -13,7 +11,7 @@ import {
13} from '../initializers' 11} from '../initializers'
14import { root, readFileBufferPromise, escapeHTML } from '../helpers' 12import { root, readFileBufferPromise, escapeHTML } from '../helpers'
15import { asyncMiddleware } from '../middlewares' 13import { asyncMiddleware } from '../middlewares'
16import { VideoInstance } from '../models' 14import { VideoModel } from '../models/video/video'
17 15
18const clientsRouter = express.Router() 16const clientsRouter = express.Router()
19 17
@@ -49,7 +47,7 @@ export {
49 47
50// --------------------------------------------------------------------------- 48// ---------------------------------------------------------------------------
51 49
52function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) { 50function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
53 const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() 51 const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
54 const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid 52 const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
55 53
@@ -108,13 +106,13 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance
108 106
109async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { 107async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
110 const videoId = '' + req.params.id 108 const videoId = '' + req.params.id
111 let videoPromise: Bluebird<VideoInstance> 109 let videoPromise: Bluebird<VideoModel>
112 110
113 // Let Angular application handle errors 111 // Let Angular application handle errors
114 if (validator.isUUID(videoId, 4)) { 112 if (validator.isUUID(videoId, 4)) {
115 videoPromise = db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(videoId) 113 videoPromise = VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(videoId)
116 } else if (validator.isInt(videoId)) { 114 } else if (validator.isInt(videoId)) {
117 videoPromise = db.Video.loadAndPopulateAccountAndServerAndTags(+videoId) 115 videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId)
118 } else { 116 } else {
119 return res.sendFile(indexPath) 117 return res.sendFile(indexPath)
120 } 118 }
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index 0c325678c..3ac78a5df 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -1,9 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2
3import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' 2import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers'
4import { oembedValidator } from '../middlewares' 3import { asyncMiddleware, oembedValidator } from '../middlewares'
5import { asyncMiddleware } from '../middlewares/async' 4import { VideoModel } from '../models/video/video'
6import { VideoInstance } from '../models'
7 5
8const servicesRouter = express.Router() 6const servicesRouter = express.Router()
9 7
@@ -21,7 +19,7 @@ export {
21// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
22 20
23function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { 21function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) {
24 const video = res.locals.video as VideoInstance 22 const video = res.locals.video as VideoModel
25 const webserverUrl = CONFIG.WEBSERVER.URL 23 const webserverUrl = CONFIG.WEBSERVER.URL
26 const maxHeight = parseInt(req.query.maxheight, 10) 24 const maxHeight = parseInt(req.query.maxheight, 10)
27 const maxWidth = parseInt(req.query.maxwidth, 10) 25 const maxWidth = parseInt(req.query.maxwidth, 10)
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 7425fd097..33aed8927 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -1,6 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as cors from 'cors' 2import * as cors from 'cors'
3
4import { 3import {
5 CONFIG, 4 CONFIG,
6 STATIC_MAX_AGE, 5 STATIC_MAX_AGE,
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index 78e5dee79..bb2ea40fa 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares/async' 2import { asyncMiddleware } from '../middlewares'
3import { webfingerValidator } from '../middlewares/validators/webfinger' 3import { webfingerValidator } from '../middlewares/validators'
4import { AccountInstance } from '../models/account/account-interface' 4import { AccountModel } from '../models/account/account'
5 5
6const webfingerRouter = express.Router() 6const webfingerRouter = express.Router()
7 7
@@ -19,7 +19,7 @@ export {
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { 21function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) {
22 const account: AccountInstance = res.locals.account 22 const account = res.locals.account as AccountModel
23 23
24 const json = { 24 const json = {
25 subject: req.query.resource, 25 subject: req.query.resource,
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 1ea6422ca..43907b596 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -1,8 +1,8 @@
1import { Activity } from '../../shared/models/activitypub/activity' 1import { ResultList } from '../../shared/models'
2import { ResultList } from '../../shared/models/result-list.model' 2import { Activity } from '../../shared/models/activitypub'
3import { AccountInstance } from '../models/account/account-interface' 3import { ACTIVITY_PUB } from '../initializers'
4import { AccountModel } from '../models/account/account'
4import { signObject } from './peertube-crypto' 5import { signObject } from './peertube-crypto'
5import { ACTIVITY_PUB } from '../initializers/constants'
6 6
7function activityPubContextify <T> (data: T) { 7function activityPubContextify <T> (data: T) {
8 return Object.assign(data,{ 8 return Object.assign(data,{
@@ -71,7 +71,7 @@ function activityPubCollectionPagination (url: string, page: any, result: Result
71 return orderedCollectionPagination 71 return orderedCollectionPagination
72} 72}
73 73
74function buildSignedActivity (byAccount: AccountInstance, data: Object) { 74function buildSignedActivity (byAccount: AccountModel, data: Object) {
75 const activity = activityPubContextify(data) 75 const activity = activityPubContextify(data)
76 76
77 return signObject(byAccount, activity) as Promise<Activity> 77 return signObject(byAccount, activity) as Promise<Activity>
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts
index e3c477414..8dc5d1f0d 100644
--- a/server/helpers/custom-validators/accounts.ts
+++ b/server/helpers/custom-validators/accounts.ts
@@ -2,8 +2,7 @@ import * as Bluebird from 'bluebird'
2import { Response } from 'express' 2import { Response } from 'express'
3import 'express-validator' 3import 'express-validator'
4import * as validator from 'validator' 4import * as validator from 'validator'
5import { database as db } from '../../initializers' 5import { AccountModel } from '../../models/account/account'
6import { AccountInstance } from '../../models'
7import { isUserUsernameValid } from './users' 6import { isUserUsernameValid } from './users'
8 7
9function isAccountNameValid (value: string) { 8function isAccountNameValid (value: string) {
@@ -11,24 +10,24 @@ function isAccountNameValid (value: string) {
11} 10}
12 11
13function isAccountIdExist (id: number | string, res: Response) { 12function isAccountIdExist (id: number | string, res: Response) {
14 let promise: Bluebird<AccountInstance> 13 let promise: Bluebird<AccountModel>
15 14
16 if (validator.isInt('' + id)) { 15 if (validator.isInt('' + id)) {
17 promise = db.Account.load(+id) 16 promise = AccountModel.load(+id)
18 } else { // UUID 17 } else { // UUID
19 promise = db.Account.loadByUUID('' + id) 18 promise = AccountModel.loadByUUID('' + id)
20 } 19 }
21 20
22 return isAccountExist(promise, res) 21 return isAccountExist(promise, res)
23} 22}
24 23
25function isLocalAccountNameExist (name: string, res: Response) { 24function isLocalAccountNameExist (name: string, res: Response) {
26 const promise = db.Account.loadLocalByName(name) 25 const promise = AccountModel.loadLocalByName(name)
27 26
28 return isAccountExist(promise, res) 27 return isAccountExist(promise, res)
29} 28}
30 29
31async function isAccountExist (p: Bluebird<AccountInstance>, res: Response) { 30async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
32 const account = await p 31 const account = await p
33 32
34 if (!account) { 33 if (!account) {
diff --git a/server/helpers/custom-validators/activitypub/account.ts b/server/helpers/custom-validators/activitypub/account.ts
index cab39a654..10bf81e8a 100644
--- a/server/helpers/custom-validators/activitypub/account.ts
+++ b/server/helpers/custom-validators/activitypub/account.ts
@@ -1,5 +1,5 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' 2import { CONSTRAINTS_FIELDS } from '../../../initializers'
3import { isAccountNameValid } from '../accounts' 3import { isAccountNameValid } from '../accounts'
4import { exists, isUUIDValid } from '../misc' 4import { exists, isUUIDValid } from '../misc'
5import { isActivityPubUrlValid, isBaseActivityValid } from './misc' 5import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts
index 3a0e8197c..043e3c55e 100644
--- a/server/helpers/custom-validators/activitypub/activity.ts
+++ b/server/helpers/custom-validators/activitypub/activity.ts
@@ -1,8 +1,9 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' 2import { Activity, ActivityType } from '../../../../shared/models/activitypub'
3import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' 3import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
4import { isAnnounceActivityValid } from './announce' 4import { isAnnounceActivityValid } from './announce'
5import { isActivityPubUrlValid } from './misc' 5import { isActivityPubUrlValid } from './misc'
6import { isDislikeActivityValid, isLikeActivityValid } from './rate'
6import { isUndoActivityValid } from './undo' 7import { isUndoActivityValid } from './undo'
7import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' 8import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels'
8import { 9import {
@@ -12,7 +13,6 @@ import {
12 isVideoTorrentUpdateActivityValid 13 isVideoTorrentUpdateActivityValid
13} from './videos' 14} from './videos'
14import { isViewActivityValid } from './view' 15import { isViewActivityValid } from './view'
15import { isDislikeActivityValid, isLikeActivityValid } from './rate'
16 16
17function isRootActivityValid (activity: any) { 17function isRootActivityValid (activity: any) {
18 return Array.isArray(activity['@context']) && 18 return Array.isArray(activity['@context']) &&
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts
index 1bbfd0fc4..65f5ca809 100644
--- a/server/helpers/custom-validators/activitypub/misc.ts
+++ b/server/helpers/custom-validators/activitypub/misc.ts
@@ -1,7 +1,7 @@
1import * as validator from 'validator' 1import * as validator from 'validator'
2import { exists } from '../misc' 2import { CONSTRAINTS_FIELDS } from '../../../initializers'
3import { isTestInstance } from '../../core-utils' 3import { isTestInstance } from '../../core-utils'
4import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' 4import { exists } from '../misc'
5 5
6function isActivityPubUrlValid (url: string) { 6function isActivityPubUrlValid (url: string) {
7 const isURLOptions = { 7 const isURLOptions = {
diff --git a/server/helpers/custom-validators/index.ts b/server/helpers/custom-validators/index.ts
deleted file mode 100644
index d3b2f5393..000000000
--- a/server/helpers/custom-validators/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
1export * from './activitypub'
2export * from './misc'
3export * from './servers'
4export * from './servers'
5export * from './users'
6export * from './accounts'
7export * from './video-channels'
8export * from './videos'
9export * from './webfinger'
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts
index 3de9f041b..6bc96bf51 100644
--- a/server/helpers/custom-validators/video-channels.ts
+++ b/server/helpers/custom-validators/video-channels.ts
@@ -2,8 +2,8 @@ import * as express from 'express'
2import 'express-validator' 2import 'express-validator'
3import 'multer' 3import 'multer'
4import * as validator from 'validator' 4import * as validator from 'validator'
5import { CONSTRAINTS_FIELDS, database as db } from '../../initializers' 5import { CONSTRAINTS_FIELDS } from '../../initializers'
6import { VideoChannelInstance } from '../../models' 6import { VideoChannelModel } from '../../models/video/video-channel'
7import { exists } from './misc' 7import { exists } from './misc'
8 8
9const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS 9const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS
@@ -17,11 +17,11 @@ function isVideoChannelNameValid (value: string) {
17} 17}
18 18
19async function isVideoChannelExist (id: string, res: express.Response) { 19async function isVideoChannelExist (id: string, res: express.Response) {
20 let videoChannel: VideoChannelInstance 20 let videoChannel: VideoChannelModel
21 if (validator.isInt(id)) { 21 if (validator.isInt(id)) {
22 videoChannel = await db.VideoChannel.loadAndPopulateAccount(+id) 22 videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
23 } else { // UUID 23 } else { // UUID
24 videoChannel = await db.VideoChannel.loadByUUIDAndPopulateAccount(id) 24 videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id)
25 } 25 }
26 26
27 if (!videoChannel) { 27 if (!videoChannel) {
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 37fa8b08a..ee9d0ed19 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -4,10 +4,15 @@ import { values } from 'lodash'
4import 'multer' 4import 'multer'
5import * as validator from 'validator' 5import * as validator from 'validator'
6import { VideoRateType } from '../../../shared' 6import { VideoRateType } from '../../../shared'
7import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_RATE_TYPES } from '../../initializers' 7import {
8import { VIDEO_PRIVACIES } from '../../initializers/constants' 8 CONSTRAINTS_FIELDS,
9import { database as db } from '../../initializers/database' 9 VIDEO_CATEGORIES,
10import { VideoInstance } from '../../models/video/video-interface' 10 VIDEO_LANGUAGES,
11 VIDEO_LICENCES,
12 VIDEO_PRIVACIES,
13 VIDEO_RATE_TYPES
14} from '../../initializers'
15import { VideoModel } from '../../models/video/video'
11import { exists, isArray } from './misc' 16import { exists, isArray } from './misc'
12 17
13const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS 18const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
@@ -100,12 +105,12 @@ function isVideoFileSizeValid (value: string) {
100} 105}
101 106
102async function isVideoExist (id: string, res: Response) { 107async function isVideoExist (id: string, res: Response) {
103 let video: VideoInstance 108 let video: VideoModel
104 109
105 if (validator.isInt(id)) { 110 if (validator.isInt(id)) {
106 video = await db.Video.loadAndPopulateAccountAndServerAndTags(+id) 111 video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id)
107 } else { // UUID 112 } else { // UUID
108 video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id) 113 video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id)
109 } 114 }
110 115
111 if (!video) { 116 if (!video) {
diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts
index e93115d81..38f6b938d 100644
--- a/server/helpers/custom-validators/webfinger.ts
+++ b/server/helpers/custom-validators/webfinger.ts
@@ -1,6 +1,4 @@
1import 'express-validator' 1import { CONFIG } from '../../initializers'
2import 'multer'
3import { CONFIG } from '../../initializers/constants'
4import { exists } from './misc' 2import { exists } from './misc'
5 3
6function isWebfingerResourceValid (value: string) { 4function isWebfingerResourceValid (value: string) {
@@ -13,9 +11,7 @@ function isWebfingerResourceValid (value: string) {
13 11
14 const host = accountParts[1] 12 const host = accountParts[1]
15 13
16 if (host !== CONFIG.WEBSERVER.HOST) return false 14 return host === CONFIG.WEBSERVER.HOST
17
18 return true
19} 15}
20 16
21// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 8ad205961..c2581f460 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,5 +1,5 @@
1import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
2import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' 2import { VideoResolution } from '../../shared/models/videos'
3import { CONFIG } from '../initializers' 3import { CONFIG } from '../initializers'
4 4
5function getVideoFileHeight (path: string) { 5function getVideoFileHeight (path: string) {
diff --git a/server/helpers/index.ts b/server/helpers/index.ts
index 2c7ac3954..d96bc48e9 100644
--- a/server/helpers/index.ts
+++ b/server/helpers/index.ts
@@ -1,7 +1,6 @@
1export * from './activitypub' 1export * from './activitypub'
2export * from './core-utils' 2export * from './core-utils'
3export * from './logger' 3export * from './logger'
4export * from './custom-validators'
5export * from './ffmpeg-utils' 4export * from './ffmpeg-utils'
6export * from './database-utils' 5export * from './database-utils'
7export * from './peertube-crypto' 6export * from './peertube-crypto'
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 8d809d16d..2676133db 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -2,7 +2,7 @@
2import * as mkdirp from 'mkdirp' 2import * as mkdirp from 'mkdirp'
3import * as path from 'path' 3import * as path from 'path'
4import * as winston from 'winston' 4import * as winston from 'winston'
5import { CONFIG } from '../initializers/constants' 5import { CONFIG } from '../initializers'
6 6
7const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 7const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
8 8
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 74e4cc703..c4c735cb8 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,5 +1,5 @@
1import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' 1import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers'
2import { AccountInstance } from '../models/account/account-interface' 2import { AccountModel } from '../models/account/account'
3import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' 3import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils'
4import { jsig } from './custom-jsonld-signature' 4import { jsig } from './custom-jsonld-signature'
5import { logger } from './logger' 5import { logger } from './logger'
@@ -13,7 +13,7 @@ async function createPrivateAndPublicKeys () {
13 return { privateKey: key, publicKey } 13 return { privateKey: key, publicKey }
14} 14}
15 15
16function isSignatureVerified (fromAccount: AccountInstance, signedDocument: object) { 16function isSignatureVerified (fromAccount: AccountModel, signedDocument: object) {
17 const publicKeyObject = { 17 const publicKeyObject = {
18 '@context': jsig.SECURITY_CONTEXT_URL, 18 '@context': jsig.SECURITY_CONTEXT_URL,
19 '@id': fromAccount.url, 19 '@id': fromAccount.url,
@@ -40,7 +40,7 @@ function isSignatureVerified (fromAccount: AccountInstance, signedDocument: obje
40 }) 40 })
41} 41}
42 42
43function signObject (byAccount: AccountInstance, data: any) { 43function signObject (byAccount: AccountModel, data: any) {
44 const options = { 44 const options = {
45 privateKeyPem: byAccount.privateKey, 45 privateKeyPem: byAccount.privateKey,
46 creator: byAccount.url 46 creator: byAccount.url
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 3464341e6..cb5e536b8 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,9 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as Sequelize from 'sequelize' 2import { Model } from 'sequelize-typescript'
3import { ResultList } from '../../shared' 3import { ResultList } from '../../shared'
4import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' 4import { VideoResolution } from '../../shared/models/videos'
5import { CONFIG, database as db } from '../initializers' 5import { CONFIG } from '../initializers'
6import { AccountInstance } from '../models/account/account-interface' 6import { AccountModel } from '../models/account/account'
7import { UserModel } from '../models/account/user'
7import { pseudoRandomBytesPromise } from './core-utils' 8import { pseudoRandomBytesPromise } from './core-utils'
8import { logger } from './logger' 9import { logger } from './logger'
9 10
@@ -46,7 +47,7 @@ async function isSignupAllowed () {
46 return true 47 return true
47 } 48 }
48 49
49 const totalUsers = await db.User.countTotal() 50 const totalUsers = await UserModel.countTotal()
50 51
51 return totalUsers < CONFIG.SIGNUP.LIMIT 52 return totalUsers < CONFIG.SIGNUP.LIMIT
52} 53}
@@ -72,17 +73,17 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
72 return resolutionsEnabled 73 return resolutionsEnabled
73} 74}
74 75
75function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields: object) { 76function resetSequelizeInstance (instance: Model<any>, savedFields: object) {
76 Object.keys(savedFields).forEach(key => { 77 Object.keys(savedFields).forEach(key => {
77 const value = savedFields[key] 78 const value = savedFields[key]
78 instance.set(key, value) 79 instance.set(key, value)
79 }) 80 })
80} 81}
81 82
82let serverAccount: AccountInstance 83let serverAccount: AccountModel
83async function getServerAccount () { 84async function getServerAccount () {
84 if (serverAccount === undefined) { 85 if (serverAccount === undefined) {
85 serverAccount = await db.Account.loadApplication() 86 serverAccount = await AccountModel.loadApplication()
86 } 87 }
87 88
88 if (!serverAccount) { 89 if (!serverAccount) {
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
index ab2888981..d98068cd7 100644
--- a/server/helpers/webfinger.ts
+++ b/server/helpers/webfinger.ts
@@ -1,8 +1,8 @@
1import * as WebFinger from 'webfinger.js' 1import * as WebFinger from 'webfinger.js'
2import { WebFingerData } from '../../shared' 2import { WebFingerData } from '../../shared'
3import { fetchRemoteAccount } from '../lib/activitypub/account' 3import { fetchRemoteAccount } from '../lib/activitypub'
4import { isTestInstance } from './core-utils' 4import { isTestInstance } from './core-utils'
5import { isActivityPubUrlValid } from './custom-validators' 5import { isActivityPubUrlValid } from './custom-validators/activitypub'
6 6
7const webfinger = new WebFinger({ 7const webfinger = new WebFinger({
8 webfist_fallback: false, 8 webfist_fallback: false,
diff --git a/server/initializers/checker.ts b/server/initializers/checker.ts
index 317d59423..7e76990b5 100644
--- a/server/initializers/checker.ts
+++ b/server/initializers/checker.ts
@@ -1,8 +1,8 @@
1import * as config from 'config' 1import * as config from 'config'
2import { promisify0 } from '../helpers/core-utils' 2import { promisify0 } from '../helpers'
3import { UserModel } from '../models/account/user-interface' 3import { UserModel } from '../models/account/user'
4import { ApplicationModel } from '../models/application/application-interface' 4import { ApplicationModel } from '../models/application/application'
5import { OAuthClientModel } from '../models/oauth/oauth-client-interface' 5import { OAuthClientModel } from '../models/oauth/oauth-client'
6 6
7// Some checks on configuration files 7// Some checks on configuration files
8function checkConfig () { 8function checkConfig () {
@@ -57,22 +57,22 @@ async function checkFFmpeg (CONFIG: { TRANSCODING: { ENABLED: boolean } }) {
57} 57}
58 58
59// We get db by param to not import it in this file (import orders) 59// We get db by param to not import it in this file (import orders)
60async function clientsExist (OAuthClient: OAuthClientModel) { 60async function clientsExist () {
61 const totalClients = await OAuthClient.countTotal() 61 const totalClients = await OAuthClientModel.countTotal()
62 62
63 return totalClients !== 0 63 return totalClients !== 0
64} 64}
65 65
66// We get db by param to not import it in this file (import orders) 66// We get db by param to not import it in this file (import orders)
67async function usersExist (User: UserModel) { 67async function usersExist () {
68 const totalUsers = await User.countTotal() 68 const totalUsers = await UserModel.countTotal()
69 69
70 return totalUsers !== 0 70 return totalUsers !== 0
71} 71}
72 72
73// We get db by param to not import it in this file (import orders) 73// We get db by param to not import it in this file (import orders)
74async function applicationExist (Application: ApplicationModel) { 74async function applicationExist () {
75 const totalApplication = await Application.countTotal() 75 const totalApplication = await ApplicationModel.countTotal()
76 76
77 return totalApplication !== 0 77 return totalApplication !== 0
78} 78}
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 7be7a5f95..f539eb2ee 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,16 +1,10 @@
1import * as config from 'config' 1import * as config from 'config'
2import { join } from 'path' 2import { join } from 'path'
3 3import { JobCategory, JobState, VideoRateType } from '../../shared/models'
4import { FollowState } from '../../shared/models/accounts'
5import { VideoPrivacy } from '../../shared/models/videos'
4// Do not use barrels, remain constants as independent as possible 6// Do not use barrels, remain constants as independent as possible
5import { root, isTestInstance } from '../helpers/core-utils' 7import { isTestInstance, root } from '../helpers/core-utils'
6
7import {
8 VideoRateType,
9 JobState,
10 JobCategory
11} from '../../shared/models'
12import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum'
13import { FollowState } from '../../shared/models/accounts/follow.model'
14 8
15// --------------------------------------------------------------------------- 9// ---------------------------------------------------------------------------
16 10
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index bb95992e1..f9e24c6b8 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -1,72 +1,43 @@
1import { join } from 'path' 1import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
2import { flattenDepth } from 'lodash' 2import { isTestInstance } from '../helpers/core-utils'
3require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 3import { logger } from '../helpers/logger'
4import * as Sequelize from 'sequelize'
5import { AvatarModel } from '../models/avatar'
6 4
5import { AccountModel } from '../models/account/account'
6import { AccountFollowModel } from '../models/account/account-follow'
7import { AccountVideoRateModel } from '../models/account/account-video-rate'
8import { UserModel } from '../models/account/user'
9import { ApplicationModel } from '../models/application/application'
10import { AvatarModel } from '../models/avatar/avatar'
11import { JobModel } from '../models/job/job'
12import { OAuthClientModel } from '../models/oauth/oauth-client'
13import { OAuthTokenModel } from '../models/oauth/oauth-token'
14import { ServerModel } from '../models/server/server'
15import { TagModel } from '../models/video/tag'
16import { VideoModel } from '../models/video/video'
17import { VideoAbuseModel } from '../models/video/video-abuse'
18import { VideoBlacklistModel } from '../models/video/video-blacklist'
19import { VideoChannelModel } from '../models/video/video-channel'
20import { VideoChannelShareModel } from '../models/video/video-channel-share'
21import { VideoFileModel } from '../models/video/video-file'
22import { VideoShareModel } from '../models/video/video-share'
23import { VideoTagModel } from '../models/video/video-tag'
7import { CONFIG } from './constants' 24import { CONFIG } from './constants'
8// Do not use barrel, we need to load database first
9import { logger } from '../helpers/logger'
10import { isTestInstance, readdirPromise } from '../helpers/core-utils'
11 25
12import { VideoModel } from './../models/video/video-interface' 26require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
13import { VideoTagModel } from './../models/video/video-tag-interface'
14import { BlacklistedVideoModel } from './../models/video/video-blacklist-interface'
15import { VideoFileModel } from './../models/video/video-file-interface'
16import { VideoAbuseModel } from './../models/video/video-abuse-interface'
17import { VideoChannelModel } from './../models/video/video-channel-interface'
18import { UserModel } from '../models/account/user-interface'
19import { AccountVideoRateModel } from '../models/account/account-video-rate-interface'
20import { AccountFollowModel } from '../models/account/account-follow-interface'
21import { TagModel } from './../models/video/tag-interface'
22import { ServerModel } from '../models/server/server-interface'
23import { OAuthTokenModel } from './../models/oauth/oauth-token-interface'
24import { OAuthClientModel } from './../models/oauth/oauth-client-interface'
25import { JobModel } from './../models/job/job-interface'
26import { AccountModel } from './../models/account/account-interface'
27import { ApplicationModel } from './../models/application/application-interface'
28import { VideoChannelShareModel } from '../models/video/video-channel-share-interface'
29import { VideoShareModel } from '../models/video/video-share-interface'
30 27
31const dbname = CONFIG.DATABASE.DBNAME 28const dbname = CONFIG.DATABASE.DBNAME
32const username = CONFIG.DATABASE.USERNAME 29const username = CONFIG.DATABASE.USERNAME
33const password = CONFIG.DATABASE.PASSWORD 30const password = CONFIG.DATABASE.PASSWORD
34 31
35export type PeerTubeDatabase = { 32const sequelizeTypescript = new SequelizeTypescript({
36 sequelize?: Sequelize.Sequelize, 33 database: dbname,
37 init?: (silent: boolean) => Promise<void>,
38
39 Application?: ApplicationModel,
40 Avatar?: AvatarModel,
41 Account?: AccountModel,
42 Job?: JobModel,
43 OAuthClient?: OAuthClientModel,
44 OAuthToken?: OAuthTokenModel,
45 Server?: ServerModel,
46 Tag?: TagModel,
47 AccountVideoRate?: AccountVideoRateModel,
48 AccountFollow?: AccountFollowModel,
49 User?: UserModel,
50 VideoAbuse?: VideoAbuseModel,
51 VideoChannel?: VideoChannelModel,
52 VideoChannelShare?: VideoChannelShareModel,
53 VideoShare?: VideoShareModel,
54 VideoFile?: VideoFileModel,
55 BlacklistedVideo?: BlacklistedVideoModel,
56 VideoTag?: VideoTagModel,
57 Video?: VideoModel
58}
59
60const database: PeerTubeDatabase = {}
61
62const sequelize = new Sequelize(dbname, username, password, {
63 dialect: 'postgres', 34 dialect: 'postgres',
64 host: CONFIG.DATABASE.HOSTNAME, 35 username,
65 port: CONFIG.DATABASE.PORT, 36 password,
37 modelPaths: [__dirname + '/models'],
66 benchmark: isTestInstance(), 38 benchmark: isTestInstance(),
67 isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE, 39 isolationLevel: SequelizeTypescript.Transaction.ISOLATION_LEVELS.SERIALIZABLE,
68 operatorsAliases: false, 40 operatorsAliases: false,
69
70 logging: (message: string, benchmark: number) => { 41 logging: (message: string, benchmark: number) => {
71 if (process.env.NODE_DB_LOG === 'false') return 42 if (process.env.NODE_DB_LOG === 'false') return
72 43
@@ -79,34 +50,28 @@ const sequelize = new Sequelize(dbname, username, password, {
79 } 50 }
80}) 51})
81 52
82database.sequelize = sequelize 53async function initDatabase (silent: boolean) {
83 54 sequelizeTypescript.addModels([
84database.init = async (silent: boolean) => { 55 ApplicationModel,
85 const modelDirectory = join(__dirname, '..', 'models') 56 AvatarModel,
86 57 AccountModel,
87 const filePaths = await getModelFiles(modelDirectory) 58 JobModel,
88 59 OAuthClientModel,
89 for (const filePath of filePaths) { 60 OAuthTokenModel,
90 try { 61 ServerModel,
91 const model = sequelize.import(filePath) 62 TagModel,
92 63 AccountVideoRateModel,
93 database[model['name']] = model 64 AccountFollowModel,
94 } catch (err) { 65 UserModel,
95 logger.error('Cannot import database model %s.', filePath, err) 66 VideoAbuseModel,
96 process.exit(0) 67 VideoChannelModel,
97 } 68 VideoChannelShareModel,
98 } 69 VideoShareModel,
99 70 VideoFileModel,
100 for (const modelName of Object.keys(database)) { 71 VideoBlacklistModel,
101 if ('associate' in database[modelName]) { 72 VideoTagModel,
102 try { 73 VideoModel
103 database[modelName].associate(database) 74 ])
104 } catch (err) {
105 logger.error('Cannot associate model %s.', modelName, err)
106 process.exit(0)
107 }
108 }
109 }
110 75
111 if (!silent) logger.info('Database %s is ready.', dbname) 76 if (!silent) logger.info('Database %s is ready.', dbname)
112 77
@@ -116,51 +81,6 @@ database.init = async (silent: boolean) => {
116// --------------------------------------------------------------------------- 81// ---------------------------------------------------------------------------
117 82
118export { 83export {
119 database 84 initDatabase,
120} 85 sequelizeTypescript
121
122// ---------------------------------------------------------------------------
123
124async function getModelFiles (modelDirectory: string) {
125 const files = await readdirPromise(modelDirectory)
126 const directories = files.filter(directory => {
127 // Find directories
128 if (
129 directory.endsWith('.js.map') ||
130 directory === 'index.js' || directory === 'index.ts' ||
131 directory === 'utils.js' || directory === 'utils.ts'
132 ) return false
133
134 return true
135 })
136
137 const tasks: Promise<any>[] = []
138
139 // For each directory we read it and append model in the modelFilePaths array
140 for (const directory of directories) {
141 const modelDirectoryPath = join(modelDirectory, directory)
142
143 const promise = readdirPromise(modelDirectoryPath)
144 .then(files => {
145 const filteredFiles = files
146 .filter(file => {
147 if (
148 file === 'index.js' || file === 'index.ts' ||
149 file === 'utils.js' || file === 'utils.ts' ||
150 file.endsWith('-interface.js') || file.endsWith('-interface.ts') ||
151 file.endsWith('.js.map')
152 ) return false
153
154 return true
155 })
156 .map(file => join(modelDirectoryPath, file))
157
158 return filteredFiles
159 })
160
161 tasks.push(promise)
162 }
163
164 const filteredFilesArray: string[][] = await Promise.all(tasks)
165 return flattenDepth<string>(filteredFilesArray, 1)
166} 86}
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 954516057..5452743b6 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -1,16 +1,17 @@
1import * as passwordGenerator from 'password-generator' 1import * as passwordGenerator from 'password-generator'
2import { UserRole } from '../../shared' 2import { UserRole } from '../../shared'
3import { logger, mkdirpPromise, rimrafPromise } from '../helpers' 3import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers'
4import { createUserAccountAndChannel } from '../lib' 4import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib'
5import { createLocalAccountWithoutKeys } from '../lib/user' 5import { UserModel } from '../models/account/user'
6import { ApplicationModel } from '../models/application/application'
7import { OAuthClientModel } from '../models/oauth/oauth-client'
6import { applicationExist, clientsExist, usersExist } from './checker' 8import { applicationExist, clientsExist, usersExist } from './checker'
7import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' 9import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants'
8import { database as db } from './database' 10import { sequelizeTypescript } from './database'
9import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto'
10 11
11async function installApplication () { 12async function installApplication () {
12 try { 13 try {
13 await db.sequelize.sync() 14 await sequelizeTypescript.sync()
14 await removeCacheDirectories() 15 await removeCacheDirectories()
15 await createDirectoriesIfNotExist() 16 await createDirectoriesIfNotExist()
16 await createApplicationIfNotExist() 17 await createApplicationIfNotExist()
@@ -64,7 +65,7 @@ function createDirectoriesIfNotExist () {
64} 65}
65 66
66async function createOAuthClientIfNotExist () { 67async function createOAuthClientIfNotExist () {
67 const exist = await clientsExist(db.OAuthClient) 68 const exist = await clientsExist()
68 // Nothing to do, clients already exist 69 // Nothing to do, clients already exist
69 if (exist === true) return undefined 70 if (exist === true) return undefined
70 71
@@ -72,7 +73,7 @@ async function createOAuthClientIfNotExist () {
72 73
73 const id = passwordGenerator(32, false, /[a-z0-9]/) 74 const id = passwordGenerator(32, false, /[a-z0-9]/)
74 const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/) 75 const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/)
75 const client = db.OAuthClient.build({ 76 const client = new OAuthClientModel({
76 clientId: id, 77 clientId: id,
77 clientSecret: secret, 78 clientSecret: secret,
78 grants: [ 'password', 'refresh_token' ], 79 grants: [ 'password', 'refresh_token' ],
@@ -87,7 +88,7 @@ async function createOAuthClientIfNotExist () {
87} 88}
88 89
89async function createOAuthAdminIfNotExist () { 90async function createOAuthAdminIfNotExist () {
90 const exist = await usersExist(db.User) 91 const exist = await usersExist()
91 // Nothing to do, users already exist 92 // Nothing to do, users already exist
92 if (exist === true) return undefined 93 if (exist === true) return undefined
93 94
@@ -120,7 +121,7 @@ async function createOAuthAdminIfNotExist () {
120 role, 121 role,
121 videoQuota: -1 122 videoQuota: -1
122 } 123 }
123 const user = db.User.build(userData) 124 const user = new UserModel(userData)
124 125
125 await createUserAccountAndChannel(user, validatePassword) 126 await createUserAccountAndChannel(user, validatePassword)
126 logger.info('Username: ' + username) 127 logger.info('Username: ' + username)
@@ -128,12 +129,12 @@ async function createOAuthAdminIfNotExist () {
128} 129}
129 130
130async function createApplicationIfNotExist () { 131async function createApplicationIfNotExist () {
131 const exist = await applicationExist(db.Application) 132 const exist = await applicationExist()
132 // Nothing to do, application already exist 133 // Nothing to do, application already exist
133 if (exist === true) return undefined 134 if (exist === true) return undefined
134 135
135 logger.info('Creating Application table.') 136 logger.info('Creating Application table.')
136 const applicationInstance = await db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }) 137 const applicationInstance = await ApplicationModel.create({ migrationVersion: LAST_MIGRATION_VERSION })
137 138
138 logger.info('Creating application account.') 139 logger.info('Creating application account.')
139 140
diff --git a/server/initializers/migrations/0065-video-file-size.ts b/server/initializers/migrations/0065-video-file-size.ts
index 58f8f3bcc..4e2075f8b 100644
--- a/server/initializers/migrations/0065-video-file-size.ts
+++ b/server/initializers/migrations/0065-video-file-size.ts
@@ -1,8 +1,7 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3import { stat } from 'fs' 3import { stat } from 'fs'
4 4import { VideoModel } from '../../models/video/video'
5import { VideoInstance } from '../../models'
6 5
7function up (utils: { 6function up (utils: {
8 transaction: Sequelize.Transaction, 7 transaction: Sequelize.Transaction,
@@ -11,7 +10,7 @@ function up (utils: {
11 db: any 10 db: any
12}): Promise<void> { 11}): Promise<void> {
13 return utils.db.Video.listOwnedAndPopulateAuthorAndTags() 12 return utils.db.Video.listOwnedAndPopulateAuthorAndTags()
14 .then((videos: VideoInstance[]) => { 13 .then((videos: VideoModel[]) => {
15 const tasks: Promise<any>[] = [] 14 const tasks: Promise<any>[] = []
16 15
17 videos.forEach(video => { 16 videos.forEach(video => {
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index 50a0adc14..fb42e1d57 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -4,14 +4,14 @@ import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
4import { shareVideoByServer } from '../../lib/activitypub/share' 4import { shareVideoByServer } from '../../lib/activitypub/share'
5import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' 5import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url'
6import { createLocalAccountWithoutKeys } from '../../lib/user' 6import { createLocalAccountWithoutKeys } from '../../lib/user'
7import { ApplicationModel } from '../../models/application/application'
7import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' 8import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants'
8import { PeerTubeDatabase } from '../database'
9 9
10async function up (utils: { 10async function up (utils: {
11 transaction: Sequelize.Transaction, 11 transaction: Sequelize.Transaction,
12 queryInterface: Sequelize.QueryInterface, 12 queryInterface: Sequelize.QueryInterface,
13 sequelize: Sequelize.Sequelize, 13 sequelize: Sequelize.Sequelize,
14 db: PeerTubeDatabase 14 db: any
15}): Promise<void> { 15}): Promise<void> {
16 const q = utils.queryInterface 16 const q = utils.queryInterface
17 const db = utils.db 17 const db = utils.db
@@ -65,7 +65,7 @@ async function up (utils: {
65 65
66 // Create application account 66 // Create application account
67 { 67 {
68 const applicationInstance = await db.Application.findOne() 68 const applicationInstance = await ApplicationModel.findOne()
69 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined) 69 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined)
70 70
71 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 71 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
diff --git a/server/initializers/migrations/0105-server-mail.ts b/server/initializers/migrations/0105-server-mail.ts
index 5836992d5..4b9600e91 100644
--- a/server/initializers/migrations/0105-server-mail.ts
+++ b/server/initializers/migrations/0105-server-mail.ts
@@ -1,11 +1,10 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { PeerTubeDatabase } from '../database'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize,
8 db: PeerTubeDatabase 7 db: any
9}): Promise<void> { 8}): Promise<void> {
10 await utils.queryInterface.removeColumn('Servers', 'email') 9 await utils.queryInterface.removeColumn('Servers', 'email')
11} 10}
diff --git a/server/initializers/migrations/0110-server-key.ts b/server/initializers/migrations/0110-server-key.ts
index 560353945..5ff6daf69 100644
--- a/server/initializers/migrations/0110-server-key.ts
+++ b/server/initializers/migrations/0110-server-key.ts
@@ -1,11 +1,10 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { PeerTubeDatabase } from '../database'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize,
8 db: PeerTubeDatabase 7 db: any
9}): Promise<void> { 8}): Promise<void> {
10 await utils.queryInterface.removeColumn('Servers', 'publicKey') 9 await utils.queryInterface.removeColumn('Servers', 'publicKey')
11} 10}
diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts
index 2b947ceda..b318e8163 100644
--- a/server/initializers/migrations/0115-account-avatar.ts
+++ b/server/initializers/migrations/0115-account-avatar.ts
@@ -1,11 +1,10 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { PeerTubeDatabase } from '../database'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize,
8 db: PeerTubeDatabase 7 db: any
9}): Promise<void> { 8}): Promise<void> {
10 await utils.db.Avatar.sync() 9 await utils.db.Avatar.sync()
11 10
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts
index 9130d10ee..63f3984dd 100644
--- a/server/initializers/migrations/0120-video-null.ts
+++ b/server/initializers/migrations/0120-video-null.ts
@@ -1,12 +1,11 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants' 2import { CONSTRAINTS_FIELDS } from '../constants'
3import { PeerTubeDatabase } from '../database'
4 3
5async function up (utils: { 4async function up (utils: {
6 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction,
7 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface,
8 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize,
9 db: PeerTubeDatabase 8 db: any
10}): Promise<void> { 9}): Promise<void> {
11 10
12 { 11 {
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts
index 187c9be6e..f3a05cc8c 100644
--- a/server/initializers/migrator.ts
+++ b/server/initializers/migrator.ts
@@ -1,19 +1,19 @@
1import * as path from 'path' 1import * as path from 'path'
2
3import { database as db } from './database'
4import { LAST_MIGRATION_VERSION } from './constants'
5import { logger, readdirPromise } from '../helpers' 2import { logger, readdirPromise } from '../helpers'
3import { ApplicationModel } from '../models/application/application'
4import { LAST_MIGRATION_VERSION } from './constants'
5import { sequelizeTypescript } from './database'
6 6
7async function migrate () { 7async function migrate () {
8 const tables = await db.sequelize.getQueryInterface().showAllTables() 8 const tables = await sequelizeTypescript.getQueryInterface().showAllTables()
9 9
10 // No tables, we don't need to migrate anything 10 // No tables, we don't need to migrate anything
11 // The installer will do that 11 // The installer will do that
12 if (tables.length === 0) return 12 if (tables.length === 0) return
13 13
14 let actualVersion = await db.Application.loadMigrationVersion() 14 let actualVersion = await ApplicationModel.loadMigrationVersion()
15 if (actualVersion === null) { 15 if (actualVersion === null) {
16 await db.Application.create({ migrationVersion: 0 }) 16 await ApplicationModel.create({ migrationVersion: 0 })
17 actualVersion = 0 17 actualVersion = 0
18 } 18 }
19 19
@@ -78,17 +78,16 @@ async function executeMigration (actualVersion: number, entity: { version: strin
78 78
79 const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) 79 const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName))
80 80
81 await db.sequelize.transaction(async t => { 81 await sequelizeTypescript.transaction(async t => {
82 const options = { 82 const options = {
83 transaction: t, 83 transaction: t,
84 queryInterface: db.sequelize.getQueryInterface(), 84 queryInterface: sequelizeTypescript.getQueryInterface(),
85 sequelize: db.sequelize, 85 sequelize: sequelizeTypescript
86 db
87 } 86 }
88 87
89 await migrationScript.up(options) 88 await migrationScript.up(options)
90 89
91 // Update the new migration version 90 // Update the new migration version
92 await db.Application.updateMigrationVersion(versionScript, t) 91 await ApplicationModel.updateMigrationVersion(versionScript, t)
93 }) 92 })
94} 93}
diff --git a/server/lib/activitypub/account.ts b/server/lib/activitypub/account.ts
index 906c8ff29..45690b88d 100644
--- a/server/lib/activitypub/account.ts
+++ b/server/lib/activitypub/account.ts
@@ -1,17 +1,15 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import * as url from 'url'
3import { ActivityPubActor } from '../../../shared/models/activitypub/activitypub-actor'
4import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub/account'
5import { retryTransactionWrapper } from '../../helpers/database-utils'
6import { logger } from '../../helpers/logger'
7import { doRequest } from '../../helpers/requests'
8import { ACTIVITY_PUB } from '../../initializers/constants'
9import { database as db } from '../../initializers/database'
10import { AccountInstance } from '../../models/account/account-interface'
11import { Transaction } from 'sequelize' 2import { Transaction } from 'sequelize'
3import * as url from 'url'
4import { ActivityPubActor } from '../../../shared/models/activitypub'
5import { doRequest, logger, retryTransactionWrapper } from '../../helpers'
6import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub'
7import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers'
8import { AccountModel } from '../../models/account/account'
9import { ServerModel } from '../../models/server/server'
12 10
13async function getOrCreateAccountAndServer (accountUrl: string) { 11async function getOrCreateAccountAndServer (accountUrl: string) {
14 let account = await db.Account.loadByUrl(accountUrl) 12 let account = await AccountModel.loadByUrl(accountUrl)
15 13
16 // We don't have this account in our database, fetch it on remote 14 // We don't have this account in our database, fetch it on remote
17 if (!account) { 15 if (!account) {
@@ -28,11 +26,11 @@ async function getOrCreateAccountAndServer (accountUrl: string) {
28 return account 26 return account
29} 27}
30 28
31function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transaction): Bluebird<AccountInstance> | Promise<AccountInstance> { 29function saveAccountAndServerIfNotExist (account: AccountModel, t?: Transaction): Bluebird<AccountModel> | Promise<AccountModel> {
32 if (t !== undefined) { 30 if (t !== undefined) {
33 return save(t) 31 return save(t)
34 } else { 32 } else {
35 return db.sequelize.transaction(t => { 33 return sequelizeTypescript.transaction(t => {
36 return save(t) 34 return save(t)
37 }) 35 })
38 } 36 }
@@ -49,7 +47,7 @@ function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transacti
49 }, 47 },
50 transaction: t 48 transaction: t
51 } 49 }
52 const [ server ] = await db.Server.findOrCreate(serverOptions) 50 const [ server ] = await ServerModel.findOrCreate(serverOptions)
53 51
54 // Save our new account in database 52 // Save our new account in database
55 account.set('serverId', server.id) 53 account.set('serverId', server.id)
@@ -87,7 +85,7 @@ async function fetchRemoteAccount (accountUrl: string) {
87 const followersCount = await fetchAccountCount(accountJSON.followers) 85 const followersCount = await fetchAccountCount(accountJSON.followers)
88 const followingCount = await fetchAccountCount(accountJSON.following) 86 const followingCount = await fetchAccountCount(accountJSON.following)
89 87
90 const account = db.Account.build({ 88 return new AccountModel({
91 uuid: accountJSON.uuid, 89 uuid: accountJSON.uuid,
92 name: accountJSON.preferredUsername, 90 name: accountJSON.preferredUsername,
93 url: accountJSON.url, 91 url: accountJSON.url,
@@ -101,8 +99,6 @@ async function fetchRemoteAccount (accountUrl: string) {
101 followersUrl: accountJSON.followers, 99 followersUrl: accountJSON.followers,
102 followingUrl: accountJSON.following 100 followingUrl: accountJSON.following
103 }) 101 })
104
105 return account
106} 102}
107 103
108export { 104export {
diff --git a/server/lib/activitypub/fetch.ts b/server/lib/activitypub/fetch.ts
index fd2af0761..aa4dea8e0 100644
--- a/server/lib/activitypub/fetch.ts
+++ b/server/lib/activitypub/fetch.ts
@@ -1,8 +1,8 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { AccountInstance } from '../../models/account/account-interface' 2import { AccountModel } from '../../models/account/account'
3import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' 3import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler'
4 4
5async function addFetchOutboxJob (account: AccountInstance, t: Transaction) { 5async function addFetchOutboxJob (account: AccountModel, t: Transaction) {
6 const jobPayload: ActivityPubHttpPayload = { 6 const jobPayload: ActivityPubHttpPayload = {
7 uris: [ account.outboxUrl ] 7 uris: [ account.outboxUrl ]
8 } 8 }
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index 0baa22c26..a775c858a 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -1,18 +1,18 @@
1import * as magnetUtil from 'magnet-uri' 1import * as magnetUtil from 'magnet-uri'
2import { VideoTorrentObject } from '../../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object' 3import { VideoChannelObject } from '../../../../shared/models/activitypub/objects'
4import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' 4import { VideoPrivacy } from '../../../../shared/models/videos'
5import { doRequest } from '../../../helpers'
5import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' 6import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
6import { doRequest } from '../../../helpers/requests' 7import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers'
7import { database as db } from '../../../initializers' 8import { AccountModel } from '../../../models/account/account'
8import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants' 9import { VideoModel } from '../../../models/video/video'
9import { AccountInstance } from '../../../models/account/account-interface' 10import { VideoChannelModel } from '../../../models/video/video-channel'
10import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 11import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
11import { VideoFileAttributes } from '../../../models/video/video-file-interface' 12import { VideoShareModel } from '../../../models/video/video-share'
12import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface'
13import { getOrCreateAccountAndServer } from '../account' 13import { getOrCreateAccountAndServer } from '../account'
14 14
15function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { 15function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountModel) {
16 return { 16 return {
17 name: videoChannelObject.name, 17 name: videoChannelObject.name,
18 description: videoChannelObject.content, 18 description: videoChannelObject.content,
@@ -26,7 +26,7 @@ function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChan
26} 26}
27 27
28async function videoActivityObjectToDBAttributes ( 28async function videoActivityObjectToDBAttributes (
29 videoChannel: VideoChannelInstance, 29 videoChannel: VideoChannelModel,
30 videoObject: VideoTorrentObject, 30 videoObject: VideoTorrentObject,
31 to: string[] = [], 31 to: string[] = [],
32 cc: string[] = [] 32 cc: string[] = []
@@ -56,7 +56,7 @@ async function videoActivityObjectToDBAttributes (
56 description = videoObject.content 56 description = videoObject.content
57 } 57 }
58 58
59 const videoData: VideoAttributes = { 59 return {
60 name: videoObject.name, 60 name: videoObject.name,
61 uuid: videoObject.uuid, 61 uuid: videoObject.uuid,
62 url: videoObject.id, 62 url: videoObject.id,
@@ -76,11 +76,9 @@ async function videoActivityObjectToDBAttributes (
76 remote: true, 76 remote: true,
77 privacy 77 privacy
78 } 78 }
79
80 return videoData
81} 79}
82 80
83function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) { 81function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) {
84 const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT) 82 const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
85 const fileUrls = videoObject.url.filter(u => { 83 const fileUrls = videoObject.url.filter(u => {
86 return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/') 84 return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/')
@@ -90,7 +88,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO
90 throw new Error('Cannot find video files for ' + videoCreated.url) 88 throw new Error('Cannot find video files for ' + videoCreated.url)
91 } 89 }
92 90
93 const attributes: VideoFileAttributes[] = [] 91 const attributes = []
94 for (const fileUrl of fileUrls) { 92 for (const fileUrl of fileUrls) {
95 // Fetch associated magnet uri 93 // Fetch associated magnet uri
96 const magnet = videoObject.url.find(u => { 94 const magnet = videoObject.url.find(u => {
@@ -115,7 +113,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO
115 return attributes 113 return attributes
116} 114}
117 115
118async function addVideoShares (instance: VideoInstance, shares: string[]) { 116async function addVideoShares (instance: VideoModel, shares: string[]) {
119 for (const share of shares) { 117 for (const share of shares) {
120 // Fetch url 118 // Fetch url
121 const json = await doRequest({ 119 const json = await doRequest({
@@ -132,14 +130,14 @@ async function addVideoShares (instance: VideoInstance, shares: string[]) {
132 videoId: instance.id 130 videoId: instance.id
133 } 131 }
134 132
135 await db.VideoShare.findOrCreate({ 133 await VideoShareModel.findOrCreate({
136 where: entry, 134 where: entry,
137 defaults: entry 135 defaults: entry
138 }) 136 })
139 } 137 }
140} 138}
141 139
142async function addVideoChannelShares (instance: VideoChannelInstance, shares: string[]) { 140async function addVideoChannelShares (instance: VideoChannelModel, shares: string[]) {
143 for (const share of shares) { 141 for (const share of shares) {
144 // Fetch url 142 // Fetch url
145 const json = await doRequest({ 143 const json = await doRequest({
@@ -156,7 +154,7 @@ async function addVideoChannelShares (instance: VideoChannelInstance, shares: st
156 videoChannelId: instance.id 154 videoChannelId: instance.id
157 } 155 }
158 156
159 await db.VideoChannelShare.findOrCreate({ 157 await VideoChannelShareModel.findOrCreate({
160 where: entry, 158 where: entry,
161 defaults: entry 159 defaults: entry
162 }) 160 })
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 73c6cb279..5b321f771 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,12 +1,12 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub/activity' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { database as db } from '../../../initializers' 2import { AccountModel } from '../../../models/account/account'
3import { AccountInstance } from '../../../models/account/account-interface' 3import { AccountFollowModel } from '../../../models/account/account-follow'
4import { addFetchOutboxJob } from '../fetch' 4import { addFetchOutboxJob } from '../fetch'
5 5
6async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) { 6async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) {
7 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.') 7 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
8 8
9 const targetAccount = await db.Account.loadByUrl(activity.actor) 9 const targetAccount = await AccountModel.loadByUrl(activity.actor)
10 10
11 return processAccept(inboxAccount, targetAccount) 11 return processAccept(inboxAccount, targetAccount)
12} 12}
@@ -19,8 +19,8 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processAccept (account: AccountInstance, targetAccount: AccountInstance) { 22async function processAccept (account: AccountModel, targetAccount: AccountModel) {
23 const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id) 23 const follow = await AccountFollowModel.loadByAccountAndTarget(account.id, targetAccount.id)
24 if (!follow) throw new Error('Cannot find associated follow.') 24 if (!follow) throw new Error('Cannot find associated follow.')
25 25
26 follow.set('state', 'accepted') 26 follow.set('state', 'accepted')
diff --git a/server/lib/activitypub/process/process-add.ts b/server/lib/activitypub/process/process-add.ts
index e6bf63eb2..550593eab 100644
--- a/server/lib/activitypub/process/process-add.ts
+++ b/server/lib/activitypub/process/process-add.ts
@@ -1,13 +1,15 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoTorrentObject } from '../../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { ActivityAdd } from '../../../../shared/models/activitypub/activity' 3import { ActivityAdd } from '../../../../shared/models/activitypub'
4import { VideoRateType } from '../../../../shared/models/videos/video-rate.type' 4import { VideoRateType } from '../../../../shared/models/videos'
5import { retryTransactionWrapper } from '../../../helpers/database-utils' 5import { logger, retryTransactionWrapper } from '../../../helpers'
6import { logger } from '../../../helpers/logger' 6import { sequelizeTypescript } from '../../../initializers'
7import { database as db } from '../../../initializers' 7import { AccountModel } from '../../../models/account/account'
8import { AccountInstance } from '../../../models/account/account-interface' 8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
9import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 9import { TagModel } from '../../../models/video/tag'
10import { VideoInstance } from '../../../models/video/video-interface' 10import { VideoModel } from '../../../models/video/video'
11import { VideoChannelModel } from '../../../models/video/video-channel'
12import { VideoFileModel } from '../../../models/video/video-file'
11import { getOrCreateAccountAndServer } from '../account' 13import { getOrCreateAccountAndServer } from '../account'
12import { getOrCreateVideoChannel } from '../video-channels' 14import { getOrCreateVideoChannel } from '../video-channels'
13import { generateThumbnailFromUrl } from '../videos' 15import { generateThumbnailFromUrl } from '../videos'
@@ -37,9 +39,9 @@ export {
37 39
38// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
39 41
40async function processAddVideo (account: AccountInstance, 42async function processAddVideo (account: AccountModel,
41 activity: ActivityAdd, 43 activity: ActivityAdd,
42 videoChannel: VideoChannelInstance, 44 videoChannel: VideoChannelModel,
43 videoToCreateData: VideoTorrentObject) { 45 videoToCreateData: VideoTorrentObject) {
44 const options = { 46 const options = {
45 arguments: [ account, activity, videoChannel, videoToCreateData ], 47 arguments: [ account, activity, videoChannel, videoToCreateData ],
@@ -64,24 +66,24 @@ async function processAddVideo (account: AccountInstance,
64 return video 66 return video
65} 67}
66 68
67function addRemoteVideo (account: AccountInstance, 69function addRemoteVideo (account: AccountModel,
68 activity: ActivityAdd, 70 activity: ActivityAdd,
69 videoChannel: VideoChannelInstance, 71 videoChannel: VideoChannelModel,
70 videoToCreateData: VideoTorrentObject) { 72 videoToCreateData: VideoTorrentObject) {
71 logger.debug('Adding remote video %s.', videoToCreateData.id) 73 logger.debug('Adding remote video %s.', videoToCreateData.id)
72 74
73 return db.sequelize.transaction(async t => { 75 return sequelizeTypescript.transaction(async t => {
74 const sequelizeOptions = { 76 const sequelizeOptions = {
75 transaction: t 77 transaction: t
76 } 78 }
77 79
78 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.') 80 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
79 81
80 const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t) 82 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
81 if (videoFromDatabase) return videoFromDatabase 83 if (videoFromDatabase) return videoFromDatabase
82 84
83 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc) 85 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
84 const video = db.Video.build(videoData) 86 const video = VideoModel.build(videoData)
85 87
86 // Don't block on request 88 // Don't block on request
87 generateThumbnailFromUrl(video, videoToCreateData.icon) 89 generateThumbnailFromUrl(video, videoToCreateData.icon)
@@ -94,12 +96,12 @@ function addRemoteVideo (account: AccountInstance,
94 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url) 96 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
95 } 97 }
96 98
97 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f, { transaction: t })) 99 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
98 await Promise.all(tasks) 100 await Promise.all(tasks)
99 101
100 const tags = videoToCreateData.tag.map(t => t.name) 102 const tags = videoToCreateData.tag.map(t => t.name)
101 const tagInstances = await db.Tag.findOrCreateTags(tags, t) 103 const tagInstances = await TagModel.findOrCreateTags(tags, t)
102 await videoCreated.setTags(tagInstances, sequelizeOptions) 104 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
103 105
104 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid) 106 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
105 107
@@ -107,13 +109,13 @@ function addRemoteVideo (account: AccountInstance,
107 }) 109 })
108} 110}
109 111
110async function createRates (accountUrls: string[], video: VideoInstance, rate: VideoRateType) { 112async function createRates (accountUrls: string[], video: VideoModel, rate: VideoRateType) {
111 let rateCounts = 0 113 let rateCounts = 0
112 const tasks: Bluebird<any>[] = [] 114 const tasks: Bluebird<any>[] = []
113 115
114 for (const accountUrl of accountUrls) { 116 for (const accountUrl of accountUrls) {
115 const account = await getOrCreateAccountAndServer(accountUrl) 117 const account = await getOrCreateAccountAndServer(accountUrl)
116 const p = db.AccountVideoRate 118 const p = AccountVideoRateModel
117 .create({ 119 .create({
118 videoId: video.id, 120 videoId: video.id,
119 accountId: account.id, 121 accountId: account.id,
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index 2aa9ad5c7..ff2c6d708 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -1,10 +1,11 @@
1import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' 1import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { logger } from '../../../helpers/logger' 3import { sequelizeTypescript } from '../../../initializers'
4import { database as db } from '../../../initializers/index' 4import { AccountModel } from '../../../models/account/account'
5import { AccountInstance } from '../../../models/account/account-interface' 5import { VideoModel } from '../../../models/video/video'
6import { VideoInstance } from '../../../models/index' 6import { VideoChannelModel } from '../../../models/video/video-channel'
7import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 7import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
8import { VideoShareModel } from '../../../models/video/video-share'
8import { getOrCreateAccountAndServer } from '../account' 9import { getOrCreateAccountAndServer } from '../account'
9import { forwardActivity } from '../send/misc' 10import { forwardActivity } from '../send/misc'
10import { processAddActivity } from './process-add' 11import { processAddActivity } from './process-add'
@@ -36,7 +37,7 @@ export {
36 37
37// --------------------------------------------------------------------------- 38// ---------------------------------------------------------------------------
38 39
39function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { 40function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
40 const options = { 41 const options = {
41 arguments: [ accountAnnouncer, activity ], 42 arguments: [ accountAnnouncer, activity ],
42 errorMessage: 'Cannot share the video channel with many retries.' 43 errorMessage: 'Cannot share the video channel with many retries.'
@@ -45,18 +46,18 @@ function processVideoChannelShare (accountAnnouncer: AccountInstance, activity:
45 return retryTransactionWrapper(shareVideoChannel, options) 46 return retryTransactionWrapper(shareVideoChannel, options)
46} 47}
47 48
48async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { 49async function shareVideoChannel (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
49 const announcedActivity = activity.object as ActivityCreate 50 const announcedActivity = activity.object as ActivityCreate
50 51
51 return db.sequelize.transaction(async t => { 52 return sequelizeTypescript.transaction(async t => {
52 // Add share entry 53 // Add share entry
53 const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity) 54 const videoChannel: VideoChannelModel = await processCreateActivity(announcedActivity)
54 const share = { 55 const share = {
55 accountId: accountAnnouncer.id, 56 accountId: accountAnnouncer.id,
56 videoChannelId: videoChannel.id 57 videoChannelId: videoChannel.id
57 } 58 }
58 59
59 const [ , created ] = await db.VideoChannelShare.findOrCreate({ 60 const [ , created ] = await VideoChannelShareModel.findOrCreate({
60 where: share, 61 where: share,
61 defaults: share, 62 defaults: share,
62 transaction: t 63 transaction: t
@@ -72,7 +73,7 @@ async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: A
72 }) 73 })
73} 74}
74 75
75function processVideoShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { 76function processVideoShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
76 const options = { 77 const options = {
77 arguments: [ accountAnnouncer, activity ], 78 arguments: [ accountAnnouncer, activity ],
78 errorMessage: 'Cannot share the video with many retries.' 79 errorMessage: 'Cannot share the video with many retries.'
@@ -81,19 +82,19 @@ function processVideoShare (accountAnnouncer: AccountInstance, activity: Activit
81 return retryTransactionWrapper(shareVideo, options) 82 return retryTransactionWrapper(shareVideo, options)
82} 83}
83 84
84function shareVideo (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { 85function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
85 const announcedActivity = activity.object as ActivityAdd 86 const announcedActivity = activity.object as ActivityAdd
86 87
87 return db.sequelize.transaction(async t => { 88 return sequelizeTypescript.transaction(async t => {
88 // Add share entry 89 // Add share entry
89 const video: VideoInstance = await processAddActivity(announcedActivity) 90 const video: VideoModel = await processAddActivity(announcedActivity)
90 91
91 const share = { 92 const share = {
92 accountId: accountAnnouncer.id, 93 accountId: accountAnnouncer.id,
93 videoId: video.id 94 videoId: video.id
94 } 95 }
95 96
96 const [ , created ] = await db.VideoShare.findOrCreate({ 97 const [ , created ] = await VideoShareModel.findOrCreate({
97 where: share, 98 where: share,
98 defaults: share, 99 defaults: share,
99 transaction: t 100 transaction: t
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 4740dc432..c1eb2a8ab 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,10 +1,12 @@
1import { ActivityCreate, VideoChannelObject } from '../../../../shared' 1import { ActivityCreate, VideoChannelObject } from '../../../../shared'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' 2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
3import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
4import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object'
5import { logger, retryTransactionWrapper } from '../../../helpers' 3import { logger, retryTransactionWrapper } from '../../../helpers'
6import { database as db } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
7import { AccountInstance } from '../../../models/account/account-interface' 5import { AccountModel } from '../../../models/account/account'
6import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
7import { VideoModel } from '../../../models/video/video'
8import { VideoAbuseModel } from '../../../models/video/video-abuse'
9import { VideoChannelModel } from '../../../models/video/video-channel'
8import { getOrCreateAccountAndServer } from '../account' 10import { getOrCreateAccountAndServer } from '../account'
9import { forwardActivity } from '../send/misc' 11import { forwardActivity } from '../send/misc'
10import { getVideoChannelActivityPubUrl } from '../url' 12import { getVideoChannelActivityPubUrl } from '../url'
@@ -37,7 +39,7 @@ export {
37 39
38// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
39 41
40async function processCreateDislike (byAccount: AccountInstance, activity: ActivityCreate) { 42async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) {
41 const options = { 43 const options = {
42 arguments: [ byAccount, activity ], 44 arguments: [ byAccount, activity ],
43 errorMessage: 'Cannot dislike the video with many retries.' 45 errorMessage: 'Cannot dislike the video with many retries.'
@@ -46,11 +48,11 @@ async function processCreateDislike (byAccount: AccountInstance, activity: Activ
46 return retryTransactionWrapper(createVideoDislike, options) 48 return retryTransactionWrapper(createVideoDislike, options)
47} 49}
48 50
49function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreate) { 51function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) {
50 const dislike = activity.object as DislikeObject 52 const dislike = activity.object as DislikeObject
51 53
52 return db.sequelize.transaction(async t => { 54 return sequelizeTypescript.transaction(async t => {
53 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t) 55 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
54 if (!video) throw new Error('Unknown video ' + dislike.object) 56 if (!video) throw new Error('Unknown video ' + dislike.object)
55 57
56 const rate = { 58 const rate = {
@@ -58,7 +60,7 @@ function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreat
58 videoId: video.id, 60 videoId: video.id,
59 accountId: byAccount.id 61 accountId: byAccount.id
60 } 62 }
61 const [ , created ] = await db.AccountVideoRate.findOrCreate({ 63 const [ , created ] = await AccountVideoRateModel.findOrCreate({
62 where: rate, 64 where: rate,
63 defaults: rate, 65 defaults: rate,
64 transaction: t 66 transaction: t
@@ -73,14 +75,14 @@ function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreat
73 }) 75 })
74} 76}
75 77
76async function processCreateView (byAccount: AccountInstance, activity: ActivityCreate) { 78async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) {
77 const view = activity.object as ViewObject 79 const view = activity.object as ViewObject
78 80
79 const video = await db.Video.loadByUrlAndPopulateAccount(view.object) 81 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
80 82
81 if (!video) throw new Error('Unknown video ' + view.object) 83 if (!video) throw new Error('Unknown video ' + view.object)
82 84
83 const account = await db.Account.loadByUrl(view.actor) 85 const account = await AccountModel.loadByUrl(view.actor)
84 if (!account) throw new Error('Unknown account ' + view.actor) 86 if (!account) throw new Error('Unknown account ' + view.actor)
85 87
86 await video.increment('views') 88 await video.increment('views')
@@ -92,7 +94,7 @@ async function processCreateView (byAccount: AccountInstance, activity: Activity
92 } 94 }
93} 95}
94 96
95async function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 97async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
96 const options = { 98 const options = {
97 arguments: [ account, videoChannelToCreateData ], 99 arguments: [ account, videoChannelToCreateData ],
98 errorMessage: 'Cannot insert the remote video channel with many retries.' 100 errorMessage: 'Cannot insert the remote video channel with many retries.'
@@ -107,15 +109,15 @@ async function processCreateVideoChannel (account: AccountInstance, videoChannel
107 return videoChannel 109 return videoChannel
108} 110}
109 111
110function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 112function addRemoteVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
111 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid) 113 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
112 114
113 return db.sequelize.transaction(async t => { 115 return sequelizeTypescript.transaction(async t => {
114 let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t) 116 let videoChannel = await VideoChannelModel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
115 if (videoChannel) return videoChannel 117 if (videoChannel) return videoChannel
116 118
117 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account) 119 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
118 videoChannel = db.VideoChannel.build(videoChannelData) 120 videoChannel = new VideoChannelModel(videoChannelData)
119 videoChannel.url = getVideoChannelActivityPubUrl(videoChannel) 121 videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
120 122
121 videoChannel = await videoChannel.save({ transaction: t }) 123 videoChannel = await videoChannel.save({ transaction: t })
@@ -125,7 +127,7 @@ function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateDa
125 }) 127 })
126} 128}
127 129
128function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { 130function processCreateVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
129 const options = { 131 const options = {
130 arguments: [ account, videoAbuseToCreateData ], 132 arguments: [ account, videoAbuseToCreateData ],
131 errorMessage: 'Cannot insert the remote video abuse with many retries.' 133 errorMessage: 'Cannot insert the remote video abuse with many retries.'
@@ -134,11 +136,11 @@ function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateDa
134 return retryTransactionWrapper(addRemoteVideoAbuse, options) 136 return retryTransactionWrapper(addRemoteVideoAbuse, options)
135} 137}
136 138
137function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { 139function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
138 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) 140 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
139 141
140 return db.sequelize.transaction(async t => { 142 return sequelizeTypescript.transaction(async t => {
141 const video = await db.Video.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) 143 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
142 if (!video) { 144 if (!video) {
143 logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object) 145 logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object)
144 return undefined 146 return undefined
@@ -150,7 +152,7 @@ function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData:
150 videoId: video.id 152 videoId: video.id
151 } 153 }
152 154
153 await db.VideoAbuse.create(videoAbuseData) 155 await VideoAbuseModel.create(videoAbuseData)
154 156
155 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) 157 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
156 }) 158 })
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 41cdc236d..8f280d37f 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -1,10 +1,9 @@
1import { ActivityDelete } from '../../../../shared/models/activitypub/activity' 1import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { logger } from '../../../helpers/logger' 3import { sequelizeTypescript } from '../../../initializers'
4import { database as db } from '../../../initializers' 4import { AccountModel } from '../../../models/account/account'
5import { AccountInstance } from '../../../models/account/account-interface' 5import { VideoModel } from '../../../models/video/video'
6import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 6import { VideoChannelModel } from '../../../models/video/video-channel'
7import { VideoInstance } from '../../../models/video/video-interface'
8import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateAccountAndServer } from '../account'
9 8
10async function processDeleteActivity (activity: ActivityDelete) { 9async function processDeleteActivity (activity: ActivityDelete) {
@@ -15,14 +14,14 @@ async function processDeleteActivity (activity: ActivityDelete) {
15 } 14 }
16 15
17 { 16 {
18 let videoObject = await db.Video.loadByUrlAndPopulateAccount(activity.id) 17 let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
19 if (videoObject !== undefined) { 18 if (videoObject !== undefined) {
20 return processDeleteVideo(account, videoObject) 19 return processDeleteVideo(account, videoObject)
21 } 20 }
22 } 21 }
23 22
24 { 23 {
25 let videoChannelObject = await db.VideoChannel.loadByUrl(activity.id) 24 let videoChannelObject = await VideoChannelModel.loadByUrl(activity.id)
26 if (videoChannelObject !== undefined) { 25 if (videoChannelObject !== undefined) {
27 return processDeleteVideoChannel(account, videoChannelObject) 26 return processDeleteVideoChannel(account, videoChannelObject)
28 } 27 }
@@ -39,7 +38,7 @@ export {
39 38
40// --------------------------------------------------------------------------- 39// ---------------------------------------------------------------------------
41 40
42async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) { 41async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) {
43 const options = { 42 const options = {
44 arguments: [ account, videoToDelete ], 43 arguments: [ account, videoToDelete ],
45 errorMessage: 'Cannot remove the remote video with many retries.' 44 errorMessage: 'Cannot remove the remote video with many retries.'
@@ -48,10 +47,10 @@ async function processDeleteVideo (account: AccountInstance, videoToDelete: Vide
48 await retryTransactionWrapper(deleteRemoteVideo, options) 47 await retryTransactionWrapper(deleteRemoteVideo, options)
49} 48}
50 49
51async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) { 50async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) {
52 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 51 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
53 52
54 await db.sequelize.transaction(async t => { 53 await sequelizeTypescript.transaction(async t => {
55 if (videoToDelete.VideoChannel.Account.id !== account.id) { 54 if (videoToDelete.VideoChannel.Account.id !== account.id) {
56 throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url) 55 throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url)
57 } 56 }
@@ -62,7 +61,7 @@ async function deleteRemoteVideo (account: AccountInstance, videoToDelete: Video
62 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) 61 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
63} 62}
64 63
65async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { 64async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
66 const options = { 65 const options = {
67 arguments: [ account, videoChannelToRemove ], 66 arguments: [ account, videoChannelToRemove ],
68 errorMessage: 'Cannot remove the remote video channel with many retries.' 67 errorMessage: 'Cannot remove the remote video channel with many retries.'
@@ -71,10 +70,10 @@ async function processDeleteVideoChannel (account: AccountInstance, videoChannel
71 await retryTransactionWrapper(deleteRemoteVideoChannel, options) 70 await retryTransactionWrapper(deleteRemoteVideoChannel, options)
72} 71}
73 72
74async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { 73async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) {
75 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid) 74 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
76 75
77 await db.sequelize.transaction(async t => { 76 await sequelizeTypescript.transaction(async t => {
78 if (videoChannelToRemove.Account.id !== account.id) { 77 if (videoChannelToRemove.Account.id !== account.id) {
79 throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url) 78 throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
80 } 79 }
@@ -85,7 +84,7 @@ async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelT
85 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid) 84 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
86} 85}
87 86
88async function processDeleteAccount (accountToRemove: AccountInstance) { 87async function processDeleteAccount (accountToRemove: AccountModel) {
89 const options = { 88 const options = {
90 arguments: [ accountToRemove ], 89 arguments: [ accountToRemove ],
91 errorMessage: 'Cannot remove the remote account with many retries.' 90 errorMessage: 'Cannot remove the remote account with many retries.'
@@ -94,10 +93,10 @@ async function processDeleteAccount (accountToRemove: AccountInstance) {
94 await retryTransactionWrapper(deleteRemoteAccount, options) 93 await retryTransactionWrapper(deleteRemoteAccount, options)
95} 94}
96 95
97async function deleteRemoteAccount (accountToRemove: AccountInstance) { 96async function deleteRemoteAccount (accountToRemove: AccountModel) {
98 logger.debug('Removing remote account "%s".', accountToRemove.uuid) 97 logger.debug('Removing remote account "%s".', accountToRemove.uuid)
99 98
100 await db.sequelize.transaction(async t => { 99 await sequelizeTypescript.transaction(async t => {
101 await accountToRemove.destroy({ transaction: t }) 100 await accountToRemove.destroy({ transaction: t })
102 }) 101 })
103 102
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 320dc1138..ccaee43a6 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -1,10 +1,10 @@
1import { ActivityFollow } from '../../../../shared/models/activitypub/activity' 1import { ActivityFollow } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { database as db } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface' 4import { AccountModel } from '../../../models/account/account'
5import { logger } from '../../../helpers/logger' 5import { AccountFollowModel } from '../../../models/account/account-follow'
6import { sendAccept } from '../send/send-accept'
7import { getOrCreateAccountAndServer } from '../account' 6import { getOrCreateAccountAndServer } from '../account'
7import { sendAccept } from '../send'
8 8
9async function processFollowActivity (activity: ActivityFollow) { 9async function processFollowActivity (activity: ActivityFollow) {
10 const activityObject = activity.object 10 const activityObject = activity.object
@@ -21,7 +21,7 @@ export {
21 21
22// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
23 23
24function processFollow (account: AccountInstance, targetAccountURL: string) { 24function processFollow (account: AccountModel, targetAccountURL: string) {
25 const options = { 25 const options = {
26 arguments: [ account, targetAccountURL ], 26 arguments: [ account, targetAccountURL ],
27 errorMessage: 'Cannot follow with many retries.' 27 errorMessage: 'Cannot follow with many retries.'
@@ -30,14 +30,14 @@ function processFollow (account: AccountInstance, targetAccountURL: string) {
30 return retryTransactionWrapper(follow, options) 30 return retryTransactionWrapper(follow, options)
31} 31}
32 32
33async function follow (account: AccountInstance, targetAccountURL: string) { 33async function follow (account: AccountModel, targetAccountURL: string) {
34 await db.sequelize.transaction(async t => { 34 await sequelizeTypescript.transaction(async t => {
35 const targetAccount = await db.Account.loadByUrl(targetAccountURL, t) 35 const targetAccount = await AccountModel.loadByUrl(targetAccountURL, t)
36 36
37 if (!targetAccount) throw new Error('Unknown account') 37 if (!targetAccount) throw new Error('Unknown account')
38 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') 38 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
39 39
40 const [ accountFollow ] = await db.AccountFollow.findOrCreate({ 40 const [ accountFollow ] = await AccountFollowModel.findOrCreate({
41 where: { 41 where: {
42 accountId: account.id, 42 accountId: account.id,
43 targetAccountId: targetAccount.id 43 targetAccountId: targetAccount.id
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index 5f2ffe7ea..a6e391f1e 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -1,7 +1,9 @@
1import { ActivityLike } from '../../../../shared/models/activitypub/activity' 1import { ActivityLike } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { retryTransactionWrapper } from '../../../helpers'
3import { database as db } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface' 4import { AccountModel } from '../../../models/account/account'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
6import { VideoModel } from '../../../models/video/video'
5import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateAccountAndServer } from '../account'
6import { forwardActivity } from '../send/misc' 8import { forwardActivity } from '../send/misc'
7 9
@@ -19,7 +21,7 @@ export {
19 21
20// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
21 23
22async function processLikeVideo (byAccount: AccountInstance, activity: ActivityLike) { 24async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) {
23 const options = { 25 const options = {
24 arguments: [ byAccount, activity ], 26 arguments: [ byAccount, activity ],
25 errorMessage: 'Cannot like the video with many retries.' 27 errorMessage: 'Cannot like the video with many retries.'
@@ -28,11 +30,11 @@ async function processLikeVideo (byAccount: AccountInstance, activity: ActivityL
28 return retryTransactionWrapper(createVideoLike, options) 30 return retryTransactionWrapper(createVideoLike, options)
29} 31}
30 32
31function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) { 33function createVideoLike (byAccount: AccountModel, activity: ActivityLike) {
32 const videoUrl = activity.object 34 const videoUrl = activity.object
33 35
34 return db.sequelize.transaction(async t => { 36 return sequelizeTypescript.transaction(async t => {
35 const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl) 37 const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl)
36 38
37 if (!video) throw new Error('Unknown video ' + videoUrl) 39 if (!video) throw new Error('Unknown video ' + videoUrl)
38 40
@@ -41,7 +43,7 @@ function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) {
41 videoId: video.id, 43 videoId: video.id,
42 accountId: byAccount.id 44 accountId: byAccount.id
43 } 45 }
44 const [ , created ] = await db.AccountVideoRate.findOrCreate({ 46 const [ , created ] = await AccountVideoRateModel.findOrCreate({
45 where: rate, 47 where: rate,
46 defaults: rate, 48 defaults: rate,
47 transaction: t 49 transaction: t
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index cc221045f..efa63122b 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -1,8 +1,11 @@
1import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity' 1import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' 2import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { retryTransactionWrapper } from '../../../helpers/database-utils' 3import { logger, retryTransactionWrapper } from '../../../helpers'
4import { logger } from '../../../helpers/logger' 4import { sequelizeTypescript } from '../../../initializers'
5import { database as db } from '../../../initializers' 5import { AccountModel } from '../../../models/account/account'
6import { AccountFollowModel } from '../../../models/account/account-follow'
7import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
8import { VideoModel } from '../../../models/video/video'
6import { forwardActivity } from '../send/misc' 9import { forwardActivity } from '../send/misc'
7 10
8async function processUndoActivity (activity: ActivityUndo) { 11async function processUndoActivity (activity: ActivityUndo) {
@@ -41,14 +44,14 @@ function processUndoLike (actor: string, activity: ActivityUndo) {
41function undoLike (actor: string, activity: ActivityUndo) { 44function undoLike (actor: string, activity: ActivityUndo) {
42 const likeActivity = activity.object as ActivityLike 45 const likeActivity = activity.object as ActivityLike
43 46
44 return db.sequelize.transaction(async t => { 47 return sequelizeTypescript.transaction(async t => {
45 const byAccount = await db.Account.loadByUrl(actor, t) 48 const byAccount = await AccountModel.loadByUrl(actor, t)
46 if (!byAccount) throw new Error('Unknown account ' + actor) 49 if (!byAccount) throw new Error('Unknown account ' + actor)
47 50
48 const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object, t) 51 const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t)
49 if (!video) throw new Error('Unknown video ' + likeActivity.actor) 52 if (!video) throw new Error('Unknown video ' + likeActivity.actor)
50 53
51 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) 54 const rate = await AccountVideoRateModel.load(byAccount.id, video.id, t)
52 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) 55 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
53 56
54 await rate.destroy({ transaction: t }) 57 await rate.destroy({ transaction: t })
@@ -74,14 +77,14 @@ function processUndoDislike (actor: string, activity: ActivityUndo) {
74function undoDislike (actor: string, activity: ActivityUndo) { 77function undoDislike (actor: string, activity: ActivityUndo) {
75 const dislike = activity.object.object as DislikeObject 78 const dislike = activity.object.object as DislikeObject
76 79
77 return db.sequelize.transaction(async t => { 80 return sequelizeTypescript.transaction(async t => {
78 const byAccount = await db.Account.loadByUrl(actor, t) 81 const byAccount = await AccountModel.loadByUrl(actor, t)
79 if (!byAccount) throw new Error('Unknown account ' + actor) 82 if (!byAccount) throw new Error('Unknown account ' + actor)
80 83
81 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t) 84 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
82 if (!video) throw new Error('Unknown video ' + dislike.actor) 85 if (!video) throw new Error('Unknown video ' + dislike.actor)
83 86
84 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) 87 const rate = await AccountVideoRateModel.load(byAccount.id, video.id, t)
85 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) 88 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
86 89
87 await rate.destroy({ transaction: t }) 90 await rate.destroy({ transaction: t })
@@ -105,10 +108,10 @@ function processUndoFollow (actor: string, followActivity: ActivityFollow) {
105} 108}
106 109
107function undoFollow (actor: string, followActivity: ActivityFollow) { 110function undoFollow (actor: string, followActivity: ActivityFollow) {
108 return db.sequelize.transaction(async t => { 111 return sequelizeTypescript.transaction(async t => {
109 const follower = await db.Account.loadByUrl(actor, t) 112 const follower = await AccountModel.loadByUrl(actor, t)
110 const following = await db.Account.loadByUrl(followActivity.object, t) 113 const following = await AccountModel.loadByUrl(followActivity.object, t)
111 const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id, t) 114 const accountFollow = await AccountFollowModel.loadByAccountAndTarget(follower.id, following.id, t)
112 115
113 if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`) 116 if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`)
114 117
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 11c6de8f5..771021f0c 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,12 +1,13 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoChannelObject, VideoTorrentObject } from '../../../../shared' 2import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
3import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' 3import { ActivityUpdate } from '../../../../shared/models/activitypub'
4import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
5import { logger } from '../../../helpers/logger' 5import { sequelizeTypescript } from '../../../initializers'
6import { resetSequelizeInstance } from '../../../helpers/utils' 6import { AccountModel } from '../../../models/account/account'
7import { database as db } from '../../../initializers' 7import { TagModel } from '../../../models/video/tag'
8import { AccountInstance } from '../../../models/account/account-interface' 8import { VideoModel } from '../../../models/video/video'
9import { VideoInstance } from '../../../models/video/video-interface' 9import { VideoChannelModel } from '../../../models/video/video-channel'
10import { VideoFileModel } from '../../../models/video/video-file'
10import { getOrCreateAccountAndServer } from '../account' 11import { getOrCreateAccountAndServer } from '../account'
11import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 12import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
12 13
@@ -30,7 +31,7 @@ export {
30 31
31// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
32 33
33function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject) { 34function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) {
34 const options = { 35 const options = {
35 arguments: [ account, video ], 36 arguments: [ account, video ],
36 errorMessage: 'Cannot update the remote video with many retries' 37 errorMessage: 'Cannot update the remote video with many retries'
@@ -39,18 +40,18 @@ function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject
39 return retryTransactionWrapper(updateRemoteVideo, options) 40 return retryTransactionWrapper(updateRemoteVideo, options)
40} 41}
41 42
42async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpdate: VideoTorrentObject) { 43async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) {
43 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 44 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
44 let videoInstance: VideoInstance 45 let videoInstance: VideoModel
45 let videoFieldsSave: object 46 let videoFieldsSave: object
46 47
47 try { 48 try {
48 await db.sequelize.transaction(async t => { 49 await sequelizeTypescript.transaction(async t => {
49 const sequelizeOptions = { 50 const sequelizeOptions = {
50 transaction: t 51 transaction: t
51 } 52 }
52 53
53 const videoInstance = await db.Video.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) 54 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
54 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.') 55 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
55 56
56 if (videoInstance.VideoChannel.Account.id !== account.id) { 57 if (videoInstance.VideoChannel.Account.id !== account.id) {
@@ -81,12 +82,12 @@ async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpd
81 await Promise.all(videoFileDestroyTasks) 82 await Promise.all(videoFileDestroyTasks)
82 83
83 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate) 84 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate)
84 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f)) 85 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f))
85 await Promise.all(tasks) 86 await Promise.all(tasks)
86 87
87 const tags = videoAttributesToUpdate.tag.map(t => t.name) 88 const tags = videoAttributesToUpdate.tag.map(t => t.name)
88 const tagInstances = await db.Tag.findOrCreateTags(tags, t) 89 const tagInstances = await TagModel.findOrCreateTags(tags, t)
89 await videoInstance.setTags(tagInstances, sequelizeOptions) 90 await videoInstance.$set('Tags', tagInstances, sequelizeOptions)
90 }) 91 })
91 92
92 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid) 93 logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)
@@ -101,7 +102,7 @@ async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpd
101 } 102 }
102} 103}
103 104
104async function processUpdateVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { 105async function processUpdateVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
105 const options = { 106 const options = {
106 arguments: [ account, videoChannel ], 107 arguments: [ account, videoChannel ],
107 errorMessage: 'Cannot update the remote video channel with many retries.' 108 errorMessage: 'Cannot update the remote video channel with many retries.'
@@ -110,13 +111,13 @@ async function processUpdateVideoChannel (account: AccountInstance, videoChannel
110 await retryTransactionWrapper(updateRemoteVideoChannel, options) 111 await retryTransactionWrapper(updateRemoteVideoChannel, options)
111} 112}
112 113
113async function updateRemoteVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { 114async function updateRemoteVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
114 logger.debug('Updating remote video channel "%s".', videoChannel.uuid) 115 logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
115 116
116 await db.sequelize.transaction(async t => { 117 await sequelizeTypescript.transaction(async t => {
117 const sequelizeOptions = { transaction: t } 118 const sequelizeOptions = { transaction: t }
118 119
119 const videoChannelInstance = await db.VideoChannel.loadByUrl(videoChannel.id) 120 const videoChannelInstance = await VideoChannelModel.loadByUrl(videoChannel.id)
120 if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.') 121 if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
121 122
122 if (videoChannelInstance.Account.id !== account.id) { 123 if (videoChannelInstance.Account.id !== account.id) {
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index 54981c289..bfbf8053c 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,6 +1,6 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers'
3import { AccountInstance } from '../../../models/account/account-interface' 3import { AccountModel } from '../../../models/account/account'
4import { processAcceptActivity } from './process-accept' 4import { processAcceptActivity } from './process-accept'
5import { processAddActivity } from './process-add' 5import { processAddActivity } from './process-add'
6import { processAnnounceActivity } from './process-announce' 6import { processAnnounceActivity } from './process-announce'
@@ -11,7 +11,7 @@ import { processLikeActivity } from './process-like'
11import { processUndoActivity } from './process-undo' 11import { processUndoActivity } from './process-undo'
12import { processUpdateActivity } from './process-update' 12import { processUpdateActivity } from './process-update'
13 13
14const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = { 14const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = {
15 Create: processCreateActivity, 15 Create: processCreateActivity,
16 Add: processAddActivity, 16 Add: processAddActivity,
17 Update: processUpdateActivity, 17 Update: processUpdateActivity,
@@ -23,7 +23,7 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
23 Like: processLikeActivity 23 Like: processLikeActivity
24} 24}
25 25
26async function processActivities (activities: Activity[], signatureAccount?: AccountInstance, inboxAccount?: AccountInstance) { 26async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) {
27 for (const activity of activities) { 27 for (const activity of activities) {
28 // When we fetch remote data, we don't have signature 28 // When we fetch remote data, we don't have signature
29 if (signatureAccount && activity.actor !== signatureAccount.url) { 29 if (signatureAccount && activity.actor !== signatureAccount.url) {
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index 999def701..ffc221477 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -1,19 +1,19 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { Activity } from '../../../../shared/models/activitypub/activity' 2import { Activity } from '../../../../shared/models/activitypub'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers'
4import { ACTIVITY_PUB, database as db } from '../../../initializers' 4import { ACTIVITY_PUB } from '../../../initializers'
5import { AccountInstance } from '../../../models/account/account-interface' 5import { AccountModel } from '../../../models/account/account'
6import { VideoChannelInstance } from '../../../models/index' 6import { AccountFollowModel } from '../../../models/account/account-follow'
7import { VideoInstance } from '../../../models/video/video-interface' 7import { VideoModel } from '../../../models/video/video'
8import { 8import { VideoChannelModel } from '../../../models/video/video-channel'
9 activitypubHttpJobScheduler, 9import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
10 ActivityPubHttpPayload 10import { VideoShareModel } from '../../../models/video/video-share'
11} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' 11import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler'
12 12
13async function forwardActivity ( 13async function forwardActivity (
14 activity: Activity, 14 activity: Activity,
15 t: Transaction, 15 t: Transaction,
16 followersException: AccountInstance[] = [] 16 followersException: AccountModel[] = []
17) { 17) {
18 const to = activity.to || [] 18 const to = activity.to || []
19 const cc = activity.cc || [] 19 const cc = activity.cc || []
@@ -25,7 +25,7 @@ async function forwardActivity (
25 } 25 }
26 } 26 }
27 27
28 const toAccountFollowers = await db.Account.listByFollowersUrls(followersUrls, t) 28 const toAccountFollowers = await AccountModel.listByFollowersUrls(followersUrls, t)
29 const uris = await computeFollowerUris(toAccountFollowers, followersException, t) 29 const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
30 30
31 if (uris.length === 0) { 31 if (uris.length === 0) {
@@ -45,10 +45,10 @@ async function forwardActivity (
45 45
46async function broadcastToFollowers ( 46async function broadcastToFollowers (
47 data: any, 47 data: any,
48 byAccount: AccountInstance, 48 byAccount: AccountModel,
49 toAccountFollowers: AccountInstance[], 49 toAccountFollowers: AccountModel[],
50 t: Transaction, 50 t: Transaction,
51 followersException: AccountInstance[] = [] 51 followersException: AccountModel[] = []
52) { 52) {
53 const uris = await computeFollowerUris(toAccountFollowers, followersException, t) 53 const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
54 if (uris.length === 0) { 54 if (uris.length === 0) {
@@ -67,7 +67,7 @@ async function broadcastToFollowers (
67 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload) 67 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
68} 68}
69 69
70async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) { 70async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) {
71 logger.debug('Creating unicast job.', { uri: toAccountUrl }) 71 logger.debug('Creating unicast job.', { uri: toAccountUrl })
72 72
73 const jobPayload: ActivityPubHttpPayload = { 73 const jobPayload: ActivityPubHttpPayload = {
@@ -79,42 +79,42 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s
79 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload) 79 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
80} 80}
81 81
82function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo: AccountInstance[]) { 82function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) {
83 return { 83 return {
84 to: [ video.VideoChannel.Account.url ], 84 to: [ video.VideoChannel.Account.url ],
85 cc: accountsInvolvedInVideo.map(a => a.followersUrl) 85 cc: accountsInvolvedInVideo.map(a => a.followersUrl)
86 } 86 }
87} 87}
88 88
89function getOriginVideoChannelAudience (videoChannel: VideoChannelInstance, accountsInvolved: AccountInstance[]) { 89function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) {
90 return { 90 return {
91 to: [ videoChannel.Account.url ], 91 to: [ videoChannel.Account.url ],
92 cc: accountsInvolved.map(a => a.followersUrl) 92 cc: accountsInvolved.map(a => a.followersUrl)
93 } 93 }
94} 94}
95 95
96function getObjectFollowersAudience (accountsInvolvedInObject: AccountInstance[]) { 96function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) {
97 return { 97 return {
98 to: accountsInvolvedInObject.map(a => a.followersUrl), 98 to: accountsInvolvedInObject.map(a => a.followersUrl),
99 cc: [] 99 cc: []
100 } 100 }
101} 101}
102 102
103async function getAccountsInvolvedInVideo (video: VideoInstance, t: Transaction) { 103async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) {
104 const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id, t) 104 const accountsToForwardView = await VideoShareModel.loadAccountsByShare(video.id, t)
105 accountsToForwardView.push(video.VideoChannel.Account) 105 accountsToForwardView.push(video.VideoChannel.Account)
106 106
107 return accountsToForwardView 107 return accountsToForwardView
108} 108}
109 109
110async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { 110async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
111 const accountsToForwardView = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) 111 const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
112 accountsToForwardView.push(videoChannel.Account) 112 accountsToForwardView.push(videoChannel.Account)
113 113
114 return accountsToForwardView 114 return accountsToForwardView
115} 115}
116 116
117async function getAudience (accountSender: AccountInstance, t: Transaction, isPublic = true) { 117async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) {
118 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t) 118 const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t)
119 119
120 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47 120 // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
@@ -132,14 +132,12 @@ async function getAudience (accountSender: AccountInstance, t: Transaction, isPu
132 return { to, cc } 132 return { to, cc }
133} 133}
134 134
135async function computeFollowerUris (toAccountFollower: AccountInstance[], followersException: AccountInstance[], t: Transaction) { 135async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) {
136 const toAccountFollowerIds = toAccountFollower.map(a => a.id) 136 const toAccountFollowerIds = toAccountFollower.map(a => a.id)
137 137
138 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t) 138 const result = await AccountFollowModel.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t)
139 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) 139 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
140 const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) 140 return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
141
142 return uris
143} 141}
144 142
145// --------------------------------------------------------------------------- 143// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index d3f8fbe38..f160af3c9 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -1,11 +1,11 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAccept } from '../../../../shared/models/activitypub/activity' 2import { ActivityAccept } from '../../../../shared/models/activitypub'
3import { AccountInstance } from '../../../models' 3import { AccountModel } from '../../../models/account/account'
4import { AccountFollowInstance } from '../../../models/account/account-follow-interface' 4import { AccountFollowModel } from '../../../models/account/account-follow'
5import { unicastTo } from './misc'
6import { getAccountFollowAcceptActivityPubUrl } from '../url' 5import { getAccountFollowAcceptActivityPubUrl } from '../url'
6import { unicastTo } from './misc'
7 7
8async function sendAccept (accountFollow: AccountFollowInstance, t: Transaction) { 8async function sendAccept (accountFollow: AccountFollowModel, t: Transaction) {
9 const follower = accountFollow.AccountFollower 9 const follower = accountFollow.AccountFollower
10 const me = accountFollow.AccountFollowing 10 const me = accountFollow.AccountFollowing
11 11
@@ -23,7 +23,7 @@ export {
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26function acceptActivityData (url: string, byAccount: AccountInstance) { 26function acceptActivityData (url: string, byAccount: AccountModel) {
27 const activity: ActivityAccept = { 27 const activity: ActivityAccept = {
28 type: 'Accept', 28 type: 'Accept',
29 id: url, 29 id: url,
diff --git a/server/lib/activitypub/send/send-add.ts b/server/lib/activitypub/send/send-add.ts
index d8ac2853e..fd614db75 100644
--- a/server/lib/activitypub/send/send-add.ts
+++ b/server/lib/activitypub/send/send-add.ts
@@ -1,10 +1,11 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/models/activitypub/activity' 2import { ActivityAdd } from '../../../../shared/models/activitypub'
3import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' 3import { VideoPrivacy } from '../../../../shared/models/videos'
4import { AccountInstance, VideoInstance } from '../../../models' 4import { AccountModel } from '../../../models/account/account'
5import { VideoModel } from '../../../models/video/video'
5import { broadcastToFollowers, getAudience } from './misc' 6import { broadcastToFollowers, getAudience } from './misc'
6 7
7async function sendAddVideo (video: VideoInstance, t: Transaction) { 8async function sendAddVideo (video: VideoModel, t: Transaction) {
8 const byAccount = video.VideoChannel.Account 9 const byAccount = video.VideoChannel.Account
9 10
10 const videoObject = video.toActivityPubObject() 11 const videoObject = video.toActivityPubObject()
@@ -15,16 +16,17 @@ async function sendAddVideo (video: VideoInstance, t: Transaction) {
15 16
16async function addActivityData ( 17async function addActivityData (
17 url: string, 18 url: string,
18 byAccount: AccountInstance, 19 byAccount: AccountModel,
19 video: VideoInstance, 20 video: VideoModel,
20 target: string, 21 target: string,
21 object: any, 22 object: any,
22 t: Transaction 23 t: Transaction
23) { 24): Promise<ActivityAdd> {
24 const videoPublic = video.privacy === VideoPrivacy.PUBLIC 25 const videoPublic = video.privacy === VideoPrivacy.PUBLIC
25 26
26 const { to, cc } = await getAudience(byAccount, t, videoPublic) 27 const { to, cc } = await getAudience(byAccount, t, videoPublic)
27 const activity: ActivityAdd = { 28
29 return {
28 type: 'Add', 30 type: 'Add',
29 id: url, 31 id: url,
30 actor: byAccount.url, 32 actor: byAccount.url,
@@ -33,8 +35,6 @@ async function addActivityData (
33 object, 35 object,
34 target 36 target
35 } 37 }
36
37 return activity
38} 38}
39 39
40// --------------------------------------------------------------------------- 40// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 3acf604cd..e685323e8 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -1,8 +1,9 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAdd } from '../../../../shared/index' 2import { ActivityAdd } from '../../../../shared/index'
3import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' 3import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
4import { AccountInstance, VideoInstance } from '../../../models' 4import { AccountModel } from '../../../models/account/account'
5import { VideoChannelInstance } from '../../../models/video/video-channel-interface' 5import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel'
6import { getAnnounceActivityPubUrl } from '../url' 7import { getAnnounceActivityPubUrl } from '../url'
7import { 8import {
8 broadcastToFollowers, 9 broadcastToFollowers,
@@ -17,7 +18,7 @@ import {
17import { addActivityData } from './send-add' 18import { addActivityData } from './send-add'
18import { createActivityData } from './send-create' 19import { createActivityData } from './send-create'
19 20
20async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 21async function buildVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
21 const url = getAnnounceActivityPubUrl(video.url, byAccount) 22 const url = getAnnounceActivityPubUrl(video.url, byAccount)
22 23
23 const videoChannel = video.VideoChannel 24 const videoChannel = video.VideoChannel
@@ -25,18 +26,16 @@ async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video:
25 26
26 const accountsToForwardView = await getAccountsInvolvedInVideo(video, t) 27 const accountsToForwardView = await getAccountsInvolvedInVideo(video, t)
27 const audience = getObjectFollowersAudience(accountsToForwardView) 28 const audience = getObjectFollowersAudience(accountsToForwardView)
28 const data = await announceActivityData(url, byAccount, announcedActivity, t, audience) 29 return announceActivityData(url, byAccount, announcedActivity, t, audience)
29
30 return data
31} 30}
32 31
33async function sendVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 32async function sendVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
34 const data = await buildVideoAnnounceToFollowers(byAccount, video, t) 33 const data = await buildVideoAnnounceToFollowers(byAccount, video, t)
35 34
36 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 35 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
37} 36}
38 37
39async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 38async function sendVideoAnnounceToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
40 const url = getAnnounceActivityPubUrl(video.url, byAccount) 39 const url = getAnnounceActivityPubUrl(video.url, byAccount)
41 40
42 const videoChannel = video.VideoChannel 41 const videoChannel = video.VideoChannel
@@ -49,24 +48,22 @@ async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: Vid
49 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t) 48 return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t)
50} 49}
51 50
52async function buildVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { 51async function buildVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
53 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) 52 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
54 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t) 53 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
55 54
56 const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel, t) 55 const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel, t)
57 const audience = getObjectFollowersAudience(accountsToForwardView) 56 const audience = getObjectFollowersAudience(accountsToForwardView)
58 const data = await announceActivityData(url, byAccount, announcedActivity, t, audience) 57 return announceActivityData(url, byAccount, announcedActivity, t, audience)
59
60 return data
61} 58}
62 59
63async function sendVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { 60async function sendVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
64 const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t) 61 const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t)
65 62
66 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 63 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
67} 64}
68 65
69async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { 66async function sendVideoChannelAnnounceToOrigin (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) {
70 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) 67 const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount)
71 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t) 68 const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t)
72 69
@@ -79,16 +76,16 @@ async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, vid
79 76
80async function announceActivityData ( 77async function announceActivityData (
81 url: string, 78 url: string,
82 byAccount: AccountInstance, 79 byAccount: AccountModel,
83 object: ActivityCreate | ActivityAdd, 80 object: ActivityCreate | ActivityAdd,
84 t: Transaction, 81 t: Transaction,
85 audience?: ActivityAudience 82 audience?: ActivityAudience
86) { 83): Promise<ActivityAnnounce> {
87 if (!audience) { 84 if (!audience) {
88 audience = await getAudience(byAccount, t) 85 audience = await getAudience(byAccount, t)
89 } 86 }
90 87
91 const activity: ActivityAnnounce = { 88 return {
92 type: 'Announce', 89 type: 'Announce',
93 to: audience.to, 90 to: audience.to,
94 cc: audience.cc, 91 cc: audience.cc,
@@ -96,8 +93,6 @@ async function announceActivityData (
96 actor: byAccount.url, 93 actor: byAccount.url,
97 object 94 object
98 } 95 }
99
100 return activity
101} 96}
102 97
103// --------------------------------------------------------------------------- 98// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index a34d3776c..9fbaa8196 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -1,8 +1,10 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' 2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
3import { getServerAccount } from '../../../helpers/utils' 3import { getServerAccount } from '../../../helpers'
4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' 4import { AccountModel } from '../../../models/account/account'
5import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' 5import { VideoModel } from '../../../models/video/video'
6import { VideoAbuseModel } from '../../../models/video/video-abuse'
7import { VideoChannelModel } from '../../../models/video/video-channel'
6import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 8import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
7import { 9import {
8 broadcastToFollowers, 10 broadcastToFollowers,
@@ -13,7 +15,7 @@ import {
13 unicastTo 15 unicastTo
14} from './misc' 16} from './misc'
15 17
16async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { 18async function sendCreateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
17 const byAccount = videoChannel.Account 19 const byAccount = videoChannel.Account
18 20
19 const videoChannelObject = videoChannel.toActivityPubObject() 21 const videoChannelObject = videoChannel.toActivityPubObject()
@@ -22,7 +24,7 @@ async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Tr
22 return broadcastToFollowers(data, byAccount, [ byAccount ], t) 24 return broadcastToFollowers(data, byAccount, [ byAccount ], t)
23} 25}
24 26
25async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) { 27async function sendVideoAbuse (byAccount: AccountModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
26 const url = getVideoAbuseActivityPubUrl(videoAbuse) 28 const url = getVideoAbuseActivityPubUrl(videoAbuse)
27 29
28 const audience = { to: [ video.VideoChannel.Account.url ], cc: [] } 30 const audience = { to: [ video.VideoChannel.Account.url ], cc: [] }
@@ -31,7 +33,7 @@ async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbus
31 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 33 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
32} 34}
33 35
34async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 36async function sendCreateViewToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
35 const url = getVideoViewActivityPubUrl(byAccount, video) 37 const url = getVideoViewActivityPubUrl(byAccount, video)
36 const viewActivity = createViewActivityData(byAccount, video) 38 const viewActivity = createViewActivityData(byAccount, video)
37 39
@@ -42,7 +44,7 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI
42 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 44 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
43} 45}
44 46
45async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 47async function sendCreateViewToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
46 const url = getVideoViewActivityPubUrl(byAccount, video) 48 const url = getVideoViewActivityPubUrl(byAccount, video)
47 const viewActivity = createViewActivityData(byAccount, video) 49 const viewActivity = createViewActivityData(byAccount, video)
48 50
@@ -56,7 +58,7 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
56 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 58 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
57} 59}
58 60
59async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 61async function sendCreateDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
60 const url = getVideoDislikeActivityPubUrl(byAccount, video) 62 const url = getVideoDislikeActivityPubUrl(byAccount, video)
61 const dislikeActivity = createDislikeActivityData(byAccount, video) 63 const dislikeActivity = createDislikeActivityData(byAccount, video)
62 64
@@ -67,7 +69,7 @@ async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: Vid
67 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 69 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
68} 70}
69 71
70async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 72async function sendCreateDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
71 const url = getVideoDislikeActivityPubUrl(byAccount, video) 73 const url = getVideoDislikeActivityPubUrl(byAccount, video)
72 const dislikeActivity = createDislikeActivityData(byAccount, video) 74 const dislikeActivity = createDislikeActivityData(byAccount, video)
73 75
@@ -79,12 +81,18 @@ async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, vi
79 return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException) 81 return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException)
80} 82}
81 83
82async function createActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction, audience?: ActivityAudience) { 84async function createActivityData (
85 url: string,
86 byAccount: AccountModel,
87 object: any,
88 t: Transaction,
89 audience?: ActivityAudience
90): Promise<ActivityCreate> {
83 if (!audience) { 91 if (!audience) {
84 audience = await getAudience(byAccount, t) 92 audience = await getAudience(byAccount, t)
85 } 93 }
86 94
87 const activity: ActivityCreate = { 95 return {
88 type: 'Create', 96 type: 'Create',
89 id: url, 97 id: url,
90 actor: byAccount.url, 98 actor: byAccount.url,
@@ -92,18 +100,14 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje
92 cc: audience.cc, 100 cc: audience.cc,
93 object 101 object
94 } 102 }
95
96 return activity
97} 103}
98 104
99function createDislikeActivityData (byAccount: AccountInstance, video: VideoInstance) { 105function createDislikeActivityData (byAccount: AccountModel, video: VideoModel) {
100 const obj = { 106 return {
101 type: 'Dislike', 107 type: 'Dislike',
102 actor: byAccount.url, 108 actor: byAccount.url,
103 object: video.url 109 object: video.url
104 } 110 }
105
106 return obj
107} 111}
108 112
109// --------------------------------------------------------------------------- 113// ---------------------------------------------------------------------------
@@ -121,12 +125,10 @@ export {
121 125
122// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
123 127
124function createViewActivityData (byAccount: AccountInstance, video: VideoInstance) { 128function createViewActivityData (byAccount: AccountModel, video: VideoModel) {
125 const obj = { 129 return {
126 type: 'View', 130 type: 'View',
127 actor: byAccount.url, 131 actor: byAccount.url,
128 object: video.url 132 object: video.url
129 } 133 }
130
131 return obj
132} 134}
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 8193790b3..0a45ea10f 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -1,32 +1,35 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityDelete } from '../../../../shared/models/activitypub/activity' 2import { ActivityDelete } from '../../../../shared/models/activitypub'
3import { database as db } from '../../../initializers' 3import { AccountModel } from '../../../models/account/account'
4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' 4import { VideoModel } from '../../../models/video/video'
5import { VideoChannelModel } from '../../../models/video/video-channel'
6import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
7import { VideoShareModel } from '../../../models/video/video-share'
5import { broadcastToFollowers } from './misc' 8import { broadcastToFollowers } from './misc'
6 9
7async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { 10async function sendDeleteVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
8 const byAccount = videoChannel.Account 11 const byAccount = videoChannel.Account
9 12
10 const data = deleteActivityData(videoChannel.url, byAccount) 13 const data = deleteActivityData(videoChannel.url, byAccount)
11 14
12 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) 15 const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
13 accountsInvolved.push(byAccount) 16 accountsInvolved.push(byAccount)
14 17
15 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 18 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
16} 19}
17 20
18async function sendDeleteVideo (video: VideoInstance, t: Transaction) { 21async function sendDeleteVideo (video: VideoModel, t: Transaction) {
19 const byAccount = video.VideoChannel.Account 22 const byAccount = video.VideoChannel.Account
20 23
21 const data = deleteActivityData(video.url, byAccount) 24 const data = deleteActivityData(video.url, byAccount)
22 25
23 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id, t) 26 const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t)
24 accountsInvolved.push(byAccount) 27 accountsInvolved.push(byAccount)
25 28
26 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 29 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
27} 30}
28 31
29async function sendDeleteAccount (account: AccountInstance, t: Transaction) { 32async function sendDeleteAccount (account: AccountModel, t: Transaction) {
30 const data = deleteActivityData(account.url, account) 33 const data = deleteActivityData(account.url, account)
31 34
32 return broadcastToFollowers(data, account, [ account ], t) 35 return broadcastToFollowers(data, account, [ account ], t)
@@ -42,12 +45,10 @@ export {
42 45
43// --------------------------------------------------------------------------- 46// ---------------------------------------------------------------------------
44 47
45function deleteActivityData (url: string, byAccount: AccountInstance) { 48function deleteActivityData (url: string, byAccount: AccountModel): ActivityDelete {
46 const activity: ActivityDelete = { 49 return {
47 type: 'Delete', 50 type: 'Delete',
48 id: url, 51 id: url,
49 actor: byAccount.url 52 actor: byAccount.url
50 } 53 }
51
52 return activity
53} 54}
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index 8fba1b6b5..51735ddfd 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -1,11 +1,11 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityFollow } from '../../../../shared/models/activitypub/activity' 2import { ActivityFollow } from '../../../../shared/models/activitypub'
3import { AccountInstance } from '../../../models' 3import { AccountModel } from '../../../models/account/account'
4import { AccountFollowInstance } from '../../../models/account/account-follow-interface' 4import { AccountFollowModel } from '../../../models/account/account-follow'
5import { getAccountFollowActivityPubUrl } from '../url' 5import { getAccountFollowActivityPubUrl } from '../url'
6import { unicastTo } from './misc' 6import { unicastTo } from './misc'
7 7
8function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) { 8function sendFollow (accountFollow: AccountFollowModel, t: Transaction) {
9 const me = accountFollow.AccountFollower 9 const me = accountFollow.AccountFollower
10 const following = accountFollow.AccountFollowing 10 const following = accountFollow.AccountFollowing
11 11
@@ -15,15 +15,13 @@ function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) {
15 return unicastTo(data, me, following.inboxUrl, t) 15 return unicastTo(data, me, following.inboxUrl, t)
16} 16}
17 17
18function followActivityData (url: string, byAccount: AccountInstance, targetAccount: AccountInstance) { 18function followActivityData (url: string, byAccount: AccountModel, targetAccount: AccountModel): ActivityFollow {
19 const activity: ActivityFollow = { 19 return {
20 type: 'Follow', 20 type: 'Follow',
21 id: url, 21 id: url,
22 actor: byAccount.url, 22 actor: byAccount.url,
23 object: targetAccount.url 23 object: targetAccount.url
24 } 24 }
25
26 return activity
27} 25}
28 26
29// --------------------------------------------------------------------------- 27// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 0c464b2d3..1a35d0db0 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -1,6 +1,7 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub/activity' 2import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
3import { AccountInstance, VideoInstance } from '../../../models' 3import { AccountModel } from '../../../models/account/account'
4import { VideoModel } from '../../../models/video/video'
4import { getVideoLikeActivityPubUrl } from '../url' 5import { getVideoLikeActivityPubUrl } from '../url'
5import { 6import {
6 broadcastToFollowers, 7 broadcastToFollowers,
@@ -11,7 +12,7 @@ import {
11 unicastTo 12 unicastTo
12} from './misc' 13} from './misc'
13 14
14async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 15async function sendLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
15 const url = getVideoLikeActivityPubUrl(byAccount, video) 16 const url = getVideoLikeActivityPubUrl(byAccount, video)
16 17
17 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 18 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
@@ -21,7 +22,7 @@ async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstanc
21 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 22 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
22} 23}
23 24
24async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 25async function sendLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
25 const url = getVideoLikeActivityPubUrl(byAccount, video) 26 const url = getVideoLikeActivityPubUrl(byAccount, video)
26 27
27 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) 28 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t)
@@ -34,16 +35,16 @@ async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: Vide
34 35
35async function likeActivityData ( 36async function likeActivityData (
36 url: string, 37 url: string,
37 byAccount: AccountInstance, 38 byAccount: AccountModel,
38 video: VideoInstance, 39 video: VideoModel,
39 t: Transaction, 40 t: Transaction,
40 audience?: ActivityAudience 41 audience?: ActivityAudience
41) { 42): Promise<ActivityLike> {
42 if (!audience) { 43 if (!audience) {
43 audience = await getAudience(byAccount, t) 44 audience = await getAudience(byAccount, t)
44 } 45 }
45 46
46 const activity: ActivityLike = { 47 return {
47 type: 'Like', 48 type: 'Like',
48 id: url, 49 id: url,
49 actor: byAccount.url, 50 actor: byAccount.url,
@@ -51,8 +52,6 @@ async function likeActivityData (
51 cc: audience.cc, 52 cc: audience.cc,
52 object: video.url 53 object: video.url
53 } 54 }
54
55 return activity
56} 55}
57 56
58// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 015f02b35..699f920f0 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -5,10 +5,10 @@ import {
5 ActivityFollow, 5 ActivityFollow,
6 ActivityLike, 6 ActivityLike,
7 ActivityUndo 7 ActivityUndo
8} from '../../../../shared/models/activitypub/activity' 8} from '../../../../shared/models/activitypub'
9import { AccountInstance } from '../../../models' 9import { AccountModel } from '../../../models/account/account'
10import { AccountFollowInstance } from '../../../models/account/account-follow-interface' 10import { AccountFollowModel } from '../../../models/account/account-follow'
11import { VideoInstance } from '../../../models/video/video-interface' 11import { VideoModel } from '../../../models/video/video'
12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
13import { 13import {
14 broadcastToFollowers, 14 broadcastToFollowers,
@@ -22,7 +22,7 @@ import { createActivityData, createDislikeActivityData } from './send-create'
22import { followActivityData } from './send-follow' 22import { followActivityData } from './send-follow'
23import { likeActivityData } from './send-like' 23import { likeActivityData } from './send-like'
24 24
25async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) { 25async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction) {
26 const me = accountFollow.AccountFollower 26 const me = accountFollow.AccountFollower
27 const following = accountFollow.AccountFollowing 27 const following = accountFollow.AccountFollowing
28 28
@@ -35,7 +35,7 @@ async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transact
35 return unicastTo(data, me, following.inboxUrl, t) 35 return unicastTo(data, me, following.inboxUrl, t)
36} 36}
37 37
38async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 38async function sendUndoLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
39 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) 39 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
40 const undoUrl = getUndoActivityPubUrl(likeUrl) 40 const undoUrl = getUndoActivityPubUrl(likeUrl)
41 41
@@ -47,7 +47,7 @@ async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoIns
47 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 47 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
48} 48}
49 49
50async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 50async function sendUndoLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
51 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) 51 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
52 const undoUrl = getUndoActivityPubUrl(likeUrl) 52 const undoUrl = getUndoActivityPubUrl(likeUrl)
53 53
@@ -60,7 +60,7 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video:
60 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException) 60 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
61} 61}
62 62
63async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 63async function sendUndoDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) {
64 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) 64 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
65 const undoUrl = getUndoActivityPubUrl(dislikeUrl) 65 const undoUrl = getUndoActivityPubUrl(dislikeUrl)
66 66
@@ -74,7 +74,7 @@ async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: Video
74 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 74 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
75} 75}
76 76
77async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 77async function sendUndoDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) {
78 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) 78 const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video)
79 const undoUrl = getUndoActivityPubUrl(dislikeUrl) 79 const undoUrl = getUndoActivityPubUrl(dislikeUrl)
80 80
@@ -103,16 +103,16 @@ export {
103 103
104async function undoActivityData ( 104async function undoActivityData (
105 url: string, 105 url: string,
106 byAccount: AccountInstance, 106 byAccount: AccountModel,
107 object: ActivityFollow | ActivityLike | ActivityCreate, 107 object: ActivityFollow | ActivityLike | ActivityCreate,
108 t: Transaction, 108 t: Transaction,
109 audience?: ActivityAudience 109 audience?: ActivityAudience
110) { 110): Promise<ActivityUndo> {
111 if (!audience) { 111 if (!audience) {
112 audience = await getAudience(byAccount, t) 112 audience = await getAudience(byAccount, t)
113 } 113 }
114 114
115 const activity: ActivityUndo = { 115 return {
116 type: 'Undo', 116 type: 'Undo',
117 id: url, 117 id: url,
118 actor: byAccount.url, 118 actor: byAccount.url,
@@ -120,6 +120,4 @@ async function undoActivityData (
120 cc: audience.cc, 120 cc: audience.cc,
121 object 121 object
122 } 122 }
123
124 return activity
125} 123}
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 59524e523..9baf13a87 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -1,31 +1,34 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' 2import { ActivityUpdate } from '../../../../shared/models/activitypub'
3import { database as db } from '../../../initializers' 3import { AccountModel } from '../../../models/account/account'
4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' 4import { VideoModel } from '../../../models/video/video'
5import { VideoChannelModel } from '../../../models/video/video-channel'
6import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
7import { VideoShareModel } from '../../../models/video/video-share'
5import { getUpdateActivityPubUrl } from '../url' 8import { getUpdateActivityPubUrl } from '../url'
6import { broadcastToFollowers, getAudience } from './misc' 9import { broadcastToFollowers, getAudience } from './misc'
7 10
8async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { 11async function sendUpdateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
9 const byAccount = videoChannel.Account 12 const byAccount = videoChannel.Account
10 13
11 const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString()) 14 const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString())
12 const videoChannelObject = videoChannel.toActivityPubObject() 15 const videoChannelObject = videoChannel.toActivityPubObject()
13 const data = await updateActivityData(url, byAccount, videoChannelObject, t) 16 const data = await updateActivityData(url, byAccount, videoChannelObject, t)
14 17
15 const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) 18 const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
16 accountsInvolved.push(byAccount) 19 accountsInvolved.push(byAccount)
17 20
18 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 21 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
19} 22}
20 23
21async function sendUpdateVideo (video: VideoInstance, t: Transaction) { 24async function sendUpdateVideo (video: VideoModel, t: Transaction) {
22 const byAccount = video.VideoChannel.Account 25 const byAccount = video.VideoChannel.Account
23 26
24 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 27 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
25 const videoObject = video.toActivityPubObject() 28 const videoObject = video.toActivityPubObject()
26 const data = await updateActivityData(url, byAccount, videoObject, t) 29 const data = await updateActivityData(url, byAccount, videoObject, t)
27 30
28 const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id, t) 31 const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t)
29 accountsInvolved.push(byAccount) 32 accountsInvolved.push(byAccount)
30 33
31 return broadcastToFollowers(data, byAccount, accountsInvolved, t) 34 return broadcastToFollowers(data, byAccount, accountsInvolved, t)
@@ -40,9 +43,9 @@ export {
40 43
41// --------------------------------------------------------------------------- 44// ---------------------------------------------------------------------------
42 45
43async function updateActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction) { 46async function updateActivityData (url: string, byAccount: AccountModel, object: any, t: Transaction): Promise<ActivityUpdate> {
44 const { to, cc } = await getAudience(byAccount, t) 47 const { to, cc } = await getAudience(byAccount, t)
45 const activity: ActivityUpdate = { 48 return {
46 type: 'Update', 49 type: 'Update',
47 id: url, 50 id: url,
48 actor: byAccount.url, 51 actor: byAccount.url,
@@ -50,6 +53,4 @@ async function updateActivityData (url: string, byAccount: AccountInstance, obje
50 cc, 53 cc,
51 object 54 object
52 } 55 }
53
54 return activity
55} 56}
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts
index e14b0f50c..5bec61c05 100644
--- a/server/lib/activitypub/share.ts
+++ b/server/lib/activitypub/share.ts
@@ -1,14 +1,15 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { getServerAccount } from '../../helpers/utils' 2import { getServerAccount } from '../../helpers'
3import { database as db } from '../../initializers' 3import { VideoModel } from '../../models/video/video'
4import { VideoChannelInstance } from '../../models/index' 4import { VideoChannelModel } from '../../models/video/video-channel'
5import { VideoInstance } from '../../models/video/video-interface' 5import { VideoChannelShareModel } from '../../models/video/video-channel-share'
6import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send/send-announce' 6import { VideoShareModel } from '../../models/video/video-share'
7import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send'
7 8
8async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) { 9async function shareVideoChannelByServer (videoChannel: VideoChannelModel, t: Transaction) {
9 const serverAccount = await getServerAccount() 10 const serverAccount = await getServerAccount()
10 11
11 await db.VideoChannelShare.create({ 12 await VideoChannelShareModel.create({
12 accountId: serverAccount.id, 13 accountId: serverAccount.id,
13 videoChannelId: videoChannel.id 14 videoChannelId: videoChannel.id
14 }, { transaction: t }) 15 }, { transaction: t })
@@ -16,10 +17,10 @@ async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t:
16 return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t) 17 return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t)
17} 18}
18 19
19async function shareVideoByServer (video: VideoInstance, t: Transaction) { 20async function shareVideoByServer (video: VideoModel, t: Transaction) {
20 const serverAccount = await getServerAccount() 21 const serverAccount = await getServerAccount()
21 22
22 await db.VideoShare.create({ 23 await VideoShareModel.create({
23 accountId: serverAccount.id, 24 accountId: serverAccount.id,
24 videoId: video.id 25 videoId: video.id
25 }, { transaction: t }) 26 }, { transaction: t })
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts
index 6475c4218..00b4e8852 100644
--- a/server/lib/activitypub/url.ts
+++ b/server/lib/activitypub/url.ts
@@ -1,15 +1,15 @@
1import { CONFIG } from '../../initializers/constants' 1import { CONFIG } from '../../initializers'
2import { VideoInstance } from '../../models/video/video-interface' 2import { AccountModel } from '../../models/account/account'
3import { VideoChannelInstance } from '../../models/video/video-channel-interface' 3import { AccountFollowModel } from '../../models/account/account-follow'
4import { VideoAbuseInstance } from '../../models/video/video-abuse-interface' 4import { VideoModel } from '../../models/video/video'
5import { AccountFollowInstance } from '../../models/account/account-follow-interface' 5import { VideoAbuseModel } from '../../models/video/video-abuse'
6import { AccountInstance } from '../../models/account/account-interface' 6import { VideoChannelModel } from '../../models/video/video-channel'
7 7
8function getVideoActivityPubUrl (video: VideoInstance) { 8function getVideoActivityPubUrl (video: VideoModel) {
9 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid 9 return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
10} 10}
11 11
12function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) { 12function getVideoChannelActivityPubUrl (videoChannel: VideoChannelModel) {
13 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid 13 return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid
14} 14}
15 15
@@ -17,37 +17,37 @@ function getAccountActivityPubUrl (accountName: string) {
17 return CONFIG.WEBSERVER.URL + '/account/' + accountName 17 return CONFIG.WEBSERVER.URL + '/account/' + accountName
18} 18}
19 19
20function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) { 20function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
21 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id 21 return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
22} 22}
23 23
24function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 24function getVideoViewActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
25 return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString() 25 return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString()
26} 26}
27 27
28function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 28function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
29 return byAccount.url + '/likes/' + video.id 29 return byAccount.url + '/likes/' + video.id
30} 30}
31 31
32function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { 32function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) {
33 return byAccount.url + '/dislikes/' + video.id 33 return byAccount.url + '/dislikes/' + video.id
34} 34}
35 35
36function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { 36function getAccountFollowActivityPubUrl (accountFollow: AccountFollowModel) {
37 const me = accountFollow.AccountFollower 37 const me = accountFollow.AccountFollower
38 const following = accountFollow.AccountFollowing 38 const following = accountFollow.AccountFollowing
39 39
40 return me.url + '/follows/' + following.id 40 return me.url + '/follows/' + following.id
41} 41}
42 42
43function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { 43function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowModel) {
44 const follower = accountFollow.AccountFollower 44 const follower = accountFollow.AccountFollower
45 const me = accountFollow.AccountFollowing 45 const me = accountFollow.AccountFollowing
46 46
47 return follower.url + '/accepts/follows/' + me.id 47 return follower.url + '/accepts/follows/' + me.id
48} 48}
49 49
50function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { 50function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountModel) {
51 return originalUrl + '/announces/' + byAccount.id 51 return originalUrl + '/announces/' + byAccount.id
52} 52}
53 53
diff --git a/server/lib/activitypub/video-channels.ts b/server/lib/activitypub/video-channels.ts
index 7339d79f9..c05a46f95 100644
--- a/server/lib/activitypub/video-channels.ts
+++ b/server/lib/activitypub/video-channels.ts
@@ -1,14 +1,13 @@
1import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' 1import { VideoChannelObject } from '../../../shared/models/activitypub/objects'
2import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub/video-channels' 2import { doRequest, logger } from '../../helpers'
3import { logger } from '../../helpers/logger' 3import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub'
4import { doRequest } from '../../helpers/requests' 4import { ACTIVITY_PUB } from '../../initializers'
5import { database as db } from '../../initializers' 5import { AccountModel } from '../../models/account/account'
6import { ACTIVITY_PUB } from '../../initializers/constants' 6import { VideoChannelModel } from '../../models/video/video-channel'
7import { AccountInstance } from '../../models/account/account-interface'
8import { videoChannelActivityObjectToDBAttributes } from './process/misc' 7import { videoChannelActivityObjectToDBAttributes } from './process/misc'
9 8
10async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { 9async function getOrCreateVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
11 let videoChannel = await db.VideoChannel.loadByUrl(videoChannelUrl) 10 let videoChannel = await VideoChannelModel.loadByUrl(videoChannelUrl)
12 11
13 // We don't have this account in our database, fetch it on remote 12 // We don't have this account in our database, fetch it on remote
14 if (!videoChannel) { 13 if (!videoChannel) {
@@ -22,7 +21,7 @@ async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChan
22 return videoChannel 21 return videoChannel
23} 22}
24 23
25async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { 24async function fetchRemoteVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) {
26 const options = { 25 const options = {
27 uri: videoChannelUrl, 26 uri: videoChannelUrl,
28 method: 'GET', 27 method: 'GET',
@@ -48,7 +47,7 @@ async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChan
48 } 47 }
49 48
50 const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount) 49 const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount)
51 const videoChannel = db.VideoChannel.build(videoChannelAttributes) 50 const videoChannel = new VideoChannelModel(videoChannelAttributes)
52 videoChannel.Account = ownerAccount 51 videoChannel.Account = ownerAccount
53 52
54 return videoChannel 53 return videoChannel
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index 6b82f12d5..14c07fec0 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -2,21 +2,22 @@ import { join } from 'path'
2import * as request from 'request' 2import * as request from 'request'
3import { Transaction } from 'sequelize' 3import { Transaction } from 'sequelize'
4import { ActivityIconObject } from '../../../shared/index' 4import { ActivityIconObject } from '../../../shared/index'
5import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 5import { doRequest, doRequestAndSaveToFile } from '../../helpers'
6import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants' 6import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers'
7import { AccountInstance } from '../../models/account/account-interface' 7import { AccountModel } from '../../models/account/account'
8import { VideoInstance } from '../../models/video/video-interface' 8import { VideoModel } from '../../models/video/video'
9import { sendLikeToOrigin } from './index'
10import { sendCreateDislikeToOrigin, sendCreateDislikeToVideoFollowers } from './send/send-create'
11import { sendLikeToVideoFollowers } from './send/send-like'
12import { 9import {
10 sendCreateDislikeToOrigin,
11 sendCreateDislikeToVideoFollowers,
12 sendLikeToOrigin,
13 sendLikeToVideoFollowers,
13 sendUndoDislikeToOrigin, 14 sendUndoDislikeToOrigin,
14 sendUndoDislikeToVideoFollowers, 15 sendUndoDislikeToVideoFollowers,
15 sendUndoLikeToOrigin, 16 sendUndoLikeToOrigin,
16 sendUndoLikeToVideoFollowers 17 sendUndoLikeToVideoFollowers
17} from './send/send-undo' 18} from './send'
18 19
19function fetchRemoteVideoPreview (video: VideoInstance) { 20function fetchRemoteVideoPreview (video: VideoModel) {
20 // FIXME: use url 21 // FIXME: use url
21 const host = video.VideoChannel.Account.Server.host 22 const host = video.VideoChannel.Account.Server.host
22 const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) 23 const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName())
@@ -24,7 +25,7 @@ function fetchRemoteVideoPreview (video: VideoInstance) {
24 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) 25 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
25} 26}
26 27
27async function fetchRemoteVideoDescription (video: VideoInstance) { 28async function fetchRemoteVideoDescription (video: VideoModel) {
28 // FIXME: use url 29 // FIXME: use url
29 const host = video.VideoChannel.Account.Server.host 30 const host = video.VideoChannel.Account.Server.host
30 const path = video.getDescriptionPath() 31 const path = video.getDescriptionPath()
@@ -37,7 +38,7 @@ async function fetchRemoteVideoDescription (video: VideoInstance) {
37 return body.description ? body.description : '' 38 return body.description ? body.description : ''
38} 39}
39 40
40function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { 41function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) {
41 const thumbnailName = video.getThumbnailName() 42 const thumbnailName = video.getThumbnailName()
42 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) 43 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
43 44
@@ -49,8 +50,8 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec
49} 50}
50 51
51async function sendVideoRateChangeToFollowers ( 52async function sendVideoRateChangeToFollowers (
52 account: AccountInstance, 53 account: AccountModel,
53 video: VideoInstance, 54 video: VideoModel,
54 likes: number, 55 likes: number,
55 dislikes: number, 56 dislikes: number,
56 t: Transaction 57 t: Transaction
@@ -69,8 +70,8 @@ async function sendVideoRateChangeToFollowers (
69} 70}
70 71
71async function sendVideoRateChangeToOrigin ( 72async function sendVideoRateChangeToOrigin (
72 account: AccountInstance, 73 account: AccountModel,
73 video: VideoInstance, 74 video: VideoModel,
74 likes: number, 75 likes: number,
75 dislikes: number, 76 dislikes: number,
76 t: Transaction 77 t: Transaction
diff --git a/server/lib/cache/videos-preview-cache.ts b/server/lib/cache/videos-preview-cache.ts
index 7f352f361..c5bda8dd8 100644
--- a/server/lib/cache/videos-preview-cache.ts
+++ b/server/lib/cache/videos-preview-cache.ts
@@ -1,11 +1,10 @@
1import * as asyncLRU from 'async-lru' 1import * as asyncLRU from 'async-lru'
2import { join } from 'path'
3import { createWriteStream } from 'fs' 2import { createWriteStream } from 'fs'
4 3import { join } from 'path'
5import { database as db, CONFIG, CACHE } from '../../initializers'
6import { logger, unlinkPromise } from '../../helpers' 4import { logger, unlinkPromise } from '../../helpers'
7import { VideoInstance } from '../../models' 5import { CACHE, CONFIG } from '../../initializers'
8import { fetchRemoteVideoPreview } from '../activitypub/videos' 6import { VideoModel } from '../../models/video/video'
7import { fetchRemoteVideoPreview } from '../activitypub'
9 8
10class VideosPreviewCache { 9class VideosPreviewCache {
11 10
@@ -43,7 +42,7 @@ class VideosPreviewCache {
43 } 42 }
44 43
45 private async loadPreviews (key: string) { 44 private async loadPreviews (key: string) {
46 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(key) 45 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key)
47 if (!video) return undefined 46 if (!video) return undefined
48 47
49 if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) 48 if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName())
@@ -53,7 +52,7 @@ class VideosPreviewCache {
53 return res 52 return res
54 } 53 }
55 54
56 private saveRemotePreviewAndReturnPath (video: VideoInstance) { 55 private saveRemotePreviewAndReturnPath (video: VideoModel) {
57 const req = fetchRemoteVideoPreview(video) 56 const req = fetchRemoteVideoPreview(video)
58 57
59 return new Promise<string>((res, rej) => { 58 return new Promise<string>((res, rej) => {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
index 49d4bf5c6..8040dde2a 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
@@ -1,5 +1,4 @@
1import { logger } from '../../../helpers' 1import { doRequest, logger } from '../../../helpers'
2import { doRequest } from '../../../helpers/requests'
3import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 2import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
4 3
5async function process (payload: ActivityPubHttpPayload, jobId: number) { 4async function process (payload: ActivityPubHttpPayload, jobId: number) {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
index 9adceab84..638150202 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
@@ -1,7 +1,6 @@
1import { logger } from '../../../helpers' 1import { doRequest, logger } from '../../../helpers'
2import { doRequest } from '../../../helpers/requests' 2import { ACTIVITY_PUB } from '../../../initializers'
3import { ACTIVITY_PUB } from '../../../initializers/constants' 3import { processActivities } from '../../activitypub/process'
4import { processActivities } from '../../activitypub/process/process'
5import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' 4import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler'
6 5
7async function process (payload: ActivityPubHttpPayload, jobId: number) { 6async function process (payload: ActivityPubHttpPayload, jobId: number) {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
index fcc81eb16..76da5b724 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
@@ -1,8 +1,7 @@
1import { JobCategory } from '../../../../shared' 1import { JobCategory } from '../../../../shared'
2import { buildSignedActivity } from '../../../helpers/activitypub' 2import { buildSignedActivity, logger } from '../../../helpers'
3import { logger } from '../../../helpers/logger' 3import { ACTIVITY_PUB } from '../../../initializers'
4import { ACTIVITY_PUB } from '../../../initializers/constants' 4import { AccountModel } from '../../../models/account/account'
5import { database as db } from '../../../initializers/database'
6import { JobHandler, JobScheduler } from '../job-scheduler' 5import { JobHandler, JobScheduler } from '../job-scheduler'
7 6
8import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' 7import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
@@ -46,7 +45,7 @@ async function computeBody (payload: ActivityPubHttpPayload) {
46 let body = payload.body 45 let body = payload.body
47 46
48 if (payload.signatureAccountId) { 47 if (payload.signatureAccountId) {
49 const accountSignature = await db.Account.load(payload.signatureAccountId) 48 const accountSignature = await AccountModel.load(payload.signatureAccountId)
50 if (!accountSignature) throw new Error('Unknown signature account id.') 49 if (!accountSignature) throw new Error('Unknown signature account id.')
51 body = await buildSignedActivity(accountSignature, payload.body) 50 body = await buildSignedActivity(accountSignature, payload.body)
52 } 51 }
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
index 4c95197c4..f16cfcec3 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
@@ -1,5 +1,4 @@
1import { logger } from '../../../helpers' 1import { doRequest, logger } from '../../../helpers'
2import { doRequest } from '../../../helpers/requests'
3import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 2import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
4 3
5async function process (payload: ActivityPubHttpPayload, jobId: number) { 4async function process (payload: ActivityPubHttpPayload, jobId: number) {
diff --git a/server/lib/jobs/job-scheduler.ts b/server/lib/jobs/job-scheduler.ts
index 62ce6927e..88fe8a4a3 100644
--- a/server/lib/jobs/job-scheduler.ts
+++ b/server/lib/jobs/job-scheduler.ts
@@ -2,8 +2,8 @@ import { AsyncQueue, forever, queue } from 'async'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3import { JobCategory } from '../../../shared' 3import { JobCategory } from '../../../shared'
4import { logger } from '../../helpers' 4import { logger } from '../../helpers'
5import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' 5import { JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers'
6import { JobInstance } from '../../models' 6import { JobModel } from '../../models/job/job'
7 7
8export interface JobHandler<P, T> { 8export interface JobHandler<P, T> {
9 process (data: object, jobId: number): Promise<T> 9 process (data: object, jobId: number): Promise<T>
@@ -24,12 +24,12 @@ class JobScheduler<P, T> {
24 24
25 logger.info('Jobs scheduler %s activated.', this.jobCategory) 25 logger.info('Jobs scheduler %s activated.', this.jobCategory)
26 26
27 const jobsQueue = queue<JobInstance, JobQueueCallback>(this.processJob.bind(this)) 27 const jobsQueue = queue<JobModel, JobQueueCallback>(this.processJob.bind(this))
28 28
29 // Finish processing jobs from a previous start 29 // Finish processing jobs from a previous start
30 const state = JOB_STATES.PROCESSING 30 const state = JOB_STATES.PROCESSING
31 try { 31 try {
32 const jobs = await db.Job.listWithLimitByCategory(limit, state, this.jobCategory) 32 const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory)
33 33
34 this.enqueueJobs(jobsQueue, jobs) 34 this.enqueueJobs(jobsQueue, jobs)
35 } catch (err) { 35 } catch (err) {
@@ -45,7 +45,7 @@ class JobScheduler<P, T> {
45 45
46 const state = JOB_STATES.PENDING 46 const state = JOB_STATES.PENDING
47 try { 47 try {
48 const jobs = await db.Job.listWithLimitByCategory(limit, state, this.jobCategory) 48 const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory)
49 49
50 this.enqueueJobs(jobsQueue, jobs) 50 this.enqueueJobs(jobsQueue, jobs)
51 } catch (err) { 51 } catch (err) {
@@ -70,14 +70,14 @@ class JobScheduler<P, T> {
70 70
71 const options = { transaction } 71 const options = { transaction }
72 72
73 return db.Job.create(createQuery, options) 73 return JobModel.create(createQuery, options)
74 } 74 }
75 75
76 private enqueueJobs (jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) { 76 private enqueueJobs (jobsQueue: AsyncQueue<JobModel>, jobs: JobModel[]) {
77 jobs.forEach(job => jobsQueue.push(job)) 77 jobs.forEach(job => jobsQueue.push(job))
78 } 78 }
79 79
80 private async processJob (job: JobInstance, callback: (err: Error) => void) { 80 private async processJob (job: JobModel, callback: (err: Error) => void) {
81 const jobHandler = this.jobHandlers[job.handlerName] 81 const jobHandler = this.jobHandlers[job.handlerName]
82 if (jobHandler === undefined) { 82 if (jobHandler === undefined) {
83 const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id 83 const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id
@@ -110,7 +110,7 @@ class JobScheduler<P, T> {
110 return callback(null) 110 return callback(null)
111 } 111 }
112 112
113 private async onJobError (jobHandler: JobHandler<P, T>, job: JobInstance, err: Error) { 113 private async onJobError (jobHandler: JobHandler<P, T>, job: JobModel, err: Error) {
114 job.state = JOB_STATES.ERROR 114 job.state = JOB_STATES.ERROR
115 115
116 try { 116 try {
@@ -121,7 +121,7 @@ class JobScheduler<P, T> {
121 } 121 }
122 } 122 }
123 123
124 private async onJobSuccess (jobHandler: JobHandler<P, T>, job: JobInstance, jobResult: T) { 124 private async onJobSuccess (jobHandler: JobHandler<P, T>, job: JobModel, jobResult: T) {
125 job.state = JOB_STATES.SUCCESS 125 job.state = JOB_STATES.SUCCESS
126 126
127 try { 127 try {
diff --git a/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts b/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
index c5efe8eeb..e5530a73c 100644
--- a/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
@@ -1,14 +1,15 @@
1import { JobCategory } from '../../../../shared' 1import { JobCategory } from '../../../../shared'
2import { VideoModel } from '../../../models/video/video'
2import { JobHandler, JobScheduler } from '../job-scheduler' 3import { JobHandler, JobScheduler } from '../job-scheduler'
4
3import * as videoFileOptimizer from './video-file-optimizer-handler' 5import * as videoFileOptimizer from './video-file-optimizer-handler'
4import * as videoFileTranscoder from './video-file-transcoder-handler' 6import * as videoFileTranscoder from './video-file-transcoder-handler'
5import { VideoInstance } from '../../../models/video/video-interface'
6 7
7type TranscodingJobPayload = { 8type TranscodingJobPayload = {
8 videoUUID: string 9 videoUUID: string
9 resolution?: number 10 resolution?: number
10} 11}
11const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoInstance> } = { 12const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoModel> } = {
12 videoFileOptimizer, 13 videoFileOptimizer,
13 videoFileTranscoder 14 videoFileTranscoder
14} 15}
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
index e65ab3ee1..1786ce971 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
@@ -1,14 +1,14 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { computeResolutionsToTranscode, logger } from '../../../helpers' 2import { computeResolutionsToTranscode, logger } from '../../../helpers'
3import { database as db } from '../../../initializers/database' 3import { sequelizeTypescript } from '../../../initializers'
4import { VideoInstance } from '../../../models' 4import { VideoModel } from '../../../models/video/video'
5import { sendAddVideo } from '../../activitypub/send/send-add' 5import { shareVideoByServer } from '../../activitypub'
6import { sendAddVideo } from '../../activitypub/send'
6import { JobScheduler } from '../job-scheduler' 7import { JobScheduler } from '../job-scheduler'
7import { TranscodingJobPayload } from './transcoding-job-scheduler' 8import { TranscodingJobPayload } from './transcoding-job-scheduler'
8import { shareVideoByServer } from '../../activitypub/share'
9 9
10async function process (data: TranscodingJobPayload, jobId: number) { 10async function process (data: TranscodingJobPayload, jobId: number) {
11 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 11 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
12 // No video, maybe deleted? 12 // No video, maybe deleted?
13 if (!video) { 13 if (!video) {
14 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid }) 14 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
@@ -25,13 +25,13 @@ function onError (err: Error, jobId: number) {
25 return Promise.resolve() 25 return Promise.resolve()
26} 26}
27 27
28async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: JobScheduler<TranscodingJobPayload, VideoInstance>) { 28async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobScheduler<TranscodingJobPayload, VideoModel>) {
29 if (video === undefined) return undefined 29 if (video === undefined) return undefined
30 30
31 logger.info('Job %d is a success.', jobId) 31 logger.info('Job %d is a success.', jobId)
32 32
33 // Maybe the video changed in database, refresh it 33 // Maybe the video changed in database, refresh it
34 const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) 34 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid)
35 // Video does not exist anymore 35 // Video does not exist anymore
36 if (!videoDatabase) return undefined 36 if (!videoDatabase) return undefined
37 37
@@ -50,7 +50,7 @@ async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: Job
50 50
51 if (resolutionsEnabled.length !== 0) { 51 if (resolutionsEnabled.length !== 0) {
52 try { 52 try {
53 await db.sequelize.transaction(async t => { 53 await sequelizeTypescript.transaction(async t => {
54 const tasks: Bluebird<any>[] = [] 54 const tasks: Bluebird<any>[] = []
55 55
56 for (const resolution of resolutionsEnabled) { 56 for (const resolution of resolutionsEnabled) {
diff --git a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
index 867580200..8957b4565 100644
--- a/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
+++ b/server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
@@ -1,11 +1,10 @@
1import { VideoResolution } from '../../../../shared' 1import { VideoResolution } from '../../../../shared'
2import { logger } from '../../../helpers' 2import { logger } from '../../../helpers'
3import { database as db } from '../../../initializers/database' 3import { VideoModel } from '../../../models/video/video'
4import { VideoInstance } from '../../../models' 4import { sendUpdateVideo } from '../../activitypub/send'
5import { sendUpdateVideo } from '../../activitypub/send/send-update'
6 5
7async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) { 6async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) {
8 const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) 7 const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID)
9 // No video, maybe deleted? 8 // No video, maybe deleted?
10 if (!video) { 9 if (!video) {
11 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid }) 10 logger.info('Do not process job %d, video does not exist.', jobId, { videoUUID: video.uuid })
@@ -22,13 +21,13 @@ function onError (err: Error, jobId: number) {
22 return Promise.resolve() 21 return Promise.resolve()
23} 22}
24 23
25async function onSuccess (jobId: number, video: VideoInstance) { 24async function onSuccess (jobId: number, video: VideoModel) {
26 if (video === undefined) return undefined 25 if (video === undefined) return undefined
27 26
28 logger.info('Job %d is a success.', jobId) 27 logger.info('Job %d is a success.', jobId)
29 28
30 // Maybe the video changed in database, refresh it 29 // Maybe the video changed in database, refresh it
31 const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) 30 const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid)
32 // Video does not exist anymore 31 // Video does not exist anymore
33 if (!videoDatabase) return undefined 32 if (!videoDatabase) return undefined
34 33
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts
index d91b00c55..dce71e83b 100644
--- a/server/lib/oauth-model.ts
+++ b/server/lib/oauth-model.ts
@@ -1,6 +1,7 @@
1import { OAuthClientInstance, UserInstance } from '../models'
2import { database as db } from '../initializers/database'
3import { logger } from '../helpers' 1import { logger } from '../helpers'
2import { UserModel } from '../models/account/user'
3import { OAuthClientModel } from '../models/oauth/oauth-client'
4import { OAuthTokenModel } from '../models/oauth/oauth-token'
4 5
5type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } 6type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
6 7
@@ -9,25 +10,25 @@ type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpires
9function getAccessToken (bearerToken: string) { 10function getAccessToken (bearerToken: string) {
10 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') 11 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
11 12
12 return db.OAuthToken.getByTokenAndPopulateUser(bearerToken) 13 return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
13} 14}
14 15
15function getClient (clientId: string, clientSecret: string) { 16function getClient (clientId: string, clientSecret: string) {
16 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') 17 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').')
17 18
18 return db.OAuthClient.getByIdAndSecret(clientId, clientSecret) 19 return OAuthClientModel.getByIdAndSecret(clientId, clientSecret)
19} 20}
20 21
21function getRefreshToken (refreshToken: string) { 22function getRefreshToken (refreshToken: string) {
22 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') 23 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').')
23 24
24 return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) 25 return OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken)
25} 26}
26 27
27async function getUser (username: string, password: string) { 28async function getUser (username: string, password: string) {
28 logger.debug('Getting User (username: ' + username + ', password: ******).') 29 logger.debug('Getting User (username: ' + username + ', password: ******).')
29 30
30 const user = await db.User.getByUsername(username) 31 const user = await UserModel.getByUsername(username)
31 if (!user) return null 32 if (!user) return null
32 33
33 const passwordMatch = await user.isPasswordMatch(password) 34 const passwordMatch = await user.isPasswordMatch(password)
@@ -37,7 +38,7 @@ async function getUser (username: string, password: string) {
37} 38}
38 39
39async function revokeToken (tokenInfo: TokenInfo) { 40async function revokeToken (tokenInfo: TokenInfo) {
40 const token = await db.OAuthToken.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) 41 const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
41 if (token) token.destroy() 42 if (token) token.destroy()
42 43
43 /* 44 /*
@@ -53,7 +54,7 @@ async function revokeToken (tokenInfo: TokenInfo) {
53 return expiredToken 54 return expiredToken
54} 55}
55 56
56async function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserInstance) { 57async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) {
57 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') 58 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
58 59
59 const tokenToCreate = { 60 const tokenToCreate = {
@@ -65,7 +66,7 @@ async function saveToken (token: TokenInfo, client: OAuthClientInstance, user: U
65 userId: user.id 66 userId: user.id
66 } 67 }
67 68
68 const tokenCreated = await db.OAuthToken.create(tokenToCreate) 69 const tokenCreated = await OAuthTokenModel.create(tokenToCreate)
69 const tokenToReturn = Object.assign(tokenCreated, { client, user }) 70 const tokenToReturn = Object.assign(tokenCreated, { client, user })
70 71
71 return tokenToReturn 72 return tokenToReturn
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 5653d8e65..c4722fae2 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,14 +1,13 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' 2import { createPrivateAndPublicKeys, logger } from '../helpers'
3import { database as db } from '../initializers' 3import { CONFIG, sequelizeTypescript } from '../initializers'
4import { CONFIG } from '../initializers/constants' 4import { AccountModel } from '../models/account/account'
5import { UserInstance } from '../models' 5import { UserModel } from '../models/account/user'
6import { getAccountActivityPubUrl } from './activitypub'
6import { createVideoChannel } from './video-channel' 7import { createVideoChannel } from './video-channel'
7import { logger } from '../helpers/logger'
8import { getAccountActivityPubUrl } from './activitypub/url'
9 8
10async function createUserAccountAndChannel (user: UserInstance, validateUser = true) { 9async function createUserAccountAndChannel (user: UserModel, validateUser = true) {
11 const { account, videoChannel } = await db.sequelize.transaction(async t => { 10 const { account, videoChannel } = await sequelizeTypescript.transaction(async t => {
12 const userOptions = { 11 const userOptions = {
13 transaction: t, 12 transaction: t,
14 validate: validateUser 13 validate: validateUser
@@ -38,7 +37,7 @@ async function createUserAccountAndChannel (user: UserInstance, validateUser = t
38async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { 37async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) {
39 const url = getAccountActivityPubUrl(name) 38 const url = getAccountActivityPubUrl(name)
40 39
41 const accountInstance = db.Account.build({ 40 const accountInstance = new AccountModel({
42 name, 41 name,
43 url, 42 url,
44 publicKey: null, 43 publicKey: null,
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index beb01da9b..97924aa9e 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -1,10 +1,10 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { VideoChannelCreate } from '../../shared/models' 2import { VideoChannelCreate } from '../../shared/models'
3import { database as db } from '../initializers' 3import { AccountModel } from '../models/account/account'
4import { AccountInstance } from '../models' 4import { VideoChannelModel } from '../models/video/video-channel'
5import { getVideoChannelActivityPubUrl } from './activitypub/url' 5import { getVideoChannelActivityPubUrl } from './activitypub'
6 6
7async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { 7async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
8 const videoChannelData = { 8 const videoChannelData = {
9 name: videoChannelInfo.name, 9 name: videoChannelInfo.name,
10 description: videoChannelInfo.description, 10 description: videoChannelInfo.description,
@@ -12,7 +12,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
12 accountId: account.id 12 accountId: account.id
13 } 13 }
14 14
15 const videoChannel = db.VideoChannel.build(videoChannelData) 15 const videoChannel = VideoChannelModel.build(videoChannelData)
16 videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel)) 16 videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel))
17 17
18 const options = { transaction: t } 18 const options = { transaction: t }
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index c2ad18195..489396447 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -2,16 +2,16 @@ import { eachSeries } from 'async'
2import { NextFunction, Request, RequestHandler, Response } from 'express' 2import { NextFunction, Request, RequestHandler, Response } from 'express'
3import { ActivityPubSignature } from '../../shared' 3import { ActivityPubSignature } from '../../shared'
4import { isSignatureVerified, logger } from '../helpers' 4import { isSignatureVerified, logger } from '../helpers'
5import { database as db } from '../initializers' 5import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers'
6import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers/constants' 6import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub'
7import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub/account' 7import { AccountModel } from '../models/account/account'
8 8
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 9async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 const signatureObject: ActivityPubSignature = req.body.signature 10 const signatureObject: ActivityPubSignature = req.body.signature
11 11
12 logger.debug('Checking signature of account %s...', signatureObject.creator) 12 logger.debug('Checking signature of account %s...', signatureObject.creator)
13 13
14 let account = await db.Account.loadByUrl(signatureObject.creator) 14 let account = await AccountModel.loadByUrl(signatureObject.creator)
15 15
16 // We don't have this account in our database, fetch it on remote 16 // We don't have this account in our database, fetch it on remote
17 if (!account) { 17 if (!account) {
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 7b60920de..5d2a43acc 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -1,8 +1,6 @@
1import 'express-validator'
2import * as express from 'express' 1import * as express from 'express'
3 2import 'express-validator'
4import { SortType } from '../helpers' 3import { SortType } from '../helpers'
5import { database } from '../initializers'
6 4
7function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) { 5function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
8 if (!req.query.sort) req.query.sort = '-createdAt' 6 if (!req.query.sort) req.query.sort = '-createdAt'
@@ -57,7 +55,7 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
57 // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ... 55 // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ...
58 newSort.sortModel = undefined 56 newSort.sortModel = undefined
59 } else { 57 } else {
60 newSort.sortModel = database.Video 58 newSort.sortModel = 'Video'
61 } 59 }
62 60
63 newSort.sortValue = req.query.sort 61 newSort.sortValue = req.query.sort
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts
index bcebe9d7f..5d63ebaf4 100644
--- a/server/middlewares/user-right.ts
+++ b/server/middlewares/user-right.ts
@@ -1,13 +1,12 @@
1import 'express-validator'
2import * as express from 'express' 1import * as express from 'express'
3 2import 'express-validator'
4import { UserInstance } from '../models'
5import { UserRight } from '../../shared' 3import { UserRight } from '../../shared'
6import { logger } from '../helpers' 4import { logger } from '../helpers'
5import { UserModel } from '../models/account/user'
7 6
8function ensureUserHasRight (userRight: UserRight) { 7function ensureUserHasRight (userRight: UserRight) {
9 return function (req: express.Request, res: express.Response, next: express.NextFunction) { 8 return function (req: express.Request, res: express.Response, next: express.NextFunction) {
10 const user: UserInstance = res.locals.oauth.token.user 9 const user = res.locals.oauth.token.user as UserModel
11 if (user.hasRight(userRight) === false) { 10 if (user.hasRight(userRight) === false) {
12 logger.info('User %s does not have right %s to access to %s.', user.username, UserRight[userRight], req.path) 11 logger.info('User %s does not have right %s to access to %s.', user.username, UserRight[userRight], req.path)
13 return res.sendStatus(403) 12 return res.sendStatus(403)
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts
index 70f4e4d3b..6951dfc80 100644
--- a/server/middlewares/validators/account.ts
+++ b/server/middlewares/validators/account.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator/check' 2import { param } from 'express-validator/check'
3import { logger, isLocalAccountNameExist } from '../../helpers' 3import { logger } from '../../helpers'
4import { isAccountNameValid } from '../../helpers/custom-validators/accounts' 4import { isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts'
5import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
6 6
7const localAccountValidator = [ 7const localAccountValidator = [
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts
index c63be5979..e0225f30c 100644
--- a/server/middlewares/validators/activitypub/activity.ts
+++ b/server/middlewares/validators/activitypub/activity.ts
@@ -1,6 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body } from 'express-validator/check' 2import { body } from 'express-validator/check'
3import { isRootActivityValid, logger } from '../../../helpers' 3import { logger } from '../../../helpers'
4import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub'
4import { areValidationErrors } from '../utils' 5import { areValidationErrors } from '../utils'
5 6
6const activityPubValidator = [ 7const activityPubValidator = [
diff --git a/server/middlewares/validators/activitypub/signature.ts b/server/middlewares/validators/activitypub/signature.ts
index 360685512..d41bb6a8d 100644
--- a/server/middlewares/validators/activitypub/signature.ts
+++ b/server/middlewares/validators/activitypub/signature.ts
@@ -1,6 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body } from 'express-validator/check' 2import { body } from 'express-validator/check'
3import { isDateValid, isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid, logger } from '../../../helpers' 3import { logger } from '../../../helpers'
4import { isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid } from '../../../helpers/custom-validators/activitypub'
5import { isDateValid } from '../../../helpers/custom-validators/misc'
4import { areValidationErrors } from '../utils' 6import { areValidationErrors } from '../utils'
5 7
6const signatureValidator = [ 8const signatureValidator = [
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index 605872ecf..10482e5d0 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -1,12 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator/check' 2import { body, param } from 'express-validator/check'
3import { isTestInstance } from '../../helpers/core-utils' 3import { getServerAccount, isTestInstance, logger } from '../../helpers'
4import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
4import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' 5import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers'
5import { logger } from '../../helpers/logger' 6import { CONFIG } from '../../initializers'
6import { CONFIG, database as db } from '../../initializers' 7import { AccountFollowModel } from '../../models/account/account-follow'
7import { areValidationErrors } from './utils' 8import { areValidationErrors } from './utils'
8import { getServerAccount } from '../../helpers/utils'
9import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
10 9
11const followValidator = [ 10const followValidator = [
12 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), 11 body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
@@ -38,7 +37,7 @@ const removeFollowingValidator = [
38 if (areValidationErrors(req, res)) return 37 if (areValidationErrors(req, res)) return
39 38
40 const serverAccount = await getServerAccount() 39 const serverAccount = await getServerAccount()
41 const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId) 40 const follow = await AccountFollowModel.loadByAccountAndTarget(serverAccount.id, req.params.accountId)
42 41
43 if (!follow) { 42 if (!follow) {
44 return res.status(404) 43 return res.status(404)
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts
index 31f06dc65..fb7b726e5 100644
--- a/server/middlewares/validators/oembed.ts
+++ b/server/middlewares/validators/oembed.ts
@@ -1,10 +1,11 @@
1import * as express from 'express' 1import * as express from 'express'
2import { query } from 'express-validator/check' 2import { query } from 'express-validator/check'
3import { join } from 'path' 3import { join } from 'path'
4import { isIdOrUUIDValid, isTestInstance, logger } from '../../helpers' 4import { isTestInstance, logger } from '../../helpers'
5import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
6import { isVideoExist } from '../../helpers/custom-validators/videos'
5import { CONFIG } from '../../initializers' 7import { CONFIG } from '../../initializers'
6import { areValidationErrors } from './utils' 8import { areValidationErrors } from './utils'
7import { isVideoExist } from '../../helpers/custom-validators/videos'
8 9
9const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' 10const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/'
10const videoWatchRegex = new RegExp('([^/]+)$') 11const videoWatchRegex = new RegExp('([^/]+)$')
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index d5822ac81..38184fefa 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -1,6 +1,5 @@
1import { query } from 'express-validator/check' 1import { query } from 'express-validator/check'
2import * as express from 'express' 2import * as express from 'express'
3
4import { logger } from '../../helpers' 3import { logger } from '../../helpers'
5import { SORTABLE_COLUMNS } from '../../initializers' 4import { SORTABLE_COLUMNS } from '../../initializers'
6import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index ac7435b7d..920176d07 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -1,18 +1,17 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'express-validator' 2import 'express-validator'
3import { body, param } from 'express-validator/check' 3import { body, param } from 'express-validator/check'
4import { isSignupAllowed, logger } from '../../helpers'
5import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
4import { 6import {
5 isIdOrUUIDValid,
6 isSignupAllowed,
7 isUserDisplayNSFWValid, 7 isUserDisplayNSFWValid,
8 isUserPasswordValid, 8 isUserPasswordValid,
9 isUserRoleValid, 9 isUserRoleValid,
10 isUserUsernameValid, 10 isUserUsernameValid,
11 isUserVideoQuotaValid, 11 isUserVideoQuotaValid
12 logger 12} from '../../helpers/custom-validators/users'
13} from '../../helpers'
14import { isVideoExist } from '../../helpers/custom-validators/videos' 13import { isVideoExist } from '../../helpers/custom-validators/videos'
15import { database as db } from '../../initializers/database' 14import { UserModel } from '../../models/account/user'
16import { areValidationErrors } from './utils' 15import { areValidationErrors } from './utils'
17 16
18const usersAddValidator = [ 17const usersAddValidator = [
@@ -153,7 +152,7 @@ export {
153// --------------------------------------------------------------------------- 152// ---------------------------------------------------------------------------
154 153
155async function checkUserIdExist (id: number, res: express.Response) { 154async function checkUserIdExist (id: number, res: express.Response) {
156 const user = await db.User.loadById(id) 155 const user = await UserModel.loadById(id)
157 156
158 if (!user) { 157 if (!user) {
159 res.status(404) 158 res.status(404)
@@ -168,7 +167,7 @@ async function checkUserIdExist (id: number, res: express.Response) {
168} 167}
169 168
170async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { 169async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) {
171 const user = await db.User.loadByUsernameOrEmail(username, email) 170 const user = await UserModel.loadByUsernameOrEmail(username, email)
172 171
173 if (user) { 172 if (user) {
174 res.status(409) 173 res.status(409)
diff --git a/server/middlewares/validators/utils.ts b/server/middlewares/validators/utils.ts
index ca80acf29..61f76b457 100644
--- a/server/middlewares/validators/utils.ts
+++ b/server/middlewares/validators/utils.ts
@@ -1,6 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { validationResult } from 'express-validator/check' 2import { validationResult } from 'express-validator/check'
3
4import { logger } from '../../helpers' 3import { logger } from '../../helpers'
5 4
6function areValidationErrors (req: express.Request, res: express.Response) { 5function areValidationErrors (req: express.Request, res: express.Response) {
diff --git a/server/middlewares/validators/video-blacklist.ts b/server/middlewares/validators/video-blacklist.ts
index f1cc04950..98099fe3f 100644
--- a/server/middlewares/validators/video-blacklist.ts
+++ b/server/middlewares/validators/video-blacklist.ts
@@ -1,9 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param } from 'express-validator/check' 2import { param } from 'express-validator/check'
3import { isIdOrUUIDValid, logger } from '../../helpers' 3import { logger } from '../../helpers'
4import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
4import { isVideoExist } from '../../helpers/custom-validators/videos' 5import { isVideoExist } from '../../helpers/custom-validators/videos'
5import { database as db } from '../../initializers/database' 6import { VideoModel } from '../../models/video/video'
6import { VideoInstance } from '../../models/video/video-interface' 7import { VideoBlacklistModel } from '../../models/video/video-blacklist'
7import { areValidationErrors } from './utils' 8import { areValidationErrors } from './utils'
8 9
9const videosBlacklistRemoveValidator = [ 10const videosBlacklistRemoveValidator = [
@@ -42,7 +43,7 @@ export {
42} 43}
43// --------------------------------------------------------------------------- 44// ---------------------------------------------------------------------------
44 45
45function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) { 46function checkVideoIsBlacklistable (video: VideoModel, res: express.Response) {
46 if (video.isOwned() === true) { 47 if (video.isOwned() === true) {
47 res.status(403) 48 res.status(403)
48 .json({ error: 'Cannot blacklist a local video' }) 49 .json({ error: 'Cannot blacklist a local video' })
@@ -54,8 +55,8 @@ function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response)
54 return true 55 return true
55} 56}
56 57
57async function checkVideoIsBlacklisted (video: VideoInstance, res: express.Response) { 58async function checkVideoIsBlacklisted (video: VideoModel, res: express.Response) {
58 const blacklistedVideo = await db.BlacklistedVideo.loadByVideoId(video.id) 59 const blacklistedVideo = await VideoBlacklistModel.loadByVideoId(video.id)
59 if (!blacklistedVideo) { 60 if (!blacklistedVideo) {
60 res.status(404) 61 res.status(404)
61 .send('Blacklisted video not found') 62 .send('Blacklisted video not found')
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts
index 3d31a7e52..660390080 100644
--- a/server/middlewares/validators/video-channels.ts
+++ b/server/middlewares/validators/video-channels.ts
@@ -1,19 +1,18 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator/check' 2import { body, param } from 'express-validator/check'
3import { UserRight } from '../../../shared' 3import { UserRight } from '../../../shared'
4import { isIdValid } from '../../helpers/custom-validators/misc' 4import { logger } from '../../helpers'
5import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
6import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
5import { 7import {
6 isVideoChannelDescriptionValid, 8 isVideoChannelDescriptionValid,
7 isVideoChannelExist, 9 isVideoChannelExist,
8 isVideoChannelNameValid 10 isVideoChannelNameValid
9} from '../../helpers/custom-validators/video-channels' 11} from '../../helpers/custom-validators/video-channels'
10import { isIdOrUUIDValid } from '../../helpers/index' 12import { UserModel } from '../../models/account/user'
11import { logger } from '../../helpers/logger' 13import { VideoChannelModel } from '../../models/video/video-channel'
12import { database as db } from '../../initializers' 14import { VideoChannelShareModel } from '../../models/video/video-channel-share'
13import { UserInstance } from '../../models'
14import { areValidationErrors } from './utils' 15import { areValidationErrors } from './utils'
15import { isAccountIdExist } from '../../helpers/custom-validators/accounts'
16import { VideoChannelInstance } from '../../models/video/video-channel-interface'
17 16
18const listVideoAccountChannelsValidator = [ 17const listVideoAccountChannelsValidator = [
19 param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), 18 param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'),
@@ -109,7 +108,7 @@ const videoChannelsShareValidator = [
109 if (areValidationErrors(req, res)) return 108 if (areValidationErrors(req, res)) return
110 if (!await isVideoChannelExist(req.params.id, res)) return 109 if (!await isVideoChannelExist(req.params.id, res)) return
111 110
112 const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId, undefined) 111 const share = await VideoChannelShareModel.load(res.locals.video.id, req.params.accountId, undefined)
113 if (!share) { 112 if (!share) {
114 return res.status(404) 113 return res.status(404)
115 .end() 114 .end()
@@ -134,7 +133,7 @@ export {
134 133
135// --------------------------------------------------------------------------- 134// ---------------------------------------------------------------------------
136 135
137function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: VideoChannelInstance, res: express.Response) { 136function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
138 // Retrieve the user who did the request 137 // Retrieve the user who did the request
139 if (videoChannel.isOwned() === false) { 138 if (videoChannel.isOwned() === false) {
140 res.status(403) 139 res.status(403)
@@ -159,7 +158,7 @@ function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: Video
159} 158}
160 159
161async function checkVideoChannelIsNotTheLastOne (res: express.Response) { 160async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
162 const count = await db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id) 161 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
163 162
164 if (count <= 1) { 163 if (count <= 1) {
165 res.status(409) 164 res.status(409)
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index 10625e41d..b52d5f285 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -1,6 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'express-validator'
2import { body, param, query } from 'express-validator/check' 3import { body, param, query } from 'express-validator/check'
3import { UserRight, VideoPrivacy } from '../../../shared' 4import { UserRight, VideoPrivacy } from '../../../shared'
5import { getDurationFromVideoFile, logger } from '../../helpers'
4import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' 6import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
5import { 7import {
6 isVideoAbuseReasonValid, 8 isVideoAbuseReasonValid,
@@ -16,12 +18,11 @@ import {
16 isVideoRatingTypeValid, 18 isVideoRatingTypeValid,
17 isVideoTagsValid 19 isVideoTagsValid
18} from '../../helpers/custom-validators/videos' 20} from '../../helpers/custom-validators/videos'
19import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
20import { logger } from '../../helpers/logger'
21import { CONSTRAINTS_FIELDS } from '../../initializers' 21import { CONSTRAINTS_FIELDS } from '../../initializers'
22import { database as db } from '../../initializers/database' 22import { UserModel } from '../../models/account/user'
23import { UserInstance } from '../../models/account/user-interface' 23import { VideoModel } from '../../models/video/video'
24import { VideoInstance } from '../../models/video/video-interface' 24import { VideoChannelModel } from '../../models/video/video-channel'
25import { VideoShareModel } from '../../models/video/video-share'
25import { authenticate } from '../oauth' 26import { authenticate } from '../oauth'
26import { areValidationErrors } from './utils' 27import { areValidationErrors } from './utils'
27 28
@@ -48,7 +49,7 @@ const videosAddValidator = [
48 const videoFile: Express.Multer.File = req.files['videofile'][0] 49 const videoFile: Express.Multer.File = req.files['videofile'][0]
49 const user = res.locals.oauth.token.User 50 const user = res.locals.oauth.token.User
50 51
51 const videoChannel = await db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id) 52 const videoChannel = await VideoChannelModel.loadByIdAndAccount(req.body.channelId, user.Account.id)
52 if (!videoChannel) { 53 if (!videoChannel) {
53 res.status(400) 54 res.status(400)
54 .json({ error: 'Unknown video video channel for this account.' }) 55 .json({ error: 'Unknown video video channel for this account.' })
@@ -221,7 +222,7 @@ const videosShareValidator = [
221 if (areValidationErrors(req, res)) return 222 if (areValidationErrors(req, res)) return
222 if (!await isVideoExist(req.params.id, res)) return 223 if (!await isVideoExist(req.params.id, res)) return
223 224
224 const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id, undefined) 225 const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined)
225 if (!share) { 226 if (!share) {
226 return res.status(404) 227 return res.status(404)
227 .end() 228 .end()
@@ -249,7 +250,7 @@ export {
249 250
250// --------------------------------------------------------------------------- 251// ---------------------------------------------------------------------------
251 252
252function checkUserCanDeleteVideo (user: UserInstance, video: VideoInstance, res: express.Response) { 253function checkUserCanDeleteVideo (user: UserModel, video: VideoModel, res: express.Response) {
253 // Retrieve the user who did the request 254 // Retrieve the user who did the request
254 if (video.isOwned() === false) { 255 if (video.isOwned() === false) {
255 res.status(403) 256 res.status(403)
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index 34e62c66d..7903c7400 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -1,8 +1,8 @@
1import * as express from 'express' 1import * as express from 'express'
2import { query } from 'express-validator/check' 2import { query } from 'express-validator/check'
3import { logger } from '../../helpers'
3import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' 4import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger'
4import { logger } from '../../helpers/logger' 5import { AccountModel } from '../../models/account/account'
5import { database as db } from '../../initializers'
6import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
7 7
8const webfingerValidator = [ 8const webfingerValidator = [
@@ -17,7 +17,7 @@ const webfingerValidator = [
17 const nameWithHost = req.query.resource.substr(5) 17 const nameWithHost = req.query.resource.substr(5)
18 const [ name ] = nameWithHost.split('@') 18 const [ name ] = nameWithHost.split('@')
19 19
20 const account = await db.Account.loadLocalByName(name) 20 const account = await AccountModel.loadLocalByName(name)
21 if (!account) { 21 if (!account) {
22 return res.status(404) 22 return res.status(404)
23 .send({ error: 'Account not found' }) 23 .send({ error: 'Account not found' })
diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts
deleted file mode 100644
index 7975a46f3..000000000
--- a/server/models/account/account-follow-interface.ts
+++ /dev/null
@@ -1,60 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { AccountFollow, FollowState } from '../../../shared/models/accounts/follow.model'
4import { ResultList } from '../../../shared/models/result-list.model'
5import { AccountInstance } from './account-interface'
6
7export namespace AccountFollowMethods {
8 export type LoadByAccountAndTarget = (
9 accountId: number,
10 targetAccountId: number,
11 t?: Sequelize.Transaction
12 ) => Bluebird<AccountFollowInstance>
13
14 export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
15 export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
16
17 export type ListAcceptedFollowerUrlsForApi = (
18 accountId: number[],
19 t: Sequelize.Transaction,
20 start?: number,
21 count?: number
22 ) => Promise< ResultList<string> >
23 export type ListAcceptedFollowingUrlsForApi = (
24 accountId: number[],
25 t: Sequelize.Transaction,
26 start?: number,
27 count?: number
28 ) => Promise< ResultList<string> >
29 export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[], t: Sequelize.Transaction) => Promise< ResultList<string> >
30 export type ToFormattedJSON = (this: AccountFollowInstance) => AccountFollow
31}
32
33export interface AccountFollowClass {
34 loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
35 listFollowersForApi: AccountFollowMethods.ListFollowersForApi
36 listFollowingForApi: AccountFollowMethods.ListFollowingForApi
37
38 listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
39 listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
40 listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
41}
42
43export interface AccountFollowAttributes {
44 accountId: number
45 targetAccountId: number
46 state: FollowState
47}
48
49export interface AccountFollowInstance extends AccountFollowClass, AccountFollowAttributes, Sequelize.Instance<AccountFollowAttributes> {
50 id: number
51 createdAt: Date
52 updatedAt: Date
53
54 AccountFollower?: AccountInstance
55 AccountFollowing?: AccountInstance
56
57 toFormattedJSON: AccountFollowMethods.ToFormattedJSON
58}
59
60export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {}
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts
index 724f37baa..975e7ee7d 100644
--- a/server/models/account/account-follow.ts
+++ b/server/models/account/account-follow.ts
@@ -1,64 +1,45 @@
1import * as Bluebird from 'bluebird'
1import { values } from 'lodash' 2import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 3import * as Sequelize from 'sequelize'
3 4import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { addMethodsToModel, getSort } from '../utils' 5import { FollowState } from '../../../shared/models/accounts'
5import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface'
6import { FOLLOW_STATES } from '../../initializers/constants' 6import { FOLLOW_STATES } from '../../initializers/constants'
7import { ServerModel } from '../server/server'
8import { getSort } from '../utils'
9import { AccountModel } from './account'
7 10
8let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> 11@Table({
9let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget 12 tableName: 'accountFollow',
10let listFollowingForApi: AccountFollowMethods.ListFollowingForApi 13 indexes: [
11let listFollowersForApi: AccountFollowMethods.ListFollowersForApi
12let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi
13let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi
14let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls
15let toFormattedJSON: AccountFollowMethods.ToFormattedJSON
16
17export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
18 AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
19 { 14 {
20 state: { 15 fields: [ 'accountId' ]
21 type: DataTypes.ENUM(values(FOLLOW_STATES)),
22 allowNull: false
23 }
24 }, 16 },
25 { 17 {
26 indexes: [ 18 fields: [ 'targetAccountId' ]
27 { 19 },
28 fields: [ 'accountId' ] 20 {
29 }, 21 fields: [ 'accountId', 'targetAccountId' ],
30 { 22 unique: true
31 fields: [ 'targetAccountId' ]
32 },
33 {
34 fields: [ 'accountId', 'targetAccountId' ],
35 unique: true
36 }
37 ]
38 } 23 }
39 )
40
41 const classMethods = [
42 associate,
43 loadByAccountAndTarget,
44 listFollowingForApi,
45 listFollowersForApi,
46 listAcceptedFollowerUrlsForApi,
47 listAcceptedFollowingUrlsForApi,
48 listAcceptedFollowerSharedInboxUrls
49 ] 24 ]
50 const instanceMethods = [ 25})
51 toFormattedJSON 26export class AccountFollowModel extends Model<AccountFollowModel> {
52 ]
53 addMethodsToModel(AccountFollow, classMethods, instanceMethods)
54 27
55 return AccountFollow 28 @AllowNull(false)
56} 29 @Column(DataType.ENUM(values(FOLLOW_STATES)))
30 state: FollowState
57 31
58// ------------------------------ STATICS ------------------------------ 32 @CreatedAt
33 createdAt: Date
59 34
60function associate (models) { 35 @UpdatedAt
61 AccountFollow.belongsTo(models.Account, { 36 updatedAt: Date
37
38 @ForeignKey(() => AccountModel)
39 @Column
40 accountId: number
41
42 @BelongsTo(() => AccountModel, {
62 foreignKey: { 43 foreignKey: {
63 name: 'accountId', 44 name: 'accountId',
64 allowNull: false 45 allowNull: false
@@ -66,8 +47,13 @@ function associate (models) {
66 as: 'AccountFollower', 47 as: 'AccountFollower',
67 onDelete: 'CASCADE' 48 onDelete: 'CASCADE'
68 }) 49 })
50 AccountFollower: AccountModel
69 51
70 AccountFollow.belongsTo(models.Account, { 52 @ForeignKey(() => AccountModel)
53 @Column
54 targetAccountId: number
55
56 @BelongsTo(() => AccountModel, {
71 foreignKey: { 57 foreignKey: {
72 name: 'targetAccountId', 58 name: 'targetAccountId',
73 allowNull: false 59 allowNull: false
@@ -75,170 +61,168 @@ function associate (models) {
75 as: 'AccountFollowing', 61 as: 'AccountFollowing',
76 onDelete: 'CASCADE' 62 onDelete: 'CASCADE'
77 }) 63 })
78} 64 AccountFollowing: AccountModel
79 65
80toFormattedJSON = function (this: AccountFollowInstance) { 66 static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
81 const follower = this.AccountFollower.toFormattedJSON() 67 const query = {
82 const following = this.AccountFollowing.toFormattedJSON() 68 where: {
83 69 accountId,
84 const json = { 70 targetAccountId
85 id: this.id,
86 follower,
87 following,
88 state: this.state,
89 createdAt: this.createdAt,
90 updatedAt: this.updatedAt
91 }
92
93 return json
94}
95
96loadByAccountAndTarget = function (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) {
97 const query = {
98 where: {
99 accountId,
100 targetAccountId
101 },
102 include: [
103 {
104 model: AccountFollow[ 'sequelize' ].models.Account,
105 required: true,
106 as: 'AccountFollower'
107 }, 71 },
108 { 72 include: [
109 model: AccountFollow['sequelize'].models.Account, 73 {
110 required: true, 74 model: AccountModel,
111 as: 'AccountFollowing' 75 required: true,
112 } 76 as: 'AccountFollower'
113 ], 77 },
114 transaction: t 78 {
79 model: AccountModel,
80 required: true,
81 as: 'AccountFollowing'
82 }
83 ],
84 transaction: t
85 }
86
87 return AccountFollowModel.findOne(query)
115 } 88 }
116 89
117 return AccountFollow.findOne(query) 90 static listFollowingForApi (id: number, start: number, count: number, sort: string) {
118} 91 const query = {
92 distinct: true,
93 offset: start,
94 limit: count,
95 order: [ getSort(sort) ],
96 include: [
97 {
98 model: AccountModel,
99 required: true,
100 as: 'AccountFollower',
101 where: {
102 id
103 }
104 },
105 {
106 model: AccountModel,
107 as: 'AccountFollowing',
108 required: true,
109 include: [ ServerModel ]
110 }
111 ]
112 }
119 113
120listFollowingForApi = function (id: number, start: number, count: number, sort: string) { 114 return AccountFollowModel.findAndCountAll(query)
121 const query = { 115 .then(({ rows, count }) => {
122 distinct: true, 116 return {
123 offset: start, 117 data: rows,
124 limit: count, 118 total: count
125 order: [ getSort(sort) ],
126 include: [
127 {
128 model: AccountFollow[ 'sequelize' ].models.Account,
129 required: true,
130 as: 'AccountFollower',
131 where: {
132 id
133 } 119 }
134 }, 120 })
135 {
136 model: AccountFollow['sequelize'].models.Account,
137 as: 'AccountFollowing',
138 required: true,
139 include: [ AccountFollow['sequelize'].models.Server ]
140 }
141 ]
142 } 121 }
143 122
144 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { 123 static listFollowersForApi (id: number, start: number, count: number, sort: string) {
145 return { 124 const query = {
146 data: rows, 125 distinct: true,
147 total: count 126 offset: start,
127 limit: count,
128 order: [ getSort(sort) ],
129 include: [
130 {
131 model: AccountModel,
132 required: true,
133 as: 'AccountFollower',
134 include: [ ServerModel ]
135 },
136 {
137 model: AccountModel,
138 as: 'AccountFollowing',
139 required: true,
140 where: {
141 id
142 }
143 }
144 ]
148 } 145 }
149 })
150}
151 146
152listFollowersForApi = function (id: number, start: number, count: number, sort: string) { 147 return AccountFollowModel.findAndCountAll(query)
153 const query = { 148 .then(({ rows, count }) => {
154 distinct: true, 149 return {
155 offset: start, 150 data: rows,
156 limit: count, 151 total: count
157 order: [ getSort(sort) ],
158 include: [
159 {
160 model: AccountFollow[ 'sequelize' ].models.Account,
161 required: true,
162 as: 'AccountFollower',
163 include: [ AccountFollow['sequelize'].models.Server ]
164 },
165 {
166 model: AccountFollow['sequelize'].models.Account,
167 as: 'AccountFollowing',
168 required: true,
169 where: {
170 id
171 } 152 }
172 } 153 })
173 ]
174 } 154 }
175 155
176 return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { 156 static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
177 return { 157 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count)
178 data: rows, 158 }
179 total: count
180 }
181 })
182}
183 159
184listAcceptedFollowerUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { 160 static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) {
185 return createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) 161 return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl')
186} 162 }
187 163
188listAcceptedFollowerSharedInboxUrls = function (accountIds: number[], t: Sequelize.Transaction) { 164 static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) {
189 return createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') 165 return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count)
190} 166 }
191 167
192listAcceptedFollowingUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { 168 private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following',
193 return createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) 169 accountIds: number[],
194} 170 t: Sequelize.Transaction,
171 start?: number,
172 count?: number,
173 columnUrl = 'url') {
174 let firstJoin: string
175 let secondJoin: string
176
177 if (type === 'followers') {
178 firstJoin = 'targetAccountId'
179 secondJoin = 'accountId'
180 } else {
181 firstJoin = 'accountId'
182 secondJoin = 'targetAccountId'
183 }
195 184
196// ------------------------------ UTILS ------------------------------ 185 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
197 186 const tasks: Bluebird<any>[] = []
198async function createListAcceptedFollowForApiQuery (
199 type: 'followers' | 'following',
200 accountIds: number[],
201 t: Sequelize.Transaction,
202 start?: number,
203 count?: number,
204 columnUrl = 'url'
205) {
206 let firstJoin: string
207 let secondJoin: string
208
209 if (type === 'followers') {
210 firstJoin = 'targetAccountId'
211 secondJoin = 'accountId'
212 } else {
213 firstJoin = 'accountId'
214 secondJoin = 'targetAccountId'
215 }
216 187
217 const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] 188 for (const selection of selections) {
218 const tasks: Promise<any>[] = [] 189 let query = 'SELECT ' + selection + ' FROM "account" ' +
190 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' +
191 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' +
192 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' '
219 193
220 for (const selection of selections) { 194 if (count !== undefined) query += 'LIMIT ' + count
221 let query = 'SELECT ' + selection + ' FROM "Accounts" ' + 195 if (start !== undefined) query += ' OFFSET ' + start
222 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' +
223 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' +
224 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' '
225 196
226 if (count !== undefined) query += 'LIMIT ' + count 197 const options = {
227 if (start !== undefined) query += ' OFFSET ' + start 198 bind: { accountIds },
199 type: Sequelize.QueryTypes.SELECT,
200 transaction: t
201 }
202 tasks.push(AccountFollowModel.sequelize.query(query, options))
203 }
228 204
229 const options = { 205 const [ followers, [ { total } ] ] = await
230 bind: { accountIds }, 206 Promise.all(tasks)
231 type: Sequelize.QueryTypes.SELECT, 207 const urls: string[] = followers.map(f => f.url)
232 transaction: t 208
209 return {
210 data: urls,
211 total: parseInt(total, 10)
233 } 212 }
234 tasks.push(AccountFollow['sequelize'].query(query, options))
235 } 213 }
236 214
237 const [ followers, [ { total } ]] = await Promise.all(tasks) 215 toFormattedJSON () {
238 const urls: string[] = followers.map(f => f.url) 216 const follower = this.AccountFollower.toFormattedJSON()
217 const following = this.AccountFollowing.toFormattedJSON()
239 218
240 return { 219 return {
241 data: urls, 220 id: this.id,
242 total: parseInt(total, 10) 221 follower,
222 following,
223 state: this.state,
224 createdAt: this.createdAt,
225 updatedAt: this.updatedAt
226 }
243 } 227 }
244} 228}
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts
deleted file mode 100644
index 46fe068e3..000000000
--- a/server/models/account/account-interface.ts
+++ /dev/null
@@ -1,76 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { Account as FormattedAccount, ActivityPubActor } from '../../../shared'
4import { AvatarInstance } from '../avatar'
5import { ServerInstance } from '../server/server-interface'
6import { VideoChannelInstance } from '../video/video-channel-interface'
7
8export namespace AccountMethods {
9 export type LoadApplication = () => Bluebird<AccountInstance>
10
11 export type Load = (id: number) => Bluebird<AccountInstance>
12 export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance>
13 export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance>
14 export type LoadLocalByName = (name: string) => Bluebird<AccountInstance>
15 export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance>
16 export type ListByFollowersUrls = (followerUrls: string[], transaction: Sequelize.Transaction) => Bluebird<AccountInstance[]>
17
18 export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor
19 export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount
20 export type IsOwned = (this: AccountInstance) => boolean
21 export type GetFollowerSharedInboxUrls = (this: AccountInstance, t: Sequelize.Transaction) => Bluebird<string[]>
22 export type GetFollowingUrl = (this: AccountInstance) => string
23 export type GetFollowersUrl = (this: AccountInstance) => string
24 export type GetPublicKeyUrl = (this: AccountInstance) => string
25}
26
27export interface AccountClass {
28 loadApplication: AccountMethods.LoadApplication
29 load: AccountMethods.Load
30 loadByUUID: AccountMethods.LoadByUUID
31 loadByUrl: AccountMethods.LoadByUrl
32 loadLocalByName: AccountMethods.LoadLocalByName
33 loadByNameAndHost: AccountMethods.LoadByNameAndHost
34 listByFollowersUrls: AccountMethods.ListByFollowersUrls
35}
36
37export interface AccountAttributes {
38 name: string
39 url?: string
40 publicKey: string
41 privateKey: string
42 followersCount: number
43 followingCount: number
44 inboxUrl: string
45 outboxUrl: string
46 sharedInboxUrl: string
47 followersUrl: string
48 followingUrl: string
49
50 uuid?: string
51
52 serverId?: number
53 userId?: number
54 applicationId?: number
55 avatarId?: number
56}
57
58export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance<AccountAttributes> {
59 isOwned: AccountMethods.IsOwned
60 toActivityPubObject: AccountMethods.ToActivityPubObject
61 toFormattedJSON: AccountMethods.ToFormattedJSON
62 getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
63 getFollowingUrl: AccountMethods.GetFollowingUrl
64 getFollowersUrl: AccountMethods.GetFollowersUrl
65 getPublicKeyUrl: AccountMethods.GetPublicKeyUrl
66
67 id: number
68 createdAt: Date
69 updatedAt: Date
70
71 Server: ServerInstance
72 VideoChannels: VideoChannelInstance[]
73 Avatar: AvatarInstance
74}
75
76export interface AccountModel extends AccountClass, Sequelize.Model<AccountInstance, AccountAttributes> {}
diff --git a/server/models/account/account-video-rate-interface.ts b/server/models/account/account-video-rate-interface.ts
deleted file mode 100644
index 1f395bc45..000000000
--- a/server/models/account/account-video-rate-interface.ts
+++ /dev/null
@@ -1,31 +0,0 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird'
3
4import { VideoRateType } from '../../../shared/models/videos/video-rate.type'
5import { AccountInstance } from './account-interface'
6
7export namespace AccountVideoRateMethods {
8 export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<AccountVideoRateInstance>
9}
10
11export interface AccountVideoRateClass {
12 load: AccountVideoRateMethods.Load
13}
14
15export interface AccountVideoRateAttributes {
16 type: VideoRateType
17 accountId: number
18 videoId: number
19
20 Account?: AccountInstance
21}
22
23export interface AccountVideoRateInstance
24 extends AccountVideoRateClass, AccountVideoRateAttributes, Sequelize.Instance<AccountVideoRateAttributes> {
25 id: number
26 createdAt: Date
27 updatedAt: Date
28}
29
30export interface AccountVideoRateModel
31 extends AccountVideoRateClass, Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes> {}
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index d92834bbb..e969e4a43 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -1,78 +1,69 @@
1/*
2 Account rates per video.
3*/
4import { values } from 'lodash' 1import { values } from 'lodash'
5import * as Sequelize from 'sequelize' 2import { Transaction } from 'sequelize'
6 3import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
5import { VideoRateType } from '../../../shared/models/videos'
7import { VIDEO_RATE_TYPES } from '../../initializers' 6import { VIDEO_RATE_TYPES } from '../../initializers'
7import { VideoModel } from '../video/video'
8import { AccountModel } from './account'
8 9
9import { addMethodsToModel } from '../utils' 10/*
10import { 11 Account rates per video.
11 AccountVideoRateInstance, 12*/
12 AccountVideoRateAttributes, 13@Table({
13 14 tableName: 'accountVideoRate',
14 AccountVideoRateMethods 15 indexes: [
15} from './account-video-rate-interface'
16
17let AccountVideoRate: Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes>
18let load: AccountVideoRateMethods.Load
19
20export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
21 AccountVideoRate = sequelize.define<AccountVideoRateInstance, AccountVideoRateAttributes>('AccountVideoRate',
22 {
23 type: {
24 type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)),
25 allowNull: false
26 }
27 },
28 { 16 {
29 indexes: [ 17 fields: [ 'videoId', 'accountId' ],
30 { 18 unique: true
31 fields: [ 'videoId', 'accountId' ],
32 unique: true
33 }
34 ]
35 } 19 }
36 ) 20 ]
21})
22export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
37 23
38 const classMethods = [ 24 @AllowNull(false)
39 associate, 25 @Column(DataType.ENUM(values(VIDEO_RATE_TYPES)))
26 type: VideoRateType
40 27
41 load 28 @CreatedAt
42 ] 29 createdAt: Date
43 addMethodsToModel(AccountVideoRate, classMethods)
44 30
45 return AccountVideoRate 31 @UpdatedAt
46} 32 updatedAt: Date
47 33
48// ------------------------------ STATICS ------------------------------ 34 @ForeignKey(() => VideoModel)
35 @Column
36 videoId: number
49 37
50function associate (models) { 38 @BelongsTo(() => VideoModel, {
51 AccountVideoRate.belongsTo(models.Video, {
52 foreignKey: { 39 foreignKey: {
53 name: 'videoId',
54 allowNull: false 40 allowNull: false
55 }, 41 },
56 onDelete: 'CASCADE' 42 onDelete: 'CASCADE'
57 }) 43 })
44 Video: VideoModel
58 45
59 AccountVideoRate.belongsTo(models.Account, { 46 @ForeignKey(() => AccountModel)
47 @Column
48 accountId: number
49
50 @BelongsTo(() => AccountModel, {
60 foreignKey: { 51 foreignKey: {
61 name: 'accountId',
62 allowNull: false 52 allowNull: false
63 }, 53 },
64 onDelete: 'CASCADE' 54 onDelete: 'CASCADE'
65 }) 55 })
66} 56 Account: AccountModel
67 57
68load = function (accountId: number, videoId: number, transaction: Sequelize.Transaction) { 58 static load (accountId: number, videoId: number, transaction: Transaction) {
69 const options: Sequelize.FindOptions<AccountVideoRateAttributes> = { 59 const options: IFindOptions<AccountVideoRateModel> = {
70 where: { 60 where: {
71 accountId, 61 accountId,
72 videoId 62 videoId
63 }
73 } 64 }
74 } 65 if (transaction) options.transaction = transaction
75 if (transaction) options.transaction = transaction
76 66
77 return AccountVideoRate.findOne(options) 67 return AccountVideoRateModel.findOne(options)
68 }
78} 69}
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 8b0819f39..d6758fa10 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -1,253 +1,200 @@
1import { join } from 'path' 1import { join } from 'path'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3import {
4 AfterDestroy,
5 AllowNull,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 DataType,
10 Default,
11 ForeignKey,
12 HasMany,
13 Is,
14 IsUUID,
15 Model,
16 Table,
17 UpdatedAt
18} from 'sequelize-typescript'
3import { Avatar } from '../../../shared/models/avatars/avatar.model' 19import { Avatar } from '../../../shared/models/avatars/avatar.model'
20import { activityPubContextify } from '../../helpers'
4import { 21import {
5 activityPubContextify,
6 isAccountFollowersCountValid, 22 isAccountFollowersCountValid,
7 isAccountFollowingCountValid, 23 isAccountFollowingCountValid,
8 isAccountPrivateKeyValid, 24 isAccountPrivateKeyValid,
9 isAccountPublicKeyValid, 25 isAccountPublicKeyValid,
10 isUserUsernameValid 26 isActivityPubUrlValid
11} from '../../helpers' 27} from '../../helpers/custom-validators/activitypub'
12import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 28import { isUserUsernameValid } from '../../helpers/custom-validators/users'
13import { AVATARS_DIR } from '../../initializers' 29import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
14import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' 30import { sendDeleteAccount } from '../../lib/activitypub/send'
15import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' 31import { ApplicationModel } from '../application/application'
16import { addMethodsToModel } from '../utils' 32import { AvatarModel } from '../avatar/avatar'
17import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' 33import { ServerModel } from '../server/server'
18 34import { throwIfNotValid } from '../utils'
19let Account: Sequelize.Model<AccountInstance, AccountAttributes> 35import { VideoChannelModel } from '../video/video-channel'
20let load: AccountMethods.Load 36import { AccountFollowModel } from './account-follow'
21let loadApplication: AccountMethods.LoadApplication 37import { UserModel } from './user'
22let loadByUUID: AccountMethods.LoadByUUID 38
23let loadByUrl: AccountMethods.LoadByUrl 39@Table({
24let loadLocalByName: AccountMethods.LoadLocalByName 40 tableName: 'account',
25let loadByNameAndHost: AccountMethods.LoadByNameAndHost 41 indexes: [
26let listByFollowersUrls: AccountMethods.ListByFollowersUrls
27let isOwned: AccountMethods.IsOwned
28let toActivityPubObject: AccountMethods.ToActivityPubObject
29let toFormattedJSON: AccountMethods.ToFormattedJSON
30let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
31let getFollowingUrl: AccountMethods.GetFollowingUrl
32let getFollowersUrl: AccountMethods.GetFollowersUrl
33let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl
34
35export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
36 Account = sequelize.define<AccountInstance, AccountAttributes>('Account',
37 { 42 {
38 uuid: { 43 fields: [ 'name' ]
39 type: DataTypes.UUID,
40 defaultValue: DataTypes.UUIDV4,
41 allowNull: false,
42 validate: {
43 isUUID: 4
44 }
45 },
46 name: {
47 type: DataTypes.STRING,
48 allowNull: false,
49 validate: {
50 nameValid: value => {
51 const res = isUserUsernameValid(value)
52 if (res === false) throw new Error('Name is not valid.')
53 }
54 }
55 },
56 url: {
57 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
58 allowNull: false,
59 validate: {
60 urlValid: value => {
61 const res = isActivityPubUrlValid(value)
62 if (res === false) throw new Error('URL is not valid.')
63 }
64 }
65 },
66 publicKey: {
67 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
68 allowNull: true,
69 validate: {
70 publicKeyValid: value => {
71 const res = isAccountPublicKeyValid(value)
72 if (res === false) throw new Error('Public key is not valid.')
73 }
74 }
75 },
76 privateKey: {
77 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
78 allowNull: true,
79 validate: {
80 privateKeyValid: value => {
81 const res = isAccountPrivateKeyValid(value)
82 if (res === false) throw new Error('Private key is not valid.')
83 }
84 }
85 },
86 followersCount: {
87 type: DataTypes.INTEGER,
88 allowNull: false,
89 validate: {
90 followersCountValid: value => {
91 const res = isAccountFollowersCountValid(value)
92 if (res === false) throw new Error('Followers count is not valid.')
93 }
94 }
95 },
96 followingCount: {
97 type: DataTypes.INTEGER,
98 allowNull: false,
99 validate: {
100 followingCountValid: value => {
101 const res = isAccountFollowingCountValid(value)
102 if (res === false) throw new Error('Following count is not valid.')
103 }
104 }
105 },
106 inboxUrl: {
107 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
108 allowNull: false,
109 validate: {
110 inboxUrlValid: value => {
111 const res = isActivityPubUrlValid(value)
112 if (res === false) throw new Error('Inbox URL is not valid.')
113 }
114 }
115 },
116 outboxUrl: {
117 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
118 allowNull: false,
119 validate: {
120 outboxUrlValid: value => {
121 const res = isActivityPubUrlValid(value)
122 if (res === false) throw new Error('Outbox URL is not valid.')
123 }
124 }
125 },
126 sharedInboxUrl: {
127 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
128 allowNull: false,
129 validate: {
130 sharedInboxUrlValid: value => {
131 const res = isActivityPubUrlValid(value)
132 if (res === false) throw new Error('Shared inbox URL is not valid.')
133 }
134 }
135 },
136 followersUrl: {
137 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
138 allowNull: false,
139 validate: {
140 followersUrlValid: value => {
141 const res = isActivityPubUrlValid(value)
142 if (res === false) throw new Error('Followers URL is not valid.')
143 }
144 }
145 },
146 followingUrl: {
147 type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
148 allowNull: false,
149 validate: {
150 followingUrlValid: value => {
151 const res = isActivityPubUrlValid(value)
152 if (res === false) throw new Error('Following URL is not valid.')
153 }
154 }
155 }
156 }, 44 },
157 { 45 {
158 indexes: [ 46 fields: [ 'serverId' ]
159 { 47 },
160 fields: [ 'name' ] 48 {
161 }, 49 fields: [ 'userId' ],
162 { 50 unique: true
163 fields: [ 'serverId' ] 51 },
164 }, 52 {
165 { 53 fields: [ 'applicationId' ],
166 fields: [ 'userId' ], 54 unique: true
167 unique: true 55 },
168 }, 56 {
169 { 57 fields: [ 'name', 'serverId', 'applicationId' ],
170 fields: [ 'applicationId' ], 58 unique: true
171 unique: true
172 },
173 {
174 fields: [ 'name', 'serverId', 'applicationId' ],
175 unique: true
176 }
177 ],
178 hooks: { afterDestroy }
179 } 59 }
180 )
181
182 const classMethods = [
183 associate,
184 loadApplication,
185 load,
186 loadByUUID,
187 loadByUrl,
188 loadLocalByName,
189 loadByNameAndHost,
190 listByFollowersUrls
191 ]
192 const instanceMethods = [
193 isOwned,
194 toActivityPubObject,
195 toFormattedJSON,
196 getFollowerSharedInboxUrls,
197 getFollowingUrl,
198 getFollowersUrl,
199 getPublicKeyUrl
200 ] 60 ]
201 addMethodsToModel(Account, classMethods, instanceMethods) 61})
202 62export class AccountModel extends Model<Account> {
203 return Account 63
204} 64 @AllowNull(false)
65 @Default(DataType.UUIDV4)
66 @IsUUID(4)
67 @Column(DataType.UUID)
68 uuid: string
69
70 @AllowNull(false)
71 @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
72 @Column
73 name: string
74
75 @AllowNull(false)
76 @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
77 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
78 url: string
79
80 @AllowNull(true)
81 @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key'))
82 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max))
83 publicKey: string
84
85 @AllowNull(true)
86 @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key'))
87 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max))
88 privateKey: string
89
90 @AllowNull(false)
91 @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count'))
92 @Column
93 followersCount: number
94
95 @AllowNull(false)
96 @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count'))
97 @Column
98 followingCount: number
99
100 @AllowNull(false)
101 @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
102 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
103 inboxUrl: string
104
105 @AllowNull(false)
106 @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
107 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
108 outboxUrl: string
109
110 @AllowNull(false)
111 @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
112 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
113 sharedInboxUrl: string
114
115 @AllowNull(false)
116 @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
117 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
118 followersUrl: string
119
120 @AllowNull(false)
121 @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
122 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
123 followingUrl: string
124
125 @CreatedAt
126 createdAt: Date
127
128 @UpdatedAt
129 updatedAt: Date
130
131 @ForeignKey(() => AvatarModel)
132 @Column
133 avatarId: number
134
135 @BelongsTo(() => AvatarModel, {
136 foreignKey: {
137 allowNull: true
138 },
139 onDelete: 'cascade'
140 })
141 Avatar: AvatarModel
205 142
206// --------------------------------------------------------------------------- 143 @ForeignKey(() => ServerModel)
144 @Column
145 serverId: number
207 146
208function associate (models) { 147 @BelongsTo(() => ServerModel, {
209 Account.belongsTo(models.Server, {
210 foreignKey: { 148 foreignKey: {
211 name: 'serverId',
212 allowNull: true 149 allowNull: true
213 }, 150 },
214 onDelete: 'cascade' 151 onDelete: 'cascade'
215 }) 152 })
153 Server: ServerModel
216 154
217 Account.belongsTo(models.User, { 155 @ForeignKey(() => UserModel)
156 @Column
157 userId: number
158
159 @BelongsTo(() => UserModel, {
218 foreignKey: { 160 foreignKey: {
219 name: 'userId',
220 allowNull: true 161 allowNull: true
221 }, 162 },
222 onDelete: 'cascade' 163 onDelete: 'cascade'
223 }) 164 })
165 User: UserModel
166
167 @ForeignKey(() => ApplicationModel)
168 @Column
169 applicationId: number
224 170
225 Account.belongsTo(models.Application, { 171 @BelongsTo(() => ApplicationModel, {
226 foreignKey: { 172 foreignKey: {
227 name: 'applicationId',
228 allowNull: true 173 allowNull: true
229 }, 174 },
230 onDelete: 'cascade' 175 onDelete: 'cascade'
231 }) 176 })
177 Application: ApplicationModel
232 178
233 Account.hasMany(models.VideoChannel, { 179 @HasMany(() => VideoChannelModel, {
234 foreignKey: { 180 foreignKey: {
235 name: 'accountId',
236 allowNull: false 181 allowNull: false
237 }, 182 },
238 onDelete: 'cascade', 183 onDelete: 'cascade',
239 hooks: true 184 hooks: true
240 }) 185 })
186 VideoChannels: VideoChannelModel[]
241 187
242 Account.hasMany(models.AccountFollow, { 188 @HasMany(() => AccountFollowModel, {
243 foreignKey: { 189 foreignKey: {
244 name: 'accountId', 190 name: 'accountId',
245 allowNull: false 191 allowNull: false
246 }, 192 },
247 onDelete: 'cascade' 193 onDelete: 'cascade'
248 }) 194 })
195 AccountFollowing: AccountFollowModel[]
249 196
250 Account.hasMany(models.AccountFollow, { 197 @HasMany(() => AccountFollowModel, {
251 foreignKey: { 198 foreignKey: {
252 name: 'targetAccountId', 199 name: 'targetAccountId',
253 allowNull: false 200 allowNull: false
@@ -255,209 +202,199 @@ function associate (models) {
255 as: 'followers', 202 as: 'followers',
256 onDelete: 'cascade' 203 onDelete: 'cascade'
257 }) 204 })
205 AccountFollowers: AccountFollowModel[]
258 206
259 Account.hasOne(models.Avatar, { 207 @AfterDestroy
260 foreignKey: { 208 static sendDeleteIfOwned (instance: AccountModel) {
261 name: 'avatarId', 209 if (instance.isOwned()) {
262 allowNull: true 210 return sendDeleteAccount(instance, undefined)
263 }, 211 }
264 onDelete: 'cascade'
265 })
266}
267 212
268function afterDestroy (account: AccountInstance) { 213 return undefined
269 if (account.isOwned()) {
270 return sendDeleteAccount(account, undefined)
271 } 214 }
272 215
273 return undefined 216 static loadApplication () {
274} 217 return AccountModel.findOne({
218 include: [
219 {
220 model: ApplicationModel,
221 required: true
222 }
223 ]
224 })
225 }
275 226
276toFormattedJSON = function (this: AccountInstance) { 227 static load (id: number) {
277 let host = CONFIG.WEBSERVER.HOST 228 return AccountModel.findById(id)
278 let score: number 229 }
279 let avatar: Avatar = null
280 230
281 if (this.Avatar) { 231 static loadByUUID (uuid: string) {
282 avatar = { 232 const query = {
283 path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), 233 where: {
284 createdAt: this.Avatar.createdAt, 234 uuid
285 updatedAt: this.Avatar.updatedAt 235 }
286 } 236 }
287 }
288 237
289 if (this.Server) { 238 return AccountModel.findOne(query)
290 host = this.Server.host
291 score = this.Server.score as number
292 } 239 }
293 240
294 const json = { 241 static loadLocalByName (name: string) {
295 id: this.id, 242 const query = {
296 uuid: this.uuid, 243 where: {
297 host, 244 name,
298 score, 245 [ Sequelize.Op.or ]: [
299 name: this.name, 246 {
300 followingCount: this.followingCount, 247 userId: {
301 followersCount: this.followersCount, 248 [ Sequelize.Op.ne ]: null
302 createdAt: this.createdAt, 249 }
303 updatedAt: this.updatedAt, 250 },
304 avatar 251 {
305 } 252 applicationId: {
253 [ Sequelize.Op.ne ]: null
254 }
255 }
256 ]
257 }
258 }
306 259
307 return json 260 return AccountModel.findOne(query)
308} 261 }
309 262
310toActivityPubObject = function (this: AccountInstance) { 263 static loadByNameAndHost (name: string, host: string) {
311 const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' 264 const query = {
312 265 where: {
313 const json = { 266 name
314 type, 267 },
315 id: this.url, 268 include: [
316 following: this.getFollowingUrl(), 269 {
317 followers: this.getFollowersUrl(), 270 model: ServerModel,
318 inbox: this.inboxUrl, 271 required: true,
319 outbox: this.outboxUrl, 272 where: {
320 preferredUsername: this.name, 273 host
321 url: this.url, 274 }
322 name: this.name, 275 }
323 endpoints: { 276 ]
324 sharedInbox: this.sharedInboxUrl
325 },
326 uuid: this.uuid,
327 publicKey: {
328 id: this.getPublicKeyUrl(),
329 owner: this.url,
330 publicKeyPem: this.publicKey
331 } 277 }
278
279 return AccountModel.findOne(query)
332 } 280 }
333 281
334 return activityPubContextify(json) 282 static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
335} 283 const query = {
284 where: {
285 url
286 },
287 transaction
288 }
336 289
337isOwned = function (this: AccountInstance) { 290 return AccountModel.findOne(query)
338 return this.serverId === null 291 }
339}
340 292
341getFollowerSharedInboxUrls = function (this: AccountInstance, t: Sequelize.Transaction) { 293 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
342 const query: Sequelize.FindOptions<AccountAttributes> = { 294 const query = {
343 attributes: [ 'sharedInboxUrl' ], 295 where: {
344 include: [ 296 followersUrl: {
345 { 297 [ Sequelize.Op.in ]: followersUrls
346 model: Account['sequelize'].models.AccountFollow,
347 required: true,
348 as: 'followers',
349 where: {
350 targetAccountId: this.id
351 } 298 }
352 } 299 },
353 ], 300 transaction
354 transaction: t 301 }
355 }
356 302
357 return Account.findAll(query) 303 return AccountModel.findAll(query)
358 .then(accounts => accounts.map(a => a.sharedInboxUrl)) 304 }
359}
360 305
361getFollowingUrl = function (this: AccountInstance) { 306 toFormattedJSON () {
362 return this.url + '/following' 307 let host = CONFIG.WEBSERVER.HOST
363} 308 let score: number
309 let avatar: Avatar = null
364 310
365getFollowersUrl = function (this: AccountInstance) { 311 if (this.Avatar) {
366 return this.url + '/followers' 312 avatar = {
367} 313 path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
314 createdAt: this.Avatar.createdAt,
315 updatedAt: this.Avatar.updatedAt
316 }
317 }
368 318
369getPublicKeyUrl = function (this: AccountInstance) { 319 if (this.Server) {
370 return this.url + '#main-key' 320 host = this.Server.host
371} 321 score = this.Server.score
322 }
372 323
373// ------------------------------ STATICS ------------------------------ 324 return {
325 id: this.id,
326 uuid: this.uuid,
327 host,
328 score,
329 name: this.name,
330 followingCount: this.followingCount,
331 followersCount: this.followersCount,
332 createdAt: this.createdAt,
333 updatedAt: this.updatedAt,
334 avatar
335 }
336 }
374 337
375loadApplication = function () { 338 toActivityPubObject () {
376 return Account.findOne({ 339 const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
377 include: [ 340
378 { 341 const json = {
379 model: Account['sequelize'].models.Application, 342 type,
380 required: true 343 id: this.url,
344 following: this.getFollowingUrl(),
345 followers: this.getFollowersUrl(),
346 inbox: this.inboxUrl,
347 outbox: this.outboxUrl,
348 preferredUsername: this.name,
349 url: this.url,
350 name: this.name,
351 endpoints: {
352 sharedInbox: this.sharedInboxUrl
353 },
354 uuid: this.uuid,
355 publicKey: {
356 id: this.getPublicKeyUrl(),
357 owner: this.url,
358 publicKeyPem: this.publicKey
381 } 359 }
382 ]
383 })
384}
385
386load = function (id: number) {
387 return Account.findById(id)
388}
389
390loadByUUID = function (uuid: string) {
391 const query: Sequelize.FindOptions<AccountAttributes> = {
392 where: {
393 uuid
394 } 360 }
361
362 return activityPubContextify(json)
395 } 363 }
396 364
397 return Account.findOne(query) 365 isOwned () {
398} 366 return this.serverId === null
367 }
399 368
400loadLocalByName = function (name: string) { 369 getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
401 const query: Sequelize.FindOptions<AccountAttributes> = { 370 const query = {
402 where: { 371 attributes: [ 'sharedInboxUrl' ],
403 name, 372 include: [
404 [Sequelize.Op.or]: [
405 {
406 userId: {
407 [Sequelize.Op.ne]: null
408 }
409 },
410 { 373 {
411 applicationId: { 374 model: AccountFollowModel,
412 [Sequelize.Op.ne]: null 375 required: true,
376 as: 'followers',
377 where: {
378 targetAccountId: this.id
413 } 379 }
414 } 380 }
415 ] 381 ],
382 transaction: t
416 } 383 }
417 }
418 384
419 return Account.findOne(query) 385 return AccountModel.findAll(query)
420} 386 .then(accounts => accounts.map(a => a.sharedInboxUrl))
421
422loadByNameAndHost = function (name: string, host: string) {
423 const query: Sequelize.FindOptions<AccountAttributes> = {
424 where: {
425 name
426 },
427 include: [
428 {
429 model: Account['sequelize'].models.Server,
430 required: true,
431 where: {
432 host
433 }
434 }
435 ]
436 } 387 }
437 388
438 return Account.findOne(query) 389 getFollowingUrl () {
439} 390 return this.url + '/following'
440
441loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
442 const query: Sequelize.FindOptions<AccountAttributes> = {
443 where: {
444 url
445 },
446 transaction
447 } 391 }
448 392
449 return Account.findOne(query) 393 getFollowersUrl () {
450} 394 return this.url + '/followers'
451
452listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) {
453 const query: Sequelize.FindOptions<AccountAttributes> = {
454 where: {
455 followersUrl: {
456 [Sequelize.Op.in]: followersUrls
457 }
458 },
459 transaction
460 } 395 }
461 396
462 return Account.findAll(query) 397 getPublicKeyUrl () {
398 return this.url + '#main-key'
399 }
463} 400}
diff --git a/server/models/account/index.ts b/server/models/account/index.ts
deleted file mode 100644
index 179f66974..000000000
--- a/server/models/account/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
1export * from './account-interface'
2export * from './account-follow-interface'
3export * from './account-video-rate-interface'
4export * from './user-interface'
diff --git a/server/models/account/user-interface.ts b/server/models/account/user-interface.ts
deleted file mode 100644
index 0f0b72063..000000000
--- a/server/models/account/user-interface.ts
+++ /dev/null
@@ -1,67 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { ResultList } from '../../../shared/models/result-list.model'
4import { UserRight } from '../../../shared/models/users/user-right.enum'
5import { UserRole } from '../../../shared/models/users/user-role'
6import { User as FormattedUser } from '../../../shared/models/users/user.model'
7import { AccountInstance } from './account-interface'
8
9export namespace UserMethods {
10 export type HasRight = (this: UserInstance, right: UserRight) => boolean
11 export type IsPasswordMatch = (this: UserInstance, password: string) => Promise<boolean>
12
13 export type ToFormattedJSON = (this: UserInstance) => FormattedUser
14 export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean>
15
16 export type CountTotal = () => Bluebird<number>
17
18 export type GetByUsername = (username: string) => Bluebird<UserInstance>
19
20 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<UserInstance> >
21
22 export type LoadById = (id: number) => Bluebird<UserInstance>
23
24 export type LoadByUsername = (username: string) => Bluebird<UserInstance>
25 export type LoadByUsernameAndPopulateChannels = (username: string) => Bluebird<UserInstance>
26
27 export type LoadByUsernameOrEmail = (username: string, email: string) => Bluebird<UserInstance>
28}
29
30export interface UserClass {
31 isPasswordMatch: UserMethods.IsPasswordMatch,
32 toFormattedJSON: UserMethods.ToFormattedJSON,
33 hasRight: UserMethods.HasRight,
34 isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo,
35
36 countTotal: UserMethods.CountTotal,
37 getByUsername: UserMethods.GetByUsername,
38 listForApi: UserMethods.ListForApi,
39 loadById: UserMethods.LoadById,
40 loadByUsername: UserMethods.LoadByUsername,
41 loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels,
42 loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
43}
44
45export interface UserAttributes {
46 id?: number
47 password: string
48 username: string
49 email: string
50 displayNSFW?: boolean
51 role: UserRole
52 videoQuota: number
53
54 Account?: AccountInstance
55}
56
57export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
58 id: number
59 createdAt: Date
60 updatedAt: Date
61
62 isPasswordMatch: UserMethods.IsPasswordMatch
63 toFormattedJSON: UserMethods.ToFormattedJSON
64 hasRight: UserMethods.HasRight
65}
66
67export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 3705947c0..84adad96e 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -1,301 +1,251 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import {
3 AllowNull,
4 BeforeCreate,
5 BeforeUpdate,
6 Column, CreatedAt,
7 DataType,
8 Default,
9 HasMany,
10 HasOne,
11 Is,
12 IsEmail,
13 Model,
14 Table, UpdatedAt
15} from 'sequelize-typescript'
2import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' 16import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
3import { 17import {
4 comparePassword, 18 comparePassword,
5 cryptPassword, 19 cryptPassword
6 isUserDisplayNSFWValid,
7 isUserPasswordValid,
8 isUserRoleValid,
9 isUserUsernameValid,
10 isUserVideoQuotaValid
11} from '../../helpers' 20} from '../../helpers'
12import { addMethodsToModel, getSort } from '../utils' 21import {
13import { UserAttributes, UserInstance, UserMethods } from './user-interface' 22 isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid,
14 23 isUserVideoQuotaValid
15let User: Sequelize.Model<UserInstance, UserAttributes> 24} from '../../helpers/custom-validators/users'
16let isPasswordMatch: UserMethods.IsPasswordMatch 25import { OAuthTokenModel } from '../oauth/oauth-token'
17let hasRight: UserMethods.HasRight 26import { getSort, throwIfNotValid } from '../utils'
18let toFormattedJSON: UserMethods.ToFormattedJSON 27import { VideoChannelModel } from '../video/video-channel'
19let countTotal: UserMethods.CountTotal 28import { AccountModel } from './account'
20let getByUsername: UserMethods.GetByUsername 29
21let listForApi: UserMethods.ListForApi 30@Table({
22let loadById: UserMethods.LoadById 31 tableName: 'user',
23let loadByUsername: UserMethods.LoadByUsername 32 indexes: [
24let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels
25let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
26let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo
27
28export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
29 User = sequelize.define<UserInstance, UserAttributes>('User',
30 { 33 {
31 password: { 34 fields: [ 'username' ],
32 type: DataTypes.STRING, 35 unique: true
33 allowNull: false,
34 validate: {
35 passwordValid: value => {
36 const res = isUserPasswordValid(value)
37 if (res === false) throw new Error('Password not valid.')
38 }
39 }
40 },
41 username: {
42 type: DataTypes.STRING,
43 allowNull: false,
44 validate: {
45 usernameValid: value => {
46 const res = isUserUsernameValid(value)
47 if (res === false) throw new Error('Username not valid.')
48 }
49 }
50 },
51 email: {
52 type: DataTypes.STRING(400),
53 allowNull: false,
54 validate: {
55 isEmail: true
56 }
57 },
58 displayNSFW: {
59 type: DataTypes.BOOLEAN,
60 allowNull: false,
61 defaultValue: false,
62 validate: {
63 nsfwValid: value => {
64 const res = isUserDisplayNSFWValid(value)
65 if (res === false) throw new Error('Display NSFW is not valid.')
66 }
67 }
68 },
69 role: {
70 type: DataTypes.INTEGER,
71 allowNull: false,
72 validate: {
73 roleValid: value => {
74 const res = isUserRoleValid(value)
75 if (res === false) throw new Error('Role is not valid.')
76 }
77 }
78 },
79 videoQuota: {
80 type: DataTypes.BIGINT,
81 allowNull: false,
82 validate: {
83 videoQuotaValid: value => {
84 const res = isUserVideoQuotaValid(value)
85 if (res === false) throw new Error('Video quota is not valid.')
86 }
87 }
88 }
89 }, 36 },
90 { 37 {
91 indexes: [ 38 fields: [ 'email' ],
92 { 39 unique: true
93 fields: [ 'username' ],
94 unique: true
95 },
96 {
97 fields: [ 'email' ],
98 unique: true
99 }
100 ],
101 hooks: {
102 beforeCreate: beforeCreateOrUpdate,
103 beforeUpdate: beforeCreateOrUpdate
104 }
105 } 40 }
106 )
107
108 const classMethods = [
109 associate,
110
111 countTotal,
112 getByUsername,
113 listForApi,
114 loadById,
115 loadByUsername,
116 loadByUsernameAndPopulateChannels,
117 loadByUsernameOrEmail
118 ] 41 ]
119 const instanceMethods = [ 42})
120 hasRight, 43export class UserModel extends Model<UserModel> {
121 isPasswordMatch, 44
122 toFormattedJSON, 45 @AllowNull(false)
123 isAbleToUploadVideo 46 @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password'))
124 ] 47 @Column
125 addMethodsToModel(User, classMethods, instanceMethods) 48 password: string
126 49
127 return User 50 @AllowNull(false)
128} 51 @Is('UserPassword', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
52 @Column
53 username: string
54
55 @AllowNull(false)
56 @IsEmail
57 @Column(DataType.STRING(400))
58 email: string
59
60 @AllowNull(false)
61 @Default(false)
62 @Is('UserDisplayNSFW', value => throwIfNotValid(value, isUserDisplayNSFWValid, 'display NSFW boolean'))
63 @Column
64 displayNSFW: boolean
65
66 @AllowNull(false)
67 @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
68 @Column
69 role: number
70
71 @AllowNull(false)
72 @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota'))
73 @Column(DataType.BIGINT)
74 videoQuota: number
75
76 @CreatedAt
77 createdAt: Date
78
79 @UpdatedAt
80 updatedAt: Date
81
82 @HasOne(() => AccountModel, {
83 foreignKey: 'userId',
84 onDelete: 'cascade'
85 })
86 Account: AccountModel
129 87
130function beforeCreateOrUpdate (user: UserInstance) { 88 @HasMany(() => OAuthTokenModel, {
131 if (user.changed('password')) { 89 foreignKey: 'userId',
132 return cryptPassword(user.password) 90 onDelete: 'cascade'
133 .then(hash => { 91 })
134 user.password = hash 92 OAuthTokens: OAuthTokenModel[]
135 return undefined 93
136 }) 94 @BeforeCreate
95 @BeforeUpdate
96 static cryptPasswordIfNeeded (instance: UserModel) {
97 if (instance.changed('password')) {
98 return cryptPassword(instance.password)
99 .then(hash => {
100 instance.password = hash
101 return undefined
102 })
103 }
137 } 104 }
138}
139
140// ------------------------------ METHODS ------------------------------
141 105
142hasRight = function (this: UserInstance, right: UserRight) { 106 static countTotal () {
143 return hasUserRight(this.role, right) 107 return this.count()
144} 108 }
145 109
146isPasswordMatch = function (this: UserInstance, password: string) { 110 static getByUsername (username: string) {
147 return comparePassword(password, this.password) 111 const query = {
148} 112 where: {
113 username: username
114 },
115 include: [ { model: AccountModel, required: true } ]
116 }
149 117
150toFormattedJSON = function (this: UserInstance) { 118 return UserModel.findOne(query)
151 const json = {
152 id: this.id,
153 username: this.username,
154 email: this.email,
155 displayNSFW: this.displayNSFW,
156 role: this.role,
157 roleLabel: USER_ROLE_LABELS[this.role],
158 videoQuota: this.videoQuota,
159 createdAt: this.createdAt,
160 account: this.Account.toFormattedJSON()
161 } 119 }
162 120
163 if (Array.isArray(this.Account.VideoChannels) === true) { 121 static listForApi (start: number, count: number, sort: string) {
164 const videoChannels = this.Account.VideoChannels 122 const query = {
165 .map(c => c.toFormattedJSON()) 123 offset: start,
166 .sort((v1, v2) => { 124 limit: count,
167 if (v1.createdAt < v2.createdAt) return -1 125 order: [ getSort(sort) ],
168 if (v1.createdAt === v2.createdAt) return 0 126 include: [ { model: AccountModel, required: true } ]
127 }
169 128
170 return 1 129 return UserModel.findAndCountAll(query)
130 .then(({ rows, count }) => {
131 return {
132 data: rows,
133 total: count
134 }
171 }) 135 })
172
173 json['videoChannels'] = videoChannels
174 } 136 }
175 137
176 return json 138 static loadById (id: number) {
177} 139 const options = {
178 140 include: [ { model: AccountModel, required: true } ]
179isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) { 141 }
180 if (this.videoQuota === -1) return Promise.resolve(true)
181
182 return getOriginalVideoFileTotalFromUser(this).then(totalBytes => {
183 return (videoFile.size + totalBytes) < this.videoQuota
184 })
185}
186
187// ------------------------------ STATICS ------------------------------
188
189function associate (models) {
190 User.hasOne(models.Account, {
191 foreignKey: 'userId',
192 onDelete: 'cascade'
193 })
194 142
195 User.hasMany(models.OAuthToken, { 143 return UserModel.findById(id, options)
196 foreignKey: 'userId', 144 }
197 onDelete: 'cascade'
198 })
199}
200 145
201countTotal = function () { 146 static loadByUsername (username: string) {
202 return this.count() 147 const query = {
203} 148 where: {
149 username
150 },
151 include: [ { model: AccountModel, required: true } ]
152 }
204 153
205getByUsername = function (username: string) { 154 return UserModel.findOne(query)
206 const query = {
207 where: {
208 username: username
209 },
210 include: [ { model: User['sequelize'].models.Account, required: true } ]
211 } 155 }
212 156
213 return User.findOne(query) 157 static loadByUsernameAndPopulateChannels (username: string) {
214} 158 const query = {
159 where: {
160 username
161 },
162 include: [
163 {
164 model: AccountModel,
165 required: true,
166 include: [ VideoChannelModel ]
167 }
168 ]
169 }
215 170
216listForApi = function (start: number, count: number, sort: string) { 171 return UserModel.findOne(query)
217 const query = {
218 offset: start,
219 limit: count,
220 order: [ getSort(sort) ],
221 include: [ { model: User['sequelize'].models.Account, required: true } ]
222 } 172 }
223 173
224 return User.findAndCountAll(query).then(({ rows, count }) => { 174 static loadByUsernameOrEmail (username: string, email: string) {
225 return { 175 const query = {
226 data: rows, 176 include: [ { model: AccountModel, required: true } ],
227 total: count 177 where: {
178 [ Sequelize.Op.or ]: [ { username }, { email } ]
179 }
228 } 180 }
229 })
230}
231 181
232loadById = function (id: number) { 182 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387
233 const options = { 183 return (UserModel as any).findOne(query)
234 include: [ { model: User['sequelize'].models.Account, required: true } ]
235 } 184 }
236 185
237 return User.findById(id, options) 186 private static getOriginalVideoFileTotalFromUser (user: UserModel) {
238} 187 // Don't use sequelize because we need to use a sub query
188 const query = 'SELECT SUM("size") AS "total" FROM ' +
189 '(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
190 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
191 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
192 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
193 'INNER JOIN "user" ON "account"."userId" = "user"."id" ' +
194 'WHERE "user"."id" = $userId GROUP BY "video"."id") t'
195
196 const options = {
197 bind: { userId: user.id },
198 type: Sequelize.QueryTypes.SELECT
199 }
200 return UserModel.sequelize.query(query, options)
201 .then(([ { total } ]) => {
202 if (total === null) return 0
239 203
240loadByUsername = function (username: string) { 204 return parseInt(total, 10)
241 const query = { 205 })
242 where: {
243 username
244 },
245 include: [ { model: User['sequelize'].models.Account, required: true } ]
246 } 206 }
247 207
248 return User.findOne(query) 208 hasRight (right: UserRight) {
249} 209 return hasUserRight(this.role, right)
210 }
250 211
251loadByUsernameAndPopulateChannels = function (username: string) { 212 isPasswordMatch (password: string) {
252 const query = { 213 return comparePassword(password, this.password)
253 where: {
254 username
255 },
256 include: [
257 {
258 model: User['sequelize'].models.Account,
259 required: true,
260 include: [ User['sequelize'].models.VideoChannel ]
261 }
262 ]
263 } 214 }
264 215
265 return User.findOne(query) 216 toFormattedJSON () {
266} 217 const json = {
218 id: this.id,
219 username: this.username,
220 email: this.email,
221 displayNSFW: this.displayNSFW,
222 role: this.role,
223 roleLabel: USER_ROLE_LABELS[ this.role ],
224 videoQuota: this.videoQuota,
225 createdAt: this.createdAt,
226 account: this.Account.toFormattedJSON()
227 }
228
229 if (Array.isArray(this.Account.VideoChannels) === true) {
230 json['videoChannels'] = this.Account.VideoChannels
231 .map(c => c.toFormattedJSON())
232 .sort((v1, v2) => {
233 if (v1.createdAt < v2.createdAt) return -1
234 if (v1.createdAt === v2.createdAt) return 0
267 235
268loadByUsernameOrEmail = function (username: string, email: string) { 236 return 1
269 const query = { 237 })
270 include: [ { model: User['sequelize'].models.Account, required: true } ],
271 where: {
272 [Sequelize.Op.or]: [ { username }, { email } ]
273 } 238 }
239
240 return json
274 } 241 }
275 242
276 // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 243 isAbleToUploadVideo (videoFile: Express.Multer.File) {
277 return (User as any).findOne(query) 244 if (this.videoQuota === -1) return Promise.resolve(true)
278}
279 245
280// --------------------------------------------------------------------------- 246 return UserModel.getOriginalVideoFileTotalFromUser(this)
281 247 .then(totalBytes => {
282function getOriginalVideoFileTotalFromUser (user: UserInstance) { 248 return (videoFile.size + totalBytes) < this.videoQuota
283 // Don't use sequelize because we need to use a sub query 249 })
284 const query = 'SELECT SUM("size") AS "total" FROM ' +
285 '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' +
286 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' +
287 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' +
288 'INNER JOIN "Accounts" ON "VideoChannels"."accountId" = "Accounts"."id" ' +
289 'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' +
290 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t'
291
292 const options = {
293 bind: { userId: user.id },
294 type: Sequelize.QueryTypes.SELECT
295 } 250 }
296 return User['sequelize'].query(query, options).then(([ { total } ]) => {
297 if (total === null) return 0
298
299 return parseInt(total, 10)
300 })
301} 251}
diff --git a/server/models/application/application-interface.ts b/server/models/application/application-interface.ts
deleted file mode 100644
index 2c391dba3..000000000
--- a/server/models/application/application-interface.ts
+++ /dev/null
@@ -1,31 +0,0 @@
1import * as Sequelize from 'sequelize'
2import * as Bluebird from 'bluebird'
3
4export namespace ApplicationMethods {
5 export type LoadMigrationVersion = () => Bluebird<number>
6
7 export type UpdateMigrationVersion = (
8 newVersion: number,
9 transaction: Sequelize.Transaction
10 ) => Bluebird<[ number, ApplicationInstance[] ]>
11
12 export type CountTotal = () => Bluebird<number>
13}
14
15export interface ApplicationClass {
16 loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
17 updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
18 countTotal: ApplicationMethods.CountTotal
19}
20
21export interface ApplicationAttributes {
22 migrationVersion: number
23}
24
25export interface ApplicationInstance extends ApplicationClass, ApplicationAttributes, Sequelize.Instance<ApplicationAttributes> {
26 id: number
27 createdAt: Date
28 updatedAt: Date
29}
30
31export interface ApplicationModel extends ApplicationClass, Sequelize.Model<ApplicationInstance, ApplicationAttributes> {}
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
index 8ba40a895..f3c0f1052 100644
--- a/server/models/application/application.ts
+++ b/server/models/application/application.ts
@@ -1,61 +1,35 @@
1import * as Sequelize from 'sequelize' 1import { Transaction } from 'sequelize'
2 2import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript'
3import { addMethodsToModel } from '../utils' 3
4import { 4@Table({
5 ApplicationAttributes, 5 tableName: 'application'
6 ApplicationInstance, 6})
7 7export class ApplicationModel extends Model<ApplicationModel> {
8 ApplicationMethods 8
9} from './application-interface' 9 @AllowNull(false)
10 10 @Default(0)
11let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes> 11 @IsInt
12let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion 12 @Column
13let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion 13 migrationVersion: number
14let countTotal: ApplicationMethods.CountTotal 14
15 static countTotal () {
16 return ApplicationModel.count()
17 }
15 18
16export default function defineApplication (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 19 static loadMigrationVersion () {
17 Application = sequelize.define<ApplicationInstance, ApplicationAttributes>('Application', 20 const query = {
18 { 21 attributes: [ 'migrationVersion' ]
19 migrationVersion: {
20 type: DataTypes.INTEGER,
21 defaultValue: 0,
22 allowNull: false,
23 validate: {
24 isInt: true
25 }
26 }
27 } 22 }
28 )
29
30 const classMethods = [
31 countTotal,
32 loadMigrationVersion,
33 updateMigrationVersion
34 ]
35 addMethodsToModel(Application, classMethods)
36
37 return Application
38}
39 23
40// --------------------------------------------------------------------------- 24 return ApplicationModel.findOne(query).then(data => data ? data.migrationVersion : null)
41
42countTotal = function () {
43 return this.count()
44}
45
46loadMigrationVersion = function () {
47 const query = {
48 attributes: [ 'migrationVersion' ]
49 } 25 }
50 26
51 return Application.findOne(query).then(data => data ? data.migrationVersion : null) 27 static updateMigrationVersion (newVersion: number, transaction: Transaction) {
52} 28 const options = {
29 where: {},
30 transaction: transaction
31 }
53 32
54updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction) { 33 return ApplicationModel.update({ migrationVersion: newVersion }, options)
55 const options: Sequelize.UpdateOptions = {
56 where: {},
57 transaction: transaction
58 } 34 }
59
60 return Application.update({ migrationVersion: newVersion }, options)
61} 35}
diff --git a/server/models/application/index.ts b/server/models/application/index.ts
deleted file mode 100644
index 706f85cb9..000000000
--- a/server/models/application/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './application-interface'
diff --git a/server/models/avatar/avatar-interface.ts b/server/models/avatar/avatar-interface.ts
deleted file mode 100644
index 4af2b87b7..000000000
--- a/server/models/avatar/avatar-interface.ts
+++ /dev/null
@@ -1,16 +0,0 @@
1import * as Sequelize from 'sequelize'
2
3export namespace AvatarMethods {}
4
5export interface AvatarClass {}
6
7export interface AvatarAttributes {
8 filename: string
9}
10
11export interface AvatarInstance extends AvatarClass, AvatarAttributes, Sequelize.Instance<AvatarAttributes> {
12 createdAt: Date
13 updatedAt: Date
14}
15
16export interface AvatarModel extends AvatarClass, Sequelize.Model<AvatarInstance, AvatarAttributes> {}
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts
index 96308fd5f..2e7a8ae2c 100644
--- a/server/models/avatar/avatar.ts
+++ b/server/models/avatar/avatar.ts
@@ -1,24 +1,17 @@
1import * as Sequelize from 'sequelize' 1import { AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { addMethodsToModel } from '../utils'
3import { AvatarAttributes, AvatarInstance } from './avatar-interface'
4 2
5let Avatar: Sequelize.Model<AvatarInstance, AvatarAttributes> 3@Table({
4 tableName: 'avatar'
5})
6export class AvatarModel extends Model<AvatarModel> {
6 7
7export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 8 @AllowNull(false)
8 Avatar = sequelize.define<AvatarInstance, AvatarAttributes>('Avatar', 9 @Column
9 { 10 filename: string
10 filename: {
11 type: DataTypes.STRING,
12 allowNull: false
13 }
14 },
15 {}
16 )
17 11
18 const classMethods = [] 12 @CreatedAt
19 addMethodsToModel(Avatar, classMethods) 13 createdAt: Date
20 14
21 return Avatar 15 @UpdatedAt
16 updatedAt: Date
22} 17}
23
24// ------------------------------ Statics ------------------------------
diff --git a/server/models/avatar/index.ts b/server/models/avatar/index.ts
deleted file mode 100644
index 877aed1ce..000000000
--- a/server/models/avatar/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './avatar-interface'
diff --git a/server/models/index.ts b/server/models/index.ts
deleted file mode 100644
index fedd97dd1..000000000
--- a/server/models/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
1export * from './application'
2export * from './avatar'
3export * from './job'
4export * from './oauth'
5export * from './server'
6export * from './account'
7export * from './video'
diff --git a/server/models/job/index.ts b/server/models/job/index.ts
deleted file mode 100644
index 56925fd32..000000000
--- a/server/models/job/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './job-interface'
diff --git a/server/models/job/job-interface.ts b/server/models/job/job-interface.ts
deleted file mode 100644
index 3cfc0fbed..000000000
--- a/server/models/job/job-interface.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model'
4import { ResultList } from '../../../shared/models/result-list.model'
5
6export namespace JobMethods {
7 export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Bluebird<JobInstance[]>
8 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<JobInstance> >
9
10 export type ToFormattedJSON = (this: JobInstance) => FormattedJob
11}
12
13export interface JobClass {
14 listWithLimitByCategory: JobMethods.ListWithLimitByCategory
15 listForApi: JobMethods.ListForApi,
16}
17
18export interface JobAttributes {
19 state: JobState
20 category: JobCategory
21 handlerName: string
22 handlerInputData: any
23}
24
25export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance<JobAttributes> {
26 id: number
27 createdAt: Date
28 updatedAt: Date
29
30 toFormattedJSON: JobMethods.ToFormattedJSON
31}
32
33export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {}
diff --git a/server/models/job/job.ts b/server/models/job/job.ts
index f428e26db..35c357e69 100644
--- a/server/models/job/job.ts
+++ b/server/models/job/job.ts
@@ -1,96 +1,79 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 2import { AllowNull, Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { JobCategory, JobState } from '../../../shared/models/job.model' 3import { JobCategory, JobState } from '../../../shared/models'
4import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' 4import { JOB_CATEGORIES, JOB_STATES } from '../../initializers'
5import { addMethodsToModel, getSort } from '../utils' 5import { getSort } from '../utils'
6import { JobAttributes, JobInstance, JobMethods } from './job-interface'
7 6
8let Job: Sequelize.Model<JobInstance, JobAttributes> 7@Table({
9let listWithLimitByCategory: JobMethods.ListWithLimitByCategory 8 tableName: 'job',
10let listForApi: JobMethods.ListForApi 9 indexes: [
11let toFormattedJSON: JobMethods.ToFormattedJSON
12
13export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
14 Job = sequelize.define<JobInstance, JobAttributes>('Job',
15 {
16 state: {
17 type: DataTypes.ENUM(values(JOB_STATES)),
18 allowNull: false
19 },
20 category: {
21 type: DataTypes.ENUM(values(JOB_CATEGORIES)),
22 allowNull: false
23 },
24 handlerName: {
25 type: DataTypes.STRING,
26 allowNull: false
27 },
28 handlerInputData: {
29 type: DataTypes.JSON,
30 allowNull: true
31 }
32 },
33 { 10 {
34 indexes: [ 11 fields: [ 'state', 'category' ]
35 {
36 fields: [ 'state', 'category' ]
37 }
38 ]
39 } 12 }
40 )
41
42 const classMethods = [
43 listWithLimitByCategory,
44 listForApi
45 ] 13 ]
46 const instanceMethods = [ 14})
47 toFormattedJSON 15export class JobModel extends Model<JobModel> {
48 ] 16 @AllowNull(false)
49 addMethodsToModel(Job, classMethods, instanceMethods) 17 @Column(DataType.ENUM(values(JOB_STATES)))
18 state: JobState
50 19
51 return Job 20 @AllowNull(false)
52} 21 @Column(DataType.ENUM(values(JOB_CATEGORIES)))
22 category: JobCategory
53 23
54toFormattedJSON = function (this: JobInstance) { 24 @AllowNull(false)
55 return { 25 @Column
56 id: this.id, 26 handlerName: string
57 state: this.state,
58 category: this.category,
59 handlerName: this.handlerName,
60 handlerInputData: this.handlerInputData,
61 createdAt: this.createdAt,
62 updatedAt: this.updatedAt
63 }
64}
65 27
66// --------------------------------------------------------------------------- 28 @AllowNull(true)
29 @Column(DataType.JSON)
30 handlerInputData: any
67 31
68listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { 32 @CreatedAt
69 const query = { 33 creationDate: Date
70 order: [ 34
71 [ 'id', 'ASC' ] 35 @UpdatedAt
72 ], 36 updatedOn: Date
73 limit: limit, 37
74 where: { 38 static listWithLimitByCategory (limit: number, state: JobState, jobCategory: JobCategory) {
75 state, 39 const query = {
76 category: jobCategory 40 order: [
41 [ 'id', 'ASC' ]
42 ],
43 limit: limit,
44 where: {
45 state,
46 category: jobCategory
47 }
77 } 48 }
49
50 return JobModel.findAll(query)
78 } 51 }
79 52
80 return Job.findAll(query) 53 static listForApi (start: number, count: number, sort: string) {
81} 54 const query = {
55 offset: start,
56 limit: count,
57 order: [ getSort(sort) ]
58 }
82 59
83listForApi = function (start: number, count: number, sort: string) { 60 return JobModel.findAndCountAll(query).then(({ rows, count }) => {
84 const query = { 61 return {
85 offset: start, 62 data: rows,
86 limit: count, 63 total: count
87 order: [ getSort(sort) ] 64 }
65 })
88 } 66 }
89 67
90 return Job.findAndCountAll(query).then(({ rows, count }) => { 68 toFormattedJSON () {
91 return { 69 return {
92 data: rows, 70 id: this.id,
93 total: count 71 state: this.state,
72 category: this.category,
73 handlerName: this.handlerName,
74 handlerInputData: this.handlerInputData,
75 createdAt: this.createdAt,
76 updatedAt: this.updatedAt
94 } 77 }
95 }) 78 }
96} 79}
diff --git a/server/models/oauth/index.ts b/server/models/oauth/index.ts
deleted file mode 100644
index a20d3a56a..000000000
--- a/server/models/oauth/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from './oauth-client-interface'
2export * from './oauth-token-interface'
diff --git a/server/models/oauth/oauth-client-interface.ts b/server/models/oauth/oauth-client-interface.ts
deleted file mode 100644
index 3526e4159..000000000
--- a/server/models/oauth/oauth-client-interface.ts
+++ /dev/null
@@ -1,31 +0,0 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird'
3
4export namespace OAuthClientMethods {
5 export type CountTotal = () => Promise<number>
6
7 export type LoadFirstClient = () => Promise<OAuthClientInstance>
8
9 export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise<OAuthClientInstance>
10}
11
12export interface OAuthClientClass {
13 countTotal: OAuthClientMethods.CountTotal
14 loadFirstClient: OAuthClientMethods.LoadFirstClient
15 getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret
16}
17
18export interface OAuthClientAttributes {
19 clientId: string
20 clientSecret: string
21 grants: string[]
22 redirectUris: string[]
23}
24
25export interface OAuthClientInstance extends OAuthClientClass, OAuthClientAttributes, Sequelize.Instance<OAuthClientAttributes> {
26 id: number
27 createdAt: Date
28 updatedAt: Date
29}
30
31export interface OAuthClientModel extends OAuthClientClass, Sequelize.Model<OAuthClientInstance, OAuthClientAttributes> {}
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts
index 9cc68771d..42c59bb79 100644
--- a/server/models/oauth/oauth-client.ts
+++ b/server/models/oauth/oauth-client.ts
@@ -1,86 +1,62 @@
1import * as Sequelize from 'sequelize' 1import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { OAuthTokenModel } from './oauth-token'
2 3
3import { addMethodsToModel } from '../utils' 4@Table({
4import { 5 tableName: 'oAuthClient',
5 OAuthClientInstance, 6 indexes: [
6 OAuthClientAttributes,
7
8 OAuthClientMethods
9} from './oauth-client-interface'
10
11let OAuthClient: Sequelize.Model<OAuthClientInstance, OAuthClientAttributes>
12let countTotal: OAuthClientMethods.CountTotal
13let loadFirstClient: OAuthClientMethods.LoadFirstClient
14let getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret
15
16export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
17 OAuthClient = sequelize.define<OAuthClientInstance, OAuthClientAttributes>('OAuthClient',
18 { 7 {
19 clientId: { 8 fields: [ 'clientId' ],
20 type: DataTypes.STRING, 9 unique: true
21 allowNull: false
22 },
23 clientSecret: {
24 type: DataTypes.STRING,
25 allowNull: false
26 },
27 grants: {
28 type: DataTypes.ARRAY(DataTypes.STRING)
29 },
30 redirectUris: {
31 type: DataTypes.ARRAY(DataTypes.STRING)
32 }
33 }, 10 },
34 { 11 {
35 indexes: [ 12 fields: [ 'clientId', 'clientSecret' ],
36 { 13 unique: true
37 fields: [ 'clientId' ],
38 unique: true
39 },
40 {
41 fields: [ 'clientId', 'clientSecret' ],
42 unique: true
43 }
44 ]
45 } 14 }
46 ) 15 ]
16})
17export class OAuthClientModel extends Model<OAuthClientModel> {
47 18
48 const classMethods = [ 19 @AllowNull(false)
49 associate, 20 @Column
21 clientId: string
50 22
51 countTotal, 23 @AllowNull(false)
52 getByIdAndSecret, 24 @Column
53 loadFirstClient 25 clientSecret: string
54 ]
55 addMethodsToModel(OAuthClient, classMethods)
56 26
57 return OAuthClient 27 @Column(DataType.ARRAY(DataType.STRING))
58} 28 grants: string[]
29
30 @Column(DataType.ARRAY(DataType.STRING))
31 redirectUris: string[]
32
33 @CreatedAt
34 createdAt: Date
59 35
60// --------------------------------------------------------------------------- 36 @UpdatedAt
37 updatedAt: Date
61 38
62function associate (models) { 39 @HasMany(() => OAuthTokenModel, {
63 OAuthClient.hasMany(models.OAuthToken, {
64 foreignKey: 'oAuthClientId',
65 onDelete: 'cascade' 40 onDelete: 'cascade'
66 }) 41 })
67} 42 OAuthTokens: OAuthTokenModel[]
68 43
69countTotal = function () { 44 static countTotal () {
70 return OAuthClient.count() 45 return OAuthClientModel.count()
71} 46 }
72 47
73loadFirstClient = function () { 48 static loadFirstClient () {
74 return OAuthClient.findOne() 49 return OAuthClientModel.findOne()
75} 50 }
76 51
77getByIdAndSecret = function (clientId: string, clientSecret: string) { 52 static getByIdAndSecret (clientId: string, clientSecret: string) {
78 const query = { 53 const query = {
79 where: { 54 where: {
80 clientId: clientId, 55 clientId: clientId,
81 clientSecret: clientSecret 56 clientSecret: clientSecret
57 }
82 } 58 }
83 }
84 59
85 return OAuthClient.findOne(query) 60 return OAuthClientModel.findOne(query)
61 }
86} 62}
diff --git a/server/models/oauth/oauth-token-interface.ts b/server/models/oauth/oauth-token-interface.ts
deleted file mode 100644
index 47d95d5fc..000000000
--- a/server/models/oauth/oauth-token-interface.ts
+++ /dev/null
@@ -1,46 +0,0 @@
1import * as Promise from 'bluebird'
2import * as Sequelize from 'sequelize'
3
4import { UserModel } from '../account/user-interface'
5
6export type OAuthTokenInfo = {
7 refreshToken: string
8 refreshTokenExpiresAt: Date,
9 client: {
10 id: number
11 },
12 user: {
13 id: number
14 }
15}
16
17export namespace OAuthTokenMethods {
18 export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Promise<OAuthTokenInfo>
19 export type GetByTokenAndPopulateUser = (bearerToken: string) => Promise<OAuthTokenInstance>
20 export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Promise<OAuthTokenInstance>
21}
22
23export interface OAuthTokenClass {
24 getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
25 getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
26 getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
27}
28
29export interface OAuthTokenAttributes {
30 accessToken: string
31 accessTokenExpiresAt: Date
32 refreshToken: string
33 refreshTokenExpiresAt: Date
34
35 userId?: number
36 oAuthClientId?: number
37 User?: UserModel
38}
39
40export interface OAuthTokenInstance extends OAuthTokenClass, OAuthTokenAttributes, Sequelize.Instance<OAuthTokenAttributes> {
41 id: number
42 createdAt: Date
43 updatedAt: Date
44}
45
46export interface OAuthTokenModel extends OAuthTokenClass, Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes> {}
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index a82bff130..0d21c42fd 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -1,164 +1,163 @@
1import * as Sequelize from 'sequelize' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2
3import { logger } from '../../helpers' 2import { logger } from '../../helpers'
3import { AccountModel } from '../account/account'
4import { UserModel } from '../account/user'
5import { OAuthClientModel } from './oauth-client'
6
7export type OAuthTokenInfo = {
8 refreshToken: string
9 refreshTokenExpiresAt: Date,
10 client: {
11 id: number
12 },
13 user: {
14 id: number
15 }
16}
4 17
5import { addMethodsToModel } from '../utils' 18@Table({
6import { OAuthTokenAttributes, OAuthTokenInfo, OAuthTokenInstance, OAuthTokenMethods } from './oauth-token-interface' 19 tableName: 'oAuthToken',
7 20 indexes: [
8let OAuthToken: Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes>
9let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
10let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
11let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
12
13export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
14 OAuthToken = sequelize.define<OAuthTokenInstance, OAuthTokenAttributes>('OAuthToken',
15 { 21 {
16 accessToken: { 22 fields: [ 'refreshToken' ],
17 type: DataTypes.STRING, 23 unique: true
18 allowNull: false
19 },
20 accessTokenExpiresAt: {
21 type: DataTypes.DATE,
22 allowNull: false
23 },
24 refreshToken: {
25 type: DataTypes.STRING,
26 allowNull: false
27 },
28 refreshTokenExpiresAt: {
29 type: DataTypes.DATE,
30 allowNull: false
31 }
32 }, 24 },
33 { 25 {
34 indexes: [ 26 fields: [ 'accessToken' ],
35 { 27 unique: true
36 fields: [ 'refreshToken' ], 28 },
37 unique: true 29 {
38 }, 30 fields: [ 'userId' ]
39 { 31 },
40 fields: [ 'accessToken' ], 32 {
41 unique: true 33 fields: [ 'oAuthClientId' ]
42 },
43 {
44 fields: [ 'userId' ]
45 },
46 {
47 fields: [ 'oAuthClientId' ]
48 }
49 ]
50 } 34 }
51 ) 35 ]
36})
37export class OAuthTokenModel extends Model<OAuthTokenModel> {
52 38
53 const classMethods = [ 39 @AllowNull(false)
54 associate, 40 @Column
41 accessToken: string
55 42
56 getByRefreshTokenAndPopulateClient, 43 @AllowNull(false)
57 getByTokenAndPopulateUser, 44 @Column
58 getByRefreshTokenAndPopulateUser 45 accessTokenExpiresAt: Date
59 ]
60 addMethodsToModel(OAuthToken, classMethods)
61 46
62 return OAuthToken 47 @AllowNull(false)
63} 48 @Column
49 refreshToken: string
64 50
65// --------------------------------------------------------------------------- 51 @AllowNull(false)
52 @Column
53 refreshTokenExpiresAt: Date
66 54
67function associate (models) { 55 @CreatedAt
68 OAuthToken.belongsTo(models.User, { 56 createdAt: Date
57
58 @UpdatedAt
59 updatedAt: Date
60
61 @ForeignKey(() => UserModel)
62 @Column
63 userId: number
64
65 @BelongsTo(() => UserModel, {
69 foreignKey: { 66 foreignKey: {
70 name: 'userId',
71 allowNull: false 67 allowNull: false
72 }, 68 },
73 onDelete: 'cascade' 69 onDelete: 'cascade'
74 }) 70 })
71 User: UserModel
75 72
76 OAuthToken.belongsTo(models.OAuthClient, { 73 @ForeignKey(() => OAuthClientModel)
74 @Column
75 oAuthClientId: number
76
77 @BelongsTo(() => OAuthClientModel, {
77 foreignKey: { 78 foreignKey: {
78 name: 'oAuthClientId',
79 allowNull: false 79 allowNull: false
80 }, 80 },
81 onDelete: 'cascade' 81 onDelete: 'cascade'
82 }) 82 })
83} 83 OAuthClients: OAuthClientModel[]
84 84
85getByRefreshTokenAndPopulateClient = function (refreshToken: string) { 85 static getByRefreshTokenAndPopulateClient (refreshToken: string) {
86 const query = { 86 const query = {
87 where: { 87 where: {
88 refreshToken: refreshToken 88 refreshToken: refreshToken
89 }, 89 },
90 include: [ OAuthToken['sequelize'].models.OAuthClient ] 90 include: [ OAuthClientModel ]
91 }
92
93 return OAuthTokenModel.findOne(query)
94 .then(token => {
95 if (!token) return null
96
97 return {
98 refreshToken: token.refreshToken,
99 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
100 client: {
101 id: token.oAuthClientId
102 },
103 user: {
104 id: token.userId
105 }
106 } as OAuthTokenInfo
107 })
108 .catch(err => {
109 logger.info('getRefreshToken error.', err)
110 throw err
111 })
91 } 112 }
92 113
93 return OAuthToken.findOne(query) 114 static getByTokenAndPopulateUser (bearerToken: string) {
94 .then(token => { 115 const query = {
95 if (!token) return null 116 where: {
96 117 accessToken: bearerToken
97 const tokenInfos: OAuthTokenInfo = { 118 },
98 refreshToken: token.refreshToken, 119 include: [
99 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 120 {
100 client: { 121 model: UserModel,
101 id: token.oAuthClientId 122 include: [
102 }, 123 {
103 user: { 124 model: AccountModel,
104 id: token.userId 125 required: true
126 }
127 ]
105 } 128 }
106 } 129 ]
130 }
107 131
108 return tokenInfos 132 return OAuthTokenModel.findOne(query).then(token => {
109 }) 133 if (token) token['user'] = token.User
110 .catch(err => {
111 logger.info('getRefreshToken error.', err)
112 throw err
113 })
114}
115 134
116getByTokenAndPopulateUser = function (bearerToken: string) { 135 return token
117 const query = { 136 })
118 where: {
119 accessToken: bearerToken
120 },
121 include: [
122 {
123 model: OAuthToken['sequelize'].models.User,
124 include: [
125 {
126 model: OAuthToken['sequelize'].models.Account,
127 required: true
128 }
129 ]
130 }
131 ]
132 } 137 }
133 138
134 return OAuthToken.findOne(query).then(token => { 139 static getByRefreshTokenAndPopulateUser (refreshToken: string) {
135 if (token) token['user'] = token.User 140 const query = {
141 where: {
142 refreshToken: refreshToken
143 },
144 include: [
145 {
146 model: UserModel,
147 include: [
148 {
149 model: AccountModel,
150 required: true
151 }
152 ]
153 }
154 ]
155 }
136 156
137 return token 157 return OAuthTokenModel.findOne(query).then(token => {
138 }) 158 token['user'] = token.User
139}
140 159
141getByRefreshTokenAndPopulateUser = function (refreshToken: string) { 160 return token
142 const query = { 161 })
143 where: {
144 refreshToken: refreshToken
145 },
146 include: [
147 {
148 model: OAuthToken['sequelize'].models.User,
149 include: [
150 {
151 model: OAuthToken['sequelize'].models.Account,
152 required: true
153 }
154 ]
155 }
156 ]
157 } 162 }
158
159 return OAuthToken.findOne(query).then(token => {
160 token['user'] = token.User
161
162 return token
163 })
164} 163}
diff --git a/server/models/server/index.ts b/server/models/server/index.ts
deleted file mode 100644
index 4cb2994aa..000000000
--- a/server/models/server/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './server-interface'
diff --git a/server/models/server/server-interface.ts b/server/models/server/server-interface.ts
deleted file mode 100644
index be1a4917e..000000000
--- a/server/models/server/server-interface.ts
+++ /dev/null
@@ -1,24 +0,0 @@
1import * as Promise from 'bluebird'
2import * as Sequelize from 'sequelize'
3
4export namespace ServerMethods {
5 export type ListBadServers = () => Promise<ServerInstance[]>
6 export type UpdateServersScoreAndRemoveBadOnes = (goodServers: number[], badServers: number[]) => void
7}
8
9export interface ServerClass {
10 updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes
11}
12
13export interface ServerAttributes {
14 id?: number
15 host?: string
16 score?: number | Sequelize.literal // Sequelize literal for 'score +' + value
17}
18
19export interface ServerInstance extends ServerClass, ServerAttributes, Sequelize.Instance<ServerAttributes> {
20 createdAt: Date
21 updatedAt: Date
22}
23
24export interface ServerModel extends ServerClass, Sequelize.Model<ServerInstance, ServerAttributes> {}
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index ebd216b08..edfd8010b 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -1,124 +1,109 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { isHostValid, logger } from '../../helpers' 2import { AllowNull, Column, CreatedAt, Default, Is, IsInt, Max, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { logger } from '../../helpers'
4import { isHostValid } from '../../helpers/custom-validators/servers'
3import { SERVERS_SCORE } from '../../initializers' 5import { SERVERS_SCORE } from '../../initializers'
4import { addMethodsToModel } from '../utils' 6import { throwIfNotValid } from '../utils'
5import { ServerAttributes, ServerInstance, ServerMethods } from './server-interface'
6 7
7let Server: Sequelize.Model<ServerInstance, ServerAttributes> 8@Table({
8let updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes 9 tableName: 'server',
9 10 indexes: [
10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
11 Server = sequelize.define<ServerInstance, ServerAttributes>('Server',
12 { 11 {
13 host: { 12 fields: [ 'host' ],
14 type: DataTypes.STRING, 13 unique: true
15 allowNull: false,
16 validate: {
17 isHost: value => {
18 const res = isHostValid(value)
19 if (res === false) throw new Error('Host not valid.')
20 }
21 }
22 },
23 score: {
24 type: DataTypes.INTEGER,
25 defaultValue: SERVERS_SCORE.BASE,
26 allowNull: false,
27 validate: {
28 isInt: true,
29 max: SERVERS_SCORE.MAX
30 }
31 }
32 }, 14 },
33 { 15 {
34 indexes: [ 16 fields: [ 'score' ]
35 {
36 fields: [ 'host' ],
37 unique: true
38 },
39 {
40 fields: [ 'score' ]
41 }
42 ]
43 } 17 }
44 )
45
46 const classMethods = [
47 updateServersScoreAndRemoveBadOnes
48 ] 18 ]
49 addMethodsToModel(Server, classMethods) 19})
50 20export class ServerModel extends Model<ServerModel> {
51 return Server 21
52} 22 @AllowNull(false)
53 23 @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host'))
54// ------------------------------ Statics ------------------------------ 24 @Column
55 25 host: string
56updateServersScoreAndRemoveBadOnes = function (goodServers: number[], badServers: number[]) { 26
57 logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length) 27 @AllowNull(false)
28 @Default(SERVERS_SCORE.BASE)
29 @IsInt
30 @Max(SERVERS_SCORE.MAX)
31 @Column
32 score: number
33
34 @CreatedAt
35 createdAt: Date
36
37 @UpdatedAt
38 updatedAt: Date
39
40 static updateServersScoreAndRemoveBadOnes (goodServers: number[], badServers: number[]) {
41 logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length)
42
43 if (goodServers.length !== 0) {
44 ServerModel.incrementScores(goodServers, SERVERS_SCORE.BONUS)
45 .catch(err => {
46 logger.error('Cannot increment scores of good servers.', err)
47 })
48 }
58 49
59 if (goodServers.length !== 0) { 50 if (badServers.length !== 0) {
60 incrementScores(goodServers, SERVERS_SCORE.BONUS).catch(err => { 51 ServerModel.incrementScores(badServers, SERVERS_SCORE.PENALTY)
61 logger.error('Cannot increment scores of good servers.', err) 52 .then(() => ServerModel.removeBadServers())
62 }) 53 .catch(err => {
63 } 54 if (err) logger.error('Cannot decrement scores of bad servers.', err)
55 })
64 56
65 if (badServers.length !== 0) { 57 }
66 incrementScores(badServers, SERVERS_SCORE.PENALTY)
67 .then(() => removeBadServers())
68 .catch(err => {
69 if (err) logger.error('Cannot decrement scores of bad servers.', err)
70 })
71 } 58 }
72}
73
74// ---------------------------------------------------------------------------
75 59
76// Remove servers with a score of 0 (too many requests where they were unreachable) 60 // Remove servers with a score of 0 (too many requests where they were unreachable)
77async function removeBadServers () { 61 private static async removeBadServers () {
78 try { 62 try {
79 const servers = await listBadServers() 63 const servers = await ServerModel.listBadServers()
80 64
81 const serversRemovePromises = servers.map(server => server.destroy()) 65 const serversRemovePromises = servers.map(server => server.destroy())
82 await Promise.all(serversRemovePromises) 66 await Promise.all(serversRemovePromises)
83 67
84 const numberOfServersRemoved = servers.length 68 const numberOfServersRemoved = servers.length
85 69
86 if (numberOfServersRemoved) { 70 if (numberOfServersRemoved) {
87 logger.info('Removed %d servers.', numberOfServersRemoved) 71 logger.info('Removed %d servers.', numberOfServersRemoved)
88 } else { 72 } else {
89 logger.info('No need to remove bad servers.') 73 logger.info('No need to remove bad servers.')
74 }
75 } catch (err) {
76 logger.error('Cannot remove bad servers.', err)
90 } 77 }
91 } catch (err) {
92 logger.error('Cannot remove bad servers.', err)
93 } 78 }
94}
95 79
96function incrementScores (ids: number[], value: number) { 80 private static incrementScores (ids: number[], value: number) {
97 const update = { 81 const update = {
98 score: Sequelize.literal('score +' + value) 82 score: Sequelize.literal('score +' + value)
99 } 83 }
100 84
101 const options = { 85 const options = {
102 where: { 86 where: {
103 id: { 87 id: {
104 [Sequelize.Op.in]: ids 88 [Sequelize.Op.in]: ids
105 } 89 }
106 }, 90 },
107 // In this case score is a literal and not an integer so we do not validate it 91 // In this case score is a literal and not an integer so we do not validate it
108 validate: false 92 validate: false
109 } 93 }
110 94
111 return Server.update(update, options) 95 return ServerModel.update(update, options)
112} 96 }
113 97
114function listBadServers () { 98 private static listBadServers () {
115 const query = { 99 const query = {
116 where: { 100 where: {
117 score: { 101 score: {
118 [Sequelize.Op.lte]: 0 102 [Sequelize.Op.lte]: 0
103 }
119 } 104 }
120 } 105 }
121 }
122 106
123 return Server.findAll(query) 107 return ServerModel.findAll(query)
108 }
124} 109}
diff --git a/server/models/utils.ts b/server/models/utils.ts
index 1bf61d2a6..1606453e0 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -14,22 +14,23 @@ function getSort (value: string) {
14 return [ field, direction ] 14 return [ field, direction ]
15} 15}
16 16
17function addMethodsToModel (model: any, classMethods: Function[], instanceMethods: Function[] = []) {
18 classMethods.forEach(m => model[m.name] = m)
19 instanceMethods.forEach(m => model.prototype[m.name] = m)
20}
21
22function getSortOnModel (model: any, value: string) { 17function getSortOnModel (model: any, value: string) {
23 let sort = getSort(value) 18 let sort = getSort(value)
24 19
25 if (model) return [ { model: model }, sort[0], sort[1] ] 20 if (model) return [ model, sort[0], sort[1] ]
26 return sort 21 return sort
27} 22}
28 23
24function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value') {
25 if (validator(value) === false) {
26 throw new Error(`"${value}" is not a valid ${fieldName}.`)
27 }
28}
29
29// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
30 31
31export { 32export {
32 addMethodsToModel,
33 getSort, 33 getSort,
34 getSortOnModel 34 getSortOnModel,
35 throwIfNotValid
35} 36}
diff --git a/server/models/video/index.ts b/server/models/video/index.ts
deleted file mode 100644
index e17bbfab4..000000000
--- a/server/models/video/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
1export * from './tag-interface'
2export * from './video-abuse-interface'
3export * from './video-blacklist-interface'
4export * from './video-channel-interface'
5export * from './video-tag-interface'
6export * from './video-file-interface'
7export * from './video-interface'
8export * from './video-share-interface'
9export * from './video-channel-share-interface'
diff --git a/server/models/video/tag-interface.ts b/server/models/video/tag-interface.ts
deleted file mode 100644
index 08e5c3246..000000000
--- a/server/models/video/tag-interface.ts
+++ /dev/null
@@ -1,20 +0,0 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird'
3
4export namespace TagMethods {
5 export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction) => Promise<TagInstance[]>
6}
7
8export interface TagClass {
9 findOrCreateTags: TagMethods.FindOrCreateTags
10}
11
12export interface TagAttributes {
13 name: string
14}
15
16export interface TagInstance extends TagClass, TagAttributes, Sequelize.Instance<TagAttributes> {
17 id: number
18}
19
20export interface TagModel extends TagClass, Sequelize.Model<TagInstance, TagAttributes> {}
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index 0c0757fc8..0ae74d808 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -1,73 +1,60 @@
1import * as Sequelize from 'sequelize' 1import * as Bluebird from 'bluebird'
2import * as Promise from 'bluebird' 2import { Transaction } from 'sequelize'
3 3import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { addMethodsToModel } from '../utils' 4import { isVideoTagValid } from '../../helpers/custom-validators/videos'
5import { 5import { throwIfNotValid } from '../utils'
6 TagInstance, 6import { VideoModel } from './video'
7 TagAttributes, 7import { VideoTagModel } from './video-tag'
8 8
9 TagMethods 9@Table({
10} from './tag-interface' 10 tableName: 'tag',
11 11 timestamps: false,
12let Tag: Sequelize.Model<TagInstance, TagAttributes> 12 indexes: [
13let findOrCreateTags: TagMethods.FindOrCreateTags
14
15export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
16 Tag = sequelize.define<TagInstance, TagAttributes>('Tag',
17 { 13 {
18 name: { 14 fields: [ 'name' ],
19 type: DataTypes.STRING, 15 unique: true
20 allowNull: false
21 }
22 },
23 {
24 timestamps: false,
25 indexes: [
26 {
27 fields: [ 'name' ],
28 unique: true
29 }
30 ]
31 } 16 }
32 )
33
34 const classMethods = [
35 associate,
36
37 findOrCreateTags
38 ] 17 ]
39 addMethodsToModel(Tag, classMethods) 18})
19export class TagModel extends Model<TagModel> {
40 20
41 return Tag 21 @AllowNull(false)
42} 22 @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag'))
23 @Column
24 name: string
43 25
44// --------------------------------------------------------------------------- 26 @CreatedAt
27 createdAt: Date
45 28
46function associate (models) { 29 @UpdatedAt
47 Tag.belongsToMany(models.Video, { 30 updatedAt: Date
31
32 @BelongsToMany(() => VideoModel, {
48 foreignKey: 'tagId', 33 foreignKey: 'tagId',
49 through: models.VideoTag, 34 through: () => VideoTagModel,
50 onDelete: 'CASCADE' 35 onDelete: 'CASCADE'
51 }) 36 })
52} 37 Videos: VideoModel[]
53 38
54findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction) { 39 static findOrCreateTags (tags: string[], transaction: Transaction) {
55 const tasks: Promise<TagInstance>[] = [] 40 const tasks: Bluebird<TagModel>[] = []
56 tags.forEach(tag => { 41 tags.forEach(tag => {
57 const query: Sequelize.FindOrInitializeOptions<TagAttributes> = { 42 const query = {
58 where: { 43 where: {
59 name: tag 44 name: tag
60 }, 45 },
61 defaults: { 46 defaults: {
62 name: tag 47 name: tag
48 }
63 } 49 }
64 }
65 50
66 if (transaction) query.transaction = transaction 51 if (transaction) query['transaction'] = transaction
67 52
68 const promise = Tag.findOrCreate(query).then(([ tagInstance ]) => tagInstance) 53 const promise = TagModel.findOrCreate(query)
69 tasks.push(promise) 54 .then(([ tagInstance ]) => tagInstance)
70 }) 55 tasks.push(promise)
56 })
71 57
72 return Promise.all(tasks) 58 return Promise.all(tasks)
59 }
73} 60}
diff --git a/server/models/video/video-abuse-interface.ts b/server/models/video/video-abuse-interface.ts
deleted file mode 100644
index feafc4a19..000000000
--- a/server/models/video/video-abuse-interface.ts
+++ /dev/null
@@ -1,41 +0,0 @@
1import * as Promise from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { ResultList } from '../../../shared'
4import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model'
5import { AccountInstance } from '../account/account-interface'
6import { ServerInstance } from '../server/server-interface'
7import { VideoInstance } from './video-interface'
8import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
9
10export namespace VideoAbuseMethods {
11 export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse
12
13 export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoAbuseInstance> >
14 export type ToActivityPubObject = () => VideoAbuseObject
15}
16
17export interface VideoAbuseClass {
18 listForApi: VideoAbuseMethods.ListForApi
19 toActivityPubObject: VideoAbuseMethods.ToActivityPubObject
20}
21
22export interface VideoAbuseAttributes {
23 reason: string
24 videoId: number
25 reporterAccountId: number
26
27 Account?: AccountInstance
28 Video?: VideoInstance
29}
30
31export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
32 id: number
33 createdAt: Date
34 updatedAt: Date
35
36 Server: ServerInstance
37
38 toFormattedJSON: VideoAbuseMethods.ToFormattedJSON
39}
40
41export interface VideoAbuseModel extends VideoAbuseClass, Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes> {}
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index d09f5f7a1..d0ee969fb 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -1,142 +1,116 @@
1import * as Sequelize from 'sequelize' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2 2import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
3import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos'
3import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers'
4import { isVideoAbuseReasonValid } from '../../helpers' 5import { AccountModel } from '../account/account'
5 6import { ServerModel } from '../server/server'
6import { addMethodsToModel, getSort } from '../utils' 7import { getSort, throwIfNotValid } from '../utils'
7import { 8import { VideoModel } from './video'
8 VideoAbuseInstance, 9
9 VideoAbuseAttributes, 10@Table({
10 11 tableName: 'videoAbuse',
11 VideoAbuseMethods 12 indexes: [
12} from './video-abuse-interface'
13import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object'
14
15let VideoAbuse: Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes>
16let toFormattedJSON: VideoAbuseMethods.ToFormattedJSON
17let listForApi: VideoAbuseMethods.ListForApi
18let toActivityPubObject: VideoAbuseMethods.ToActivityPubObject
19
20export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
21 VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse',
22 { 13 {
23 reason: { 14 fields: [ 'videoId' ]
24 type: DataTypes.STRING,
25 allowNull: false,
26 validate: {
27 reasonValid: value => {
28 const res = isVideoAbuseReasonValid(value)
29 if (res === false) throw new Error('Video abuse reason is not valid.')
30 }
31 }
32 }
33 }, 15 },
34 { 16 {
35 indexes: [ 17 fields: [ 'reporterAccountId' ]
36 {
37 fields: [ 'videoId' ]
38 },
39 {
40 fields: [ 'reporterAccountId' ]
41 }
42 ]
43 } 18 }
44 )
45
46 const classMethods = [
47 associate,
48
49 listForApi
50 ]
51 const instanceMethods = [
52 toFormattedJSON,
53 toActivityPubObject
54 ] 19 ]
55 addMethodsToModel(VideoAbuse, classMethods, instanceMethods) 20})
56 21export class VideoAbuseModel extends Model<VideoAbuseModel> {
57 return VideoAbuse
58}
59
60// ------------------------------ METHODS ------------------------------
61
62toFormattedJSON = function (this: VideoAbuseInstance) {
63 let reporterServerHost
64
65 if (this.Account.Server) {
66 reporterServerHost = this.Account.Server.host
67 } else {
68 // It means it's our video
69 reporterServerHost = CONFIG.WEBSERVER.HOST
70 }
71
72 const json = {
73 id: this.id,
74 reason: this.reason,
75 reporterUsername: this.Account.name,
76 reporterServerHost,
77 videoId: this.Video.id,
78 videoUUID: this.Video.uuid,
79 videoName: this.Video.name,
80 createdAt: this.createdAt
81 }
82 22
83 return json 23 @AllowNull(false)
84} 24 @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason'))
25 @Column
26 reason: string
85 27
86toActivityPubObject = function (this: VideoAbuseInstance) { 28 @CreatedAt
87 const videoAbuseObject: VideoAbuseObject = { 29 createdAt: Date
88 type: 'Flag' as 'Flag',
89 content: this.reason,
90 object: this.Video.url
91 }
92 30
93 return videoAbuseObject 31 @UpdatedAt
94} 32 updatedAt: Date
95 33
96// ------------------------------ STATICS ------------------------------ 34 @ForeignKey(() => AccountModel)
35 @Column
36 reporterAccountId: number
97 37
98function associate (models) { 38 @BelongsTo(() => AccountModel, {
99 VideoAbuse.belongsTo(models.Account, {
100 foreignKey: { 39 foreignKey: {
101 name: 'reporterAccountId',
102 allowNull: false 40 allowNull: false
103 }, 41 },
104 onDelete: 'CASCADE' 42 onDelete: 'cascade'
105 }) 43 })
44 Account: AccountModel
45
46 @ForeignKey(() => VideoModel)
47 @Column
48 videoId: number
106 49
107 VideoAbuse.belongsTo(models.Video, { 50 @BelongsTo(() => VideoModel, {
108 foreignKey: { 51 foreignKey: {
109 name: 'videoId',
110 allowNull: false 52 allowNull: false
111 }, 53 },
112 onDelete: 'CASCADE' 54 onDelete: 'cascade'
113 }) 55 })
114} 56 Video: VideoModel
57
58 static listForApi (start: number, count: number, sort: string) {
59 const query = {
60 offset: start,
61 limit: count,
62 order: [ getSort(sort) ],
63 include: [
64 {
65 model: AccountModel,
66 required: true,
67 include: [
68 {
69 model: ServerModel,
70 required: false
71 }
72 ]
73 },
74 {
75 model: VideoModel,
76 required: true
77 }
78 ]
79 }
115 80
116listForApi = function (start: number, count: number, sort: string) { 81 return VideoAbuseModel.findAndCountAll(query)
117 const query = { 82 .then(({ rows, count }) => {
118 offset: start, 83 return { total: count, data: rows }
119 limit: count, 84 })
120 order: [ getSort(sort) ],
121 include: [
122 {
123 model: VideoAbuse['sequelize'].models.Account,
124 required: true,
125 include: [
126 {
127 model: VideoAbuse['sequelize'].models.Server,
128 required: false
129 }
130 ]
131 },
132 {
133 model: VideoAbuse['sequelize'].models.Video,
134 required: true
135 }
136 ]
137 } 85 }
138 86
139 return VideoAbuse.findAndCountAll(query).then(({ rows, count }) => { 87 toFormattedJSON () {
140 return { total: count, data: rows } 88 let reporterServerHost
141 }) 89
90 if (this.Account.Server) {
91 reporterServerHost = this.Account.Server.host
92 } else {
93 // It means it's our video
94 reporterServerHost = CONFIG.WEBSERVER.HOST
95 }
96
97 return {
98 id: this.id,
99 reason: this.reason,
100 reporterUsername: this.Account.name,
101 reporterServerHost,
102 videoId: this.Video.id,
103 videoUUID: this.Video.uuid,
104 videoName: this.Video.name,
105 createdAt: this.createdAt
106 }
107 }
108
109 toActivityPubObject (): VideoAbuseObject {
110 return {
111 type: 'Flag' as 'Flag',
112 content: this.reason,
113 object: this.Video.url
114 }
115 }
142} 116}
diff --git a/server/models/video/video-blacklist-interface.ts b/server/models/video/video-blacklist-interface.ts
deleted file mode 100644
index be2483d4c..000000000
--- a/server/models/video/video-blacklist-interface.ts
+++ /dev/null
@@ -1,39 +0,0 @@
1import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird'
3
4import { SortType } from '../../helpers'
5import { ResultList } from '../../../shared'
6import { VideoInstance } from './video-interface'
7
8// Don't use barrel, import just what we need
9import { BlacklistedVideo as FormattedBlacklistedVideo } from '../../../shared/models/videos/video-blacklist.model'
10
11export namespace BlacklistedVideoMethods {
12 export type ToFormattedJSON = (this: BlacklistedVideoInstance) => FormattedBlacklistedVideo
13 export type ListForApi = (start: number, count: number, sort: SortType) => Promise< ResultList<BlacklistedVideoInstance> >
14 export type LoadByVideoId = (id: number) => Promise<BlacklistedVideoInstance>
15}
16
17export interface BlacklistedVideoClass {
18 toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON
19 listForApi: BlacklistedVideoMethods.ListForApi
20 loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
21}
22
23export interface BlacklistedVideoAttributes {
24 videoId: number
25
26 Video?: VideoInstance
27}
28
29export interface BlacklistedVideoInstance
30 extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
31 id: number
32 createdAt: Date
33 updatedAt: Date
34
35 toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON
36}
37
38export interface BlacklistedVideoModel
39 extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index ae8286285..6db562719 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,104 +1,80 @@
1import * as Sequelize from 'sequelize' 1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2
3import { SortType } from '../../helpers' 2import { SortType } from '../../helpers'
4import { addMethodsToModel, getSortOnModel } from '../utils' 3import { getSortOnModel } from '../utils'
5import { VideoInstance } from './video-interface' 4import { VideoModel } from './video'
6import {
7 BlacklistedVideoInstance,
8 BlacklistedVideoAttributes,
9
10 BlacklistedVideoMethods
11} from './video-blacklist-interface'
12
13let BlacklistedVideo: Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes>
14let toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON
15let listForApi: BlacklistedVideoMethods.ListForApi
16let loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
17 5
18export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 6@Table({
19 BlacklistedVideo = sequelize.define<BlacklistedVideoInstance, BlacklistedVideoAttributes>('BlacklistedVideo', 7 tableName: 'videoBlacklist',
20 {}, 8 indexes: [
21 { 9 {
22 indexes: [ 10 fields: [ 'videoId' ],
23 { 11 unique: true
24 fields: [ 'videoId' ],
25 unique: true
26 }
27 ]
28 } 12 }
29 )
30
31 const classMethods = [
32 associate,
33
34 listForApi,
35 loadByVideoId
36 ] 13 ]
37 const instanceMethods = [ 14})
38 toFormattedJSON 15export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
39 ]
40 addMethodsToModel(BlacklistedVideo, classMethods, instanceMethods)
41
42 return BlacklistedVideo
43}
44 16
45// ------------------------------ METHODS ------------------------------ 17 @CreatedAt
18 createdAt: Date
46 19
47toFormattedJSON = function (this: BlacklistedVideoInstance) { 20 @UpdatedAt
48 let video: VideoInstance 21 updatedAt: Date
49
50 video = this.Video
51
52 return {
53 id: this.id,
54 videoId: this.videoId,
55 createdAt: this.createdAt,
56 updatedAt: this.updatedAt,
57 name: video.name,
58 uuid: video.uuid,
59 description: video.description,
60 duration: video.duration,
61 views: video.views,
62 likes: video.likes,
63 dislikes: video.dislikes,
64 nsfw: video.nsfw
65 }
66}
67 22
68// ------------------------------ STATICS ------------------------------ 23 @ForeignKey(() => VideoModel)
24 @Column
25 videoId: number
69 26
70function associate (models) { 27 @BelongsTo(() => VideoModel, {
71 BlacklistedVideo.belongsTo(models.Video, {
72 foreignKey: { 28 foreignKey: {
73 name: 'videoId',
74 allowNull: false 29 allowNull: false
75 }, 30 },
76 onDelete: 'CASCADE' 31 onDelete: 'cascade'
77 }) 32 })
78} 33 Video: VideoModel
34
35 static listForApi (start: number, count: number, sort: SortType) {
36 const query = {
37 offset: start,
38 limit: count,
39 order: [ getSortOnModel(sort.sortModel, sort.sortValue) ],
40 include: [ { model: VideoModel } ]
41 }
79 42
80listForApi = function (start: number, count: number, sort: SortType) { 43 return VideoBlacklistModel.findAndCountAll(query)
81 const query = { 44 .then(({ rows, count }) => {
82 offset: start, 45 return {
83 limit: count, 46 data: rows,
84 order: [ getSortOnModel(sort.sortModel, sort.sortValue) ], 47 total: count
85 include: [ { model: BlacklistedVideo['sequelize'].models.Video } ] 48 }
49 })
86 } 50 }
87 51
88 return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => { 52 static loadByVideoId (id: number) {
89 return { 53 const query = {
90 data: rows, 54 where: {
91 total: count 55 videoId: id
56 }
92 } 57 }
93 })
94}
95 58
96loadByVideoId = function (id: number) { 59 return VideoBlacklistModel.findOne(query)
97 const query = {
98 where: {
99 videoId: id
100 }
101 } 60 }
102 61
103 return BlacklistedVideo.findOne(query) 62 toFormattedJSON () {
63 const video = this.Video
64
65 return {
66 id: this.id,
67 videoId: this.videoId,
68 createdAt: this.createdAt,
69 updatedAt: this.updatedAt,
70 name: video.name,
71 uuid: video.uuid,
72 description: video.description,
73 duration: video.duration,
74 views: video.views,
75 likes: video.likes,
76 dislikes: video.dislikes,
77 nsfw: video.nsfw
78 }
79 }
104} 80}
diff --git a/server/models/video/video-channel-interface.ts b/server/models/video/video-channel-interface.ts
deleted file mode 100644
index 21f81e901..000000000
--- a/server/models/video/video-channel-interface.ts
+++ /dev/null
@@ -1,64 +0,0 @@
1import * as Promise from 'bluebird'
2import * as Sequelize from 'sequelize'
3
4import { ResultList } from '../../../shared'
5import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object'
6import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model'
7import { AccountInstance } from '../account/account-interface'
8import { VideoInstance } from './video-interface'
9import { VideoChannelShareInstance } from './video-channel-share-interface'
10
11export namespace VideoChannelMethods {
12 export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel
13 export type ToActivityPubObject = (this: VideoChannelInstance) => VideoChannelObject
14 export type IsOwned = (this: VideoChannelInstance) => boolean
15
16 export type CountByAccount = (accountId: number) => Promise<number>
17 export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> >
18 export type LoadByIdAndAccount = (id: number, accountId: number) => Promise<VideoChannelInstance>
19 export type ListByAccount = (accountId: number) => Promise< ResultList<VideoChannelInstance> >
20 export type LoadAndPopulateAccount = (id: number) => Promise<VideoChannelInstance>
21 export type LoadByUUIDAndPopulateAccount = (uuid: string) => Promise<VideoChannelInstance>
22 export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
23 export type LoadByHostAndUUID = (uuid: string, serverHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
24 export type LoadAndPopulateAccountAndVideos = (id: number) => Promise<VideoChannelInstance>
25 export type LoadByUrl = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
26 export type LoadByUUIDOrUrl = (uuid: string, url: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
27}
28
29export interface VideoChannelClass {
30 countByAccount: VideoChannelMethods.CountByAccount
31 listForApi: VideoChannelMethods.ListForApi
32 listByAccount: VideoChannelMethods.ListByAccount
33 loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount
34 loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount
35 loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount
36 loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos
37 loadByUrl: VideoChannelMethods.LoadByUrl
38 loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl
39}
40
41export interface VideoChannelAttributes {
42 id?: number
43 uuid?: string
44 name: string
45 description: string
46 remote: boolean
47 url?: string
48
49 Account?: AccountInstance
50 Videos?: VideoInstance[]
51 VideoChannelShares?: VideoChannelShareInstance[]
52}
53
54export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance<VideoChannelAttributes> {
55 id: number
56 createdAt: Date
57 updatedAt: Date
58
59 isOwned: VideoChannelMethods.IsOwned
60 toFormattedJSON: VideoChannelMethods.ToFormattedJSON
61 toActivityPubObject: VideoChannelMethods.ToActivityPubObject
62}
63
64export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {}
diff --git a/server/models/video/video-channel-share-interface.ts b/server/models/video/video-channel-share-interface.ts
deleted file mode 100644
index 2fff41a1b..000000000
--- a/server/models/video/video-channel-share-interface.ts
+++ /dev/null
@@ -1,32 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { AccountInstance } from '../account/account-interface'
4import { VideoChannelInstance } from './video-channel-interface'
5
6export namespace VideoChannelShareMethods {
7 export type LoadAccountsByShare = (videoChannelId: number, t: Sequelize.Transaction) => Bluebird<AccountInstance[]>
8 export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird<VideoChannelShareInstance>
9}
10
11export interface VideoChannelShareClass {
12 loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
13 load: VideoChannelShareMethods.Load
14}
15
16export interface VideoChannelShareAttributes {
17 accountId: number
18 videoChannelId: number
19}
20
21export interface VideoChannelShareInstance
22 extends VideoChannelShareClass, VideoChannelShareAttributes, Sequelize.Instance<VideoChannelShareAttributes> {
23 id: number
24 createdAt: Date
25 updatedAt: Date
26
27 Account?: AccountInstance
28 VideoChannel?: VideoChannelInstance
29}
30
31export interface VideoChannelShareModel
32 extends VideoChannelShareClass, Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> {}
diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts
index 2e9b658a3..cdba32fcd 100644
--- a/server/models/video/video-channel-share.ts
+++ b/server/models/video/video-channel-share.ts
@@ -1,85 +1,79 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account'
4import { VideoChannelModel } from './video-channel'
2 5
3import { addMethodsToModel } from '../utils' 6@Table({
4import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelShareMethods } from './video-channel-share-interface' 7 tableName: 'videoChannelShare',
5 8 indexes: [
6let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes>
7let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
8let load: VideoChannelShareMethods.Load
9
10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
11 VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('VideoChannelShare',
12 { },
13 { 9 {
14 indexes: [ 10 fields: [ 'accountId' ]
15 { 11 },
16 fields: [ 'accountId' ] 12 {
17 }, 13 fields: [ 'videoChannelId' ]
18 {
19 fields: [ 'videoChannelId' ]
20 }
21 ]
22 } 14 }
23 )
24
25 const classMethods = [
26 associate,
27 load,
28 loadAccountsByShare
29 ] 15 ]
30 addMethodsToModel(VideoChannelShare, classMethods) 16})
17export class VideoChannelShareModel extends Model<VideoChannelShareModel> {
18 @CreatedAt
19 createdAt: Date
31 20
32 return VideoChannelShare 21 @UpdatedAt
33} 22 updatedAt: Date
34 23
35// ------------------------------ METHODS ------------------------------ 24 @ForeignKey(() => AccountModel)
25 @Column
26 accountId: number
36 27
37function associate (models) { 28 @BelongsTo(() => AccountModel, {
38 VideoChannelShare.belongsTo(models.Account, {
39 foreignKey: { 29 foreignKey: {
40 name: 'accountId',
41 allowNull: false 30 allowNull: false
42 }, 31 },
43 onDelete: 'cascade' 32 onDelete: 'cascade'
44 }) 33 })
34 Account: AccountModel
45 35
46 VideoChannelShare.belongsTo(models.VideoChannel, { 36 @ForeignKey(() => VideoChannelModel)
37 @Column
38 videoChannelId: number
39
40 @BelongsTo(() => VideoChannelModel, {
47 foreignKey: { 41 foreignKey: {
48 name: 'videoChannelId', 42 allowNull: false
49 allowNull: true
50 }, 43 },
51 onDelete: 'cascade' 44 onDelete: 'cascade'
52 }) 45 })
53} 46 VideoChannel: VideoChannelModel
54
55load = function (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
56 return VideoChannelShare.findOne({
57 where: {
58 accountId,
59 videoChannelId
60 },
61 include: [
62 VideoChannelShare['sequelize'].models.Account,
63 VideoChannelShare['sequelize'].models.VideoChannel
64 ],
65 transaction: t
66 })
67}
68 47
69loadAccountsByShare = function (videoChannelId: number, t: Sequelize.Transaction) { 48 static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
70 const query = { 49 return VideoChannelShareModel.findOne({
71 where: { 50 where: {
72 videoChannelId 51 accountId,
73 }, 52 videoChannelId
74 include: [ 53 },
75 { 54 include: [
76 model: VideoChannelShare['sequelize'].models.Account, 55 AccountModel,
77 required: true 56 VideoChannelModel
78 } 57 ],
79 ], 58 transaction: t
80 transaction: t 59 })
81 } 60 }
82 61
83 return VideoChannelShare.findAll(query) 62 static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) {
84 .then(res => res.map(r => r.Account)) 63 const query = {
64 where: {
65 videoChannelId
66 },
67 include: [
68 {
69 model: AccountModel,
70 required: true
71 }
72 ],
73 transaction: t
74 }
75
76 return VideoChannelShareModel.findAll(query)
77 .then(res => res.map(r => r.Account))
78 }
85} 79}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 54f12dce3..9b545a4ef 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -1,371 +1,341 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers' 2import {
3import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 3 AfterDestroy,
4import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete' 4 AllowNull,
5 5 BelongsTo,
6import { addMethodsToModel, getSort } from '../utils' 6 Column,
7import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface' 7 CreatedAt,
8import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' 8 DataType,
9import { activityPubCollection } from '../../helpers/activitypub' 9 Default,
10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 10 ForeignKey,
11 11 HasMany,
12let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> 12 Is,
13let toFormattedJSON: VideoChannelMethods.ToFormattedJSON 13 IsUUID,
14let toActivityPubObject: VideoChannelMethods.ToActivityPubObject 14 Model,
15let isOwned: VideoChannelMethods.IsOwned 15 Table,
16let countByAccount: VideoChannelMethods.CountByAccount 16 UpdatedAt
17let listForApi: VideoChannelMethods.ListForApi 17} from 'sequelize-typescript'
18let listByAccount: VideoChannelMethods.ListByAccount 18import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
19let loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount 19import { activityPubCollection } from '../../helpers'
20let loadByUUID: VideoChannelMethods.LoadByUUID 20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
21let loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount 21import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
22let loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount 22import { CONSTRAINTS_FIELDS } from '../../initializers'
23let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID 23import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
24let loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos 24import { sendDeleteVideoChannel } from '../../lib/activitypub/send'
25let loadByUrl: VideoChannelMethods.LoadByUrl 25import { AccountModel } from '../account/account'
26let loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl 26import { ServerModel } from '../server/server'
27 27import { getSort, throwIfNotValid } from '../utils'
28export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 28import { VideoModel } from './video'
29 VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel', 29import { VideoChannelShareModel } from './video-channel-share'
30
31@Table({
32 tableName: 'videoChannel',
33 indexes: [
30 { 34 {
31 uuid: { 35 fields: [ 'accountId' ]
32 type: DataTypes.UUID,
33 defaultValue: DataTypes.UUIDV4,
34 allowNull: false,
35 validate: {
36 isUUID: 4
37 }
38 },
39 name: {
40 type: DataTypes.STRING,
41 allowNull: false,
42 validate: {
43 nameValid: value => {
44 const res = isVideoChannelNameValid(value)
45 if (res === false) throw new Error('Video channel name is not valid.')
46 }
47 }
48 },
49 description: {
50 type: DataTypes.STRING,
51 allowNull: true,
52 validate: {
53 descriptionValid: value => {
54 const res = isVideoChannelDescriptionValid(value)
55 if (res === false) throw new Error('Video channel description is not valid.')
56 }
57 }
58 },
59 remote: {
60 type: DataTypes.BOOLEAN,
61 allowNull: false,
62 defaultValue: false
63 },
64 url: {
65 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max),
66 allowNull: false,
67 validate: {
68 urlValid: value => {
69 const res = isActivityPubUrlValid(value)
70 if (res === false) throw new Error('Video channel URL is not valid.')
71 }
72 }
73 }
74 },
75 {
76 indexes: [
77 {
78 fields: [ 'accountId' ]
79 }
80 ],
81 hooks: {
82 afterDestroy
83 }
84 } 36 }
85 )
86
87 const classMethods = [
88 associate,
89
90 listForApi,
91 listByAccount,
92 loadByIdAndAccount,
93 loadAndPopulateAccount,
94 loadByUUIDAndPopulateAccount,
95 loadByUUID,
96 loadByHostAndUUID,
97 loadAndPopulateAccountAndVideos,
98 countByAccount,
99 loadByUrl,
100 loadByUUIDOrUrl
101 ] 37 ]
102 const instanceMethods = [ 38})
103 isOwned, 39export class VideoChannelModel extends Model<VideoChannelModel> {
104 toFormattedJSON,
105 toActivityPubObject
106 ]
107 addMethodsToModel(VideoChannel, classMethods, instanceMethods)
108 40
109 return VideoChannel 41 @AllowNull(false)
110} 42 @Default(DataType.UUIDV4)
43 @IsUUID(4)
44 @Column(DataType.UUID)
45 uuid: string
111 46
112// ------------------------------ METHODS ------------------------------ 47 @AllowNull(false)
48 @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name'))
49 @Column
50 name: string
113 51
114isOwned = function (this: VideoChannelInstance) { 52 @AllowNull(true)
115 return this.remote === false 53 @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description'))
116} 54 @Column
117 55 description: string
118toFormattedJSON = function (this: VideoChannelInstance) {
119 const json = {
120 id: this.id,
121 uuid: this.uuid,
122 name: this.name,
123 description: this.description,
124 isLocal: this.isOwned(),
125 createdAt: this.createdAt,
126 updatedAt: this.updatedAt
127 }
128 56
129 if (this.Account !== undefined) { 57 @AllowNull(false)
130 json['owner'] = { 58 @Column
131 name: this.Account.name, 59 remote: boolean
132 uuid: this.Account.uuid
133 }
134 }
135 60
136 if (Array.isArray(this.Videos)) { 61 @AllowNull(false)
137 json['videos'] = this.Videos.map(v => v.toFormattedJSON()) 62 @Is('VideoChannelUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
138 } 63 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max))
64 url: string
139 65
140 return json 66 @CreatedAt
141} 67 createdAt: Date
142 68
143toActivityPubObject = function (this: VideoChannelInstance) { 69 @UpdatedAt
144 let sharesObject 70 updatedAt: Date
145 if (Array.isArray(this.VideoChannelShares)) {
146 const shares: string[] = []
147 71
148 for (const videoChannelShare of this.VideoChannelShares) { 72 @ForeignKey(() => AccountModel)
149 const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account) 73 @Column
150 shares.push(shareUrl) 74 accountId: number
151 }
152 75
153 sharesObject = activityPubCollection(shares) 76 @BelongsTo(() => AccountModel, {
154 } 77 foreignKey: {
155 78 allowNull: false
156 const json = { 79 },
157 type: 'VideoChannel' as 'VideoChannel', 80 onDelete: 'CASCADE'
158 id: this.url, 81 })
159 uuid: this.uuid, 82 Account: AccountModel
160 content: this.description,
161 name: this.name,
162 published: this.createdAt.toISOString(),
163 updated: this.updatedAt.toISOString(),
164 shares: sharesObject
165 }
166
167 return json
168}
169
170// ------------------------------ STATICS ------------------------------
171 83
172function associate (models) { 84 @HasMany(() => VideoModel, {
173 VideoChannel.belongsTo(models.Account, {
174 foreignKey: { 85 foreignKey: {
175 name: 'accountId', 86 name: 'channelId',
176 allowNull: false 87 allowNull: false
177 }, 88 },
178 onDelete: 'CASCADE' 89 onDelete: 'CASCADE'
179 }) 90 })
91 Videos: VideoModel[]
180 92
181 VideoChannel.hasMany(models.Video, { 93 @HasMany(() => VideoChannelShareModel, {
182 foreignKey: { 94 foreignKey: {
183 name: 'channelId', 95 name: 'channelId',
184 allowNull: false 96 allowNull: false
185 }, 97 },
186 onDelete: 'CASCADE' 98 onDelete: 'CASCADE'
187 }) 99 })
188} 100 VideoChannelShares: VideoChannelShareModel[]
189 101
190function afterDestroy (videoChannel: VideoChannelInstance) { 102 @AfterDestroy
191 if (videoChannel.isOwned()) { 103 static sendDeleteIfOwned (instance: VideoChannelModel) {
192 return sendDeleteVideoChannel(videoChannel, undefined) 104 if (instance.isOwned()) {
193 } 105 return sendDeleteVideoChannel(instance, undefined)
106 }
194 107
195 return undefined 108 return undefined
196} 109 }
197 110
198countByAccount = function (accountId: number) { 111 static countByAccount (accountId: number) {
199 const query = { 112 const query = {
200 where: { 113 where: {
201 accountId 114 accountId
115 }
202 } 116 }
117
118 return VideoChannelModel.count(query)
203 } 119 }
204 120
205 return VideoChannel.count(query) 121 static listForApi (start: number, count: number, sort: string) {
206} 122 const query = {
123 offset: start,
124 limit: count,
125 order: [ getSort(sort) ],
126 include: [
127 {
128 model: AccountModel,
129 required: true,
130 include: [ { model: ServerModel, required: false } ]
131 }
132 ]
133 }
207 134
208listForApi = function (start: number, count: number, sort: string) { 135 return VideoChannelModel.findAndCountAll(query)
209 const query = { 136 .then(({ rows, count }) => {
210 offset: start, 137 return { total: count, data: rows }
211 limit: count, 138 })
212 order: [ getSort(sort) ],
213 include: [
214 {
215 model: VideoChannel['sequelize'].models.Account,
216 required: true,
217 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
218 }
219 ]
220 } 139 }
221 140
222 return VideoChannel.findAndCountAll(query).then(({ rows, count }) => { 141 static listByAccount (accountId: number) {
223 return { total: count, data: rows } 142 const query = {
224 }) 143 order: [ getSort('createdAt') ],
225} 144 include: [
145 {
146 model: AccountModel,
147 where: {
148 id: accountId
149 },
150 required: true,
151 include: [ { model: ServerModel, required: false } ]
152 }
153 ]
154 }
226 155
227listByAccount = function (accountId: number) { 156 return VideoChannelModel.findAndCountAll(query)
228 const query = { 157 .then(({ rows, count }) => {
229 order: [ getSort('createdAt') ], 158 return { total: count, data: rows }
230 include: [ 159 })
231 {
232 model: VideoChannel['sequelize'].models.Account,
233 where: {
234 id: accountId
235 },
236 required: true,
237 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
238 }
239 ]
240 } 160 }
241 161
242 return VideoChannel.findAndCountAll(query).then(({ rows, count }) => { 162 static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
243 return { total: count, data: rows } 163 const query: IFindOptions<VideoChannelModel> = {
244 }) 164 where: {
245} 165 uuid
166 }
167 }
168
169 if (t !== undefined) query.transaction = t
246 170
247loadByUUID = function (uuid: string, t?: Sequelize.Transaction) { 171 return VideoChannelModel.findOne(query)
248 const query: Sequelize.FindOptions<VideoChannelAttributes> = { 172 }
249 where: { 173
250 uuid 174 static loadByUrl (url: string, t?: Sequelize.Transaction) {
175 const query: IFindOptions<VideoChannelModel> = {
176 where: {
177 url
178 },
179 include: [ AccountModel ]
251 } 180 }
181
182 if (t !== undefined) query.transaction = t
183
184 return VideoChannelModel.findOne(query)
252 } 185 }
253 186
254 if (t !== undefined) query.transaction = t 187 static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) {
188 const query: IFindOptions<VideoChannelModel> = {
189 where: {
190 [ Sequelize.Op.or ]: [
191 { uuid },
192 { url }
193 ]
194 }
195 }
255 196
256 return VideoChannel.findOne(query) 197 if (t !== undefined) query.transaction = t
257}
258 198
259loadByUrl = function (url: string, t?: Sequelize.Transaction) { 199 return VideoChannelModel.findOne(query)
260 const query: Sequelize.FindOptions<VideoChannelAttributes> = {
261 where: {
262 url
263 },
264 include: [ VideoChannel['sequelize'].models.Account ]
265 } 200 }
266 201
267 if (t !== undefined) query.transaction = t 202 static loadByHostAndUUID (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
203 const query: IFindOptions<VideoChannelModel> = {
204 where: {
205 uuid
206 },
207 include: [
208 {
209 model: AccountModel,
210 include: [
211 {
212 model: ServerModel,
213 required: true,
214 where: {
215 host: fromHost
216 }
217 }
218 ]
219 }
220 ]
221 }
268 222
269 return VideoChannel.findOne(query) 223 if (t !== undefined) query.transaction = t
270} 224
225 return VideoChannelModel.findOne(query)
226 }
271 227
272loadByUUIDOrUrl = function (uuid: string, url: string, t?: Sequelize.Transaction) { 228 static loadByIdAndAccount (id: number, accountId: number) {
273 const query: Sequelize.FindOptions<VideoChannelAttributes> = { 229 const options = {
274 where: { 230 where: {
275 [Sequelize.Op.or]: [ 231 id,
276 { uuid }, 232 accountId
277 { url } 233 },
234 include: [
235 {
236 model: AccountModel,
237 include: [ { model: ServerModel, required: false } ]
238 }
278 ] 239 ]
279 } 240 }
241
242 return VideoChannelModel.findOne(options)
280 } 243 }
281 244
282 if (t !== undefined) query.transaction = t 245 static loadAndPopulateAccount (id: number) {
246 const options = {
247 include: [
248 {
249 model: AccountModel,
250 include: [ { model: ServerModel, required: false } ]
251 }
252 ]
253 }
283 254
284 return VideoChannel.findOne(query) 255 return VideoChannelModel.findById(id, options)
285} 256 }
286 257
287loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) { 258 static loadByUUIDAndPopulateAccount (uuid: string) {
288 const query: Sequelize.FindOptions<VideoChannelAttributes> = { 259 const options = {
289 where: { 260 where: {
290 uuid 261 uuid
291 }, 262 },
292 include: [ 263 include: [
293 { 264 {
294 model: VideoChannel['sequelize'].models.Account, 265 model: AccountModel,
295 include: [ 266 include: [ { model: ServerModel, required: false } ]
296 { 267 }
297 model: VideoChannel['sequelize'].models.Server, 268 ]
298 required: true, 269 }
299 where: { 270
300 host: fromHost 271 return VideoChannelModel.findOne(options)
301 }
302 }
303 ]
304 }
305 ]
306 } 272 }
307 273
308 if (t !== undefined) query.transaction = t 274 static loadAndPopulateAccountAndVideos (id: number) {
275 const options = {
276 include: [
277 {
278 model: AccountModel,
279 include: [ { model: ServerModel, required: false } ]
280 },
281 VideoModel
282 ]
283 }
309 284
310 return VideoChannel.findOne(query) 285 return VideoChannelModel.findById(id, options)
311} 286 }
312 287
313loadByIdAndAccount = function (id: number, accountId: number) { 288 isOwned () {
314 const options = { 289 return this.remote === false
315 where: {
316 id,
317 accountId
318 },
319 include: [
320 {
321 model: VideoChannel['sequelize'].models.Account,
322 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
323 }
324 ]
325 } 290 }
326 291
327 return VideoChannel.findOne(options) 292 toFormattedJSON () {
328} 293 const json = {
294 id: this.id,
295 uuid: this.uuid,
296 name: this.name,
297 description: this.description,
298 isLocal: this.isOwned(),
299 createdAt: this.createdAt,
300 updatedAt: this.updatedAt
301 }
329 302
330loadAndPopulateAccount = function (id: number) { 303 if (this.Account !== undefined) {
331 const options = { 304 json[ 'owner' ] = {
332 include: [ 305 name: this.Account.name,
333 { 306 uuid: this.Account.uuid
334 model: VideoChannel['sequelize'].models.Account,
335 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
336 } 307 }
337 ] 308 }
309
310 if (Array.isArray(this.Videos)) {
311 json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON())
312 }
313
314 return json
338 } 315 }
339 316
340 return VideoChannel.findById(id, options) 317 toActivityPubObject () {
341} 318 let sharesObject
319 if (Array.isArray(this.VideoChannelShares)) {
320 const shares: string[] = []
342 321
343loadByUUIDAndPopulateAccount = function (uuid: string) { 322 for (const videoChannelShare of this.VideoChannelShares) {
344 const options = { 323 const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account)
345 where: { 324 shares.push(shareUrl)
346 uuid
347 },
348 include: [
349 {
350 model: VideoChannel['sequelize'].models.Account,
351 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ]
352 } 325 }
353 ]
354 }
355 326
356 return VideoChannel.findOne(options) 327 sharesObject = activityPubCollection(shares)
357} 328 }
358 329
359loadAndPopulateAccountAndVideos = function (id: number) { 330 return {
360 const options = { 331 type: 'VideoChannel' as 'VideoChannel',
361 include: [ 332 id: this.url,
362 { 333 uuid: this.uuid,
363 model: VideoChannel['sequelize'].models.Account, 334 content: this.description,
364 include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] 335 name: this.name,
365 }, 336 published: this.createdAt.toISOString(),
366 VideoChannel['sequelize'].models.Video 337 updated: this.updatedAt.toISOString(),
367 ] 338 shares: sharesObject
339 }
368 } 340 }
369
370 return VideoChannel.findById(id, options)
371} 341}
diff --git a/server/models/video/video-file-interface.ts b/server/models/video/video-file-interface.ts
deleted file mode 100644
index c9fb8b8ae..000000000
--- a/server/models/video/video-file-interface.ts
+++ /dev/null
@@ -1,24 +0,0 @@
1import * as Sequelize from 'sequelize'
2
3export namespace VideoFileMethods {
4}
5
6export interface VideoFileClass {
7}
8
9export interface VideoFileAttributes {
10 resolution: number
11 size: number
12 infoHash?: string
13 extname: string
14
15 videoId?: number
16}
17
18export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance<VideoFileAttributes> {
19 id: number
20 createdAt: Date
21 updatedAt: Date
22}
23
24export interface VideoFileModel extends VideoFileClass, Sequelize.Model<VideoFileInstance, VideoFileAttributes> {}
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 600141994..df4067a4e 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -1,81 +1,56 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import * as Sequelize from 'sequelize' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos' 3import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos'
4import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 4import { CONSTRAINTS_FIELDS } from '../../initializers'
5import { throwIfNotValid } from '../utils'
6import { VideoModel } from './video'
5 7
6import { addMethodsToModel } from '../utils' 8@Table({
7import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' 9 tableName: 'videoFile',
8 10 indexes: [
9let VideoFile: Sequelize.Model<VideoFileInstance, VideoFileAttributes>
10
11export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
12 VideoFile = sequelize.define<VideoFileInstance, VideoFileAttributes>('VideoFile',
13 { 11 {
14 resolution: { 12 fields: [ 'videoId' ]
15 type: DataTypes.INTEGER,
16 allowNull: false,
17 validate: {
18 resolutionValid: value => {
19 const res = isVideoFileResolutionValid(value)
20 if (res === false) throw new Error('Video file resolution is not valid.')
21 }
22 }
23 },
24 size: {
25 type: DataTypes.BIGINT,
26 allowNull: false,
27 validate: {
28 sizeValid: value => {
29 const res = isVideoFileSizeValid(value)
30 if (res === false) throw new Error('Video file size is not valid.')
31 }
32 }
33 },
34 extname: {
35 type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
36 allowNull: false
37 },
38 infoHash: {
39 type: DataTypes.STRING,
40 allowNull: false,
41 validate: {
42 infoHashValid: value => {
43 const res = isVideoFileInfoHashValid(value)
44 if (res === false) throw new Error('Video file info hash is not valid.')
45 }
46 }
47 }
48 }, 13 },
49 { 14 {
50 indexes: [ 15 fields: [ 'infoHash' ]
51 {
52 fields: [ 'videoId' ]
53 },
54 {
55 fields: [ 'infoHash' ]
56 }
57 ]
58 } 16 }
59 )
60
61 const classMethods = [
62 associate
63 ] 17 ]
64 addMethodsToModel(VideoFile, classMethods) 18})
65 19export class VideoFileModel extends Model<VideoFileModel> {
66 return VideoFile 20 @CreatedAt
67} 21 createdAt: Date
68 22
69// ------------------------------ STATICS ------------------------------ 23 @UpdatedAt
70 24 updatedAt: Date
71function associate (models) { 25
72 VideoFile.belongsTo(models.Video, { 26 @AllowNull(false)
27 @Is('VideoFileResolution', value => throwIfNotValid(value, isVideoFileResolutionValid, 'resolution'))
28 @Column
29 resolution: number
30
31 @AllowNull(false)
32 @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileSizeValid, 'size'))
33 @Column(DataType.BIGINT)
34 size: number
35
36 @AllowNull(false)
37 @Column(DataType.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)))
38 extname: string
39
40 @AllowNull(false)
41 @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash'))
42 @Column
43 infoHash: string
44
45 @ForeignKey(() => VideoModel)
46 @Column
47 videoId: number
48
49 @BelongsTo(() => VideoModel, {
73 foreignKey: { 50 foreignKey: {
74 name: 'videoId',
75 allowNull: false 51 allowNull: false
76 }, 52 },
77 onDelete: 'CASCADE' 53 onDelete: 'CASCADE'
78 }) 54 })
55 Video: VideoModel
79} 56}
80
81// ------------------------------ METHODS ------------------------------
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
deleted file mode 100644
index 2a63350af..000000000
--- a/server/models/video/video-interface.ts
+++ /dev/null
@@ -1,150 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
4import { ResultList } from '../../../shared/models/result-list.model'
5import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from '../../../shared/models/videos/video.model'
6import { AccountVideoRateInstance } from '../account/account-video-rate-interface'
7
8import { TagAttributes, TagInstance } from './tag-interface'
9import { VideoChannelInstance } from './video-channel-interface'
10import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
11import { VideoShareInstance } from './video-share-interface'
12
13export namespace VideoMethods {
14 export type GetThumbnailName = (this: VideoInstance) => string
15 export type GetPreviewName = (this: VideoInstance) => string
16 export type IsOwned = (this: VideoInstance) => boolean
17 export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
18 export type ToFormattedDetailsJSON = (this: VideoInstance) => FormattedDetailsVideo
19
20 export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
21 export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
22 export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string
23 export type CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
24 export type CreateThumbnail = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
25 export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
26 export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
27
28 export type ToActivityPubObject = (this: VideoInstance) => VideoTorrentObject
29
30 export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void>
31 export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void>
32 export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number>
33 export type GetEmbedPath = (this: VideoInstance) => string
34 export type GetThumbnailPath = (this: VideoInstance) => string
35 export type GetPreviewPath = (this: VideoInstance) => string
36 export type GetDescriptionPath = (this: VideoInstance) => string
37 export type GetTruncatedDescription = (this: VideoInstance) => string
38 export type GetCategoryLabel = (this: VideoInstance) => string
39 export type GetLicenceLabel = (this: VideoInstance) => string
40 export type GetLanguageLabel = (this: VideoInstance) => string
41
42 export type List = () => Bluebird<VideoInstance[]>
43
44 export type ListAllAndSharedByAccountForOutbox = (
45 accountId: number,
46 start: number,
47 count: number
48 ) => Bluebird< ResultList<VideoInstance> >
49 export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
50 export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
51 export type SearchAndPopulateAccountAndServerAndTags = (
52 value: string,
53 start: number,
54 count: number,
55 sort: string
56 ) => Bluebird< ResultList<VideoInstance> >
57
58 export type Load = (id: number) => Bluebird<VideoInstance>
59 export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
60 export type LoadByUrlAndPopulateAccount = (url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
61 export type LoadAndPopulateAccountAndServerAndTags = (id: number) => Bluebird<VideoInstance>
62 export type LoadByUUIDAndPopulateAccountAndServerAndTags = (uuid: string) => Bluebird<VideoInstance>
63 export type LoadByUUIDOrURL = (uuid: string, url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
64
65 export type RemoveThumbnail = (this: VideoInstance) => Promise<void>
66 export type RemovePreview = (this: VideoInstance) => Promise<void>
67 export type RemoveFile = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
68 export type RemoveTorrent = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
69}
70
71export interface VideoClass {
72 list: VideoMethods.List
73 listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
74 listForApi: VideoMethods.ListForApi
75 listUserVideosForApi: VideoMethods.ListUserVideosForApi
76 load: VideoMethods.Load
77 loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags
78 loadByUUID: VideoMethods.LoadByUUID
79 loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount
80 loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL
81 loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags
82 searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags
83}
84
85export interface VideoAttributes {
86 id?: number
87 uuid?: string
88 name: string
89 category: number
90 licence: number
91 language: number
92 nsfw: boolean
93 description: string
94 duration: number
95 privacy: number
96 views?: number
97 likes?: number
98 dislikes?: number
99 remote: boolean
100 url?: string
101
102 createdAt?: Date
103 updatedAt?: Date
104
105 parentId?: number
106 channelId?: number
107
108 VideoChannel?: VideoChannelInstance
109 Tags?: TagInstance[]
110 VideoFiles?: VideoFileInstance[]
111 VideoShares?: VideoShareInstance[]
112 AccountVideoRates?: AccountVideoRateInstance[]
113}
114
115export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
116 createPreview: VideoMethods.CreatePreview
117 createThumbnail: VideoMethods.CreateThumbnail
118 createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
119 getOriginalFile: VideoMethods.GetOriginalFile
120 getPreviewName: VideoMethods.GetPreviewName
121 getPreviewPath: VideoMethods.GetPreviewPath
122 getThumbnailName: VideoMethods.GetThumbnailName
123 getThumbnailPath: VideoMethods.GetThumbnailPath
124 getTorrentFileName: VideoMethods.GetTorrentFileName
125 getVideoFilename: VideoMethods.GetVideoFilename
126 getVideoFilePath: VideoMethods.GetVideoFilePath
127 isOwned: VideoMethods.IsOwned
128 removeFile: VideoMethods.RemoveFile
129 removePreview: VideoMethods.RemovePreview
130 removeThumbnail: VideoMethods.RemoveThumbnail
131 removeTorrent: VideoMethods.RemoveTorrent
132 toActivityPubObject: VideoMethods.ToActivityPubObject
133 toFormattedJSON: VideoMethods.ToFormattedJSON
134 toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
135 optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
136 transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
137 getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
138 getEmbedPath: VideoMethods.GetEmbedPath
139 getDescriptionPath: VideoMethods.GetDescriptionPath
140 getTruncatedDescription: VideoMethods.GetTruncatedDescription
141 getCategoryLabel: VideoMethods.GetCategoryLabel
142 getLicenceLabel: VideoMethods.GetLicenceLabel
143 getLanguageLabel: VideoMethods.GetLanguageLabel
144
145 setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
146 addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
147 setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
148}
149
150export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
diff --git a/server/models/video/video-share-interface.ts b/server/models/video/video-share-interface.ts
deleted file mode 100644
index 3946303f1..000000000
--- a/server/models/video/video-share-interface.ts
+++ /dev/null
@@ -1,30 +0,0 @@
1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize'
3import { AccountInstance } from '../account/account-interface'
4import { VideoInstance } from './video-interface'
5
6export namespace VideoShareMethods {
7 export type LoadAccountsByShare = (videoId: number, t: Sequelize.Transaction) => Bluebird<AccountInstance[]>
8 export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird<VideoShareInstance>
9}
10
11export interface VideoShareClass {
12 loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
13 load: VideoShareMethods.Load
14}
15
16export interface VideoShareAttributes {
17 accountId: number
18 videoId: number
19}
20
21export interface VideoShareInstance extends VideoShareClass, VideoShareAttributes, Sequelize.Instance<VideoShareAttributes> {
22 id: number
23 createdAt: Date
24 updatedAt: Date
25
26 Account?: AccountInstance
27 Video?: VideoInstance
28}
29
30export interface VideoShareModel extends VideoShareClass, Sequelize.Model<VideoShareInstance, VideoShareAttributes> {}
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 37e405fa9..01b6d3d34 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,84 +1,78 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { AccountModel } from '../account/account'
4import { VideoModel } from './video'
2 5
3import { addMethodsToModel } from '../utils' 6@Table({
4import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './video-share-interface' 7 tableName: 'videoShare',
5 8 indexes: [
6let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes>
7let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
8let load: VideoShareMethods.Load
9
10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
11 VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('VideoShare',
12 { },
13 { 9 {
14 indexes: [ 10 fields: [ 'accountId' ]
15 { 11 },
16 fields: [ 'accountId' ] 12 {
17 }, 13 fields: [ 'videoId' ]
18 {
19 fields: [ 'videoId' ]
20 }
21 ]
22 } 14 }
23 )
24
25 const classMethods = [
26 associate,
27 loadAccountsByShare,
28 load
29 ] 15 ]
30 addMethodsToModel(VideoShare, classMethods) 16})
17export class VideoShareModel extends Model<VideoShareModel> {
18 @CreatedAt
19 createdAt: Date
31 20
32 return VideoShare 21 @UpdatedAt
33} 22 updatedAt: Date
34 23
35// ------------------------------ METHODS ------------------------------ 24 @ForeignKey(() => AccountModel)
25 @Column
26 accountId: number
36 27
37function associate (models) { 28 @BelongsTo(() => AccountModel, {
38 VideoShare.belongsTo(models.Account, {
39 foreignKey: { 29 foreignKey: {
40 name: 'accountId',
41 allowNull: false 30 allowNull: false
42 }, 31 },
43 onDelete: 'cascade' 32 onDelete: 'cascade'
44 }) 33 })
34 Account: AccountModel
45 35
46 VideoShare.belongsTo(models.Video, { 36 @ForeignKey(() => VideoModel)
37 @Column
38 videoId: number
39
40 @BelongsTo(() => VideoModel, {
47 foreignKey: { 41 foreignKey: {
48 name: 'videoId', 42 allowNull: false
49 allowNull: true
50 }, 43 },
51 onDelete: 'cascade' 44 onDelete: 'cascade'
52 }) 45 })
53} 46 Video: VideoModel
54
55load = function (accountId: number, videoId: number, t: Sequelize.Transaction) {
56 return VideoShare.findOne({
57 where: {
58 accountId,
59 videoId
60 },
61 include: [
62 VideoShare['sequelize'].models.Account
63 ],
64 transaction: t
65 })
66}
67 47
68loadAccountsByShare = function (videoId: number, t: Sequelize.Transaction) { 48 static load (accountId: number, videoId: number, t: Sequelize.Transaction) {
69 const query = { 49 return VideoShareModel.findOne({
70 where: { 50 where: {
71 videoId 51 accountId,
72 }, 52 videoId
73 include: [ 53 },
74 { 54 include: [
75 model: VideoShare['sequelize'].models.Account, 55 AccountModel
76 required: true 56 ],
77 } 57 transaction: t
78 ], 58 })
79 transaction: t
80 } 59 }
81 60
82 return VideoShare.findAll(query) 61 static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) {
83 .then(res => res.map(r => r.Account)) 62 const query = {
63 where: {
64 videoId
65 },
66 include: [
67 {
68 model: AccountModel,
69 required: true
70 }
71 ],
72 transaction: t
73 }
74
75 return VideoShareModel.findAll(query)
76 .then(res => res.map(r => r.Account))
77 }
84} 78}
diff --git a/server/models/video/video-tag-interface.ts b/server/models/video/video-tag-interface.ts
deleted file mode 100644
index f928cecff..000000000
--- a/server/models/video/video-tag-interface.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1import * as Sequelize from 'sequelize'
2
3export namespace VideoTagMethods {
4}
5
6export interface VideoTagClass {
7}
8
9export interface VideoTagAttributes {
10}
11
12export interface VideoTagInstance extends VideoTagClass, VideoTagAttributes, Sequelize.Instance<VideoTagAttributes> {
13 id: number
14 createdAt: Date
15 updatedAt: Date
16}
17
18export interface VideoTagModel extends VideoTagClass, Sequelize.Model<VideoTagInstance, VideoTagAttributes> {}
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts
index ac45374f8..ca15e3426 100644
--- a/server/models/video/video-tag.ts
+++ b/server/models/video/video-tag.ts
@@ -1,23 +1,30 @@
1import * as Sequelize from 'sequelize' 1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { TagModel } from './tag'
3import { VideoModel } from './video'
2 4
3import { 5@Table({
4 VideoTagInstance, 6 tableName: 'videoTag',
5 VideoTagAttributes 7 indexes: [
6} from './video-tag-interface' 8 {
9 fields: [ 'videoId' ]
10 },
11 {
12 fields: [ 'tagId' ]
13 }
14 ]
15})
16export class VideoTagModel extends Model<VideoTagModel> {
17 @CreatedAt
18 createdAt: Date
7 19
8let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes> 20 @UpdatedAt
21 updatedAt: Date
9 22
10export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { 23 @ForeignKey(() => VideoModel)
11 VideoTag = sequelize.define<VideoTagInstance, VideoTagAttributes>('VideoTag', {}, { 24 @Column
12 indexes: [ 25 videoId: number
13 {
14 fields: [ 'videoId' ]
15 },
16 {
17 fields: [ 'tagId' ]
18 }
19 ]
20 })
21 26
22 return VideoTag 27 @ForeignKey(() => TagModel)
28 @Column
29 tagId: number
23} 30}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index d46fdeebe..9e26f9bbe 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -4,21 +4,52 @@ import * as magnetUtil from 'magnet-uri'
4import * as parseTorrent from 'parse-torrent' 4import * as parseTorrent from 'parse-torrent'
5import { join } from 'path' 5import { join } from 'path'
6import * as Sequelize from 'sequelize' 6import * as Sequelize from 'sequelize'
7import {
8 AfterDestroy,
9 AllowNull,
10 BelongsTo,
11 BelongsToMany,
12 Column,
13 CreatedAt,
14 DataType,
15 Default,
16 ForeignKey,
17 HasMany,
18 IFindOptions,
19 Is,
20 IsInt,
21 IsUUID,
22 Min,
23 Model,
24 Table,
25 UpdatedAt
26} from 'sequelize-typescript'
27import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions'
7import { VideoPrivacy, VideoResolution } from '../../../shared' 28import { VideoPrivacy, VideoResolution } from '../../../shared'
8import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' 29import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
9import { activityPubCollection } from '../../helpers/activitypub' 30import {
10import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils' 31 activityPubCollection,
11import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid } from '../../helpers/custom-validators/videos' 32 createTorrentPromise,
12import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils' 33 generateImageFromVideoFile,
34 getVideoFileHeight,
35 logger,
36 renamePromise,
37 statPromise,
38 transcode,
39 unlinkPromise,
40 writeFilePromise
41} from '../../helpers'
42import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
13import { 43import {
14 isActivityPubUrlValid, 44 isVideoCategoryValid,
15 isVideoDescriptionValid, 45 isVideoDescriptionValid,
16 isVideoDurationValid, 46 isVideoDurationValid,
47 isVideoLanguageValid,
17 isVideoLicenceValid, 48 isVideoLicenceValid,
18 isVideoNameValid, 49 isVideoNameValid,
19 isVideoNSFWValid 50 isVideoNSFWValid,
20} from '../../helpers/index' 51 isVideoPrivacyValid
21import { logger } from '../../helpers/logger' 52} from '../../helpers/custom-validators/videos'
22import { 53import {
23 API_VERSION, 54 API_VERSION,
24 CONFIG, 55 CONFIG,
@@ -31,1169 +62,1025 @@ import {
31 VIDEO_LANGUAGES, 62 VIDEO_LANGUAGES,
32 VIDEO_LICENCES, 63 VIDEO_LICENCES,
33 VIDEO_PRIVACIES 64 VIDEO_PRIVACIES
34} from '../../initializers/constants' 65} from '../../initializers'
35import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' 66import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
36import { sendDeleteVideo } from '../../lib/index' 67import { sendDeleteVideo } from '../../lib/index'
37import { addMethodsToModel, getSort } from '../utils' 68import { AccountModel } from '../account/account'
38import { TagInstance } from './tag-interface' 69import { AccountVideoRateModel } from '../account/account-video-rate'
39import { VideoFileInstance, VideoFileModel } from './video-file-interface' 70import { ServerModel } from '../server/server'
40import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface' 71import { getSort, throwIfNotValid } from '../utils'
41 72import { TagModel } from './tag'
42let Video: Sequelize.Model<VideoInstance, VideoAttributes> 73import { VideoAbuseModel } from './video-abuse'
43let getOriginalFile: VideoMethods.GetOriginalFile 74import { VideoChannelModel } from './video-channel'
44let getVideoFilename: VideoMethods.GetVideoFilename 75import { VideoFileModel } from './video-file'
45let getThumbnailName: VideoMethods.GetThumbnailName 76import { VideoShareModel } from './video-share'
46let getThumbnailPath: VideoMethods.GetThumbnailPath 77import { VideoTagModel } from './video-tag'
47let getPreviewName: VideoMethods.GetPreviewName 78
48let getPreviewPath: VideoMethods.GetPreviewPath 79@Table({
49let getTorrentFileName: VideoMethods.GetTorrentFileName 80 tableName: 'video',
50let isOwned: VideoMethods.IsOwned 81 indexes: [
51let toFormattedJSON: VideoMethods.ToFormattedJSON
52let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON
53let toActivityPubObject: VideoMethods.ToActivityPubObject
54let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile
55let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
56let createPreview: VideoMethods.CreatePreview
57let createThumbnail: VideoMethods.CreateThumbnail
58let getVideoFilePath: VideoMethods.GetVideoFilePath
59let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
60let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
61let getEmbedPath: VideoMethods.GetEmbedPath
62let getDescriptionPath: VideoMethods.GetDescriptionPath
63let getTruncatedDescription: VideoMethods.GetTruncatedDescription
64let getCategoryLabel: VideoMethods.GetCategoryLabel
65let getLicenceLabel: VideoMethods.GetLicenceLabel
66let getLanguageLabel: VideoMethods.GetLanguageLabel
67
68let list: VideoMethods.List
69let listForApi: VideoMethods.ListForApi
70let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox
71let listUserVideosForApi: VideoMethods.ListUserVideosForApi
72let load: VideoMethods.Load
73let loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount
74let loadByUUID: VideoMethods.LoadByUUID
75let loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL
76let loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags
77let loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags
78let searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags
79let removeThumbnail: VideoMethods.RemoveThumbnail
80let removePreview: VideoMethods.RemovePreview
81let removeFile: VideoMethods.RemoveFile
82let removeTorrent: VideoMethods.RemoveTorrent
83
84export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
85 Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
86 { 82 {
87 uuid: { 83 fields: [ 'name' ]
88 type: DataTypes.UUID,
89 defaultValue: DataTypes.UUIDV4,
90 allowNull: false,
91 validate: {
92 isUUID: 4
93 }
94 },
95 name: {
96 type: DataTypes.STRING,
97 allowNull: false,
98 validate: {
99 nameValid: value => {
100 const res = isVideoNameValid(value)
101 if (res === false) throw new Error('Video name is not valid.')
102 }
103 }
104 },
105 category: {
106 type: DataTypes.INTEGER,
107 allowNull: true,
108 defaultValue: null,
109 validate: {
110 categoryValid: value => {
111 const res = isVideoCategoryValid(value)
112 if (res === false) throw new Error('Video category is not valid.')
113 }
114 }
115 },
116 licence: {
117 type: DataTypes.INTEGER,
118 allowNull: true,
119 defaultValue: null,
120 validate: {
121 licenceValid: value => {
122 const res = isVideoLicenceValid(value)
123 if (res === false) throw new Error('Video licence is not valid.')
124 }
125 }
126 },
127 language: {
128 type: DataTypes.INTEGER,
129 allowNull: true,
130 defaultValue: null,
131 validate: {
132 languageValid: value => {
133 const res = isVideoLanguageValid(value)
134 if (res === false) throw new Error('Video language is not valid.')
135 }
136 }
137 },
138 privacy: {
139 type: DataTypes.INTEGER,
140 allowNull: false,
141 validate: {
142 privacyValid: value => {
143 const res = isVideoPrivacyValid(value)
144 if (res === false) throw new Error('Video privacy is not valid.')
145 }
146 }
147 },
148 nsfw: {
149 type: DataTypes.BOOLEAN,
150 allowNull: false,
151 validate: {
152 nsfwValid: value => {
153 const res = isVideoNSFWValid(value)
154 if (res === false) throw new Error('Video nsfw attribute is not valid.')
155 }
156 }
157 },
158 description: {
159 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max),
160 allowNull: true,
161 defaultValue: null,
162 validate: {
163 descriptionValid: value => {
164 const res = isVideoDescriptionValid(value)
165 if (res === false) throw new Error('Video description is not valid.')
166 }
167 }
168 },
169 duration: {
170 type: DataTypes.INTEGER,
171 allowNull: false,
172 validate: {
173 durationValid: value => {
174 const res = isVideoDurationValid(value)
175 if (res === false) throw new Error('Video duration is not valid.')
176 }
177 }
178 },
179 views: {
180 type: DataTypes.INTEGER,
181 allowNull: false,
182 defaultValue: 0,
183 validate: {
184 min: 0,
185 isInt: true
186 }
187 },
188 likes: {
189 type: DataTypes.INTEGER,
190 allowNull: false,
191 defaultValue: 0,
192 validate: {
193 min: 0,
194 isInt: true
195 }
196 },
197 dislikes: {
198 type: DataTypes.INTEGER,
199 allowNull: false,
200 defaultValue: 0,
201 validate: {
202 min: 0,
203 isInt: true
204 }
205 },
206 remote: {
207 type: DataTypes.BOOLEAN,
208 allowNull: false,
209 defaultValue: false
210 },
211 url: {
212 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max),
213 allowNull: false,
214 validate: {
215 urlValid: value => {
216 const res = isActivityPubUrlValid(value)
217 if (res === false) throw new Error('Video URL is not valid.')
218 }
219 }
220 }
221 }, 84 },
222 { 85 {
223 indexes: [ 86 fields: [ 'createdAt' ]
224 { 87 },
225 fields: [ 'name' ] 88 {
226 }, 89 fields: [ 'duration' ]
227 { 90 },
228 fields: [ 'createdAt' ] 91 {
229 }, 92 fields: [ 'views' ]
230 { 93 },
231 fields: [ 'duration' ] 94 {
232 }, 95 fields: [ 'likes' ]
233 { 96 },
234 fields: [ 'views' ] 97 {
235 }, 98 fields: [ 'uuid' ]
236 { 99 },
237 fields: [ 'likes' ] 100 {
238 }, 101 fields: [ 'channelId' ]
239 {
240 fields: [ 'uuid' ]
241 },
242 {
243 fields: [ 'channelId' ]
244 }
245 ],
246 hooks: {
247 afterDestroy
248 }
249 } 102 }
250 )
251
252 const classMethods = [
253 associate,
254
255 list,
256 listAllAndSharedByAccountForOutbox,
257 listForApi,
258 listUserVideosForApi,
259 load,
260 loadByUrlAndPopulateAccount,
261 loadAndPopulateAccountAndServerAndTags,
262 loadByUUIDOrURL,
263 loadByUUID,
264 loadByUUIDAndPopulateAccountAndServerAndTags,
265 searchAndPopulateAccountAndServerAndTags
266 ]
267 const instanceMethods = [
268 createPreview,
269 createThumbnail,
270 createTorrentAndSetInfoHash,
271 getPreviewName,
272 getPreviewPath,
273 getThumbnailName,
274 getThumbnailPath,
275 getTorrentFileName,
276 getVideoFilename,
277 getVideoFilePath,
278 getOriginalFile,
279 isOwned,
280 removeFile,
281 removePreview,
282 removeThumbnail,
283 removeTorrent,
284 toActivityPubObject,
285 toFormattedJSON,
286 toFormattedDetailsJSON,
287 optimizeOriginalVideofile,
288 transcodeOriginalVideofile,
289 getOriginalFileHeight,
290 getEmbedPath,
291 getTruncatedDescription,
292 getDescriptionPath,
293 getCategoryLabel,
294 getLicenceLabel,
295 getLanguageLabel
296 ] 103 ]
297 addMethodsToModel(Video, classMethods, instanceMethods) 104})
298 105export class VideoModel extends Model<VideoModel> {
299 return Video 106
300} 107 @AllowNull(false)
301 108 @Default(DataType.UUIDV4)
302// ------------------------------ METHODS ------------------------------ 109 @IsUUID(4)
303 110 @Column(DataType.UUID)
304function associate (models) { 111 uuid: string
305 Video.belongsTo(models.VideoChannel, { 112
113 @AllowNull(false)
114 @Is('VideoName', value => throwIfNotValid(value, isVideoNameValid, 'name'))
115 @Column
116 name: string
117
118 @AllowNull(true)
119 @Default(null)
120 @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category'))
121 @Column
122 category: number
123
124 @AllowNull(true)
125 @Default(null)
126 @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence'))
127 @Column
128 licence: number
129
130 @AllowNull(true)
131 @Default(null)
132 @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language'))
133 @Column
134 language: number
135
136 @AllowNull(false)
137 @Is('VideoPrivacy', value => throwIfNotValid(value, isVideoPrivacyValid, 'privacy'))
138 @Column
139 privacy: number
140
141 @AllowNull(false)
142 @Is('VideoNSFW', value => throwIfNotValid(value, isVideoNSFWValid, 'NSFW boolean'))
143 @Column
144 nsfw: boolean
145
146 @AllowNull(true)
147 @Default(null)
148 @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description'))
149 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max))
150 description: string
151
152 @AllowNull(false)
153 @Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration'))
154 @Column
155 duration: number
156
157 @AllowNull(false)
158 @Default(0)
159 @IsInt
160 @Min(0)
161 @Column
162 views: number
163
164 @AllowNull(false)
165 @Default(0)
166 @IsInt
167 @Min(0)
168 @Column
169 likes: number
170
171 @AllowNull(false)
172 @Default(0)
173 @IsInt
174 @Min(0)
175 @Column
176 dislikes: number
177
178 @AllowNull(false)
179 @Column
180 remote: boolean
181
182 @AllowNull(false)
183 @Is('VideoUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
184 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max))
185 url: string
186
187 @CreatedAt
188 createdAt: Date
189
190 @UpdatedAt
191 updatedAt: Date
192
193 @ForeignKey(() => VideoChannelModel)
194 @Column
195 channelId: number
196
197 @BelongsTo(() => VideoChannelModel, {
306 foreignKey: { 198 foreignKey: {
307 name: 'channelId',
308 allowNull: false 199 allowNull: false
309 }, 200 },
310 onDelete: 'cascade' 201 onDelete: 'cascade'
311 }) 202 })
203 VideoChannel: VideoChannelModel
312 204
313 Video.belongsToMany(models.Tag, { 205 @BelongsToMany(() => TagModel, {
314 foreignKey: 'videoId', 206 foreignKey: 'videoId',
315 through: models.VideoTag, 207 through: () => VideoTagModel,
316 onDelete: 'cascade' 208 onDelete: 'CASCADE'
317 }) 209 })
210 Tags: TagModel[]
318 211
319 Video.hasMany(models.VideoAbuse, { 212 @HasMany(() => VideoAbuseModel, {
320 foreignKey: { 213 foreignKey: {
321 name: 'videoId', 214 name: 'videoId',
322 allowNull: false 215 allowNull: false
323 }, 216 },
324 onDelete: 'cascade' 217 onDelete: 'cascade'
325 }) 218 })
219 VideoAbuses: VideoAbuseModel[]
326 220
327 Video.hasMany(models.VideoFile, { 221 @HasMany(() => VideoFileModel, {
328 foreignKey: { 222 foreignKey: {
329 name: 'videoId', 223 name: 'videoId',
330 allowNull: false 224 allowNull: false
331 }, 225 },
332 onDelete: 'cascade' 226 onDelete: 'cascade'
333 }) 227 })
228 VideoFiles: VideoFileModel[]
334 229
335 Video.hasMany(models.VideoShare, { 230 @HasMany(() => VideoShareModel, {
336 foreignKey: { 231 foreignKey: {
337 name: 'videoId', 232 name: 'videoId',
338 allowNull: false 233 allowNull: false
339 }, 234 },
340 onDelete: 'cascade' 235 onDelete: 'cascade'
341 }) 236 })
237 VideoShares: VideoShareModel[]
342 238
343 Video.hasMany(models.AccountVideoRate, { 239 @HasMany(() => AccountVideoRateModel, {
344 foreignKey: { 240 foreignKey: {
345 name: 'videoId', 241 name: 'videoId',
346 allowNull: false 242 allowNull: false
347 }, 243 },
348 onDelete: 'cascade' 244 onDelete: 'cascade'
349 }) 245 })
350} 246 AccountVideoRates: AccountVideoRateModel[]
351
352function afterDestroy (video: VideoInstance) {
353 const tasks = []
354 247
355 tasks.push( 248 @AfterDestroy
356 video.removeThumbnail() 249 static removeFilesAndSendDelete (instance: VideoModel) {
357 ) 250 const tasks = []
358 251
359 if (video.isOwned()) {
360 tasks.push( 252 tasks.push(
361 video.removePreview(), 253 instance.removeThumbnail()
362 sendDeleteVideo(video, undefined)
363 ) 254 )
364 255
365 // Remove physical files and torrents 256 if (instance.isOwned()) {
366 video.VideoFiles.forEach(file => { 257 tasks.push(
367 tasks.push(video.removeFile(file)) 258 instance.removePreview(),
368 tasks.push(video.removeTorrent(file)) 259 sendDeleteVideo(instance, undefined)
369 }) 260 )
370 }
371
372 return Promise.all(tasks)
373 .catch(err => {
374 logger.error('Some errors when removing files of video %s in after destroy hook.', video.uuid, err)
375 })
376}
377
378getOriginalFile = function (this: VideoInstance) {
379 if (Array.isArray(this.VideoFiles) === false) return undefined
380 261
381 // The original file is the file that have the higher resolution 262 // Remove physical files and torrents
382 return maxBy(this.VideoFiles, file => file.resolution) 263 instance.VideoFiles.forEach(file => {
383} 264 tasks.push(instance.removeFile(file))
265 tasks.push(instance.removeTorrent(file))
266 })
267 }
384 268
385getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { 269 return Promise.all(tasks)
386 return this.uuid + '-' + videoFile.resolution + videoFile.extname 270 .catch(err => {
387} 271 logger.error('Some errors when removing files of video %s in after destroy hook.', instance.uuid, err)
272 })
273 }
388 274
389getThumbnailName = function (this: VideoInstance) { 275 static list () {
390 // We always have a copy of the thumbnail 276 const query = {
391 const extension = '.jpg' 277 include: [ VideoFileModel ]
392 return this.uuid + extension 278 }
393}
394 279
395getPreviewName = function (this: VideoInstance) { 280 return VideoModel.findAll(query)
396 const extension = '.jpg' 281 }
397 return this.uuid + extension
398}
399 282
400getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { 283 static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) {
401 const extension = '.torrent' 284 function getRawQuery (select: string) {
402 return this.uuid + '-' + videoFile.resolution + extension 285 const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' +
403} 286 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' +
287 'WHERE "VideoChannel"."accountId" = ' + accountId
288 const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' +
289 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
290 'WHERE "VideoShare"."accountId" = ' + accountId
404 291
405isOwned = function (this: VideoInstance) { 292 return `(${queryVideo}) UNION (${queryVideoShare})`
406 return this.remote === false 293 }
407}
408 294
409createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { 295 const rawQuery = getRawQuery('"Video"."id"')
410 const imageSize = PREVIEWS_SIZE.width + 'x' + PREVIEWS_SIZE.height 296 const rawCountQuery = getRawQuery('COUNT("Video"."id") as "total"')
297
298 const query = {
299 distinct: true,
300 offset: start,
301 limit: count,
302 order: [ getSort('createdAt'), [ 'Tags', 'name', 'ASC' ] ],
303 where: {
304 id: {
305 [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')')
306 }
307 },
308 include: [
309 {
310 model: VideoShareModel,
311 required: false,
312 where: {
313 [Sequelize.Op.and]: [
314 {
315 id: {
316 [Sequelize.Op.not]: null
317 }
318 },
319 {
320 accountId
321 }
322 ]
323 },
324 include: [ AccountModel ]
325 },
326 {
327 model: VideoChannelModel,
328 required: true,
329 include: [
330 {
331 model: AccountModel,
332 required: true
333 }
334 ]
335 },
336 {
337 model: AccountVideoRateModel,
338 include: [ AccountModel ]
339 },
340 VideoFileModel,
341 TagModel
342 ]
343 }
411 344
412 return generateImageFromVideoFile( 345 return Bluebird.all([
413 this.getVideoFilePath(videoFile), 346 // FIXME: typing issue
414 CONFIG.STORAGE.PREVIEWS_DIR, 347 VideoModel.findAll(query as any),
415 this.getPreviewName(), 348 VideoModel.sequelize.query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT })
416 imageSize 349 ]).then(([ rows, totals ]) => {
417 ) 350 // totals: totalVideos + totalVideoShares
418} 351 let totalVideos = 0
352 let totalVideoShares = 0
353 if (totals[0]) totalVideos = parseInt(totals[0].total, 10)
354 if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10)
355
356 const total = totalVideos + totalVideoShares
357 return {
358 data: rows,
359 total: total
360 }
361 })
362 }
419 363
420createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { 364 static listUserVideosForApi (userId: number, start: number, count: number, sort: string) {
421 const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height 365 const query = {
366 distinct: true,
367 offset: start,
368 limit: count,
369 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ],
370 include: [
371 {
372 model: VideoChannelModel,
373 required: true,
374 include: [
375 {
376 model: AccountModel,
377 where: {
378 userId
379 },
380 required: true
381 }
382 ]
383 },
384 TagModel
385 ]
386 }
422 387
423 return generateImageFromVideoFile( 388 return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
424 this.getVideoFilePath(videoFile), 389 return {
425 CONFIG.STORAGE.THUMBNAILS_DIR, 390 data: rows,
426 this.getThumbnailName(), 391 total: count
427 imageSize 392 }
428 ) 393 })
429} 394 }
430 395
431getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) { 396 static listForApi (start: number, count: number, sort: string) {
432 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) 397 const query = {
433} 398 distinct: true,
399 offset: start,
400 limit: count,
401 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ],
402 include: [
403 {
404 model: VideoChannelModel,
405 required: true,
406 include: [
407 {
408 model: AccountModel,
409 required: true,
410 include: [
411 {
412 model: ServerModel,
413 required: false
414 }
415 ]
416 }
417 ]
418 },
419 TagModel
420 ],
421 where: this.createBaseVideosWhere()
422 }
434 423
435createTorrentAndSetInfoHash = async function (this: VideoInstance, videoFile: VideoFileInstance) { 424 return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
436 const options = { 425 return {
437 announceList: [ 426 data: rows,
438 [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] 427 total: count
439 ], 428 }
440 urlList: [ 429 })
441 CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
442 ]
443 } 430 }
444 431
445 const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) 432 static load (id: number) {
433 return VideoModel.findById(id)
434 }
446 435
447 const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 436 static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
448 logger.info('Creating torrent %s.', filePath) 437 const query: IFindOptions<VideoModel> = {
438 where: {
439 uuid
440 },
441 include: [ VideoFileModel ]
442 }
449 443
450 await writeFilePromise(filePath, torrent) 444 if (t !== undefined) query.transaction = t
451 445
452 const parsedTorrent = parseTorrent(torrent) 446 return VideoModel.findOne(query)
453 videoFile.infoHash = parsedTorrent.infoHash 447 }
454}
455 448
456getEmbedPath = function (this: VideoInstance) { 449 static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
457 return '/videos/embed/' + this.uuid 450 const query: IFindOptions<VideoModel> = {
458} 451 where: {
452 url
453 },
454 include: [
455 VideoFileModel,
456 {
457 model: VideoChannelModel,
458 include: [ AccountModel ]
459 }
460 ]
461 }
459 462
460getThumbnailPath = function (this: VideoInstance) { 463 if (t !== undefined) query.transaction = t
461 return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName())
462}
463 464
464getPreviewPath = function (this: VideoInstance) { 465 return VideoModel.findOne(query)
465 return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) 466 }
466}
467 467
468toFormattedJSON = function (this: VideoInstance) { 468 static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) {
469 let serverHost 469 const query: IFindOptions<VideoModel> = {
470 where: {
471 [Sequelize.Op.or]: [
472 { uuid },
473 { url }
474 ]
475 },
476 include: [ VideoFileModel ]
477 }
470 478
471 if (this.VideoChannel.Account.Server) { 479 if (t !== undefined) query.transaction = t
472 serverHost = this.VideoChannel.Account.Server.host
473 } else {
474 // It means it's our video
475 serverHost = CONFIG.WEBSERVER.HOST
476 }
477 480
478 const json = { 481 return VideoModel.findOne(query)
479 id: this.id,
480 uuid: this.uuid,
481 name: this.name,
482 category: this.category,
483 categoryLabel: this.getCategoryLabel(),
484 licence: this.licence,
485 licenceLabel: this.getLicenceLabel(),
486 language: this.language,
487 languageLabel: this.getLanguageLabel(),
488 nsfw: this.nsfw,
489 description: this.getTruncatedDescription(),
490 serverHost,
491 isLocal: this.isOwned(),
492 accountName: this.VideoChannel.Account.name,
493 duration: this.duration,
494 views: this.views,
495 likes: this.likes,
496 dislikes: this.dislikes,
497 tags: map<TagInstance, string>(this.Tags, 'name'),
498 thumbnailPath: this.getThumbnailPath(),
499 previewPath: this.getPreviewPath(),
500 embedPath: this.getEmbedPath(),
501 createdAt: this.createdAt,
502 updatedAt: this.updatedAt
503 } 482 }
504 483
505 return json 484 static loadAndPopulateAccountAndServerAndTags (id: number) {
506} 485 const options = {
486 order: [ [ 'Tags', 'name', 'ASC' ] ],
487 include: [
488 {
489 model: VideoChannelModel,
490 include: [
491 {
492 model: AccountModel,
493 include: [ { model: ServerModel, required: false } ]
494 }
495 ]
496 },
497 {
498 model: AccountVideoRateModel,
499 include: [ AccountModel ]
500 },
501 {
502 model: VideoShareModel,
503 include: [ AccountModel ]
504 },
505 TagModel,
506 VideoFileModel
507 ]
508 }
507 509
508toFormattedDetailsJSON = function (this: VideoInstance) { 510 return VideoModel.findById(id, options)
509 const formattedJson = this.toFormattedJSON() 511 }
510 512
511 // Maybe our server is not up to date and there are new privacy settings since our version 513 static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) {
512 let privacyLabel = VIDEO_PRIVACIES[this.privacy] 514 const options = {
513 if (!privacyLabel) privacyLabel = 'Unknown' 515 order: [ [ 'Tags', 'name', 'ASC' ] ],
516 where: {
517 uuid
518 },
519 include: [
520 {
521 model: VideoChannelModel,
522 include: [
523 {
524 model: AccountModel,
525 include: [ { model: ServerModel, required: false } ]
526 }
527 ]
528 },
529 {
530 model: AccountVideoRateModel,
531 include: [ AccountModel ]
532 },
533 {
534 model: VideoShareModel,
535 include: [ AccountModel ]
536 },
537 TagModel,
538 VideoFileModel
539 ]
540 }
514 541
515 const detailsJson = { 542 return VideoModel.findOne(options)
516 privacyLabel,
517 privacy: this.privacy,
518 descriptionPath: this.getDescriptionPath(),
519 channel: this.VideoChannel.toFormattedJSON(),
520 account: this.VideoChannel.Account.toFormattedJSON(),
521 files: []
522 } 543 }
523 544
524 // Format and sort video files 545 static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) {
525 const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) 546 const serverInclude: IIncludeOptions = {
526 detailsJson.files = this.VideoFiles 547 model: ServerModel,
527 .map(videoFile => { 548 required: false
528 let resolutionLabel = videoFile.resolution + 'p' 549 }
529
530 const videoFileJson = {
531 resolution: videoFile.resolution,
532 resolutionLabel,
533 magnetUri: generateMagnetUri(this, videoFile, baseUrlHttp, baseUrlWs),
534 size: videoFile.size,
535 torrentUrl: getTorrentUrl(this, videoFile, baseUrlHttp),
536 fileUrl: getVideoFileUrl(this, videoFile, baseUrlHttp)
537 }
538
539 return videoFileJson
540 })
541 .sort((a, b) => {
542 if (a.resolution < b.resolution) return 1
543 if (a.resolution === b.resolution) return 0
544 return -1
545 })
546
547 return Object.assign(formattedJson, detailsJson)
548}
549
550toActivityPubObject = function (this: VideoInstance) {
551 const { baseUrlHttp, baseUrlWs } = getBaseUrls(this)
552 if (!this.Tags) this.Tags = []
553 550
554 const tag = this.Tags.map(t => ({ 551 const accountInclude: IIncludeOptions = {
555 type: 'Hashtag' as 'Hashtag', 552 model: AccountModel,
556 name: t.name 553 include: [ serverInclude ]
557 })) 554 }
558 555
559 let language 556 const videoChannelInclude: IIncludeOptions = {
560 if (this.language) { 557 model: VideoChannelModel,
561 language = { 558 include: [ accountInclude ],
562 identifier: this.language + '', 559 required: true
563 name: this.getLanguageLabel()
564 } 560 }
565 }
566 561
567 let category 562 const tagInclude: IIncludeOptions = {
568 if (this.category) { 563 model: TagModel
569 category = {
570 identifier: this.category + '',
571 name: this.getCategoryLabel()
572 } 564 }
573 }
574 565
575 let licence 566 const query: IFindOptions<VideoModel> = {
576 if (this.licence) { 567 distinct: true,
577 licence = { 568 where: this.createBaseVideosWhere(),
578 identifier: this.licence + '', 569 offset: start,
579 name: this.getLicenceLabel() 570 limit: count,
571 order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ]
580 } 572 }
581 }
582 573
583 let likesObject 574 // TODO: search on tags too
584 let dislikesObject 575 // const escapedValue = Video['sequelize'].escape('%' + value + '%')
576 // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal(
577 // `(SELECT "VideoTags"."videoId"
578 // FROM "Tags"
579 // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId"
580 // WHERE name ILIKE ${escapedValue}
581 // )`
582 // )
583
584 // TODO: search on account too
585 // accountInclude.where = {
586 // name: {
587 // [Sequelize.Op.iLike]: '%' + value + '%'
588 // }
589 // }
590 query.where['name'] = {
591 [Sequelize.Op.iLike]: '%' + value + '%'
592 }
585 593
586 if (Array.isArray(this.AccountVideoRates)) { 594 query.include = [
587 const likes: string[] = [] 595 videoChannelInclude, tagInclude
588 const dislikes: string[] = [] 596 ]
589 597
590 for (const rate of this.AccountVideoRates) { 598 return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
591 if (rate.type === 'like') { 599 return {
592 likes.push(rate.Account.url) 600 data: rows,
593 } else if (rate.type === 'dislike') { 601 total: count
594 dislikes.push(rate.Account.url)
595 } 602 }
596 } 603 })
597
598 likesObject = activityPubCollection(likes)
599 dislikesObject = activityPubCollection(dislikes)
600 } 604 }
601 605
602 let sharesObject 606 private static createBaseVideosWhere () {
603 if (Array.isArray(this.VideoShares)) { 607 return {
604 const shares: string[] = [] 608 id: {
605 609 [Sequelize.Op.notIn]: VideoModel.sequelize.literal(
606 for (const videoShare of this.VideoShares) { 610 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
607 const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) 611 )
608 shares.push(shareUrl) 612 },
613 privacy: VideoPrivacy.PUBLIC
609 } 614 }
610
611 sharesObject = activityPubCollection(shares)
612 } 615 }
613 616
614 const url = [] 617 getOriginalFile () {
615 for (const file of this.VideoFiles) { 618 if (Array.isArray(this.VideoFiles) === false) return undefined
616 url.push({
617 type: 'Link',
618 mimeType: 'video/' + file.extname.replace('.', ''),
619 url: getVideoFileUrl(this, file, baseUrlHttp),
620 width: file.resolution,
621 size: file.size
622 })
623 619
624 url.push({ 620 // The original file is the file that have the higher resolution
625 type: 'Link', 621 return maxBy(this.VideoFiles, file => file.resolution)
626 mimeType: 'application/x-bittorrent',
627 url: getTorrentUrl(this, file, baseUrlHttp),
628 width: file.resolution
629 })
630
631 url.push({
632 type: 'Link',
633 mimeType: 'application/x-bittorrent;x-scheme-handler/magnet',
634 url: generateMagnetUri(this, file, baseUrlHttp, baseUrlWs),
635 width: file.resolution
636 })
637 } 622 }
638 623
639 // Add video url too 624 getVideoFilename (videoFile: VideoFileModel) {
640 url.push({ 625 return this.uuid + '-' + videoFile.resolution + videoFile.extname
641 type: 'Link', 626 }
642 mimeType: 'text/html',
643 url: CONFIG.WEBSERVER.URL + '/videos/watch/' + this.uuid
644 })
645 627
646 const videoObject: VideoTorrentObject = { 628 getThumbnailName () {
647 type: 'Video' as 'Video', 629 // We always have a copy of the thumbnail
648 id: this.url, 630 const extension = '.jpg'
649 name: this.name, 631 return this.uuid + extension
650 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
651 duration: 'PT' + this.duration + 'S',
652 uuid: this.uuid,
653 tag,
654 category,
655 licence,
656 language,
657 views: this.views,
658 nsfw: this.nsfw,
659 published: this.createdAt.toISOString(),
660 updated: this.updatedAt.toISOString(),
661 mediaType: 'text/markdown',
662 content: this.getTruncatedDescription(),
663 icon: {
664 type: 'Image',
665 url: getThumbnailUrl(this, baseUrlHttp),
666 mediaType: 'image/jpeg',
667 width: THUMBNAILS_SIZE.width,
668 height: THUMBNAILS_SIZE.height
669 },
670 url,
671 likes: likesObject,
672 dislikes: dislikesObject,
673 shares: sharesObject
674 } 632 }
675 633
676 return videoObject 634 getPreviewName () {
677} 635 const extension = '.jpg'
636 return this.uuid + extension
637 }
678 638
679getTruncatedDescription = function (this: VideoInstance) { 639 getTorrentFileName (videoFile: VideoFileModel) {
680 if (!this.description) return null 640 const extension = '.torrent'
641 return this.uuid + '-' + videoFile.resolution + extension
642 }
681 643
682 const options = { 644 isOwned () {
683 length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max 645 return this.remote === false
684 } 646 }
685 647
686 return truncate(this.description, options) 648 createPreview (videoFile: VideoFileModel) {
687} 649 const imageSize = PREVIEWS_SIZE.width + 'x' + PREVIEWS_SIZE.height
650
651 return generateImageFromVideoFile(
652 this.getVideoFilePath(videoFile),
653 CONFIG.STORAGE.PREVIEWS_DIR,
654 this.getPreviewName(),
655 imageSize
656 )
657 }
688 658
689optimizeOriginalVideofile = async function (this: VideoInstance) { 659 createThumbnail (videoFile: VideoFileModel) {
690 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 660 const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height
691 const newExtname = '.mp4'
692 const inputVideoFile = this.getOriginalFile()
693 const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
694 const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
695 661
696 const transcodeOptions = { 662 return generateImageFromVideoFile(
697 inputPath: videoInputPath, 663 this.getVideoFilePath(videoFile),
698 outputPath: videoOutputPath 664 CONFIG.STORAGE.THUMBNAILS_DIR,
665 this.getThumbnailName(),
666 imageSize
667 )
699 } 668 }
700 669
701 try { 670 getVideoFilePath (videoFile: VideoFileModel) {
702 // Could be very long! 671 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
703 await transcode(transcodeOptions) 672 }
704 673
705 await unlinkPromise(videoInputPath) 674 createTorrentAndSetInfoHash = async function (videoFile: VideoFileModel) {
675 const options = {
676 announceList: [
677 [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
678 ],
679 urlList: [
680 CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
681 ]
682 }
706 683
707 // Important to do this before getVideoFilename() to take in account the new file extension 684 const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options)
708 inputVideoFile.set('extname', newExtname)
709 685
710 await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) 686 const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
711 const stats = await statPromise(this.getVideoFilePath(inputVideoFile)) 687 logger.info('Creating torrent %s.', filePath)
712 688
713 inputVideoFile.set('size', stats.size) 689 await writeFilePromise(filePath, torrent)
714 690
715 await this.createTorrentAndSetInfoHash(inputVideoFile) 691 const parsedTorrent = parseTorrent(torrent)
716 await inputVideoFile.save() 692 videoFile.infoHash = parsedTorrent.infoHash
693 }
717 694
718 } catch (err) { 695 getEmbedPath () {
719 // Auto destruction... 696 return '/videos/embed/' + this.uuid
720 this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) 697 }
721 698
722 throw err 699 getThumbnailPath () {
700 return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName())
723 } 701 }
724}
725 702
726transcodeOriginalVideofile = async function (this: VideoInstance, resolution: VideoResolution) { 703 getPreviewPath () {
727 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 704 return join(STATIC_PATHS.PREVIEWS, this.getPreviewName())
728 const extname = '.mp4' 705 }
729 706
730 // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed 707 toFormattedJSON () {
731 const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile())) 708 let serverHost
732 709
733 const newVideoFile = (Video['sequelize'].models.VideoFile as VideoFileModel).build({ 710 if (this.VideoChannel.Account.Server) {
734 resolution, 711 serverHost = this.VideoChannel.Account.Server.host
735 extname, 712 } else {
736 size: 0, 713 // It means it's our video
737 videoId: this.id 714 serverHost = CONFIG.WEBSERVER.HOST
738 }) 715 }
739 const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile))
740 716
741 const transcodeOptions = { 717 return {
742 inputPath: videoInputPath, 718 id: this.id,
743 outputPath: videoOutputPath, 719 uuid: this.uuid,
744 resolution 720 name: this.name,
721 category: this.category,
722 categoryLabel: this.getCategoryLabel(),
723 licence: this.licence,
724 licenceLabel: this.getLicenceLabel(),
725 language: this.language,
726 languageLabel: this.getLanguageLabel(),
727 nsfw: this.nsfw,
728 description: this.getTruncatedDescription(),
729 serverHost,
730 isLocal: this.isOwned(),
731 accountName: this.VideoChannel.Account.name,
732 duration: this.duration,
733 views: this.views,
734 likes: this.likes,
735 dislikes: this.dislikes,
736 tags: map<TagModel, string>(this.Tags, 'name'),
737 thumbnailPath: this.getThumbnailPath(),
738 previewPath: this.getPreviewPath(),
739 embedPath: this.getEmbedPath(),
740 createdAt: this.createdAt,
741 updatedAt: this.updatedAt
742 }
745 } 743 }
746 744
747 await transcode(transcodeOptions) 745 toFormattedDetailsJSON () {
746 const formattedJson = this.toFormattedJSON()
748 747
749 const stats = await statPromise(videoOutputPath) 748 // Maybe our server is not up to date and there are new privacy settings since our version
749 let privacyLabel = VIDEO_PRIVACIES[this.privacy]
750 if (!privacyLabel) privacyLabel = 'Unknown'
750 751
751 newVideoFile.set('size', stats.size) 752 const detailsJson = {
753 privacyLabel,
754 privacy: this.privacy,
755 descriptionPath: this.getDescriptionPath(),
756 channel: this.VideoChannel.toFormattedJSON(),
757 account: this.VideoChannel.Account.toFormattedJSON(),
758 files: []
759 }
752 760
753 await this.createTorrentAndSetInfoHash(newVideoFile) 761 // Format and sort video files
762 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
763 detailsJson.files = this.VideoFiles
764 .map(videoFile => {
765 let resolutionLabel = videoFile.resolution + 'p'
766
767 return {
768 resolution: videoFile.resolution,
769 resolutionLabel,
770 magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs),
771 size: videoFile.size,
772 torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp),
773 fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp)
774 }
775 })
776 .sort((a, b) => {
777 if (a.resolution < b.resolution) return 1
778 if (a.resolution === b.resolution) return 0
779 return -1
780 })
781
782 return Object.assign(formattedJson, detailsJson)
783 }
754 784
755 await newVideoFile.save() 785 toActivityPubObject (): VideoTorrentObject {
786 const { baseUrlHttp, baseUrlWs } = this.getBaseUrls()
787 if (!this.Tags) this.Tags = []
756 788
757 this.VideoFiles.push(newVideoFile) 789 const tag = this.Tags.map(t => ({
758} 790 type: 'Hashtag' as 'Hashtag',
791 name: t.name
792 }))
759 793
760getOriginalFileHeight = function (this: VideoInstance) { 794 let language
761 const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) 795 if (this.language) {
796 language = {
797 identifier: this.language + '',
798 name: this.getLanguageLabel()
799 }
800 }
762 801
763 return getVideoFileHeight(originalFilePath) 802 let category
764} 803 if (this.category) {
804 category = {
805 identifier: this.category + '',
806 name: this.getCategoryLabel()
807 }
808 }
765 809
766getDescriptionPath = function (this: VideoInstance) { 810 let licence
767 return `/api/${API_VERSION}/videos/${this.uuid}/description` 811 if (this.licence) {
768} 812 licence = {
813 identifier: this.licence + '',
814 name: this.getLicenceLabel()
815 }
816 }
769 817
770getCategoryLabel = function (this: VideoInstance) { 818 let likesObject
771 let categoryLabel = VIDEO_CATEGORIES[this.category] 819 let dislikesObject
772 if (!categoryLabel) categoryLabel = 'Misc'
773 820
774 return categoryLabel 821 if (Array.isArray(this.AccountVideoRates)) {
775} 822 const likes: string[] = []
823 const dislikes: string[] = []
776 824
777getLicenceLabel = function (this: VideoInstance) { 825 for (const rate of this.AccountVideoRates) {
778 let licenceLabel = VIDEO_LICENCES[this.licence] 826 if (rate.type === 'like') {
779 if (!licenceLabel) licenceLabel = 'Unknown' 827 likes.push(rate.Account.url)
828 } else if (rate.type === 'dislike') {
829 dislikes.push(rate.Account.url)
830 }
831 }
780 832
781 return licenceLabel 833 likesObject = activityPubCollection(likes)
782} 834 dislikesObject = activityPubCollection(dislikes)
835 }
783 836
784getLanguageLabel = function (this: VideoInstance) { 837 let sharesObject
785 let languageLabel = VIDEO_LANGUAGES[this.language] 838 if (Array.isArray(this.VideoShares)) {
786 if (!languageLabel) languageLabel = 'Unknown' 839 const shares: string[] = []
787 840
788 return languageLabel 841 for (const videoShare of this.VideoShares) {
789} 842 const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account)
843 shares.push(shareUrl)
844 }
790 845
791removeThumbnail = function (this: VideoInstance) { 846 sharesObject = activityPubCollection(shares)
792 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) 847 }
793 return unlinkPromise(thumbnailPath)
794}
795 848
796removePreview = function (this: VideoInstance) { 849 const url = []
797 // Same name than video thumbnail 850 for (const file of this.VideoFiles) {
798 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) 851 url.push({
799} 852 type: 'Link',
853 mimeType: 'video/' + file.extname.replace('.', ''),
854 url: this.getVideoFileUrl(file, baseUrlHttp),
855 width: file.resolution,
856 size: file.size
857 })
858
859 url.push({
860 type: 'Link',
861 mimeType: 'application/x-bittorrent',
862 url: this.getTorrentUrl(file, baseUrlHttp),
863 width: file.resolution
864 })
865
866 url.push({
867 type: 'Link',
868 mimeType: 'application/x-bittorrent;x-scheme-handler/magnet',
869 url: this.generateMagnetUri(file, baseUrlHttp, baseUrlWs),
870 width: file.resolution
871 })
872 }
800 873
801removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) { 874 // Add video url too
802 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) 875 url.push({
803 return unlinkPromise(filePath) 876 type: 'Link',
804} 877 mimeType: 'text/html',
878 url: CONFIG.WEBSERVER.URL + '/videos/watch/' + this.uuid
879 })
805 880
806removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) { 881 return {
807 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 882 type: 'Video' as 'Video',
808 return unlinkPromise(torrentPath) 883 id: this.url,
809} 884 name: this.name,
885 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
886 duration: 'PT' + this.duration + 'S',
887 uuid: this.uuid,
888 tag,
889 category,
890 licence,
891 language,
892 views: this.views,
893 nsfw: this.nsfw,
894 published: this.createdAt.toISOString(),
895 updated: this.updatedAt.toISOString(),
896 mediaType: 'text/markdown',
897 content: this.getTruncatedDescription(),
898 icon: {
899 type: 'Image',
900 url: this.getThumbnailUrl(baseUrlHttp),
901 mediaType: 'image/jpeg',
902 width: THUMBNAILS_SIZE.width,
903 height: THUMBNAILS_SIZE.height
904 },
905 url,
906 likes: likesObject,
907 dislikes: dislikesObject,
908 shares: sharesObject
909 }
910 }
911
912 getTruncatedDescription () {
913 if (!this.description) return null
810 914
811// ------------------------------ STATICS ------------------------------ 915 const options = {
916 length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max
917 }
812 918
813list = function () { 919 return truncate(this.description, options)
814 const query = {
815 include: [ Video['sequelize'].models.VideoFile ]
816 } 920 }
817 921
818 return Video.findAll(query) 922 optimizeOriginalVideofile = async function () {
819} 923 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
924 const newExtname = '.mp4'
925 const inputVideoFile = this.getOriginalFile()
926 const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile))
927 const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname)
820 928
821listAllAndSharedByAccountForOutbox = function (accountId: number, start: number, count: number) { 929 const transcodeOptions = {
822 function getRawQuery (select: string) { 930 inputPath: videoInputPath,
823 const queryVideo = 'SELECT ' + select + ' FROM "Videos" AS "Video" ' + 931 outputPath: videoOutputPath
824 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + 932 }
825 'WHERE "VideoChannel"."accountId" = ' + accountId
826 const queryVideoShare = 'SELECT ' + select + ' FROM "VideoShares" AS "VideoShare" ' +
827 'INNER JOIN "Videos" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' +
828 'WHERE "VideoShare"."accountId" = ' + accountId
829 933
830 let rawQuery = `(${queryVideo}) UNION (${queryVideoShare})` 934 try {
935 // Could be very long!
936 await transcode(transcodeOptions)
831 937
832 return rawQuery 938 await unlinkPromise(videoInputPath)
833 }
834 939
835 const rawQuery = getRawQuery('"Video"."id"') 940 // Important to do this before getVideoFilename() to take in account the new file extension
836 const rawCountQuery = getRawQuery('COUNT("Video"."id") as "total"') 941 inputVideoFile.set('extname', newExtname)
837 942
838 const query = { 943 await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile))
839 distinct: true, 944 const stats = await statPromise(this.getVideoFilePath(inputVideoFile))
840 offset: start,
841 limit: count,
842 order: [ getSort('createdAt'), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
843 where: {
844 id: {
845 [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')')
846 }
847 },
848 include: [
849 {
850 model: Video['sequelize'].models.VideoShare,
851 required: false,
852 where: {
853 [Sequelize.Op.and]: [
854 {
855 id: {
856 [Sequelize.Op.not]: null
857 }
858 },
859 {
860 accountId
861 }
862 ]
863 },
864 include: [ Video['sequelize'].models.Account ]
865 },
866 {
867 model: Video['sequelize'].models.VideoChannel,
868 required: true,
869 include: [
870 {
871 model: Video['sequelize'].models.Account,
872 required: true
873 }
874 ]
875 },
876 {
877 model: Video['sequelize'].models.AccountVideoRate,
878 include: [ Video['sequelize'].models.Account ]
879 },
880 Video['sequelize'].models.VideoFile,
881 Video['sequelize'].models.Tag
882 ]
883 }
884 945
885 return Bluebird.all([ 946 inputVideoFile.set('size', stats.size)
886 Video.findAll(query),
887 Video['sequelize'].query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT })
888 ]).then(([ rows, totals ]) => {
889 // totals: totalVideos + totalVideoShares
890 let totalVideos = 0
891 let totalVideoShares = 0
892 if (totals[0]) totalVideos = parseInt(totals[0].total, 10)
893 if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10)
894
895 const total = totalVideos + totalVideoShares
896 return {
897 data: rows,
898 total: total
899 }
900 })
901}
902 947
903listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { 948 await this.createTorrentAndSetInfoHash(inputVideoFile)
904 const query = { 949 await inputVideoFile.save()
905 distinct: true,
906 offset: start,
907 limit: count,
908 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
909 include: [
910 {
911 model: Video['sequelize'].models.VideoChannel,
912 required: true,
913 include: [
914 {
915 model: Video['sequelize'].models.Account,
916 where: {
917 userId
918 },
919 required: true
920 }
921 ]
922 },
923 Video['sequelize'].models.Tag
924 ]
925 }
926 950
927 return Video.findAndCountAll(query).then(({ rows, count }) => { 951 } catch (err) {
928 return { 952 // Auto destruction...
929 data: rows, 953 this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err))
930 total: count
931 }
932 })
933}
934 954
935listForApi = function (start: number, count: number, sort: string) { 955 throw err
936 const query = { 956 }
937 distinct: true,
938 offset: start,
939 limit: count,
940 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
941 include: [
942 {
943 model: Video['sequelize'].models.VideoChannel,
944 required: true,
945 include: [
946 {
947 model: Video['sequelize'].models.Account,
948 required: true,
949 include: [
950 {
951 model: Video['sequelize'].models.Server,
952 required: false
953 }
954 ]
955 }
956 ]
957 },
958 Video['sequelize'].models.Tag
959 ],
960 where: createBaseVideosWhere()
961 } 957 }
962 958
963 return Video.findAndCountAll(query).then(({ rows, count }) => { 959 transcodeOriginalVideofile = async function (resolution: VideoResolution) {
964 return { 960 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
965 data: rows, 961 const extname = '.mp4'
966 total: count
967 }
968 })
969}
970 962
971load = function (id: number) { 963 // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
972 return Video.findById(id) 964 const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile()))
973}
974 965
975loadByUUID = function (uuid: string, t?: Sequelize.Transaction) { 966 const newVideoFile = new VideoFileModel({
976 const query: Sequelize.FindOptions<VideoAttributes> = { 967 resolution,
977 where: { 968 extname,
978 uuid 969 size: 0,
979 }, 970 videoId: this.id
980 include: [ Video['sequelize'].models.VideoFile ] 971 })
981 } 972 const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile))
982 973
983 if (t !== undefined) query.transaction = t 974 const transcodeOptions = {
975 inputPath: videoInputPath,
976 outputPath: videoOutputPath,
977 resolution
978 }
984 979
985 return Video.findOne(query) 980 await transcode(transcodeOptions)
986}
987 981
988loadByUrlAndPopulateAccount = function (url: string, t?: Sequelize.Transaction) { 982 const stats = await statPromise(videoOutputPath)
989 const query: Sequelize.FindOptions<VideoAttributes> = {
990 where: {
991 url
992 },
993 include: [
994 Video['sequelize'].models.VideoFile,
995 {
996 model: Video['sequelize'].models.VideoChannel,
997 include: [ Video['sequelize'].models.Account ]
998 }
999 ]
1000 }
1001 983
1002 if (t !== undefined) query.transaction = t 984 newVideoFile.set('size', stats.size)
1003 985
1004 return Video.findOne(query) 986 await this.createTorrentAndSetInfoHash(newVideoFile)
1005}
1006 987
1007loadByUUIDOrURL = function (uuid: string, url: string, t?: Sequelize.Transaction) { 988 await newVideoFile.save()
1008 const query: Sequelize.FindOptions<VideoAttributes> = { 989
1009 where: { 990 this.VideoFiles.push(newVideoFile)
1010 [Sequelize.Op.or]: [
1011 { uuid },
1012 { url }
1013 ]
1014 },
1015 include: [ Video['sequelize'].models.VideoFile ]
1016 } 991 }
1017 992
1018 if (t !== undefined) query.transaction = t 993 getOriginalFileHeight () {
994 const originalFilePath = this.getVideoFilePath(this.getOriginalFile())
1019 995
1020 return Video.findOne(query) 996 return getVideoFileHeight(originalFilePath)
1021} 997 }
1022 998
1023loadAndPopulateAccountAndServerAndTags = function (id: number) { 999 getDescriptionPath () {
1024 const options = { 1000 return `/api/${API_VERSION}/videos/${this.uuid}/description`
1025 order: [ [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
1026 include: [
1027 {
1028 model: Video['sequelize'].models.VideoChannel,
1029 include: [
1030 {
1031 model: Video['sequelize'].models.Account,
1032 include: [ { model: Video['sequelize'].models.Server, required: false } ]
1033 }
1034 ]
1035 },
1036 {
1037 model: Video['sequelize'].models.AccountVideoRate,
1038 include: [ Video['sequelize'].models.Account ]
1039 },
1040 {
1041 model: Video['sequelize'].models.VideoShare,
1042 include: [ Video['sequelize'].models.Account ]
1043 },
1044 Video['sequelize'].models.Tag,
1045 Video['sequelize'].models.VideoFile
1046 ]
1047 } 1001 }
1048 1002
1049 return Video.findById(id, options) 1003 getCategoryLabel () {
1050} 1004 let categoryLabel = VIDEO_CATEGORIES[this.category]
1005 if (!categoryLabel) categoryLabel = 'Misc'
1051 1006
1052loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) { 1007 return categoryLabel
1053 const options = {
1054 order: [ [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
1055 where: {
1056 uuid
1057 },
1058 include: [
1059 {
1060 model: Video['sequelize'].models.VideoChannel,
1061 include: [
1062 {
1063 model: Video['sequelize'].models.Account,
1064 include: [ { model: Video['sequelize'].models.Server, required: false } ]
1065 }
1066 ]
1067 },
1068 {
1069 model: Video['sequelize'].models.AccountVideoRate,
1070 include: [ Video['sequelize'].models.Account ]
1071 },
1072 {
1073 model: Video['sequelize'].models.VideoShare,
1074 include: [ Video['sequelize'].models.Account ]
1075 },
1076 Video['sequelize'].models.Tag,
1077 Video['sequelize'].models.VideoFile
1078 ]
1079 } 1008 }
1080 1009
1081 return Video.findOne(options) 1010 getLicenceLabel () {
1082} 1011 let licenceLabel = VIDEO_LICENCES[this.licence]
1012 if (!licenceLabel) licenceLabel = 'Unknown'
1083 1013
1084searchAndPopulateAccountAndServerAndTags = function (value: string, start: number, count: number, sort: string) { 1014 return licenceLabel
1085 const serverInclude: Sequelize.IncludeOptions = {
1086 model: Video['sequelize'].models.Server,
1087 required: false
1088 } 1015 }
1089 1016
1090 const accountInclude: Sequelize.IncludeOptions = { 1017 getLanguageLabel () {
1091 model: Video['sequelize'].models.Account, 1018 let languageLabel = VIDEO_LANGUAGES[this.language]
1092 include: [ serverInclude ] 1019 if (!languageLabel) languageLabel = 'Unknown'
1020
1021 return languageLabel
1093 } 1022 }
1094 1023
1095 const videoChannelInclude: Sequelize.IncludeOptions = { 1024 removeThumbnail () {
1096 model: Video['sequelize'].models.VideoChannel, 1025 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
1097 include: [ accountInclude ], 1026 return unlinkPromise(thumbnailPath)
1098 required: true
1099 } 1027 }
1100 1028
1101 const tagInclude: Sequelize.IncludeOptions = { 1029 removePreview () {
1102 model: Video['sequelize'].models.Tag 1030 // Same name than video thumbnail
1031 return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName())
1103 } 1032 }
1104 1033
1105 const query: Sequelize.FindOptions<VideoAttributes> = { 1034 removeFile (videoFile: VideoFileModel) {
1106 distinct: true, 1035 const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
1107 where: createBaseVideosWhere(), 1036 return unlinkPromise(filePath)
1108 offset: start,
1109 limit: count,
1110 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
1111 } 1037 }
1112 1038
1113 // TODO: search on tags too 1039 removeTorrent (videoFile: VideoFileModel) {
1114 // const escapedValue = Video['sequelize'].escape('%' + value + '%') 1040 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1115 // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( 1041 return unlinkPromise(torrentPath)
1116 // `(SELECT "VideoTags"."videoId"
1117 // FROM "Tags"
1118 // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId"
1119 // WHERE name ILIKE ${escapedValue}
1120 // )`
1121 // )
1122
1123 // TODO: search on account too
1124 // accountInclude.where = {
1125 // name: {
1126 // [Sequelize.Op.iLike]: '%' + value + '%'
1127 // }
1128 // }
1129 query.where['name'] = {
1130 [Sequelize.Op.iLike]: '%' + value + '%'
1131 } 1042 }
1132 1043
1133 query.include = [ 1044 private getBaseUrls () {
1134 videoChannelInclude, tagInclude 1045 let baseUrlHttp
1135 ] 1046 let baseUrlWs
1136 1047
1137 return Video.findAndCountAll(query).then(({ rows, count }) => { 1048 if (this.isOwned()) {
1138 return { 1049 baseUrlHttp = CONFIG.WEBSERVER.URL
1139 data: rows, 1050 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
1140 total: count 1051 } else {
1052 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host
1053 baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host
1141 } 1054 }
1142 })
1143}
1144
1145// ---------------------------------------------------------------------------
1146 1055
1147function createBaseVideosWhere () { 1056 return { baseUrlHttp, baseUrlWs }
1148 return {
1149 id: {
1150 [Sequelize.Op.notIn]: Video['sequelize'].literal(
1151 '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
1152 )
1153 },
1154 privacy: VideoPrivacy.PUBLIC
1155 } 1057 }
1156}
1157 1058
1158function getBaseUrls (video: VideoInstance) { 1059 private getThumbnailUrl (baseUrlHttp: string) {
1159 let baseUrlHttp 1060 return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName()
1160 let baseUrlWs
1161
1162 if (video.isOwned()) {
1163 baseUrlHttp = CONFIG.WEBSERVER.URL
1164 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
1165 } else {
1166 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Account.Server.host
1167 baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Account.Server.host
1168 } 1061 }
1169 1062
1170 return { baseUrlHttp, baseUrlWs } 1063 private getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
1171} 1064 return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
1172 1065 }
1173function getThumbnailUrl (video: VideoInstance, baseUrlHttp: string) {
1174 return baseUrlHttp + STATIC_PATHS.THUMBNAILS + video.getThumbnailName()
1175}
1176 1066
1177function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { 1067 private getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
1178 return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile) 1068 return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
1179} 1069 }
1180 1070
1181function getVideoFileUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { 1071 private generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) {
1182 return baseUrlHttp + STATIC_PATHS.WEBSEED + video.getVideoFilename(videoFile) 1072 const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
1183} 1073 const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
1074 const urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
1075
1076 const magnetHash = {
1077 xs,
1078 announce,
1079 urlList,
1080 infoHash: videoFile.infoHash,
1081 name: this.name
1082 }
1184 1083
1185function generateMagnetUri (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string, baseUrlWs: string) { 1084 return magnetUtil.encode(magnetHash)
1186 const xs = getTorrentUrl(video, videoFile, baseUrlHttp)
1187 const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
1188 const urlList = [ getVideoFileUrl(video, videoFile, baseUrlHttp) ]
1189
1190 const magnetHash = {
1191 xs,
1192 announce,
1193 urlList,
1194 infoHash: videoFile.infoHash,
1195 name: video.name
1196 } 1085 }
1197
1198 return magnetUtil.encode(magnetHash)
1199} 1086}