From: Chocobozzz Date: Tue, 12 Dec 2017 16:53:50 +0000 (+0100) Subject: Move models to typescript-sequelize X-Git-Tag: v0.0.1-alpha~136 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=3fd3ab2d34d512b160a5e6084d7609be7b4f4452;p=github%2FChocobozzz%2FPeerTube.git Move models to typescript-sequelize --- diff --git a/package.json b/package.json index eee282038..1f98b56e8 100644 --- a/package.json +++ b/package.json @@ -77,11 +77,13 @@ "pem": "^1.12.3", "pg": "^6.4.2", "pg-hstore": "^2.3.2", + "reflect-metadata": "^0.1.10", "request": "^2.81.0", "rimraf": "^2.5.4", "safe-buffer": "^5.0.1", "scripty": "^1.5.0", "sequelize": "^4.7.5", + "sequelize-typescript": "^0.6.1", "ts-node": "^3.3.0", "typescript": "^2.5.2", "uuid": "^3.1.0", diff --git a/scripts/danger/clean/cleaner.ts b/scripts/danger/clean/cleaner.ts index 428333528..a22e2ed6a 100644 --- a/scripts/danger/clean/cleaner.ts +++ b/scripts/danger/clean/cleaner.ts @@ -1,12 +1,10 @@ -import * as rimraf from 'rimraf' import * as Promise from 'bluebird' +import * as rimraf from 'rimraf' +import { CONFIG, initDatabase, sequelizeTypescript } from '../../../server/initializers' -import { CONFIG } from '../../../server/initializers/constants' -import { database as db } from '../../../server/initializers/database' - -db.init(true) +initDatabase(true) .then(() => { - return db.sequelize.drop() + return sequelizeTypescript.drop() }) .then(() => { console.info('Tables of %s deleted.', CONFIG.DATABASE.DBNAME) diff --git a/scripts/reset-password.ts b/scripts/reset-password.ts index f0c06a7bf..a6863f807 100755 --- a/scripts/reset-password.ts +++ b/scripts/reset-password.ts @@ -1,6 +1,6 @@ import * as program from 'commander' - -import { database as db } from '../server/initializers/database' +import { initDatabase } from '../server/initializers' +import { UserModel } from '../server/models/account/user' program .option('-u, --user [user]', 'User') @@ -11,9 +11,9 @@ if (program['user'] === undefined) { process.exit(-1) } -db.init(true) +initDatabase(true) .then(() => { - return db.User.loadByUsername(program['user']) + return UserModel.loadByUsername(program['user']) }) .then(user => { if (!user) { diff --git a/scripts/update-host.ts b/scripts/update-host.ts index 759443623..ba4656b75 100755 --- a/scripts/update-host.ts +++ b/scripts/update-host.ts @@ -1,12 +1,14 @@ -import { database as db } from '../server/initializers/database' -import { getServerAccount } from '../server/helpers/utils' +import { getServerAccount } from '../server/helpers' +import { initDatabase } from '../server/initializers' +import { AccountFollowModel } from '../server/models/account/account-follow' +import { VideoModel } from '../server/models/video/video' -db.init(true) +initDatabase(true) .then(() => { return getServerAccount() }) .then(serverAccount => { - return db.AccountFollow.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined) + return AccountFollowModel.listAcceptedFollowingUrlsForApi([ serverAccount.id ], undefined) }) .then(res => { return res.total > 0 @@ -18,7 +20,7 @@ db.init(true) } console.log('Updating torrent files.') - return db.Video.list() + return VideoModel.list() }) .then(videos => { const tasks: Promise[] = [] diff --git a/server.ts b/server.ts index 39caf8996..2e9ed31d2 100644 --- a/server.ts +++ b/server.ts @@ -41,8 +41,8 @@ if (errorMessage !== null) { // Do not use barrels because we don't want to load all modules here (we need to initialize database first) import { logger } from './server/helpers/logger' // Initialize database and models -import { database as db } from './server/initializers/database' -db.init(false).then(() => onDatabaseInitDone()) +import { initDatabase } from './server/initializers/database' +initDatabase(false).then(() => onDatabaseInitDone()) // ----------- PeerTube modules ----------- import { migrate, installApplication } from './server/initializers' 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 @@ // Intercept ActivityPub client requests import * as express from 'express' -import { pageToStartAndCount } from '../../helpers' -import { activityPubCollectionPagination } from '../../helpers/activitypub' - -import { database as db } from '../../initializers' -import { ACTIVITY_PUB, CONFIG } from '../../initializers/constants' -import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send/send-announce' +import { activityPubCollectionPagination, pageToStartAndCount } from '../../helpers' +import { ACTIVITY_PUB, CONFIG } from '../../initializers' +import { buildVideoChannelAnnounceToFollowers } from '../../lib/activitypub/send' import { buildVideoAnnounceToFollowers } from '../../lib/index' -import { executeIfActivityPub, localAccountValidator } from '../../middlewares' -import { asyncMiddleware } from '../../middlewares/async' -import { videoChannelsGetValidator, videoChannelsShareValidator } from '../../middlewares/validators/video-channels' -import { videosGetValidator, videosShareValidator } from '../../middlewares/validators/videos' -import { AccountInstance, VideoChannelInstance } from '../../models' -import { VideoChannelShareInstance } from '../../models/video/video-channel-share-interface' -import { VideoInstance } from '../../models/video/video-interface' -import { VideoShareInstance } from '../../models/video/video-share-interface' +import { asyncMiddleware, executeIfActivityPub, localAccountValidator } from '../../middlewares' +import { + videoChannelsGetValidator, + videoChannelsShareValidator, + videosGetValidator, + videosShareValidator +} from '../../middlewares/validators' +import { AccountModel } from '../../models/account/account' +import { AccountFollowModel } from '../../models/account/account-follow' +import { VideoModel } from '../../models/video/video' +import { VideoChannelModel } from '../../models/video/video-channel' +import { VideoChannelShareModel } from '../../models/video/video-channel-share' +import { VideoShareModel } from '../../models/video/video-share' const activityPubClientRouter = express.Router() @@ -62,57 +64,57 @@ export { // --------------------------------------------------------------------------- function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { - const account: AccountInstance = res.locals.account + const account: AccountModel = res.locals.account return res.json(account.toActivityPubObject()).end() } async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { - const account: AccountInstance = res.locals.account + const account: AccountModel = res.locals.account const page = req.query.page || 1 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) - const result = await db.AccountFollow.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count) + const result = await AccountFollowModel.listAcceptedFollowerUrlsForApi([ account.id ], undefined, start, count) const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) return res.json(activityPubResult) } async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { - const account: AccountInstance = res.locals.account + const account: AccountModel = res.locals.account const page = req.query.page || 1 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) - const result = await db.AccountFollow.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count) + const result = await AccountFollowModel.listAcceptedFollowingUrlsForApi([ account.id ], undefined, start, count) const activityPubResult = activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.url, page, result) return res.json(activityPubResult) } function videoController (req: express.Request, res: express.Response, next: express.NextFunction) { - const video: VideoInstance = res.locals.video + const video: VideoModel = res.locals.video return res.json(video.toActivityPubObject()) } async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { - const share = res.locals.videoShare as VideoShareInstance + const share = res.locals.videoShare as VideoShareModel const object = await buildVideoAnnounceToFollowers(share.Account, res.locals.video, undefined) return res.json(object) } async function videoChannelAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { - const share = res.locals.videoChannelShare as VideoChannelShareInstance + const share = res.locals.videoChannelShare as VideoChannelShareModel const object = await buildVideoChannelAnnounceToFollowers(share.Account, share.VideoChannel, undefined) return res.json(object) } async function videoChannelController (req: express.Request, res: express.Response, next: express.NextFunction) { - const videoChannel: VideoChannelInstance = res.locals.videoChannel + const videoChannel: VideoChannelModel = res.locals.videoChannel return res.json(videoChannel.toActivityPubObject()) } 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' import { Activity } from '../../../shared/models/activitypub/activity' import { activityPubCollectionPagination } from '../../helpers/activitypub' import { pageToStartAndCount } from '../../helpers/core-utils' -import { database as db } from '../../initializers' import { ACTIVITY_PUB } from '../../initializers/constants' import { addActivityData } from '../../lib/activitypub/send/send-add' import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' import { announceActivityData } from '../../lib/index' import { asyncMiddleware, localAccountValidator } from '../../middlewares' -import { AccountInstance } from '../../models/account/account-interface' +import { AccountModel } from '../../models/account/account' +import { VideoModel } from '../../models/video/video' const outboxRouter = express.Router() @@ -26,12 +26,12 @@ export { // --------------------------------------------------------------------------- async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { - const account: AccountInstance = res.locals.account + const account: AccountModel = res.locals.account const page = req.query.page || 1 const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE) - const data = await db.Video.listAllAndSharedByAccountForOutbox(account.id, start, count) + const data = await VideoModel.listAllAndSharedByAccountForOutbox(account.id, start, count) const activities: Activity[] = [] 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 @@ import * as express from 'express' -import { asyncMiddleware, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' -import { paginationValidator } from '../../middlewares/validators/pagination' -import { database as db } from '../../initializers' -import { getFormattedObjects } from '../../helpers/utils' -import { authenticate } from '../../middlewares/oauth' -import { ensureUserHasRight } from '../../middlewares/user-right' -import { UserRight } from '../../../shared/models/users/user-right.enum' +import { UserRight } from '../../../shared/models/users' +import { getFormattedObjects } from '../../helpers' +import { asyncMiddleware, authenticate, ensureUserHasRight, jobsSortValidator, setJobsSort, setPagination } from '../../middlewares' +import { paginationValidator } from '../../middlewares/validators' +import { JobModel } from '../../models/job/job' const jobsRouter = express.Router() @@ -28,7 +26,7 @@ export { // --------------------------------------------------------------------------- async function listJobs (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.Job.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await JobModel.listForApi(req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } 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' import { CONFIG } from '../../initializers' import { logger } from '../../helpers' import { asyncMiddleware } from '../../middlewares' -import { database as db } from '../../initializers/database' import { OAuthClientLocal } from '../../../shared' +import { OAuthClientModel } from '../../models/oauth/oauth-client' const oauthClientsRouter = express.Router() @@ -27,7 +27,7 @@ async function getLocalClient (req: express.Request, res: express.Response, next return res.type('json').status(403).end() } - const client = await db.OAuthClient.loadFirstClient() + const client = await OAuthClientModel.loadFirstClient() if (!client) throw new Error('No client available.') 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 @@ import * as express from 'express' -import { UserRight } from '../../../../shared/models/users/user-right.enum' -import { getFormattedObjects } from '../../../helpers' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { getServerAccount } from '../../../helpers/utils' -import { getAccountFromWebfinger } from '../../../helpers/webfinger' -import { SERVER_ACCOUNT_NAME } from '../../../initializers/constants' -import { database as db } from '../../../initializers/database' -import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub/account' -import { sendUndoFollow } from '../../../lib/activitypub/send/send-undo' +import { UserRight } from '../../../../shared/models/users' +import { getAccountFromWebfinger, getFormattedObjects, getServerAccount, logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript, SERVER_ACCOUNT_NAME } from '../../../initializers' +import { saveAccountAndServerIfNotExist } from '../../../lib/activitypub' +import { sendUndoFollow } from '../../../lib/activitypub/send' import { sendFollow } from '../../../lib/index' -import { asyncMiddleware, paginationValidator, removeFollowingValidator, setFollowersSort, setPagination } from '../../../middlewares' -import { authenticate } from '../../../middlewares/oauth' -import { setBodyHostsPort } from '../../../middlewares/servers' -import { setFollowingSort } from '../../../middlewares/sort' -import { ensureUserHasRight } from '../../../middlewares/user-right' -import { followValidator } from '../../../middlewares/validators/follows' -import { followersSortValidator, followingSortValidator } from '../../../middlewares/validators/sort' -import { AccountInstance } from '../../../models/account/account-interface' -import { AccountFollowInstance } from '../../../models/index' +import { + asyncMiddleware, + authenticate, + ensureUserHasRight, + paginationValidator, + removeFollowingValidator, + setBodyHostsPort, + setFollowersSort, + setFollowingSort, + setPagination +} from '../../../middlewares' +import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' const serverFollowsRouter = express.Router() @@ -63,14 +63,14 @@ export { async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { const serverAccount = await getServerAccount() - const resultList = await db.AccountFollow.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) + const resultList = await AccountFollowModel.listFollowingForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { const serverAccount = await getServerAccount() - const resultList = await db.AccountFollow.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) + const resultList = await AccountFollowModel.listFollowersForApi(serverAccount.id, req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -110,14 +110,14 @@ async function followRetry (req: express.Request, res: express.Response, next: e return res.status(204).end() } -async function follow (fromAccount: AccountInstance, targetAccount: AccountInstance, targetAlreadyInDB: boolean) { +async function follow (fromAccount: AccountModel, targetAccount: AccountModel, targetAlreadyInDB: boolean) { try { - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { if (targetAlreadyInDB === false) { await saveAccountAndServerIfNotExist(targetAccount, t) } - const [ accountFollow ] = await db.AccountFollow.findOrCreate({ + const [ accountFollow ] = await AccountFollowModel.findOrCreate({ where: { accountId: fromAccount.id, targetAccountId: targetAccount.id @@ -145,9 +145,9 @@ async function follow (fromAccount: AccountInstance, targetAccount: AccountInsta } async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { - const follow: AccountFollowInstance = res.locals.follow + const follow: AccountFollowModel = res.locals.follow - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { if (follow.state === 'accepted') await sendUndoFollow(follow, t) await follow.destroy({ transaction: t }) @@ -164,7 +164,7 @@ async function removeFollow (req: express.Request, res: express.Response, next: async function loadLocalOrGetAccountFromWebfinger (name: string, host: string) { let loadedFromDB = true - let account = await db.Account.loadByNameAndHost(name, host) + let account = await AccountModel.loadByNameAndHost(name, host) if (!account) { 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 @@ import * as express from 'express' import { UserCreate, UserRight, UserRole, UserUpdate, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../shared' import { getFormattedObjects, logger, retryTransactionWrapper } from '../../helpers' -import { CONFIG, database as db } from '../../initializers' +import { CONFIG } from '../../initializers' import { createUserAccountAndChannel } from '../../lib' import { asyncMiddleware, @@ -11,6 +11,7 @@ import { paginationValidator, setPagination, setUsersSort, + setVideosSort, token, usersAddValidator, usersGetValidator, @@ -21,9 +22,10 @@ import { usersUpdateValidator, usersVideoRatingValidator } from '../../middlewares' -import { setVideosSort } from '../../middlewares/sort' -import { videosSortValidator } from '../../middlewares/validators/sort' -import { UserInstance } from '../../models' +import { videosSortValidator } from '../../middlewares/validators' +import { AccountVideoRateModel } from '../../models/account/account-video-rate' +import { UserModel } from '../../models/account/user' +import { VideoModel } from '../../models/video/video' const usersRouter = express.Router() @@ -107,8 +109,8 @@ export { // --------------------------------------------------------------------------- async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { - const user = res.locals.oauth.token.User - const resultList = await db.Video.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort) + const user = res.locals.oauth.token.User as UserModel + const resultList = await VideoModel.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -127,7 +129,7 @@ async function createUserRetryWrapper (req: express.Request, res: express.Respon async function createUser (req: express.Request) { const body: UserCreate = req.body - const user = db.User.build({ + const user = new UserModel({ username: body.username, password: body.password, email: body.email, @@ -155,7 +157,7 @@ async function registerUserRetryWrapper (req: express.Request, res: express.Resp async function registerUser (req: express.Request) { const body: UserCreate = req.body - const user = db.User.build({ + const user = new UserModel({ username: body.username, password: body.password, email: body.email, @@ -171,7 +173,7 @@ async function registerUser (req: express.Request) { async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { // We did not load channels in res.locals.user - const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) + const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) return res.json(user.toFormattedJSON()) } @@ -184,7 +186,7 @@ async function getUserVideoRating (req: express.Request, res: express.Response, const videoId = +req.params.videoId const accountId = +res.locals.oauth.token.User.Account.id - const ratingObj = await db.AccountVideoRate.load(accountId, videoId, null) + const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) const rating = ratingObj ? ratingObj.type : 'none' const json: FormattedUserVideoRate = { @@ -195,13 +197,13 @@ async function getUserVideoRating (req: express.Request, res: express.Response, } async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.User.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { - const user = await db.User.loadById(req.params.id) + const user = await UserModel.loadById(req.params.id) await user.destroy() @@ -225,7 +227,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { const body: UserUpdate = req.body - const user: UserInstance = res.locals.user + const user = res.locals.user as UserModel if (body.email !== undefined) user.email = body.email 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 @@ import * as express from 'express' - -import { database as db } from '../../../initializers/database' import { logger, getFormattedObjects, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' import { authenticate, ensureUserHasRight, @@ -16,9 +15,11 @@ import { setPagination, asyncMiddleware } from '../../../middlewares' -import { VideoInstance } from '../../../models' import { VideoAbuseCreate, UserRight } from '../../../../shared' import { sendVideoAbuse } from '../../../lib/index' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoAbuseModel } from '../../../models/video/video-abuse' const abuseVideoRouter = express.Router() @@ -46,7 +47,7 @@ export { // --------------------------------------------------------------------------- async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -63,8 +64,8 @@ async function reportVideoAbuseRetryWrapper (req: express.Request, res: express. } async function reportVideoAbuse (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video as VideoInstance - const reporterAccount = res.locals.oauth.token.User.Account + const videoInstance = res.locals.video as VideoModel + const reporterAccount = res.locals.oauth.token.User.Account as AccountModel const body: VideoAbuseCreate = req.body const abuseToCreate = { @@ -73,8 +74,8 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { videoId: videoInstance.id } - await db.sequelize.transaction(async t => { - const videoAbuseInstance = await db.VideoAbuse.create(abuseToCreate, { transaction: t }) + await sequelizeTypescript.transaction(async t => { + const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) videoAbuseInstance.Video = videoInstance // 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 @@ import * as express from 'express' - -import { database as db } from '../../../initializers' import { logger, getFormattedObjects } from '../../../helpers' import { authenticate, @@ -13,8 +11,8 @@ import { setPagination, asyncMiddleware } from '../../../middlewares' -import { BlacklistedVideoInstance } from '../../../models' import { BlacklistedVideo, UserRight } from '../../../../shared' +import { VideoBlacklistModel } from '../../../models/video/video-blacklist' const blacklistRouter = express.Router() @@ -57,18 +55,18 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response, videoId: videoInstance.id } - await db.BlacklistedVideo.create(toCreate) + await VideoBlacklistModel.create(toCreate) return res.type('json').status(204).end() } async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.BlacklistedVideo.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) - return res.json(getFormattedObjects(resultList.data, resultList.total)) + return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { - const blacklistedVideo = res.locals.blacklistedVideo as BlacklistedVideoInstance + const blacklistedVideo = res.locals.blacklistedVideo as VideoBlacklistModel try { 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 @@ import * as express from 'express' import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' import { getFormattedObjects, logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' -import { database as db } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers' import { createVideoChannel } from '../../../lib' import { sendUpdateVideoChannel } from '../../../lib/activitypub/send/send-update' import { @@ -17,7 +17,8 @@ import { videoChannelsSortValidator, videoChannelsUpdateValidator } from '../../../middlewares' -import { AccountInstance, VideoChannelInstance } from '../../../models' +import { AccountModel } from '../../../models/account/account' +import { VideoChannelModel } from '../../../models/video/video-channel' const videoChannelRouter = express.Router() @@ -66,13 +67,13 @@ export { // --------------------------------------------------------------------------- async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.VideoChannel.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.VideoChannel.listByAccount(res.locals.account.id) + const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -93,10 +94,10 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R async function addVideoChannel (req: express.Request, res: express.Response) { const videoChannelInfo: VideoChannelCreate = req.body - const account: AccountInstance = res.locals.oauth.token.User.Account - let videoChannelCreated: VideoChannelInstance + const account: AccountModel = res.locals.oauth.token.User.Account + let videoChannelCreated: VideoChannelModel - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t) }) @@ -115,12 +116,12 @@ async function updateVideoChannelRetryWrapper (req: express.Request, res: expres } async function updateVideoChannel (req: express.Request, res: express.Response) { - const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel + const videoChannelInstance = res.locals.videoChannel as VideoChannelModel const videoChannelFieldsSave = videoChannelInstance.toJSON() - const videoChannelInfoToUpdate: VideoChannelUpdate = req.body + const videoChannelInfoToUpdate = req.body as VideoChannelUpdate try { - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } @@ -158,9 +159,9 @@ async function removeVideoChannelRetryWrapper (req: express.Request, res: expres } async function removeVideoChannel (req: express.Request, res: express.Response) { - const videoChannelInstance: VideoChannelInstance = res.locals.videoChannel + const videoChannelInstance: VideoChannelModel = res.locals.videoChannel - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { await videoChannelInstance.destroy({ transaction: t }) }) @@ -168,7 +169,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response) } async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { - const videoChannelWithVideos = await db.VideoChannel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) + const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) return res.json(videoChannelWithVideos.toFormattedJSON()) } 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 { retryTransactionWrapper } from '../../../helpers' import { getServerAccount } from '../../../helpers/utils' -import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' -import { database as db } from '../../../initializers/database' -import { sendAddVideo } from '../../../lib/activitypub/send/send-add' -import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create' -import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' -import { shareVideoByServer } from '../../../lib/activitypub/share' -import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' -import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' +import { + CONFIG, + sequelizeTypescript, + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_MIMETYPE_EXT, + VIDEO_PRIVACIES +} from '../../../initializers' +import { fetchRemoteVideoDescription, getVideoActivityPubUrl, shareVideoByServer } from '../../../lib/activitypub' +import { sendAddVideo, sendCreateViewToOrigin, sendUpdateVideo } from '../../../lib/activitypub/send' import { sendCreateViewToVideoFollowers } from '../../../lib/index' -import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler' +import { transcodingJobScheduler } from '../../../lib/jobs/transcoding-job-scheduler' import { asyncMiddleware, authenticate, @@ -35,7 +38,9 @@ import { videosSortValidator, videosUpdateValidator } from '../../../middlewares' -import { VideoInstance } from '../../../models' +import { TagModel } from '../../../models/video/tag' +import { VideoModel } from '../../../models/video/video' +import { VideoFileModel } from '../../../models/video/video-file' import { abuseVideoRouter } from './abuse' import { blacklistRouter } from './blacklist' import { videoChannelRouter } from './channel' @@ -99,7 +104,7 @@ videosRouter.put('/:id', videosRouter.post('/upload', authenticate, reqFiles, - videosAddValidator, + asyncMiddleware(videosAddValidator), asyncMiddleware(addVideoRetryWrapper) ) @@ -181,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi duration: videoPhysicalFile['duration'], // duration was added by a previous middleware channelId: res.locals.videoChannel.id } - const video = db.Video.build(videoData) + const video = new VideoModel(videoData) video.url = getVideoActivityPubUrl(video) const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) @@ -192,7 +197,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi resolution: videoFileHeight, size: videoPhysicalFile.size } - const videoFile = db.VideoFile.build(videoFileData) + const videoFile = new VideoFileModel(videoFileData) const videoDir = CONFIG.STORAGE.VIDEOS_DIR const source = join(videoDir, videoPhysicalFile.filename) const destination = join(videoDir, video.getVideoFilename(videoFile)) @@ -210,7 +215,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi ) await Promise.all(tasks) - return db.sequelize.transaction(async t => { + return sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } if (CONFIG.TRANSCODING.ENABLED === true) { @@ -232,9 +237,9 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi video.VideoFiles = [ videoFile ] if (videoInfo.tags) { - const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t) + const tagInstances = await TagModel.findOrCreateTags(videoInfo.tags, t) - await video.setTags(tagInstances, sequelizeOptions) + await video.$set('Tags', tagInstances, sequelizeOptions) video.Tags = tagInstances } @@ -264,13 +269,13 @@ async function updateVideoRetryWrapper (req: express.Request, res: express.Respo } async function updateVideo (req: express.Request, res: express.Response) { - const videoInstance: VideoInstance = res.locals.video + const videoInstance: VideoModel = res.locals.video const videoFieldsSave = videoInstance.toJSON() const videoInfoToUpdate: VideoUpdate = req.body const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE try { - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } @@ -286,9 +291,9 @@ async function updateVideo (req: express.Request, res: express.Response) { const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) if (videoInfoToUpdate.tags) { - const tagInstances = await db.Tag.findOrCreateTags(videoInfoToUpdate.tags, t) + const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) - await videoInstance.setTags(tagInstances, sequelizeOptions) + await videoInstance.$set('Tags', tagInstances, sequelizeOptions) videoInstance.Tags = tagInstances } @@ -350,7 +355,7 @@ async function getVideoDescription (req: express.Request, res: express.Response) } async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort) + const resultList = await VideoModel.listForApi(req.query.start, req.query.count, req.query.sort) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -367,9 +372,9 @@ async function removeVideoRetryWrapper (req: express.Request, res: express.Respo } async function removeVideo (req: express.Request, res: express.Response) { - const videoInstance: VideoInstance = res.locals.video + const videoInstance: VideoModel = res.locals.video - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { await videoInstance.destroy({ transaction: t }) }) @@ -377,7 +382,7 @@ async function removeVideo (req: express.Request, res: express.Response) { } async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { - const resultList = await db.Video.searchAndPopulateAccountAndServerAndTags( + const resultList = await VideoModel.searchAndPopulateAccountAndServerAndTags( req.query.search, req.query.start, 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 @@ import * as express from 'express' import { UserVideoRateUpdate } from '../../../../shared' import { logger, retryTransactionWrapper } from '../../../helpers' -import { VIDEO_RATE_TYPES } from '../../../initializers' -import { database as db } from '../../../initializers/database' -import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub/videos' +import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' +import { sendVideoRateChangeToFollowers, sendVideoRateChangeToOrigin } from '../../../lib/activitypub' import { asyncMiddleware, authenticate, videoRateValidator } from '../../../middlewares' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoInstance } from '../../../models/video/video-interface' +import { AccountModel } from '../../../models/account/account' +import { AccountVideoRateModel } from '../../../models/account/account-video-rate' +import { VideoModel } from '../../../models/video/video' const rateVideoRouter = express.Router() @@ -38,12 +38,12 @@ async function rateVideoRetryWrapper (req: express.Request, res: express.Respons async function rateVideo (req: express.Request, res: express.Response) { const body: UserVideoRateUpdate = req.body const rateType = body.rating - const videoInstance: VideoInstance = res.locals.video - const accountInstance: AccountInstance = res.locals.oauth.token.User.Account + const videoInstance: VideoModel = res.locals.video + const accountInstance: AccountModel = res.locals.oauth.token.User.Account - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } - const previousRate = await db.AccountVideoRate.load(accountInstance.id, videoInstance.id, t) + const previousRate = await AccountVideoRateModel.load(accountInstance.id, videoInstance.id, t) let likesToIncrement = 0 let dislikesToIncrement = 0 @@ -71,7 +71,7 @@ async function rateVideo (req: express.Request, res: express.Response) { type: rateType } - await db.AccountVideoRate.create(query, sequelizeOptions) + await AccountVideoRateModel.create(query, sequelizeOptions) } 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' import { join } from 'path' import * as validator from 'validator' import * as Bluebird from 'bluebird' - -import { database as db } from '../initializers/database' import { CONFIG, STATIC_PATHS, @@ -13,7 +11,7 @@ import { } from '../initializers' import { root, readFileBufferPromise, escapeHTML } from '../helpers' import { asyncMiddleware } from '../middlewares' -import { VideoInstance } from '../models' +import { VideoModel } from '../models/video/video' const clientsRouter = express.Router() @@ -49,7 +47,7 @@ export { // --------------------------------------------------------------------------- -function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) { +function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid @@ -108,13 +106,13 @@ function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { const videoId = '' + req.params.id - let videoPromise: Bluebird + let videoPromise: Bluebird // Let Angular application handle errors if (validator.isUUID(videoId, 4)) { - videoPromise = db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(videoId) + videoPromise = VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(videoId) } else if (validator.isInt(videoId)) { - videoPromise = db.Video.loadAndPopulateAccountAndServerAndTags(+videoId) + videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId) } else { return res.sendFile(indexPath) } 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 @@ import * as express from 'express' - import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' -import { oembedValidator } from '../middlewares' -import { asyncMiddleware } from '../middlewares/async' -import { VideoInstance } from '../models' +import { asyncMiddleware, oembedValidator } from '../middlewares' +import { VideoModel } from '../models/video/video' const servicesRouter = express.Router() @@ -21,7 +19,7 @@ export { // --------------------------------------------------------------------------- function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { - const video = res.locals.video as VideoInstance + const video = res.locals.video as VideoModel const webserverUrl = CONFIG.WEBSERVER.URL const maxHeight = parseInt(req.query.maxheight, 10) 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 @@ import * as express from 'express' import * as cors from 'cors' - import { CONFIG, 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 @@ import * as express from 'express' -import { asyncMiddleware } from '../middlewares/async' -import { webfingerValidator } from '../middlewares/validators/webfinger' -import { AccountInstance } from '../models/account/account-interface' +import { asyncMiddleware } from '../middlewares' +import { webfingerValidator } from '../middlewares/validators' +import { AccountModel } from '../models/account/account' const webfingerRouter = express.Router() @@ -19,7 +19,7 @@ export { // --------------------------------------------------------------------------- function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { - const account: AccountInstance = res.locals.account + const account = res.locals.account as AccountModel const json = { 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 @@ -import { Activity } from '../../shared/models/activitypub/activity' -import { ResultList } from '../../shared/models/result-list.model' -import { AccountInstance } from '../models/account/account-interface' +import { ResultList } from '../../shared/models' +import { Activity } from '../../shared/models/activitypub' +import { ACTIVITY_PUB } from '../initializers' +import { AccountModel } from '../models/account/account' import { signObject } from './peertube-crypto' -import { ACTIVITY_PUB } from '../initializers/constants' function activityPubContextify (data: T) { return Object.assign(data,{ @@ -71,7 +71,7 @@ function activityPubCollectionPagination (url: string, page: any, result: Result return orderedCollectionPagination } -function buildSignedActivity (byAccount: AccountInstance, data: Object) { +function buildSignedActivity (byAccount: AccountModel, data: Object) { const activity = activityPubContextify(data) return signObject(byAccount, activity) as Promise 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' import { Response } from 'express' import 'express-validator' import * as validator from 'validator' -import { database as db } from '../../initializers' -import { AccountInstance } from '../../models' +import { AccountModel } from '../../models/account/account' import { isUserUsernameValid } from './users' function isAccountNameValid (value: string) { @@ -11,24 +10,24 @@ function isAccountNameValid (value: string) { } function isAccountIdExist (id: number | string, res: Response) { - let promise: Bluebird + let promise: Bluebird if (validator.isInt('' + id)) { - promise = db.Account.load(+id) + promise = AccountModel.load(+id) } else { // UUID - promise = db.Account.loadByUUID('' + id) + promise = AccountModel.loadByUUID('' + id) } return isAccountExist(promise, res) } function isLocalAccountNameExist (name: string, res: Response) { - const promise = db.Account.loadLocalByName(name) + const promise = AccountModel.loadLocalByName(name) return isAccountExist(promise, res) } -async function isAccountExist (p: Bluebird, res: Response) { +async function isAccountExist (p: Bluebird, res: Response) { const account = await p 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 @@ import * as validator from 'validator' -import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' +import { CONSTRAINTS_FIELDS } from '../../../initializers' import { isAccountNameValid } from '../accounts' import { exists, isUUIDValid } from '../misc' 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 @@ import * as validator from 'validator' -import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' +import { Activity, ActivityType } from '../../../../shared/models/activitypub' import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' import { isAnnounceActivityValid } from './announce' import { isActivityPubUrlValid } from './misc' +import { isDislikeActivityValid, isLikeActivityValid } from './rate' import { isUndoActivityValid } from './undo' import { isVideoChannelCreateActivityValid, isVideoChannelDeleteActivityValid, isVideoChannelUpdateActivityValid } from './video-channels' import { @@ -12,7 +13,6 @@ import { isVideoTorrentUpdateActivityValid } from './videos' import { isViewActivityValid } from './view' -import { isDislikeActivityValid, isLikeActivityValid } from './rate' function isRootActivityValid (activity: any) { 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 @@ import * as validator from 'validator' -import { exists } from '../misc' +import { CONSTRAINTS_FIELDS } from '../../../initializers' import { isTestInstance } from '../../core-utils' -import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' +import { exists } from '../misc' function isActivityPubUrlValid (url: string) { 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 @@ -export * from './activitypub' -export * from './misc' -export * from './servers' -export * from './servers' -export * from './users' -export * from './accounts' -export * from './video-channels' -export * from './videos' -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' import 'express-validator' import 'multer' import * as validator from 'validator' -import { CONSTRAINTS_FIELDS, database as db } from '../../initializers' -import { VideoChannelInstance } from '../../models' +import { CONSTRAINTS_FIELDS } from '../../initializers' +import { VideoChannelModel } from '../../models/video/video-channel' import { exists } from './misc' const VIDEO_CHANNELS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_CHANNELS @@ -17,11 +17,11 @@ function isVideoChannelNameValid (value: string) { } async function isVideoChannelExist (id: string, res: express.Response) { - let videoChannel: VideoChannelInstance + let videoChannel: VideoChannelModel if (validator.isInt(id)) { - videoChannel = await db.VideoChannel.loadAndPopulateAccount(+id) + videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) } else { // UUID - videoChannel = await db.VideoChannel.loadByUUIDAndPopulateAccount(id) + videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id) } 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' import 'multer' import * as validator from 'validator' import { VideoRateType } from '../../../shared' -import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_RATE_TYPES } from '../../initializers' -import { VIDEO_PRIVACIES } from '../../initializers/constants' -import { database as db } from '../../initializers/database' -import { VideoInstance } from '../../models/video/video-interface' +import { + CONSTRAINTS_FIELDS, + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_PRIVACIES, + VIDEO_RATE_TYPES +} from '../../initializers' +import { VideoModel } from '../../models/video/video' import { exists, isArray } from './misc' const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS @@ -100,12 +105,12 @@ function isVideoFileSizeValid (value: string) { } async function isVideoExist (id: string, res: Response) { - let video: VideoInstance + let video: VideoModel if (validator.isInt(id)) { - video = await db.Video.loadAndPopulateAccountAndServerAndTags(+id) + video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id) } else { // UUID - video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(id) + video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id) } 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 @@ -import 'express-validator' -import 'multer' -import { CONFIG } from '../../initializers/constants' +import { CONFIG } from '../../initializers' import { exists } from './misc' function isWebfingerResourceValid (value: string) { @@ -13,9 +11,7 @@ function isWebfingerResourceValid (value: string) { const host = accountParts[1] - if (host !== CONFIG.WEBSERVER.HOST) return false - - return true + return host === CONFIG.WEBSERVER.HOST } // --------------------------------------------------------------------------- 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 @@ import * as ffmpeg from 'fluent-ffmpeg' -import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' +import { VideoResolution } from '../../shared/models/videos' import { CONFIG } from '../initializers' 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 @@ export * from './activitypub' export * from './core-utils' export * from './logger' -export * from './custom-validators' export * from './ffmpeg-utils' export * from './database-utils' 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 @@ import * as mkdirp from 'mkdirp' import * as path from 'path' import * as winston from 'winston' -import { CONFIG } from '../initializers/constants' +import { CONFIG } from '../initializers' const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 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 @@ import { BCRYPT_SALT_SIZE, PRIVATE_RSA_KEY_SIZE } from '../initializers' -import { AccountInstance } from '../models/account/account-interface' +import { AccountModel } from '../models/account/account' import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey } from './core-utils' import { jsig } from './custom-jsonld-signature' import { logger } from './logger' @@ -13,7 +13,7 @@ async function createPrivateAndPublicKeys () { return { privateKey: key, publicKey } } -function isSignatureVerified (fromAccount: AccountInstance, signedDocument: object) { +function isSignatureVerified (fromAccount: AccountModel, signedDocument: object) { const publicKeyObject = { '@context': jsig.SECURITY_CONTEXT_URL, '@id': fromAccount.url, @@ -40,7 +40,7 @@ function isSignatureVerified (fromAccount: AccountInstance, signedDocument: obje }) } -function signObject (byAccount: AccountInstance, data: any) { +function signObject (byAccount: AccountModel, data: any) { const options = { privateKeyPem: byAccount.privateKey, 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 @@ import * as express from 'express' -import * as Sequelize from 'sequelize' +import { Model } from 'sequelize-typescript' import { ResultList } from '../../shared' -import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' -import { CONFIG, database as db } from '../initializers' -import { AccountInstance } from '../models/account/account-interface' +import { VideoResolution } from '../../shared/models/videos' +import { CONFIG } from '../initializers' +import { AccountModel } from '../models/account/account' +import { UserModel } from '../models/account/user' import { pseudoRandomBytesPromise } from './core-utils' import { logger } from './logger' @@ -46,7 +47,7 @@ async function isSignupAllowed () { return true } - const totalUsers = await db.User.countTotal() + const totalUsers = await UserModel.countTotal() return totalUsers < CONFIG.SIGNUP.LIMIT } @@ -72,17 +73,17 @@ function computeResolutionsToTranscode (videoFileHeight: number) { return resolutionsEnabled } -function resetSequelizeInstance (instance: Sequelize.Instance, savedFields: object) { +function resetSequelizeInstance (instance: Model, savedFields: object) { Object.keys(savedFields).forEach(key => { const value = savedFields[key] instance.set(key, value) }) } -let serverAccount: AccountInstance +let serverAccount: AccountModel async function getServerAccount () { if (serverAccount === undefined) { - serverAccount = await db.Account.loadApplication() + serverAccount = await AccountModel.loadApplication() } 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 @@ import * as WebFinger from 'webfinger.js' import { WebFingerData } from '../../shared' -import { fetchRemoteAccount } from '../lib/activitypub/account' +import { fetchRemoteAccount } from '../lib/activitypub' import { isTestInstance } from './core-utils' -import { isActivityPubUrlValid } from './custom-validators' +import { isActivityPubUrlValid } from './custom-validators/activitypub' const webfinger = new WebFinger({ 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 @@ import * as config from 'config' -import { promisify0 } from '../helpers/core-utils' -import { UserModel } from '../models/account/user-interface' -import { ApplicationModel } from '../models/application/application-interface' -import { OAuthClientModel } from '../models/oauth/oauth-client-interface' +import { promisify0 } from '../helpers' +import { UserModel } from '../models/account/user' +import { ApplicationModel } from '../models/application/application' +import { OAuthClientModel } from '../models/oauth/oauth-client' // Some checks on configuration files function checkConfig () { @@ -57,22 +57,22 @@ async function checkFFmpeg (CONFIG: { TRANSCODING: { ENABLED: boolean } }) { } // We get db by param to not import it in this file (import orders) -async function clientsExist (OAuthClient: OAuthClientModel) { - const totalClients = await OAuthClient.countTotal() +async function clientsExist () { + const totalClients = await OAuthClientModel.countTotal() return totalClients !== 0 } // We get db by param to not import it in this file (import orders) -async function usersExist (User: UserModel) { - const totalUsers = await User.countTotal() +async function usersExist () { + const totalUsers = await UserModel.countTotal() return totalUsers !== 0 } // We get db by param to not import it in this file (import orders) -async function applicationExist (Application: ApplicationModel) { - const totalApplication = await Application.countTotal() +async function applicationExist () { + const totalApplication = await ApplicationModel.countTotal() return totalApplication !== 0 } 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 @@ import * as config from 'config' import { join } from 'path' - +import { JobCategory, JobState, VideoRateType } from '../../shared/models' +import { FollowState } from '../../shared/models/accounts' +import { VideoPrivacy } from '../../shared/models/videos' // Do not use barrels, remain constants as independent as possible -import { root, isTestInstance } from '../helpers/core-utils' - -import { - VideoRateType, - JobState, - JobCategory -} from '../../shared/models' -import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum' -import { FollowState } from '../../shared/models/accounts/follow.model' +import { isTestInstance, root } from '../helpers/core-utils' // --------------------------------------------------------------------------- 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 @@ -import { join } from 'path' -import { flattenDepth } from 'lodash' -require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string -import * as Sequelize from 'sequelize' -import { AvatarModel } from '../models/avatar' +import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' +import { isTestInstance } from '../helpers/core-utils' +import { logger } from '../helpers/logger' +import { AccountModel } from '../models/account/account' +import { AccountFollowModel } from '../models/account/account-follow' +import { AccountVideoRateModel } from '../models/account/account-video-rate' +import { UserModel } from '../models/account/user' +import { ApplicationModel } from '../models/application/application' +import { AvatarModel } from '../models/avatar/avatar' +import { JobModel } from '../models/job/job' +import { OAuthClientModel } from '../models/oauth/oauth-client' +import { OAuthTokenModel } from '../models/oauth/oauth-token' +import { ServerModel } from '../models/server/server' +import { TagModel } from '../models/video/tag' +import { VideoModel } from '../models/video/video' +import { VideoAbuseModel } from '../models/video/video-abuse' +import { VideoBlacklistModel } from '../models/video/video-blacklist' +import { VideoChannelModel } from '../models/video/video-channel' +import { VideoChannelShareModel } from '../models/video/video-channel-share' +import { VideoFileModel } from '../models/video/video-file' +import { VideoShareModel } from '../models/video/video-share' +import { VideoTagModel } from '../models/video/video-tag' import { CONFIG } from './constants' -// Do not use barrel, we need to load database first -import { logger } from '../helpers/logger' -import { isTestInstance, readdirPromise } from '../helpers/core-utils' -import { VideoModel } from './../models/video/video-interface' -import { VideoTagModel } from './../models/video/video-tag-interface' -import { BlacklistedVideoModel } from './../models/video/video-blacklist-interface' -import { VideoFileModel } from './../models/video/video-file-interface' -import { VideoAbuseModel } from './../models/video/video-abuse-interface' -import { VideoChannelModel } from './../models/video/video-channel-interface' -import { UserModel } from '../models/account/user-interface' -import { AccountVideoRateModel } from '../models/account/account-video-rate-interface' -import { AccountFollowModel } from '../models/account/account-follow-interface' -import { TagModel } from './../models/video/tag-interface' -import { ServerModel } from '../models/server/server-interface' -import { OAuthTokenModel } from './../models/oauth/oauth-token-interface' -import { OAuthClientModel } from './../models/oauth/oauth-client-interface' -import { JobModel } from './../models/job/job-interface' -import { AccountModel } from './../models/account/account-interface' -import { ApplicationModel } from './../models/application/application-interface' -import { VideoChannelShareModel } from '../models/video/video-channel-share-interface' -import { VideoShareModel } from '../models/video/video-share-interface' +require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string const dbname = CONFIG.DATABASE.DBNAME const username = CONFIG.DATABASE.USERNAME const password = CONFIG.DATABASE.PASSWORD -export type PeerTubeDatabase = { - sequelize?: Sequelize.Sequelize, - init?: (silent: boolean) => Promise, - - Application?: ApplicationModel, - Avatar?: AvatarModel, - Account?: AccountModel, - Job?: JobModel, - OAuthClient?: OAuthClientModel, - OAuthToken?: OAuthTokenModel, - Server?: ServerModel, - Tag?: TagModel, - AccountVideoRate?: AccountVideoRateModel, - AccountFollow?: AccountFollowModel, - User?: UserModel, - VideoAbuse?: VideoAbuseModel, - VideoChannel?: VideoChannelModel, - VideoChannelShare?: VideoChannelShareModel, - VideoShare?: VideoShareModel, - VideoFile?: VideoFileModel, - BlacklistedVideo?: BlacklistedVideoModel, - VideoTag?: VideoTagModel, - Video?: VideoModel -} - -const database: PeerTubeDatabase = {} - -const sequelize = new Sequelize(dbname, username, password, { +const sequelizeTypescript = new SequelizeTypescript({ + database: dbname, dialect: 'postgres', - host: CONFIG.DATABASE.HOSTNAME, - port: CONFIG.DATABASE.PORT, + username, + password, + modelPaths: [__dirname + '/models'], benchmark: isTestInstance(), - isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE, + isolationLevel: SequelizeTypescript.Transaction.ISOLATION_LEVELS.SERIALIZABLE, operatorsAliases: false, - logging: (message: string, benchmark: number) => { if (process.env.NODE_DB_LOG === 'false') return @@ -79,34 +50,28 @@ const sequelize = new Sequelize(dbname, username, password, { } }) -database.sequelize = sequelize - -database.init = async (silent: boolean) => { - const modelDirectory = join(__dirname, '..', 'models') - - const filePaths = await getModelFiles(modelDirectory) - - for (const filePath of filePaths) { - try { - const model = sequelize.import(filePath) - - database[model['name']] = model - } catch (err) { - logger.error('Cannot import database model %s.', filePath, err) - process.exit(0) - } - } - - for (const modelName of Object.keys(database)) { - if ('associate' in database[modelName]) { - try { - database[modelName].associate(database) - } catch (err) { - logger.error('Cannot associate model %s.', modelName, err) - process.exit(0) - } - } - } +async function initDatabase (silent: boolean) { + sequelizeTypescript.addModels([ + ApplicationModel, + AvatarModel, + AccountModel, + JobModel, + OAuthClientModel, + OAuthTokenModel, + ServerModel, + TagModel, + AccountVideoRateModel, + AccountFollowModel, + UserModel, + VideoAbuseModel, + VideoChannelModel, + VideoChannelShareModel, + VideoShareModel, + VideoFileModel, + VideoBlacklistModel, + VideoTagModel, + VideoModel + ]) if (!silent) logger.info('Database %s is ready.', dbname) @@ -116,51 +81,6 @@ database.init = async (silent: boolean) => { // --------------------------------------------------------------------------- export { - database -} - -// --------------------------------------------------------------------------- - -async function getModelFiles (modelDirectory: string) { - const files = await readdirPromise(modelDirectory) - const directories = files.filter(directory => { - // Find directories - if ( - directory.endsWith('.js.map') || - directory === 'index.js' || directory === 'index.ts' || - directory === 'utils.js' || directory === 'utils.ts' - ) return false - - return true - }) - - const tasks: Promise[] = [] - - // For each directory we read it and append model in the modelFilePaths array - for (const directory of directories) { - const modelDirectoryPath = join(modelDirectory, directory) - - const promise = readdirPromise(modelDirectoryPath) - .then(files => { - const filteredFiles = files - .filter(file => { - if ( - file === 'index.js' || file === 'index.ts' || - file === 'utils.js' || file === 'utils.ts' || - file.endsWith('-interface.js') || file.endsWith('-interface.ts') || - file.endsWith('.js.map') - ) return false - - return true - }) - .map(file => join(modelDirectoryPath, file)) - - return filteredFiles - }) - - tasks.push(promise) - } - - const filteredFilesArray: string[][] = await Promise.all(tasks) - return flattenDepth(filteredFilesArray, 1) + initDatabase, + sequelizeTypescript } 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 @@ import * as passwordGenerator from 'password-generator' import { UserRole } from '../../shared' -import { logger, mkdirpPromise, rimrafPromise } from '../helpers' -import { createUserAccountAndChannel } from '../lib' -import { createLocalAccountWithoutKeys } from '../lib/user' +import { createPrivateAndPublicKeys, logger, mkdirpPromise, rimrafPromise } from '../helpers' +import { createLocalAccountWithoutKeys, createUserAccountAndChannel } from '../lib' +import { UserModel } from '../models/account/user' +import { ApplicationModel } from '../models/application/application' +import { OAuthClientModel } from '../models/oauth/oauth-client' import { applicationExist, clientsExist, usersExist } from './checker' import { CACHE, CONFIG, LAST_MIGRATION_VERSION, SERVER_ACCOUNT_NAME } from './constants' -import { database as db } from './database' -import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' +import { sequelizeTypescript } from './database' async function installApplication () { try { - await db.sequelize.sync() + await sequelizeTypescript.sync() await removeCacheDirectories() await createDirectoriesIfNotExist() await createApplicationIfNotExist() @@ -64,7 +65,7 @@ function createDirectoriesIfNotExist () { } async function createOAuthClientIfNotExist () { - const exist = await clientsExist(db.OAuthClient) + const exist = await clientsExist() // Nothing to do, clients already exist if (exist === true) return undefined @@ -72,7 +73,7 @@ async function createOAuthClientIfNotExist () { const id = passwordGenerator(32, false, /[a-z0-9]/) const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/) - const client = db.OAuthClient.build({ + const client = new OAuthClientModel({ clientId: id, clientSecret: secret, grants: [ 'password', 'refresh_token' ], @@ -87,7 +88,7 @@ async function createOAuthClientIfNotExist () { } async function createOAuthAdminIfNotExist () { - const exist = await usersExist(db.User) + const exist = await usersExist() // Nothing to do, users already exist if (exist === true) return undefined @@ -120,7 +121,7 @@ async function createOAuthAdminIfNotExist () { role, videoQuota: -1 } - const user = db.User.build(userData) + const user = new UserModel(userData) await createUserAccountAndChannel(user, validatePassword) logger.info('Username: ' + username) @@ -128,12 +129,12 @@ async function createOAuthAdminIfNotExist () { } async function createApplicationIfNotExist () { - const exist = await applicationExist(db.Application) + const exist = await applicationExist() // Nothing to do, application already exist if (exist === true) return undefined logger.info('Creating Application table.') - const applicationInstance = await db.Application.create({ migrationVersion: LAST_MIGRATION_VERSION }) + const applicationInstance = await ApplicationModel.create({ migrationVersion: LAST_MIGRATION_VERSION }) logger.info('Creating application account.') 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 @@ import * as Sequelize from 'sequelize' import * as Promise from 'bluebird' import { stat } from 'fs' - -import { VideoInstance } from '../../models' +import { VideoModel } from '../../models/video/video' function up (utils: { transaction: Sequelize.Transaction, @@ -11,7 +10,7 @@ function up (utils: { db: any }): Promise { return utils.db.Video.listOwnedAndPopulateAuthorAndTags() - .then((videos: VideoInstance[]) => { + .then((videos: VideoModel[]) => { const tasks: Promise[] = [] 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' import { shareVideoByServer } from '../../lib/activitypub/share' import { getVideoActivityPubUrl, getVideoChannelActivityPubUrl } from '../../lib/activitypub/url' import { createLocalAccountWithoutKeys } from '../../lib/user' +import { ApplicationModel } from '../../models/application/application' import { JOB_CATEGORIES, SERVER_ACCOUNT_NAME } from '../constants' -import { PeerTubeDatabase } from '../database' async function up (utils: { transaction: Sequelize.Transaction, queryInterface: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, - db: PeerTubeDatabase + db: any }): Promise { const q = utils.queryInterface const db = utils.db @@ -65,7 +65,7 @@ async function up (utils: { // Create application account { - const applicationInstance = await db.Application.findOne() + const applicationInstance = await ApplicationModel.findOne() const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACCOUNT_NAME, null, applicationInstance.id, undefined) 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 @@ import * as Sequelize from 'sequelize' -import { PeerTubeDatabase } from '../database' async function up (utils: { transaction: Sequelize.Transaction, queryInterface: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, - db: PeerTubeDatabase + db: any }): Promise { await utils.queryInterface.removeColumn('Servers', 'email') } 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 @@ import * as Sequelize from 'sequelize' -import { PeerTubeDatabase } from '../database' async function up (utils: { transaction: Sequelize.Transaction, queryInterface: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, - db: PeerTubeDatabase + db: any }): Promise { await utils.queryInterface.removeColumn('Servers', 'publicKey') } 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 @@ import * as Sequelize from 'sequelize' -import { PeerTubeDatabase } from '../database' async function up (utils: { transaction: Sequelize.Transaction, queryInterface: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, - db: PeerTubeDatabase + db: any }): Promise { await utils.db.Avatar.sync() 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 @@ import * as Sequelize from 'sequelize' import { CONSTRAINTS_FIELDS } from '../constants' -import { PeerTubeDatabase } from '../database' async function up (utils: { transaction: Sequelize.Transaction, queryInterface: Sequelize.QueryInterface, sequelize: Sequelize.Sequelize, - db: PeerTubeDatabase + db: any }): Promise { { 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 @@ import * as path from 'path' - -import { database as db } from './database' -import { LAST_MIGRATION_VERSION } from './constants' import { logger, readdirPromise } from '../helpers' +import { ApplicationModel } from '../models/application/application' +import { LAST_MIGRATION_VERSION } from './constants' +import { sequelizeTypescript } from './database' async function migrate () { - const tables = await db.sequelize.getQueryInterface().showAllTables() + const tables = await sequelizeTypescript.getQueryInterface().showAllTables() // No tables, we don't need to migrate anything // The installer will do that if (tables.length === 0) return - let actualVersion = await db.Application.loadMigrationVersion() + let actualVersion = await ApplicationModel.loadMigrationVersion() if (actualVersion === null) { - await db.Application.create({ migrationVersion: 0 }) + await ApplicationModel.create({ migrationVersion: 0 }) actualVersion = 0 } @@ -78,17 +78,16 @@ async function executeMigration (actualVersion: number, entity: { version: strin const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const options = { transaction: t, - queryInterface: db.sequelize.getQueryInterface(), - sequelize: db.sequelize, - db + queryInterface: sequelizeTypescript.getQueryInterface(), + sequelize: sequelizeTypescript } await migrationScript.up(options) // Update the new migration version - await db.Application.updateMigrationVersion(versionScript, t) + await ApplicationModel.updateMigrationVersion(versionScript, t) }) } 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 @@ import * as Bluebird from 'bluebird' -import * as url from 'url' -import { ActivityPubActor } from '../../../shared/models/activitypub/activitypub-actor' -import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub/account' -import { retryTransactionWrapper } from '../../helpers/database-utils' -import { logger } from '../../helpers/logger' -import { doRequest } from '../../helpers/requests' -import { ACTIVITY_PUB } from '../../initializers/constants' -import { database as db } from '../../initializers/database' -import { AccountInstance } from '../../models/account/account-interface' import { Transaction } from 'sequelize' +import * as url from 'url' +import { ActivityPubActor } from '../../../shared/models/activitypub' +import { doRequest, logger, retryTransactionWrapper } from '../../helpers' +import { isRemoteAccountValid } from '../../helpers/custom-validators/activitypub' +import { ACTIVITY_PUB, sequelizeTypescript } from '../../initializers' +import { AccountModel } from '../../models/account/account' +import { ServerModel } from '../../models/server/server' async function getOrCreateAccountAndServer (accountUrl: string) { - let account = await db.Account.loadByUrl(accountUrl) + let account = await AccountModel.loadByUrl(accountUrl) // We don't have this account in our database, fetch it on remote if (!account) { @@ -28,11 +26,11 @@ async function getOrCreateAccountAndServer (accountUrl: string) { return account } -function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transaction): Bluebird | Promise { +function saveAccountAndServerIfNotExist (account: AccountModel, t?: Transaction): Bluebird | Promise { if (t !== undefined) { return save(t) } else { - return db.sequelize.transaction(t => { + return sequelizeTypescript.transaction(t => { return save(t) }) } @@ -49,7 +47,7 @@ function saveAccountAndServerIfNotExist (account: AccountInstance, t?: Transacti }, transaction: t } - const [ server ] = await db.Server.findOrCreate(serverOptions) + const [ server ] = await ServerModel.findOrCreate(serverOptions) // Save our new account in database account.set('serverId', server.id) @@ -87,7 +85,7 @@ async function fetchRemoteAccount (accountUrl: string) { const followersCount = await fetchAccountCount(accountJSON.followers) const followingCount = await fetchAccountCount(accountJSON.following) - const account = db.Account.build({ + return new AccountModel({ uuid: accountJSON.uuid, name: accountJSON.preferredUsername, url: accountJSON.url, @@ -101,8 +99,6 @@ async function fetchRemoteAccount (accountUrl: string) { followersUrl: accountJSON.followers, followingUrl: accountJSON.following }) - - return account } 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 @@ import { Transaction } from 'sequelize' -import { AccountInstance } from '../../models/account/account-interface' -import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' +import { AccountModel } from '../../models/account/account' +import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../jobs/activitypub-http-job-scheduler' -async function addFetchOutboxJob (account: AccountInstance, t: Transaction) { +async function addFetchOutboxJob (account: AccountModel, t: Transaction) { const jobPayload: ActivityPubHttpPayload = { uris: [ account.outboxUrl ] } 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 @@ import * as magnetUtil from 'magnet-uri' import { VideoTorrentObject } from '../../../../shared' -import { VideoChannelObject } from '../../../../shared/models/activitypub/objects/video-channel-object' -import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' +import { VideoChannelObject } from '../../../../shared/models/activitypub/objects' +import { VideoPrivacy } from '../../../../shared/models/videos' +import { doRequest } from '../../../helpers' import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' -import { doRequest } from '../../../helpers/requests' -import { database as db } from '../../../initializers' -import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers/constants' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoChannelInstance } from '../../../models/video/video-channel-interface' -import { VideoFileAttributes } from '../../../models/video/video-file-interface' -import { VideoAttributes, VideoInstance } from '../../../models/video/video-interface' +import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoChannelShareModel } from '../../../models/video/video-channel-share' +import { VideoShareModel } from '../../../models/video/video-share' import { getOrCreateAccountAndServer } from '../account' -function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountInstance) { +function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountModel) { return { name: videoChannelObject.name, description: videoChannelObject.content, @@ -26,7 +26,7 @@ function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChan } async function videoActivityObjectToDBAttributes ( - videoChannel: VideoChannelInstance, + videoChannel: VideoChannelModel, videoObject: VideoTorrentObject, to: string[] = [], cc: string[] = [] @@ -56,7 +56,7 @@ async function videoActivityObjectToDBAttributes ( description = videoObject.content } - const videoData: VideoAttributes = { + return { name: videoObject.name, uuid: videoObject.uuid, url: videoObject.id, @@ -76,11 +76,9 @@ async function videoActivityObjectToDBAttributes ( remote: true, privacy } - - return videoData } -function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoObject: VideoTorrentObject) { +function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) { const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT) const fileUrls = videoObject.url.filter(u => { return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/') @@ -90,7 +88,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO throw new Error('Cannot find video files for ' + videoCreated.url) } - const attributes: VideoFileAttributes[] = [] + const attributes = [] for (const fileUrl of fileUrls) { // Fetch associated magnet uri const magnet = videoObject.url.find(u => { @@ -115,7 +113,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoInstance, videoO return attributes } -async function addVideoShares (instance: VideoInstance, shares: string[]) { +async function addVideoShares (instance: VideoModel, shares: string[]) { for (const share of shares) { // Fetch url const json = await doRequest({ @@ -132,14 +130,14 @@ async function addVideoShares (instance: VideoInstance, shares: string[]) { videoId: instance.id } - await db.VideoShare.findOrCreate({ + await VideoShareModel.findOrCreate({ where: entry, defaults: entry }) } } -async function addVideoChannelShares (instance: VideoChannelInstance, shares: string[]) { +async function addVideoChannelShares (instance: VideoChannelModel, shares: string[]) { for (const share of shares) { // Fetch url const json = await doRequest({ @@ -156,7 +154,7 @@ async function addVideoChannelShares (instance: VideoChannelInstance, shares: st videoChannelId: instance.id } - await db.VideoChannelShare.findOrCreate({ + await VideoChannelShareModel.findOrCreate({ where: entry, defaults: entry }) 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 @@ -import { ActivityAccept } from '../../../../shared/models/activitypub/activity' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' +import { ActivityAccept } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' import { addFetchOutboxJob } from '../fetch' -async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) { +async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) { if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.') - const targetAccount = await db.Account.loadByUrl(activity.actor) + const targetAccount = await AccountModel.loadByUrl(activity.actor) return processAccept(inboxAccount, targetAccount) } @@ -19,8 +19,8 @@ export { // --------------------------------------------------------------------------- -async function processAccept (account: AccountInstance, targetAccount: AccountInstance) { - const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id) +async function processAccept (account: AccountModel, targetAccount: AccountModel) { + const follow = await AccountFollowModel.loadByAccountAndTarget(account.id, targetAccount.id) if (!follow) throw new Error('Cannot find associated follow.') 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 @@ import * as Bluebird from 'bluebird' import { VideoTorrentObject } from '../../../../shared' -import { ActivityAdd } from '../../../../shared/models/activitypub/activity' -import { VideoRateType } from '../../../../shared/models/videos/video-rate.type' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoChannelInstance } from '../../../models/video/video-channel-interface' -import { VideoInstance } from '../../../models/video/video-interface' +import { ActivityAdd } from '../../../../shared/models/activitypub' +import { VideoRateType } from '../../../../shared/models/videos' +import { logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountVideoRateModel } from '../../../models/account/account-video-rate' +import { TagModel } from '../../../models/video/tag' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoFileModel } from '../../../models/video/video-file' import { getOrCreateAccountAndServer } from '../account' import { getOrCreateVideoChannel } from '../video-channels' import { generateThumbnailFromUrl } from '../videos' @@ -37,9 +39,9 @@ export { // --------------------------------------------------------------------------- -async function processAddVideo (account: AccountInstance, +async function processAddVideo (account: AccountModel, activity: ActivityAdd, - videoChannel: VideoChannelInstance, + videoChannel: VideoChannelModel, videoToCreateData: VideoTorrentObject) { const options = { arguments: [ account, activity, videoChannel, videoToCreateData ], @@ -64,24 +66,24 @@ async function processAddVideo (account: AccountInstance, return video } -function addRemoteVideo (account: AccountInstance, +function addRemoteVideo (account: AccountModel, activity: ActivityAdd, - videoChannel: VideoChannelInstance, + videoChannel: VideoChannelModel, videoToCreateData: VideoTorrentObject) { logger.debug('Adding remote video %s.', videoToCreateData.id) - return db.sequelize.transaction(async t => { + return sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.') - const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t) + const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t) if (videoFromDatabase) return videoFromDatabase const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc) - const video = db.Video.build(videoData) + const video = VideoModel.build(videoData) // Don't block on request generateThumbnailFromUrl(video, videoToCreateData.icon) @@ -94,12 +96,12 @@ function addRemoteVideo (account: AccountInstance, throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url) } - const tasks: Bluebird[] = videoFileAttributes.map(f => db.VideoFile.create(f, { transaction: t })) + const tasks: Bluebird[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t })) await Promise.all(tasks) const tags = videoToCreateData.tag.map(t => t.name) - const tagInstances = await db.Tag.findOrCreateTags(tags, t) - await videoCreated.setTags(tagInstances, sequelizeOptions) + const tagInstances = await TagModel.findOrCreateTags(tags, t) + await videoCreated.$set('Tags', tagInstances, sequelizeOptions) logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid) @@ -107,13 +109,13 @@ function addRemoteVideo (account: AccountInstance, }) } -async function createRates (accountUrls: string[], video: VideoInstance, rate: VideoRateType) { +async function createRates (accountUrls: string[], video: VideoModel, rate: VideoRateType) { let rateCounts = 0 const tasks: Bluebird[] = [] for (const accountUrl of accountUrls) { const account = await getOrCreateAccountAndServer(accountUrl) - const p = db.AccountVideoRate + const p = AccountVideoRateModel .create({ videoId: video.id, 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 @@ -import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub/activity' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { database as db } from '../../../initializers/index' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoInstance } from '../../../models/index' -import { VideoChannelInstance } from '../../../models/video/video-channel-interface' +import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub' +import { logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoChannelShareModel } from '../../../models/video/video-channel-share' +import { VideoShareModel } from '../../../models/video/video-share' import { getOrCreateAccountAndServer } from '../account' import { forwardActivity } from '../send/misc' import { processAddActivity } from './process-add' @@ -36,7 +37,7 @@ export { // --------------------------------------------------------------------------- -function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { +function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { const options = { arguments: [ accountAnnouncer, activity ], errorMessage: 'Cannot share the video channel with many retries.' @@ -45,18 +46,18 @@ function processVideoChannelShare (accountAnnouncer: AccountInstance, activity: return retryTransactionWrapper(shareVideoChannel, options) } -async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { +async function shareVideoChannel (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { const announcedActivity = activity.object as ActivityCreate - return db.sequelize.transaction(async t => { + return sequelizeTypescript.transaction(async t => { // Add share entry - const videoChannel: VideoChannelInstance = await processCreateActivity(announcedActivity) + const videoChannel: VideoChannelModel = await processCreateActivity(announcedActivity) const share = { accountId: accountAnnouncer.id, videoChannelId: videoChannel.id } - const [ , created ] = await db.VideoChannelShare.findOrCreate({ + const [ , created ] = await VideoChannelShareModel.findOrCreate({ where: share, defaults: share, transaction: t @@ -72,7 +73,7 @@ async function shareVideoChannel (accountAnnouncer: AccountInstance, activity: A }) } -function processVideoShare (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { +function processVideoShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { const options = { arguments: [ accountAnnouncer, activity ], errorMessage: 'Cannot share the video with many retries.' @@ -81,19 +82,19 @@ function processVideoShare (accountAnnouncer: AccountInstance, activity: Activit return retryTransactionWrapper(shareVideo, options) } -function shareVideo (accountAnnouncer: AccountInstance, activity: ActivityAnnounce) { +function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { const announcedActivity = activity.object as ActivityAdd - return db.sequelize.transaction(async t => { + return sequelizeTypescript.transaction(async t => { // Add share entry - const video: VideoInstance = await processAddActivity(announcedActivity) + const video: VideoModel = await processAddActivity(announcedActivity) const share = { accountId: accountAnnouncer.id, videoId: video.id } - const [ , created ] = await db.VideoShare.findOrCreate({ + const [ , created ] = await VideoShareModel.findOrCreate({ where: share, defaults: share, 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 @@ import { ActivityCreate, VideoChannelObject } from '../../../../shared' -import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' -import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' -import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object' +import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' import { logger, retryTransactionWrapper } from '../../../helpers' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountVideoRateModel } from '../../../models/account/account-video-rate' +import { VideoModel } from '../../../models/video/video' +import { VideoAbuseModel } from '../../../models/video/video-abuse' +import { VideoChannelModel } from '../../../models/video/video-channel' import { getOrCreateAccountAndServer } from '../account' import { forwardActivity } from '../send/misc' import { getVideoChannelActivityPubUrl } from '../url' @@ -37,7 +39,7 @@ export { // --------------------------------------------------------------------------- -async function processCreateDislike (byAccount: AccountInstance, activity: ActivityCreate) { +async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) { const options = { arguments: [ byAccount, activity ], errorMessage: 'Cannot dislike the video with many retries.' @@ -46,11 +48,11 @@ async function processCreateDislike (byAccount: AccountInstance, activity: Activ return retryTransactionWrapper(createVideoDislike, options) } -function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreate) { +function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) { const dislike = activity.object as DislikeObject - return db.sequelize.transaction(async t => { - const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t) + return sequelizeTypescript.transaction(async t => { + const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) if (!video) throw new Error('Unknown video ' + dislike.object) const rate = { @@ -58,7 +60,7 @@ function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreat videoId: video.id, accountId: byAccount.id } - const [ , created ] = await db.AccountVideoRate.findOrCreate({ + const [ , created ] = await AccountVideoRateModel.findOrCreate({ where: rate, defaults: rate, transaction: t @@ -73,14 +75,14 @@ function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreat }) } -async function processCreateView (byAccount: AccountInstance, activity: ActivityCreate) { +async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) { const view = activity.object as ViewObject - const video = await db.Video.loadByUrlAndPopulateAccount(view.object) + const video = await VideoModel.loadByUrlAndPopulateAccount(view.object) if (!video) throw new Error('Unknown video ' + view.object) - const account = await db.Account.loadByUrl(view.actor) + const account = await AccountModel.loadByUrl(view.actor) if (!account) throw new Error('Unknown account ' + view.actor) await video.increment('views') @@ -92,7 +94,7 @@ async function processCreateView (byAccount: AccountInstance, activity: Activity } } -async function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { +async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) { const options = { arguments: [ account, videoChannelToCreateData ], errorMessage: 'Cannot insert the remote video channel with many retries.' @@ -107,15 +109,15 @@ async function processCreateVideoChannel (account: AccountInstance, videoChannel return videoChannel } -function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { +function addRemoteVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) { logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid) - return db.sequelize.transaction(async t => { - let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t) + return sequelizeTypescript.transaction(async t => { + let videoChannel = await VideoChannelModel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t) if (videoChannel) return videoChannel const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account) - videoChannel = db.VideoChannel.build(videoChannelData) + videoChannel = new VideoChannelModel(videoChannelData) videoChannel.url = getVideoChannelActivityPubUrl(videoChannel) videoChannel = await videoChannel.save({ transaction: t }) @@ -125,7 +127,7 @@ function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateDa }) } -function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { +function processCreateVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) { const options = { arguments: [ account, videoAbuseToCreateData ], errorMessage: 'Cannot insert the remote video abuse with many retries.' @@ -134,11 +136,11 @@ function processCreateVideoAbuse (account: AccountInstance, videoAbuseToCreateDa return retryTransactionWrapper(addRemoteVideoAbuse, options) } -function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: VideoAbuseObject) { +function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) { logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) - return db.sequelize.transaction(async t => { - const video = await db.Video.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) + return sequelizeTypescript.transaction(async t => { + const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) if (!video) { logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object) return undefined @@ -150,7 +152,7 @@ function addRemoteVideoAbuse (account: AccountInstance, videoAbuseToCreateData: videoId: video.id } - await db.VideoAbuse.create(videoAbuseData) + await VideoAbuseModel.create(videoAbuseData) logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) }) 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 @@ -import { ActivityDelete } from '../../../../shared/models/activitypub/activity' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoChannelInstance } from '../../../models/video/video-channel-interface' -import { VideoInstance } from '../../../models/video/video-interface' +import { ActivityDelete } from '../../../../shared/models/activitypub' +import { logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' import { getOrCreateAccountAndServer } from '../account' async function processDeleteActivity (activity: ActivityDelete) { @@ -15,14 +14,14 @@ async function processDeleteActivity (activity: ActivityDelete) { } { - let videoObject = await db.Video.loadByUrlAndPopulateAccount(activity.id) + let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id) if (videoObject !== undefined) { return processDeleteVideo(account, videoObject) } } { - let videoChannelObject = await db.VideoChannel.loadByUrl(activity.id) + let videoChannelObject = await VideoChannelModel.loadByUrl(activity.id) if (videoChannelObject !== undefined) { return processDeleteVideoChannel(account, videoChannelObject) } @@ -39,7 +38,7 @@ export { // --------------------------------------------------------------------------- -async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) { +async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) { const options = { arguments: [ account, videoToDelete ], errorMessage: 'Cannot remove the remote video with many retries.' @@ -48,10 +47,10 @@ async function processDeleteVideo (account: AccountInstance, videoToDelete: Vide await retryTransactionWrapper(deleteRemoteVideo, options) } -async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) { +async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) { logger.debug('Removing remote video "%s".', videoToDelete.uuid) - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { if (videoToDelete.VideoChannel.Account.id !== account.id) { throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url) } @@ -62,7 +61,7 @@ async function deleteRemoteVideo (account: AccountInstance, videoToDelete: Video logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) } -async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { +async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { const options = { arguments: [ account, videoChannelToRemove ], errorMessage: 'Cannot remove the remote video channel with many retries.' @@ -71,10 +70,10 @@ async function processDeleteVideoChannel (account: AccountInstance, videoChannel await retryTransactionWrapper(deleteRemoteVideoChannel, options) } -async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) { +async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid) - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { if (videoChannelToRemove.Account.id !== account.id) { throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url) } @@ -85,7 +84,7 @@ async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelT logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid) } -async function processDeleteAccount (accountToRemove: AccountInstance) { +async function processDeleteAccount (accountToRemove: AccountModel) { const options = { arguments: [ accountToRemove ], errorMessage: 'Cannot remove the remote account with many retries.' @@ -94,10 +93,10 @@ async function processDeleteAccount (accountToRemove: AccountInstance) { await retryTransactionWrapper(deleteRemoteAccount, options) } -async function deleteRemoteAccount (accountToRemove: AccountInstance) { +async function deleteRemoteAccount (accountToRemove: AccountModel) { logger.debug('Removing remote account "%s".', accountToRemove.uuid) - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { await accountToRemove.destroy({ transaction: t }) }) 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 @@ -import { ActivityFollow } from '../../../../shared/models/activitypub/activity' -import { retryTransactionWrapper } from '../../../helpers' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' -import { logger } from '../../../helpers/logger' -import { sendAccept } from '../send/send-accept' +import { ActivityFollow } from '../../../../shared/models/activitypub' +import { logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' import { getOrCreateAccountAndServer } from '../account' +import { sendAccept } from '../send' async function processFollowActivity (activity: ActivityFollow) { const activityObject = activity.object @@ -21,7 +21,7 @@ export { // --------------------------------------------------------------------------- -function processFollow (account: AccountInstance, targetAccountURL: string) { +function processFollow (account: AccountModel, targetAccountURL: string) { const options = { arguments: [ account, targetAccountURL ], errorMessage: 'Cannot follow with many retries.' @@ -30,14 +30,14 @@ function processFollow (account: AccountInstance, targetAccountURL: string) { return retryTransactionWrapper(follow, options) } -async function follow (account: AccountInstance, targetAccountURL: string) { - await db.sequelize.transaction(async t => { - const targetAccount = await db.Account.loadByUrl(targetAccountURL, t) +async function follow (account: AccountModel, targetAccountURL: string) { + await sequelizeTypescript.transaction(async t => { + const targetAccount = await AccountModel.loadByUrl(targetAccountURL, t) if (!targetAccount) throw new Error('Unknown account') if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') - const [ accountFollow ] = await db.AccountFollow.findOrCreate({ + const [ accountFollow ] = await AccountFollowModel.findOrCreate({ where: { accountId: account.id, 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 @@ -import { ActivityLike } from '../../../../shared/models/activitypub/activity' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' +import { ActivityLike } from '../../../../shared/models/activitypub' +import { retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountVideoRateModel } from '../../../models/account/account-video-rate' +import { VideoModel } from '../../../models/video/video' import { getOrCreateAccountAndServer } from '../account' import { forwardActivity } from '../send/misc' @@ -19,7 +21,7 @@ export { // --------------------------------------------------------------------------- -async function processLikeVideo (byAccount: AccountInstance, activity: ActivityLike) { +async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) { const options = { arguments: [ byAccount, activity ], errorMessage: 'Cannot like the video with many retries.' @@ -28,11 +30,11 @@ async function processLikeVideo (byAccount: AccountInstance, activity: ActivityL return retryTransactionWrapper(createVideoLike, options) } -function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) { +function createVideoLike (byAccount: AccountModel, activity: ActivityLike) { const videoUrl = activity.object - return db.sequelize.transaction(async t => { - const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl) + return sequelizeTypescript.transaction(async t => { + const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) if (!video) throw new Error('Unknown video ' + videoUrl) @@ -41,7 +43,7 @@ function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) { videoId: video.id, accountId: byAccount.id } - const [ , created ] = await db.AccountVideoRate.findOrCreate({ + const [ , created ] = await AccountVideoRateModel.findOrCreate({ where: rate, defaults: rate, 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 @@ -import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity' -import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { database as db } from '../../../initializers' +import { ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' +import { DislikeObject } from '../../../../shared/models/activitypub/objects' +import { logger, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' +import { AccountVideoRateModel } from '../../../models/account/account-video-rate' +import { VideoModel } from '../../../models/video/video' import { forwardActivity } from '../send/misc' async function processUndoActivity (activity: ActivityUndo) { @@ -41,14 +44,14 @@ function processUndoLike (actor: string, activity: ActivityUndo) { function undoLike (actor: string, activity: ActivityUndo) { const likeActivity = activity.object as ActivityLike - return db.sequelize.transaction(async t => { - const byAccount = await db.Account.loadByUrl(actor, t) + return sequelizeTypescript.transaction(async t => { + const byAccount = await AccountModel.loadByUrl(actor, t) if (!byAccount) throw new Error('Unknown account ' + actor) - const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object, t) + const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t) if (!video) throw new Error('Unknown video ' + likeActivity.actor) - const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) + const rate = await AccountVideoRateModel.load(byAccount.id, video.id, t) if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) await rate.destroy({ transaction: t }) @@ -74,14 +77,14 @@ function processUndoDislike (actor: string, activity: ActivityUndo) { function undoDislike (actor: string, activity: ActivityUndo) { const dislike = activity.object.object as DislikeObject - return db.sequelize.transaction(async t => { - const byAccount = await db.Account.loadByUrl(actor, t) + return sequelizeTypescript.transaction(async t => { + const byAccount = await AccountModel.loadByUrl(actor, t) if (!byAccount) throw new Error('Unknown account ' + actor) - const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t) + const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) if (!video) throw new Error('Unknown video ' + dislike.actor) - const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) + const rate = await AccountVideoRateModel.load(byAccount.id, video.id, t) if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) await rate.destroy({ transaction: t }) @@ -105,10 +108,10 @@ function processUndoFollow (actor: string, followActivity: ActivityFollow) { } function undoFollow (actor: string, followActivity: ActivityFollow) { - return db.sequelize.transaction(async t => { - const follower = await db.Account.loadByUrl(actor, t) - const following = await db.Account.loadByUrl(followActivity.object, t) - const accountFollow = await db.AccountFollow.loadByAccountAndTarget(follower.id, following.id, t) + return sequelizeTypescript.transaction(async t => { + const follower = await AccountModel.loadByUrl(actor, t) + const following = await AccountModel.loadByUrl(followActivity.object, t) + const accountFollow = await AccountFollowModel.loadByAccountAndTarget(follower.id, following.id, t) if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`) 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 @@ import * as Bluebird from 'bluebird' import { VideoChannelObject, VideoTorrentObject } from '../../../../shared' -import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { logger } from '../../../helpers/logger' -import { resetSequelizeInstance } from '../../../helpers/utils' -import { database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoInstance } from '../../../models/video/video-interface' +import { ActivityUpdate } from '../../../../shared/models/activitypub' +import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' +import { sequelizeTypescript } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { TagModel } from '../../../models/video/tag' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoFileModel } from '../../../models/video/video-file' import { getOrCreateAccountAndServer } from '../account' import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' @@ -30,7 +31,7 @@ export { // --------------------------------------------------------------------------- -function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject) { +function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) { const options = { arguments: [ account, video ], errorMessage: 'Cannot update the remote video with many retries' @@ -39,18 +40,18 @@ function processUpdateVideo (account: AccountInstance, video: VideoTorrentObject return retryTransactionWrapper(updateRemoteVideo, options) } -async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpdate: VideoTorrentObject) { +async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) { logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) - let videoInstance: VideoInstance + let videoInstance: VideoModel let videoFieldsSave: object try { - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } - const videoInstance = await db.Video.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) + const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.') if (videoInstance.VideoChannel.Account.id !== account.id) { @@ -81,12 +82,12 @@ async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpd await Promise.all(videoFileDestroyTasks) const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoInstance, videoAttributesToUpdate) - const tasks: Bluebird[] = videoFileAttributes.map(f => db.VideoFile.create(f)) + const tasks: Bluebird[] = videoFileAttributes.map(f => VideoFileModel.create(f)) await Promise.all(tasks) const tags = videoAttributesToUpdate.tag.map(t => t.name) - const tagInstances = await db.Tag.findOrCreateTags(tags, t) - await videoInstance.setTags(tagInstances, sequelizeOptions) + const tagInstances = await TagModel.findOrCreateTags(tags, t) + await videoInstance.$set('Tags', tagInstances, sequelizeOptions) }) logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid) @@ -101,7 +102,7 @@ async function updateRemoteVideo (account: AccountInstance, videoAttributesToUpd } } -async function processUpdateVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { +async function processUpdateVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) { const options = { arguments: [ account, videoChannel ], errorMessage: 'Cannot update the remote video channel with many retries.' @@ -110,13 +111,13 @@ async function processUpdateVideoChannel (account: AccountInstance, videoChannel await retryTransactionWrapper(updateRemoteVideoChannel, options) } -async function updateRemoteVideoChannel (account: AccountInstance, videoChannel: VideoChannelObject) { +async function updateRemoteVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) { logger.debug('Updating remote video channel "%s".', videoChannel.uuid) - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } - const videoChannelInstance = await db.VideoChannel.loadByUrl(videoChannel.id) + const videoChannelInstance = await VideoChannelModel.loadByUrl(videoChannel.id) if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.') 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 @@ -import { Activity, ActivityType } from '../../../../shared/models/activitypub/activity' -import { logger } from '../../../helpers/logger' -import { AccountInstance } from '../../../models/account/account-interface' +import { Activity, ActivityType } from '../../../../shared/models/activitypub' +import { logger } from '../../../helpers' +import { AccountModel } from '../../../models/account/account' import { processAcceptActivity } from './process-accept' import { processAddActivity } from './process-add' import { processAnnounceActivity } from './process-announce' @@ -11,7 +11,7 @@ import { processLikeActivity } from './process-like' import { processUndoActivity } from './process-undo' import { processUpdateActivity } from './process-update' -const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise } = { +const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise } = { Create: processCreateActivity, Add: processAddActivity, Update: processUpdateActivity, @@ -23,7 +23,7 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun Like: processLikeActivity } -async function processActivities (activities: Activity[], signatureAccount?: AccountInstance, inboxAccount?: AccountInstance) { +async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) { for (const activity of activities) { // When we fetch remote data, we don't have signature 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 @@ import { Transaction } from 'sequelize' -import { Activity } from '../../../../shared/models/activitypub/activity' -import { logger } from '../../../helpers/logger' -import { ACTIVITY_PUB, database as db } from '../../../initializers' -import { AccountInstance } from '../../../models/account/account-interface' -import { VideoChannelInstance } from '../../../models/index' -import { VideoInstance } from '../../../models/video/video-interface' -import { - activitypubHttpJobScheduler, - ActivityPubHttpPayload -} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' +import { Activity } from '../../../../shared/models/activitypub' +import { logger } from '../../../helpers' +import { ACTIVITY_PUB } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoChannelShareModel } from '../../../models/video/video-channel-share' +import { VideoShareModel } from '../../../models/video/video-share' +import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler' async function forwardActivity ( activity: Activity, t: Transaction, - followersException: AccountInstance[] = [] + followersException: AccountModel[] = [] ) { const to = activity.to || [] const cc = activity.cc || [] @@ -25,7 +25,7 @@ async function forwardActivity ( } } - const toAccountFollowers = await db.Account.listByFollowersUrls(followersUrls, t) + const toAccountFollowers = await AccountModel.listByFollowersUrls(followersUrls, t) const uris = await computeFollowerUris(toAccountFollowers, followersException, t) if (uris.length === 0) { @@ -45,10 +45,10 @@ async function forwardActivity ( async function broadcastToFollowers ( data: any, - byAccount: AccountInstance, - toAccountFollowers: AccountInstance[], + byAccount: AccountModel, + toAccountFollowers: AccountModel[], t: Transaction, - followersException: AccountInstance[] = [] + followersException: AccountModel[] = [] ) { const uris = await computeFollowerUris(toAccountFollowers, followersException, t) if (uris.length === 0) { @@ -67,7 +67,7 @@ async function broadcastToFollowers ( return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload) } -async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) { +async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) { logger.debug('Creating unicast job.', { uri: toAccountUrl }) const jobPayload: ActivityPubHttpPayload = { @@ -79,42 +79,42 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload) } -function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo: AccountInstance[]) { +function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) { return { to: [ video.VideoChannel.Account.url ], cc: accountsInvolvedInVideo.map(a => a.followersUrl) } } -function getOriginVideoChannelAudience (videoChannel: VideoChannelInstance, accountsInvolved: AccountInstance[]) { +function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) { return { to: [ videoChannel.Account.url ], cc: accountsInvolved.map(a => a.followersUrl) } } -function getObjectFollowersAudience (accountsInvolvedInObject: AccountInstance[]) { +function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) { return { to: accountsInvolvedInObject.map(a => a.followersUrl), cc: [] } } -async function getAccountsInvolvedInVideo (video: VideoInstance, t: Transaction) { - const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id, t) +async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) { + const accountsToForwardView = await VideoShareModel.loadAccountsByShare(video.id, t) accountsToForwardView.push(video.VideoChannel.Account) return accountsToForwardView } -async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { - const accountsToForwardView = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) +async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { + const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t) accountsToForwardView.push(videoChannel.Account) return accountsToForwardView } -async function getAudience (accountSender: AccountInstance, t: Transaction, isPublic = true) { +async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) { const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t) // 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 return { to, cc } } -async function computeFollowerUris (toAccountFollower: AccountInstance[], followersException: AccountInstance[], t: Transaction) { +async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) { const toAccountFollowerIds = toAccountFollower.map(a => a.id) - const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t) + const result = await AccountFollowModel.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t) const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) - const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) - - return uris + return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) } // --------------------------------------------------------------------------- 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 @@ import { Transaction } from 'sequelize' -import { ActivityAccept } from '../../../../shared/models/activitypub/activity' -import { AccountInstance } from '../../../models' -import { AccountFollowInstance } from '../../../models/account/account-follow-interface' -import { unicastTo } from './misc' +import { ActivityAccept } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' import { getAccountFollowAcceptActivityPubUrl } from '../url' +import { unicastTo } from './misc' -async function sendAccept (accountFollow: AccountFollowInstance, t: Transaction) { +async function sendAccept (accountFollow: AccountFollowModel, t: Transaction) { const follower = accountFollow.AccountFollower const me = accountFollow.AccountFollowing @@ -23,7 +23,7 @@ export { // --------------------------------------------------------------------------- -function acceptActivityData (url: string, byAccount: AccountInstance) { +function acceptActivityData (url: string, byAccount: AccountModel) { const activity: ActivityAccept = { type: 'Accept', 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 @@ import { Transaction } from 'sequelize' -import { ActivityAdd } from '../../../../shared/models/activitypub/activity' -import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' -import { AccountInstance, VideoInstance } from '../../../models' +import { ActivityAdd } from '../../../../shared/models/activitypub' +import { VideoPrivacy } from '../../../../shared/models/videos' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' import { broadcastToFollowers, getAudience } from './misc' -async function sendAddVideo (video: VideoInstance, t: Transaction) { +async function sendAddVideo (video: VideoModel, t: Transaction) { const byAccount = video.VideoChannel.Account const videoObject = video.toActivityPubObject() @@ -15,16 +16,17 @@ async function sendAddVideo (video: VideoInstance, t: Transaction) { async function addActivityData ( url: string, - byAccount: AccountInstance, - video: VideoInstance, + byAccount: AccountModel, + video: VideoModel, target: string, object: any, t: Transaction -) { +): Promise { const videoPublic = video.privacy === VideoPrivacy.PUBLIC const { to, cc } = await getAudience(byAccount, t, videoPublic) - const activity: ActivityAdd = { + + return { type: 'Add', id: url, actor: byAccount.url, @@ -33,8 +35,6 @@ async function addActivityData ( object, target } - - return activity } // --------------------------------------------------------------------------- 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 @@ import { Transaction } from 'sequelize' import { ActivityAdd } from '../../../../shared/index' -import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' -import { AccountInstance, VideoInstance } from '../../../models' -import { VideoChannelInstance } from '../../../models/video/video-channel-interface' +import { ActivityAnnounce, ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' import { getAnnounceActivityPubUrl } from '../url' import { broadcastToFollowers, @@ -17,7 +18,7 @@ import { import { addActivityData } from './send-add' import { createActivityData } from './send-create' -async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function buildVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getAnnounceActivityPubUrl(video.url, byAccount) const videoChannel = video.VideoChannel @@ -25,18 +26,16 @@ async function buildVideoAnnounceToFollowers (byAccount: AccountInstance, video: const accountsToForwardView = await getAccountsInvolvedInVideo(video, t) const audience = getObjectFollowersAudience(accountsToForwardView) - const data = await announceActivityData(url, byAccount, announcedActivity, t, audience) - - return data + return announceActivityData(url, byAccount, announcedActivity, t, audience) } -async function sendVideoAnnounceToFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendVideoAnnounceToFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const data = await buildVideoAnnounceToFollowers(byAccount, video, t) return broadcastToFollowers(data, byAccount, [ byAccount ], t) } -async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendVideoAnnounceToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getAnnounceActivityPubUrl(video.url, byAccount) const videoChannel = video.VideoChannel @@ -49,24 +48,22 @@ async function sendVideoAnnounceToOrigin (byAccount: AccountInstance, video: Vid return unicastTo(data, byAccount, videoChannel.Account.sharedInboxUrl, t) } -async function buildVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { +async function buildVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) { const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t) const accountsToForwardView = await getAccountsInvolvedInVideoChannel(videoChannel, t) const audience = getObjectFollowersAudience(accountsToForwardView) - const data = await announceActivityData(url, byAccount, announcedActivity, t, audience) - - return data + return announceActivityData(url, byAccount, announcedActivity, t, audience) } -async function sendVideoChannelAnnounceToFollowers (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { +async function sendVideoChannelAnnounceToFollowers (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) { const data = await buildVideoChannelAnnounceToFollowers(byAccount, videoChannel, t) return broadcastToFollowers(data, byAccount, [ byAccount ], t) } -async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, videoChannel: VideoChannelInstance, t: Transaction) { +async function sendVideoChannelAnnounceToOrigin (byAccount: AccountModel, videoChannel: VideoChannelModel, t: Transaction) { const url = getAnnounceActivityPubUrl(videoChannel.url, byAccount) const announcedActivity = await createActivityData(url, videoChannel.Account, videoChannel.toActivityPubObject(), t) @@ -79,16 +76,16 @@ async function sendVideoChannelAnnounceToOrigin (byAccount: AccountInstance, vid async function announceActivityData ( url: string, - byAccount: AccountInstance, + byAccount: AccountModel, object: ActivityCreate | ActivityAdd, t: Transaction, audience?: ActivityAudience -) { +): Promise { if (!audience) { audience = await getAudience(byAccount, t) } - const activity: ActivityAnnounce = { + return { type: 'Announce', to: audience.to, cc: audience.cc, @@ -96,8 +93,6 @@ async function announceActivityData ( actor: byAccount.url, object } - - return activity } // --------------------------------------------------------------------------- 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 @@ import { Transaction } from 'sequelize' -import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity' -import { getServerAccount } from '../../../helpers/utils' -import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' -import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' +import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' +import { getServerAccount } from '../../../helpers' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoAbuseModel } from '../../../models/video/video-abuse' +import { VideoChannelModel } from '../../../models/video/video-channel' import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' import { broadcastToFollowers, @@ -13,7 +15,7 @@ import { unicastTo } from './misc' -async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { +async function sendCreateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { const byAccount = videoChannel.Account const videoChannelObject = videoChannel.toActivityPubObject() @@ -22,7 +24,7 @@ async function sendCreateVideoChannel (videoChannel: VideoChannelInstance, t: Tr return broadcastToFollowers(data, byAccount, [ byAccount ], t) } -async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbuseInstance, video: VideoInstance, t: Transaction) { +async function sendVideoAbuse (byAccount: AccountModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { const url = getVideoAbuseActivityPubUrl(videoAbuse) const audience = { to: [ video.VideoChannel.Account.url ], cc: [] } @@ -31,7 +33,7 @@ async function sendVideoAbuse (byAccount: AccountInstance, videoAbuse: VideoAbus return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendCreateViewToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoViewActivityPubUrl(byAccount, video) const viewActivity = createViewActivityData(byAccount, video) @@ -42,7 +44,7 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendCreateViewToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoViewActivityPubUrl(byAccount, video) const viewActivity = createViewActivityData(byAccount, video) @@ -56,7 +58,7 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) } -async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendCreateDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoDislikeActivityPubUrl(byAccount, video) const dislikeActivity = createDislikeActivityData(byAccount, video) @@ -67,7 +69,7 @@ async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: Vid return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendCreateDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoDislikeActivityPubUrl(byAccount, video) const dislikeActivity = createDislikeActivityData(byAccount, video) @@ -79,12 +81,18 @@ async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, vi return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException) } -async function createActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction, audience?: ActivityAudience) { +async function createActivityData ( + url: string, + byAccount: AccountModel, + object: any, + t: Transaction, + audience?: ActivityAudience +): Promise { if (!audience) { audience = await getAudience(byAccount, t) } - const activity: ActivityCreate = { + return { type: 'Create', id: url, actor: byAccount.url, @@ -92,18 +100,14 @@ async function createActivityData (url: string, byAccount: AccountInstance, obje cc: audience.cc, object } - - return activity } -function createDislikeActivityData (byAccount: AccountInstance, video: VideoInstance) { - const obj = { +function createDislikeActivityData (byAccount: AccountModel, video: VideoModel) { + return { type: 'Dislike', actor: byAccount.url, object: video.url } - - return obj } // --------------------------------------------------------------------------- @@ -121,12 +125,10 @@ export { // --------------------------------------------------------------------------- -function createViewActivityData (byAccount: AccountInstance, video: VideoInstance) { - const obj = { +function createViewActivityData (byAccount: AccountModel, video: VideoModel) { + return { type: 'View', actor: byAccount.url, object: video.url } - - return obj } 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 @@ import { Transaction } from 'sequelize' -import { ActivityDelete } from '../../../../shared/models/activitypub/activity' -import { database as db } from '../../../initializers' -import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' +import { ActivityDelete } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoChannelShareModel } from '../../../models/video/video-channel-share' +import { VideoShareModel } from '../../../models/video/video-share' import { broadcastToFollowers } from './misc' -async function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { +async function sendDeleteVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { const byAccount = videoChannel.Account const data = deleteActivityData(videoChannel.url, byAccount) - const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) + const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t) accountsInvolved.push(byAccount) return broadcastToFollowers(data, byAccount, accountsInvolved, t) } -async function sendDeleteVideo (video: VideoInstance, t: Transaction) { +async function sendDeleteVideo (video: VideoModel, t: Transaction) { const byAccount = video.VideoChannel.Account const data = deleteActivityData(video.url, byAccount) - const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id, t) + const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t) accountsInvolved.push(byAccount) return broadcastToFollowers(data, byAccount, accountsInvolved, t) } -async function sendDeleteAccount (account: AccountInstance, t: Transaction) { +async function sendDeleteAccount (account: AccountModel, t: Transaction) { const data = deleteActivityData(account.url, account) return broadcastToFollowers(data, account, [ account ], t) @@ -42,12 +45,10 @@ export { // --------------------------------------------------------------------------- -function deleteActivityData (url: string, byAccount: AccountInstance) { - const activity: ActivityDelete = { +function deleteActivityData (url: string, byAccount: AccountModel): ActivityDelete { + return { type: 'Delete', id: url, actor: byAccount.url } - - return activity } 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 @@ import { Transaction } from 'sequelize' -import { ActivityFollow } from '../../../../shared/models/activitypub/activity' -import { AccountInstance } from '../../../models' -import { AccountFollowInstance } from '../../../models/account/account-follow-interface' +import { ActivityFollow } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' import { getAccountFollowActivityPubUrl } from '../url' import { unicastTo } from './misc' -function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) { +function sendFollow (accountFollow: AccountFollowModel, t: Transaction) { const me = accountFollow.AccountFollower const following = accountFollow.AccountFollowing @@ -15,15 +15,13 @@ function sendFollow (accountFollow: AccountFollowInstance, t: Transaction) { return unicastTo(data, me, following.inboxUrl, t) } -function followActivityData (url: string, byAccount: AccountInstance, targetAccount: AccountInstance) { - const activity: ActivityFollow = { +function followActivityData (url: string, byAccount: AccountModel, targetAccount: AccountModel): ActivityFollow { + return { type: 'Follow', id: url, actor: byAccount.url, object: targetAccount.url } - - return activity } // --------------------------------------------------------------------------- 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 @@ import { Transaction } from 'sequelize' -import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub/activity' -import { AccountInstance, VideoInstance } from '../../../models' +import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' import { getVideoLikeActivityPubUrl } from '../url' import { broadcastToFollowers, @@ -11,7 +12,7 @@ import { unicastTo } from './misc' -async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoLikeActivityPubUrl(byAccount, video) const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) @@ -21,7 +22,7 @@ async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstanc return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const url = getVideoLikeActivityPubUrl(byAccount, video) const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video, t) @@ -34,16 +35,16 @@ async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: Vide async function likeActivityData ( url: string, - byAccount: AccountInstance, - video: VideoInstance, + byAccount: AccountModel, + video: VideoModel, t: Transaction, audience?: ActivityAudience -) { +): Promise { if (!audience) { audience = await getAudience(byAccount, t) } - const activity: ActivityLike = { + return { type: 'Like', id: url, actor: byAccount.url, @@ -51,8 +52,6 @@ async function likeActivityData ( cc: audience.cc, object: video.url } - - return activity } // --------------------------------------------------------------------------- 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 { ActivityFollow, ActivityLike, ActivityUndo -} from '../../../../shared/models/activitypub/activity' -import { AccountInstance } from '../../../models' -import { AccountFollowInstance } from '../../../models/account/account-follow-interface' -import { VideoInstance } from '../../../models/video/video-interface' +} from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { AccountFollowModel } from '../../../models/account/account-follow' +import { VideoModel } from '../../../models/video/video' import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' import { broadcastToFollowers, @@ -22,7 +22,7 @@ import { createActivityData, createDislikeActivityData } from './send-create' import { followActivityData } from './send-follow' import { likeActivityData } from './send-like' -async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transaction) { +async function sendUndoFollow (accountFollow: AccountFollowModel, t: Transaction) { const me = accountFollow.AccountFollower const following = accountFollow.AccountFollowing @@ -35,7 +35,7 @@ async function sendUndoFollow (accountFollow: AccountFollowInstance, t: Transact return unicastTo(data, me, following.inboxUrl, t) } -async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendUndoLikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) const undoUrl = getUndoActivityPubUrl(likeUrl) @@ -47,7 +47,7 @@ async function sendUndoLikeToOrigin (byAccount: AccountInstance, video: VideoIns return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendUndoLikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) const undoUrl = getUndoActivityPubUrl(likeUrl) @@ -60,7 +60,7 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video: return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException) } -async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendUndoDislikeToOrigin (byAccount: AccountModel, video: VideoModel, t: Transaction) { const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) const undoUrl = getUndoActivityPubUrl(dislikeUrl) @@ -74,7 +74,7 @@ async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: Video return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) } -async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { +async function sendUndoDislikeToVideoFollowers (byAccount: AccountModel, video: VideoModel, t: Transaction) { const dislikeUrl = getVideoDislikeActivityPubUrl(byAccount, video) const undoUrl = getUndoActivityPubUrl(dislikeUrl) @@ -103,16 +103,16 @@ export { async function undoActivityData ( url: string, - byAccount: AccountInstance, + byAccount: AccountModel, object: ActivityFollow | ActivityLike | ActivityCreate, t: Transaction, audience?: ActivityAudience -) { +): Promise { if (!audience) { audience = await getAudience(byAccount, t) } - const activity: ActivityUndo = { + return { type: 'Undo', id: url, actor: byAccount.url, @@ -120,6 +120,4 @@ async function undoActivityData ( cc: audience.cc, object } - - return activity } 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 @@ import { Transaction } from 'sequelize' -import { ActivityUpdate } from '../../../../shared/models/activitypub/activity' -import { database as db } from '../../../initializers' -import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' +import { ActivityUpdate } from '../../../../shared/models/activitypub' +import { AccountModel } from '../../../models/account/account' +import { VideoModel } from '../../../models/video/video' +import { VideoChannelModel } from '../../../models/video/video-channel' +import { VideoChannelShareModel } from '../../../models/video/video-channel-share' +import { VideoShareModel } from '../../../models/video/video-share' import { getUpdateActivityPubUrl } from '../url' import { broadcastToFollowers, getAudience } from './misc' -async function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Transaction) { +async function sendUpdateVideoChannel (videoChannel: VideoChannelModel, t: Transaction) { const byAccount = videoChannel.Account const url = getUpdateActivityPubUrl(videoChannel.url, videoChannel.updatedAt.toISOString()) const videoChannelObject = videoChannel.toActivityPubObject() const data = await updateActivityData(url, byAccount, videoChannelObject, t) - const accountsInvolved = await db.VideoChannelShare.loadAccountsByShare(videoChannel.id, t) + const accountsInvolved = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t) accountsInvolved.push(byAccount) return broadcastToFollowers(data, byAccount, accountsInvolved, t) } -async function sendUpdateVideo (video: VideoInstance, t: Transaction) { +async function sendUpdateVideo (video: VideoModel, t: Transaction) { const byAccount = video.VideoChannel.Account const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) const videoObject = video.toActivityPubObject() const data = await updateActivityData(url, byAccount, videoObject, t) - const accountsInvolved = await db.VideoShare.loadAccountsByShare(video.id, t) + const accountsInvolved = await VideoShareModel.loadAccountsByShare(video.id, t) accountsInvolved.push(byAccount) return broadcastToFollowers(data, byAccount, accountsInvolved, t) @@ -40,9 +43,9 @@ export { // --------------------------------------------------------------------------- -async function updateActivityData (url: string, byAccount: AccountInstance, object: any, t: Transaction) { +async function updateActivityData (url: string, byAccount: AccountModel, object: any, t: Transaction): Promise { const { to, cc } = await getAudience(byAccount, t) - const activity: ActivityUpdate = { + return { type: 'Update', id: url, actor: byAccount.url, @@ -50,6 +53,4 @@ async function updateActivityData (url: string, byAccount: AccountInstance, obje cc, object } - - return activity } 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 @@ import { Transaction } from 'sequelize' -import { getServerAccount } from '../../helpers/utils' -import { database as db } from '../../initializers' -import { VideoChannelInstance } from '../../models/index' -import { VideoInstance } from '../../models/video/video-interface' -import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send/send-announce' +import { getServerAccount } from '../../helpers' +import { VideoModel } from '../../models/video/video' +import { VideoChannelModel } from '../../models/video/video-channel' +import { VideoChannelShareModel } from '../../models/video/video-channel-share' +import { VideoShareModel } from '../../models/video/video-share' +import { sendVideoAnnounceToFollowers, sendVideoChannelAnnounceToFollowers } from './send' -async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: Transaction) { +async function shareVideoChannelByServer (videoChannel: VideoChannelModel, t: Transaction) { const serverAccount = await getServerAccount() - await db.VideoChannelShare.create({ + await VideoChannelShareModel.create({ accountId: serverAccount.id, videoChannelId: videoChannel.id }, { transaction: t }) @@ -16,10 +17,10 @@ async function shareVideoChannelByServer (videoChannel: VideoChannelInstance, t: return sendVideoChannelAnnounceToFollowers(serverAccount, videoChannel, t) } -async function shareVideoByServer (video: VideoInstance, t: Transaction) { +async function shareVideoByServer (video: VideoModel, t: Transaction) { const serverAccount = await getServerAccount() - await db.VideoShare.create({ + await VideoShareModel.create({ accountId: serverAccount.id, videoId: video.id }, { 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 @@ -import { CONFIG } from '../../initializers/constants' -import { VideoInstance } from '../../models/video/video-interface' -import { VideoChannelInstance } from '../../models/video/video-channel-interface' -import { VideoAbuseInstance } from '../../models/video/video-abuse-interface' -import { AccountFollowInstance } from '../../models/account/account-follow-interface' -import { AccountInstance } from '../../models/account/account-interface' +import { CONFIG } from '../../initializers' +import { AccountModel } from '../../models/account/account' +import { AccountFollowModel } from '../../models/account/account-follow' +import { VideoModel } from '../../models/video/video' +import { VideoAbuseModel } from '../../models/video/video-abuse' +import { VideoChannelModel } from '../../models/video/video-channel' -function getVideoActivityPubUrl (video: VideoInstance) { +function getVideoActivityPubUrl (video: VideoModel) { return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid } -function getVideoChannelActivityPubUrl (videoChannel: VideoChannelInstance) { +function getVideoChannelActivityPubUrl (videoChannel: VideoChannelModel) { return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannel.uuid } @@ -17,37 +17,37 @@ function getAccountActivityPubUrl (accountName: string) { return CONFIG.WEBSERVER.URL + '/account/' + accountName } -function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseInstance) { +function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id } -function getVideoViewActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { +function getVideoViewActivityPubUrl (byAccount: AccountModel, video: VideoModel) { return video.url + '/views/' + byAccount.uuid + '/' + new Date().toISOString() } -function getVideoLikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { +function getVideoLikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { return byAccount.url + '/likes/' + video.id } -function getVideoDislikeActivityPubUrl (byAccount: AccountInstance, video: VideoInstance) { +function getVideoDislikeActivityPubUrl (byAccount: AccountModel, video: VideoModel) { return byAccount.url + '/dislikes/' + video.id } -function getAccountFollowActivityPubUrl (accountFollow: AccountFollowInstance) { +function getAccountFollowActivityPubUrl (accountFollow: AccountFollowModel) { const me = accountFollow.AccountFollower const following = accountFollow.AccountFollowing return me.url + '/follows/' + following.id } -function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowInstance) { +function getAccountFollowAcceptActivityPubUrl (accountFollow: AccountFollowModel) { const follower = accountFollow.AccountFollower const me = accountFollow.AccountFollowing return follower.url + '/accepts/follows/' + me.id } -function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountInstance) { +function getAnnounceActivityPubUrl (originalUrl: string, byAccount: AccountModel) { return originalUrl + '/announces/' + byAccount.id } 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 @@ -import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' -import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub/video-channels' -import { logger } from '../../helpers/logger' -import { doRequest } from '../../helpers/requests' -import { database as db } from '../../initializers' -import { ACTIVITY_PUB } from '../../initializers/constants' -import { AccountInstance } from '../../models/account/account-interface' +import { VideoChannelObject } from '../../../shared/models/activitypub/objects' +import { doRequest, logger } from '../../helpers' +import { isVideoChannelObjectValid } from '../../helpers/custom-validators/activitypub' +import { ACTIVITY_PUB } from '../../initializers' +import { AccountModel } from '../../models/account/account' +import { VideoChannelModel } from '../../models/video/video-channel' import { videoChannelActivityObjectToDBAttributes } from './process/misc' -async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { - let videoChannel = await db.VideoChannel.loadByUrl(videoChannelUrl) +async function getOrCreateVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) { + let videoChannel = await VideoChannelModel.loadByUrl(videoChannelUrl) // We don't have this account in our database, fetch it on remote if (!videoChannel) { @@ -22,7 +21,7 @@ async function getOrCreateVideoChannel (ownerAccount: AccountInstance, videoChan return videoChannel } -async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChannelUrl: string) { +async function fetchRemoteVideoChannel (ownerAccount: AccountModel, videoChannelUrl: string) { const options = { uri: videoChannelUrl, method: 'GET', @@ -48,7 +47,7 @@ async function fetchRemoteVideoChannel (ownerAccount: AccountInstance, videoChan } const videoChannelAttributes = videoChannelActivityObjectToDBAttributes(videoChannelJSON, ownerAccount) - const videoChannel = db.VideoChannel.build(videoChannelAttributes) + const videoChannel = new VideoChannelModel(videoChannelAttributes) videoChannel.Account = ownerAccount 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' import * as request from 'request' import { Transaction } from 'sequelize' import { ActivityIconObject } from '../../../shared/index' -import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' -import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants' -import { AccountInstance } from '../../models/account/account-interface' -import { VideoInstance } from '../../models/video/video-interface' -import { sendLikeToOrigin } from './index' -import { sendCreateDislikeToOrigin, sendCreateDislikeToVideoFollowers } from './send/send-create' -import { sendLikeToVideoFollowers } from './send/send-like' +import { doRequest, doRequestAndSaveToFile } from '../../helpers' +import { CONFIG, REMOTE_SCHEME, STATIC_PATHS } from '../../initializers' +import { AccountModel } from '../../models/account/account' +import { VideoModel } from '../../models/video/video' import { + sendCreateDislikeToOrigin, + sendCreateDislikeToVideoFollowers, + sendLikeToOrigin, + sendLikeToVideoFollowers, sendUndoDislikeToOrigin, sendUndoDislikeToVideoFollowers, sendUndoLikeToOrigin, sendUndoLikeToVideoFollowers -} from './send/send-undo' +} from './send' -function fetchRemoteVideoPreview (video: VideoInstance) { +function fetchRemoteVideoPreview (video: VideoModel) { // FIXME: use url const host = video.VideoChannel.Account.Server.host const path = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) @@ -24,7 +25,7 @@ function fetchRemoteVideoPreview (video: VideoInstance) { return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) } -async function fetchRemoteVideoDescription (video: VideoInstance) { +async function fetchRemoteVideoDescription (video: VideoModel) { // FIXME: use url const host = video.VideoChannel.Account.Server.host const path = video.getDescriptionPath() @@ -37,7 +38,7 @@ async function fetchRemoteVideoDescription (video: VideoInstance) { return body.description ? body.description : '' } -function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObject) { +function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) { const thumbnailName = video.getThumbnailName() const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName) @@ -49,8 +50,8 @@ function generateThumbnailFromUrl (video: VideoInstance, icon: ActivityIconObjec } async function sendVideoRateChangeToFollowers ( - account: AccountInstance, - video: VideoInstance, + account: AccountModel, + video: VideoModel, likes: number, dislikes: number, t: Transaction @@ -69,8 +70,8 @@ async function sendVideoRateChangeToFollowers ( } async function sendVideoRateChangeToOrigin ( - account: AccountInstance, - video: VideoInstance, + account: AccountModel, + video: VideoModel, likes: number, dislikes: number, 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 @@ import * as asyncLRU from 'async-lru' -import { join } from 'path' import { createWriteStream } from 'fs' - -import { database as db, CONFIG, CACHE } from '../../initializers' +import { join } from 'path' import { logger, unlinkPromise } from '../../helpers' -import { VideoInstance } from '../../models' -import { fetchRemoteVideoPreview } from '../activitypub/videos' +import { CACHE, CONFIG } from '../../initializers' +import { VideoModel } from '../../models/video/video' +import { fetchRemoteVideoPreview } from '../activitypub' class VideosPreviewCache { @@ -43,7 +42,7 @@ class VideosPreviewCache { } private async loadPreviews (key: string) { - const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(key) + const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(key) if (!video) return undefined if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) @@ -53,7 +52,7 @@ class VideosPreviewCache { return res } - private saveRemotePreviewAndReturnPath (video: VideoInstance) { + private saveRemotePreviewAndReturnPath (video: VideoModel) { const req = fetchRemoteVideoPreview(video) return new Promise((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 @@ -import { logger } from '../../../helpers' -import { doRequest } from '../../../helpers/requests' +import { doRequest, logger } from '../../../helpers' import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 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 @@ -import { logger } from '../../../helpers' -import { doRequest } from '../../../helpers/requests' -import { ACTIVITY_PUB } from '../../../initializers/constants' -import { processActivities } from '../../activitypub/process/process' +import { doRequest, logger } from '../../../helpers' +import { ACTIVITY_PUB } from '../../../initializers' +import { processActivities } from '../../activitypub/process' import { ActivityPubHttpPayload } from './activitypub-http-job-scheduler' 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 @@ import { JobCategory } from '../../../../shared' -import { buildSignedActivity } from '../../../helpers/activitypub' -import { logger } from '../../../helpers/logger' -import { ACTIVITY_PUB } from '../../../initializers/constants' -import { database as db } from '../../../initializers/database' +import { buildSignedActivity, logger } from '../../../helpers' +import { ACTIVITY_PUB } from '../../../initializers' +import { AccountModel } from '../../../models/account/account' import { JobHandler, JobScheduler } from '../job-scheduler' import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' @@ -46,7 +45,7 @@ async function computeBody (payload: ActivityPubHttpPayload) { let body = payload.body if (payload.signatureAccountId) { - const accountSignature = await db.Account.load(payload.signatureAccountId) + const accountSignature = await AccountModel.load(payload.signatureAccountId) if (!accountSignature) throw new Error('Unknown signature account id.') body = await buildSignedActivity(accountSignature, payload.body) } 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 @@ -import { logger } from '../../../helpers' -import { doRequest } from '../../../helpers/requests' +import { doRequest, logger } from '../../../helpers' import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 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' import * as Sequelize from 'sequelize' import { JobCategory } from '../../../shared' import { logger } from '../../helpers' -import { database as db, JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' -import { JobInstance } from '../../models' +import { JOB_STATES, JOBS_FETCH_LIMIT_PER_CYCLE, JOBS_FETCHING_INTERVAL } from '../../initializers' +import { JobModel } from '../../models/job/job' export interface JobHandler { process (data: object, jobId: number): Promise @@ -24,12 +24,12 @@ class JobScheduler { logger.info('Jobs scheduler %s activated.', this.jobCategory) - const jobsQueue = queue(this.processJob.bind(this)) + const jobsQueue = queue(this.processJob.bind(this)) // Finish processing jobs from a previous start const state = JOB_STATES.PROCESSING try { - const jobs = await db.Job.listWithLimitByCategory(limit, state, this.jobCategory) + const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory) this.enqueueJobs(jobsQueue, jobs) } catch (err) { @@ -45,7 +45,7 @@ class JobScheduler { const state = JOB_STATES.PENDING try { - const jobs = await db.Job.listWithLimitByCategory(limit, state, this.jobCategory) + const jobs = await JobModel.listWithLimitByCategory(limit, state, this.jobCategory) this.enqueueJobs(jobsQueue, jobs) } catch (err) { @@ -70,14 +70,14 @@ class JobScheduler { const options = { transaction } - return db.Job.create(createQuery, options) + return JobModel.create(createQuery, options) } - private enqueueJobs (jobsQueue: AsyncQueue, jobs: JobInstance[]) { + private enqueueJobs (jobsQueue: AsyncQueue, jobs: JobModel[]) { jobs.forEach(job => jobsQueue.push(job)) } - private async processJob (job: JobInstance, callback: (err: Error) => void) { + private async processJob (job: JobModel, callback: (err: Error) => void) { const jobHandler = this.jobHandlers[job.handlerName] if (jobHandler === undefined) { const errorString = 'Unknown job handler ' + job.handlerName + ' for job ' + job.id @@ -110,7 +110,7 @@ class JobScheduler { return callback(null) } - private async onJobError (jobHandler: JobHandler, job: JobInstance, err: Error) { + private async onJobError (jobHandler: JobHandler, job: JobModel, err: Error) { job.state = JOB_STATES.ERROR try { @@ -121,7 +121,7 @@ class JobScheduler { } } - private async onJobSuccess (jobHandler: JobHandler, job: JobInstance, jobResult: T) { + private async onJobSuccess (jobHandler: JobHandler, job: JobModel, jobResult: T) { job.state = JOB_STATES.SUCCESS 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 @@ import { JobCategory } from '../../../../shared' +import { VideoModel } from '../../../models/video/video' import { JobHandler, JobScheduler } from '../job-scheduler' + import * as videoFileOptimizer from './video-file-optimizer-handler' import * as videoFileTranscoder from './video-file-transcoder-handler' -import { VideoInstance } from '../../../models/video/video-interface' type TranscodingJobPayload = { videoUUID: string resolution?: number } -const jobHandlers: { [ handlerName: string ]: JobHandler } = { +const jobHandlers: { [ handlerName: string ]: JobHandler } = { videoFileOptimizer, videoFileTranscoder } 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 @@ import * as Bluebird from 'bluebird' import { computeResolutionsToTranscode, logger } from '../../../helpers' -import { database as db } from '../../../initializers/database' -import { VideoInstance } from '../../../models' -import { sendAddVideo } from '../../activitypub/send/send-add' +import { sequelizeTypescript } from '../../../initializers' +import { VideoModel } from '../../../models/video/video' +import { shareVideoByServer } from '../../activitypub' +import { sendAddVideo } from '../../activitypub/send' import { JobScheduler } from '../job-scheduler' import { TranscodingJobPayload } from './transcoding-job-scheduler' -import { shareVideoByServer } from '../../activitypub/share' async function process (data: TranscodingJobPayload, jobId: number) { - const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) + const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) // No video, maybe deleted? if (!video) { 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) { return Promise.resolve() } -async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: JobScheduler) { +async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobScheduler) { if (video === undefined) return undefined logger.info('Job %d is a success.', jobId) // Maybe the video changed in database, refresh it - const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) + const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) // Video does not exist anymore if (!videoDatabase) return undefined @@ -50,7 +50,7 @@ async function onSuccess (jobId: number, video: VideoInstance, jobScheduler: Job if (resolutionsEnabled.length !== 0) { try { - await db.sequelize.transaction(async t => { + await sequelizeTypescript.transaction(async t => { const tasks: Bluebird[] = [] 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 @@ import { VideoResolution } from '../../../../shared' import { logger } from '../../../helpers' -import { database as db } from '../../../initializers/database' -import { VideoInstance } from '../../../models' -import { sendUpdateVideo } from '../../activitypub/send/send-update' +import { VideoModel } from '../../../models/video/video' +import { sendUpdateVideo } from '../../activitypub/send' async function process (data: { videoUUID: string, resolution: VideoResolution }, jobId: number) { - const video = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) + const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(data.videoUUID) // No video, maybe deleted? if (!video) { 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) { return Promise.resolve() } -async function onSuccess (jobId: number, video: VideoInstance) { +async function onSuccess (jobId: number, video: VideoModel) { if (video === undefined) return undefined logger.info('Job %d is a success.', jobId) // Maybe the video changed in database, refresh it - const videoDatabase = await db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) + const videoDatabase = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(video.uuid) // Video does not exist anymore if (!videoDatabase) return undefined 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 @@ -import { OAuthClientInstance, UserInstance } from '../models' -import { database as db } from '../initializers/database' import { logger } from '../helpers' +import { UserModel } from '../models/account/user' +import { OAuthClientModel } from '../models/oauth/oauth-client' +import { OAuthTokenModel } from '../models/oauth/oauth-token' type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } @@ -9,25 +10,25 @@ type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpires function getAccessToken (bearerToken: string) { logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') - return db.OAuthToken.getByTokenAndPopulateUser(bearerToken) + return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) } function getClient (clientId: string, clientSecret: string) { logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') - return db.OAuthClient.getByIdAndSecret(clientId, clientSecret) + return OAuthClientModel.getByIdAndSecret(clientId, clientSecret) } function getRefreshToken (refreshToken: string) { logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') - return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) + return OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) } async function getUser (username: string, password: string) { logger.debug('Getting User (username: ' + username + ', password: ******).') - const user = await db.User.getByUsername(username) + const user = await UserModel.getByUsername(username) if (!user) return null const passwordMatch = await user.isPasswordMatch(password) @@ -37,7 +38,7 @@ async function getUser (username: string, password: string) { } async function revokeToken (tokenInfo: TokenInfo) { - const token = await db.OAuthToken.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) + const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) if (token) token.destroy() /* @@ -53,7 +54,7 @@ async function revokeToken (tokenInfo: TokenInfo) { return expiredToken } -async function saveToken (token: TokenInfo, client: OAuthClientInstance, user: UserInstance) { +async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') const tokenToCreate = { @@ -65,7 +66,7 @@ async function saveToken (token: TokenInfo, client: OAuthClientInstance, user: U userId: user.id } - const tokenCreated = await db.OAuthToken.create(tokenToCreate) + const tokenCreated = await OAuthTokenModel.create(tokenToCreate) const tokenToReturn = Object.assign(tokenCreated, { client, user }) 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 @@ import * as Sequelize from 'sequelize' -import { createPrivateAndPublicKeys } from '../helpers/peertube-crypto' -import { database as db } from '../initializers' -import { CONFIG } from '../initializers/constants' -import { UserInstance } from '../models' +import { createPrivateAndPublicKeys, logger } from '../helpers' +import { CONFIG, sequelizeTypescript } from '../initializers' +import { AccountModel } from '../models/account/account' +import { UserModel } from '../models/account/user' +import { getAccountActivityPubUrl } from './activitypub' import { createVideoChannel } from './video-channel' -import { logger } from '../helpers/logger' -import { getAccountActivityPubUrl } from './activitypub/url' -async function createUserAccountAndChannel (user: UserInstance, validateUser = true) { - const { account, videoChannel } = await db.sequelize.transaction(async t => { +async function createUserAccountAndChannel (user: UserModel, validateUser = true) { + const { account, videoChannel } = await sequelizeTypescript.transaction(async t => { const userOptions = { transaction: t, validate: validateUser @@ -38,7 +37,7 @@ async function createUserAccountAndChannel (user: UserInstance, validateUser = t async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { const url = getAccountActivityPubUrl(name) - const accountInstance = db.Account.build({ + const accountInstance = new AccountModel({ name, url, 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 @@ import * as Sequelize from 'sequelize' import { VideoChannelCreate } from '../../shared/models' -import { database as db } from '../initializers' -import { AccountInstance } from '../models' -import { getVideoChannelActivityPubUrl } from './activitypub/url' +import { AccountModel } from '../models/account/account' +import { VideoChannelModel } from '../models/video/video-channel' +import { getVideoChannelActivityPubUrl } from './activitypub' -async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountInstance, t: Sequelize.Transaction) { +async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { const videoChannelData = { name: videoChannelInfo.name, description: videoChannelInfo.description, @@ -12,7 +12,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account accountId: account.id } - const videoChannel = db.VideoChannel.build(videoChannelData) + const videoChannel = VideoChannelModel.build(videoChannelData) videoChannel.set('url', getVideoChannelActivityPubUrl(videoChannel)) 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' import { NextFunction, Request, RequestHandler, Response } from 'express' import { ActivityPubSignature } from '../../shared' import { isSignatureVerified, logger } from '../helpers' -import { database as db } from '../initializers' -import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers/constants' -import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub/account' +import { ACCEPT_HEADERS, ACTIVITY_PUB } from '../initializers' +import { fetchRemoteAccount, saveAccountAndServerIfNotExist } from '../lib/activitypub' +import { AccountModel } from '../models/account/account' async function checkSignature (req: Request, res: Response, next: NextFunction) { const signatureObject: ActivityPubSignature = req.body.signature logger.debug('Checking signature of account %s...', signatureObject.creator) - let account = await db.Account.loadByUrl(signatureObject.creator) + let account = await AccountModel.loadByUrl(signatureObject.creator) // We don't have this account in our database, fetch it on remote 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 @@ -import 'express-validator' import * as express from 'express' - +import 'express-validator' import { SortType } from '../helpers' -import { database } from '../initializers' function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) { if (!req.query.sort) req.query.sort = '-createdAt' @@ -57,7 +55,7 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter ... newSort.sortModel = undefined } else { - newSort.sortModel = database.Video + newSort.sortModel = 'Video' } 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 @@ -import 'express-validator' import * as express from 'express' - -import { UserInstance } from '../models' +import 'express-validator' import { UserRight } from '../../shared' import { logger } from '../helpers' +import { UserModel } from '../models/account/user' function ensureUserHasRight (userRight: UserRight) { return function (req: express.Request, res: express.Response, next: express.NextFunction) { - const user: UserInstance = res.locals.oauth.token.user + const user = res.locals.oauth.token.user as UserModel if (user.hasRight(userRight) === false) { logger.info('User %s does not have right %s to access to %s.', user.username, UserRight[userRight], req.path) 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 @@ import * as express from 'express' import { param } from 'express-validator/check' -import { logger, isLocalAccountNameExist } from '../../helpers' -import { isAccountNameValid } from '../../helpers/custom-validators/accounts' +import { logger } from '../../helpers' +import { isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts' import { areValidationErrors } from './utils' 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 @@ import * as express from 'express' import { body } from 'express-validator/check' -import { isRootActivityValid, logger } from '../../../helpers' +import { logger } from '../../../helpers' +import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub' import { areValidationErrors } from '../utils' 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 @@ import * as express from 'express' import { body } from 'express-validator/check' -import { isDateValid, isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid, logger } from '../../../helpers' +import { logger } from '../../../helpers' +import { isSignatureCreatorValid, isSignatureTypeValid, isSignatureValueValid } from '../../../helpers/custom-validators/activitypub' +import { isDateValid } from '../../../helpers/custom-validators/misc' import { areValidationErrors } from '../utils' 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 @@ import * as express from 'express' import { body, param } from 'express-validator/check' -import { isTestInstance } from '../../helpers/core-utils' +import { getServerAccount, isTestInstance, logger } from '../../helpers' +import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' import { isEachUniqueHostValid } from '../../helpers/custom-validators/servers' -import { logger } from '../../helpers/logger' -import { CONFIG, database as db } from '../../initializers' +import { CONFIG } from '../../initializers' +import { AccountFollowModel } from '../../models/account/account-follow' import { areValidationErrors } from './utils' -import { getServerAccount } from '../../helpers/utils' -import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' const followValidator = [ body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), @@ -38,7 +37,7 @@ const removeFollowingValidator = [ if (areValidationErrors(req, res)) return const serverAccount = await getServerAccount() - const follow = await db.AccountFollow.loadByAccountAndTarget(serverAccount.id, req.params.accountId) + const follow = await AccountFollowModel.loadByAccountAndTarget(serverAccount.id, req.params.accountId) if (!follow) { 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 @@ import * as express from 'express' import { query } from 'express-validator/check' import { join } from 'path' -import { isIdOrUUIDValid, isTestInstance, logger } from '../../helpers' +import { isTestInstance, logger } from '../../helpers' +import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' +import { isVideoExist } from '../../helpers/custom-validators/videos' import { CONFIG } from '../../initializers' import { areValidationErrors } from './utils' -import { isVideoExist } from '../../helpers/custom-validators/videos' const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' 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 @@ import { query } from 'express-validator/check' import * as express from 'express' - import { logger } from '../../helpers' import { SORTABLE_COLUMNS } from '../../initializers' 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 @@ import * as express from 'express' import 'express-validator' import { body, param } from 'express-validator/check' +import { isSignupAllowed, logger } from '../../helpers' +import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' import { - isIdOrUUIDValid, - isSignupAllowed, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, - isUserVideoQuotaValid, - logger -} from '../../helpers' + isUserVideoQuotaValid +} from '../../helpers/custom-validators/users' import { isVideoExist } from '../../helpers/custom-validators/videos' -import { database as db } from '../../initializers/database' +import { UserModel } from '../../models/account/user' import { areValidationErrors } from './utils' const usersAddValidator = [ @@ -153,7 +152,7 @@ export { // --------------------------------------------------------------------------- async function checkUserIdExist (id: number, res: express.Response) { - const user = await db.User.loadById(id) + const user = await UserModel.loadById(id) if (!user) { res.status(404) @@ -168,7 +167,7 @@ async function checkUserIdExist (id: number, res: express.Response) { } async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: string, res: express.Response) { - const user = await db.User.loadByUsernameOrEmail(username, email) + const user = await UserModel.loadByUsernameOrEmail(username, email) if (user) { 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 @@ import * as express from 'express' import { validationResult } from 'express-validator/check' - import { logger } from '../../helpers' 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 @@ import * as express from 'express' import { param } from 'express-validator/check' -import { isIdOrUUIDValid, logger } from '../../helpers' +import { logger } from '../../helpers' +import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' import { isVideoExist } from '../../helpers/custom-validators/videos' -import { database as db } from '../../initializers/database' -import { VideoInstance } from '../../models/video/video-interface' +import { VideoModel } from '../../models/video/video' +import { VideoBlacklistModel } from '../../models/video/video-blacklist' import { areValidationErrors } from './utils' const videosBlacklistRemoveValidator = [ @@ -42,7 +43,7 @@ export { } // --------------------------------------------------------------------------- -function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) { +function checkVideoIsBlacklistable (video: VideoModel, res: express.Response) { if (video.isOwned() === true) { res.status(403) .json({ error: 'Cannot blacklist a local video' }) @@ -54,8 +55,8 @@ function checkVideoIsBlacklistable (video: VideoInstance, res: express.Response) return true } -async function checkVideoIsBlacklisted (video: VideoInstance, res: express.Response) { - const blacklistedVideo = await db.BlacklistedVideo.loadByVideoId(video.id) +async function checkVideoIsBlacklisted (video: VideoModel, res: express.Response) { + const blacklistedVideo = await VideoBlacklistModel.loadByVideoId(video.id) if (!blacklistedVideo) { res.status(404) .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 @@ import * as express from 'express' import { body, param } from 'express-validator/check' import { UserRight } from '../../../shared' -import { isIdValid } from '../../helpers/custom-validators/misc' +import { logger } from '../../helpers' +import { isAccountIdExist } from '../../helpers/custom-validators/accounts' +import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' import { isVideoChannelDescriptionValid, isVideoChannelExist, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' -import { isIdOrUUIDValid } from '../../helpers/index' -import { logger } from '../../helpers/logger' -import { database as db } from '../../initializers' -import { UserInstance } from '../../models' +import { UserModel } from '../../models/account/user' +import { VideoChannelModel } from '../../models/video/video-channel' +import { VideoChannelShareModel } from '../../models/video/video-channel-share' import { areValidationErrors } from './utils' -import { isAccountIdExist } from '../../helpers/custom-validators/accounts' -import { VideoChannelInstance } from '../../models/video/video-channel-interface' const listVideoAccountChannelsValidator = [ param('accountId').custom(isIdOrUUIDValid).withMessage('Should have a valid account id'), @@ -109,7 +108,7 @@ const videoChannelsShareValidator = [ if (areValidationErrors(req, res)) return if (!await isVideoChannelExist(req.params.id, res)) return - const share = await db.VideoChannelShare.load(res.locals.video.id, req.params.accountId, undefined) + const share = await VideoChannelShareModel.load(res.locals.video.id, req.params.accountId, undefined) if (!share) { return res.status(404) .end() @@ -134,7 +133,7 @@ export { // --------------------------------------------------------------------------- -function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: VideoChannelInstance, res: express.Response) { +function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { // Retrieve the user who did the request if (videoChannel.isOwned() === false) { res.status(403) @@ -159,7 +158,7 @@ function checkUserCanDeleteVideoChannel (user: UserInstance, videoChannel: Video } async function checkVideoChannelIsNotTheLastOne (res: express.Response) { - const count = await db.VideoChannel.countByAccount(res.locals.oauth.token.User.Account.id) + const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id) if (count <= 1) { 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 @@ import * as express from 'express' +import 'express-validator' import { body, param, query } from 'express-validator/check' import { UserRight, VideoPrivacy } from '../../../shared' +import { getDurationFromVideoFile, logger } from '../../helpers' import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' import { isVideoAbuseReasonValid, @@ -16,12 +18,11 @@ import { isVideoRatingTypeValid, isVideoTagsValid } from '../../helpers/custom-validators/videos' -import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' -import { logger } from '../../helpers/logger' import { CONSTRAINTS_FIELDS } from '../../initializers' -import { database as db } from '../../initializers/database' -import { UserInstance } from '../../models/account/user-interface' -import { VideoInstance } from '../../models/video/video-interface' +import { UserModel } from '../../models/account/user' +import { VideoModel } from '../../models/video/video' +import { VideoChannelModel } from '../../models/video/video-channel' +import { VideoShareModel } from '../../models/video/video-share' import { authenticate } from '../oauth' import { areValidationErrors } from './utils' @@ -48,7 +49,7 @@ const videosAddValidator = [ const videoFile: Express.Multer.File = req.files['videofile'][0] const user = res.locals.oauth.token.User - const videoChannel = await db.VideoChannel.loadByIdAndAccount(req.body.channelId, user.Account.id) + const videoChannel = await VideoChannelModel.loadByIdAndAccount(req.body.channelId, user.Account.id) if (!videoChannel) { res.status(400) .json({ error: 'Unknown video video channel for this account.' }) @@ -221,7 +222,7 @@ const videosShareValidator = [ if (areValidationErrors(req, res)) return if (!await isVideoExist(req.params.id, res)) return - const share = await db.VideoShare.load(req.params.accountId, res.locals.video.id, undefined) + const share = await VideoShareModel.load(req.params.accountId, res.locals.video.id, undefined) if (!share) { return res.status(404) .end() @@ -249,7 +250,7 @@ export { // --------------------------------------------------------------------------- -function checkUserCanDeleteVideo (user: UserInstance, video: VideoInstance, res: express.Response) { +function checkUserCanDeleteVideo (user: UserModel, video: VideoModel, res: express.Response) { // Retrieve the user who did the request if (video.isOwned() === false) { 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 @@ import * as express from 'express' import { query } from 'express-validator/check' +import { logger } from '../../helpers' import { isWebfingerResourceValid } from '../../helpers/custom-validators/webfinger' -import { logger } from '../../helpers/logger' -import { database as db } from '../../initializers' +import { AccountModel } from '../../models/account/account' import { areValidationErrors } from './utils' const webfingerValidator = [ @@ -17,7 +17,7 @@ const webfingerValidator = [ const nameWithHost = req.query.resource.substr(5) const [ name ] = nameWithHost.split('@') - const account = await db.Account.loadLocalByName(name) + const account = await AccountModel.loadLocalByName(name) if (!account) { return res.status(404) .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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { AccountFollow, FollowState } from '../../../shared/models/accounts/follow.model' -import { ResultList } from '../../../shared/models/result-list.model' -import { AccountInstance } from './account-interface' - -export namespace AccountFollowMethods { - export type LoadByAccountAndTarget = ( - accountId: number, - targetAccountId: number, - t?: Sequelize.Transaction - ) => Bluebird - - export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList> - export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList> - - export type ListAcceptedFollowerUrlsForApi = ( - accountId: number[], - t: Sequelize.Transaction, - start?: number, - count?: number - ) => Promise< ResultList > - export type ListAcceptedFollowingUrlsForApi = ( - accountId: number[], - t: Sequelize.Transaction, - start?: number, - count?: number - ) => Promise< ResultList > - export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[], t: Sequelize.Transaction) => Promise< ResultList > - export type ToFormattedJSON = (this: AccountFollowInstance) => AccountFollow -} - -export interface AccountFollowClass { - loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget - listFollowersForApi: AccountFollowMethods.ListFollowersForApi - listFollowingForApi: AccountFollowMethods.ListFollowingForApi - - listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi - listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi - listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls -} - -export interface AccountFollowAttributes { - accountId: number - targetAccountId: number - state: FollowState -} - -export interface AccountFollowInstance extends AccountFollowClass, AccountFollowAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - AccountFollower?: AccountInstance - AccountFollowing?: AccountInstance - - toFormattedJSON: AccountFollowMethods.ToFormattedJSON -} - -export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model {} 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 @@ +import * as Bluebird from 'bluebird' import { values } from 'lodash' import * as Sequelize from 'sequelize' - -import { addMethodsToModel, getSort } from '../utils' -import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { FollowState } from '../../../shared/models/accounts' import { FOLLOW_STATES } from '../../initializers/constants' +import { ServerModel } from '../server/server' +import { getSort } from '../utils' +import { AccountModel } from './account' -let AccountFollow: Sequelize.Model -let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget -let listFollowingForApi: AccountFollowMethods.ListFollowingForApi -let listFollowersForApi: AccountFollowMethods.ListFollowersForApi -let listAcceptedFollowerUrlsForApi: AccountFollowMethods.ListAcceptedFollowerUrlsForApi -let listAcceptedFollowingUrlsForApi: AccountFollowMethods.ListAcceptedFollowingUrlsForApi -let listAcceptedFollowerSharedInboxUrls: AccountFollowMethods.ListAcceptedFollowerSharedInboxUrls -let toFormattedJSON: AccountFollowMethods.ToFormattedJSON - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - AccountFollow = sequelize.define('AccountFollow', +@Table({ + tableName: 'accountFollow', + indexes: [ { - state: { - type: DataTypes.ENUM(values(FOLLOW_STATES)), - allowNull: false - } + fields: [ 'accountId' ] }, { - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'targetAccountId' ] - }, - { - fields: [ 'accountId', 'targetAccountId' ], - unique: true - } - ] + fields: [ 'targetAccountId' ] + }, + { + fields: [ 'accountId', 'targetAccountId' ], + unique: true } - ) - - const classMethods = [ - associate, - loadByAccountAndTarget, - listFollowingForApi, - listFollowersForApi, - listAcceptedFollowerUrlsForApi, - listAcceptedFollowingUrlsForApi, - listAcceptedFollowerSharedInboxUrls ] - const instanceMethods = [ - toFormattedJSON - ] - addMethodsToModel(AccountFollow, classMethods, instanceMethods) +}) +export class AccountFollowModel extends Model { - return AccountFollow -} + @AllowNull(false) + @Column(DataType.ENUM(values(FOLLOW_STATES))) + state: FollowState -// ------------------------------ STATICS ------------------------------ + @CreatedAt + createdAt: Date -function associate (models) { - AccountFollow.belongsTo(models.Account, { + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => AccountModel) + @Column + accountId: number + + @BelongsTo(() => AccountModel, { foreignKey: { name: 'accountId', allowNull: false @@ -66,8 +47,13 @@ function associate (models) { as: 'AccountFollower', onDelete: 'CASCADE' }) + AccountFollower: AccountModel - AccountFollow.belongsTo(models.Account, { + @ForeignKey(() => AccountModel) + @Column + targetAccountId: number + + @BelongsTo(() => AccountModel, { foreignKey: { name: 'targetAccountId', allowNull: false @@ -75,170 +61,168 @@ function associate (models) { as: 'AccountFollowing', onDelete: 'CASCADE' }) -} + AccountFollowing: AccountModel -toFormattedJSON = function (this: AccountFollowInstance) { - const follower = this.AccountFollower.toFormattedJSON() - const following = this.AccountFollowing.toFormattedJSON() - - const json = { - id: this.id, - follower, - following, - state: this.state, - createdAt: this.createdAt, - updatedAt: this.updatedAt - } - - return json -} - -loadByAccountAndTarget = function (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) { - const query = { - where: { - accountId, - targetAccountId - }, - include: [ - { - model: AccountFollow[ 'sequelize' ].models.Account, - required: true, - as: 'AccountFollower' + static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) { + const query = { + where: { + accountId, + targetAccountId }, - { - model: AccountFollow['sequelize'].models.Account, - required: true, - as: 'AccountFollowing' - } - ], - transaction: t + include: [ + { + model: AccountModel, + required: true, + as: 'AccountFollower' + }, + { + model: AccountModel, + required: true, + as: 'AccountFollowing' + } + ], + transaction: t + } + + return AccountFollowModel.findOne(query) } - return AccountFollow.findOne(query) -} + static listFollowingForApi (id: number, start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: AccountModel, + required: true, + as: 'AccountFollower', + where: { + id + } + }, + { + model: AccountModel, + as: 'AccountFollowing', + required: true, + include: [ ServerModel ] + } + ] + } -listFollowingForApi = function (id: number, start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: AccountFollow[ 'sequelize' ].models.Account, - required: true, - as: 'AccountFollower', - where: { - id + return AccountFollowModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count } - }, - { - model: AccountFollow['sequelize'].models.Account, - as: 'AccountFollowing', - required: true, - include: [ AccountFollow['sequelize'].models.Server ] - } - ] + }) } - return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count + static listFollowersForApi (id: number, start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: AccountModel, + required: true, + as: 'AccountFollower', + include: [ ServerModel ] + }, + { + model: AccountModel, + as: 'AccountFollowing', + required: true, + where: { + id + } + } + ] } - }) -} -listFollowersForApi = function (id: number, start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: AccountFollow[ 'sequelize' ].models.Account, - required: true, - as: 'AccountFollower', - include: [ AccountFollow['sequelize'].models.Server ] - }, - { - model: AccountFollow['sequelize'].models.Account, - as: 'AccountFollowing', - required: true, - where: { - id + return AccountFollowModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count } - } - ] + }) } - return AccountFollow.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) -} + static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { + return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) + } -listAcceptedFollowerUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { - return createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) -} + static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) { + return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') + } -listAcceptedFollowerSharedInboxUrls = function (accountIds: number[], t: Sequelize.Transaction) { - return createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') -} + static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { + return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) + } -listAcceptedFollowingUrlsForApi = function (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { - return createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) -} + private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following', + accountIds: number[], + t: Sequelize.Transaction, + start?: number, + count?: number, + columnUrl = 'url') { + let firstJoin: string + let secondJoin: string + + if (type === 'followers') { + firstJoin = 'targetAccountId' + secondJoin = 'accountId' + } else { + firstJoin = 'accountId' + secondJoin = 'targetAccountId' + } -// ------------------------------ UTILS ------------------------------ - -async function createListAcceptedFollowForApiQuery ( - type: 'followers' | 'following', - accountIds: number[], - t: Sequelize.Transaction, - start?: number, - count?: number, - columnUrl = 'url' -) { - let firstJoin: string - let secondJoin: string - - if (type === 'followers') { - firstJoin = 'targetAccountId' - secondJoin = 'accountId' - } else { - firstJoin = 'accountId' - secondJoin = 'targetAccountId' - } + const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] + const tasks: Bluebird[] = [] - const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] - const tasks: Promise[] = [] + for (const selection of selections) { + let query = 'SELECT ' + selection + ' FROM "account" ' + + 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' + + 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' + + 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' ' - for (const selection of selections) { - let query = 'SELECT ' + selection + ' FROM "Accounts" ' + - 'INNER JOIN "AccountFollows" ON "AccountFollows"."' + firstJoin + '" = "Accounts"."id" ' + - 'INNER JOIN "Accounts" AS "Follows" ON "AccountFollows"."' + secondJoin + '" = "Follows"."id" ' + - 'WHERE "Accounts"."id" = ANY ($accountIds) AND "AccountFollows"."state" = \'accepted\' ' + if (count !== undefined) query += 'LIMIT ' + count + if (start !== undefined) query += ' OFFSET ' + start - if (count !== undefined) query += 'LIMIT ' + count - if (start !== undefined) query += ' OFFSET ' + start + const options = { + bind: { accountIds }, + type: Sequelize.QueryTypes.SELECT, + transaction: t + } + tasks.push(AccountFollowModel.sequelize.query(query, options)) + } - const options = { - bind: { accountIds }, - type: Sequelize.QueryTypes.SELECT, - transaction: t + const [ followers, [ { total } ] ] = await + Promise.all(tasks) + const urls: string[] = followers.map(f => f.url) + + return { + data: urls, + total: parseInt(total, 10) } - tasks.push(AccountFollow['sequelize'].query(query, options)) } - const [ followers, [ { total } ]] = await Promise.all(tasks) - const urls: string[] = followers.map(f => f.url) + toFormattedJSON () { + const follower = this.AccountFollower.toFormattedJSON() + const following = this.AccountFollowing.toFormattedJSON() - return { - data: urls, - total: parseInt(total, 10) + return { + id: this.id, + follower, + following, + state: this.state, + createdAt: this.createdAt, + updatedAt: this.updatedAt + } } } 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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { Account as FormattedAccount, ActivityPubActor } from '../../../shared' -import { AvatarInstance } from '../avatar' -import { ServerInstance } from '../server/server-interface' -import { VideoChannelInstance } from '../video/video-channel-interface' - -export namespace AccountMethods { - export type LoadApplication = () => Bluebird - - export type Load = (id: number) => Bluebird - export type LoadByUUID = (uuid: string) => Bluebird - export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird - export type LoadLocalByName = (name: string) => Bluebird - export type LoadByNameAndHost = (name: string, host: string) => Bluebird - export type ListByFollowersUrls = (followerUrls: string[], transaction: Sequelize.Transaction) => Bluebird - - export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor - export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount - export type IsOwned = (this: AccountInstance) => boolean - export type GetFollowerSharedInboxUrls = (this: AccountInstance, t: Sequelize.Transaction) => Bluebird - export type GetFollowingUrl = (this: AccountInstance) => string - export type GetFollowersUrl = (this: AccountInstance) => string - export type GetPublicKeyUrl = (this: AccountInstance) => string -} - -export interface AccountClass { - loadApplication: AccountMethods.LoadApplication - load: AccountMethods.Load - loadByUUID: AccountMethods.LoadByUUID - loadByUrl: AccountMethods.LoadByUrl - loadLocalByName: AccountMethods.LoadLocalByName - loadByNameAndHost: AccountMethods.LoadByNameAndHost - listByFollowersUrls: AccountMethods.ListByFollowersUrls -} - -export interface AccountAttributes { - name: string - url?: string - publicKey: string - privateKey: string - followersCount: number - followingCount: number - inboxUrl: string - outboxUrl: string - sharedInboxUrl: string - followersUrl: string - followingUrl: string - - uuid?: string - - serverId?: number - userId?: number - applicationId?: number - avatarId?: number -} - -export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance { - isOwned: AccountMethods.IsOwned - toActivityPubObject: AccountMethods.ToActivityPubObject - toFormattedJSON: AccountMethods.ToFormattedJSON - getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls - getFollowingUrl: AccountMethods.GetFollowingUrl - getFollowersUrl: AccountMethods.GetFollowersUrl - getPublicKeyUrl: AccountMethods.GetPublicKeyUrl - - id: number - createdAt: Date - updatedAt: Date - - Server: ServerInstance - VideoChannels: VideoChannelInstance[] - Avatar: AvatarInstance -} - -export interface AccountModel extends AccountClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -import { VideoRateType } from '../../../shared/models/videos/video-rate.type' -import { AccountInstance } from './account-interface' - -export namespace AccountVideoRateMethods { - export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise -} - -export interface AccountVideoRateClass { - load: AccountVideoRateMethods.Load -} - -export interface AccountVideoRateAttributes { - type: VideoRateType - accountId: number - videoId: number - - Account?: AccountInstance -} - -export interface AccountVideoRateInstance - extends AccountVideoRateClass, AccountVideoRateAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface AccountVideoRateModel - extends AccountVideoRateClass, Sequelize.Model {} 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 @@ -/* - Account rates per video. -*/ import { values } from 'lodash' -import * as Sequelize from 'sequelize' - +import { Transaction } from 'sequelize' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' +import { VideoRateType } from '../../../shared/models/videos' import { VIDEO_RATE_TYPES } from '../../initializers' +import { VideoModel } from '../video/video' +import { AccountModel } from './account' -import { addMethodsToModel } from '../utils' -import { - AccountVideoRateInstance, - AccountVideoRateAttributes, - - AccountVideoRateMethods -} from './account-video-rate-interface' - -let AccountVideoRate: Sequelize.Model -let load: AccountVideoRateMethods.Load - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - AccountVideoRate = sequelize.define('AccountVideoRate', - { - type: { - type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)), - allowNull: false - } - }, +/* + Account rates per video. +*/ +@Table({ + tableName: 'accountVideoRate', + indexes: [ { - indexes: [ - { - fields: [ 'videoId', 'accountId' ], - unique: true - } - ] + fields: [ 'videoId', 'accountId' ], + unique: true } - ) + ] +}) +export class AccountVideoRateModel extends Model { - const classMethods = [ - associate, + @AllowNull(false) + @Column(DataType.ENUM(values(VIDEO_RATE_TYPES))) + type: VideoRateType - load - ] - addMethodsToModel(AccountVideoRate, classMethods) + @CreatedAt + createdAt: Date - return AccountVideoRate -} + @UpdatedAt + updatedAt: Date -// ------------------------------ STATICS ------------------------------ + @ForeignKey(() => VideoModel) + @Column + videoId: number -function associate (models) { - AccountVideoRate.belongsTo(models.Video, { + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', allowNull: false }, onDelete: 'CASCADE' }) + Video: VideoModel - AccountVideoRate.belongsTo(models.Account, { + @ForeignKey(() => AccountModel) + @Column + accountId: number + + @BelongsTo(() => AccountModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'CASCADE' }) -} + Account: AccountModel -load = function (accountId: number, videoId: number, transaction: Sequelize.Transaction) { - const options: Sequelize.FindOptions = { - where: { - accountId, - videoId + static load (accountId: number, videoId: number, transaction: Transaction) { + const options: IFindOptions = { + where: { + accountId, + videoId + } } - } - if (transaction) options.transaction = transaction + if (transaction) options.transaction = transaction - return AccountVideoRate.findOne(options) + return AccountVideoRateModel.findOne(options) + } } 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 @@ import { join } from 'path' import * as Sequelize from 'sequelize' +import { + AfterDestroy, + AllowNull, + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + Is, + IsUUID, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' import { Avatar } from '../../../shared/models/avatars/avatar.model' +import { activityPubContextify } from '../../helpers' import { - activityPubContextify, isAccountFollowersCountValid, isAccountFollowingCountValid, isAccountPrivateKeyValid, isAccountPublicKeyValid, - isUserUsernameValid -} from '../../helpers' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { AVATARS_DIR } from '../../initializers' -import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' -import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' -import { addMethodsToModel } from '../utils' -import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' - -let Account: Sequelize.Model -let load: AccountMethods.Load -let loadApplication: AccountMethods.LoadApplication -let loadByUUID: AccountMethods.LoadByUUID -let loadByUrl: AccountMethods.LoadByUrl -let loadLocalByName: AccountMethods.LoadLocalByName -let loadByNameAndHost: AccountMethods.LoadByNameAndHost -let listByFollowersUrls: AccountMethods.ListByFollowersUrls -let isOwned: AccountMethods.IsOwned -let toActivityPubObject: AccountMethods.ToActivityPubObject -let toFormattedJSON: AccountMethods.ToFormattedJSON -let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls -let getFollowingUrl: AccountMethods.GetFollowingUrl -let getFollowersUrl: AccountMethods.GetFollowersUrl -let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl - -export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Account = sequelize.define('Account', + isActivityPubUrlValid +} from '../../helpers/custom-validators/activitypub' +import { isUserUsernameValid } from '../../helpers/custom-validators/users' +import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' +import { sendDeleteAccount } from '../../lib/activitypub/send' +import { ApplicationModel } from '../application/application' +import { AvatarModel } from '../avatar/avatar' +import { ServerModel } from '../server/server' +import { throwIfNotValid } from '../utils' +import { VideoChannelModel } from '../video/video-channel' +import { AccountFollowModel } from './account-follow' +import { UserModel } from './user' + +@Table({ + tableName: 'account', + indexes: [ { - uuid: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - allowNull: false, - validate: { - isUUID: 4 - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - nameValid: value => { - const res = isUserUsernameValid(value) - if (res === false) throw new Error('Name is not valid.') - } - } - }, - url: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - urlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('URL is not valid.') - } - } - }, - publicKey: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max), - allowNull: true, - validate: { - publicKeyValid: value => { - const res = isAccountPublicKeyValid(value) - if (res === false) throw new Error('Public key is not valid.') - } - } - }, - privateKey: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max), - allowNull: true, - validate: { - privateKeyValid: value => { - const res = isAccountPrivateKeyValid(value) - if (res === false) throw new Error('Private key is not valid.') - } - } - }, - followersCount: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - followersCountValid: value => { - const res = isAccountFollowersCountValid(value) - if (res === false) throw new Error('Followers count is not valid.') - } - } - }, - followingCount: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - followingCountValid: value => { - const res = isAccountFollowingCountValid(value) - if (res === false) throw new Error('Following count is not valid.') - } - } - }, - inboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - inboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Inbox URL is not valid.') - } - } - }, - outboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - outboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Outbox URL is not valid.') - } - } - }, - sharedInboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - sharedInboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Shared inbox URL is not valid.') - } - } - }, - followersUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - followersUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Followers URL is not valid.') - } - } - }, - followingUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - followingUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Following URL is not valid.') - } - } - } + fields: [ 'name' ] }, { - indexes: [ - { - fields: [ 'name' ] - }, - { - fields: [ 'serverId' ] - }, - { - fields: [ 'userId' ], - unique: true - }, - { - fields: [ 'applicationId' ], - unique: true - }, - { - fields: [ 'name', 'serverId', 'applicationId' ], - unique: true - } - ], - hooks: { afterDestroy } + fields: [ 'serverId' ] + }, + { + fields: [ 'userId' ], + unique: true + }, + { + fields: [ 'applicationId' ], + unique: true + }, + { + fields: [ 'name', 'serverId', 'applicationId' ], + unique: true } - ) - - const classMethods = [ - associate, - loadApplication, - load, - loadByUUID, - loadByUrl, - loadLocalByName, - loadByNameAndHost, - listByFollowersUrls - ] - const instanceMethods = [ - isOwned, - toActivityPubObject, - toFormattedJSON, - getFollowerSharedInboxUrls, - getFollowingUrl, - getFollowersUrl, - getPublicKeyUrl ] - addMethodsToModel(Account, classMethods, instanceMethods) - - return Account -} +}) +export class AccountModel extends Model { + + @AllowNull(false) + @Default(DataType.UUIDV4) + @IsUUID(4) + @Column(DataType.UUID) + uuid: string + + @AllowNull(false) + @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name')) + @Column + name: string + + @AllowNull(false) + @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + url: string + + @AllowNull(true) + @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max)) + publicKey: string + + @AllowNull(true) + @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max)) + privateKey: string + + @AllowNull(false) + @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count')) + @Column + followersCount: number + + @AllowNull(false) + @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count')) + @Column + followingCount: number + + @AllowNull(false) + @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + inboxUrl: string + + @AllowNull(false) + @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + outboxUrl: string + + @AllowNull(false) + @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + sharedInboxUrl: string + + @AllowNull(false) + @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + followersUrl: string + + @AllowNull(false) + @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) + followingUrl: string + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => AvatarModel) + @Column + avatarId: number + + @BelongsTo(() => AvatarModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + Avatar: AvatarModel -// --------------------------------------------------------------------------- + @ForeignKey(() => ServerModel) + @Column + serverId: number -function associate (models) { - Account.belongsTo(models.Server, { + @BelongsTo(() => ServerModel, { foreignKey: { - name: 'serverId', allowNull: true }, onDelete: 'cascade' }) + Server: ServerModel - Account.belongsTo(models.User, { + @ForeignKey(() => UserModel) + @Column + userId: number + + @BelongsTo(() => UserModel, { foreignKey: { - name: 'userId', allowNull: true }, onDelete: 'cascade' }) + User: UserModel + + @ForeignKey(() => ApplicationModel) + @Column + applicationId: number - Account.belongsTo(models.Application, { + @BelongsTo(() => ApplicationModel, { foreignKey: { - name: 'applicationId', allowNull: true }, onDelete: 'cascade' }) + Application: ApplicationModel - Account.hasMany(models.VideoChannel, { + @HasMany(() => VideoChannelModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'cascade', hooks: true }) + VideoChannels: VideoChannelModel[] - Account.hasMany(models.AccountFollow, { + @HasMany(() => AccountFollowModel, { foreignKey: { name: 'accountId', allowNull: false }, onDelete: 'cascade' }) + AccountFollowing: AccountFollowModel[] - Account.hasMany(models.AccountFollow, { + @HasMany(() => AccountFollowModel, { foreignKey: { name: 'targetAccountId', allowNull: false @@ -255,209 +202,199 @@ function associate (models) { as: 'followers', onDelete: 'cascade' }) + AccountFollowers: AccountFollowModel[] - Account.hasOne(models.Avatar, { - foreignKey: { - name: 'avatarId', - allowNull: true - }, - onDelete: 'cascade' - }) -} + @AfterDestroy + static sendDeleteIfOwned (instance: AccountModel) { + if (instance.isOwned()) { + return sendDeleteAccount(instance, undefined) + } -function afterDestroy (account: AccountInstance) { - if (account.isOwned()) { - return sendDeleteAccount(account, undefined) + return undefined } - return undefined -} + static loadApplication () { + return AccountModel.findOne({ + include: [ + { + model: ApplicationModel, + required: true + } + ] + }) + } -toFormattedJSON = function (this: AccountInstance) { - let host = CONFIG.WEBSERVER.HOST - let score: number - let avatar: Avatar = null + static load (id: number) { + return AccountModel.findById(id) + } - if (this.Avatar) { - avatar = { - path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), - createdAt: this.Avatar.createdAt, - updatedAt: this.Avatar.updatedAt + static loadByUUID (uuid: string) { + const query = { + where: { + uuid + } } - } - if (this.Server) { - host = this.Server.host - score = this.Server.score as number + return AccountModel.findOne(query) } - const json = { - id: this.id, - uuid: this.uuid, - host, - score, - name: this.name, - followingCount: this.followingCount, - followersCount: this.followersCount, - createdAt: this.createdAt, - updatedAt: this.updatedAt, - avatar - } + static loadLocalByName (name: string) { + const query = { + where: { + name, + [ Sequelize.Op.or ]: [ + { + userId: { + [ Sequelize.Op.ne ]: null + } + }, + { + applicationId: { + [ Sequelize.Op.ne ]: null + } + } + ] + } + } - return json -} + return AccountModel.findOne(query) + } -toActivityPubObject = function (this: AccountInstance) { - const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' - - const json = { - type, - id: this.url, - following: this.getFollowingUrl(), - followers: this.getFollowersUrl(), - inbox: this.inboxUrl, - outbox: this.outboxUrl, - preferredUsername: this.name, - url: this.url, - name: this.name, - endpoints: { - sharedInbox: this.sharedInboxUrl - }, - uuid: this.uuid, - publicKey: { - id: this.getPublicKeyUrl(), - owner: this.url, - publicKeyPem: this.publicKey + static loadByNameAndHost (name: string, host: string) { + const query = { + where: { + name + }, + include: [ + { + model: ServerModel, + required: true, + where: { + host + } + } + ] } + + return AccountModel.findOne(query) } - return activityPubContextify(json) -} + static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + const query = { + where: { + url + }, + transaction + } -isOwned = function (this: AccountInstance) { - return this.serverId === null -} + return AccountModel.findOne(query) + } -getFollowerSharedInboxUrls = function (this: AccountInstance, t: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - attributes: [ 'sharedInboxUrl' ], - include: [ - { - model: Account['sequelize'].models.AccountFollow, - required: true, - as: 'followers', - where: { - targetAccountId: this.id + static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { + const query = { + where: { + followersUrl: { + [ Sequelize.Op.in ]: followersUrls } - } - ], - transaction: t - } + }, + transaction + } - return Account.findAll(query) - .then(accounts => accounts.map(a => a.sharedInboxUrl)) -} + return AccountModel.findAll(query) + } -getFollowingUrl = function (this: AccountInstance) { - return this.url + '/following' -} + toFormattedJSON () { + let host = CONFIG.WEBSERVER.HOST + let score: number + let avatar: Avatar = null -getFollowersUrl = function (this: AccountInstance) { - return this.url + '/followers' -} + if (this.Avatar) { + avatar = { + path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), + createdAt: this.Avatar.createdAt, + updatedAt: this.Avatar.updatedAt + } + } -getPublicKeyUrl = function (this: AccountInstance) { - return this.url + '#main-key' -} + if (this.Server) { + host = this.Server.host + score = this.Server.score + } -// ------------------------------ STATICS ------------------------------ + return { + id: this.id, + uuid: this.uuid, + host, + score, + name: this.name, + followingCount: this.followingCount, + followersCount: this.followersCount, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + avatar + } + } -loadApplication = function () { - return Account.findOne({ - include: [ - { - model: Account['sequelize'].models.Application, - required: true + toActivityPubObject () { + const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' + + const json = { + type, + id: this.url, + following: this.getFollowingUrl(), + followers: this.getFollowersUrl(), + inbox: this.inboxUrl, + outbox: this.outboxUrl, + preferredUsername: this.name, + url: this.url, + name: this.name, + endpoints: { + sharedInbox: this.sharedInboxUrl + }, + uuid: this.uuid, + publicKey: { + id: this.getPublicKeyUrl(), + owner: this.url, + publicKeyPem: this.publicKey } - ] - }) -} - -load = function (id: number) { - return Account.findById(id) -} - -loadByUUID = function (uuid: string) { - const query: Sequelize.FindOptions = { - where: { - uuid } + + return activityPubContextify(json) } - return Account.findOne(query) -} + isOwned () { + return this.serverId === null + } -loadLocalByName = function (name: string) { - const query: Sequelize.FindOptions = { - where: { - name, - [Sequelize.Op.or]: [ - { - userId: { - [Sequelize.Op.ne]: null - } - }, + getFollowerSharedInboxUrls (t: Sequelize.Transaction) { + const query = { + attributes: [ 'sharedInboxUrl' ], + include: [ { - applicationId: { - [Sequelize.Op.ne]: null + model: AccountFollowModel, + required: true, + as: 'followers', + where: { + targetAccountId: this.id } } - ] + ], + transaction: t } - } - return Account.findOne(query) -} - -loadByNameAndHost = function (name: string, host: string) { - const query: Sequelize.FindOptions = { - where: { - name - }, - include: [ - { - model: Account['sequelize'].models.Server, - required: true, - where: { - host - } - } - ] + return AccountModel.findAll(query) + .then(accounts => accounts.map(a => a.sharedInboxUrl)) } - return Account.findOne(query) -} - -loadByUrl = function (url: string, transaction?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - url - }, - transaction + getFollowingUrl () { + return this.url + '/following' } - return Account.findOne(query) -} - -listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - followersUrl: { - [Sequelize.Op.in]: followersUrls - } - }, - transaction + getFollowersUrl () { + return this.url + '/followers' } - return Account.findAll(query) + getPublicKeyUrl () { + return this.url + '#main-key' + } } 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 @@ -export * from './account-interface' -export * from './account-follow-interface' -export * from './account-video-rate-interface' -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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { ResultList } from '../../../shared/models/result-list.model' -import { UserRight } from '../../../shared/models/users/user-right.enum' -import { UserRole } from '../../../shared/models/users/user-role' -import { User as FormattedUser } from '../../../shared/models/users/user.model' -import { AccountInstance } from './account-interface' - -export namespace UserMethods { - export type HasRight = (this: UserInstance, right: UserRight) => boolean - export type IsPasswordMatch = (this: UserInstance, password: string) => Promise - - export type ToFormattedJSON = (this: UserInstance) => FormattedUser - export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise - - export type CountTotal = () => Bluebird - - export type GetByUsername = (username: string) => Bluebird - - export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList > - - export type LoadById = (id: number) => Bluebird - - export type LoadByUsername = (username: string) => Bluebird - export type LoadByUsernameAndPopulateChannels = (username: string) => Bluebird - - export type LoadByUsernameOrEmail = (username: string, email: string) => Bluebird -} - -export interface UserClass { - isPasswordMatch: UserMethods.IsPasswordMatch, - toFormattedJSON: UserMethods.ToFormattedJSON, - hasRight: UserMethods.HasRight, - isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo, - - countTotal: UserMethods.CountTotal, - getByUsername: UserMethods.GetByUsername, - listForApi: UserMethods.ListForApi, - loadById: UserMethods.LoadById, - loadByUsername: UserMethods.LoadByUsername, - loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels, - loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail -} - -export interface UserAttributes { - id?: number - password: string - username: string - email: string - displayNSFW?: boolean - role: UserRole - videoQuota: number - - Account?: AccountInstance -} - -export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - isPasswordMatch: UserMethods.IsPasswordMatch - toFormattedJSON: UserMethods.ToFormattedJSON - hasRight: UserMethods.HasRight -} - -export interface UserModel extends UserClass, Sequelize.Model {} 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 @@ import * as Sequelize from 'sequelize' +import { + AllowNull, + BeforeCreate, + BeforeUpdate, + Column, CreatedAt, + DataType, + Default, + HasMany, + HasOne, + Is, + IsEmail, + Model, + Table, UpdatedAt +} from 'sequelize-typescript' import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' import { comparePassword, - cryptPassword, - isUserDisplayNSFWValid, - isUserPasswordValid, - isUserRoleValid, - isUserUsernameValid, - isUserVideoQuotaValid + cryptPassword } from '../../helpers' -import { addMethodsToModel, getSort } from '../utils' -import { UserAttributes, UserInstance, UserMethods } from './user-interface' - -let User: Sequelize.Model -let isPasswordMatch: UserMethods.IsPasswordMatch -let hasRight: UserMethods.HasRight -let toFormattedJSON: UserMethods.ToFormattedJSON -let countTotal: UserMethods.CountTotal -let getByUsername: UserMethods.GetByUsername -let listForApi: UserMethods.ListForApi -let loadById: UserMethods.LoadById -let loadByUsername: UserMethods.LoadByUsername -let loadByUsernameAndPopulateChannels: UserMethods.LoadByUsernameAndPopulateChannels -let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail -let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - User = sequelize.define('User', +import { + isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, + isUserVideoQuotaValid +} from '../../helpers/custom-validators/users' +import { OAuthTokenModel } from '../oauth/oauth-token' +import { getSort, throwIfNotValid } from '../utils' +import { VideoChannelModel } from '../video/video-channel' +import { AccountModel } from './account' + +@Table({ + tableName: 'user', + indexes: [ { - password: { - type: DataTypes.STRING, - allowNull: false, - validate: { - passwordValid: value => { - const res = isUserPasswordValid(value) - if (res === false) throw new Error('Password not valid.') - } - } - }, - username: { - type: DataTypes.STRING, - allowNull: false, - validate: { - usernameValid: value => { - const res = isUserUsernameValid(value) - if (res === false) throw new Error('Username not valid.') - } - } - }, - email: { - type: DataTypes.STRING(400), - allowNull: false, - validate: { - isEmail: true - } - }, - displayNSFW: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - validate: { - nsfwValid: value => { - const res = isUserDisplayNSFWValid(value) - if (res === false) throw new Error('Display NSFW is not valid.') - } - } - }, - role: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - roleValid: value => { - const res = isUserRoleValid(value) - if (res === false) throw new Error('Role is not valid.') - } - } - }, - videoQuota: { - type: DataTypes.BIGINT, - allowNull: false, - validate: { - videoQuotaValid: value => { - const res = isUserVideoQuotaValid(value) - if (res === false) throw new Error('Video quota is not valid.') - } - } - } + fields: [ 'username' ], + unique: true }, { - indexes: [ - { - fields: [ 'username' ], - unique: true - }, - { - fields: [ 'email' ], - unique: true - } - ], - hooks: { - beforeCreate: beforeCreateOrUpdate, - beforeUpdate: beforeCreateOrUpdate - } + fields: [ 'email' ], + unique: true } - ) - - const classMethods = [ - associate, - - countTotal, - getByUsername, - listForApi, - loadById, - loadByUsername, - loadByUsernameAndPopulateChannels, - loadByUsernameOrEmail ] - const instanceMethods = [ - hasRight, - isPasswordMatch, - toFormattedJSON, - isAbleToUploadVideo - ] - addMethodsToModel(User, classMethods, instanceMethods) - - return User -} +}) +export class UserModel extends Model { + + @AllowNull(false) + @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password')) + @Column + password: string + + @AllowNull(false) + @Is('UserPassword', value => throwIfNotValid(value, isUserUsernameValid, 'user name')) + @Column + username: string + + @AllowNull(false) + @IsEmail + @Column(DataType.STRING(400)) + email: string + + @AllowNull(false) + @Default(false) + @Is('UserDisplayNSFW', value => throwIfNotValid(value, isUserDisplayNSFWValid, 'display NSFW boolean')) + @Column + displayNSFW: boolean + + @AllowNull(false) + @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role')) + @Column + role: number + + @AllowNull(false) + @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota')) + @Column(DataType.BIGINT) + videoQuota: number + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @HasOne(() => AccountModel, { + foreignKey: 'userId', + onDelete: 'cascade' + }) + Account: AccountModel -function beforeCreateOrUpdate (user: UserInstance) { - if (user.changed('password')) { - return cryptPassword(user.password) - .then(hash => { - user.password = hash - return undefined - }) + @HasMany(() => OAuthTokenModel, { + foreignKey: 'userId', + onDelete: 'cascade' + }) + OAuthTokens: OAuthTokenModel[] + + @BeforeCreate + @BeforeUpdate + static cryptPasswordIfNeeded (instance: UserModel) { + if (instance.changed('password')) { + return cryptPassword(instance.password) + .then(hash => { + instance.password = hash + return undefined + }) + } } -} - -// ------------------------------ METHODS ------------------------------ -hasRight = function (this: UserInstance, right: UserRight) { - return hasUserRight(this.role, right) -} + static countTotal () { + return this.count() + } -isPasswordMatch = function (this: UserInstance, password: string) { - return comparePassword(password, this.password) -} + static getByUsername (username: string) { + const query = { + where: { + username: username + }, + include: [ { model: AccountModel, required: true } ] + } -toFormattedJSON = function (this: UserInstance) { - const json = { - id: this.id, - username: this.username, - email: this.email, - displayNSFW: this.displayNSFW, - role: this.role, - roleLabel: USER_ROLE_LABELS[this.role], - videoQuota: this.videoQuota, - createdAt: this.createdAt, - account: this.Account.toFormattedJSON() + return UserModel.findOne(query) } - if (Array.isArray(this.Account.VideoChannels) === true) { - const videoChannels = this.Account.VideoChannels - .map(c => c.toFormattedJSON()) - .sort((v1, v2) => { - if (v1.createdAt < v2.createdAt) return -1 - if (v1.createdAt === v2.createdAt) return 0 + static listForApi (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ { model: AccountModel, required: true } ] + } - return 1 + return UserModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count + } }) - - json['videoChannels'] = videoChannels } - return json -} - -isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) { - if (this.videoQuota === -1) return Promise.resolve(true) - - return getOriginalVideoFileTotalFromUser(this).then(totalBytes => { - return (videoFile.size + totalBytes) < this.videoQuota - }) -} - -// ------------------------------ STATICS ------------------------------ - -function associate (models) { - User.hasOne(models.Account, { - foreignKey: 'userId', - onDelete: 'cascade' - }) + static loadById (id: number) { + const options = { + include: [ { model: AccountModel, required: true } ] + } - User.hasMany(models.OAuthToken, { - foreignKey: 'userId', - onDelete: 'cascade' - }) -} + return UserModel.findById(id, options) + } -countTotal = function () { - return this.count() -} + static loadByUsername (username: string) { + const query = { + where: { + username + }, + include: [ { model: AccountModel, required: true } ] + } -getByUsername = function (username: string) { - const query = { - where: { - username: username - }, - include: [ { model: User['sequelize'].models.Account, required: true } ] + return UserModel.findOne(query) } - return User.findOne(query) -} + static loadByUsernameAndPopulateChannels (username: string) { + const query = { + where: { + username + }, + include: [ + { + model: AccountModel, + required: true, + include: [ VideoChannelModel ] + } + ] + } -listForApi = function (start: number, count: number, sort: string) { - const query = { - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ { model: User['sequelize'].models.Account, required: true } ] + return UserModel.findOne(query) } - return User.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count + static loadByUsernameOrEmail (username: string, email: string) { + const query = { + include: [ { model: AccountModel, required: true } ], + where: { + [ Sequelize.Op.or ]: [ { username }, { email } ] + } } - }) -} -loadById = function (id: number) { - const options = { - include: [ { model: User['sequelize'].models.Account, required: true } ] + // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 + return (UserModel as any).findOne(query) } - return User.findById(id, options) -} + private static getOriginalVideoFileTotalFromUser (user: UserModel) { + // Don't use sequelize because we need to use a sub query + const query = 'SELECT SUM("size") AS "total" FROM ' + + '(SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + + 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + + 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + + 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + + 'INNER JOIN "user" ON "account"."userId" = "user"."id" ' + + 'WHERE "user"."id" = $userId GROUP BY "video"."id") t' + + const options = { + bind: { userId: user.id }, + type: Sequelize.QueryTypes.SELECT + } + return UserModel.sequelize.query(query, options) + .then(([ { total } ]) => { + if (total === null) return 0 -loadByUsername = function (username: string) { - const query = { - where: { - username - }, - include: [ { model: User['sequelize'].models.Account, required: true } ] + return parseInt(total, 10) + }) } - return User.findOne(query) -} + hasRight (right: UserRight) { + return hasUserRight(this.role, right) + } -loadByUsernameAndPopulateChannels = function (username: string) { - const query = { - where: { - username - }, - include: [ - { - model: User['sequelize'].models.Account, - required: true, - include: [ User['sequelize'].models.VideoChannel ] - } - ] + isPasswordMatch (password: string) { + return comparePassword(password, this.password) } - return User.findOne(query) -} + toFormattedJSON () { + const json = { + id: this.id, + username: this.username, + email: this.email, + displayNSFW: this.displayNSFW, + role: this.role, + roleLabel: USER_ROLE_LABELS[ this.role ], + videoQuota: this.videoQuota, + createdAt: this.createdAt, + account: this.Account.toFormattedJSON() + } + + if (Array.isArray(this.Account.VideoChannels) === true) { + json['videoChannels'] = this.Account.VideoChannels + .map(c => c.toFormattedJSON()) + .sort((v1, v2) => { + if (v1.createdAt < v2.createdAt) return -1 + if (v1.createdAt === v2.createdAt) return 0 -loadByUsernameOrEmail = function (username: string, email: string) { - const query = { - include: [ { model: User['sequelize'].models.Account, required: true } ], - where: { - [Sequelize.Op.or]: [ { username }, { email } ] + return 1 + }) } + + return json } - // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 - return (User as any).findOne(query) -} + isAbleToUploadVideo (videoFile: Express.Multer.File) { + if (this.videoQuota === -1) return Promise.resolve(true) -// --------------------------------------------------------------------------- - -function getOriginalVideoFileTotalFromUser (user: UserInstance) { - // Don't use sequelize because we need to use a sub query - const query = 'SELECT SUM("size") AS "total" FROM ' + - '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' + - 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' + - 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' + - 'INNER JOIN "Accounts" ON "VideoChannels"."accountId" = "Accounts"."id" ' + - 'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' + - 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t' - - const options = { - bind: { userId: user.id }, - type: Sequelize.QueryTypes.SELECT + return UserModel.getOriginalVideoFileTotalFromUser(this) + .then(totalBytes => { + return (videoFile.size + totalBytes) < this.videoQuota + }) } - return User['sequelize'].query(query, options).then(([ { total } ]) => { - if (total === null) return 0 - - return parseInt(total, 10) - }) } 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 @@ -import * as Sequelize from 'sequelize' -import * as Bluebird from 'bluebird' - -export namespace ApplicationMethods { - export type LoadMigrationVersion = () => Bluebird - - export type UpdateMigrationVersion = ( - newVersion: number, - transaction: Sequelize.Transaction - ) => Bluebird<[ number, ApplicationInstance[] ]> - - export type CountTotal = () => Bluebird -} - -export interface ApplicationClass { - loadMigrationVersion: ApplicationMethods.LoadMigrationVersion - updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion - countTotal: ApplicationMethods.CountTotal -} - -export interface ApplicationAttributes { - migrationVersion: number -} - -export interface ApplicationInstance extends ApplicationClass, ApplicationAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface ApplicationModel extends ApplicationClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' - -import { addMethodsToModel } from '../utils' -import { - ApplicationAttributes, - ApplicationInstance, - - ApplicationMethods -} from './application-interface' - -let Application: Sequelize.Model -let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion -let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion -let countTotal: ApplicationMethods.CountTotal +import { Transaction } from 'sequelize' +import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' + +@Table({ + tableName: 'application' +}) +export class ApplicationModel extends Model { + + @AllowNull(false) + @Default(0) + @IsInt + @Column + migrationVersion: number + + static countTotal () { + return ApplicationModel.count() + } -export default function defineApplication (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Application = sequelize.define('Application', - { - migrationVersion: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - validate: { - isInt: true - } - } + static loadMigrationVersion () { + const query = { + attributes: [ 'migrationVersion' ] } - ) - - const classMethods = [ - countTotal, - loadMigrationVersion, - updateMigrationVersion - ] - addMethodsToModel(Application, classMethods) - - return Application -} -// --------------------------------------------------------------------------- - -countTotal = function () { - return this.count() -} - -loadMigrationVersion = function () { - const query = { - attributes: [ 'migrationVersion' ] + return ApplicationModel.findOne(query).then(data => data ? data.migrationVersion : null) } - return Application.findOne(query).then(data => data ? data.migrationVersion : null) -} + static updateMigrationVersion (newVersion: number, transaction: Transaction) { + const options = { + where: {}, + transaction: transaction + } -updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction) { - const options: Sequelize.UpdateOptions = { - where: {}, - transaction: transaction + return ApplicationModel.update({ migrationVersion: newVersion }, options) } - - return Application.update({ migrationVersion: newVersion }, options) } 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 @@ -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 @@ -import * as Sequelize from 'sequelize' - -export namespace AvatarMethods {} - -export interface AvatarClass {} - -export interface AvatarAttributes { - filename: string -} - -export interface AvatarInstance extends AvatarClass, AvatarAttributes, Sequelize.Instance { - createdAt: Date - updatedAt: Date -} - -export interface AvatarModel extends AvatarClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' -import { addMethodsToModel } from '../utils' -import { AvatarAttributes, AvatarInstance } from './avatar-interface' +import { AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' -let Avatar: Sequelize.Model +@Table({ + tableName: 'avatar' +}) +export class AvatarModel extends Model { -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Avatar = sequelize.define('Avatar', - { - filename: { - type: DataTypes.STRING, - allowNull: false - } - }, - {} - ) + @AllowNull(false) + @Column + filename: string - const classMethods = [] - addMethodsToModel(Avatar, classMethods) + @CreatedAt + createdAt: Date - return Avatar + @UpdatedAt + updatedAt: Date } - -// ------------------------------ 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 @@ -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 @@ -export * from './application' -export * from './avatar' -export * from './job' -export * from './oauth' -export * from './server' -export * from './account' -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 @@ -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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { Job as FormattedJob, JobCategory, JobState } from '../../../shared/models/job.model' -import { ResultList } from '../../../shared/models/result-list.model' - -export namespace JobMethods { - export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Bluebird - export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList > - - export type ToFormattedJSON = (this: JobInstance) => FormattedJob -} - -export interface JobClass { - listWithLimitByCategory: JobMethods.ListWithLimitByCategory - listForApi: JobMethods.ListForApi, -} - -export interface JobAttributes { - state: JobState - category: JobCategory - handlerName: string - handlerInputData: any -} - -export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - toFormattedJSON: JobMethods.ToFormattedJSON -} - -export interface JobModel extends JobClass, Sequelize.Model {} 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 @@ import { values } from 'lodash' -import * as Sequelize from 'sequelize' -import { JobCategory, JobState } from '../../../shared/models/job.model' +import { AllowNull, Column, CreatedAt, DataType, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { JobCategory, JobState } from '../../../shared/models' import { JOB_CATEGORIES, JOB_STATES } from '../../initializers' -import { addMethodsToModel, getSort } from '../utils' -import { JobAttributes, JobInstance, JobMethods } from './job-interface' +import { getSort } from '../utils' -let Job: Sequelize.Model -let listWithLimitByCategory: JobMethods.ListWithLimitByCategory -let listForApi: JobMethods.ListForApi -let toFormattedJSON: JobMethods.ToFormattedJSON - -export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Job = sequelize.define('Job', - { - state: { - type: DataTypes.ENUM(values(JOB_STATES)), - allowNull: false - }, - category: { - type: DataTypes.ENUM(values(JOB_CATEGORIES)), - allowNull: false - }, - handlerName: { - type: DataTypes.STRING, - allowNull: false - }, - handlerInputData: { - type: DataTypes.JSON, - allowNull: true - } - }, +@Table({ + tableName: 'job', + indexes: [ { - indexes: [ - { - fields: [ 'state', 'category' ] - } - ] + fields: [ 'state', 'category' ] } - ) - - const classMethods = [ - listWithLimitByCategory, - listForApi ] - const instanceMethods = [ - toFormattedJSON - ] - addMethodsToModel(Job, classMethods, instanceMethods) +}) +export class JobModel extends Model { + @AllowNull(false) + @Column(DataType.ENUM(values(JOB_STATES))) + state: JobState - return Job -} + @AllowNull(false) + @Column(DataType.ENUM(values(JOB_CATEGORIES))) + category: JobCategory -toFormattedJSON = function (this: JobInstance) { - return { - id: this.id, - state: this.state, - category: this.category, - handlerName: this.handlerName, - handlerInputData: this.handlerInputData, - createdAt: this.createdAt, - updatedAt: this.updatedAt - } -} + @AllowNull(false) + @Column + handlerName: string -// --------------------------------------------------------------------------- + @AllowNull(true) + @Column(DataType.JSON) + handlerInputData: any -listWithLimitByCategory = function (limit: number, state: JobState, jobCategory: JobCategory) { - const query = { - order: [ - [ 'id', 'ASC' ] - ], - limit: limit, - where: { - state, - category: jobCategory + @CreatedAt + creationDate: Date + + @UpdatedAt + updatedOn: Date + + static listWithLimitByCategory (limit: number, state: JobState, jobCategory: JobCategory) { + const query = { + order: [ + [ 'id', 'ASC' ] + ], + limit: limit, + where: { + state, + category: jobCategory + } } + + return JobModel.findAll(query) } - return Job.findAll(query) -} + static listForApi (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: [ getSort(sort) ] + } -listForApi = function (start: number, count: number, sort: string) { - const query = { - offset: start, - limit: count, - order: [ getSort(sort) ] + return JobModel.findAndCountAll(query).then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) } - return Job.findAndCountAll(query).then(({ rows, count }) => { + toFormattedJSON () { return { - data: rows, - total: count + id: this.id, + state: this.state, + category: this.category, + handlerName: this.handlerName, + handlerInputData: this.handlerInputData, + createdAt: this.createdAt, + updatedAt: this.updatedAt } - }) + } } 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 @@ -export * from './oauth-client-interface' -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 @@ -import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -export namespace OAuthClientMethods { - export type CountTotal = () => Promise - - export type LoadFirstClient = () => Promise - - export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise -} - -export interface OAuthClientClass { - countTotal: OAuthClientMethods.CountTotal - loadFirstClient: OAuthClientMethods.LoadFirstClient - getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret -} - -export interface OAuthClientAttributes { - clientId: string - clientSecret: string - grants: string[] - redirectUris: string[] -} - -export interface OAuthClientInstance extends OAuthClientClass, OAuthClientAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface OAuthClientModel extends OAuthClientClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' +import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { OAuthTokenModel } from './oauth-token' -import { addMethodsToModel } from '../utils' -import { - OAuthClientInstance, - OAuthClientAttributes, - - OAuthClientMethods -} from './oauth-client-interface' - -let OAuthClient: Sequelize.Model -let countTotal: OAuthClientMethods.CountTotal -let loadFirstClient: OAuthClientMethods.LoadFirstClient -let getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - OAuthClient = sequelize.define('OAuthClient', +@Table({ + tableName: 'oAuthClient', + indexes: [ { - clientId: { - type: DataTypes.STRING, - allowNull: false - }, - clientSecret: { - type: DataTypes.STRING, - allowNull: false - }, - grants: { - type: DataTypes.ARRAY(DataTypes.STRING) - }, - redirectUris: { - type: DataTypes.ARRAY(DataTypes.STRING) - } + fields: [ 'clientId' ], + unique: true }, { - indexes: [ - { - fields: [ 'clientId' ], - unique: true - }, - { - fields: [ 'clientId', 'clientSecret' ], - unique: true - } - ] + fields: [ 'clientId', 'clientSecret' ], + unique: true } - ) + ] +}) +export class OAuthClientModel extends Model { - const classMethods = [ - associate, + @AllowNull(false) + @Column + clientId: string - countTotal, - getByIdAndSecret, - loadFirstClient - ] - addMethodsToModel(OAuthClient, classMethods) + @AllowNull(false) + @Column + clientSecret: string - return OAuthClient -} + @Column(DataType.ARRAY(DataType.STRING)) + grants: string[] + + @Column(DataType.ARRAY(DataType.STRING)) + redirectUris: string[] + + @CreatedAt + createdAt: Date -// --------------------------------------------------------------------------- + @UpdatedAt + updatedAt: Date -function associate (models) { - OAuthClient.hasMany(models.OAuthToken, { - foreignKey: 'oAuthClientId', + @HasMany(() => OAuthTokenModel, { onDelete: 'cascade' }) -} + OAuthTokens: OAuthTokenModel[] -countTotal = function () { - return OAuthClient.count() -} + static countTotal () { + return OAuthClientModel.count() + } -loadFirstClient = function () { - return OAuthClient.findOne() -} + static loadFirstClient () { + return OAuthClientModel.findOne() + } -getByIdAndSecret = function (clientId: string, clientSecret: string) { - const query = { - where: { - clientId: clientId, - clientSecret: clientSecret + static getByIdAndSecret (clientId: string, clientSecret: string) { + const query = { + where: { + clientId: clientId, + clientSecret: clientSecret + } } - } - return OAuthClient.findOne(query) + return OAuthClientModel.findOne(query) + } } 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 @@ -import * as Promise from 'bluebird' -import * as Sequelize from 'sequelize' - -import { UserModel } from '../account/user-interface' - -export type OAuthTokenInfo = { - refreshToken: string - refreshTokenExpiresAt: Date, - client: { - id: number - }, - user: { - id: number - } -} - -export namespace OAuthTokenMethods { - export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Promise - export type GetByTokenAndPopulateUser = (bearerToken: string) => Promise - export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Promise -} - -export interface OAuthTokenClass { - getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient - getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser - getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser -} - -export interface OAuthTokenAttributes { - accessToken: string - accessTokenExpiresAt: Date - refreshToken: string - refreshTokenExpiresAt: Date - - userId?: number - oAuthClientId?: number - User?: UserModel -} - -export interface OAuthTokenInstance extends OAuthTokenClass, OAuthTokenAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface OAuthTokenModel extends OAuthTokenClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' - +import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' import { logger } from '../../helpers' +import { AccountModel } from '../account/account' +import { UserModel } from '../account/user' +import { OAuthClientModel } from './oauth-client' + +export type OAuthTokenInfo = { + refreshToken: string + refreshTokenExpiresAt: Date, + client: { + id: number + }, + user: { + id: number + } +} -import { addMethodsToModel } from '../utils' -import { OAuthTokenAttributes, OAuthTokenInfo, OAuthTokenInstance, OAuthTokenMethods } from './oauth-token-interface' - -let OAuthToken: Sequelize.Model -let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient -let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser -let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - OAuthToken = sequelize.define('OAuthToken', +@Table({ + tableName: 'oAuthToken', + indexes: [ { - accessToken: { - type: DataTypes.STRING, - allowNull: false - }, - accessTokenExpiresAt: { - type: DataTypes.DATE, - allowNull: false - }, - refreshToken: { - type: DataTypes.STRING, - allowNull: false - }, - refreshTokenExpiresAt: { - type: DataTypes.DATE, - allowNull: false - } + fields: [ 'refreshToken' ], + unique: true }, { - indexes: [ - { - fields: [ 'refreshToken' ], - unique: true - }, - { - fields: [ 'accessToken' ], - unique: true - }, - { - fields: [ 'userId' ] - }, - { - fields: [ 'oAuthClientId' ] - } - ] + fields: [ 'accessToken' ], + unique: true + }, + { + fields: [ 'userId' ] + }, + { + fields: [ 'oAuthClientId' ] } - ) + ] +}) +export class OAuthTokenModel extends Model { - const classMethods = [ - associate, + @AllowNull(false) + @Column + accessToken: string - getByRefreshTokenAndPopulateClient, - getByTokenAndPopulateUser, - getByRefreshTokenAndPopulateUser - ] - addMethodsToModel(OAuthToken, classMethods) + @AllowNull(false) + @Column + accessTokenExpiresAt: Date - return OAuthToken -} + @AllowNull(false) + @Column + refreshToken: string -// --------------------------------------------------------------------------- + @AllowNull(false) + @Column + refreshTokenExpiresAt: Date -function associate (models) { - OAuthToken.belongsTo(models.User, { + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => UserModel) + @Column + userId: number + + @BelongsTo(() => UserModel, { foreignKey: { - name: 'userId', allowNull: false }, onDelete: 'cascade' }) + User: UserModel - OAuthToken.belongsTo(models.OAuthClient, { + @ForeignKey(() => OAuthClientModel) + @Column + oAuthClientId: number + + @BelongsTo(() => OAuthClientModel, { foreignKey: { - name: 'oAuthClientId', allowNull: false }, onDelete: 'cascade' }) -} + OAuthClients: OAuthClientModel[] -getByRefreshTokenAndPopulateClient = function (refreshToken: string) { - const query = { - where: { - refreshToken: refreshToken - }, - include: [ OAuthToken['sequelize'].models.OAuthClient ] + static getByRefreshTokenAndPopulateClient (refreshToken: string) { + const query = { + where: { + refreshToken: refreshToken + }, + include: [ OAuthClientModel ] + } + + return OAuthTokenModel.findOne(query) + .then(token => { + if (!token) return null + + return { + refreshToken: token.refreshToken, + refreshTokenExpiresAt: token.refreshTokenExpiresAt, + client: { + id: token.oAuthClientId + }, + user: { + id: token.userId + } + } as OAuthTokenInfo + }) + .catch(err => { + logger.info('getRefreshToken error.', err) + throw err + }) } - return OAuthToken.findOne(query) - .then(token => { - if (!token) return null - - const tokenInfos: OAuthTokenInfo = { - refreshToken: token.refreshToken, - refreshTokenExpiresAt: token.refreshTokenExpiresAt, - client: { - id: token.oAuthClientId - }, - user: { - id: token.userId + static getByTokenAndPopulateUser (bearerToken: string) { + const query = { + where: { + accessToken: bearerToken + }, + include: [ + { + model: UserModel, + include: [ + { + model: AccountModel, + required: true + } + ] } - } + ] + } - return tokenInfos - }) - .catch(err => { - logger.info('getRefreshToken error.', err) - throw err - }) -} + return OAuthTokenModel.findOne(query).then(token => { + if (token) token['user'] = token.User -getByTokenAndPopulateUser = function (bearerToken: string) { - const query = { - where: { - accessToken: bearerToken - }, - include: [ - { - model: OAuthToken['sequelize'].models.User, - include: [ - { - model: OAuthToken['sequelize'].models.Account, - required: true - } - ] - } - ] + return token + }) } - return OAuthToken.findOne(query).then(token => { - if (token) token['user'] = token.User + static getByRefreshTokenAndPopulateUser (refreshToken: string) { + const query = { + where: { + refreshToken: refreshToken + }, + include: [ + { + model: UserModel, + include: [ + { + model: AccountModel, + required: true + } + ] + } + ] + } - return token - }) -} + return OAuthTokenModel.findOne(query).then(token => { + token['user'] = token.User -getByRefreshTokenAndPopulateUser = function (refreshToken: string) { - const query = { - where: { - refreshToken: refreshToken - }, - include: [ - { - model: OAuthToken['sequelize'].models.User, - include: [ - { - model: OAuthToken['sequelize'].models.Account, - required: true - } - ] - } - ] + return token + }) } - - return OAuthToken.findOne(query).then(token => { - token['user'] = token.User - - return token - }) } 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 @@ -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 @@ -import * as Promise from 'bluebird' -import * as Sequelize from 'sequelize' - -export namespace ServerMethods { - export type ListBadServers = () => Promise - export type UpdateServersScoreAndRemoveBadOnes = (goodServers: number[], badServers: number[]) => void -} - -export interface ServerClass { - updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes -} - -export interface ServerAttributes { - id?: number - host?: string - score?: number | Sequelize.literal // Sequelize literal for 'score +' + value -} - -export interface ServerInstance extends ServerClass, ServerAttributes, Sequelize.Instance { - createdAt: Date - updatedAt: Date -} - -export interface ServerModel extends ServerClass, Sequelize.Model {} 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 @@ import * as Sequelize from 'sequelize' -import { isHostValid, logger } from '../../helpers' +import { AllowNull, Column, CreatedAt, Default, Is, IsInt, Max, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { logger } from '../../helpers' +import { isHostValid } from '../../helpers/custom-validators/servers' import { SERVERS_SCORE } from '../../initializers' -import { addMethodsToModel } from '../utils' -import { ServerAttributes, ServerInstance, ServerMethods } from './server-interface' +import { throwIfNotValid } from '../utils' -let Server: Sequelize.Model -let updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Server = sequelize.define('Server', +@Table({ + tableName: 'server', + indexes: [ { - host: { - type: DataTypes.STRING, - allowNull: false, - validate: { - isHost: value => { - const res = isHostValid(value) - if (res === false) throw new Error('Host not valid.') - } - } - }, - score: { - type: DataTypes.INTEGER, - defaultValue: SERVERS_SCORE.BASE, - allowNull: false, - validate: { - isInt: true, - max: SERVERS_SCORE.MAX - } - } + fields: [ 'host' ], + unique: true }, { - indexes: [ - { - fields: [ 'host' ], - unique: true - }, - { - fields: [ 'score' ] - } - ] + fields: [ 'score' ] } - ) - - const classMethods = [ - updateServersScoreAndRemoveBadOnes ] - addMethodsToModel(Server, classMethods) - - return Server -} - -// ------------------------------ Statics ------------------------------ - -updateServersScoreAndRemoveBadOnes = function (goodServers: number[], badServers: number[]) { - logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length) +}) +export class ServerModel extends Model { + + @AllowNull(false) + @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host')) + @Column + host: string + + @AllowNull(false) + @Default(SERVERS_SCORE.BASE) + @IsInt + @Max(SERVERS_SCORE.MAX) + @Column + score: number + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + static updateServersScoreAndRemoveBadOnes (goodServers: number[], badServers: number[]) { + logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length) + + if (goodServers.length !== 0) { + ServerModel.incrementScores(goodServers, SERVERS_SCORE.BONUS) + .catch(err => { + logger.error('Cannot increment scores of good servers.', err) + }) + } - if (goodServers.length !== 0) { - incrementScores(goodServers, SERVERS_SCORE.BONUS).catch(err => { - logger.error('Cannot increment scores of good servers.', err) - }) - } + if (badServers.length !== 0) { + ServerModel.incrementScores(badServers, SERVERS_SCORE.PENALTY) + .then(() => ServerModel.removeBadServers()) + .catch(err => { + if (err) logger.error('Cannot decrement scores of bad servers.', err) + }) - if (badServers.length !== 0) { - incrementScores(badServers, SERVERS_SCORE.PENALTY) - .then(() => removeBadServers()) - .catch(err => { - if (err) logger.error('Cannot decrement scores of bad servers.', err) - }) + } } -} - -// --------------------------------------------------------------------------- -// Remove servers with a score of 0 (too many requests where they were unreachable) -async function removeBadServers () { - try { - const servers = await listBadServers() + // Remove servers with a score of 0 (too many requests where they were unreachable) + private static async removeBadServers () { + try { + const servers = await ServerModel.listBadServers() - const serversRemovePromises = servers.map(server => server.destroy()) - await Promise.all(serversRemovePromises) + const serversRemovePromises = servers.map(server => server.destroy()) + await Promise.all(serversRemovePromises) - const numberOfServersRemoved = servers.length + const numberOfServersRemoved = servers.length - if (numberOfServersRemoved) { - logger.info('Removed %d servers.', numberOfServersRemoved) - } else { - logger.info('No need to remove bad servers.') + if (numberOfServersRemoved) { + logger.info('Removed %d servers.', numberOfServersRemoved) + } else { + logger.info('No need to remove bad servers.') + } + } catch (err) { + logger.error('Cannot remove bad servers.', err) } - } catch (err) { - logger.error('Cannot remove bad servers.', err) } -} -function incrementScores (ids: number[], value: number) { - const update = { - score: Sequelize.literal('score +' + value) - } + private static incrementScores (ids: number[], value: number) { + const update = { + score: Sequelize.literal('score +' + value) + } - const options = { - where: { - id: { - [Sequelize.Op.in]: ids - } - }, - // In this case score is a literal and not an integer so we do not validate it - validate: false - } + const options = { + where: { + id: { + [Sequelize.Op.in]: ids + } + }, + // In this case score is a literal and not an integer so we do not validate it + validate: false + } - return Server.update(update, options) -} + return ServerModel.update(update, options) + } -function listBadServers () { - const query = { - where: { - score: { - [Sequelize.Op.lte]: 0 + private static listBadServers () { + const query = { + where: { + score: { + [Sequelize.Op.lte]: 0 + } } } - } - return Server.findAll(query) + return ServerModel.findAll(query) + } } 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) { return [ field, direction ] } -function addMethodsToModel (model: any, classMethods: Function[], instanceMethods: Function[] = []) { - classMethods.forEach(m => model[m.name] = m) - instanceMethods.forEach(m => model.prototype[m.name] = m) -} - function getSortOnModel (model: any, value: string) { let sort = getSort(value) - if (model) return [ { model: model }, sort[0], sort[1] ] + if (model) return [ model, sort[0], sort[1] ] return sort } +function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value') { + if (validator(value) === false) { + throw new Error(`"${value}" is not a valid ${fieldName}.`) + } +} + // --------------------------------------------------------------------------- export { - addMethodsToModel, getSort, - getSortOnModel + getSortOnModel, + throwIfNotValid } 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 @@ -export * from './tag-interface' -export * from './video-abuse-interface' -export * from './video-blacklist-interface' -export * from './video-channel-interface' -export * from './video-tag-interface' -export * from './video-file-interface' -export * from './video-interface' -export * from './video-share-interface' -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 @@ -import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -export namespace TagMethods { - export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction) => Promise -} - -export interface TagClass { - findOrCreateTags: TagMethods.FindOrCreateTags -} - -export interface TagAttributes { - name: string -} - -export interface TagInstance extends TagClass, TagAttributes, Sequelize.Instance { - id: number -} - -export interface TagModel extends TagClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -import { addMethodsToModel } from '../utils' -import { - TagInstance, - TagAttributes, - - TagMethods -} from './tag-interface' - -let Tag: Sequelize.Model -let findOrCreateTags: TagMethods.FindOrCreateTags - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Tag = sequelize.define('Tag', +import * as Bluebird from 'bluebird' +import { Transaction } from 'sequelize' +import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { isVideoTagValid } from '../../helpers/custom-validators/videos' +import { throwIfNotValid } from '../utils' +import { VideoModel } from './video' +import { VideoTagModel } from './video-tag' + +@Table({ + tableName: 'tag', + timestamps: false, + indexes: [ { - name: { - type: DataTypes.STRING, - allowNull: false - } - }, - { - timestamps: false, - indexes: [ - { - fields: [ 'name' ], - unique: true - } - ] + fields: [ 'name' ], + unique: true } - ) - - const classMethods = [ - associate, - - findOrCreateTags ] - addMethodsToModel(Tag, classMethods) +}) +export class TagModel extends Model { - return Tag -} + @AllowNull(false) + @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag')) + @Column + name: string -// --------------------------------------------------------------------------- + @CreatedAt + createdAt: Date -function associate (models) { - Tag.belongsToMany(models.Video, { + @UpdatedAt + updatedAt: Date + + @BelongsToMany(() => VideoModel, { foreignKey: 'tagId', - through: models.VideoTag, + through: () => VideoTagModel, onDelete: 'CASCADE' }) -} - -findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction) { - const tasks: Promise[] = [] - tags.forEach(tag => { - const query: Sequelize.FindOrInitializeOptions = { - where: { - name: tag - }, - defaults: { - name: tag + Videos: VideoModel[] + + static findOrCreateTags (tags: string[], transaction: Transaction) { + const tasks: Bluebird[] = [] + tags.forEach(tag => { + const query = { + where: { + name: tag + }, + defaults: { + name: tag + } } - } - if (transaction) query.transaction = transaction + if (transaction) query['transaction'] = transaction - const promise = Tag.findOrCreate(query).then(([ tagInstance ]) => tagInstance) - tasks.push(promise) - }) + const promise = TagModel.findOrCreate(query) + .then(([ tagInstance ]) => tagInstance) + tasks.push(promise) + }) - return Promise.all(tasks) + return Promise.all(tasks) + } } 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 @@ -import * as Promise from 'bluebird' -import * as Sequelize from 'sequelize' -import { ResultList } from '../../../shared' -import { VideoAbuse as FormattedVideoAbuse } from '../../../shared/models/videos/video-abuse.model' -import { AccountInstance } from '../account/account-interface' -import { ServerInstance } from '../server/server-interface' -import { VideoInstance } from './video-interface' -import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' - -export namespace VideoAbuseMethods { - export type ToFormattedJSON = (this: VideoAbuseInstance) => FormattedVideoAbuse - - export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList > - export type ToActivityPubObject = () => VideoAbuseObject -} - -export interface VideoAbuseClass { - listForApi: VideoAbuseMethods.ListForApi - toActivityPubObject: VideoAbuseMethods.ToActivityPubObject -} - -export interface VideoAbuseAttributes { - reason: string - videoId: number - reporterAccountId: number - - Account?: AccountInstance - Video?: VideoInstance -} - -export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - Server: ServerInstance - - toFormattedJSON: VideoAbuseMethods.ToFormattedJSON -} - -export interface VideoAbuseModel extends VideoAbuseClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' - +import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' +import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' import { CONFIG } from '../../initializers' -import { isVideoAbuseReasonValid } from '../../helpers' - -import { addMethodsToModel, getSort } from '../utils' -import { - VideoAbuseInstance, - VideoAbuseAttributes, - - VideoAbuseMethods -} from './video-abuse-interface' -import { VideoAbuseObject } from '../../../shared/models/activitypub/objects/video-abuse-object' - -let VideoAbuse: Sequelize.Model -let toFormattedJSON: VideoAbuseMethods.ToFormattedJSON -let listForApi: VideoAbuseMethods.ListForApi -let toActivityPubObject: VideoAbuseMethods.ToActivityPubObject - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoAbuse = sequelize.define('VideoAbuse', +import { AccountModel } from '../account/account' +import { ServerModel } from '../server/server' +import { getSort, throwIfNotValid } from '../utils' +import { VideoModel } from './video' + +@Table({ + tableName: 'videoAbuse', + indexes: [ { - reason: { - type: DataTypes.STRING, - allowNull: false, - validate: { - reasonValid: value => { - const res = isVideoAbuseReasonValid(value) - if (res === false) throw new Error('Video abuse reason is not valid.') - } - } - } + fields: [ 'videoId' ] }, { - indexes: [ - { - fields: [ 'videoId' ] - }, - { - fields: [ 'reporterAccountId' ] - } - ] + fields: [ 'reporterAccountId' ] } - ) - - const classMethods = [ - associate, - - listForApi - ] - const instanceMethods = [ - toFormattedJSON, - toActivityPubObject ] - addMethodsToModel(VideoAbuse, classMethods, instanceMethods) - - return VideoAbuse -} - -// ------------------------------ METHODS ------------------------------ - -toFormattedJSON = function (this: VideoAbuseInstance) { - let reporterServerHost - - if (this.Account.Server) { - reporterServerHost = this.Account.Server.host - } else { - // It means it's our video - reporterServerHost = CONFIG.WEBSERVER.HOST - } - - const json = { - id: this.id, - reason: this.reason, - reporterUsername: this.Account.name, - reporterServerHost, - videoId: this.Video.id, - videoUUID: this.Video.uuid, - videoName: this.Video.name, - createdAt: this.createdAt - } +}) +export class VideoAbuseModel extends Model { - return json -} + @AllowNull(false) + @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason')) + @Column + reason: string -toActivityPubObject = function (this: VideoAbuseInstance) { - const videoAbuseObject: VideoAbuseObject = { - type: 'Flag' as 'Flag', - content: this.reason, - object: this.Video.url - } + @CreatedAt + createdAt: Date - return videoAbuseObject -} + @UpdatedAt + updatedAt: Date -// ------------------------------ STATICS ------------------------------ + @ForeignKey(() => AccountModel) + @Column + reporterAccountId: number -function associate (models) { - VideoAbuse.belongsTo(models.Account, { + @BelongsTo(() => AccountModel, { foreignKey: { - name: 'reporterAccountId', allowNull: false }, - onDelete: 'CASCADE' + onDelete: 'cascade' }) + Account: AccountModel + + @ForeignKey(() => VideoModel) + @Column + videoId: number - VideoAbuse.belongsTo(models.Video, { + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', allowNull: false }, - onDelete: 'CASCADE' + onDelete: 'cascade' }) -} + Video: VideoModel + + static listForApi (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: AccountModel, + required: true, + include: [ + { + model: ServerModel, + required: false + } + ] + }, + { + model: VideoModel, + required: true + } + ] + } -listForApi = function (start: number, count: number, sort: string) { - const query = { - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: VideoAbuse['sequelize'].models.Account, - required: true, - include: [ - { - model: VideoAbuse['sequelize'].models.Server, - required: false - } - ] - }, - { - model: VideoAbuse['sequelize'].models.Video, - required: true - } - ] + return VideoAbuseModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { total: count, data: rows } + }) } - return VideoAbuse.findAndCountAll(query).then(({ rows, count }) => { - return { total: count, data: rows } - }) + toFormattedJSON () { + let reporterServerHost + + if (this.Account.Server) { + reporterServerHost = this.Account.Server.host + } else { + // It means it's our video + reporterServerHost = CONFIG.WEBSERVER.HOST + } + + return { + id: this.id, + reason: this.reason, + reporterUsername: this.Account.name, + reporterServerHost, + videoId: this.Video.id, + videoUUID: this.Video.uuid, + videoName: this.Video.name, + createdAt: this.createdAt + } + } + + toActivityPubObject (): VideoAbuseObject { + return { + type: 'Flag' as 'Flag', + content: this.reason, + object: this.Video.url + } + } } 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 @@ -import * as Sequelize from 'sequelize' -import * as Promise from 'bluebird' - -import { SortType } from '../../helpers' -import { ResultList } from '../../../shared' -import { VideoInstance } from './video-interface' - -// Don't use barrel, import just what we need -import { BlacklistedVideo as FormattedBlacklistedVideo } from '../../../shared/models/videos/video-blacklist.model' - -export namespace BlacklistedVideoMethods { - export type ToFormattedJSON = (this: BlacklistedVideoInstance) => FormattedBlacklistedVideo - export type ListForApi = (start: number, count: number, sort: SortType) => Promise< ResultList > - export type LoadByVideoId = (id: number) => Promise -} - -export interface BlacklistedVideoClass { - toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON - listForApi: BlacklistedVideoMethods.ListForApi - loadByVideoId: BlacklistedVideoMethods.LoadByVideoId -} - -export interface BlacklistedVideoAttributes { - videoId: number - - Video?: VideoInstance -} - -export interface BlacklistedVideoInstance - extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON -} - -export interface BlacklistedVideoModel - extends BlacklistedVideoClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' - +import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' import { SortType } from '../../helpers' -import { addMethodsToModel, getSortOnModel } from '../utils' -import { VideoInstance } from './video-interface' -import { - BlacklistedVideoInstance, - BlacklistedVideoAttributes, - - BlacklistedVideoMethods -} from './video-blacklist-interface' - -let BlacklistedVideo: Sequelize.Model -let toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON -let listForApi: BlacklistedVideoMethods.ListForApi -let loadByVideoId: BlacklistedVideoMethods.LoadByVideoId +import { getSortOnModel } from '../utils' +import { VideoModel } from './video' -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - BlacklistedVideo = sequelize.define('BlacklistedVideo', - {}, +@Table({ + tableName: 'videoBlacklist', + indexes: [ { - indexes: [ - { - fields: [ 'videoId' ], - unique: true - } - ] + fields: [ 'videoId' ], + unique: true } - ) - - const classMethods = [ - associate, - - listForApi, - loadByVideoId ] - const instanceMethods = [ - toFormattedJSON - ] - addMethodsToModel(BlacklistedVideo, classMethods, instanceMethods) - - return BlacklistedVideo -} +}) +export class VideoBlacklistModel extends Model { -// ------------------------------ METHODS ------------------------------ + @CreatedAt + createdAt: Date -toFormattedJSON = function (this: BlacklistedVideoInstance) { - let video: VideoInstance - - video = this.Video - - return { - id: this.id, - videoId: this.videoId, - createdAt: this.createdAt, - updatedAt: this.updatedAt, - name: video.name, - uuid: video.uuid, - description: video.description, - duration: video.duration, - views: video.views, - likes: video.likes, - dislikes: video.dislikes, - nsfw: video.nsfw - } -} + @UpdatedAt + updatedAt: Date -// ------------------------------ STATICS ------------------------------ + @ForeignKey(() => VideoModel) + @Column + videoId: number -function associate (models) { - BlacklistedVideo.belongsTo(models.Video, { + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', allowNull: false }, - onDelete: 'CASCADE' + onDelete: 'cascade' }) -} + Video: VideoModel + + static listForApi (start: number, count: number, sort: SortType) { + const query = { + offset: start, + limit: count, + order: [ getSortOnModel(sort.sortModel, sort.sortValue) ], + include: [ { model: VideoModel } ] + } -listForApi = function (start: number, count: number, sort: SortType) { - const query = { - offset: start, - limit: count, - order: [ getSortOnModel(sort.sortModel, sort.sortValue) ], - include: [ { model: BlacklistedVideo['sequelize'].models.Video } ] + return VideoBlacklistModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) } - return BlacklistedVideo.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count + static loadByVideoId (id: number) { + const query = { + where: { + videoId: id + } } - }) -} -loadByVideoId = function (id: number) { - const query = { - where: { - videoId: id - } + return VideoBlacklistModel.findOne(query) } - return BlacklistedVideo.findOne(query) + toFormattedJSON () { + const video = this.Video + + return { + id: this.id, + videoId: this.videoId, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + name: video.name, + uuid: video.uuid, + description: video.description, + duration: video.duration, + views: video.views, + likes: video.likes, + dislikes: video.dislikes, + nsfw: video.nsfw + } + } } 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 @@ -import * as Promise from 'bluebird' -import * as Sequelize from 'sequelize' - -import { ResultList } from '../../../shared' -import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' -import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' -import { AccountInstance } from '../account/account-interface' -import { VideoInstance } from './video-interface' -import { VideoChannelShareInstance } from './video-channel-share-interface' - -export namespace VideoChannelMethods { - export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel - export type ToActivityPubObject = (this: VideoChannelInstance) => VideoChannelObject - export type IsOwned = (this: VideoChannelInstance) => boolean - - export type CountByAccount = (accountId: number) => Promise - export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList > - export type LoadByIdAndAccount = (id: number, accountId: number) => Promise - export type ListByAccount = (accountId: number) => Promise< ResultList > - export type LoadAndPopulateAccount = (id: number) => Promise - export type LoadByUUIDAndPopulateAccount = (uuid: string) => Promise - export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise - export type LoadByHostAndUUID = (uuid: string, serverHost: string, t?: Sequelize.Transaction) => Promise - export type LoadAndPopulateAccountAndVideos = (id: number) => Promise - export type LoadByUrl = (uuid: string, t?: Sequelize.Transaction) => Promise - export type LoadByUUIDOrUrl = (uuid: string, url: string, t?: Sequelize.Transaction) => Promise -} - -export interface VideoChannelClass { - countByAccount: VideoChannelMethods.CountByAccount - listForApi: VideoChannelMethods.ListForApi - listByAccount: VideoChannelMethods.ListByAccount - loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount - loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount - loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount - loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos - loadByUrl: VideoChannelMethods.LoadByUrl - loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl -} - -export interface VideoChannelAttributes { - id?: number - uuid?: string - name: string - description: string - remote: boolean - url?: string - - Account?: AccountInstance - Videos?: VideoInstance[] - VideoChannelShares?: VideoChannelShareInstance[] -} - -export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - isOwned: VideoChannelMethods.IsOwned - toFormattedJSON: VideoChannelMethods.ToFormattedJSON - toActivityPubObject: VideoChannelMethods.ToActivityPubObject -} - -export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model {} 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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { AccountInstance } from '../account/account-interface' -import { VideoChannelInstance } from './video-channel-interface' - -export namespace VideoChannelShareMethods { - export type LoadAccountsByShare = (videoChannelId: number, t: Sequelize.Transaction) => Bluebird - export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird -} - -export interface VideoChannelShareClass { - loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare - load: VideoChannelShareMethods.Load -} - -export interface VideoChannelShareAttributes { - accountId: number - videoChannelId: number -} - -export interface VideoChannelShareInstance - extends VideoChannelShareClass, VideoChannelShareAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - Account?: AccountInstance - VideoChannel?: VideoChannelInstance -} - -export interface VideoChannelShareModel - extends VideoChannelShareClass, Sequelize.Model {} 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 @@ import * as Sequelize from 'sequelize' +import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { AccountModel } from '../account/account' +import { VideoChannelModel } from './video-channel' -import { addMethodsToModel } from '../utils' -import { VideoChannelShareAttributes, VideoChannelShareInstance, VideoChannelShareMethods } from './video-channel-share-interface' - -let VideoChannelShare: Sequelize.Model -let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare -let load: VideoChannelShareMethods.Load - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoChannelShare = sequelize.define('VideoChannelShare', - { }, +@Table({ + tableName: 'videoChannelShare', + indexes: [ { - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'videoChannelId' ] - } - ] + fields: [ 'accountId' ] + }, + { + fields: [ 'videoChannelId' ] } - ) - - const classMethods = [ - associate, - load, - loadAccountsByShare ] - addMethodsToModel(VideoChannelShare, classMethods) +}) +export class VideoChannelShareModel extends Model { + @CreatedAt + createdAt: Date - return VideoChannelShare -} + @UpdatedAt + updatedAt: Date -// ------------------------------ METHODS ------------------------------ + @ForeignKey(() => AccountModel) + @Column + accountId: number -function associate (models) { - VideoChannelShare.belongsTo(models.Account, { + @BelongsTo(() => AccountModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'cascade' }) + Account: AccountModel - VideoChannelShare.belongsTo(models.VideoChannel, { + @ForeignKey(() => VideoChannelModel) + @Column + videoChannelId: number + + @BelongsTo(() => VideoChannelModel, { foreignKey: { - name: 'videoChannelId', - allowNull: true + allowNull: false }, onDelete: 'cascade' }) -} - -load = function (accountId: number, videoChannelId: number, t: Sequelize.Transaction) { - return VideoChannelShare.findOne({ - where: { - accountId, - videoChannelId - }, - include: [ - VideoChannelShare['sequelize'].models.Account, - VideoChannelShare['sequelize'].models.VideoChannel - ], - transaction: t - }) -} + VideoChannel: VideoChannelModel -loadAccountsByShare = function (videoChannelId: number, t: Sequelize.Transaction) { - const query = { - where: { - videoChannelId - }, - include: [ - { - model: VideoChannelShare['sequelize'].models.Account, - required: true - } - ], - transaction: t + static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) { + return VideoChannelShareModel.findOne({ + where: { + accountId, + videoChannelId + }, + include: [ + AccountModel, + VideoChannelModel + ], + transaction: t + }) } - return VideoChannelShare.findAll(query) - .then(res => res.map(r => r.Account)) + static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) { + const query = { + where: { + videoChannelId + }, + include: [ + { + model: AccountModel, + required: true + } + ], + transaction: t + } + + return VideoChannelShareModel.findAll(query) + .then(res => res.map(r => r.Account)) + } } 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 @@ import * as Sequelize from 'sequelize' -import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers' -import { CONSTRAINTS_FIELDS } from '../../initializers/constants' -import { sendDeleteVideoChannel } from '../../lib/activitypub/send/send-delete' - -import { addMethodsToModel, getSort } from '../utils' -import { VideoChannelAttributes, VideoChannelInstance, VideoChannelMethods } from './video-channel-interface' -import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' -import { activityPubCollection } from '../../helpers/activitypub' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' - -let VideoChannel: Sequelize.Model -let toFormattedJSON: VideoChannelMethods.ToFormattedJSON -let toActivityPubObject: VideoChannelMethods.ToActivityPubObject -let isOwned: VideoChannelMethods.IsOwned -let countByAccount: VideoChannelMethods.CountByAccount -let listForApi: VideoChannelMethods.ListForApi -let listByAccount: VideoChannelMethods.ListByAccount -let loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount -let loadByUUID: VideoChannelMethods.LoadByUUID -let loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount -let loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount -let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID -let loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos -let loadByUrl: VideoChannelMethods.LoadByUrl -let loadByUUIDOrUrl: VideoChannelMethods.LoadByUUIDOrUrl - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoChannel = sequelize.define('VideoChannel', +import { + AfterDestroy, + AllowNull, + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + Is, + IsUUID, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' +import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' +import { activityPubCollection } from '../../helpers' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' +import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' +import { CONSTRAINTS_FIELDS } from '../../initializers' +import { getAnnounceActivityPubUrl } from '../../lib/activitypub' +import { sendDeleteVideoChannel } from '../../lib/activitypub/send' +import { AccountModel } from '../account/account' +import { ServerModel } from '../server/server' +import { getSort, throwIfNotValid } from '../utils' +import { VideoModel } from './video' +import { VideoChannelShareModel } from './video-channel-share' + +@Table({ + tableName: 'videoChannel', + indexes: [ { - uuid: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - allowNull: false, - validate: { - isUUID: 4 - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - nameValid: value => { - const res = isVideoChannelNameValid(value) - if (res === false) throw new Error('Video channel name is not valid.') - } - } - }, - description: { - type: DataTypes.STRING, - allowNull: true, - validate: { - descriptionValid: value => { - const res = isVideoChannelDescriptionValid(value) - if (res === false) throw new Error('Video channel description is not valid.') - } - } - }, - remote: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - url: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max), - allowNull: false, - validate: { - urlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Video channel URL is not valid.') - } - } - } - }, - { - indexes: [ - { - fields: [ 'accountId' ] - } - ], - hooks: { - afterDestroy - } + fields: [ 'accountId' ] } - ) - - const classMethods = [ - associate, - - listForApi, - listByAccount, - loadByIdAndAccount, - loadAndPopulateAccount, - loadByUUIDAndPopulateAccount, - loadByUUID, - loadByHostAndUUID, - loadAndPopulateAccountAndVideos, - countByAccount, - loadByUrl, - loadByUUIDOrUrl ] - const instanceMethods = [ - isOwned, - toFormattedJSON, - toActivityPubObject - ] - addMethodsToModel(VideoChannel, classMethods, instanceMethods) +}) +export class VideoChannelModel extends Model { - return VideoChannel -} + @AllowNull(false) + @Default(DataType.UUIDV4) + @IsUUID(4) + @Column(DataType.UUID) + uuid: string -// ------------------------------ METHODS ------------------------------ + @AllowNull(false) + @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) + @Column + name: string -isOwned = function (this: VideoChannelInstance) { - return this.remote === false -} - -toFormattedJSON = function (this: VideoChannelInstance) { - const json = { - id: this.id, - uuid: this.uuid, - name: this.name, - description: this.description, - isLocal: this.isOwned(), - createdAt: this.createdAt, - updatedAt: this.updatedAt - } + @AllowNull(true) + @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description')) + @Column + description: string - if (this.Account !== undefined) { - json['owner'] = { - name: this.Account.name, - uuid: this.Account.uuid - } - } + @AllowNull(false) + @Column + remote: boolean - if (Array.isArray(this.Videos)) { - json['videos'] = this.Videos.map(v => v.toFormattedJSON()) - } + @AllowNull(false) + @Is('VideoChannelUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max)) + url: string - return json -} + @CreatedAt + createdAt: Date -toActivityPubObject = function (this: VideoChannelInstance) { - let sharesObject - if (Array.isArray(this.VideoChannelShares)) { - const shares: string[] = [] + @UpdatedAt + updatedAt: Date - for (const videoChannelShare of this.VideoChannelShares) { - const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account) - shares.push(shareUrl) - } + @ForeignKey(() => AccountModel) + @Column + accountId: number - sharesObject = activityPubCollection(shares) - } - - const json = { - type: 'VideoChannel' as 'VideoChannel', - id: this.url, - uuid: this.uuid, - content: this.description, - name: this.name, - published: this.createdAt.toISOString(), - updated: this.updatedAt.toISOString(), - shares: sharesObject - } - - return json -} - -// ------------------------------ STATICS ------------------------------ + @BelongsTo(() => AccountModel, { + foreignKey: { + allowNull: false + }, + onDelete: 'CASCADE' + }) + Account: AccountModel -function associate (models) { - VideoChannel.belongsTo(models.Account, { + @HasMany(() => VideoModel, { foreignKey: { - name: 'accountId', + name: 'channelId', allowNull: false }, onDelete: 'CASCADE' }) + Videos: VideoModel[] - VideoChannel.hasMany(models.Video, { + @HasMany(() => VideoChannelShareModel, { foreignKey: { name: 'channelId', allowNull: false }, onDelete: 'CASCADE' }) -} + VideoChannelShares: VideoChannelShareModel[] -function afterDestroy (videoChannel: VideoChannelInstance) { - if (videoChannel.isOwned()) { - return sendDeleteVideoChannel(videoChannel, undefined) - } + @AfterDestroy + static sendDeleteIfOwned (instance: VideoChannelModel) { + if (instance.isOwned()) { + return sendDeleteVideoChannel(instance, undefined) + } - return undefined -} + return undefined + } -countByAccount = function (accountId: number) { - const query = { - where: { - accountId + static countByAccount (accountId: number) { + const query = { + where: { + accountId + } } + + return VideoChannelModel.count(query) } - return VideoChannel.count(query) -} + static listForApi (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: AccountModel, + required: true, + include: [ { model: ServerModel, required: false } ] + } + ] + } -listForApi = function (start: number, count: number, sort: string) { - const query = { - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: VideoChannel['sequelize'].models.Account, - required: true, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] - } - ] + return VideoChannelModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { total: count, data: rows } + }) } - return VideoChannel.findAndCountAll(query).then(({ rows, count }) => { - return { total: count, data: rows } - }) -} + static listByAccount (accountId: number) { + const query = { + order: [ getSort('createdAt') ], + include: [ + { + model: AccountModel, + where: { + id: accountId + }, + required: true, + include: [ { model: ServerModel, required: false } ] + } + ] + } -listByAccount = function (accountId: number) { - const query = { - order: [ getSort('createdAt') ], - include: [ - { - model: VideoChannel['sequelize'].models.Account, - where: { - id: accountId - }, - required: true, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] - } - ] + return VideoChannelModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { total: count, data: rows } + }) } - return VideoChannel.findAndCountAll(query).then(({ rows, count }) => { - return { total: count, data: rows } - }) -} + static loadByUUID (uuid: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + uuid + } + } + + if (t !== undefined) query.transaction = t -loadByUUID = function (uuid: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - uuid + return VideoChannelModel.findOne(query) + } + + static loadByUrl (url: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + url + }, + include: [ AccountModel ] } + + if (t !== undefined) query.transaction = t + + return VideoChannelModel.findOne(query) } - if (t !== undefined) query.transaction = t + static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + [ Sequelize.Op.or ]: [ + { uuid }, + { url } + ] + } + } - return VideoChannel.findOne(query) -} + if (t !== undefined) query.transaction = t -loadByUrl = function (url: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - url - }, - include: [ VideoChannel['sequelize'].models.Account ] + return VideoChannelModel.findOne(query) } - if (t !== undefined) query.transaction = t + static loadByHostAndUUID (fromHost: string, uuid: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + uuid + }, + include: [ + { + model: AccountModel, + include: [ + { + model: ServerModel, + required: true, + where: { + host: fromHost + } + } + ] + } + ] + } - return VideoChannel.findOne(query) -} + if (t !== undefined) query.transaction = t + + return VideoChannelModel.findOne(query) + } -loadByUUIDOrUrl = function (uuid: string, url: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - [Sequelize.Op.or]: [ - { uuid }, - { url } + static loadByIdAndAccount (id: number, accountId: number) { + const options = { + where: { + id, + accountId + }, + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + } ] } + + return VideoChannelModel.findOne(options) } - if (t !== undefined) query.transaction = t + static loadAndPopulateAccount (id: number) { + const options = { + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + } + ] + } - return VideoChannel.findOne(query) -} + return VideoChannelModel.findById(id, options) + } -loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - uuid - }, - include: [ - { - model: VideoChannel['sequelize'].models.Account, - include: [ - { - model: VideoChannel['sequelize'].models.Server, - required: true, - where: { - host: fromHost - } - } - ] - } - ] + static loadByUUIDAndPopulateAccount (uuid: string) { + const options = { + where: { + uuid + }, + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + } + ] + } + + return VideoChannelModel.findOne(options) } - if (t !== undefined) query.transaction = t + static loadAndPopulateAccountAndVideos (id: number) { + const options = { + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + }, + VideoModel + ] + } - return VideoChannel.findOne(query) -} + return VideoChannelModel.findById(id, options) + } -loadByIdAndAccount = function (id: number, accountId: number) { - const options = { - where: { - id, - accountId - }, - include: [ - { - model: VideoChannel['sequelize'].models.Account, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] - } - ] + isOwned () { + return this.remote === false } - return VideoChannel.findOne(options) -} + toFormattedJSON () { + const json = { + id: this.id, + uuid: this.uuid, + name: this.name, + description: this.description, + isLocal: this.isOwned(), + createdAt: this.createdAt, + updatedAt: this.updatedAt + } -loadAndPopulateAccount = function (id: number) { - const options = { - include: [ - { - model: VideoChannel['sequelize'].models.Account, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] + if (this.Account !== undefined) { + json[ 'owner' ] = { + name: this.Account.name, + uuid: this.Account.uuid } - ] + } + + if (Array.isArray(this.Videos)) { + json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON()) + } + + return json } - return VideoChannel.findById(id, options) -} + toActivityPubObject () { + let sharesObject + if (Array.isArray(this.VideoChannelShares)) { + const shares: string[] = [] -loadByUUIDAndPopulateAccount = function (uuid: string) { - const options = { - where: { - uuid - }, - include: [ - { - model: VideoChannel['sequelize'].models.Account, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] + for (const videoChannelShare of this.VideoChannelShares) { + const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account) + shares.push(shareUrl) } - ] - } - return VideoChannel.findOne(options) -} + sharesObject = activityPubCollection(shares) + } -loadAndPopulateAccountAndVideos = function (id: number) { - const options = { - include: [ - { - model: VideoChannel['sequelize'].models.Account, - include: [ { model: VideoChannel['sequelize'].models.Server, required: false } ] - }, - VideoChannel['sequelize'].models.Video - ] + return { + type: 'VideoChannel' as 'VideoChannel', + id: this.url, + uuid: this.uuid, + content: this.description, + name: this.name, + published: this.createdAt.toISOString(), + updated: this.updatedAt.toISOString(), + shares: sharesObject + } } - - return VideoChannel.findById(id, options) } 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 @@ -import * as Sequelize from 'sequelize' - -export namespace VideoFileMethods { -} - -export interface VideoFileClass { -} - -export interface VideoFileAttributes { - resolution: number - size: number - infoHash?: string - extname: string - - videoId?: number -} - -export interface VideoFileInstance extends VideoFileClass, VideoFileAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface VideoFileModel extends VideoFileClass, Sequelize.Model {} 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 @@ import { values } from 'lodash' -import * as Sequelize from 'sequelize' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' import { isVideoFileInfoHashValid, isVideoFileResolutionValid, isVideoFileSizeValid } from '../../helpers/custom-validators/videos' -import { CONSTRAINTS_FIELDS } from '../../initializers/constants' +import { CONSTRAINTS_FIELDS } from '../../initializers' +import { throwIfNotValid } from '../utils' +import { VideoModel } from './video' -import { addMethodsToModel } from '../utils' -import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' - -let VideoFile: Sequelize.Model - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoFile = sequelize.define('VideoFile', +@Table({ + tableName: 'videoFile', + indexes: [ { - resolution: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - resolutionValid: value => { - const res = isVideoFileResolutionValid(value) - if (res === false) throw new Error('Video file resolution is not valid.') - } - } - }, - size: { - type: DataTypes.BIGINT, - allowNull: false, - validate: { - sizeValid: value => { - const res = isVideoFileSizeValid(value) - if (res === false) throw new Error('Video file size is not valid.') - } - } - }, - extname: { - type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)), - allowNull: false - }, - infoHash: { - type: DataTypes.STRING, - allowNull: false, - validate: { - infoHashValid: value => { - const res = isVideoFileInfoHashValid(value) - if (res === false) throw new Error('Video file info hash is not valid.') - } - } - } + fields: [ 'videoId' ] }, { - indexes: [ - { - fields: [ 'videoId' ] - }, - { - fields: [ 'infoHash' ] - } - ] + fields: [ 'infoHash' ] } - ) - - const classMethods = [ - associate ] - addMethodsToModel(VideoFile, classMethods) - - return VideoFile -} - -// ------------------------------ STATICS ------------------------------ - -function associate (models) { - VideoFile.belongsTo(models.Video, { +}) +export class VideoFileModel extends Model { + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @AllowNull(false) + @Is('VideoFileResolution', value => throwIfNotValid(value, isVideoFileResolutionValid, 'resolution')) + @Column + resolution: number + + @AllowNull(false) + @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileSizeValid, 'size')) + @Column(DataType.BIGINT) + size: number + + @AllowNull(false) + @Column(DataType.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME))) + extname: string + + @AllowNull(false) + @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash')) + @Column + infoHash: string + + @ForeignKey(() => VideoModel) + @Column + videoId: number + + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', allowNull: false }, onDelete: 'CASCADE' }) + Video: VideoModel } - -// ------------------------------ 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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' -import { ResultList } from '../../../shared/models/result-list.model' -import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from '../../../shared/models/videos/video.model' -import { AccountVideoRateInstance } from '../account/account-video-rate-interface' - -import { TagAttributes, TagInstance } from './tag-interface' -import { VideoChannelInstance } from './video-channel-interface' -import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' -import { VideoShareInstance } from './video-share-interface' - -export namespace VideoMethods { - export type GetThumbnailName = (this: VideoInstance) => string - export type GetPreviewName = (this: VideoInstance) => string - export type IsOwned = (this: VideoInstance) => boolean - export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo - export type ToFormattedDetailsJSON = (this: VideoInstance) => FormattedDetailsVideo - - export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance - export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string - export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string - export type CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise - export type CreateThumbnail = (this: VideoInstance, videoFile: VideoFileInstance) => Promise - export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string - export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise - - export type ToActivityPubObject = (this: VideoInstance) => VideoTorrentObject - - export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise - export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise - export type GetOriginalFileHeight = (this: VideoInstance) => Promise - export type GetEmbedPath = (this: VideoInstance) => string - export type GetThumbnailPath = (this: VideoInstance) => string - export type GetPreviewPath = (this: VideoInstance) => string - export type GetDescriptionPath = (this: VideoInstance) => string - export type GetTruncatedDescription = (this: VideoInstance) => string - export type GetCategoryLabel = (this: VideoInstance) => string - export type GetLicenceLabel = (this: VideoInstance) => string - export type GetLanguageLabel = (this: VideoInstance) => string - - export type List = () => Bluebird - - export type ListAllAndSharedByAccountForOutbox = ( - accountId: number, - start: number, - count: number - ) => Bluebird< ResultList > - export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList > - export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList > - export type SearchAndPopulateAccountAndServerAndTags = ( - value: string, - start: number, - count: number, - sort: string - ) => Bluebird< ResultList > - - export type Load = (id: number) => Bluebird - export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird - export type LoadByUrlAndPopulateAccount = (url: string, t?: Sequelize.Transaction) => Bluebird - export type LoadAndPopulateAccountAndServerAndTags = (id: number) => Bluebird - export type LoadByUUIDAndPopulateAccountAndServerAndTags = (uuid: string) => Bluebird - export type LoadByUUIDOrURL = (uuid: string, url: string, t?: Sequelize.Transaction) => Bluebird - - export type RemoveThumbnail = (this: VideoInstance) => Promise - export type RemovePreview = (this: VideoInstance) => Promise - export type RemoveFile = (this: VideoInstance, videoFile: VideoFileInstance) => Promise - export type RemoveTorrent = (this: VideoInstance, videoFile: VideoFileInstance) => Promise -} - -export interface VideoClass { - list: VideoMethods.List - listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox - listForApi: VideoMethods.ListForApi - listUserVideosForApi: VideoMethods.ListUserVideosForApi - load: VideoMethods.Load - loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags - loadByUUID: VideoMethods.LoadByUUID - loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount - loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL - loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags - searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags -} - -export interface VideoAttributes { - id?: number - uuid?: string - name: string - category: number - licence: number - language: number - nsfw: boolean - description: string - duration: number - privacy: number - views?: number - likes?: number - dislikes?: number - remote: boolean - url?: string - - createdAt?: Date - updatedAt?: Date - - parentId?: number - channelId?: number - - VideoChannel?: VideoChannelInstance - Tags?: TagInstance[] - VideoFiles?: VideoFileInstance[] - VideoShares?: VideoShareInstance[] - AccountVideoRates?: AccountVideoRateInstance[] -} - -export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance { - createPreview: VideoMethods.CreatePreview - createThumbnail: VideoMethods.CreateThumbnail - createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash - getOriginalFile: VideoMethods.GetOriginalFile - getPreviewName: VideoMethods.GetPreviewName - getPreviewPath: VideoMethods.GetPreviewPath - getThumbnailName: VideoMethods.GetThumbnailName - getThumbnailPath: VideoMethods.GetThumbnailPath - getTorrentFileName: VideoMethods.GetTorrentFileName - getVideoFilename: VideoMethods.GetVideoFilename - getVideoFilePath: VideoMethods.GetVideoFilePath - isOwned: VideoMethods.IsOwned - removeFile: VideoMethods.RemoveFile - removePreview: VideoMethods.RemovePreview - removeThumbnail: VideoMethods.RemoveThumbnail - removeTorrent: VideoMethods.RemoveTorrent - toActivityPubObject: VideoMethods.ToActivityPubObject - toFormattedJSON: VideoMethods.ToFormattedJSON - toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON - optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile - transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile - getOriginalFileHeight: VideoMethods.GetOriginalFileHeight - getEmbedPath: VideoMethods.GetEmbedPath - getDescriptionPath: VideoMethods.GetDescriptionPath - getTruncatedDescription: VideoMethods.GetTruncatedDescription - getCategoryLabel: VideoMethods.GetCategoryLabel - getLicenceLabel: VideoMethods.GetLicenceLabel - getLanguageLabel: VideoMethods.GetLanguageLabel - - setTags: Sequelize.HasManySetAssociationsMixin - addVideoFile: Sequelize.HasManyAddAssociationMixin - setVideoFiles: Sequelize.HasManySetAssociationsMixin -} - -export interface VideoModel extends VideoClass, Sequelize.Model {} 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 @@ -import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' -import { AccountInstance } from '../account/account-interface' -import { VideoInstance } from './video-interface' - -export namespace VideoShareMethods { - export type LoadAccountsByShare = (videoId: number, t: Sequelize.Transaction) => Bluebird - export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird -} - -export interface VideoShareClass { - loadAccountsByShare: VideoShareMethods.LoadAccountsByShare - load: VideoShareMethods.Load -} - -export interface VideoShareAttributes { - accountId: number - videoId: number -} - -export interface VideoShareInstance extends VideoShareClass, VideoShareAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date - - Account?: AccountInstance - Video?: VideoInstance -} - -export interface VideoShareModel extends VideoShareClass, Sequelize.Model {} 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 @@ import * as Sequelize from 'sequelize' +import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { AccountModel } from '../account/account' +import { VideoModel } from './video' -import { addMethodsToModel } from '../utils' -import { VideoShareAttributes, VideoShareInstance, VideoShareMethods } from './video-share-interface' - -let VideoShare: Sequelize.Model -let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare -let load: VideoShareMethods.Load - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoShare = sequelize.define('VideoShare', - { }, +@Table({ + tableName: 'videoShare', + indexes: [ { - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'videoId' ] - } - ] + fields: [ 'accountId' ] + }, + { + fields: [ 'videoId' ] } - ) - - const classMethods = [ - associate, - loadAccountsByShare, - load ] - addMethodsToModel(VideoShare, classMethods) +}) +export class VideoShareModel extends Model { + @CreatedAt + createdAt: Date - return VideoShare -} + @UpdatedAt + updatedAt: Date -// ------------------------------ METHODS ------------------------------ + @ForeignKey(() => AccountModel) + @Column + accountId: number -function associate (models) { - VideoShare.belongsTo(models.Account, { + @BelongsTo(() => AccountModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'cascade' }) + Account: AccountModel - VideoShare.belongsTo(models.Video, { + @ForeignKey(() => VideoModel) + @Column + videoId: number + + @BelongsTo(() => VideoModel, { foreignKey: { - name: 'videoId', - allowNull: true + allowNull: false }, onDelete: 'cascade' }) -} - -load = function (accountId: number, videoId: number, t: Sequelize.Transaction) { - return VideoShare.findOne({ - where: { - accountId, - videoId - }, - include: [ - VideoShare['sequelize'].models.Account - ], - transaction: t - }) -} + Video: VideoModel -loadAccountsByShare = function (videoId: number, t: Sequelize.Transaction) { - const query = { - where: { - videoId - }, - include: [ - { - model: VideoShare['sequelize'].models.Account, - required: true - } - ], - transaction: t + static load (accountId: number, videoId: number, t: Sequelize.Transaction) { + return VideoShareModel.findOne({ + where: { + accountId, + videoId + }, + include: [ + AccountModel + ], + transaction: t + }) } - return VideoShare.findAll(query) - .then(res => res.map(r => r.Account)) + static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) { + const query = { + where: { + videoId + }, + include: [ + { + model: AccountModel, + required: true + } + ], + transaction: t + } + + return VideoShareModel.findAll(query) + .then(res => res.map(r => r.Account)) + } } 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 @@ -import * as Sequelize from 'sequelize' - -export namespace VideoTagMethods { -} - -export interface VideoTagClass { -} - -export interface VideoTagAttributes { -} - -export interface VideoTagInstance extends VideoTagClass, VideoTagAttributes, Sequelize.Instance { - id: number - createdAt: Date - updatedAt: Date -} - -export interface VideoTagModel extends VideoTagClass, Sequelize.Model {} 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 @@ -import * as Sequelize from 'sequelize' +import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { TagModel } from './tag' +import { VideoModel } from './video' -import { - VideoTagInstance, - VideoTagAttributes -} from './video-tag-interface' +@Table({ + tableName: 'videoTag', + indexes: [ + { + fields: [ 'videoId' ] + }, + { + fields: [ 'tagId' ] + } + ] +}) +export class VideoTagModel extends Model { + @CreatedAt + createdAt: Date -let VideoTag: Sequelize.Model + @UpdatedAt + updatedAt: Date -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - VideoTag = sequelize.define('VideoTag', {}, { - indexes: [ - { - fields: [ 'videoId' ] - }, - { - fields: [ 'tagId' ] - } - ] - }) + @ForeignKey(() => VideoModel) + @Column + videoId: number - return VideoTag + @ForeignKey(() => TagModel) + @Column + tagId: number } 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' import * as parseTorrent from 'parse-torrent' import { join } from 'path' import * as Sequelize from 'sequelize' +import { + AfterDestroy, + AllowNull, + BelongsTo, + BelongsToMany, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + IFindOptions, + Is, + IsInt, + IsUUID, + Min, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' +import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' import { VideoPrivacy, VideoResolution } from '../../../shared' -import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' -import { activityPubCollection } from '../../helpers/activitypub' -import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeFilePromise } from '../../helpers/core-utils' -import { isVideoCategoryValid, isVideoLanguageValid, isVideoPrivacyValid } from '../../helpers/custom-validators/videos' -import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils' +import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' +import { + activityPubCollection, + createTorrentPromise, + generateImageFromVideoFile, + getVideoFileHeight, + logger, + renamePromise, + statPromise, + transcode, + unlinkPromise, + writeFilePromise +} from '../../helpers' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' import { - isActivityPubUrlValid, + isVideoCategoryValid, isVideoDescriptionValid, isVideoDurationValid, + isVideoLanguageValid, isVideoLicenceValid, isVideoNameValid, - isVideoNSFWValid -} from '../../helpers/index' -import { logger } from '../../helpers/logger' + isVideoNSFWValid, + isVideoPrivacyValid +} from '../../helpers/custom-validators/videos' import { API_VERSION, CONFIG, @@ -31,1169 +62,1025 @@ import { VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES -} from '../../initializers/constants' -import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' +} from '../../initializers' +import { getAnnounceActivityPubUrl } from '../../lib/activitypub' import { sendDeleteVideo } from '../../lib/index' -import { addMethodsToModel, getSort } from '../utils' -import { TagInstance } from './tag-interface' -import { VideoFileInstance, VideoFileModel } from './video-file-interface' -import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface' - -let Video: Sequelize.Model -let getOriginalFile: VideoMethods.GetOriginalFile -let getVideoFilename: VideoMethods.GetVideoFilename -let getThumbnailName: VideoMethods.GetThumbnailName -let getThumbnailPath: VideoMethods.GetThumbnailPath -let getPreviewName: VideoMethods.GetPreviewName -let getPreviewPath: VideoMethods.GetPreviewPath -let getTorrentFileName: VideoMethods.GetTorrentFileName -let isOwned: VideoMethods.IsOwned -let toFormattedJSON: VideoMethods.ToFormattedJSON -let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON -let toActivityPubObject: VideoMethods.ToActivityPubObject -let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile -let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile -let createPreview: VideoMethods.CreatePreview -let createThumbnail: VideoMethods.CreateThumbnail -let getVideoFilePath: VideoMethods.GetVideoFilePath -let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash -let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight -let getEmbedPath: VideoMethods.GetEmbedPath -let getDescriptionPath: VideoMethods.GetDescriptionPath -let getTruncatedDescription: VideoMethods.GetTruncatedDescription -let getCategoryLabel: VideoMethods.GetCategoryLabel -let getLicenceLabel: VideoMethods.GetLicenceLabel -let getLanguageLabel: VideoMethods.GetLanguageLabel - -let list: VideoMethods.List -let listForApi: VideoMethods.ListForApi -let listAllAndSharedByAccountForOutbox: VideoMethods.ListAllAndSharedByAccountForOutbox -let listUserVideosForApi: VideoMethods.ListUserVideosForApi -let load: VideoMethods.Load -let loadByUrlAndPopulateAccount: VideoMethods.LoadByUrlAndPopulateAccount -let loadByUUID: VideoMethods.LoadByUUID -let loadByUUIDOrURL: VideoMethods.LoadByUUIDOrURL -let loadAndPopulateAccountAndServerAndTags: VideoMethods.LoadAndPopulateAccountAndServerAndTags -let loadByUUIDAndPopulateAccountAndServerAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndServerAndTags -let searchAndPopulateAccountAndServerAndTags: VideoMethods.SearchAndPopulateAccountAndServerAndTags -let removeThumbnail: VideoMethods.RemoveThumbnail -let removePreview: VideoMethods.RemovePreview -let removeFile: VideoMethods.RemoveFile -let removeTorrent: VideoMethods.RemoveTorrent - -export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Video = sequelize.define('Video', +import { AccountModel } from '../account/account' +import { AccountVideoRateModel } from '../account/account-video-rate' +import { ServerModel } from '../server/server' +import { getSort, throwIfNotValid } from '../utils' +import { TagModel } from './tag' +import { VideoAbuseModel } from './video-abuse' +import { VideoChannelModel } from './video-channel' +import { VideoFileModel } from './video-file' +import { VideoShareModel } from './video-share' +import { VideoTagModel } from './video-tag' + +@Table({ + tableName: 'video', + indexes: [ { - uuid: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - allowNull: false, - validate: { - isUUID: 4 - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - nameValid: value => { - const res = isVideoNameValid(value) - if (res === false) throw new Error('Video name is not valid.') - } - } - }, - category: { - type: DataTypes.INTEGER, - allowNull: true, - defaultValue: null, - validate: { - categoryValid: value => { - const res = isVideoCategoryValid(value) - if (res === false) throw new Error('Video category is not valid.') - } - } - }, - licence: { - type: DataTypes.INTEGER, - allowNull: true, - defaultValue: null, - validate: { - licenceValid: value => { - const res = isVideoLicenceValid(value) - if (res === false) throw new Error('Video licence is not valid.') - } - } - }, - language: { - type: DataTypes.INTEGER, - allowNull: true, - defaultValue: null, - validate: { - languageValid: value => { - const res = isVideoLanguageValid(value) - if (res === false) throw new Error('Video language is not valid.') - } - } - }, - privacy: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - privacyValid: value => { - const res = isVideoPrivacyValid(value) - if (res === false) throw new Error('Video privacy is not valid.') - } - } - }, - nsfw: { - type: DataTypes.BOOLEAN, - allowNull: false, - validate: { - nsfwValid: value => { - const res = isVideoNSFWValid(value) - if (res === false) throw new Error('Video nsfw attribute is not valid.') - } - } - }, - description: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), - allowNull: true, - defaultValue: null, - validate: { - descriptionValid: value => { - const res = isVideoDescriptionValid(value) - if (res === false) throw new Error('Video description is not valid.') - } - } - }, - duration: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - durationValid: value => { - const res = isVideoDurationValid(value) - if (res === false) throw new Error('Video duration is not valid.') - } - } - }, - views: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - validate: { - min: 0, - isInt: true - } - }, - likes: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - validate: { - min: 0, - isInt: true - } - }, - dislikes: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - validate: { - min: 0, - isInt: true - } - }, - remote: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false - }, - url: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max), - allowNull: false, - validate: { - urlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Video URL is not valid.') - } - } - } + fields: [ 'name' ] }, { - indexes: [ - { - fields: [ 'name' ] - }, - { - fields: [ 'createdAt' ] - }, - { - fields: [ 'duration' ] - }, - { - fields: [ 'views' ] - }, - { - fields: [ 'likes' ] - }, - { - fields: [ 'uuid' ] - }, - { - fields: [ 'channelId' ] - } - ], - hooks: { - afterDestroy - } + fields: [ 'createdAt' ] + }, + { + fields: [ 'duration' ] + }, + { + fields: [ 'views' ] + }, + { + fields: [ 'likes' ] + }, + { + fields: [ 'uuid' ] + }, + { + fields: [ 'channelId' ] } - ) - - const classMethods = [ - associate, - - list, - listAllAndSharedByAccountForOutbox, - listForApi, - listUserVideosForApi, - load, - loadByUrlAndPopulateAccount, - loadAndPopulateAccountAndServerAndTags, - loadByUUIDOrURL, - loadByUUID, - loadByUUIDAndPopulateAccountAndServerAndTags, - searchAndPopulateAccountAndServerAndTags - ] - const instanceMethods = [ - createPreview, - createThumbnail, - createTorrentAndSetInfoHash, - getPreviewName, - getPreviewPath, - getThumbnailName, - getThumbnailPath, - getTorrentFileName, - getVideoFilename, - getVideoFilePath, - getOriginalFile, - isOwned, - removeFile, - removePreview, - removeThumbnail, - removeTorrent, - toActivityPubObject, - toFormattedJSON, - toFormattedDetailsJSON, - optimizeOriginalVideofile, - transcodeOriginalVideofile, - getOriginalFileHeight, - getEmbedPath, - getTruncatedDescription, - getDescriptionPath, - getCategoryLabel, - getLicenceLabel, - getLanguageLabel ] - addMethodsToModel(Video, classMethods, instanceMethods) - - return Video -} - -// ------------------------------ METHODS ------------------------------ - -function associate (models) { - Video.belongsTo(models.VideoChannel, { +}) +export class VideoModel extends Model { + + @AllowNull(false) + @Default(DataType.UUIDV4) + @IsUUID(4) + @Column(DataType.UUID) + uuid: string + + @AllowNull(false) + @Is('VideoName', value => throwIfNotValid(value, isVideoNameValid, 'name')) + @Column + name: string + + @AllowNull(true) + @Default(null) + @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category')) + @Column + category: number + + @AllowNull(true) + @Default(null) + @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence')) + @Column + licence: number + + @AllowNull(true) + @Default(null) + @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language')) + @Column + language: number + + @AllowNull(false) + @Is('VideoPrivacy', value => throwIfNotValid(value, isVideoPrivacyValid, 'privacy')) + @Column + privacy: number + + @AllowNull(false) + @Is('VideoNSFW', value => throwIfNotValid(value, isVideoNSFWValid, 'NSFW boolean')) + @Column + nsfw: boolean + + @AllowNull(true) + @Default(null) + @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max)) + description: string + + @AllowNull(false) + @Is('VideoDuration', value => throwIfNotValid(value, isVideoDurationValid, 'duration')) + @Column + duration: number + + @AllowNull(false) + @Default(0) + @IsInt + @Min(0) + @Column + views: number + + @AllowNull(false) + @Default(0) + @IsInt + @Min(0) + @Column + likes: number + + @AllowNull(false) + @Default(0) + @IsInt + @Min(0) + @Column + dislikes: number + + @AllowNull(false) + @Column + remote: boolean + + @AllowNull(false) + @Is('VideoUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) + url: string + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => VideoChannelModel) + @Column + channelId: number + + @BelongsTo(() => VideoChannelModel, { foreignKey: { - name: 'channelId', allowNull: false }, onDelete: 'cascade' }) + VideoChannel: VideoChannelModel - Video.belongsToMany(models.Tag, { + @BelongsToMany(() => TagModel, { foreignKey: 'videoId', - through: models.VideoTag, - onDelete: 'cascade' + through: () => VideoTagModel, + onDelete: 'CASCADE' }) + Tags: TagModel[] - Video.hasMany(models.VideoAbuse, { + @HasMany(() => VideoAbuseModel, { foreignKey: { name: 'videoId', allowNull: false }, onDelete: 'cascade' }) + VideoAbuses: VideoAbuseModel[] - Video.hasMany(models.VideoFile, { + @HasMany(() => VideoFileModel, { foreignKey: { name: 'videoId', allowNull: false }, onDelete: 'cascade' }) + VideoFiles: VideoFileModel[] - Video.hasMany(models.VideoShare, { + @HasMany(() => VideoShareModel, { foreignKey: { name: 'videoId', allowNull: false }, onDelete: 'cascade' }) + VideoShares: VideoShareModel[] - Video.hasMany(models.AccountVideoRate, { + @HasMany(() => AccountVideoRateModel, { foreignKey: { name: 'videoId', allowNull: false }, onDelete: 'cascade' }) -} - -function afterDestroy (video: VideoInstance) { - const tasks = [] + AccountVideoRates: AccountVideoRateModel[] - tasks.push( - video.removeThumbnail() - ) + @AfterDestroy + static removeFilesAndSendDelete (instance: VideoModel) { + const tasks = [] - if (video.isOwned()) { tasks.push( - video.removePreview(), - sendDeleteVideo(video, undefined) + instance.removeThumbnail() ) - // Remove physical files and torrents - video.VideoFiles.forEach(file => { - tasks.push(video.removeFile(file)) - tasks.push(video.removeTorrent(file)) - }) - } - - return Promise.all(tasks) - .catch(err => { - logger.error('Some errors when removing files of video %s in after destroy hook.', video.uuid, err) - }) -} - -getOriginalFile = function (this: VideoInstance) { - if (Array.isArray(this.VideoFiles) === false) return undefined + if (instance.isOwned()) { + tasks.push( + instance.removePreview(), + sendDeleteVideo(instance, undefined) + ) - // The original file is the file that have the higher resolution - return maxBy(this.VideoFiles, file => file.resolution) -} + // Remove physical files and torrents + instance.VideoFiles.forEach(file => { + tasks.push(instance.removeFile(file)) + tasks.push(instance.removeTorrent(file)) + }) + } -getVideoFilename = function (this: VideoInstance, videoFile: VideoFileInstance) { - return this.uuid + '-' + videoFile.resolution + videoFile.extname -} + return Promise.all(tasks) + .catch(err => { + logger.error('Some errors when removing files of video %s in after destroy hook.', instance.uuid, err) + }) + } -getThumbnailName = function (this: VideoInstance) { - // We always have a copy of the thumbnail - const extension = '.jpg' - return this.uuid + extension -} + static list () { + const query = { + include: [ VideoFileModel ] + } -getPreviewName = function (this: VideoInstance) { - const extension = '.jpg' - return this.uuid + extension -} + return VideoModel.findAll(query) + } -getTorrentFileName = function (this: VideoInstance, videoFile: VideoFileInstance) { - const extension = '.torrent' - return this.uuid + '-' + videoFile.resolution + extension -} + static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { + function getRawQuery (select: string) { + const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + + 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + + 'WHERE "VideoChannel"."accountId" = ' + accountId + const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' + + 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + + 'WHERE "VideoShare"."accountId" = ' + accountId -isOwned = function (this: VideoInstance) { - return this.remote === false -} + return `(${queryVideo}) UNION (${queryVideoShare})` + } -createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { - const imageSize = PREVIEWS_SIZE.width + 'x' + PREVIEWS_SIZE.height + const rawQuery = getRawQuery('"Video"."id"') + const rawCountQuery = getRawQuery('COUNT("Video"."id") as "total"') + + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort('createdAt'), [ 'Tags', 'name', 'ASC' ] ], + where: { + id: { + [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')') + } + }, + include: [ + { + model: VideoShareModel, + required: false, + where: { + [Sequelize.Op.and]: [ + { + id: { + [Sequelize.Op.not]: null + } + }, + { + accountId + } + ] + }, + include: [ AccountModel ] + }, + { + model: VideoChannelModel, + required: true, + include: [ + { + model: AccountModel, + required: true + } + ] + }, + { + model: AccountVideoRateModel, + include: [ AccountModel ] + }, + VideoFileModel, + TagModel + ] + } - return generateImageFromVideoFile( - this.getVideoFilePath(videoFile), - CONFIG.STORAGE.PREVIEWS_DIR, - this.getPreviewName(), - imageSize - ) -} + return Bluebird.all([ + // FIXME: typing issue + VideoModel.findAll(query as any), + VideoModel.sequelize.query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT }) + ]).then(([ rows, totals ]) => { + // totals: totalVideos + totalVideoShares + let totalVideos = 0 + let totalVideoShares = 0 + if (totals[0]) totalVideos = parseInt(totals[0].total, 10) + if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) + + const total = totalVideos + totalVideoShares + return { + data: rows, + total: total + } + }) + } -createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { - const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height + static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], + include: [ + { + model: VideoChannelModel, + required: true, + include: [ + { + model: AccountModel, + where: { + userId + }, + required: true + } + ] + }, + TagModel + ] + } - return generateImageFromVideoFile( - this.getVideoFilePath(videoFile), - CONFIG.STORAGE.THUMBNAILS_DIR, - this.getThumbnailName(), - imageSize - ) -} + return VideoModel.findAndCountAll(query).then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) + } -getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) { - return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) -} + static listForApi (start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], + include: [ + { + model: VideoChannelModel, + required: true, + include: [ + { + model: AccountModel, + required: true, + include: [ + { + model: ServerModel, + required: false + } + ] + } + ] + }, + TagModel + ], + where: this.createBaseVideosWhere() + } -createTorrentAndSetInfoHash = async function (this: VideoInstance, videoFile: VideoFileInstance) { - const options = { - announceList: [ - [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] - ], - urlList: [ - CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) - ] + return VideoModel.findAndCountAll(query).then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) } - const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) + static load (id: number) { + return VideoModel.findById(id) + } - const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) - logger.info('Creating torrent %s.', filePath) + static loadByUUID (uuid: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + uuid + }, + include: [ VideoFileModel ] + } - await writeFilePromise(filePath, torrent) + if (t !== undefined) query.transaction = t - const parsedTorrent = parseTorrent(torrent) - videoFile.infoHash = parsedTorrent.infoHash -} + return VideoModel.findOne(query) + } -getEmbedPath = function (this: VideoInstance) { - return '/videos/embed/' + this.uuid -} + static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + url + }, + include: [ + VideoFileModel, + { + model: VideoChannelModel, + include: [ AccountModel ] + } + ] + } -getThumbnailPath = function (this: VideoInstance) { - return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) -} + if (t !== undefined) query.transaction = t -getPreviewPath = function (this: VideoInstance) { - return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) -} + return VideoModel.findOne(query) + } -toFormattedJSON = function (this: VideoInstance) { - let serverHost + static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) { + const query: IFindOptions = { + where: { + [Sequelize.Op.or]: [ + { uuid }, + { url } + ] + }, + include: [ VideoFileModel ] + } - if (this.VideoChannel.Account.Server) { - serverHost = this.VideoChannel.Account.Server.host - } else { - // It means it's our video - serverHost = CONFIG.WEBSERVER.HOST - } + if (t !== undefined) query.transaction = t - const json = { - id: this.id, - uuid: this.uuid, - name: this.name, - category: this.category, - categoryLabel: this.getCategoryLabel(), - licence: this.licence, - licenceLabel: this.getLicenceLabel(), - language: this.language, - languageLabel: this.getLanguageLabel(), - nsfw: this.nsfw, - description: this.getTruncatedDescription(), - serverHost, - isLocal: this.isOwned(), - accountName: this.VideoChannel.Account.name, - duration: this.duration, - views: this.views, - likes: this.likes, - dislikes: this.dislikes, - tags: map(this.Tags, 'name'), - thumbnailPath: this.getThumbnailPath(), - previewPath: this.getPreviewPath(), - embedPath: this.getEmbedPath(), - createdAt: this.createdAt, - updatedAt: this.updatedAt + return VideoModel.findOne(query) } - return json -} + static loadAndPopulateAccountAndServerAndTags (id: number) { + const options = { + order: [ [ 'Tags', 'name', 'ASC' ] ], + include: [ + { + model: VideoChannelModel, + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + } + ] + }, + { + model: AccountVideoRateModel, + include: [ AccountModel ] + }, + { + model: VideoShareModel, + include: [ AccountModel ] + }, + TagModel, + VideoFileModel + ] + } -toFormattedDetailsJSON = function (this: VideoInstance) { - const formattedJson = this.toFormattedJSON() + return VideoModel.findById(id, options) + } - // Maybe our server is not up to date and there are new privacy settings since our version - let privacyLabel = VIDEO_PRIVACIES[this.privacy] - if (!privacyLabel) privacyLabel = 'Unknown' + static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) { + const options = { + order: [ [ 'Tags', 'name', 'ASC' ] ], + where: { + uuid + }, + include: [ + { + model: VideoChannelModel, + include: [ + { + model: AccountModel, + include: [ { model: ServerModel, required: false } ] + } + ] + }, + { + model: AccountVideoRateModel, + include: [ AccountModel ] + }, + { + model: VideoShareModel, + include: [ AccountModel ] + }, + TagModel, + VideoFileModel + ] + } - const detailsJson = { - privacyLabel, - privacy: this.privacy, - descriptionPath: this.getDescriptionPath(), - channel: this.VideoChannel.toFormattedJSON(), - account: this.VideoChannel.Account.toFormattedJSON(), - files: [] + return VideoModel.findOne(options) } - // Format and sort video files - const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) - detailsJson.files = this.VideoFiles - .map(videoFile => { - let resolutionLabel = videoFile.resolution + 'p' - - const videoFileJson = { - resolution: videoFile.resolution, - resolutionLabel, - magnetUri: generateMagnetUri(this, videoFile, baseUrlHttp, baseUrlWs), - size: videoFile.size, - torrentUrl: getTorrentUrl(this, videoFile, baseUrlHttp), - fileUrl: getVideoFileUrl(this, videoFile, baseUrlHttp) - } - - return videoFileJson - }) - .sort((a, b) => { - if (a.resolution < b.resolution) return 1 - if (a.resolution === b.resolution) return 0 - return -1 - }) - - return Object.assign(formattedJson, detailsJson) -} - -toActivityPubObject = function (this: VideoInstance) { - const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) - if (!this.Tags) this.Tags = [] + static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) { + const serverInclude: IIncludeOptions = { + model: ServerModel, + required: false + } - const tag = this.Tags.map(t => ({ - type: 'Hashtag' as 'Hashtag', - name: t.name - })) + const accountInclude: IIncludeOptions = { + model: AccountModel, + include: [ serverInclude ] + } - let language - if (this.language) { - language = { - identifier: this.language + '', - name: this.getLanguageLabel() + const videoChannelInclude: IIncludeOptions = { + model: VideoChannelModel, + include: [ accountInclude ], + required: true } - } - let category - if (this.category) { - category = { - identifier: this.category + '', - name: this.getCategoryLabel() + const tagInclude: IIncludeOptions = { + model: TagModel } - } - let licence - if (this.licence) { - licence = { - identifier: this.licence + '', - name: this.getLicenceLabel() + const query: IFindOptions = { + distinct: true, + where: this.createBaseVideosWhere(), + offset: start, + limit: count, + order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ] } - } - let likesObject - let dislikesObject + // TODO: search on tags too + // const escapedValue = Video['sequelize'].escape('%' + value + '%') + // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( + // `(SELECT "VideoTags"."videoId" + // FROM "Tags" + // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" + // WHERE name ILIKE ${escapedValue} + // )` + // ) + + // TODO: search on account too + // accountInclude.where = { + // name: { + // [Sequelize.Op.iLike]: '%' + value + '%' + // } + // } + query.where['name'] = { + [Sequelize.Op.iLike]: '%' + value + '%' + } - if (Array.isArray(this.AccountVideoRates)) { - const likes: string[] = [] - const dislikes: string[] = [] + query.include = [ + videoChannelInclude, tagInclude + ] - for (const rate of this.AccountVideoRates) { - if (rate.type === 'like') { - likes.push(rate.Account.url) - } else if (rate.type === 'dislike') { - dislikes.push(rate.Account.url) + return VideoModel.findAndCountAll(query).then(({ rows, count }) => { + return { + data: rows, + total: count } - } - - likesObject = activityPubCollection(likes) - dislikesObject = activityPubCollection(dislikes) + }) } - let sharesObject - if (Array.isArray(this.VideoShares)) { - const shares: string[] = [] - - for (const videoShare of this.VideoShares) { - const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) - shares.push(shareUrl) + private static createBaseVideosWhere () { + return { + id: { + [Sequelize.Op.notIn]: VideoModel.sequelize.literal( + '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' + ) + }, + privacy: VideoPrivacy.PUBLIC } - - sharesObject = activityPubCollection(shares) } - const url = [] - for (const file of this.VideoFiles) { - url.push({ - type: 'Link', - mimeType: 'video/' + file.extname.replace('.', ''), - url: getVideoFileUrl(this, file, baseUrlHttp), - width: file.resolution, - size: file.size - }) + getOriginalFile () { + if (Array.isArray(this.VideoFiles) === false) return undefined - url.push({ - type: 'Link', - mimeType: 'application/x-bittorrent', - url: getTorrentUrl(this, file, baseUrlHttp), - width: file.resolution - }) - - url.push({ - type: 'Link', - mimeType: 'application/x-bittorrent;x-scheme-handler/magnet', - url: generateMagnetUri(this, file, baseUrlHttp, baseUrlWs), - width: file.resolution - }) + // The original file is the file that have the higher resolution + return maxBy(this.VideoFiles, file => file.resolution) } - // Add video url too - url.push({ - type: 'Link', - mimeType: 'text/html', - url: CONFIG.WEBSERVER.URL + '/videos/watch/' + this.uuid - }) + getVideoFilename (videoFile: VideoFileModel) { + return this.uuid + '-' + videoFile.resolution + videoFile.extname + } - const videoObject: VideoTorrentObject = { - type: 'Video' as 'Video', - id: this.url, - name: this.name, - // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration - duration: 'PT' + this.duration + 'S', - uuid: this.uuid, - tag, - category, - licence, - language, - views: this.views, - nsfw: this.nsfw, - published: this.createdAt.toISOString(), - updated: this.updatedAt.toISOString(), - mediaType: 'text/markdown', - content: this.getTruncatedDescription(), - icon: { - type: 'Image', - url: getThumbnailUrl(this, baseUrlHttp), - mediaType: 'image/jpeg', - width: THUMBNAILS_SIZE.width, - height: THUMBNAILS_SIZE.height - }, - url, - likes: likesObject, - dislikes: dislikesObject, - shares: sharesObject + getThumbnailName () { + // We always have a copy of the thumbnail + const extension = '.jpg' + return this.uuid + extension } - return videoObject -} + getPreviewName () { + const extension = '.jpg' + return this.uuid + extension + } -getTruncatedDescription = function (this: VideoInstance) { - if (!this.description) return null + getTorrentFileName (videoFile: VideoFileModel) { + const extension = '.torrent' + return this.uuid + '-' + videoFile.resolution + extension + } - const options = { - length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max + isOwned () { + return this.remote === false } - return truncate(this.description, options) -} + createPreview (videoFile: VideoFileModel) { + const imageSize = PREVIEWS_SIZE.width + 'x' + PREVIEWS_SIZE.height + + return generateImageFromVideoFile( + this.getVideoFilePath(videoFile), + CONFIG.STORAGE.PREVIEWS_DIR, + this.getPreviewName(), + imageSize + ) + } -optimizeOriginalVideofile = async function (this: VideoInstance) { - const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR - const newExtname = '.mp4' - const inputVideoFile = this.getOriginalFile() - const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) - const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) + createThumbnail (videoFile: VideoFileModel) { + const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height - const transcodeOptions = { - inputPath: videoInputPath, - outputPath: videoOutputPath + return generateImageFromVideoFile( + this.getVideoFilePath(videoFile), + CONFIG.STORAGE.THUMBNAILS_DIR, + this.getThumbnailName(), + imageSize + ) } - try { - // Could be very long! - await transcode(transcodeOptions) + getVideoFilePath (videoFile: VideoFileModel) { + return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) + } - await unlinkPromise(videoInputPath) + createTorrentAndSetInfoHash = async function (videoFile: VideoFileModel) { + const options = { + announceList: [ + [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] + ], + urlList: [ + CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) + ] + } - // Important to do this before getVideoFilename() to take in account the new file extension - inputVideoFile.set('extname', newExtname) + const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) - await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) - const stats = await statPromise(this.getVideoFilePath(inputVideoFile)) + const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) + logger.info('Creating torrent %s.', filePath) - inputVideoFile.set('size', stats.size) + await writeFilePromise(filePath, torrent) - await this.createTorrentAndSetInfoHash(inputVideoFile) - await inputVideoFile.save() + const parsedTorrent = parseTorrent(torrent) + videoFile.infoHash = parsedTorrent.infoHash + } - } catch (err) { - // Auto destruction... - this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) + getEmbedPath () { + return '/videos/embed/' + this.uuid + } - throw err + getThumbnailPath () { + return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) } -} -transcodeOriginalVideofile = async function (this: VideoInstance, resolution: VideoResolution) { - const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR - const extname = '.mp4' + getPreviewPath () { + return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) + } - // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed - const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile())) + toFormattedJSON () { + let serverHost - const newVideoFile = (Video['sequelize'].models.VideoFile as VideoFileModel).build({ - resolution, - extname, - size: 0, - videoId: this.id - }) - const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile)) + if (this.VideoChannel.Account.Server) { + serverHost = this.VideoChannel.Account.Server.host + } else { + // It means it's our video + serverHost = CONFIG.WEBSERVER.HOST + } - const transcodeOptions = { - inputPath: videoInputPath, - outputPath: videoOutputPath, - resolution + return { + id: this.id, + uuid: this.uuid, + name: this.name, + category: this.category, + categoryLabel: this.getCategoryLabel(), + licence: this.licence, + licenceLabel: this.getLicenceLabel(), + language: this.language, + languageLabel: this.getLanguageLabel(), + nsfw: this.nsfw, + description: this.getTruncatedDescription(), + serverHost, + isLocal: this.isOwned(), + accountName: this.VideoChannel.Account.name, + duration: this.duration, + views: this.views, + likes: this.likes, + dislikes: this.dislikes, + tags: map(this.Tags, 'name'), + thumbnailPath: this.getThumbnailPath(), + previewPath: this.getPreviewPath(), + embedPath: this.getEmbedPath(), + createdAt: this.createdAt, + updatedAt: this.updatedAt + } } - await transcode(transcodeOptions) + toFormattedDetailsJSON () { + const formattedJson = this.toFormattedJSON() - const stats = await statPromise(videoOutputPath) + // Maybe our server is not up to date and there are new privacy settings since our version + let privacyLabel = VIDEO_PRIVACIES[this.privacy] + if (!privacyLabel) privacyLabel = 'Unknown' - newVideoFile.set('size', stats.size) + const detailsJson = { + privacyLabel, + privacy: this.privacy, + descriptionPath: this.getDescriptionPath(), + channel: this.VideoChannel.toFormattedJSON(), + account: this.VideoChannel.Account.toFormattedJSON(), + files: [] + } - await this.createTorrentAndSetInfoHash(newVideoFile) + // Format and sort video files + const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() + detailsJson.files = this.VideoFiles + .map(videoFile => { + let resolutionLabel = videoFile.resolution + 'p' + + return { + resolution: videoFile.resolution, + resolutionLabel, + magnetUri: this.generateMagnetUri(videoFile, baseUrlHttp, baseUrlWs), + size: videoFile.size, + torrentUrl: this.getTorrentUrl(videoFile, baseUrlHttp), + fileUrl: this.getVideoFileUrl(videoFile, baseUrlHttp) + } + }) + .sort((a, b) => { + if (a.resolution < b.resolution) return 1 + if (a.resolution === b.resolution) return 0 + return -1 + }) + + return Object.assign(formattedJson, detailsJson) + } - await newVideoFile.save() + toActivityPubObject (): VideoTorrentObject { + const { baseUrlHttp, baseUrlWs } = this.getBaseUrls() + if (!this.Tags) this.Tags = [] - this.VideoFiles.push(newVideoFile) -} + const tag = this.Tags.map(t => ({ + type: 'Hashtag' as 'Hashtag', + name: t.name + })) -getOriginalFileHeight = function (this: VideoInstance) { - const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) + let language + if (this.language) { + language = { + identifier: this.language + '', + name: this.getLanguageLabel() + } + } - return getVideoFileHeight(originalFilePath) -} + let category + if (this.category) { + category = { + identifier: this.category + '', + name: this.getCategoryLabel() + } + } -getDescriptionPath = function (this: VideoInstance) { - return `/api/${API_VERSION}/videos/${this.uuid}/description` -} + let licence + if (this.licence) { + licence = { + identifier: this.licence + '', + name: this.getLicenceLabel() + } + } -getCategoryLabel = function (this: VideoInstance) { - let categoryLabel = VIDEO_CATEGORIES[this.category] - if (!categoryLabel) categoryLabel = 'Misc' + let likesObject + let dislikesObject - return categoryLabel -} + if (Array.isArray(this.AccountVideoRates)) { + const likes: string[] = [] + const dislikes: string[] = [] -getLicenceLabel = function (this: VideoInstance) { - let licenceLabel = VIDEO_LICENCES[this.licence] - if (!licenceLabel) licenceLabel = 'Unknown' + for (const rate of this.AccountVideoRates) { + if (rate.type === 'like') { + likes.push(rate.Account.url) + } else if (rate.type === 'dislike') { + dislikes.push(rate.Account.url) + } + } - return licenceLabel -} + likesObject = activityPubCollection(likes) + dislikesObject = activityPubCollection(dislikes) + } -getLanguageLabel = function (this: VideoInstance) { - let languageLabel = VIDEO_LANGUAGES[this.language] - if (!languageLabel) languageLabel = 'Unknown' + let sharesObject + if (Array.isArray(this.VideoShares)) { + const shares: string[] = [] - return languageLabel -} + for (const videoShare of this.VideoShares) { + const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) + shares.push(shareUrl) + } -removeThumbnail = function (this: VideoInstance) { - const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) - return unlinkPromise(thumbnailPath) -} + sharesObject = activityPubCollection(shares) + } -removePreview = function (this: VideoInstance) { - // Same name than video thumbnail - return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) -} + const url = [] + for (const file of this.VideoFiles) { + url.push({ + type: 'Link', + mimeType: 'video/' + file.extname.replace('.', ''), + url: this.getVideoFileUrl(file, baseUrlHttp), + width: file.resolution, + size: file.size + }) + + url.push({ + type: 'Link', + mimeType: 'application/x-bittorrent', + url: this.getTorrentUrl(file, baseUrlHttp), + width: file.resolution + }) + + url.push({ + type: 'Link', + mimeType: 'application/x-bittorrent;x-scheme-handler/magnet', + url: this.generateMagnetUri(file, baseUrlHttp, baseUrlWs), + width: file.resolution + }) + } -removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) { - const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) - return unlinkPromise(filePath) -} + // Add video url too + url.push({ + type: 'Link', + mimeType: 'text/html', + url: CONFIG.WEBSERVER.URL + '/videos/watch/' + this.uuid + }) -removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) { - const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) - return unlinkPromise(torrentPath) -} + return { + type: 'Video' as 'Video', + id: this.url, + name: this.name, + // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration + duration: 'PT' + this.duration + 'S', + uuid: this.uuid, + tag, + category, + licence, + language, + views: this.views, + nsfw: this.nsfw, + published: this.createdAt.toISOString(), + updated: this.updatedAt.toISOString(), + mediaType: 'text/markdown', + content: this.getTruncatedDescription(), + icon: { + type: 'Image', + url: this.getThumbnailUrl(baseUrlHttp), + mediaType: 'image/jpeg', + width: THUMBNAILS_SIZE.width, + height: THUMBNAILS_SIZE.height + }, + url, + likes: likesObject, + dislikes: dislikesObject, + shares: sharesObject + } + } + + getTruncatedDescription () { + if (!this.description) return null -// ------------------------------ STATICS ------------------------------ + const options = { + length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max + } -list = function () { - const query = { - include: [ Video['sequelize'].models.VideoFile ] + return truncate(this.description, options) } - return Video.findAll(query) -} + optimizeOriginalVideofile = async function () { + const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const newExtname = '.mp4' + const inputVideoFile = this.getOriginalFile() + const videoInputPath = join(videosDirectory, this.getVideoFilename(inputVideoFile)) + const videoOutputPath = join(videosDirectory, this.id + '-transcoded' + newExtname) -listAllAndSharedByAccountForOutbox = function (accountId: number, start: number, count: number) { - function getRawQuery (select: string) { - const queryVideo = 'SELECT ' + select + ' FROM "Videos" AS "Video" ' + - 'INNER JOIN "VideoChannels" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + - 'WHERE "VideoChannel"."accountId" = ' + accountId - const queryVideoShare = 'SELECT ' + select + ' FROM "VideoShares" AS "VideoShare" ' + - 'INNER JOIN "Videos" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + - 'WHERE "VideoShare"."accountId" = ' + accountId + const transcodeOptions = { + inputPath: videoInputPath, + outputPath: videoOutputPath + } - let rawQuery = `(${queryVideo}) UNION (${queryVideoShare})` + try { + // Could be very long! + await transcode(transcodeOptions) - return rawQuery - } + await unlinkPromise(videoInputPath) - const rawQuery = getRawQuery('"Video"."id"') - const rawCountQuery = getRawQuery('COUNT("Video"."id") as "total"') + // Important to do this before getVideoFilename() to take in account the new file extension + inputVideoFile.set('extname', newExtname) - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort('createdAt'), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], - where: { - id: { - [Sequelize.Op.in]: Sequelize.literal('(' + rawQuery + ')') - } - }, - include: [ - { - model: Video['sequelize'].models.VideoShare, - required: false, - where: { - [Sequelize.Op.and]: [ - { - id: { - [Sequelize.Op.not]: null - } - }, - { - accountId - } - ] - }, - include: [ Video['sequelize'].models.Account ] - }, - { - model: Video['sequelize'].models.VideoChannel, - required: true, - include: [ - { - model: Video['sequelize'].models.Account, - required: true - } - ] - }, - { - model: Video['sequelize'].models.AccountVideoRate, - include: [ Video['sequelize'].models.Account ] - }, - Video['sequelize'].models.VideoFile, - Video['sequelize'].models.Tag - ] - } + await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) + const stats = await statPromise(this.getVideoFilePath(inputVideoFile)) - return Bluebird.all([ - Video.findAll(query), - Video['sequelize'].query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT }) - ]).then(([ rows, totals ]) => { - // totals: totalVideos + totalVideoShares - let totalVideos = 0 - let totalVideoShares = 0 - if (totals[0]) totalVideos = parseInt(totals[0].total, 10) - if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) - - const total = totalVideos + totalVideoShares - return { - data: rows, - total: total - } - }) -} + inputVideoFile.set('size', stats.size) -listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], - include: [ - { - model: Video['sequelize'].models.VideoChannel, - required: true, - include: [ - { - model: Video['sequelize'].models.Account, - where: { - userId - }, - required: true - } - ] - }, - Video['sequelize'].models.Tag - ] - } + await this.createTorrentAndSetInfoHash(inputVideoFile) + await inputVideoFile.save() - return Video.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) -} + } catch (err) { + // Auto destruction... + this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) -listForApi = function (start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], - include: [ - { - model: Video['sequelize'].models.VideoChannel, - required: true, - include: [ - { - model: Video['sequelize'].models.Account, - required: true, - include: [ - { - model: Video['sequelize'].models.Server, - required: false - } - ] - } - ] - }, - Video['sequelize'].models.Tag - ], - where: createBaseVideosWhere() + throw err + } } - return Video.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) -} + transcodeOriginalVideofile = async function (resolution: VideoResolution) { + const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const extname = '.mp4' -load = function (id: number) { - return Video.findById(id) -} + // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed + const videoInputPath = join(videosDirectory, this.getVideoFilename(this.getOriginalFile())) -loadByUUID = function (uuid: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - uuid - }, - include: [ Video['sequelize'].models.VideoFile ] - } + const newVideoFile = new VideoFileModel({ + resolution, + extname, + size: 0, + videoId: this.id + }) + const videoOutputPath = join(videosDirectory, this.getVideoFilename(newVideoFile)) - if (t !== undefined) query.transaction = t + const transcodeOptions = { + inputPath: videoInputPath, + outputPath: videoOutputPath, + resolution + } - return Video.findOne(query) -} + await transcode(transcodeOptions) -loadByUrlAndPopulateAccount = function (url: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - url - }, - include: [ - Video['sequelize'].models.VideoFile, - { - model: Video['sequelize'].models.VideoChannel, - include: [ Video['sequelize'].models.Account ] - } - ] - } + const stats = await statPromise(videoOutputPath) - if (t !== undefined) query.transaction = t + newVideoFile.set('size', stats.size) - return Video.findOne(query) -} + await this.createTorrentAndSetInfoHash(newVideoFile) -loadByUUIDOrURL = function (uuid: string, url: string, t?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - [Sequelize.Op.or]: [ - { uuid }, - { url } - ] - }, - include: [ Video['sequelize'].models.VideoFile ] + await newVideoFile.save() + + this.VideoFiles.push(newVideoFile) } - if (t !== undefined) query.transaction = t + getOriginalFileHeight () { + const originalFilePath = this.getVideoFilePath(this.getOriginalFile()) - return Video.findOne(query) -} + return getVideoFileHeight(originalFilePath) + } -loadAndPopulateAccountAndServerAndTags = function (id: number) { - const options = { - order: [ [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], - include: [ - { - model: Video['sequelize'].models.VideoChannel, - include: [ - { - model: Video['sequelize'].models.Account, - include: [ { model: Video['sequelize'].models.Server, required: false } ] - } - ] - }, - { - model: Video['sequelize'].models.AccountVideoRate, - include: [ Video['sequelize'].models.Account ] - }, - { - model: Video['sequelize'].models.VideoShare, - include: [ Video['sequelize'].models.Account ] - }, - Video['sequelize'].models.Tag, - Video['sequelize'].models.VideoFile - ] + getDescriptionPath () { + return `/api/${API_VERSION}/videos/${this.uuid}/description` } - return Video.findById(id, options) -} + getCategoryLabel () { + let categoryLabel = VIDEO_CATEGORIES[this.category] + if (!categoryLabel) categoryLabel = 'Misc' -loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) { - const options = { - order: [ [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], - where: { - uuid - }, - include: [ - { - model: Video['sequelize'].models.VideoChannel, - include: [ - { - model: Video['sequelize'].models.Account, - include: [ { model: Video['sequelize'].models.Server, required: false } ] - } - ] - }, - { - model: Video['sequelize'].models.AccountVideoRate, - include: [ Video['sequelize'].models.Account ] - }, - { - model: Video['sequelize'].models.VideoShare, - include: [ Video['sequelize'].models.Account ] - }, - Video['sequelize'].models.Tag, - Video['sequelize'].models.VideoFile - ] + return categoryLabel } - return Video.findOne(options) -} + getLicenceLabel () { + let licenceLabel = VIDEO_LICENCES[this.licence] + if (!licenceLabel) licenceLabel = 'Unknown' -searchAndPopulateAccountAndServerAndTags = function (value: string, start: number, count: number, sort: string) { - const serverInclude: Sequelize.IncludeOptions = { - model: Video['sequelize'].models.Server, - required: false + return licenceLabel } - const accountInclude: Sequelize.IncludeOptions = { - model: Video['sequelize'].models.Account, - include: [ serverInclude ] + getLanguageLabel () { + let languageLabel = VIDEO_LANGUAGES[this.language] + if (!languageLabel) languageLabel = 'Unknown' + + return languageLabel } - const videoChannelInclude: Sequelize.IncludeOptions = { - model: Video['sequelize'].models.VideoChannel, - include: [ accountInclude ], - required: true + removeThumbnail () { + const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) + return unlinkPromise(thumbnailPath) } - const tagInclude: Sequelize.IncludeOptions = { - model: Video['sequelize'].models.Tag + removePreview () { + // Same name than video thumbnail + return unlinkPromise(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) } - const query: Sequelize.FindOptions = { - distinct: true, - where: createBaseVideosWhere(), - offset: start, - limit: count, - order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] + removeFile (videoFile: VideoFileModel) { + const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) + return unlinkPromise(filePath) } - // TODO: search on tags too - // const escapedValue = Video['sequelize'].escape('%' + value + '%') - // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( - // `(SELECT "VideoTags"."videoId" - // FROM "Tags" - // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" - // WHERE name ILIKE ${escapedValue} - // )` - // ) - - // TODO: search on account too - // accountInclude.where = { - // name: { - // [Sequelize.Op.iLike]: '%' + value + '%' - // } - // } - query.where['name'] = { - [Sequelize.Op.iLike]: '%' + value + '%' + removeTorrent (videoFile: VideoFileModel) { + const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) + return unlinkPromise(torrentPath) } - query.include = [ - videoChannelInclude, tagInclude - ] + private getBaseUrls () { + let baseUrlHttp + let baseUrlWs - return Video.findAndCountAll(query).then(({ rows, count }) => { - return { - data: rows, - total: count + if (this.isOwned()) { + baseUrlHttp = CONFIG.WEBSERVER.URL + baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + } else { + baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host + baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host } - }) -} - -// --------------------------------------------------------------------------- -function createBaseVideosWhere () { - return { - id: { - [Sequelize.Op.notIn]: Video['sequelize'].literal( - '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")' - ) - }, - privacy: VideoPrivacy.PUBLIC + return { baseUrlHttp, baseUrlWs } } -} -function getBaseUrls (video: VideoInstance) { - let baseUrlHttp - let baseUrlWs - - if (video.isOwned()) { - baseUrlHttp = CONFIG.WEBSERVER.URL - baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT - } else { - baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Account.Server.host - baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Account.Server.host + private getThumbnailUrl (baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() } - return { baseUrlHttp, baseUrlWs } -} - -function getThumbnailUrl (video: VideoInstance, baseUrlHttp: string) { - return baseUrlHttp + STATIC_PATHS.THUMBNAILS + video.getThumbnailName() -} + private getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) + } -function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { - return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile) -} + private getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) + } -function getVideoFileUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { - return baseUrlHttp + STATIC_PATHS.WEBSEED + video.getVideoFilename(videoFile) -} + private generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { + const xs = this.getTorrentUrl(videoFile, baseUrlHttp) + const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] + const urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] + + const magnetHash = { + xs, + announce, + urlList, + infoHash: videoFile.infoHash, + name: this.name + } -function generateMagnetUri (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string, baseUrlWs: string) { - const xs = getTorrentUrl(video, videoFile, baseUrlHttp) - const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] - const urlList = [ getVideoFileUrl(video, videoFile, baseUrlHttp) ] - - const magnetHash = { - xs, - announce, - urlList, - infoHash: videoFile.infoHash, - name: video.name + return magnetUtil.encode(magnetHash) } - - return magnetUtil.encode(magnetHash) } diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index 954fa426e..cc32c768d 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts @@ -1,5 +1,4 @@ import { UserRight } from './user-right.enum' -import user from '../../../server/models/account/user' // Keep the order export enum UserRole { diff --git a/tsconfig.json b/tsconfig.json index be910b309..71674e165 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,17 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es2015", "noImplicitAny": false, "sourceMap": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "outDir": "./dist", "lib": [ "dom", - "es2015" + "es2015", + "es2016", + "es2017" ], "types": [ "node" diff --git a/yarn.lock b/yarn.lock index 986a1643a..cdf224c5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,7 +16,7 @@ dependencies: "@types/node" "*" -"@types/bluebird@*": +"@types/bluebird@*", "@types/bluebird@3.5.18": version "3.5.18" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6" @@ -111,6 +111,10 @@ version "8.0.53" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8" +"@types/node@6.0.41": + version "6.0.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.41.tgz#578cf53aaec65887bcaf16792f8722932e8ff8ea" + "@types/parse-torrent-file@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.1.tgz#056a6c18f3fac0cd7c6c74540f00496a3225976b" @@ -129,6 +133,10 @@ version "1.9.3" resolved "https://registry.yarnpkg.com/@types/pem/-/pem-1.9.3.tgz#0c864c8b79e43fef6367db895f60fd1edd10e86c" +"@types/reflect-metadata@0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@types/reflect-metadata/-/reflect-metadata-0.0.4.tgz#b6477ca9a97e5265f2ac67f9ea704eae5e0eaf4d" + "@types/request@^2.0.3": version "2.0.8" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.0.8.tgz#424d3de255868107ed4dd6695c65c5f1766aba80" @@ -136,7 +144,7 @@ "@types/form-data" "*" "@types/node" "*" -"@types/sequelize@^4.0.55": +"@types/sequelize@4.0.79", "@types/sequelize@^4.0.55": version "4.0.79" resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.0.79.tgz#74c366407a978e493e70d7cea3d80c681aed15c0" dependencies: @@ -1303,6 +1311,10 @@ es6-set@~0.1.5: es6-symbol "3.1.1" event-emitter "~0.3.5" +es6-shim@0.35.3: + version "0.35.3" + resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" + es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -3451,6 +3463,10 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +reflect-metadata@^0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.10.tgz#b4f83704416acad89988c9b15635d47e03b9344a" + regex-cache@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" @@ -3661,6 +3677,17 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" +sequelize-typescript@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-0.6.1.tgz#603c948183aebb534dbea182258ee6b0d0571bcb" + dependencies: + "@types/bluebird" "3.5.18" + "@types/node" "6.0.41" + "@types/reflect-metadata" "0.0.4" + "@types/sequelize" "4.0.79" + es6-shim "0.35.3" + glob "7.1.2" + sequelize@^4.7.5: version "4.23.1" resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-4.23.1.tgz#2ec517bbb2ccddece45f934ef3c770cfbe818a8b"