diff options
Diffstat (limited to 'server')
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 |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { pageToStartAndCount } from '../../helpers' | 3 | import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers' |
4 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | 4 | import { ACTIVITY_PUB, CONFIG } from '../../initializers' |
5 | 5 | import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send' | |
6 | import { database as db } from '../../initializers' | ||
7 | import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants' | ||
8 | import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send/send-announce' | ||
9 | import { buildVideoAnnounceToFollowers } from '../../lib/index' | 6 | import { buildVideoAnnounceToFollowers } from '../../lib/index' |
10 | import { executeIfActivityPub, localAccountValidator } from '../../middlewares' | 7 | import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares' |
11 | import { asyncMiddleware } from '../../middlewares/async' | 8 | import { |
12 | import { videoChannelsGetValidator, videoChannelsShareValidator } from '../../middlewares/validators/video-channels' | 9 | videoChannelsGetValidator, |
13 | import { videosGetValidator, videosShareValidator } from '../../middlewares/validators/videos' | 10 | videoChannelsShareValidator, |
14 | import { AccountInstance, VideoChannelInstance } from '../../models' | 11 | videosGetValidator, |
15 | import { VideoChannelShareInstance } from '../../models/video/video-channel-share-interface' | 12 | videosShareValidator |
16 | import { VideoInstance } from '../../models/video/video-interface' | 13 | } from '../../middlewares/validators' |
17 | import { VideoShareInstance } from '../../models/video/video-share-interface' | 14 | import { AccountModel } from '../../models/account/account' |
15 | import { AccountFollowModel } from '../../models/account/account-follow' | ||
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
18 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' | ||
19 | import { VideoShareModel } from '../../models/video/video-share' | ||
18 | 20 | ||
19 | const activityPubClientRouter = express.Router() | 21 | const activityPubClientRouter = express.Router() |
20 | 22 | ||
@@ -62,57 +64,57 @@ export { | |||
62 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
63 | 65 | ||
64 | function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { | 66 | function 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 | ||
70 | async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { | 72 | async 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 | ||
82 | async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { | 84 | async 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 | ||
94 | function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { | 96 | function 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 | ||
100 | async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { | 102 | async 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 | ||
107 | async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { | 109 | async 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 | ||
114 | async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) { | 116 | async 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' | |||
2 | import { Activity } from '../../../shared/models/activitypub/activity' | 2 | import { Activity } from '../../../shared/models/activitypub/activity' |
3 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | 3 | import { activityPubCollectionPagination } from '../../helpers/activitypub' |
4 | import { pageToStartAndCount } from '../../helpers/core-utils' | 4 | import { pageToStartAndCount } from '../../helpers/core-utils' |
5 | import { database as db } from '../../initializers' | ||
6 | import { ACTIVITY_PUB } from '../../initializers/constants' | 5 | import { ACTIVITY_PUB } from '../../initializers/constants' |
7 | import { addActivityData } from '../../lib/activitypub/send/send-add' | 6 | import { addActivityData } from '../../lib/activitypub/send/send-add' |
8 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 7 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' |
9 | import { announceActivityData } from '../../lib/index' | 8 | import { announceActivityData } from '../../lib/index' |
10 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' | 9 | import { asyncMiddleware, localAccountValidator } from '../../middlewares' |
11 | import { AccountInstance } from '../../models/account/account-interface' | 10 | import { AccountModel } from '../../models/account/account' |
11 | import { VideoModel } from '../../models/video/video' | ||
12 | 12 | ||
13 | const outboxRouter = express.Router() | 13 | const outboxRouter = express.Router() |
14 | 14 | ||
@@ -26,12 +26,12 @@ export { | |||
26 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
27 | 27 | ||
28 | async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { | 28 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' | 2 | import { UserRight } from '../../../shared/models/users' |
3 | import { paginationValidator } from '../../middlewares/validators/pagination' | 3 | import { getFormattedObjects } from '../../helpers' |
4 | import { database as db } from '../../initializers' | 4 | import { asyncMiddleware, authenticate, ensureUserHasRight, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' |
5 | import { getFormattedObjects } from '../../helpers/utils' | 5 | import { paginationValidator } from '../../middlewares/validators' |
6 | import { authenticate } from '../../middlewares/oauth' | 6 | import { JobModel } from '../../models/job/job' |
7 | import { ensureUserHasRight } from '../../middlewares/user-right' | ||
8 | import { UserRight } from '../../../shared/models/users/user-right.enum' | ||
9 | 7 | ||
10 | const jobsRouter = express.Router() | 8 | const jobsRouter = express.Router() |
11 | 9 | ||
@@ -28,7 +26,7 @@ export { | |||
28 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
29 | 27 | ||
30 | async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { | 28 | async 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' | |||
3 | import { CONFIG } from '../../initializers' | 3 | import { CONFIG } from '../../initializers' |
4 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
5 | import { asyncMiddleware } from '../../middlewares' | 5 | import { asyncMiddleware } from '../../middlewares' |
6 | import { database as db } from '../../initializers/database' | ||
7 | import { OAuthClientLocal } from '../../../shared' | 6 | import { OAuthClientLocal } from '../../../shared' |
7 | import { OAuthClientModel } from '../../models/oauth/oauth-client' | ||
8 | 8 | ||
9 | const oauthClientsRouter = express.Router() | 9 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { getFormattedObjects } from '../../../helpers' | 3 | import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub' |
6 | import { getServerAccount } from '../../../helpers/utils' | 6 | import { sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { getAccountFromWebfinger } from '../../../helpers/webfinger' | ||
8 | import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants' | ||
9 | import { database as db } from '../../../initializers/database' | ||
10 | import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub/account' | ||
11 | import { sendUndoFollow } from '../../../lib/activitypub/send/send-undo' | ||
12 | import { sendFollow } from '../../../lib/index' | 7 | import { sendFollow } from '../../../lib/index' |
13 | import { asyncMiddleware, paginationValidator, removeFollowingValidator, setFollowersSort, setPagination } from '../../../middlewares' | 8 | import { |
14 | import { authenticate } from '../../../middlewares/oauth' | 9 | asyncMiddleware, |
15 | import { setBodyHostsPort } from '../../../middlewares/servers' | 10 | authenticate, |
16 | import { setFollowingSort } from '../../../middlewares/sort' | 11 | ensureUserHasRight, |
17 | import { ensureUserHasRight } from '../../../middlewares/user-right' | 12 | paginationValidator, |
18 | import { followValidator } from '../../../middlewares/validators/follows' | 13 | removeFollowingValidator, |
19 | import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort' | 14 | setBodyHostsPort, |
20 | import { AccountInstance } from '../../../models/account/account-interface' | 15 | setFollowersSort, |
21 | import { AccountFollowInstance } from '../../../models/index' | 16 | setFollowingSort, |
17 | setPagination | ||
18 | } from '../../../middlewares' | ||
19 | import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' | ||
20 | import { AccountModel } from '../../../models/account/account' | ||
21 | import { AccountFollowModel } from '../../../models/account/account-follow' | ||
22 | 22 | ||
23 | const serverFollowsRouter = express.Router() | 23 | const serverFollowsRouter = express.Router() |
24 | 24 | ||
@@ -63,14 +63,14 @@ export { | |||
63 | 63 | ||
64 | async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { | 64 | async 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 | ||
71 | async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { | 71 | async 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 | ||
113 | async function follow (fromAccount: AccountInstance, targetAccount: AccountInstance, targetAlreadyInDB: boolean) { | 113 | async 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 | ||
147 | async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { | 147 | async 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 | ||
165 | async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) { | 165 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' | 2 | import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' |
3 | import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' | 3 | import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' |
4 | import { CONFIG, database as db } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
5 | import { createUserAccountAndChannel } from '../../lib' | 5 | import { createUserAccountAndChannel } from '../../lib' |
6 | import { | 6 | import { |
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' |
24 | import { setVideosSort } from '../../middlewares/sort' | 25 | import { videosSortValidator } from '../../middlewares/validators' |
25 | import { videosSortValidator } from '../../middlewares/validators/sort' | 26 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
26 | import { UserInstance } from '../../models' | 27 | import { UserModel } from '../../models/account/user' |
28 | import { VideoModel } from '../../models/video/video' | ||
27 | 29 | ||
28 | const usersRouter = express.Router() | 30 | const usersRouter = express.Router() |
29 | 31 | ||
@@ -107,8 +109,8 @@ export { | |||
107 | // --------------------------------------------------------------------------- | 109 | // --------------------------------------------------------------------------- |
108 | 110 | ||
109 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 111 | async 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 | ||
128 | async function createUser (req: express.Request) { | 130 | async 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 | |||
155 | async function registerUser (req: express.Request) { | 157 | async 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 | ||
172 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | 174 | async 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 | ||
197 | async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { | 199 | async 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 | ||
203 | async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 205 | async 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 | ||
226 | async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 228 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | |||
3 | import { database as db } from '../../../initializers/database' | ||
4 | import { | 2 | import { |
5 | logger, | 3 | logger, |
6 | getFormattedObjects, | 4 | getFormattedObjects, |
7 | retryTransactionWrapper | 5 | retryTransactionWrapper |
8 | } from '../../../helpers' | 6 | } from '../../../helpers' |
7 | import { sequelizeTypescript } from '../../../initializers' | ||
9 | import { | 8 | import { |
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' |
19 | import { VideoInstance } from '../../../models' | ||
20 | import { VideoAbuseCreate, UserRight } from '../../../../shared' | 18 | import { VideoAbuseCreate, UserRight } from '../../../../shared' |
21 | import { sendVideoAbuse } from '../../../lib/index' | 19 | import { sendVideoAbuse } from '../../../lib/index' |
20 | import { AccountModel } from '../../../models/account/account' | ||
21 | import { VideoModel } from '../../../models/video/video' | ||
22 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
22 | 23 | ||
23 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
24 | 25 | ||
@@ -46,7 +47,7 @@ export { | |||
46 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
47 | 48 | ||
48 | async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { | 49 | async 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 | ||
65 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 66 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | |||
3 | import { database as db } from '../../../initializers' | ||
4 | import { logger, getFormattedObjects } from '../../../helpers' | 2 | import { logger, getFormattedObjects } from '../../../helpers' |
5 | import { | 3 | import { |
6 | authenticate, | 4 | authenticate, |
@@ -13,8 +11,8 @@ import { | |||
13 | setPagination, | 11 | setPagination, |
14 | asyncMiddleware | 12 | asyncMiddleware |
15 | } from '../../../middlewares' | 13 | } from '../../../middlewares' |
16 | import { BlacklistedVideoInstance } from '../../../models' | ||
17 | import { BlacklistedVideo, UserRight } from '../../../../shared' | 14 | import { BlacklistedVideo, UserRight } from '../../../../shared' |
15 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
18 | 16 | ||
19 | const blacklistRouter = express.Router() | 17 | const 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 | ||
64 | async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { | 62 | async 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 | ||
70 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { | 68 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' | 2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' |
3 | import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' | 3 | import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' |
4 | import { database as db } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { createVideoChannel } from '../../../lib' | 5 | import { createVideoChannel } from '../../../lib' |
6 | import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' | 6 | import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' |
7 | import { | 7 | import { |
@@ -17,7 +17,8 @@ import { | |||
17 | videoChannelsSortValidator, | 17 | videoChannelsSortValidator, |
18 | videoChannelsUpdateValidator | 18 | videoChannelsUpdateValidator |
19 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
20 | import { AccountInstance, VideoChannelInstance } from '../../../models' | 20 | import { AccountModel } from '../../../models/account/account' |
21 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
21 | 22 | ||
22 | const videoChannelRouter = express.Router() | 23 | const videoChannelRouter = express.Router() |
23 | 24 | ||
@@ -66,13 +67,13 @@ export { | |||
66 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
67 | 68 | ||
68 | async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { | 69 | async 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 | ||
74 | async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { | 75 | async 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 | ||
94 | async function addVideoChannel (req: express.Request, res: express.Response) { | 95 | async 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 | ||
117 | async function updateVideoChannel (req: express.Request, res: express.Response) { | 118 | async 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 | ||
160 | async function removeVideoChannel (req: express.Request, res: express.Response) { | 161 | async 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 | ||
170 | async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { | 171 | async 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' |
14 | import { getServerAccount } from '../../../helpers/utils' | 14 | import { getServerAccount } from '../../../helpers/utils' |
15 | import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' | 15 | import { |
16 | import { database as db } from '../../../initializers/database' | 16 | CONFIG, |
17 | import { sendAddVideo } from '../../../lib/activitypub/send/send-add' | 17 | sequelizeTypescript, |
18 | import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' | 18 | VIDEO_CATEGORIES, |
19 | import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' | 19 | VIDEO_LANGUAGES, |
20 | import { shareVideoByServer } from '../../../lib/activitypub/share' | 20 | VIDEO_LICENCES, |
21 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | 21 | VIDEO_MIMETYPE_EXT, |
22 | import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' | 22 | VIDEO_PRIVACIES |
23 | } from '../../../initializers' | ||
24 | import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' | ||
25 | import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send' | ||
23 | import { sendCreateViewToVideoFollowers } from '../../../lib/index' | 26 | import { sendCreateViewToVideoFollowers } from '../../../lib/index' |
24 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' | 27 | import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' |
25 | import { | 28 | import { |
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' |
38 | import { VideoInstance } from '../../../models' | 41 | import { TagModel } from '../../../models/video/tag' |
42 | import { VideoModel } from '../../../models/video/video' | ||
43 | import { VideoFileModel } from '../../../models/video/video-file' | ||
39 | import { abuseVideoRouter } from './abuse' | 44 | import { abuseVideoRouter } from './abuse' |
40 | import { blacklistRouter } from './blacklist' | 45 | import { blacklistRouter } from './blacklist' |
41 | import { videoChannelRouter } from './channel' | 46 | import { videoChannelRouter } from './channel' |
@@ -99,7 +104,7 @@ videosRouter.put('/:id', | |||
99 | videosRouter.post('/upload', | 104 | videosRouter.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 | ||
266 | async function updateVideo (req: express.Request, res: express.Response) { | 271 | async 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 | ||
352 | async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 357 | async 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 | ||
369 | async function removeVideo (req: express.Request, res: express.Response) { | 374 | async 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 | ||
379 | async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 384 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserVideoRateUpdate } from '../../../../shared' | 2 | import { UserVideoRateUpdate } from '../../../../shared' |
3 | import { logger, retryTransactionWrapper } from '../../../helpers' | 3 | import { logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { VIDEO_RATE_TYPES } from '../../../initializers' | 4 | import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' |
5 | import { database as db } from '../../../initializers/database' | 5 | import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub' |
6 | import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub/videos' | ||
7 | import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares' | 6 | import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares' |
8 | import { AccountInstance } from '../../../models/account/account-interface' | 7 | import { AccountModel } from '../../../models/account/account' |
9 | import { VideoInstance } from '../../../models/video/video-interface' | 8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
9 | import { VideoModel } from '../../../models/video/video' | ||
10 | 10 | ||
11 | const rateVideoRouter = express.Router() | 11 | const rateVideoRouter = express.Router() |
12 | 12 | ||
@@ -38,12 +38,12 @@ async function rateVideoRetryWrapper (req: express.Request, res: express.Respons | |||
38 | async function rateVideo (req: express.Request, res: express.Response) { | 38 | async 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' | |||
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
5 | |||
6 | import { database as db } from '../initializers/database' | ||
7 | import { | 5 | import { |
8 | CONFIG, | 6 | CONFIG, |
9 | STATIC_PATHS, | 7 | STATIC_PATHS, |
@@ -13,7 +11,7 @@ import { | |||
13 | } from '../initializers' | 11 | } from '../initializers' |
14 | import { root, readFileBufferPromise, escapeHTML } from '../helpers' | 12 | import { root, readFileBufferPromise, escapeHTML } from '../helpers' |
15 | import { asyncMiddleware } from '../middlewares' | 13 | import { asyncMiddleware } from '../middlewares' |
16 | import { VideoInstance } from '../models' | 14 | import { VideoModel } from '../models/video/video' |
17 | 15 | ||
18 | const clientsRouter = express.Router() | 16 | const clientsRouter = express.Router() |
19 | 17 | ||
@@ -49,7 +47,7 @@ export { | |||
49 | 47 | ||
50 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
51 | 49 | ||
52 | function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) { | 50 | function 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 | ||
109 | async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { | 107 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | |||
3 | import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' | 2 | import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' |
4 | import { oembedValidator } from '../middlewares' | 3 | import { asyncMiddleware, oembedValidator } from '../middlewares' |
5 | import { asyncMiddleware } from '../middlewares/async' | 4 | import { VideoModel } from '../models/video/video' |
6 | import { VideoInstance } from '../models' | ||
7 | 5 | ||
8 | const servicesRouter = express.Router() | 6 | const servicesRouter = express.Router() |
9 | 7 | ||
@@ -21,7 +19,7 @@ export { | |||
21 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
22 | 20 | ||
23 | function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { | 21 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as cors from 'cors' | 2 | import * as cors from 'cors' |
3 | |||
4 | import { | 3 | import { |
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares/async' | 2 | import { asyncMiddleware } from '../middlewares' |
3 | import { webfingerValidator } from '../middlewares/validators/webfinger' | 3 | import { webfingerValidator } from '../middlewares/validators' |
4 | import { AccountInstance } from '../models/account/account-interface' | 4 | import { AccountModel } from '../models/account/account' |
5 | 5 | ||
6 | const webfingerRouter = express.Router() | 6 | const webfingerRouter = express.Router() |
7 | 7 | ||
@@ -19,7 +19,7 @@ export { | |||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
21 | function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { | 21 | function 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 @@ | |||
1 | import { Activity } from '../../shared/models/activitypub/activity' | 1 | import { ResultList } from '../../shared/models' |
2 | import { ResultList } from '../../shared/models/result-list.model' | 2 | import { Activity } from '../../shared/models/activitypub' |
3 | import { AccountInstance } from '../models/account/account-interface' | 3 | import { ACTIVITY_PUB } from '../initializers' |
4 | import { AccountModel } from '../models/account/account' | ||
4 | import { signObject } from './peertube-crypto' | 5 | import { signObject } from './peertube-crypto' |
5 | import { ACTIVITY_PUB } from '../initializers/constants' | ||
6 | 6 | ||
7 | function activityPubContextify <T> (data: T) { | 7 | function 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 | ||
74 | function buildSignedActivity (byAccount: AccountInstance, data: Object) { | 74 | function 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' | |||
2 | import { Response } from 'express' | 2 | import { Response } from 'express' |
3 | import 'express-validator' | 3 | import 'express-validator' |
4 | import * as validator from 'validator' | 4 | import * as validator from 'validator' |
5 | import { database as db } from '../../initializers' | 5 | import { AccountModel } from '../../models/account/account' |
6 | import { AccountInstance } from '../../models' | ||
7 | import { isUserUsernameValid } from './users' | 6 | import { isUserUsernameValid } from './users' |
8 | 7 | ||
9 | function isAccountNameValid (value: string) { | 8 | function isAccountNameValid (value: string) { |
@@ -11,24 +10,24 @@ function isAccountNameValid (value: string) { | |||
11 | } | 10 | } |
12 | 11 | ||
13 | function isAccountIdExist (id: number | string, res: Response) { | 12 | function 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 | ||
25 | function isLocalAccountNameExist (name: string, res: Response) { | 24 | function 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 | ||
31 | async function isAccountExist (p: Bluebird<AccountInstance>, res: Response) { | 30 | async 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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' |
3 | import { isAccountNameValid } from '../accounts' | 3 | import { isAccountNameValid } from '../accounts' |
4 | import { exists, isUUIDValid } from '../misc' | 4 | import { exists, isUUIDValid } from '../misc' |
5 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 5 | import { 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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' | 2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
3 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' | 3 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' |
4 | import { isAnnounceActivityValid } from './announce' | 4 | import { isAnnounceActivityValid } from './announce' |
5 | import { isActivityPubUrlValid } from './misc' | 5 | import { isActivityPubUrlValid } from './misc' |
6 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | ||
6 | import { isUndoActivityValid } from './undo' | 7 | import { isUndoActivityValid } from './undo' |
7 | import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' | 8 | import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' |
8 | import { | 9 | import { |
@@ -12,7 +13,6 @@ import { | |||
12 | isVideoTorrentUpdateActivityValid | 13 | isVideoTorrentUpdateActivityValid |
13 | } from './videos' | 14 | } from './videos' |
14 | import { isViewActivityValid } from './view' | 15 | import { isViewActivityValid } from './view' |
15 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | ||
16 | 16 | ||
17 | function isRootActivityValid (activity: any) { | 17 | function 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 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { exists } from '../misc' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' |
3 | import { isTestInstance } from '../../core-utils' | 3 | import { isTestInstance } from '../../core-utils' |
4 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 4 | import { exists } from '../misc' |
5 | 5 | ||
6 | function isActivityPubUrlValid (url: string) { | 6 | function 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 @@ | |||
1 | export * from './activitypub' | ||
2 | export * from './misc' | ||
3 | export * from './servers' | ||
4 | export * from './servers' | ||
5 | export * from './users' | ||
6 | export * from './accounts' | ||
7 | export * from './video-channels' | ||
8 | export * from './videos' | ||
9 | export * 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' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import 'multer' | 3 | import 'multer' |
4 | import * as validator from 'validator' | 4 | import * as validator from 'validator' |
5 | import { CONSTRAINTS_FIELDS, database as db } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
6 | import { VideoChannelInstance } from '../../models' | 6 | import { VideoChannelModel } from '../../models/video/video-channel' |
7 | import { exists } from './misc' | 7 | import { exists } from './misc' |
8 | 8 | ||
9 | const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS | 9 | const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS |
@@ -17,11 +17,11 @@ function isVideoChannelNameValid (value: string) { | |||
17 | } | 17 | } |
18 | 18 | ||
19 | async function isVideoChannelExist (id: string, res: express.Response) { | 19 | async 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' | |||
4 | import 'multer' | 4 | import 'multer' |
5 | import * as validator from 'validator' | 5 | import * as validator from 'validator' |
6 | import { VideoRateType } from '../../../shared' | 6 | import { VideoRateType } from '../../../shared' |
7 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_RATE_TYPES } from '../../initializers' | 7 | import { |
8 | import { VIDEO_PRIVACIES } from '../../initializers/constants' | 8 | CONSTRAINTS_FIELDS, |
9 | import { database as db } from '../../initializers/database' | 9 | VIDEO_CATEGORIES, |
10 | import { VideoInstance } from '../../models/video/video-interface' | 10 | VIDEO_LANGUAGES, |
11 | VIDEO_LICENCES, | ||
12 | VIDEO_PRIVACIES, | ||
13 | VIDEO_RATE_TYPES | ||
14 | } from '../../initializers' | ||
15 | import { VideoModel } from '../../models/video/video' | ||
11 | import { exists, isArray } from './misc' | 16 | import { exists, isArray } from './misc' |
12 | 17 | ||
13 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | 18 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS |
@@ -100,12 +105,12 @@ function isVideoFileSizeValid (value: string) { | |||
100 | } | 105 | } |
101 | 106 | ||
102 | async function isVideoExist (id: string, res: Response) { | 107 | async 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 @@ | |||
1 | import 'express-validator' | 1 | import { CONFIG } from '../../initializers' |
2 | import 'multer' | ||
3 | import { CONFIG } from '../../initializers/constants' | ||
4 | import { exists } from './misc' | 2 | import { exists } from './misc' |
5 | 3 | ||
6 | function isWebfingerResourceValid (value: string) { | 4 | function 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 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' | 2 | import { VideoResolution } from '../../shared/models/videos' |
3 | import { CONFIG } from '../initializers' | 3 | import { CONFIG } from '../initializers' |
4 | 4 | ||
5 | function getVideoFileHeight (path: string) { | 5 | function 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 @@ | |||
1 | export * from './activitypub' | 1 | export * from './activitypub' |
2 | export * from './core-utils' | 2 | export * from './core-utils' |
3 | export * from './logger' | 3 | export * from './logger' |
4 | export * from './custom-validators' | ||
5 | export * from './ffmpeg-utils' | 4 | export * from './ffmpeg-utils' |
6 | export * from './database-utils' | 5 | export * from './database-utils' |
7 | export * from './peertube-crypto' | 6 | export * 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 @@ | |||
2 | import * as mkdirp from 'mkdirp' | 2 | import * as mkdirp from 'mkdirp' |
3 | import * as path from 'path' | 3 | import * as path from 'path' |
4 | import * as winston from 'winston' | 4 | import * as winston from 'winston' |
5 | import { CONFIG } from '../initializers/constants' | 5 | import { CONFIG } from '../initializers' |
6 | 6 | ||
7 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 7 | const 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 @@ | |||
1 | import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' | 1 | import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' |
2 | import { AccountInstance } from '../models/account/account-interface' | 2 | import { AccountModel } from '../models/account/account' |
3 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' | 3 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' |
4 | import { jsig } from './custom-jsonld-signature' | 4 | import { jsig } from './custom-jsonld-signature' |
5 | import { logger } from './logger' | 5 | import { logger } from './logger' |
@@ -13,7 +13,7 @@ async function createPrivateAndPublicKeys () { | |||
13 | return { privateKey: key, publicKey } | 13 | return { privateKey: key, publicKey } |
14 | } | 14 | } |
15 | 15 | ||
16 | function isSignatureVerified (fromAccount: AccountInstance, signedDocument: object) { | 16 | function 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 | ||
43 | function signObject (byAccount: AccountInstance, data: any) { | 43 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Sequelize from 'sequelize' | 2 | import { Model } from 'sequelize-typescript' |
3 | import { ResultList } from '../../shared' | 3 | import { ResultList } from '../../shared' |
4 | import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' | 4 | import { VideoResolution } from '../../shared/models/videos' |
5 | import { CONFIG, database as db } from '../initializers' | 5 | import { CONFIG } from '../initializers' |
6 | import { AccountInstance } from '../models/account/account-interface' | 6 | import { AccountModel } from '../models/account/account' |
7 | import { UserModel } from '../models/account/user' | ||
7 | import { pseudoRandomBytesPromise } from './core-utils' | 8 | import { pseudoRandomBytesPromise } from './core-utils' |
8 | import { logger } from './logger' | 9 | import { 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 | ||
75 | function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields: object) { | 76 | function 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 | ||
82 | let serverAccount: AccountInstance | 83 | let serverAccount: AccountModel |
83 | async function getServerAccount () { | 84 | async 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 @@ | |||
1 | import * as WebFinger from 'webfinger.js' | 1 | import * as WebFinger from 'webfinger.js' |
2 | import { WebFingerData } from '../../shared' | 2 | import { WebFingerData } from '../../shared' |
3 | import { fetchRemoteAccount } from '../lib/activitypub/account' | 3 | import { fetchRemoteAccount } from '../lib/activitypub' |
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub' |
6 | 6 | ||
7 | const webfinger = new WebFinger({ | 7 | const 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 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | import { promisify0 } from '../helpers/core-utils' | 2 | import { promisify0 } from '../helpers' |
3 | import { UserModel } from '../models/account/user-interface' | 3 | import { UserModel } from '../models/account/user' |
4 | import { ApplicationModel } from '../models/application/application-interface' | 4 | import { ApplicationModel } from '../models/application/application' |
5 | import { OAuthClientModel } from '../models/oauth/oauth-client-interface' | 5 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
6 | 6 | ||
7 | // Some checks on configuration files | 7 | // Some checks on configuration files |
8 | function checkConfig () { | 8 | function 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) |
60 | async function clientsExist (OAuthClient: OAuthClientModel) { | 60 | async 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) |
67 | async function usersExist (User: UserModel) { | 67 | async 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) |
74 | async function applicationExist (Application: ApplicationModel) { | 74 | async 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 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | 3 | import { JobCategory, JobState, VideoRateType } from '../../shared/models' | |
4 | import { FollowState } from '../../shared/models/accounts' | ||
5 | import { 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 |
5 | import { root, isTestInstance } from '../helpers/core-utils' | 7 | import { isTestInstance, root } from '../helpers/core-utils' |
6 | |||
7 | import { | ||
8 | VideoRateType, | ||
9 | JobState, | ||
10 | JobCategory | ||
11 | } from '../../shared/models' | ||
12 | import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum' | ||
13 | import { 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 @@ | |||
1 | import { join } from 'path' | 1 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' |
2 | import { flattenDepth } from 'lodash' | 2 | import { isTestInstance } from '../helpers/core-utils' |
3 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 3 | import { logger } from '../helpers/logger' |
4 | import * as Sequelize from 'sequelize' | ||
5 | import { AvatarModel } from '../models/avatar' | ||
6 | 4 | ||
5 | import { AccountModel } from '../models/account/account' | ||
6 | import { AccountFollowModel } from '../models/account/account-follow' | ||
7 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | ||
8 | import { UserModel } from '../models/account/user' | ||
9 | import { ApplicationModel } from '../models/application/application' | ||
10 | import { AvatarModel } from '../models/avatar/avatar' | ||
11 | import { JobModel } from '../models/job/job' | ||
12 | import { OAuthClientModel } from '../models/oauth/oauth-client' | ||
13 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | ||
14 | import { ServerModel } from '../models/server/server' | ||
15 | import { TagModel } from '../models/video/tag' | ||
16 | import { VideoModel } from '../models/video/video' | ||
17 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
18 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
19 | import { VideoChannelModel } from '../models/video/video-channel' | ||
20 | import { VideoChannelShareModel } from '../models/video/video-channel-share' | ||
21 | import { VideoFileModel } from '../models/video/video-file' | ||
22 | import { VideoShareModel } from '../models/video/video-share' | ||
23 | import { VideoTagModel } from '../models/video/video-tag' | ||
7 | import { CONFIG } from './constants' | 24 | import { CONFIG } from './constants' |
8 | // Do not use barrel, we need to load database first | ||
9 | import { logger } from '../helpers/logger' | ||
10 | import { isTestInstance, readdirPromise } from '../helpers/core-utils' | ||
11 | 25 | ||
12 | import { VideoModel } from './../models/video/video-interface' | 26 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
13 | import { VideoTagModel } from './../models/video/video-tag-interface' | ||
14 | import { BlacklistedVideoModel } from './../models/video/video-blacklist-interface' | ||
15 | import { VideoFileModel } from './../models/video/video-file-interface' | ||
16 | import { VideoAbuseModel } from './../models/video/video-abuse-interface' | ||
17 | import { VideoChannelModel } from './../models/video/video-channel-interface' | ||
18 | import { UserModel } from '../models/account/user-interface' | ||
19 | import { AccountVideoRateModel } from '../models/account/account-video-rate-interface' | ||
20 | import { AccountFollowModel } from '../models/account/account-follow-interface' | ||
21 | import { TagModel } from './../models/video/tag-interface' | ||
22 | import { ServerModel } from '../models/server/server-interface' | ||
23 | import { OAuthTokenModel } from './../models/oauth/oauth-token-interface' | ||
24 | import { OAuthClientModel } from './../models/oauth/oauth-client-interface' | ||
25 | import { JobModel } from './../models/job/job-interface' | ||
26 | import { AccountModel } from './../models/account/account-interface' | ||
27 | import { ApplicationModel } from './../models/application/application-interface' | ||
28 | import { VideoChannelShareModel } from '../models/video/video-channel-share-interface' | ||
29 | import { VideoShareModel } from '../models/video/video-share-interface' | ||
30 | 27 | ||
31 | const dbname = CONFIG.DATABASE.DBNAME | 28 | const dbname = CONFIG.DATABASE.DBNAME |
32 | const username = CONFIG.DATABASE.USERNAME | 29 | const username = CONFIG.DATABASE.USERNAME |
33 | const password = CONFIG.DATABASE.PASSWORD | 30 | const password = CONFIG.DATABASE.PASSWORD |
34 | 31 | ||
35 | export type PeerTubeDatabase = { | 32 | const 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 | |||
60 | const database: PeerTubeDatabase = {} | ||
61 | |||
62 | const 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 | ||
82 | database.sequelize = sequelize | 53 | async function initDatabase (silent: boolean) { |
83 | 54 | sequelizeTypescript.addModels([ | |
84 | database.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 | ||
118 | export { | 83 | export { |
119 | database | 84 | initDatabase, |
120 | } | 85 | sequelizeTypescript |
121 | |||
122 | // --------------------------------------------------------------------------- | ||
123 | |||
124 | async 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 @@ | |||
1 | import * as passwordGenerator from 'password-generator' | 1 | import * as passwordGenerator from 'password-generator' |
2 | import { UserRole } from '../../shared' | 2 | import { UserRole } from '../../shared' |
3 | import { logger, mkdirpPromise, rimrafPromise } from '../helpers' | 3 | import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers' |
4 | import { createUserAccountAndChannel } from '../lib' | 4 | import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib' |
5 | import { createLocalAccountWithoutKeys } from '../lib/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { ApplicationModel } from '../models/application/application' | ||
7 | import { OAuthClientModel } from '../models/oauth/oauth-client' | ||
6 | import { applicationExist, clientsExist, usersExist } from './checker' | 8 | import { applicationExist, clientsExist, usersExist } from './checker' |
7 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' | 9 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' |
8 | import { database as db } from './database' | 10 | import { sequelizeTypescript } from './database' |
9 | import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' | ||
10 | 11 | ||
11 | async function installApplication () { | 12 | async 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 | ||
66 | async function createOAuthClientIfNotExist () { | 67 | async 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 | ||
89 | async function createOAuthAdminIfNotExist () { | 90 | async 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 | ||
130 | async function createApplicationIfNotExist () { | 131 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | import { stat } from 'fs' | 3 | import { stat } from 'fs' |
4 | 4 | import { VideoModel } from '../../models/video/video' | |
5 | import { VideoInstance } from '../../models' | ||
6 | 5 | ||
7 | function up (utils: { | 6 | function 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' | |||
4 | import { shareVideoByServer } from '../../lib/activitypub/share' | 4 | import { shareVideoByServer } from '../../lib/activitypub/share' |
5 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' | 5 | import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' |
6 | import { createLocalAccountWithoutKeys } from '../../lib/user' | 6 | import { createLocalAccountWithoutKeys } from '../../lib/user' |
7 | import { ApplicationModel } from '../../models/application/application' | ||
7 | import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' | 8 | import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' |
8 | import { PeerTubeDatabase } from '../database' | ||
9 | 9 | ||
10 | async function up (utils: { | 10 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { PeerTubeDatabase } from '../database' | ||
3 | 2 | ||
4 | async function up (utils: { | 3 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { PeerTubeDatabase } from '../database' | ||
3 | 2 | ||
4 | async function up (utils: { | 3 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { PeerTubeDatabase } from '../database' | ||
3 | 2 | ||
4 | async function up (utils: { | 3 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { CONSTRAINTS_FIELDS } from '../constants' | 2 | import { CONSTRAINTS_FIELDS } from '../constants' |
3 | import { PeerTubeDatabase } from '../database' | ||
4 | 3 | ||
5 | async function up (utils: { | 4 | async 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 @@ | |||
1 | import * as path from 'path' | 1 | import * as path from 'path' |
2 | |||
3 | import { database as db } from './database' | ||
4 | import { LAST_MIGRATION_VERSION } from './constants' | ||
5 | import { logger, readdirPromise } from '../helpers' | 2 | import { logger, readdirPromise } from '../helpers' |
3 | import { ApplicationModel } from '../models/application/application' | ||
4 | import { LAST_MIGRATION_VERSION } from './constants' | ||
5 | import { sequelizeTypescript } from './database' | ||
6 | 6 | ||
7 | async function migrate () { | 7 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as url from 'url' | ||
3 | import { ActivityPubActor } from '../../../shared/models/activitypub/activitypub-actor' | ||
4 | import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub/account' | ||
5 | import { retryTransactionWrapper } from '../../helpers/database-utils' | ||
6 | import { logger } from '../../helpers/logger' | ||
7 | import { doRequest } from '../../helpers/requests' | ||
8 | import { ACTIVITY_PUB } from '../../initializers/constants' | ||
9 | import { database as db } from '../../initializers/database' | ||
10 | import { AccountInstance } from '../../models/account/account-interface' | ||
11 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
3 | import * as url from 'url' | ||
4 | import { ActivityPubActor } from '../../../shared/models/activitypub' | ||
5 | import { doRequest, logger, retryTransactionWrapper } from '../../helpers' | ||
6 | import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub' | ||
7 | import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers' | ||
8 | import { AccountModel } from '../../models/account/account' | ||
9 | import { ServerModel } from '../../models/server/server' | ||
12 | 10 | ||
13 | async function getOrCreateAccountAndServer (accountUrl: string) { | 11 | async 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 | ||
31 | function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transaction): Bluebird<AccountInstance> | Promise<AccountInstance> { | 29 | function 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 | ||
108 | export { | 104 | export { |
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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { AccountInstance } from '../../models/account/account-interface' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' | 3 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' |
4 | 4 | ||
5 | async function addFetchOutboxJob (account: AccountInstance, t: Transaction) { | 5 | async 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 @@ | |||
1 | import * as magnetUtil from 'magnet-uri' | 1 | import * as magnetUtil from 'magnet-uri' |
2 | import { VideoTorrentObject } from '../../../../shared' | 2 | import { VideoTorrentObject } from '../../../../shared' |
3 | import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object' | 3 | import { VideoChannelObject } from '../../../../shared/models/activitypub/objects' |
4 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 4 | import { VideoPrivacy } from '../../../../shared/models/videos' |
5 | import { doRequest } from '../../../helpers' | ||
5 | import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' | 6 | import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' |
6 | import { doRequest } from '../../../helpers/requests' | 7 | import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' |
7 | import { database as db } from '../../../initializers' | 8 | import { AccountModel } from '../../../models/account/account' |
8 | import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants' | 9 | import { VideoModel } from '../../../models/video/video' |
9 | import { AccountInstance } from '../../../models/account/account-interface' | 10 | import { VideoChannelModel } from '../../../models/video/video-channel' |
10 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 11 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' |
11 | import { VideoFileAttributes } from '../../../models/video/video-file-interface' | 12 | import { VideoShareModel } from '../../../models/video/video-share' |
12 | import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface' | ||
13 | import { getOrCreateAccountAndServer } from '../account' | 13 | import { getOrCreateAccountAndServer } from '../account' |
14 | 14 | ||
15 | function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { | 15 | function 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 | ||
28 | async function videoActivityObjectToDBAttributes ( | 28 | async 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 | ||
83 | function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) { | 81 | function 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 | ||
118 | async function addVideoShares (instance: VideoInstance, shares: string[]) { | 116 | async 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 | ||
142 | async function addVideoChannelShares (instance: VideoChannelInstance, shares: string[]) { | 140 | async 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 @@ | |||
1 | import { ActivityAccept } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
2 | import { database as db } from '../../../initializers' | 2 | import { AccountModel } from '../../../models/account/account' |
3 | import { AccountInstance } from '../../../models/account/account-interface' | 3 | import { AccountFollowModel } from '../../../models/account/account-follow' |
4 | import { addFetchOutboxJob } from '../fetch' | 4 | import { addFetchOutboxJob } from '../fetch' |
5 | 5 | ||
6 | async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) { | 6 | async 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 | ||
22 | async function processAccept (account: AccountInstance, targetAccount: AccountInstance) { | 22 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { VideoTorrentObject } from '../../../../shared' | 2 | import { VideoTorrentObject } from '../../../../shared' |
3 | import { ActivityAdd } from '../../../../shared/models/activitypub/activity' | 3 | import { ActivityAdd } from '../../../../shared/models/activitypub' |
4 | import { VideoRateType } from '../../../../shared/models/videos/video-rate.type' | 4 | import { VideoRateType } from '../../../../shared/models/videos' |
5 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 5 | import { logger, retryTransactionWrapper } from '../../../helpers' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { sequelizeTypescript } from '../../../initializers' |
7 | import { database as db } from '../../../initializers' | 7 | import { AccountModel } from '../../../models/account/account' |
8 | import { AccountInstance } from '../../../models/account/account-interface' | 8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
9 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 9 | import { TagModel } from '../../../models/video/tag' |
10 | import { VideoInstance } from '../../../models/video/video-interface' | 10 | import { VideoModel } from '../../../models/video/video' |
11 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
12 | import { VideoFileModel } from '../../../models/video/video-file' | ||
11 | import { getOrCreateAccountAndServer } from '../account' | 13 | import { getOrCreateAccountAndServer } from '../account' |
12 | import { getOrCreateVideoChannel } from '../video-channels' | 14 | import { getOrCreateVideoChannel } from '../video-channels' |
13 | import { generateThumbnailFromUrl } from '../videos' | 15 | import { generateThumbnailFromUrl } from '../videos' |
@@ -37,9 +39,9 @@ export { | |||
37 | 39 | ||
38 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
39 | 41 | ||
40 | async function processAddVideo (account: AccountInstance, | 42 | async 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 | ||
67 | function addRemoteVideo (account: AccountInstance, | 69 | function 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 | ||
110 | async function createRates (accountUrls: string[], video: VideoInstance, rate: VideoRateType) { | 112 | async 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 @@ | |||
1 | import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { database as db } from '../../../initializers/index' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { AccountInstance } from '../../../models/account/account-interface' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoInstance } from '../../../models/index' | 6 | import { VideoChannelModel } from '../../../models/video/video-channel' |
7 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 7 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' |
8 | import { VideoShareModel } from '../../../models/video/video-share' | ||
8 | import { getOrCreateAccountAndServer } from '../account' | 9 | import { getOrCreateAccountAndServer } from '../account' |
9 | import { forwardActivity } from '../send/misc' | 10 | import { forwardActivity } from '../send/misc' |
10 | import { processAddActivity } from './process-add' | 11 | import { processAddActivity } from './process-add' |
@@ -36,7 +37,7 @@ export { | |||
36 | 37 | ||
37 | // --------------------------------------------------------------------------- | 38 | // --------------------------------------------------------------------------- |
38 | 39 | ||
39 | function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { | 40 | function 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 | ||
48 | async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { | 49 | async 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 | ||
75 | function processVideoShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { | 76 | function 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 | ||
84 | function shareVideo (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { | 85 | function 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 @@ | |||
1 | import { ActivityCreate, VideoChannelObject } from '../../../../shared' | 1 | import { ActivityCreate, VideoChannelObject } from '../../../../shared' |
2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' | 2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' |
3 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' | ||
4 | import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object' | ||
5 | import { logger, retryTransactionWrapper } from '../../../helpers' | 3 | import { logger, retryTransactionWrapper } from '../../../helpers' |
6 | import { database as db } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
7 | import { AccountInstance } from '../../../models/account/account-interface' | 5 | import { AccountModel } from '../../../models/account/account' |
6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | ||
7 | import { VideoModel } from '../../../models/video/video' | ||
8 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
9 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { getOrCreateAccountAndServer } from '../account' | 10 | import { getOrCreateAccountAndServer } from '../account' |
9 | import { forwardActivity } from '../send/misc' | 11 | import { forwardActivity } from '../send/misc' |
10 | import { getVideoChannelActivityPubUrl } from '../url' | 12 | import { getVideoChannelActivityPubUrl } from '../url' |
@@ -37,7 +39,7 @@ export { | |||
37 | 39 | ||
38 | // --------------------------------------------------------------------------- | 40 | // --------------------------------------------------------------------------- |
39 | 41 | ||
40 | async function processCreateDislike (byAccount: AccountInstance, activity: ActivityCreate) { | 42 | async 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 | ||
49 | function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreate) { | 51 | function 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 | ||
76 | async function processCreateView (byAccount: AccountInstance, activity: ActivityCreate) { | 78 | async 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 | ||
95 | async function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { | 97 | async 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 | ||
110 | function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { | 112 | function 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 | ||
128 | function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { | 130 | function 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 | ||
137 | function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { | 139 | function 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 @@ | |||
1 | import { ActivityDelete } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityDelete } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { database as db } from '../../../initializers' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { AccountInstance } from '../../../models/account/account-interface' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 6 | import { VideoChannelModel } from '../../../models/video/video-channel' |
7 | import { VideoInstance } from '../../../models/video/video-interface' | ||
8 | import { getOrCreateAccountAndServer } from '../account' | 7 | import { getOrCreateAccountAndServer } from '../account' |
9 | 8 | ||
10 | async function processDeleteActivity (activity: ActivityDelete) { | 9 | async 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 | ||
42 | async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) { | 41 | async 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 | ||
51 | async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) { | 50 | async 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 | ||
65 | async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { | 64 | async 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 | ||
74 | async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { | 73 | async 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 | ||
88 | async function processDeleteAccount (accountToRemove: AccountInstance) { | 87 | async 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 | ||
97 | async function deleteRemoteAccount (accountToRemove: AccountInstance) { | 96 | async 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 @@ | |||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers' | 2 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3 | import { database as db } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountInstance } from '../../../models/account/account-interface' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { AccountFollowModel } from '../../../models/account/account-follow' |
6 | import { sendAccept } from '../send/send-accept' | ||
7 | import { getOrCreateAccountAndServer } from '../account' | 6 | import { getOrCreateAccountAndServer } from '../account' |
7 | import { sendAccept } from '../send' | ||
8 | 8 | ||
9 | async function processFollowActivity (activity: ActivityFollow) { | 9 | async 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 | ||
24 | function processFollow (account: AccountInstance, targetAccountURL: string) { | 24 | function 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 | ||
33 | async function follow (account: AccountInstance, targetAccountURL: string) { | 33 | async 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 @@ | |||
1 | import { ActivityLike } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityLike } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers' |
3 | import { database as db } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { AccountInstance } from '../../../models/account/account-interface' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | ||
6 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getOrCreateAccountAndServer } from '../account' | 7 | import { getOrCreateAccountAndServer } from '../account' |
6 | import { forwardActivity } from '../send/misc' | 8 | import { forwardActivity } from '../send/misc' |
7 | 9 | ||
@@ -19,7 +21,7 @@ export { | |||
19 | 21 | ||
20 | // --------------------------------------------------------------------------- | 22 | // --------------------------------------------------------------------------- |
21 | 23 | ||
22 | async function processLikeVideo (byAccount: AccountInstance, activity: ActivityLike) { | 24 | async 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 | ||
31 | function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) { | 33 | function 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 @@ | |||
1 | import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' |
2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' | 2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' |
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { logger, retryTransactionWrapper } from '../../../helpers' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { database as db } from '../../../initializers' | 5 | import { AccountModel } from '../../../models/account/account' |
6 | import { AccountFollowModel } from '../../../models/account/account-follow' | ||
7 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | ||
8 | import { VideoModel } from '../../../models/video/video' | ||
6 | import { forwardActivity } from '../send/misc' | 9 | import { forwardActivity } from '../send/misc' |
7 | 10 | ||
8 | async function processUndoActivity (activity: ActivityUndo) { | 11 | async function processUndoActivity (activity: ActivityUndo) { |
@@ -41,14 +44,14 @@ function processUndoLike (actor: string, activity: ActivityUndo) { | |||
41 | function undoLike (actor: string, activity: ActivityUndo) { | 44 | function 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) { | |||
74 | function undoDislike (actor: string, activity: ActivityUndo) { | 77 | function 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 | ||
107 | function undoFollow (actor: string, followActivity: ActivityFollow) { | 110 | function 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { VideoChannelObject, VideoTorrentObject } from '../../../../shared' | 2 | import { VideoChannelObject, VideoTorrentObject } from '../../../../shared' |
3 | import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' | 3 | import { ActivityUpdate } from '../../../../shared/models/activitypub' |
4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { resetSequelizeInstance } from '../../../helpers/utils' | 6 | import { AccountModel } from '../../../models/account/account' |
7 | import { database as db } from '../../../initializers' | 7 | import { TagModel } from '../../../models/video/tag' |
8 | import { AccountInstance } from '../../../models/account/account-interface' | 8 | import { VideoModel } from '../../../models/video/video' |
9 | import { VideoInstance } from '../../../models/video/video-interface' | 9 | import { VideoChannelModel } from '../../../models/video/video-channel' |
10 | import { VideoFileModel } from '../../../models/video/video-file' | ||
10 | import { getOrCreateAccountAndServer } from '../account' | 11 | import { getOrCreateAccountAndServer } from '../account' |
11 | import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' | 12 | import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' |
12 | 13 | ||
@@ -30,7 +31,7 @@ export { | |||
30 | 31 | ||
31 | // --------------------------------------------------------------------------- | 32 | // --------------------------------------------------------------------------- |
32 | 33 | ||
33 | function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject) { | 34 | function 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 | ||
42 | async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpdate: VideoTorrentObject) { | 43 | async 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 | ||
104 | async function processUpdateVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { | 105 | async 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 | ||
113 | async function updateRemoteVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { | 114 | async 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 @@ | |||
1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' | 1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers' |
3 | import { AccountInstance } from '../../../models/account/account-interface' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { processAcceptActivity } from './process-accept' | 4 | import { processAcceptActivity } from './process-accept' |
5 | import { processAddActivity } from './process-add' | 5 | import { processAddActivity } from './process-add' |
6 | import { processAnnounceActivity } from './process-announce' | 6 | import { processAnnounceActivity } from './process-announce' |
@@ -11,7 +11,7 @@ import { processLikeActivity } from './process-like' | |||
11 | import { processUndoActivity } from './process-undo' | 11 | import { processUndoActivity } from './process-undo' |
12 | import { processUpdateActivity } from './process-update' | 12 | import { processUpdateActivity } from './process-update' |
13 | 13 | ||
14 | const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = { | 14 | const 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 | ||
26 | async function processActivities (activities: Activity[], signatureAccount?: AccountInstance, inboxAccount?: AccountInstance) { | 26 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { Activity } from '../../../../shared/models/activitypub/activity' | 2 | import { Activity } from '../../../../shared/models/activitypub' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers' |
4 | import { ACTIVITY_PUB, database as db } from '../../../initializers' | 4 | import { ACTIVITY_PUB } from '../../../initializers' |
5 | import { AccountInstance } from '../../../models/account/account-interface' | 5 | import { AccountModel } from '../../../models/account/account' |
6 | import { VideoChannelInstance } from '../../../models/index' | 6 | import { AccountFollowModel } from '../../../models/account/account-follow' |
7 | import { VideoInstance } from '../../../models/video/video-interface' | 7 | import { VideoModel } from '../../../models/video/video' |
8 | import { | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
9 | activitypubHttpJobScheduler, | 9 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' |
10 | ActivityPubHttpPayload | 10 | import { VideoShareModel } from '../../../models/video/video-share' |
11 | } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' | 11 | import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' |
12 | 12 | ||
13 | async function forwardActivity ( | 13 | async 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 | ||
46 | async function broadcastToFollowers ( | 46 | async 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 | ||
70 | async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) { | 70 | async 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 | ||
82 | function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo: AccountInstance[]) { | 82 | function 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 | ||
89 | function getOriginVideoChannelAudience (videoChannel: VideoChannelInstance, accountsInvolved: AccountInstance[]) { | 89 | function 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 | ||
96 | function getObjectFollowersAudience (accountsInvolvedInObject: AccountInstance[]) { | 96 | function 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 | ||
103 | async function getAccountsInvolvedInVideo (video: VideoInstance, t: Transaction) { | 103 | async 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 | ||
110 | async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { | 110 | async 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 | ||
117 | async function getAudience (accountSender: AccountInstance, t: Transaction, isPublic = true) { | 117 | async 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 | ||
135 | async function computeFollowerUris (toAccountFollower: AccountInstance[], followersException: AccountInstance[], t: Transaction) { | 135 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAccept } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
3 | import { AccountInstance } from '../../../models' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { AccountFollowInstance } from '../../../models/account/account-follow-interface' | 4 | import { AccountFollowModel } from '../../../models/account/account-follow' |
5 | import { unicastTo } from './misc' | ||
6 | import { getAccountFollowAcceptActivityPubUrl } from '../url' | 5 | import { getAccountFollowAcceptActivityPubUrl } from '../url' |
6 | import { unicastTo } from './misc' | ||
7 | 7 | ||
8 | async function sendAccept (accountFollow: AccountFollowInstance, t: Transaction) { | 8 | async 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 | ||
26 | function acceptActivityData (url: string, byAccount: AccountInstance) { | 26 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAdd } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityAdd } from '../../../../shared/models/activitypub' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { AccountInstance, VideoInstance } from '../../../models' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { broadcastToFollowers, getAudience } from './misc' | 6 | import { broadcastToFollowers, getAudience } from './misc' |
6 | 7 | ||
7 | async function sendAddVideo (video: VideoInstance, t: Transaction) { | 8 | async 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 | ||
16 | async function addActivityData ( | 17 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAdd } from '../../../../shared/index' | 2 | import { ActivityAdd } from '../../../../shared/index' |
3 | import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' | 3 | import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
4 | import { AccountInstance, VideoInstance } from '../../../models' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { VideoChannelInstance } from '../../../models/video/video-channel-interface' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { getAnnounceActivityPubUrl } from '../url' | 7 | import { getAnnounceActivityPubUrl } from '../url' |
7 | import { | 8 | import { |
8 | broadcastToFollowers, | 9 | broadcastToFollowers, |
@@ -17,7 +18,7 @@ import { | |||
17 | import { addActivityData } from './send-add' | 18 | import { addActivityData } from './send-add' |
18 | import { createActivityData } from './send-create' | 19 | import { createActivityData } from './send-create' |
19 | 20 | ||
20 | async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 21 | async 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 | ||
33 | async function sendVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 32 | async 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 | ||
39 | async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 38 | async 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 | ||
52 | async function buildVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { | 51 | async 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 | ||
63 | async function sendVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { | 60 | async 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 | ||
69 | async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { | 66 | async 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 | ||
80 | async function announceActivityData ( | 77 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
3 | import { getServerAccount } from '../../../helpers/utils' | 3 | import { getServerAccount } from '../../../helpers' |
4 | import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' | 8 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' |
7 | import { | 9 | import { |
8 | broadcastToFollowers, | 10 | broadcastToFollowers, |
@@ -13,7 +15,7 @@ import { | |||
13 | unicastTo | 15 | unicastTo |
14 | } from './misc' | 16 | } from './misc' |
15 | 17 | ||
16 | async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { | 18 | async 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 | ||
25 | async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) { | 27 | async 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 | ||
34 | async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 36 | async 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 | ||
45 | async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 47 | async 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 | ||
59 | async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 61 | async 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 | ||
70 | async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 72 | async 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 | ||
82 | async function createActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction, audience?: ActivityAudience) { | 84 | async 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 | ||
99 | function createDislikeActivityData (byAccount: AccountInstance, video: VideoInstance) { | 105 | function 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 | ||
124 | function createViewActivityData (byAccount: AccountInstance, video: VideoInstance) { | 128 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityDelete } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityDelete } from '../../../../shared/models/activitypub' |
3 | import { database as db } from '../../../initializers' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | ||
5 | import { broadcastToFollowers } from './misc' | 8 | import { broadcastToFollowers } from './misc' |
6 | 9 | ||
7 | async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { | 10 | async 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 | ||
18 | async function sendDeleteVideo (video: VideoInstance, t: Transaction) { | 21 | async 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 | ||
29 | async function sendDeleteAccount (account: AccountInstance, t: Transaction) { | 32 | async 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 | ||
45 | function deleteActivityData (url: string, byAccount: AccountInstance) { | 48 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityFollow } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
3 | import { AccountInstance } from '../../../models' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { AccountFollowInstance } from '../../../models/account/account-follow-interface' | 4 | import { AccountFollowModel } from '../../../models/account/account-follow' |
5 | import { getAccountFollowActivityPubUrl } from '../url' | 5 | import { getAccountFollowActivityPubUrl } from '../url' |
6 | import { unicastTo } from './misc' | 6 | import { unicastTo } from './misc' |
7 | 7 | ||
8 | function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) { | 8 | function 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 | ||
18 | function followActivityData (url: string, byAccount: AccountInstance, targetAccount: AccountInstance) { | 18 | function 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' |
3 | import { AccountInstance, VideoInstance } from '../../../models' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { getVideoLikeActivityPubUrl } from '../url' | 5 | import { getVideoLikeActivityPubUrl } from '../url' |
5 | import { | 6 | import { |
6 | broadcastToFollowers, | 7 | broadcastToFollowers, |
@@ -11,7 +12,7 @@ import { | |||
11 | unicastTo | 12 | unicastTo |
12 | } from './misc' | 13 | } from './misc' |
13 | 14 | ||
14 | async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 15 | async 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 | ||
24 | async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 25 | async 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 | ||
35 | async function likeActivityData ( | 36 | async 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' |
9 | import { AccountInstance } from '../../../models' | 9 | import { AccountModel } from '../../../models/account/account' |
10 | import { AccountFollowInstance } from '../../../models/account/account-follow-interface' | 10 | import { AccountFollowModel } from '../../../models/account/account-follow' |
11 | import { VideoInstance } from '../../../models/video/video-interface' | 11 | import { VideoModel } from '../../../models/video/video' |
12 | import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 12 | import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
13 | import { | 13 | import { |
14 | broadcastToFollowers, | 14 | broadcastToFollowers, |
@@ -22,7 +22,7 @@ import { createActivityData, createDislikeActivityData } from './send-create' | |||
22 | import { followActivityData } from './send-follow' | 22 | import { followActivityData } from './send-follow' |
23 | import { likeActivityData } from './send-like' | 23 | import { likeActivityData } from './send-like' |
24 | 24 | ||
25 | async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) { | 25 | async 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 | ||
38 | async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 38 | async 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 | ||
50 | async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 50 | async 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 | ||
63 | async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 63 | async 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 | ||
77 | async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { | 77 | async 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 | ||
104 | async function undoActivityData ( | 104 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' | 2 | import { ActivityUpdate } from '../../../../shared/models/activitypub' |
3 | import { database as db } from '../../../initializers' | 3 | import { AccountModel } from '../../../models/account/account' |
4 | import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
6 | import { VideoChannelShareModel } from '../../../models/video/video-channel-share' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | ||
5 | import { getUpdateActivityPubUrl } from '../url' | 8 | import { getUpdateActivityPubUrl } from '../url' |
6 | import { broadcastToFollowers, getAudience } from './misc' | 9 | import { broadcastToFollowers, getAudience } from './misc' |
7 | 10 | ||
8 | async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { | 11 | async 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 | ||
21 | async function sendUpdateVideo (video: VideoInstance, t: Transaction) { | 24 | async 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 | ||
43 | async function updateActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction) { | 46 | async 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 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { getServerAccount } from '../../helpers/utils' | 2 | import { getServerAccount } from '../../helpers' |
3 | import { database as db } from '../../initializers' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { VideoChannelInstance } from '../../models/index' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
5 | import { VideoInstance } from '../../models/video/video-interface' | 5 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' |
6 | import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send/send-announce' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send' | ||
7 | 8 | ||
8 | async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) { | 9 | async 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 | ||
19 | async function shareVideoByServer (video: VideoInstance, t: Transaction) { | 20 | async 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 @@ | |||
1 | import { CONFIG } from '../../initializers/constants' | 1 | import { CONFIG } from '../../initializers' |
2 | import { VideoInstance } from '../../models/video/video-interface' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import { VideoChannelInstance } from '../../models/video/video-channel-interface' | 3 | import { AccountFollowModel } from '../../models/account/account-follow' |
4 | import { VideoAbuseInstance } from '../../models/video/video-abuse-interface' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { AccountFollowInstance } from '../../models/account/account-follow-interface' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | import { AccountInstance } from '../../models/account/account-interface' | 6 | import { VideoChannelModel } from '../../models/video/video-channel' |
7 | 7 | ||
8 | function getVideoActivityPubUrl (video: VideoInstance) { | 8 | function 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 | ||
12 | function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) { | 12 | function 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 | ||
20 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) { | 20 | function 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 | ||
24 | function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { | 24 | function 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 | ||
28 | function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { | 28 | function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { |
29 | return byAccount.url + '/likes/' + video.id | 29 | return byAccount.url + '/likes/' + video.id |
30 | } | 30 | } |
31 | 31 | ||
32 | function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { | 32 | function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { |
33 | return byAccount.url + '/dislikes/' + video.id | 33 | return byAccount.url + '/dislikes/' + video.id |
34 | } | 34 | } |
35 | 35 | ||
36 | function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { | 36 | function 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 | ||
43 | function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { | 43 | function 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 | ||
50 | function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { | 50 | function 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 @@ | |||
1 | import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' | 1 | import { VideoChannelObject } from '../../../shared/models/activitypub/objects' |
2 | import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub/video-channels' | 2 | import { doRequest, logger } from '../../helpers' |
3 | import { logger } from '../../helpers/logger' | 3 | import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub' |
4 | import { doRequest } from '../../helpers/requests' | 4 | import { ACTIVITY_PUB } from '../../initializers' |
5 | import { database as db } from '../../initializers' | 5 | import { AccountModel } from '../../models/account/account' |
6 | import { ACTIVITY_PUB } from '../../initializers/constants' | 6 | import { VideoChannelModel } from '../../models/video/video-channel' |
7 | import { AccountInstance } from '../../models/account/account-interface' | ||
8 | import { videoChannelActivityObjectToDBAttributes } from './process/misc' | 7 | import { videoChannelActivityObjectToDBAttributes } from './process/misc' |
9 | 8 | ||
10 | async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { | 9 | async 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 | ||
25 | async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { | 24 | async 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' | |||
2 | import * as request from 'request' | 2 | import * as request from 'request' |
3 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
4 | import { ActivityIconObject } from '../../../shared/index' | 4 | import { ActivityIconObject } from '../../../shared/index' |
5 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 5 | import { doRequest, doRequestAndSaveToFile } from '../../helpers' |
6 | import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants' | 6 | import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers' |
7 | import { AccountInstance } from '../../models/account/account-interface' | 7 | import { AccountModel } from '../../models/account/account' |
8 | import { VideoInstance } from '../../models/video/video-interface' | 8 | import { VideoModel } from '../../models/video/video' |
9 | import { sendLikeToOrigin } from './index' | ||
10 | import { sendCreateDislikeToOrigin, sendCreateDislikeToVideoFollowers } from './send/send-create' | ||
11 | import { sendLikeToVideoFollowers } from './send/send-like' | ||
12 | import { | 9 | import { |
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 | ||
19 | function fetchRemoteVideoPreview (video: VideoInstance) { | 20 | function 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 | ||
27 | async function fetchRemoteVideoDescription (video: VideoInstance) { | 28 | async 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 | ||
40 | function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { | 41 | function 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 | ||
51 | async function sendVideoRateChangeToFollowers ( | 52 | async 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 | ||
71 | async function sendVideoRateChangeToOrigin ( | 72 | async 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 @@ | |||
1 | import * as asyncLRU from 'async-lru' | 1 | import * as asyncLRU from 'async-lru' |
2 | import { join } from 'path' | ||
3 | import { createWriteStream } from 'fs' | 2 | import { createWriteStream } from 'fs' |
4 | 3 | import { join } from 'path' | |
5 | import { database as db, CONFIG, CACHE } from '../../initializers' | ||
6 | import { logger, unlinkPromise } from '../../helpers' | 4 | import { logger, unlinkPromise } from '../../helpers' |
7 | import { VideoInstance } from '../../models' | 5 | import { CACHE, CONFIG } from '../../initializers' |
8 | import { fetchRemoteVideoPreview } from '../activitypub/videos' | 6 | import { VideoModel } from '../../models/video/video' |
7 | import { fetchRemoteVideoPreview } from '../activitypub' | ||
9 | 8 | ||
10 | class VideosPreviewCache { | 9 | class 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 @@ | |||
1 | import { logger } from '../../../helpers' | 1 | import { doRequest, logger } from '../../../helpers' |
2 | import { doRequest } from '../../../helpers/requests' | ||
3 | import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' | 2 | import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' |
4 | 3 | ||
5 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 4 | async 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 @@ | |||
1 | import { logger } from '../../../helpers' | 1 | import { doRequest, logger } from '../../../helpers' |
2 | import { doRequest } from '../../../helpers/requests' | 2 | import { ACTIVITY_PUB } from '../../../initializers' |
3 | import { ACTIVITY_PUB } from '../../../initializers/constants' | 3 | import { processActivities } from '../../activitypub/process' |
4 | import { processActivities } from '../../activitypub/process/process' | ||
5 | import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' | 4 | import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' |
6 | 5 | ||
7 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 6 | async 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 @@ | |||
1 | import { JobCategory } from '../../../../shared' | 1 | import { JobCategory } from '../../../../shared' |
2 | import { buildSignedActivity } from '../../../helpers/activitypub' | 2 | import { buildSignedActivity, logger } from '../../../helpers' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { ACTIVITY_PUB } from '../../../initializers' |
4 | import { ACTIVITY_PUB } from '../../../initializers/constants' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { database as db } from '../../../initializers/database' | ||
6 | import { JobHandler, JobScheduler } from '../job-scheduler' | 5 | import { JobHandler, JobScheduler } from '../job-scheduler' |
7 | 6 | ||
8 | import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' | 7 | import * 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 @@ | |||
1 | import { logger } from '../../../helpers' | 1 | import { doRequest, logger } from '../../../helpers' |
2 | import { doRequest } from '../../../helpers/requests' | ||
3 | import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' | 2 | import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' |
4 | 3 | ||
5 | async function process (payload: ActivityPubHttpPayload, jobId: number) { | 4 | async 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' | |||
2 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | import { JobCategory } from '../../../shared' | 3 | import { JobCategory } from '../../../shared' |
4 | import { logger } from '../../helpers' | 4 | import { logger } from '../../helpers' |
5 | import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' | 5 | import { JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' |
6 | import { JobInstance } from '../../models' | 6 | import { JobModel } from '../../models/job/job' |
7 | 7 | ||
8 | export interface JobHandler<P, T> { | 8 | export 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 @@ | |||
1 | import { JobCategory } from '../../../../shared' | 1 | import { JobCategory } from '../../../../shared' |
2 | import { VideoModel } from '../../../models/video/video' | ||
2 | import { JobHandler, JobScheduler } from '../job-scheduler' | 3 | import { JobHandler, JobScheduler } from '../job-scheduler' |
4 | |||
3 | import * as videoFileOptimizer from './video-file-optimizer-handler' | 5 | import * as videoFileOptimizer from './video-file-optimizer-handler' |
4 | import * as videoFileTranscoder from './video-file-transcoder-handler' | 6 | import * as videoFileTranscoder from './video-file-transcoder-handler' |
5 | import { VideoInstance } from '../../../models/video/video-interface' | ||
6 | 7 | ||
7 | type TranscodingJobPayload = { | 8 | type TranscodingJobPayload = { |
8 | videoUUID: string | 9 | videoUUID: string |
9 | resolution?: number | 10 | resolution?: number |
10 | } | 11 | } |
11 | const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoInstance> } = { | 12 | const 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { computeResolutionsToTranscode, logger } from '../../../helpers' | 2 | import { computeResolutionsToTranscode, logger } from '../../../helpers' |
3 | import { database as db } from '../../../initializers/database' | 3 | import { sequelizeTypescript } from '../../../initializers' |
4 | import { VideoInstance } from '../../../models' | 4 | import { VideoModel } from '../../../models/video/video' |
5 | import { sendAddVideo } from '../../activitypub/send/send-add' | 5 | import { shareVideoByServer } from '../../activitypub' |
6 | import { sendAddVideo } from '../../activitypub/send' | ||
6 | import { JobScheduler } from '../job-scheduler' | 7 | import { JobScheduler } from '../job-scheduler' |
7 | import { TranscodingJobPayload } from './transcoding-job-scheduler' | 8 | import { TranscodingJobPayload } from './transcoding-job-scheduler' |
8 | import { shareVideoByServer } from '../../activitypub/share' | ||
9 | 9 | ||
10 | async function process (data: TranscodingJobPayload, jobId: number) { | 10 | async 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 | ||
28 | async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: JobScheduler<TranscodingJobPayload, VideoInstance>) { | 28 | async 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 @@ | |||
1 | import { VideoResolution } from '../../../../shared' | 1 | import { VideoResolution } from '../../../../shared' |
2 | import { logger } from '../../../helpers' | 2 | import { logger } from '../../../helpers' |
3 | import { database as db } from '../../../initializers/database' | 3 | import { VideoModel } from '../../../models/video/video' |
4 | import { VideoInstance } from '../../../models' | 4 | import { sendUpdateVideo } from '../../activitypub/send' |
5 | import { sendUpdateVideo } from '../../activitypub/send/send-update' | ||
6 | 5 | ||
7 | async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) { | 6 | async 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 | ||
25 | async function onSuccess (jobId: number, video: VideoInstance) { | 24 | async 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 @@ | |||
1 | import { OAuthClientInstance, UserInstance } from '../models' | ||
2 | import { database as db } from '../initializers/database' | ||
3 | import { logger } from '../helpers' | 1 | import { logger } from '../helpers' |
2 | import { UserModel } from '../models/account/user' | ||
3 | import { OAuthClientModel } from '../models/oauth/oauth-client' | ||
4 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | ||
4 | 5 | ||
5 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 6 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
6 | 7 | ||
@@ -9,25 +10,25 @@ type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpires | |||
9 | function getAccessToken (bearerToken: string) { | 10 | function 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 | ||
15 | function getClient (clientId: string, clientSecret: string) { | 16 | function 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 | ||
21 | function getRefreshToken (refreshToken: string) { | 22 | function 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 | ||
27 | async function getUser (username: string, password: string) { | 28 | async 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 | ||
39 | async function revokeToken (tokenInfo: TokenInfo) { | 40 | async 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 | ||
56 | async function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserInstance) { | 57 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' | 2 | import { createPrivateAndPublicKeys, logger } from '../helpers' |
3 | import { database as db } from '../initializers' | 3 | import { CONFIG, sequelizeTypescript } from '../initializers' |
4 | import { CONFIG } from '../initializers/constants' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { UserInstance } from '../models' | 5 | import { UserModel } from '../models/account/user' |
6 | import { getAccountActivityPubUrl } from './activitypub' | ||
6 | import { createVideoChannel } from './video-channel' | 7 | import { createVideoChannel } from './video-channel' |
7 | import { logger } from '../helpers/logger' | ||
8 | import { getAccountActivityPubUrl } from './activitypub/url' | ||
9 | 8 | ||
10 | async function createUserAccountAndChannel (user: UserInstance, validateUser = true) { | 9 | async 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 | |||
38 | async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { | 37 | async 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoChannelCreate } from '../../shared/models' | 2 | import { VideoChannelCreate } from '../../shared/models' |
3 | import { database as db } from '../initializers' | 3 | import { AccountModel } from '../models/account/account' |
4 | import { AccountInstance } from '../models' | 4 | import { VideoChannelModel } from '../models/video/video-channel' |
5 | import { getVideoChannelActivityPubUrl } from './activitypub/url' | 5 | import { getVideoChannelActivityPubUrl } from './activitypub' |
6 | 6 | ||
7 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { | 7 | async 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' | |||
2 | import { NextFunction, Request, RequestHandler, Response } from 'express' | 2 | import { NextFunction, Request, RequestHandler, Response } from 'express' |
3 | import { ActivityPubSignature } from '../../shared' | 3 | import { ActivityPubSignature } from '../../shared' |
4 | import { isSignatureVerified, logger } from '../helpers' | 4 | import { isSignatureVerified, logger } from '../helpers' |
5 | import { database as db } from '../initializers' | 5 | import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers' |
6 | import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers/constants' | 6 | import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub' |
7 | import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub/account' | 7 | import { AccountModel } from '../models/account/account' |
8 | 8 | ||
9 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 9 | async 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 @@ | |||
1 | import 'express-validator' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import 'express-validator' | |
4 | import { SortType } from '../helpers' | 3 | import { SortType } from '../helpers' |
5 | import { database } from '../initializers' | ||
6 | 4 | ||
7 | function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 5 | function 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 @@ | |||
1 | import 'express-validator' | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
3 | 2 | import 'express-validator' | |
4 | import { UserInstance } from '../models' | ||
5 | import { UserRight } from '../../shared' | 3 | import { UserRight } from '../../shared' |
6 | import { logger } from '../helpers' | 4 | import { logger } from '../helpers' |
5 | import { UserModel } from '../models/account/user' | ||
7 | 6 | ||
8 | function ensureUserHasRight (userRight: UserRight) { | 7 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator/check' | 2 | import { param } from 'express-validator/check' |
3 | import { logger, isLocalAccountNameExist } from '../../helpers' | 3 | import { logger } from '../../helpers' |
4 | import { isAccountNameValid } from '../../helpers/custom-validators/accounts' | 4 | import { isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | 6 | ||
7 | const localAccountValidator = [ | 7 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isRootActivityValid, logger } from '../../../helpers' | 3 | import { logger } from '../../../helpers' |
4 | import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub' | ||
4 | import { areValidationErrors } from '../utils' | 5 | import { areValidationErrors } from '../utils' |
5 | 6 | ||
6 | const activityPubValidator = [ | 7 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isDateValid, isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid, logger } from '../../../helpers' | 3 | import { logger } from '../../../helpers' |
4 | import { isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid } from '../../../helpers/custom-validators/activitypub' | ||
5 | import { isDateValid } from '../../../helpers/custom-validators/misc' | ||
4 | import { areValidationErrors } from '../utils' | 6 | import { areValidationErrors } from '../utils' |
5 | 7 | ||
6 | const signatureValidator = [ | 8 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param } from 'express-validator/check' |
3 | import { isTestInstance } from '../../helpers/core-utils' | 3 | import { getServerAccount, isTestInstance, logger } from '../../helpers' |
4 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | ||
4 | import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' | 5 | import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 6 | import { CONFIG } from '../../initializers' |
6 | import { CONFIG, database as db } from '../../initializers' | 7 | import { AccountFollowModel } from '../../models/account/account-follow' |
7 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
8 | import { getServerAccount } from '../../helpers/utils' | ||
9 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | ||
10 | 9 | ||
11 | const followValidator = [ | 10 | const 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator/check' | 2 | import { query } from 'express-validator/check' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { isIdOrUUIDValid, isTestInstance, logger } from '../../helpers' | 4 | import { isTestInstance, logger } from '../../helpers' |
5 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | ||
6 | import { isVideoExist } from '../../helpers/custom-validators/videos' | ||
5 | import { CONFIG } from '../../initializers' | 7 | import { CONFIG } from '../../initializers' |
6 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
7 | import { isVideoExist } from '../../helpers/custom-validators/videos' | ||
8 | 9 | ||
9 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' | 10 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' |
10 | const videoWatchRegex = new RegExp('([^/]+)$') | 11 | const 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 @@ | |||
1 | import { query } from 'express-validator/check' | 1 | import { query } from 'express-validator/check' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | |||
4 | import { logger } from '../../helpers' | 3 | import { logger } from '../../helpers' |
5 | import { SORTABLE_COLUMNS } from '../../initializers' | 4 | import { SORTABLE_COLUMNS } from '../../initializers' |
6 | import { areValidationErrors } from './utils' | 5 | import { 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param } from 'express-validator/check' | 3 | import { body, param } from 'express-validator/check' |
4 | import { isSignupAllowed, logger } from '../../helpers' | ||
5 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | ||
4 | import { | 6 | import { |
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' | ||
14 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 13 | import { isVideoExist } from '../../helpers/custom-validators/videos' |
15 | import { database as db } from '../../initializers/database' | 14 | import { UserModel } from '../../models/account/user' |
16 | import { areValidationErrors } from './utils' | 15 | import { areValidationErrors } from './utils' |
17 | 16 | ||
18 | const usersAddValidator = [ | 17 | const usersAddValidator = [ |
@@ -153,7 +152,7 @@ export { | |||
153 | // --------------------------------------------------------------------------- | 152 | // --------------------------------------------------------------------------- |
154 | 153 | ||
155 | async function checkUserIdExist (id: number, res: express.Response) { | 154 | async 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 | ||
170 | async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { | 169 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { validationResult } from 'express-validator/check' | 2 | import { validationResult } from 'express-validator/check' |
3 | |||
4 | import { logger } from '../../helpers' | 3 | import { logger } from '../../helpers' |
5 | 4 | ||
6 | function areValidationErrors (req: express.Request, res: express.Response) { | 5 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator/check' | 2 | import { param } from 'express-validator/check' |
3 | import { isIdOrUUIDValid, logger } from '../../helpers' | 3 | import { logger } from '../../helpers' |
4 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | ||
4 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 5 | import { isVideoExist } from '../../helpers/custom-validators/videos' |
5 | import { database as db } from '../../initializers/database' | 6 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoInstance } from '../../models/video/video-interface' | 7 | import { VideoBlacklistModel } from '../../models/video/video-blacklist' |
7 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
8 | 9 | ||
9 | const videosBlacklistRemoveValidator = [ | 10 | const videosBlacklistRemoveValidator = [ |
@@ -42,7 +43,7 @@ export { | |||
42 | } | 43 | } |
43 | // --------------------------------------------------------------------------- | 44 | // --------------------------------------------------------------------------- |
44 | 45 | ||
45 | function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) { | 46 | function 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 | ||
57 | async function checkVideoIsBlacklisted (video: VideoInstance, res: express.Response) { | 58 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param } from 'express-validator/check' |
3 | import { UserRight } from '../../../shared' | 3 | import { UserRight } from '../../../shared' |
4 | import { isIdValid } from '../../helpers/custom-validators/misc' | 4 | import { logger } from '../../helpers' |
5 | import { isAccountIdExist } from '../../helpers/custom-validators/accounts' | ||
6 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | ||
5 | import { | 7 | import { |
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' |
10 | import { isIdOrUUIDValid } from '../../helpers/index' | 12 | import { UserModel } from '../../models/account/user' |
11 | import { logger } from '../../helpers/logger' | 13 | import { VideoChannelModel } from '../../models/video/video-channel' |
12 | import { database as db } from '../../initializers' | 14 | import { VideoChannelShareModel } from '../../models/video/video-channel-share' |
13 | import { UserInstance } from '../../models' | ||
14 | import { areValidationErrors } from './utils' | 15 | import { areValidationErrors } from './utils' |
15 | import { isAccountIdExist } from '../../helpers/custom-validators/accounts' | ||
16 | import { VideoChannelInstance } from '../../models/video/video-channel-interface' | ||
17 | 16 | ||
18 | const listVideoAccountChannelsValidator = [ | 17 | const 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 | ||
137 | function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: VideoChannelInstance, res: express.Response) { | 136 | function 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 | ||
161 | async function checkVideoChannelIsNotTheLastOne (res: express.Response) { | 160 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | ||
2 | import { body, param, query } from 'express-validator/check' | 3 | import { body, param, query } from 'express-validator/check' |
3 | import { UserRight, VideoPrivacy } from '../../../shared' | 4 | import { UserRight, VideoPrivacy } from '../../../shared' |
5 | import { getDurationFromVideoFile, logger } from '../../helpers' | ||
4 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | 6 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' |
5 | import { | 7 | import { |
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' |
19 | import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' | ||
20 | import { logger } from '../../helpers/logger' | ||
21 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 21 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
22 | import { database as db } from '../../initializers/database' | 22 | import { UserModel } from '../../models/account/user' |
23 | import { UserInstance } from '../../models/account/user-interface' | 23 | import { VideoModel } from '../../models/video/video' |
24 | import { VideoInstance } from '../../models/video/video-interface' | 24 | import { VideoChannelModel } from '../../models/video/video-channel' |
25 | import { VideoShareModel } from '../../models/video/video-share' | ||
25 | import { authenticate } from '../oauth' | 26 | import { authenticate } from '../oauth' |
26 | import { areValidationErrors } from './utils' | 27 | import { 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 | ||
252 | function checkUserCanDeleteVideo (user: UserInstance, video: VideoInstance, res: express.Response) { | 253 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { query } from 'express-validator/check' | 2 | import { query } from 'express-validator/check' |
3 | import { logger } from '../../helpers' | ||
3 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' | 4 | import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' |
4 | import { logger } from '../../helpers/logger' | 5 | import { AccountModel } from '../../models/account/account' |
5 | import { database as db } from '../../initializers' | ||
6 | import { areValidationErrors } from './utils' | 6 | import { areValidationErrors } from './utils' |
7 | 7 | ||
8 | const webfingerValidator = [ | 8 | const 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { AccountFollow, FollowState } from '../../../shared/models/accounts/follow.model' | ||
4 | import { ResultList } from '../../../shared/models/result-list.model' | ||
5 | import { AccountInstance } from './account-interface' | ||
6 | |||
7 | export 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 | |||
33 | export 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 | |||
43 | export interface AccountFollowAttributes { | ||
44 | accountId: number | ||
45 | targetAccountId: number | ||
46 | state: FollowState | ||
47 | } | ||
48 | |||
49 | export 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 | |||
60 | export 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
1 | import { values } from 'lodash' | 2 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
3 | 4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | |
4 | import { addMethodsToModel, getSort } from '../utils' | 5 | import { FollowState } from '../../../shared/models/accounts' |
5 | import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | 6 | import { FOLLOW_STATES } from '../../initializers/constants' |
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { AccountModel } from './account' | ||
7 | 10 | ||
8 | let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> | 11 | @Table({ |
9 | let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget | 12 | tableName: 'accountFollow', |
10 | let listFollowingForApi: AccountFollowMethods.ListFollowingForApi | 13 | indexes: [ |
11 | let listFollowersForApi: AccountFollowMethods.ListFollowersForApi | ||
12 | let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi | ||
13 | let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi | ||
14 | let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls | ||
15 | let toFormattedJSON: AccountFollowMethods.ToFormattedJSON | ||
16 | |||
17 | export 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 | 26 | export 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 | ||
60 | function 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 | ||
80 | toFormattedJSON = 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 | |||
96 | loadByAccountAndTarget = 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 | ||
120 | listFollowingForApi = 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 | ||
152 | listFollowersForApi = 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 | ||
184 | listAcceptedFollowerUrlsForApi = 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 | ||
188 | listAcceptedFollowerSharedInboxUrls = 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 | ||
192 | listAcceptedFollowingUrlsForApi = 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>[] = [] | |
198 | async 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { Account as FormattedAccount, ActivityPubActor } from '../../../shared' | ||
4 | import { AvatarInstance } from '../avatar' | ||
5 | import { ServerInstance } from '../server/server-interface' | ||
6 | import { VideoChannelInstance } from '../video/video-channel-interface' | ||
7 | |||
8 | export 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 | |||
27 | export 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 | |||
37 | export 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 | |||
58 | export 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 | |||
76 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { VideoRateType } from '../../../shared/models/videos/video-rate.type' | ||
5 | import { AccountInstance } from './account-interface' | ||
6 | |||
7 | export namespace AccountVideoRateMethods { | ||
8 | export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<AccountVideoRateInstance> | ||
9 | } | ||
10 | |||
11 | export interface AccountVideoRateClass { | ||
12 | load: AccountVideoRateMethods.Load | ||
13 | } | ||
14 | |||
15 | export interface AccountVideoRateAttributes { | ||
16 | type: VideoRateType | ||
17 | accountId: number | ||
18 | videoId: number | ||
19 | |||
20 | Account?: AccountInstance | ||
21 | } | ||
22 | |||
23 | export interface AccountVideoRateInstance | ||
24 | extends AccountVideoRateClass, AccountVideoRateAttributes, Sequelize.Instance<AccountVideoRateAttributes> { | ||
25 | id: number | ||
26 | createdAt: Date | ||
27 | updatedAt: Date | ||
28 | } | ||
29 | |||
30 | export 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 | */ | ||
4 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
5 | import * as Sequelize from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
6 | 3 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | |
4 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' | ||
5 | import { VideoRateType } from '../../../shared/models/videos' | ||
7 | import { VIDEO_RATE_TYPES } from '../../initializers' | 6 | import { VIDEO_RATE_TYPES } from '../../initializers' |
7 | import { VideoModel } from '../video/video' | ||
8 | import { AccountModel } from './account' | ||
8 | 9 | ||
9 | import { addMethodsToModel } from '../utils' | 10 | /* |
10 | import { | 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 | |||
17 | let AccountVideoRate: Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes> | ||
18 | let load: AccountVideoRateMethods.Load | ||
19 | |||
20 | export 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 | }) | ||
22 | export 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 | ||
50 | function 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 | ||
68 | load = 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 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | import { | ||
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' | ||
3 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | 19 | import { Avatar } from '../../../shared/models/avatars/avatar.model' |
20 | import { activityPubContextify } from '../../helpers' | ||
4 | import { | 21 | import { |
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' |
12 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 28 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' |
13 | import { AVATARS_DIR } from '../../initializers' | 29 | import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' |
14 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' | 30 | import { sendDeleteAccount } from '../../lib/activitypub/send' |
15 | import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' | 31 | import { ApplicationModel } from '../application/application' |
16 | import { addMethodsToModel } from '../utils' | 32 | import { AvatarModel } from '../avatar/avatar' |
17 | import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' | 33 | import { ServerModel } from '../server/server' |
18 | 34 | import { throwIfNotValid } from '../utils' | |
19 | let Account: Sequelize.Model<AccountInstance, AccountAttributes> | 35 | import { VideoChannelModel } from '../video/video-channel' |
20 | let load: AccountMethods.Load | 36 | import { AccountFollowModel } from './account-follow' |
21 | let loadApplication: AccountMethods.LoadApplication | 37 | import { UserModel } from './user' |
22 | let loadByUUID: AccountMethods.LoadByUUID | 38 | |
23 | let loadByUrl: AccountMethods.LoadByUrl | 39 | @Table({ |
24 | let loadLocalByName: AccountMethods.LoadLocalByName | 40 | tableName: 'account', |
25 | let loadByNameAndHost: AccountMethods.LoadByNameAndHost | 41 | indexes: [ |
26 | let listByFollowersUrls: AccountMethods.ListByFollowersUrls | ||
27 | let isOwned: AccountMethods.IsOwned | ||
28 | let toActivityPubObject: AccountMethods.ToActivityPubObject | ||
29 | let toFormattedJSON: AccountMethods.ToFormattedJSON | ||
30 | let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls | ||
31 | let getFollowingUrl: AccountMethods.GetFollowingUrl | ||
32 | let getFollowersUrl: AccountMethods.GetFollowersUrl | ||
33 | let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl | ||
34 | |||
35 | export 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 | 62 | export 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 | ||
208 | function 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 | ||
268 | function 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 | ||
276 | toFormattedJSON = 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 | ||
310 | toActivityPubObject = 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 | ||
337 | isOwned = function (this: AccountInstance) { | 290 | return AccountModel.findOne(query) |
338 | return this.serverId === null | 291 | } |
339 | } | ||
340 | 292 | ||
341 | getFollowerSharedInboxUrls = 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 | ||
361 | getFollowingUrl = 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 | ||
365 | getFollowersUrl = 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 | ||
369 | getPublicKeyUrl = 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 | ||
375 | loadApplication = 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 | |||
386 | load = function (id: number) { | ||
387 | return Account.findById(id) | ||
388 | } | ||
389 | |||
390 | loadByUUID = 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 | ||
400 | loadLocalByName = 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 | |||
422 | loadByNameAndHost = 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 | |||
441 | loadByUrl = 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 | |||
452 | listByFollowersUrls = 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 @@ | |||
1 | export * from './account-interface' | ||
2 | export * from './account-follow-interface' | ||
3 | export * from './account-video-rate-interface' | ||
4 | export * 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { ResultList } from '../../../shared/models/result-list.model' | ||
4 | import { UserRight } from '../../../shared/models/users/user-right.enum' | ||
5 | import { UserRole } from '../../../shared/models/users/user-role' | ||
6 | import { User as FormattedUser } from '../../../shared/models/users/user.model' | ||
7 | import { AccountInstance } from './account-interface' | ||
8 | |||
9 | export 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 | |||
30 | export 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 | |||
45 | export 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 | |||
57 | export 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 | |||
67 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { | ||
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' | ||
2 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 16 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
3 | import { | 17 | import { |
4 | comparePassword, | 18 | comparePassword, |
5 | cryptPassword, | 19 | cryptPassword |
6 | isUserDisplayNSFWValid, | ||
7 | isUserPasswordValid, | ||
8 | isUserRoleValid, | ||
9 | isUserUsernameValid, | ||
10 | isUserVideoQuotaValid | ||
11 | } from '../../helpers' | 20 | } from '../../helpers' |
12 | import { addMethodsToModel, getSort } from '../utils' | 21 | import { |
13 | import { UserAttributes, UserInstance, UserMethods } from './user-interface' | 22 | isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, |
14 | 23 | isUserVideoQuotaValid | |
15 | let User: Sequelize.Model<UserInstance, UserAttributes> | 24 | } from '../../helpers/custom-validators/users' |
16 | let isPasswordMatch: UserMethods.IsPasswordMatch | 25 | import { OAuthTokenModel } from '../oauth/oauth-token' |
17 | let hasRight: UserMethods.HasRight | 26 | import { getSort, throwIfNotValid } from '../utils' |
18 | let toFormattedJSON: UserMethods.ToFormattedJSON | 27 | import { VideoChannelModel } from '../video/video-channel' |
19 | let countTotal: UserMethods.CountTotal | 28 | import { AccountModel } from './account' |
20 | let getByUsername: UserMethods.GetByUsername | 29 | |
21 | let listForApi: UserMethods.ListForApi | 30 | @Table({ |
22 | let loadById: UserMethods.LoadById | 31 | tableName: 'user', |
23 | let loadByUsername: UserMethods.LoadByUsername | 32 | indexes: [ |
24 | let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels | ||
25 | let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail | ||
26 | let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo | ||
27 | |||
28 | export 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, | 43 | export 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 | ||
130 | function 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 | ||
142 | hasRight = function (this: UserInstance, right: UserRight) { | 106 | static countTotal () { |
143 | return hasUserRight(this.role, right) | 107 | return this.count() |
144 | } | 108 | } |
145 | 109 | ||
146 | isPasswordMatch = 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 | ||
150 | toFormattedJSON = 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 } ] | |
179 | isAbleToUploadVideo = 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 | |||
189 | function 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 | ||
201 | countTotal = 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 | ||
205 | getByUsername = 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 | ||
216 | listForApi = 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 | ||
232 | loadById = 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 | ||
240 | loadByUsername = 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 | ||
251 | loadByUsernameAndPopulateChannels = 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 | ||
268 | loadByUsernameOrEmail = 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 => { | |
282 | function 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Bluebird from 'bluebird' | ||
3 | |||
4 | export 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 | |||
15 | export interface ApplicationClass { | ||
16 | loadMigrationVersion: ApplicationMethods.LoadMigrationVersion | ||
17 | updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion | ||
18 | countTotal: ApplicationMethods.CountTotal | ||
19 | } | ||
20 | |||
21 | export interface ApplicationAttributes { | ||
22 | migrationVersion: number | ||
23 | } | ||
24 | |||
25 | export interface ApplicationInstance extends ApplicationClass, ApplicationAttributes, Sequelize.Instance<ApplicationAttributes> { | ||
26 | id: number | ||
27 | createdAt: Date | ||
28 | updatedAt: Date | ||
29 | } | ||
30 | |||
31 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | 2 | import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' | |
3 | import { addMethodsToModel } from '../utils' | 3 | |
4 | import { | 4 | @Table({ |
5 | ApplicationAttributes, | 5 | tableName: 'application' |
6 | ApplicationInstance, | 6 | }) |
7 | 7 | export class ApplicationModel extends Model<ApplicationModel> { | |
8 | ApplicationMethods | 8 | |
9 | } from './application-interface' | 9 | @AllowNull(false) |
10 | 10 | @Default(0) | |
11 | let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes> | 11 | @IsInt |
12 | let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion | 12 | @Column |
13 | let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion | 13 | migrationVersion: number |
14 | let countTotal: ApplicationMethods.CountTotal | 14 | |
15 | static countTotal () { | ||
16 | return ApplicationModel.count() | ||
17 | } | ||
15 | 18 | ||
16 | export 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 | |||
42 | countTotal = function () { | ||
43 | return this.count() | ||
44 | } | ||
45 | |||
46 | loadMigrationVersion = 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 | ||
54 | updateMigrationVersion = 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 @@ | |||
1 | export * 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | export namespace AvatarMethods {} | ||
4 | |||
5 | export interface AvatarClass {} | ||
6 | |||
7 | export interface AvatarAttributes { | ||
8 | filename: string | ||
9 | } | ||
10 | |||
11 | export interface AvatarInstance extends AvatarClass, AvatarAttributes, Sequelize.Instance<AvatarAttributes> { | ||
12 | createdAt: Date | ||
13 | updatedAt: Date | ||
14 | } | ||
15 | |||
16 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { addMethodsToModel } from '../utils' | ||
3 | import { AvatarAttributes, AvatarInstance } from './avatar-interface' | ||
4 | 2 | ||
5 | let Avatar: Sequelize.Model<AvatarInstance, AvatarAttributes> | 3 | @Table({ |
4 | tableName: 'avatar' | ||
5 | }) | ||
6 | export class AvatarModel extends Model<AvatarModel> { | ||
6 | 7 | ||
7 | export 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 @@ | |||
1 | export * 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 @@ | |||
1 | export * from './application' | ||
2 | export * from './avatar' | ||
3 | export * from './job' | ||
4 | export * from './oauth' | ||
5 | export * from './server' | ||
6 | export * from './account' | ||
7 | export * 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 @@ | |||
1 | export * 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model' | ||
4 | import { ResultList } from '../../../shared/models/result-list.model' | ||
5 | |||
6 | export 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 | |||
13 | export interface JobClass { | ||
14 | listWithLimitByCategory: JobMethods.ListWithLimitByCategory | ||
15 | listForApi: JobMethods.ListForApi, | ||
16 | } | ||
17 | |||
18 | export interface JobAttributes { | ||
19 | state: JobState | ||
20 | category: JobCategory | ||
21 | handlerName: string | ||
22 | handlerInputData: any | ||
23 | } | ||
24 | |||
25 | export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance<JobAttributes> { | ||
26 | id: number | ||
27 | createdAt: Date | ||
28 | updatedAt: Date | ||
29 | |||
30 | toFormattedJSON: JobMethods.ToFormattedJSON | ||
31 | } | ||
32 | |||
33 | export 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 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 2 | import { AllowNull, Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { JobCategory, JobState } from '../../../shared/models/job.model' | 3 | import { JobCategory, JobState } from '../../../shared/models' |
4 | import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' | 4 | import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' |
5 | import { addMethodsToModel, getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import { JobAttributes, JobInstance, JobMethods } from './job-interface' | ||
7 | 6 | ||
8 | let Job: Sequelize.Model<JobInstance, JobAttributes> | 7 | @Table({ |
9 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory | 8 | tableName: 'job', |
10 | let listForApi: JobMethods.ListForApi | 9 | indexes: [ |
11 | let toFormattedJSON: JobMethods.ToFormattedJSON | ||
12 | |||
13 | export 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 | 15 | export 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 | ||
54 | toFormattedJSON = 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 | ||
68 | listWithLimitByCategory = 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 | ||
83 | listForApi = 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 @@ | |||
1 | export * from './oauth-client-interface' | ||
2 | export * 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | export 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 | |||
12 | export interface OAuthClientClass { | ||
13 | countTotal: OAuthClientMethods.CountTotal | ||
14 | loadFirstClient: OAuthClientMethods.LoadFirstClient | ||
15 | getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret | ||
16 | } | ||
17 | |||
18 | export interface OAuthClientAttributes { | ||
19 | clientId: string | ||
20 | clientSecret: string | ||
21 | grants: string[] | ||
22 | redirectUris: string[] | ||
23 | } | ||
24 | |||
25 | export interface OAuthClientInstance extends OAuthClientClass, OAuthClientAttributes, Sequelize.Instance<OAuthClientAttributes> { | ||
26 | id: number | ||
27 | createdAt: Date | ||
28 | updatedAt: Date | ||
29 | } | ||
30 | |||
31 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { OAuthTokenModel } from './oauth-token' | ||
2 | 3 | ||
3 | import { addMethodsToModel } from '../utils' | 4 | @Table({ |
4 | import { | 5 | tableName: 'oAuthClient', |
5 | OAuthClientInstance, | 6 | indexes: [ |
6 | OAuthClientAttributes, | ||
7 | |||
8 | OAuthClientMethods | ||
9 | } from './oauth-client-interface' | ||
10 | |||
11 | let OAuthClient: Sequelize.Model<OAuthClientInstance, OAuthClientAttributes> | ||
12 | let countTotal: OAuthClientMethods.CountTotal | ||
13 | let loadFirstClient: OAuthClientMethods.LoadFirstClient | ||
14 | let getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret | ||
15 | |||
16 | export 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 | }) | ||
17 | export 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 | ||
62 | function 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 | ||
69 | countTotal = function () { | 44 | static countTotal () { |
70 | return OAuthClient.count() | 45 | return OAuthClientModel.count() |
71 | } | 46 | } |
72 | 47 | ||
73 | loadFirstClient = function () { | 48 | static loadFirstClient () { |
74 | return OAuthClient.findOne() | 49 | return OAuthClientModel.findOne() |
75 | } | 50 | } |
76 | 51 | ||
77 | getByIdAndSecret = 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 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | |||
4 | import { UserModel } from '../account/user-interface' | ||
5 | |||
6 | export type OAuthTokenInfo = { | ||
7 | refreshToken: string | ||
8 | refreshTokenExpiresAt: Date, | ||
9 | client: { | ||
10 | id: number | ||
11 | }, | ||
12 | user: { | ||
13 | id: number | ||
14 | } | ||
15 | } | ||
16 | |||
17 | export 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 | |||
23 | export interface OAuthTokenClass { | ||
24 | getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient | ||
25 | getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser | ||
26 | getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser | ||
27 | } | ||
28 | |||
29 | export 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 | |||
40 | export interface OAuthTokenInstance extends OAuthTokenClass, OAuthTokenAttributes, Sequelize.Instance<OAuthTokenAttributes> { | ||
41 | id: number | ||
42 | createdAt: Date | ||
43 | updatedAt: Date | ||
44 | } | ||
45 | |||
46 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | |||
3 | import { logger } from '../../helpers' | 2 | import { logger } from '../../helpers' |
3 | import { AccountModel } from '../account/account' | ||
4 | import { UserModel } from '../account/user' | ||
5 | import { OAuthClientModel } from './oauth-client' | ||
6 | |||
7 | export type OAuthTokenInfo = { | ||
8 | refreshToken: string | ||
9 | refreshTokenExpiresAt: Date, | ||
10 | client: { | ||
11 | id: number | ||
12 | }, | ||
13 | user: { | ||
14 | id: number | ||
15 | } | ||
16 | } | ||
4 | 17 | ||
5 | import { addMethodsToModel } from '../utils' | 18 | @Table({ |
6 | import { OAuthTokenAttributes, OAuthTokenInfo, OAuthTokenInstance, OAuthTokenMethods } from './oauth-token-interface' | 19 | tableName: 'oAuthToken', |
7 | 20 | indexes: [ | |
8 | let OAuthToken: Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes> | ||
9 | let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient | ||
10 | let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser | ||
11 | let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser | ||
12 | |||
13 | export 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 | }) | ||
37 | export 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 | ||
67 | function 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 | ||
85 | getByRefreshTokenAndPopulateClient = 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 | ||
116 | getByTokenAndPopulateUser = 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 | ||
141 | getByRefreshTokenAndPopulateUser = 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 @@ | |||
1 | export * 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 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | |||
4 | export namespace ServerMethods { | ||
5 | export type ListBadServers = () => Promise<ServerInstance[]> | ||
6 | export type UpdateServersScoreAndRemoveBadOnes = (goodServers: number[], badServers: number[]) => void | ||
7 | } | ||
8 | |||
9 | export interface ServerClass { | ||
10 | updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes | ||
11 | } | ||
12 | |||
13 | export interface ServerAttributes { | ||
14 | id?: number | ||
15 | host?: string | ||
16 | score?: number | Sequelize.literal // Sequelize literal for 'score +' + value | ||
17 | } | ||
18 | |||
19 | export interface ServerInstance extends ServerClass, ServerAttributes, Sequelize.Instance<ServerAttributes> { | ||
20 | createdAt: Date | ||
21 | updatedAt: Date | ||
22 | } | ||
23 | |||
24 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { isHostValid, logger } from '../../helpers' | 2 | import { AllowNull, Column, CreatedAt, Default, Is, IsInt, Max, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { logger } from '../../helpers' | ||
4 | import { isHostValid } from '../../helpers/custom-validators/servers' | ||
3 | import { SERVERS_SCORE } from '../../initializers' | 5 | import { SERVERS_SCORE } from '../../initializers' |
4 | import { addMethodsToModel } from '../utils' | 6 | import { throwIfNotValid } from '../utils' |
5 | import { ServerAttributes, ServerInstance, ServerMethods } from './server-interface' | ||
6 | 7 | ||
7 | let Server: Sequelize.Model<ServerInstance, ServerAttributes> | 8 | @Table({ |
8 | let updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes | 9 | tableName: 'server', |
9 | 10 | indexes: [ | |
10 | export 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 | 20 | export 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 | |
56 | updateServersScoreAndRemoveBadOnes = 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) |
77 | async 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 | ||
96 | function 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 | ||
114 | function 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 | ||
17 | function 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 | |||
22 | function getSortOnModel (model: any, value: string) { | 17 | function 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 | ||
24 | function 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 | ||
31 | export { | 32 | export { |
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 @@ | |||
1 | export * from './tag-interface' | ||
2 | export * from './video-abuse-interface' | ||
3 | export * from './video-blacklist-interface' | ||
4 | export * from './video-channel-interface' | ||
5 | export * from './video-tag-interface' | ||
6 | export * from './video-file-interface' | ||
7 | export * from './video-interface' | ||
8 | export * from './video-share-interface' | ||
9 | export * 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | export namespace TagMethods { | ||
5 | export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction) => Promise<TagInstance[]> | ||
6 | } | ||
7 | |||
8 | export interface TagClass { | ||
9 | findOrCreateTags: TagMethods.FindOrCreateTags | ||
10 | } | ||
11 | |||
12 | export interface TagAttributes { | ||
13 | name: string | ||
14 | } | ||
15 | |||
16 | export interface TagInstance extends TagClass, TagAttributes, Sequelize.Instance<TagAttributes> { | ||
17 | id: number | ||
18 | } | ||
19 | |||
20 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as Promise from 'bluebird' | 2 | import { Transaction } from 'sequelize' |
3 | 3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | |
4 | import { addMethodsToModel } from '../utils' | 4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' |
5 | import { | 5 | import { throwIfNotValid } from '../utils' |
6 | TagInstance, | 6 | import { VideoModel } from './video' |
7 | TagAttributes, | 7 | import { VideoTagModel } from './video-tag' |
8 | 8 | ||
9 | TagMethods | 9 | @Table({ |
10 | } from './tag-interface' | 10 | tableName: 'tag', |
11 | 11 | timestamps: false, | |
12 | let Tag: Sequelize.Model<TagInstance, TagAttributes> | 12 | indexes: [ |
13 | let findOrCreateTags: TagMethods.FindOrCreateTags | ||
14 | |||
15 | export 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 | }) |
19 | export 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 | ||
46 | function 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 | ||
54 | findOrCreateTags = 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 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { ResultList } from '../../../shared' | ||
4 | import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model' | ||
5 | import { AccountInstance } from '../account/account-interface' | ||
6 | import { ServerInstance } from '../server/server-interface' | ||
7 | import { VideoInstance } from './video-interface' | ||
8 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' | ||
9 | |||
10 | export 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 | |||
17 | export interface VideoAbuseClass { | ||
18 | listForApi: VideoAbuseMethods.ListForApi | ||
19 | toActivityPubObject: VideoAbuseMethods.ToActivityPubObject | ||
20 | } | ||
21 | |||
22 | export interface VideoAbuseAttributes { | ||
23 | reason: string | ||
24 | videoId: number | ||
25 | reporterAccountId: number | ||
26 | |||
27 | Account?: AccountInstance | ||
28 | Video?: VideoInstance | ||
29 | } | ||
30 | |||
31 | export 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 | |||
41 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | 2 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | |
3 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' | ||
3 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
4 | import { isVideoAbuseReasonValid } from '../../helpers' | 5 | import { AccountModel } from '../account/account' |
5 | 6 | import { ServerModel } from '../server/server' | |
6 | import { addMethodsToModel, getSort } from '../utils' | 7 | import { getSort, throwIfNotValid } from '../utils' |
7 | import { | 8 | import { VideoModel } from './video' |
8 | VideoAbuseInstance, | 9 | |
9 | VideoAbuseAttributes, | 10 | @Table({ |
10 | 11 | tableName: 'videoAbuse', | |
11 | VideoAbuseMethods | 12 | indexes: [ |
12 | } from './video-abuse-interface' | ||
13 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' | ||
14 | |||
15 | let VideoAbuse: Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes> | ||
16 | let toFormattedJSON: VideoAbuseMethods.ToFormattedJSON | ||
17 | let listForApi: VideoAbuseMethods.ListForApi | ||
18 | let toActivityPubObject: VideoAbuseMethods.ToActivityPubObject | ||
19 | |||
20 | export 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 | 21 | export class VideoAbuseModel extends Model<VideoAbuseModel> { | |
57 | return VideoAbuse | ||
58 | } | ||
59 | |||
60 | // ------------------------------ METHODS ------------------------------ | ||
61 | |||
62 | toFormattedJSON = 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 | ||
86 | toActivityPubObject = 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 | ||
98 | function 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 | ||
116 | listForApi = 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { SortType } from '../../helpers' | ||
5 | import { ResultList } from '../../../shared' | ||
6 | import { VideoInstance } from './video-interface' | ||
7 | |||
8 | // Don't use barrel, import just what we need | ||
9 | import { BlacklistedVideo as FormattedBlacklistedVideo } from '../../../shared/models/videos/video-blacklist.model' | ||
10 | |||
11 | export 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 | |||
17 | export interface BlacklistedVideoClass { | ||
18 | toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON | ||
19 | listForApi: BlacklistedVideoMethods.ListForApi | ||
20 | loadByVideoId: BlacklistedVideoMethods.LoadByVideoId | ||
21 | } | ||
22 | |||
23 | export interface BlacklistedVideoAttributes { | ||
24 | videoId: number | ||
25 | |||
26 | Video?: VideoInstance | ||
27 | } | ||
28 | |||
29 | export 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 | |||
38 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | |||
3 | import { SortType } from '../../helpers' | 2 | import { SortType } from '../../helpers' |
4 | import { addMethodsToModel, getSortOnModel } from '../utils' | 3 | import { getSortOnModel } from '../utils' |
5 | import { VideoInstance } from './video-interface' | 4 | import { VideoModel } from './video' |
6 | import { | ||
7 | BlacklistedVideoInstance, | ||
8 | BlacklistedVideoAttributes, | ||
9 | |||
10 | BlacklistedVideoMethods | ||
11 | } from './video-blacklist-interface' | ||
12 | |||
13 | let BlacklistedVideo: Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> | ||
14 | let toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON | ||
15 | let listForApi: BlacklistedVideoMethods.ListForApi | ||
16 | let loadByVideoId: BlacklistedVideoMethods.LoadByVideoId | ||
17 | 5 | ||
18 | export 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 | 15 | export 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 | ||
47 | toFormattedJSON = 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 | ||
70 | function 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 | ||
80 | listForApi = 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 | ||
96 | loadByVideoId = 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 @@ | |||
1 | import * as Promise from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | |||
4 | import { ResultList } from '../../../shared' | ||
5 | import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' | ||
6 | import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' | ||
7 | import { AccountInstance } from '../account/account-interface' | ||
8 | import { VideoInstance } from './video-interface' | ||
9 | import { VideoChannelShareInstance } from './video-channel-share-interface' | ||
10 | |||
11 | export 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 | |||
29 | export 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 | |||
41 | export 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 | |||
54 | export 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 | |||
64 | export 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { AccountInstance } from '../account/account-interface' | ||
4 | import { VideoChannelInstance } from './video-channel-interface' | ||
5 | |||
6 | export 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 | |||
11 | export interface VideoChannelShareClass { | ||
12 | loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare | ||
13 | load: VideoChannelShareMethods.Load | ||
14 | } | ||
15 | |||
16 | export interface VideoChannelShareAttributes { | ||
17 | accountId: number | ||
18 | videoChannelId: number | ||
19 | } | ||
20 | |||
21 | export 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 | |||
31 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { AccountModel } from '../account/account' | ||
4 | import { VideoChannelModel } from './video-channel' | ||
2 | 5 | ||
3 | import { addMethodsToModel } from '../utils' | 6 | @Table({ |
4 | import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelShareMethods } from './video-channel-share-interface' | 7 | tableName: 'videoChannelShare', |
5 | 8 | indexes: [ | |
6 | let VideoChannelShare: Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> | ||
7 | let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare | ||
8 | let load: VideoChannelShareMethods.Load | ||
9 | |||
10 | export 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 | }) |
17 | export 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 | ||
37 | function 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 | |||
55 | load = 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 | ||
69 | loadAccountsByShare = 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers' | 2 | import { |
3 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 3 | AfterDestroy, |
4 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete' | 4 | AllowNull, |
5 | 5 | BelongsTo, | |
6 | import { addMethodsToModel, getSort } from '../utils' | 6 | Column, |
7 | import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface' | 7 | CreatedAt, |
8 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 8 | DataType, |
9 | import { activityPubCollection } from '../../helpers/activitypub' | 9 | Default, |
10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | ForeignKey, |
11 | 11 | HasMany, | |
12 | let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> | 12 | Is, |
13 | let toFormattedJSON: VideoChannelMethods.ToFormattedJSON | 13 | IsUUID, |
14 | let toActivityPubObject: VideoChannelMethods.ToActivityPubObject | 14 | Model, |
15 | let isOwned: VideoChannelMethods.IsOwned | 15 | Table, |
16 | let countByAccount: VideoChannelMethods.CountByAccount | 16 | UpdatedAt |
17 | let listForApi: VideoChannelMethods.ListForApi | 17 | } from 'sequelize-typescript' |
18 | let listByAccount: VideoChannelMethods.ListByAccount | 18 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' |
19 | let loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount | 19 | import { activityPubCollection } from '../../helpers' |
20 | let loadByUUID: VideoChannelMethods.LoadByUUID | 20 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' |
21 | let loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount | 21 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
22 | let loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount | 22 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
23 | let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID | 23 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' |
24 | let loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos | 24 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send' |
25 | let loadByUrl: VideoChannelMethods.LoadByUrl | 25 | import { AccountModel } from '../account/account' |
26 | let loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl | 26 | import { ServerModel } from '../server/server' |
27 | 27 | import { getSort, throwIfNotValid } from '../utils' | |
28 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 28 | import { VideoModel } from './video' |
29 | VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel', | 29 | import { 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, | 39 | export 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 | ||
114 | isOwned = 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 | |
118 | toFormattedJSON = 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 | ||
143 | toActivityPubObject = 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 | ||
172 | function 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 | ||
190 | function 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 | ||
198 | countByAccount = 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 | ||
208 | listForApi = 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 | ||
227 | listByAccount = 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 | ||
247 | loadByUUID = 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 | ||
259 | loadByUrl = 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 | ||
272 | loadByUUIDOrUrl = 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 | ||
287 | loadByHostAndUUID = 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 | ||
313 | loadByIdAndAccount = 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 | ||
330 | loadAndPopulateAccount = 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 | ||
343 | loadByUUIDAndPopulateAccount = 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 | ||
359 | loadAndPopulateAccountAndVideos = 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | export namespace VideoFileMethods { | ||
4 | } | ||
5 | |||
6 | export interface VideoFileClass { | ||
7 | } | ||
8 | |||
9 | export interface VideoFileAttributes { | ||
10 | resolution: number | ||
11 | size: number | ||
12 | infoHash?: string | ||
13 | extname: string | ||
14 | |||
15 | videoId?: number | ||
16 | } | ||
17 | |||
18 | export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance<VideoFileAttributes> { | ||
19 | id: number | ||
20 | createdAt: Date | ||
21 | updatedAt: Date | ||
22 | } | ||
23 | |||
24 | export 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 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos' | 3 | import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
5 | import { throwIfNotValid } from '../utils' | ||
6 | import { VideoModel } from './video' | ||
5 | 7 | ||
6 | import { addMethodsToModel } from '../utils' | 8 | @Table({ |
7 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | 9 | tableName: 'videoFile', |
8 | 10 | indexes: [ | |
9 | let VideoFile: Sequelize.Model<VideoFileInstance, VideoFileAttributes> | ||
10 | |||
11 | export 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 | 19 | export class VideoFileModel extends Model<VideoFileModel> { | |
66 | return VideoFile | 20 | @CreatedAt |
67 | } | 21 | createdAt: Date |
68 | 22 | ||
69 | // ------------------------------ STATICS ------------------------------ | 23 | @UpdatedAt |
70 | 24 | updatedAt: Date | |
71 | function 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' | ||
4 | import { ResultList } from '../../../shared/models/result-list.model' | ||
5 | import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from '../../../shared/models/videos/video.model' | ||
6 | import { AccountVideoRateInstance } from '../account/account-video-rate-interface' | ||
7 | |||
8 | import { TagAttributes, TagInstance } from './tag-interface' | ||
9 | import { VideoChannelInstance } from './video-channel-interface' | ||
10 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | ||
11 | import { VideoShareInstance } from './video-share-interface' | ||
12 | |||
13 | export 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 | |||
71 | export 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 | |||
85 | export 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 | |||
115 | export 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 | |||
150 | export 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { AccountInstance } from '../account/account-interface' | ||
4 | import { VideoInstance } from './video-interface' | ||
5 | |||
6 | export 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 | |||
11 | export interface VideoShareClass { | ||
12 | loadAccountsByShare: VideoShareMethods.LoadAccountsByShare | ||
13 | load: VideoShareMethods.Load | ||
14 | } | ||
15 | |||
16 | export interface VideoShareAttributes { | ||
17 | accountId: number | ||
18 | videoId: number | ||
19 | } | ||
20 | |||
21 | export 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 | |||
30 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { AccountModel } from '../account/account' | ||
4 | import { VideoModel } from './video' | ||
2 | 5 | ||
3 | import { addMethodsToModel } from '../utils' | 6 | @Table({ |
4 | import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './video-share-interface' | 7 | tableName: 'videoShare', |
5 | 8 | indexes: [ | |
6 | let VideoShare: Sequelize.Model<VideoShareInstance, VideoShareAttributes> | ||
7 | let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare | ||
8 | let load: VideoShareMethods.Load | ||
9 | |||
10 | export 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 | }) |
17 | export 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 | ||
37 | function 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 | |||
55 | load = 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 | ||
68 | loadAccountsByShare = 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | export namespace VideoTagMethods { | ||
4 | } | ||
5 | |||
6 | export interface VideoTagClass { | ||
7 | } | ||
8 | |||
9 | export interface VideoTagAttributes { | ||
10 | } | ||
11 | |||
12 | export interface VideoTagInstance extends VideoTagClass, VideoTagAttributes, Sequelize.Instance<VideoTagAttributes> { | ||
13 | id: number | ||
14 | createdAt: Date | ||
15 | updatedAt: Date | ||
16 | } | ||
17 | |||
18 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { TagModel } from './tag' | ||
3 | import { VideoModel } from './video' | ||
2 | 4 | ||
3 | import { | 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 | }) | ||
16 | export class VideoTagModel extends Model<VideoTagModel> { | ||
17 | @CreatedAt | ||
18 | createdAt: Date | ||
7 | 19 | ||
8 | let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes> | 20 | @UpdatedAt |
21 | updatedAt: Date | ||
9 | 22 | ||
10 | export 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' | |||
4 | import * as parseTorrent from 'parse-torrent' | 4 | import * as parseTorrent from 'parse-torrent' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import * as Sequelize from 'sequelize' | 6 | import * as Sequelize from 'sequelize' |
7 | import { | ||
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' | ||
27 | import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' | ||
7 | import { VideoPrivacy, VideoResolution } from '../../../shared' | 28 | import { VideoPrivacy, VideoResolution } from '../../../shared' |
8 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' | 29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
9 | import { activityPubCollection } from '../../helpers/activitypub' | 30 | import { |
10 | import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils' | 31 | activityPubCollection, |
11 | import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid } from '../../helpers/custom-validators/videos' | 32 | createTorrentPromise, |
12 | import { 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' | ||
42 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' | ||
13 | import { | 43 | import { |
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 |
21 | import { logger } from '../../helpers/logger' | 52 | } from '../../helpers/custom-validators/videos' |
22 | import { | 53 | import { |
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' |
35 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' | 66 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' |
36 | import { sendDeleteVideo } from '../../lib/index' | 67 | import { sendDeleteVideo } from '../../lib/index' |
37 | import { addMethodsToModel, getSort } from '../utils' | 68 | import { AccountModel } from '../account/account' |
38 | import { TagInstance } from './tag-interface' | 69 | import { AccountVideoRateModel } from '../account/account-video-rate' |
39 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' | 70 | import { ServerModel } from '../server/server' |
40 | import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface' | 71 | import { getSort, throwIfNotValid } from '../utils' |
41 | 72 | import { TagModel } from './tag' | |
42 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> | 73 | import { VideoAbuseModel } from './video-abuse' |
43 | let getOriginalFile: VideoMethods.GetOriginalFile | 74 | import { VideoChannelModel } from './video-channel' |
44 | let getVideoFilename: VideoMethods.GetVideoFilename | 75 | import { VideoFileModel } from './video-file' |
45 | let getThumbnailName: VideoMethods.GetThumbnailName | 76 | import { VideoShareModel } from './video-share' |
46 | let getThumbnailPath: VideoMethods.GetThumbnailPath | 77 | import { VideoTagModel } from './video-tag' |
47 | let getPreviewName: VideoMethods.GetPreviewName | 78 | |
48 | let getPreviewPath: VideoMethods.GetPreviewPath | 79 | @Table({ |
49 | let getTorrentFileName: VideoMethods.GetTorrentFileName | 80 | tableName: 'video', |
50 | let isOwned: VideoMethods.IsOwned | 81 | indexes: [ |
51 | let toFormattedJSON: VideoMethods.ToFormattedJSON | ||
52 | let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON | ||
53 | let toActivityPubObject: VideoMethods.ToActivityPubObject | ||
54 | let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile | ||
55 | let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | ||
56 | let createPreview: VideoMethods.CreatePreview | ||
57 | let createThumbnail: VideoMethods.CreateThumbnail | ||
58 | let getVideoFilePath: VideoMethods.GetVideoFilePath | ||
59 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | ||
60 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | ||
61 | let getEmbedPath: VideoMethods.GetEmbedPath | ||
62 | let getDescriptionPath: VideoMethods.GetDescriptionPath | ||
63 | let getTruncatedDescription: VideoMethods.GetTruncatedDescription | ||
64 | let getCategoryLabel: VideoMethods.GetCategoryLabel | ||
65 | let getLicenceLabel: VideoMethods.GetLicenceLabel | ||
66 | let getLanguageLabel: VideoMethods.GetLanguageLabel | ||
67 | |||
68 | let list: VideoMethods.List | ||
69 | let listForApi: VideoMethods.ListForApi | ||
70 | let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox | ||
71 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi | ||
72 | let load: VideoMethods.Load | ||
73 | let loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount | ||
74 | let loadByUUID: VideoMethods.LoadByUUID | ||
75 | let loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL | ||
76 | let loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags | ||
77 | let loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags | ||
78 | let searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags | ||
79 | let removeThumbnail: VideoMethods.RemoveThumbnail | ||
80 | let removePreview: VideoMethods.RemovePreview | ||
81 | let removeFile: VideoMethods.RemoveFile | ||
82 | let removeTorrent: VideoMethods.RemoveTorrent | ||
83 | |||
84 | export 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 | 105 | export 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) | |
304 | function 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 | |||
352 | function 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 | |||
378 | getOriginalFile = 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 | ||
385 | getVideoFilename = 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 | ||
389 | getThumbnailName = 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 | ||
395 | getPreviewName = function (this: VideoInstance) { | 280 | return VideoModel.findAll(query) |
396 | const extension = '.jpg' | 281 | } |
397 | return this.uuid + extension | ||
398 | } | ||
399 | 282 | ||
400 | getTorrentFileName = 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 | ||
405 | isOwned = function (this: VideoInstance) { | 292 | return `(${queryVideo}) UNION (${queryVideoShare})` |
406 | return this.remote === false | 293 | } |
407 | } | ||
408 | 294 | ||
409 | createPreview = 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 | ||
420 | createThumbnail = 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 | ||
431 | getVideoFilePath = 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 | ||
435 | createTorrentAndSetInfoHash = 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 | ||
456 | getEmbedPath = 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 | ||
460 | getThumbnailPath = function (this: VideoInstance) { | 463 | if (t !== undefined) query.transaction = t |
461 | return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) | ||
462 | } | ||
463 | 464 | ||
464 | getPreviewPath = function (this: VideoInstance) { | 465 | return VideoModel.findOne(query) |
465 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) | 466 | } |
466 | } | ||
467 | 467 | ||
468 | toFormattedJSON = 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 | ||
508 | toFormattedDetailsJSON = 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 | |||
550 | toActivityPubObject = 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 | ||
679 | getTruncatedDescription = 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 | ||
689 | optimizeOriginalVideofile = 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 | ||
726 | transcodeOriginalVideofile = 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 | ||
760 | getOriginalFileHeight = 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 | ||
766 | getDescriptionPath = 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 | ||
770 | getCategoryLabel = 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 | ||
777 | getLicenceLabel = 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 | ||
784 | getLanguageLabel = 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 | ||
791 | removeThumbnail = 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 | ||
796 | removePreview = 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 | ||
801 | removeFile = 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 | ||
806 | removeTorrent = 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 | ||
813 | list = 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 | ||
821 | listAllAndSharedByAccountForOutbox = 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 | ||
903 | listUserVideosForApi = 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 | ||
935 | listForApi = 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 | ||
971 | load = 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 | ||
975 | loadByUUID = 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 | ||
988 | loadByUrlAndPopulateAccount = 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 | ||
1007 | loadByUUIDOrURL = 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 | ||
1023 | loadAndPopulateAccountAndServerAndTags = 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 | ||
1052 | loadByUUIDAndPopulateAccountAndServerAndTags = 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 | ||
1084 | searchAndPopulateAccountAndServerAndTags = 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 | ||
1147 | function 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 | ||
1158 | function 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 | } | |
1173 | function getThumbnailUrl (video: VideoInstance, baseUrlHttp: string) { | ||
1174 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + video.getThumbnailName() | ||
1175 | } | ||
1176 | 1066 | ||
1177 | function 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 | ||
1181 | function 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 | ||
1185 | function 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 | } |