]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Move models to typescript-sequelize
authorChocobozzz <me@florianbigard.com>
Tue, 12 Dec 2017 16:53:50 +0000 (17:53 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 13 Dec 2017 15:50:33 +0000 (16:50 +0100)
150 files changed:
package.json
scripts/danger/clean/cleaner.ts
scripts/reset-password.ts
scripts/update-host.ts
server.ts
server/controllers/activitypub/client.ts
server/controllers/activitypub/outbox.ts
server/controllers/api/jobs.ts
server/controllers/api/oauth-clients.ts
server/controllers/api/server/follows.ts
server/controllers/api/users.ts
server/controllers/api/videos/abuse.ts
server/controllers/api/videos/blacklist.ts
server/controllers/api/videos/channel.ts
server/controllers/api/videos/index.ts
server/controllers/api/videos/rate.ts
server/controllers/client.ts
server/controllers/services.ts
server/controllers/static.ts
server/controllers/webfinger.ts
server/helpers/activitypub.ts
server/helpers/custom-validators/accounts.ts
server/helpers/custom-validators/activitypub/account.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/misc.ts
server/helpers/custom-validators/index.ts [deleted file]
server/helpers/custom-validators/video-channels.ts
server/helpers/custom-validators/videos.ts
server/helpers/custom-validators/webfinger.ts
server/helpers/ffmpeg-utils.ts
server/helpers/index.ts
server/helpers/logger.ts
server/helpers/peertube-crypto.ts
server/helpers/utils.ts
server/helpers/webfinger.ts
server/initializers/checker.ts
server/initializers/constants.ts
server/initializers/database.ts
server/initializers/installer.ts
server/initializers/migrations/0065-video-file-size.ts
server/initializers/migrations/0100-activitypub.ts
server/initializers/migrations/0105-server-mail.ts
server/initializers/migrations/0110-server-key.ts
server/initializers/migrations/0115-account-avatar.ts
server/initializers/migrations/0120-video-null.ts
server/initializers/migrator.ts
server/lib/activitypub/account.ts
server/lib/activitypub/fetch.ts
server/lib/activitypub/process/misc.ts
server/lib/activitypub/process/process-accept.ts
server/lib/activitypub/process/process-add.ts
server/lib/activitypub/process/process-announce.ts
server/lib/activitypub/process/process-create.ts
server/lib/activitypub/process/process-delete.ts
server/lib/activitypub/process/process-follow.ts
server/lib/activitypub/process/process-like.ts
server/lib/activitypub/process/process-undo.ts
server/lib/activitypub/process/process-update.ts
server/lib/activitypub/process/process.ts
server/lib/activitypub/send/misc.ts
server/lib/activitypub/send/send-accept.ts
server/lib/activitypub/send/send-add.ts
server/lib/activitypub/send/send-announce.ts
server/lib/activitypub/send/send-create.ts
server/lib/activitypub/send/send-delete.ts
server/lib/activitypub/send/send-follow.ts
server/lib/activitypub/send/send-like.ts
server/lib/activitypub/send/send-undo.ts
server/lib/activitypub/send/send-update.ts
server/lib/activitypub/share.ts
server/lib/activitypub/url.ts
server/lib/activitypub/video-channels.ts
server/lib/activitypub/videos.ts
server/lib/cache/videos-preview-cache.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-fetcher-handler.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
server/lib/jobs/job-scheduler.ts
server/lib/jobs/transcoding-job-scheduler/transcoding-job-scheduler.ts
server/lib/jobs/transcoding-job-scheduler/video-file-optimizer-handler.ts
server/lib/jobs/transcoding-job-scheduler/video-file-transcoder-handler.ts
server/lib/oauth-model.ts
server/lib/user.ts
server/lib/video-channel.ts
server/middlewares/activitypub.ts
server/middlewares/sort.ts
server/middlewares/user-right.ts
server/middlewares/validators/account.ts
server/middlewares/validators/activitypub/activity.ts
server/middlewares/validators/activitypub/signature.ts
server/middlewares/validators/follows.ts
server/middlewares/validators/oembed.ts
server/middlewares/validators/sort.ts
server/middlewares/validators/users.ts
server/middlewares/validators/utils.ts
server/middlewares/validators/video-blacklist.ts
server/middlewares/validators/video-channels.ts
server/middlewares/validators/videos.ts
server/middlewares/validators/webfinger.ts
server/models/account/account-follow-interface.ts [deleted file]
server/models/account/account-follow.ts
server/models/account/account-interface.ts [deleted file]
server/models/account/account-video-rate-interface.ts [deleted file]
server/models/account/account-video-rate.ts
server/models/account/account.ts
server/models/account/index.ts [deleted file]
server/models/account/user-interface.ts [deleted file]
server/models/account/user.ts
server/models/application/application-interface.ts [deleted file]
server/models/application/application.ts
server/models/application/index.ts [deleted file]
server/models/avatar/avatar-interface.ts [deleted file]
server/models/avatar/avatar.ts
server/models/avatar/index.ts [deleted file]
server/models/index.ts [deleted file]
server/models/job/index.ts [deleted file]
server/models/job/job-interface.ts [deleted file]
server/models/job/job.ts
server/models/oauth/index.ts [deleted file]
server/models/oauth/oauth-client-interface.ts [deleted file]
server/models/oauth/oauth-client.ts
server/models/oauth/oauth-token-interface.ts [deleted file]
server/models/oauth/oauth-token.ts
server/models/server/index.ts [deleted file]
server/models/server/server-interface.ts [deleted file]
server/models/server/server.ts
server/models/utils.ts
server/models/video/index.ts [deleted file]
server/models/video/tag-interface.ts [deleted file]
server/models/video/tag.ts
server/models/video/video-abuse-interface.ts [deleted file]
server/models/video/video-abuse.ts
server/models/video/video-blacklist-interface.ts [deleted file]
server/models/video/video-blacklist.ts
server/models/video/video-channel-interface.ts [deleted file]
server/models/video/video-channel-share-interface.ts [deleted file]
server/models/video/video-channel-share.ts
server/models/video/video-channel.ts
server/models/video/video-file-interface.ts [deleted file]
server/models/video/video-file.ts
server/models/video/video-interface.ts [deleted file]
server/models/video/video-share-interface.ts [deleted file]
server/models/video/video-share.ts
server/models/video/video-tag-interface.ts [deleted file]
server/models/video/video-tag.ts
server/models/video/video.ts
shared/models/users/user-role.ts
tsconfig.json
yarn.lock

index eee28203819db5e2a11451ac9daa76a2170d426d..1f98b56e8d06e8f949a4edc72050af8b4f624347 100644 (file)
     "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",
index 4283335287c068cd98fc7af7077cc221e70c18ea..a22e2ed6aff475ccb8d54f735d2066a4edd052cb 100644 (file)
@@ -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)
index f0c06a7bfddcd9b66ef53fb54807ee5bceb99c8a..a6863f807fa6c7e6085a02ac250675d3d4c4ed24 100755 (executable)
@@ -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) {
index 759443623ef74950c8cdfcb65cc9eb5b5422b6f8..ba4656b75cb268192175fc5bdb98c8d8a0cb75a0 100755 (executable)
@@ -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<any>[] = []
index 39caf89963b7b33c219a4d7eb9707a1765e32134..2e9ed31d2c1454a225133a278d7ef7f539409637 100644 (file)
--- 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'
index a478acd1404f39dfc48fa547754554e00d307747..72b2162542dcb6ac7b56021ece767025e40e03f2 100644 (file)
@@ -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())
 }
index 5a2d43f3dd8b803dba2d6efe11ad66768d4b17c6..dc6b72a6e31231e2283a072f770dec8c3c28a20d 100644 (file)
@@ -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) {
index f6fbff36962955700801c70ed3d9cf1b10c70ee6..4e7cd1ee3edd23525ffda96583f772e7c8c943a3 100644 (file)
@@ -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))
 }
index ac1ee9e36f44cf481c151d15d8861ff37c09feaa..bc02fce904a20785af626b34194d3e979e9760d2 100644 (file)
@@ -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 = {
index c2fb37c39a451059931ec5609be50a4a1e81a535..913998e3ae1c62d6cd6939023813f8bc04da165c 100644 (file)
@@ -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
index f9b871724176dd74827764d659a4c2ff24576ab7..d6c0e67f95744facfd33b2101ae39065a35434b6 100644 (file)
@@ -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
index 29e1175c5a0d3adba97df17ff9550a6745504ed1..08cc4d0b4cc8a9fee4924442847a3d94d1dcbead 100644 (file)
@@ -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
index 06333c271244dfae0ac9c8a7163bd69b626bd188..d08c6e13f0e6c4a614e0aae5fa306715f1141f76 100644 (file)
@@ -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<BlacklistedVideo, BlacklistedVideoInstance>(resultList.data, resultList.total))
+  return res.json(getFormattedObjects<BlacklistedVideo, VideoBlacklistModel>(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()
index d99f47c32d8a27cdbe16688614053648dae5c1df..683b0448d787d2124056b50afcfa1aaaea82e028 100644 (file)
@@ -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())
 }
index 63de662a7a528a4de2f74ccf4a474b3c00a132b2..91ab8c66aead59a9463a9a04136a80a66d229442 100644 (file)
@@ -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,
index c27c611161dcb2fcb332da8ecb11901931b4aaca..48b744b0c9dc447d4b8660bd9938934388924a66 100644 (file)
@@ -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 = {
index 1b140b14a15fe748cbb79a8ad18489ac4866e11e..9a72fe8e085180b0e093ccf5b31f305e7014829f 100644 (file)
@@ -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<VideoInstance>
+  let videoPromise: Bluebird<VideoModel>
 
   // 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)
   }
index 0c325678c78cf9a1e716d03e5fcec3a631e21d6f..3ac78a5df7c0149e2d2de68d9f0ed2ff8a771bf3 100644 (file)
@@ -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)
index 7425fd0973d5c3b9a1fdbb06f175129fafb52f0a..33aed89279a216a4549e4fa0e36d2e5e07070c0e 100644 (file)
@@ -1,6 +1,5 @@
 import * as express from 'express'
 import * as cors from 'cors'
-
 import {
   CONFIG,
   STATIC_MAX_AGE,
index 78e5dee79747a31c21592d4ceae71f021ace3ed5..bb2ea40fa6da826e7a68f54238078c0b9bf0c28e 100644 (file)
@@ -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,
index 1ea6422ca111f441dccc17c22543e84d7bf1248a..43907b596be51fc4055c099e8dee486010e188a4 100644 (file)
@@ -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 <T> (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<Activity>
index e3c4774149970c2525da1b50c5ab009d7abe9bff..8dc5d1f0d778fa7f2623d85a9861beeb3f4842d5 100644 (file)
@@ -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<AccountInstance>
+  let promise: Bluebird<AccountModel>
 
   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<AccountInstance>, res: Response) {
+async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
   const account = await p
 
   if (!account) {
index cab39a6545aa3eefc8106572c4c7aa14147ae5f7..10bf81e8a0442feebc9da9d28b2098ea1d463f2d 100644 (file)
@@ -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'
index 3a0e8197c958ce616d274dd89dfc03358342277d..043e3c55e74dd119543ec47d47cd3ccf9f8071c0 100644 (file)
@@ -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']) &&
index 1bbfd0fc473c053428e0f49c447bb3bc896d65cc..65f5ca80924ca9b106fafe9b223b035ab1434fed 100644 (file)
@@ -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 (file)
index d3b2f53..0000000
+++ /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'
index 3de9f041be57de1ae6395725ee9151637dab4367..6bc96bf51bbdb3d0028c494217b0281023b533a0 100644 (file)
@@ -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) {
index 37fa8b08ab141514e02d0c884fb53fc4280c5b15..ee9d0ed19908d57dca103e89578354e4d8224dc6 100644 (file)
@@ -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) {
index e93115d81701aa06455a5ecc14c75c5db87f0ca5..38f6b938d5225441c474b9a74a311a3a55bd846d 100644 (file)
@@ -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
 }
 
 // ---------------------------------------------------------------------------
index 8ad205961706c489c22cc9351cb8a71c8a348d6c..c2581f4602315bef1b01349b8a38a6d90fc78328 100644 (file)
@@ -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) {
index 2c7ac395486e1e7c378f4b18fdab0bb0b6f63cc3..d96bc48e954f465bfbd0c6a8bab4a33da2702d12 100644 (file)
@@ -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'
index 8d809d16d62f4efa9c3b3a489a10c48c3e99177f..2676133db30df6c258e016990df50205a4cdbce2 100644 (file)
@@ -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
 
index 74e4cc703b3641de211adcab14ff7cd47e30ab7f..c4c735cb84f6517b6ed4e3cb525c06bf25d01807 100644 (file)
@@ -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
index 3464341e66beffc119b8e34ec6d4ecc5d9ad5843..cb5e536b8bcdc04d4dc167c6bc1a66fd9beee45d 100644 (file)
@@ -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<any>, savedFields: object) {
+function resetSequelizeInstance (instance: Model<any>, 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) {
index ab28889818c4244050f8011365fb3cdeee046d5b..d98068cd772d039fc7e81a22ac353de4689a1e31 100644 (file)
@@ -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,
index 317d594233006210be3c45e163826ee248a2ace2..7e76990b56026eb8dcd6fbe9ac841ad2abc64fe7 100644 (file)
@@ -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
 }
index 7be7a5f953a25adca7a9dd35ecbf9bddc80dd368..f539eb2eefe1d8e57521d5b5422079b5a38cce0f 100644 (file)
@@ -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'
 
 // ---------------------------------------------------------------------------
 
index bb95992e1334ddb0f27a0019036a00790ce25d52..f9e24c6b8fad2b62e6413fc85aa43726b84981a4 100644 (file)
@@ -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<void>,
-
-  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<any>[] = []
-
-  // 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<string>(filteredFilesArray, 1)
+  initDatabase,
+  sequelizeTypescript
 }
index 9545160571457f63decf73fb9d55ec3f316407f9..5452743b64865fd399d4fb2601a1b8312765cc1c 100644 (file)
@@ -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.')
 
index 58f8f3bcc0204b49fc052312afe8f4b9367192cd..4e2075f8b0700de9bfc22820a2f62dab1d891de7 100644 (file)
@@ -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<void> {
   return utils.db.Video.listOwnedAndPopulateAuthorAndTags()
-    .then((videos: VideoInstance[]) => {
+    .then((videos: VideoModel[]) => {
       const tasks: Promise<any>[] = []
 
       videos.forEach(video => {
index 50a0adc143e86233d1b5e03d6a4765b5af636a7f..fb42e1d57fd3c0d9d93e04bf9f93bb32fded3653 100644 (file)
@@ -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<void> {
   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()
index 5836992d593ebe4d58bf2fbf9495fb6b685231ef..4b9600e916eb10a92f848b5f9dd71ce6ffbb13e8 100644 (file)
@@ -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<void> {
   await utils.queryInterface.removeColumn('Servers', 'email')
 }
index 560353945ef818f71fa521f2313a5e4d9f554730..5ff6daf694adb125853aa07dc41da44412ebb096 100644 (file)
@@ -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<void> {
   await utils.queryInterface.removeColumn('Servers', 'publicKey')
 }
index 2b947ceda08f0c0636ab70ef1a73ee0dbb3f590a..b318e8163cce895eeb57ee73d4c49241adbb7e4d 100644 (file)
@@ -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<void> {
   await utils.db.Avatar.sync()
 
index 9130d10ee13a88e681ec5fc61f35cfd01866d48b..63f3984ddaa2a124317cde3f94f20c7d43800948 100644 (file)
@@ -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<void> {
 
   {
index 187c9be6e5bb3abb305a886434826d2fadb02425..f3a05cc8ce7cc0ef5e421070dc4d4460c92ddc70 100644 (file)
@@ -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)
   })
 }
index 906c8ff29fdbf367d9aaf239d90b9373615992b7..45690b88d229d999b31b52639a717944d5d4bcfd 100644 (file)
@@ -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<AccountInstance> | Promise<AccountInstance> {
+function saveAccountAndServerIfNotExist (account: AccountModel, t?: Transaction): Bluebird<AccountModel> | Promise<AccountModel> {
   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 {
index fd2af0761b93c62eca9db3d912dc1b3ac442c6e8..aa4dea8e0c79662dda6e8f4362ff47a4bd529a0a 100644 (file)
@@ -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 ]
   }
index 0baa22c2617d5dc0c3de9fb13eafeac43a16b870..a775c858a12d5640bb943650577e43c2193510ec 100644 (file)
@@ -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
     })
index 73c6cb27913d9fc1089d84196667f436dfb17d68..5b321f771d348aa4d3c9727c036769b209972ba1 100644 (file)
@@ -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')
index e6bf63eb245c68fa2b52fffe29fa314012e738e7..550593eab57a34759de42960345a807597e27d7d 100644 (file)
@@ -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<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f, { transaction: t }))
+    const tasks: Bluebird<any>[] = 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<any>[] = []
 
   for (const accountUrl of accountUrls) {
     const account = await getOrCreateAccountAndServer(accountUrl)
-    const p = db.AccountVideoRate
+    const p = AccountVideoRateModel
       .create({
         videoId: video.id,
         accountId: account.id,
index 2aa9ad5c745ed7892094fea53aaabddb6a5b7e69..ff2c6d708e688cb59d33d8f1b87876265a11fc7a 100644 (file)
@@ -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
index 4740dc4328d5fd7e6ef521e52b34220128b9ca47..c1eb2a8ab2dbdf4240c124ba32d5eeb1e198ed1b 100644 (file)
@@ -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)
   })
index 41cdc236d1baacdd25e3cbbe9659957b4d288257..8f280d37fa44bf0763086c2c22e679c82ae7d86a 100644 (file)
@@ -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 })
   })
 
index 320dc113814f74757c52f6e5fa753bf8f291ead1..ccaee43a66c7317bbc8997309b975c516d3091d5 100644 (file)
@@ -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
index 5f2ffe7eaa6cc2aae265683163ce2b98e07e199d..a6e391f1ed09b314ca384fd6297dfa44135a56d7 100644 (file)
@@ -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
index cc221045f972c453f2d4a19a46fd7b2fe7f5f86f..efa63122b5ef5e67d0af4b5d0a86b8ef1e1adbae 100644 (file)
@@ -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}.`)
 
index 11c6de8f56bc5f6f99e5ccbd0ca2189b1aa15def..771021f0ce7612b85cc9053f518a6587946206ca 100644 (file)
@@ -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<any>[] = videoFileAttributes.map(f => db.VideoFile.create(f))
+      const tasks: Bluebird<any>[] = 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) {
index 54981c289fd640ec9b4865ec9ac92cc72d25361b..bfbf8053ceb3848d270e0934212871ef5efb5625 100644 (file)
@@ -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<any> } = {
+const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = {
   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) {
index 999def70168a4fef7866169d9ce348c42f7c4a1e..ffc221477bb80214010f40b391c67424dc3863e3 100644 (file)
@@ -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)
 }
 
 // ---------------------------------------------------------------------------
index d3f8fbe38475dced9725849aeeaff49306f756b2..f160af3c9ce60c772072610f83eaf847311dd773 100644 (file)
@@ -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,
index d8ac2853eb4579a4ce299ebcbeb5b2f0b6cca73b..fd614db75dbf74e40a7fc8a9a9a6c77fd5a8e14b 100644 (file)
@@ -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<ActivityAdd> {
   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
 }
 
 // ---------------------------------------------------------------------------
index 3acf604cd947e2f08b470a37286a706ceb8bca59..e685323e82affd031aec8d317f2860ed1b88faf0 100644 (file)
@@ -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<ActivityAnnounce> {
   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
 }
 
 // ---------------------------------------------------------------------------
index a34d3776caa9ec8160b2359a1f5fcea81a5b6cc7..9fbaa8196a21c721670f2b48ea5956cc7e645f2e 100644 (file)
@@ -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<ActivityCreate> {
   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
 }
index 8193790b3fc8210051cbe42e198f8000fa0b4555..0a45ea10f3054a01e55144172397801309f4f1de 100644 (file)
@@ -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
 }
index 8fba1b6b57661ca765714091bc2055f36512d19a..51735ddfd0599569b4079c722b5cff27f2602f71 100644 (file)
@@ -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
 }
 
 // ---------------------------------------------------------------------------
index 0c464b2d3f401c6c185632abb945359e12caf0f1..1a35d0db063067d532d40543bbf13c86fb8dfe5d 100644 (file)
@@ -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<ActivityLike> {
   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
 }
 
 // ---------------------------------------------------------------------------
index 015f02b354245904ca58bbc67d4a4e237b55c8b0..699f920f0648678876b65e304a3fc4fff175dd2b 100644 (file)
@@ -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<ActivityUndo> {
   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
 }
index 59524e523738b636dc36d0eae3f46f5e570f5e94..9baf13a87d8be7eb80ead93002db78914cc8c369 100644 (file)
@@ -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<ActivityUpdate> {
   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
 }
index e14b0f50c0f5d290ac04befca30ac8a9ac28e94f..5bec61c051952520413a23be8d95b3e409aea7ab 100644 (file)
@@ -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 })
index 6475c4218c3aa412e473656600e61023129e0546..00b4e8852451d638474ab5b389092c7e6eeb1129 100644 (file)
@@ -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
 }
 
index 7339d79f9b62d94f31ad0dced88a6c2bb842fdea..c05a46f95ba7b60ed1d4bfe82d810d5a412a8942 100644 (file)
@@ -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
index 6b82f12d56bf311d8d8249d1319cb466bfcba6ae..14c07fec0e196d72381bda9747021d9c24c982f1 100644 (file)
@@ -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
index 7f352f361df8e84905555eb22636212d216551b4..c5bda8dd894e699471bfc50765db882f0c388555 100644 (file)
@@ -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<string>((res, rej) => {
index 49d4bf5c6b4cfcfc7db412cd5ef57f7532493fa5..8040dde2ae50a791763dc1f467c9f4127bf045d6 100644 (file)
@@ -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) {
index 9adceab84b04f42a1dfc477f716119d5c5513cbe..6381502021b415f590e3c8ba956451232e640d15 100644 (file)
@@ -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) {
index fcc81eb16e136b442653b63c9ecb3605a8e07f56..76da5b7240204a9910a7dd1e67e3d84520c2e14a 100644 (file)
@@ -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)
   }
index 4c95197c417d89e1ee072af6029e667d468d5b1e..f16cfcec3d898a7793bc3913eb6b9b9528f08960 100644 (file)
@@ -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) {
index 62ce6927eaf08d693b933fa7b422f8cb25ba5f6d..88fe8a4a3a47700af4a45971292092592ec0d22b 100644 (file)
@@ -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<P, T> {
   process (data: object, jobId: number): Promise<T>
@@ -24,12 +24,12 @@ class JobScheduler<P, T> {
 
     logger.info('Jobs scheduler %s activated.', this.jobCategory)
 
-    const jobsQueue = queue<JobInstance, JobQueueCallback>(this.processJob.bind(this))
+    const jobsQueue = queue<JobModel, JobQueueCallback>(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<P, T> {
 
         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<P, T> {
 
     const options = { transaction }
 
-    return db.Job.create(createQuery, options)
+    return JobModel.create(createQuery, options)
   }
 
-  private enqueueJobs (jobsQueue: AsyncQueue<JobInstance>, jobs: JobInstance[]) {
+  private enqueueJobs (jobsQueue: AsyncQueue<JobModel>, 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<P, T> {
     return callback(null)
   }
 
-  private async onJobError (jobHandler: JobHandler<P, T>, job: JobInstance, err: Error) {
+  private async onJobError (jobHandler: JobHandler<P, T>, job: JobModel, err: Error) {
     job.state = JOB_STATES.ERROR
 
     try {
@@ -121,7 +121,7 @@ class JobScheduler<P, T> {
     }
   }
 
-  private async onJobSuccess (jobHandler: JobHandler<P, T>, job: JobInstance, jobResult: T) {
+  private async onJobSuccess (jobHandler: JobHandler<P, T>, job: JobModel, jobResult: T) {
     job.state = JOB_STATES.SUCCESS
 
     try {
index c5efe8eeb174e0fe9977872342a4cac033cbb61d..e5530a73c083f4dae99cbd21219668c881f442ac 100644 (file)
@@ -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<TranscodingJobPayload, VideoInstance> } = {
+const jobHandlers: { [ handlerName: string ]: JobHandler<TranscodingJobPayload, VideoModel> } = {
   videoFileOptimizer,
   videoFileTranscoder
 }
index e65ab3ee1df995b00b063738a9b375414afaad8e..1786ce971afbdc24b3ba82b32be31b255210ebf8 100644 (file)
@@ -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<TranscodingJobPayload, VideoInstance>) {
+async function onSuccess (jobId: number, video: VideoModel, jobScheduler: JobScheduler<TranscodingJobPayload, 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
 
@@ -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<any>[] = []
 
         for (const resolution of resolutionsEnabled) {
index 867580200b594cfb4aba7b3d7bf9b43a94b776c0..8957b4565014ee4269dc21a69c6b1f45c3ad9628 100644 (file)
@@ -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
 
index d91b00c5577af4b1c66079951afb61d3da271eeb..dce71e83bb4e4677b2ec042b6284911b93f0af72 100644 (file)
@@ -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
index 5653d8e655fae3bb796a9ac919933661b27e29e5..c4722fae2cbf8077d72dd15dc80669576d44c1f5 100644 (file)
@@ -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,
index beb01da9b7d7847789bb632935851acc445e6835..97924aa9e7350bc01ece116d013c3d6ba5d961e6 100644 (file)
@@ -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 }
index c2ad18195bc98fc19f210755f63bf00e0870dbb7..489396447cbaa9a52fabdb8198291a35437b9d1c 100644 (file)
@@ -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) {
index 7b60920dea0c707159b16087734a100c0b698676..5d2a43accc019a8d83d860944936d4a6c82a7632 100644 (file)
@@ -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
index bcebe9d7f09797c57988de94fcf58ac1556b549a..5d63ebaf4f361dfaf373c2a8db5b5200b877b0ed 100644 (file)
@@ -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)
index 70f4e4d3bc5e33a17b4adcc05ead4de5aac526d1..6951dfc80d6fbef8c6a9ff7cec0a1d19e70a9285 100644 (file)
@@ -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 = [
index c63be5979053cec022d2f594db54b2f6bd7b569c..e0225f30cea88a90a5bf9eb804af0b80cc684d87 100644 (file)
@@ -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 = [
index 360685512aae5c5b6955db256fc53e8fc0d16d1e..d41bb6a8d6e387062ef21c0a30607fde98e4577c 100644 (file)
@@ -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 = [
index 605872ecf9c70a55f2d42fde15167ccde6c7a871..10482e5d0a87fc7b3a781177d8da88683688068e 100644 (file)
@@ -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)
index 31f06dc65e9f747ecfbd19d70726b5fb879ba88a..fb7b726e5c7913857a6242e2d41d4b52169c70d8 100644 (file)
@@ -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('([^/]+)$')
index d5822ac81562227c713c25c82b7826864958c58a..38184fefa0dc6e6a0a98fd38c0c375aa6a70eb08 100644 (file)
@@ -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'
index ac7435b7d3c647157f8490f04c0159d8ec689012..920176d07af046f8ff69441a15ffc05e6d5f05f6 100644 (file)
@@ -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)
index ca80acf29100fde1e59ccc44ddfbe7aeecf3a9d3..61f76b457a4e5df65f67369ddad3329eb4d986af 100644 (file)
@@ -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) {
index f1cc0495042127b422a0f4db2162d67277caa82b..98099fe3f53727d00bb17952c55bc7bb2c9385af 100644 (file)
@@ -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')
index 3d31a7e52ef81d631ed1b182ba673eb7c9eebfaa..660390080203c9d77df01bcef13eebdabf22ccee 100644 (file)
@@ -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)
index 10625e41d450a16f3b9a2b64188a39e7a51ab588..b52d5f28548fb80fd8e1782933c07606655bbb33 100644 (file)
@@ -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)
index 34e62c66dc6a5cfb0d7851891b8d8e0a40c05a4e..7903c740078dc3d463a9722ef9985540729f6d89 100644 (file)
@@ -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 (file)
index 7975a46..0000000
+++ /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<AccountFollowInstance>
-
-  export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
-  export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountFollowInstance>>
-
-  export type ListAcceptedFollowerUrlsForApi = (
-    accountId: number[],
-    t: Sequelize.Transaction,
-    start?: number,
-    count?: number
-  ) => Promise< ResultList<string> >
-  export type ListAcceptedFollowingUrlsForApi = (
-    accountId: number[],
-    t: Sequelize.Transaction,
-    start?: number,
-    count?: number
-  ) => Promise< ResultList<string> >
-  export type ListAcceptedFollowerSharedInboxUrls = (accountId: number[], t: Sequelize.Transaction) => Promise< ResultList<string> >
-  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<AccountFollowAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  AccountFollower?: AccountInstance
-  AccountFollowing?: AccountInstance
-
-  toFormattedJSON: AccountFollowMethods.ToFormattedJSON
-}
-
-export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {}
index 724f37baa6b6038815611ab1d8f5cf652f70e8b0..975e7ee7dafc1d385059249f17d53437545fa59b 100644 (file)
@@ -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<AccountFollowInstance, AccountFollowAttributes>
-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<AccountFollowInstance, AccountFollowAttributes>('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<AccountFollowModel> {
 
-  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<any>[] = []
 
-  const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ]
-  const tasks: Promise<any>[] = []
+    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 (file)
index 46fe068..0000000
+++ /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<AccountInstance>
-
-  export type Load = (id: number) => Bluebird<AccountInstance>
-  export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance>
-  export type LoadByUrl = (url: string, transaction?: Sequelize.Transaction) => Bluebird<AccountInstance>
-  export type LoadLocalByName = (name: string) => Bluebird<AccountInstance>
-  export type LoadByNameAndHost = (name: string, host: string) => Bluebird<AccountInstance>
-  export type ListByFollowersUrls = (followerUrls: string[], transaction: Sequelize.Transaction) => Bluebird<AccountInstance[]>
-
-  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<string[]>
-  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<AccountAttributes> {
-  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<AccountInstance, AccountAttributes> {}
diff --git a/server/models/account/account-video-rate-interface.ts b/server/models/account/account-video-rate-interface.ts
deleted file mode 100644 (file)
index 1f395bc..0000000
+++ /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<AccountVideoRateInstance>
-}
-
-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<AccountVideoRateAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface AccountVideoRateModel
-  extends AccountVideoRateClass, Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes> {}
index d92834bbbd24152ef7b495dbf973e37ee23bc68c..e969e4a436b78c9c519baec342c1f0a9165bfb9e 100644 (file)
@@ -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<AccountVideoRateInstance, AccountVideoRateAttributes>
-let load: AccountVideoRateMethods.Load
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  AccountVideoRate = sequelize.define<AccountVideoRateInstance, AccountVideoRateAttributes>('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<AccountVideoRateModel> {
 
-  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<AccountVideoRateAttributes> = {
-    where: {
-      accountId,
-      videoId
+  static load (accountId: number, videoId: number, transaction: Transaction) {
+    const options: IFindOptions<AccountVideoRateModel> = {
+      where: {
+        accountId,
+        videoId
+      }
     }
-  }
-  if (transaction) options.transaction = transaction
+    if (transaction) options.transaction = transaction
 
-  return AccountVideoRate.findOne(options)
+    return AccountVideoRateModel.findOne(options)
+  }
 }
index 8b0819f395c701dba485cbeae83566daf791487f..d6758fa109dd144e80747a47ea8a59038bde7f33 100644 (file)
 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<AccountInstance, AccountAttributes>
-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<AccountInstance, AccountAttributes>('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<Account> {
+
+  @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<AccountAttributes> = {
-    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<AccountAttributes> = {
-    where: {
-      uuid
     }
+
+    return activityPubContextify(json)
   }
 
-  return Account.findOne(query)
-}
+  isOwned () {
+    return this.serverId === null
+  }
 
-loadLocalByName = function (name: string) {
-  const query: Sequelize.FindOptions<AccountAttributes> = {
-    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<AccountAttributes> = {
-    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<AccountAttributes> = {
-    where: {
-      url
-    },
-    transaction
+  getFollowingUrl () {
+    return this.url + '/following'
   }
 
-  return Account.findOne(query)
-}
-
-listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) {
-  const query: Sequelize.FindOptions<AccountAttributes> = {
-    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 (file)
index 179f669..0000000
+++ /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 (file)
index 0f0b720..0000000
+++ /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<boolean>
-
-  export type ToFormattedJSON = (this: UserInstance) => FormattedUser
-  export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean>
-
-  export type CountTotal = () => Bluebird<number>
-
-  export type GetByUsername = (username: string) => Bluebird<UserInstance>
-
-  export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<UserInstance> >
-
-  export type LoadById = (id: number) => Bluebird<UserInstance>
-
-  export type LoadByUsername = (username: string) => Bluebird<UserInstance>
-  export type LoadByUsernameAndPopulateChannels = (username: string) => Bluebird<UserInstance>
-
-  export type LoadByUsernameOrEmail = (username: string, email: string) => Bluebird<UserInstance>
-}
-
-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<UserAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  isPasswordMatch: UserMethods.IsPasswordMatch
-  toFormattedJSON: UserMethods.ToFormattedJSON
-  hasRight: UserMethods.HasRight
-}
-
-export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
index 3705947c067b485153ad821f7395c12d8a584bff..84adad96e685409863c3474bec75f976f071f3fb 100644 (file)
 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<UserInstance, UserAttributes>
-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<UserInstance, UserAttributes>('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<UserModel> {
+
+  @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 (file)
index 2c391db..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import * as Sequelize from 'sequelize'
-import * as Bluebird from 'bluebird'
-
-export namespace ApplicationMethods {
-  export type LoadMigrationVersion = () => Bluebird<number>
-
-  export type UpdateMigrationVersion = (
-    newVersion: number,
-    transaction: Sequelize.Transaction
-  ) => Bluebird<[ number, ApplicationInstance[] ]>
-
-  export type CountTotal = () => Bluebird<number>
-}
-
-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<ApplicationAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface ApplicationModel extends ApplicationClass, Sequelize.Model<ApplicationInstance, ApplicationAttributes> {}
index 8ba40a8951afec013bc579fc3175d8b4af347c5b..f3c0f1052735c2749110a0c7bf2296086763fa84 100644 (file)
@@ -1,61 +1,35 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from '../utils'
-import {
-  ApplicationAttributes,
-  ApplicationInstance,
-
-  ApplicationMethods
-} from './application-interface'
-
-let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes>
-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<ApplicationModel> {
+
+  @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<ApplicationInstance, ApplicationAttributes>('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 (file)
index 706f85c..0000000
+++ /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 (file)
index 4af2b87..0000000
+++ /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<AvatarAttributes> {
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface AvatarModel extends AvatarClass, Sequelize.Model<AvatarInstance, AvatarAttributes> {}
index 96308fd5ffc2c1a144548d2d1e81c85b7eed045e..2e7a8ae2c1e878c6210d3574c59789fb83e496d0 100644 (file)
@@ -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<AvatarInstance, AvatarAttributes>
+@Table({
+  tableName: 'avatar'
+})
+export class AvatarModel extends Model<AvatarModel> {
 
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Avatar = sequelize.define<AvatarInstance, AvatarAttributes>('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 (file)
index 877aed1..0000000
+++ /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 (file)
index fedd97d..0000000
+++ /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 (file)
index 56925fd..0000000
+++ /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 (file)
index 3cfc0fb..0000000
+++ /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<JobInstance[]>
-  export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<JobInstance> >
-
-  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<JobAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  toFormattedJSON: JobMethods.ToFormattedJSON
-}
-
-export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {}
index f428e26dbe40cd15fd1bd506a5945373cf42a5a9..35c357e69e95df3064827dc406f1306c1983996a 100644 (file)
@@ -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<JobInstance, JobAttributes>
-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<JobInstance, JobAttributes>('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<JobModel> {
+  @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 (file)
index a20d3a5..0000000
+++ /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 (file)
index 3526e41..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import * as Sequelize from 'sequelize'
-import * as Promise from 'bluebird'
-
-export namespace OAuthClientMethods {
-  export type CountTotal = () => Promise<number>
-
-  export type LoadFirstClient = () => Promise<OAuthClientInstance>
-
-  export type GetByIdAndSecret = (clientId: string, clientSecret: string) => Promise<OAuthClientInstance>
-}
-
-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<OAuthClientAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface OAuthClientModel extends OAuthClientClass, Sequelize.Model<OAuthClientInstance, OAuthClientAttributes> {}
index 9cc68771dad5983d959f93ea6de09094574676a5..42c59bb7923431c263f44e947c3519509b9365d3 100644 (file)
@@ -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<OAuthClientInstance, OAuthClientAttributes>
-let countTotal: OAuthClientMethods.CountTotal
-let loadFirstClient: OAuthClientMethods.LoadFirstClient
-let getByIdAndSecret: OAuthClientMethods.GetByIdAndSecret
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  OAuthClient = sequelize.define<OAuthClientInstance, OAuthClientAttributes>('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<OAuthClientModel> {
 
-  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 (file)
index 47d95d5..0000000
+++ /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<OAuthTokenInfo>
-  export type GetByTokenAndPopulateUser = (bearerToken: string) => Promise<OAuthTokenInstance>
-  export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Promise<OAuthTokenInstance>
-}
-
-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<OAuthTokenAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface OAuthTokenModel extends OAuthTokenClass, Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes> {}
index a82bff130a21e634ce983b6f4e2590415a718904..0d21c42fd26a31f02cca7718fdd73d27af5d0b87 100644 (file)
-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<OAuthTokenInstance, OAuthTokenAttributes>
-let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
-let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
-let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  OAuthToken = sequelize.define<OAuthTokenInstance, OAuthTokenAttributes>('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<OAuthTokenModel> {
 
-  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 (file)
index 4cb2994..0000000
+++ /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 (file)
index be1a491..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as Promise from 'bluebird'
-import * as Sequelize from 'sequelize'
-
-export namespace ServerMethods {
-  export type ListBadServers = () => Promise<ServerInstance[]>
-  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<ServerAttributes> {
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface ServerModel extends ServerClass, Sequelize.Model<ServerInstance, ServerAttributes> {}
index ebd216b082c81ed1103b61d6332b542bd1ac5fe3..edfd8010b9f5e6b8fd67ae36871c7c6366b569ac 100644 (file)
 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<ServerInstance, ServerAttributes>
-let updateServersScoreAndRemoveBadOnes: ServerMethods.UpdateServersScoreAndRemoveBadOnes
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Server = sequelize.define<ServerInstance, ServerAttributes>('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<ServerModel> {
+
+  @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)
+  }
 }
index 1bf61d2a693021aff86a88732587dfabe3ccd87b..1606453e0e45212e04cf9591dc119415ea0f1816 100644 (file)
@@ -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 (file)
index e17bbfa..0000000
+++ /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 (file)
index 08e5c32..0000000
+++ /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<TagInstance[]>
-}
-
-export interface TagClass {
-  findOrCreateTags: TagMethods.FindOrCreateTags
-}
-
-export interface TagAttributes {
-  name: string
-}
-
-export interface TagInstance extends TagClass, TagAttributes, Sequelize.Instance<TagAttributes> {
-  id: number
-}
-
-export interface TagModel extends TagClass, Sequelize.Model<TagInstance, TagAttributes> {}
index 0c0757fc809265152633e6e31da95aa3513e1bda..0ae74d80850bbe8f1b0d2a30754967863deb4a22 100644 (file)
@@ -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<TagInstance, TagAttributes>
-let findOrCreateTags: TagMethods.FindOrCreateTags
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Tag = sequelize.define<TagInstance, TagAttributes>('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<TagModel> {
 
-  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<TagInstance>[] = []
-  tags.forEach(tag => {
-    const query: Sequelize.FindOrInitializeOptions<TagAttributes> = {
-      where: {
-        name: tag
-      },
-      defaults: {
-        name: tag
+  Videos: VideoModel[]
+
+  static findOrCreateTags (tags: string[], transaction: Transaction) {
+    const tasks: Bluebird<TagModel>[] = []
+    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 (file)
index feafc4a..0000000
+++ /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<VideoAbuseInstance> >
-  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<VideoAbuseAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  Server: ServerInstance
-
-  toFormattedJSON: VideoAbuseMethods.ToFormattedJSON
-}
-
-export interface VideoAbuseModel extends VideoAbuseClass, Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes> {}
index d09f5f7a155c4cd545bd91ae675307209bd7dfe8..d0ee969fb10c89f13fdba06c1cd880c9ce60507c 100644 (file)
-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<VideoAbuseInstance, VideoAbuseAttributes>
-let toFormattedJSON: VideoAbuseMethods.ToFormattedJSON
-let listForApi: VideoAbuseMethods.ListForApi
-let toActivityPubObject: VideoAbuseMethods.ToActivityPubObject
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('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<VideoAbuseModel> {
 
-  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 (file)
index be2483d..0000000
+++ /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<BlacklistedVideoInstance> >
-  export type LoadByVideoId = (id: number) => Promise<BlacklistedVideoInstance>
-}
-
-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<BlacklistedVideoAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  toFormattedJSON: BlacklistedVideoMethods.ToFormattedJSON
-}
-
-export interface BlacklistedVideoModel
-  extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}
index ae8286285cd1bb37d1a29591625fc27834f7c8eb..6db56271907d9456d9f1239c4c1985158c5d9732 100644 (file)
-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<BlacklistedVideoInstance, BlacklistedVideoAttributes>
-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<BlacklistedVideoInstance, BlacklistedVideoAttributes>('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<VideoBlacklistModel> {
 
-// ------------------------------ 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 (file)
index 21f81e9..0000000
+++ /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<number>
-  export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> >
-  export type LoadByIdAndAccount = (id: number, accountId: number) => Promise<VideoChannelInstance>
-  export type ListByAccount = (accountId: number) => Promise< ResultList<VideoChannelInstance> >
-  export type LoadAndPopulateAccount = (id: number) => Promise<VideoChannelInstance>
-  export type LoadByUUIDAndPopulateAccount = (uuid: string) => Promise<VideoChannelInstance>
-  export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
-  export type LoadByHostAndUUID = (uuid: string, serverHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
-  export type LoadAndPopulateAccountAndVideos = (id: number) => Promise<VideoChannelInstance>
-  export type LoadByUrl = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
-  export type LoadByUUIDOrUrl = (uuid: string, url: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance>
-}
-
-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<VideoChannelAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  isOwned: VideoChannelMethods.IsOwned
-  toFormattedJSON: VideoChannelMethods.ToFormattedJSON
-  toActivityPubObject: VideoChannelMethods.ToActivityPubObject
-}
-
-export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {}
diff --git a/server/models/video/video-channel-share-interface.ts b/server/models/video/video-channel-share-interface.ts
deleted file mode 100644 (file)
index 2fff41a..0000000
+++ /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<AccountInstance[]>
-  export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird<VideoChannelShareInstance>
-}
-
-export interface VideoChannelShareClass {
-  loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
-  load: VideoChannelShareMethods.Load
-}
-
-export interface VideoChannelShareAttributes {
-  accountId: number
-  videoChannelId: number
-}
-
-export interface VideoChannelShareInstance
-  extends VideoChannelShareClass, VideoChannelShareAttributes, Sequelize.Instance<VideoChannelShareAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  Account?: AccountInstance
-  VideoChannel?: VideoChannelInstance
-}
-
-export interface VideoChannelShareModel
-  extends VideoChannelShareClass, Sequelize.Model<VideoChannelShareInstance, VideoChannelShareAttributes> {}
index 2e9b658a39574e0a6d5e39d630cda12be65517f2..cdba32fcdc92dbe7cb23cf224d949db8e269608b 100644 (file)
@@ -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<VideoChannelShareInstance, VideoChannelShareAttributes>
-let loadAccountsByShare: VideoChannelShareMethods.LoadAccountsByShare
-let load: VideoChannelShareMethods.Load
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoChannelShare = sequelize.define<VideoChannelShareInstance, VideoChannelShareAttributes>('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<VideoChannelShareModel> {
+  @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))
+  }
 }
index 54f12dce3216273d17925ea1e2eaca21e1f9ebc2..9b545a4efc1e9ca6595c9782664e83fed9365e6a 100644 (file)
 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<VideoChannelInstance, VideoChannelAttributes>
-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<VideoChannelInstance, VideoChannelAttributes>('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<VideoChannelModel> {
 
-  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<VideoChannelModel> = {
+      where: {
+        uuid
+      }
+    }
+
+    if (t !== undefined) query.transaction = t
 
-loadByUUID = function (uuid: string, t?: Sequelize.Transaction) {
-  const query: Sequelize.FindOptions<VideoChannelAttributes> = {
-    where: {
-      uuid
+    return VideoChannelModel.findOne(query)
+  }
+
+  static loadByUrl (url: string, t?: Sequelize.Transaction) {
+    const query: IFindOptions<VideoChannelModel> = {
+      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<VideoChannelModel> = {
+      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<VideoChannelAttributes> = {
-    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<VideoChannelModel> = {
+      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<VideoChannelAttributes> = {
-    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<VideoChannelAttributes> = {
-    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 (file)
index c9fb8b8..0000000
+++ /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<VideoFileAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface VideoFileModel extends VideoFileClass, Sequelize.Model<VideoFileInstance, VideoFileAttributes> {}
index 600141994afcf2c0ae509e5472f59922038bf553..df4067a4e59c4aa439a00af170f8c8e9fd6d41a0 100644 (file)
@@ -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<VideoFileInstance, VideoFileAttributes>
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoFile = sequelize.define<VideoFileInstance, VideoFileAttributes>('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<VideoFileModel> {
+  @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 (file)
index 2a63350..0000000
+++ /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<string>
-  export type CreateThumbnail = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
-  export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string
-  export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
-
-  export type ToActivityPubObject = (this: VideoInstance) => VideoTorrentObject
-
-  export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void>
-  export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void>
-  export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number>
-  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<VideoInstance[]>
-
-  export type ListAllAndSharedByAccountForOutbox = (
-    accountId: number,
-    start: number,
-    count: number
-  ) => Bluebird< ResultList<VideoInstance> >
-  export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
-  export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
-  export type SearchAndPopulateAccountAndServerAndTags = (
-    value: string,
-    start: number,
-    count: number,
-    sort: string
-  ) => Bluebird< ResultList<VideoInstance> >
-
-  export type Load = (id: number) => Bluebird<VideoInstance>
-  export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
-  export type LoadByUrlAndPopulateAccount = (url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
-  export type LoadAndPopulateAccountAndServerAndTags = (id: number) => Bluebird<VideoInstance>
-  export type LoadByUUIDAndPopulateAccountAndServerAndTags = (uuid: string) => Bluebird<VideoInstance>
-  export type LoadByUUIDOrURL = (uuid: string, url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance>
-
-  export type RemoveThumbnail = (this: VideoInstance) => Promise<void>
-  export type RemovePreview = (this: VideoInstance) => Promise<void>
-  export type RemoveFile = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
-  export type RemoveTorrent = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void>
-}
-
-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<VideoAttributes> {
-  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<TagAttributes, string>
-  addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
-  setVideoFiles: Sequelize.HasManySetAssociationsMixin<VideoFileAttributes, string>
-}
-
-export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
diff --git a/server/models/video/video-share-interface.ts b/server/models/video/video-share-interface.ts
deleted file mode 100644 (file)
index 3946303..0000000
+++ /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<AccountInstance[]>
-  export type Load = (accountId: number, videoId: number, t: Sequelize.Transaction) => Bluebird<VideoShareInstance>
-}
-
-export interface VideoShareClass {
-  loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
-  load: VideoShareMethods.Load
-}
-
-export interface VideoShareAttributes {
-  accountId: number
-  videoId: number
-}
-
-export interface VideoShareInstance extends VideoShareClass, VideoShareAttributes, Sequelize.Instance<VideoShareAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  Account?: AccountInstance
-  Video?: VideoInstance
-}
-
-export interface VideoShareModel extends VideoShareClass, Sequelize.Model<VideoShareInstance, VideoShareAttributes> {}
index 37e405fa9810dd519dbcd02ea5d1d31961cf9fc7..01b6d3d34ab698c5f25eb821cb0c89288f0b8239 100644 (file)
@@ -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<VideoShareInstance, VideoShareAttributes>
-let loadAccountsByShare: VideoShareMethods.LoadAccountsByShare
-let load: VideoShareMethods.Load
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoShare = sequelize.define<VideoShareInstance, VideoShareAttributes>('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<VideoShareModel> {
+  @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 (file)
index f928cec..0000000
+++ /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<VideoTagAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface VideoTagModel extends VideoTagClass, Sequelize.Model<VideoTagInstance, VideoTagAttributes> {}
index ac45374f8fd3dd68323682c3a44225a3de005098..ca15e34266880f0950b044a85364be3f9d72b536 100644 (file)
@@ -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<VideoTagModel> {
+  @CreatedAt
+  createdAt: Date
 
-let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes>
+  @UpdatedAt
+  updatedAt: Date
 
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoTag = sequelize.define<VideoTagInstance, VideoTagAttributes>('VideoTag', {}, {
-    indexes: [
-      {
-        fields: [ 'videoId' ]
-      },
-      {
-        fields: [ 'tagId' ]
-      }
-    ]
-  })
+  @ForeignKey(() => VideoModel)
+  @Column
+  videoId: number
 
-  return VideoTag
+  @ForeignKey(() => TagModel)
+  @Column
+  tagId: number
 }
index d46fdeebe162aa852a07e38ecdee984a85da534c..9e26f9bbedb2c34568fd7fb814524e433eb7d9e4 100644 (file)
@@ -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<VideoInstance, VideoAttributes>
-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<VideoInstance, VideoAttributes>('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<VideoModel> {
+
+  @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<VideoModel> = {
+      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<VideoModel> = {
+      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<VideoModel> = {
+      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<TagInstance, string>(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<VideoModel> = {
+      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<TagModel, string>(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<VideoAttributes> = {
-    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<VideoAttributes> = {
-    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<VideoAttributes> = {
-    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<VideoAttributes> = {
-    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)
 }
index 954fa426ecc213e9107a588747d55b0d862d9c1d..cc32c768d9dace81751877154afdb7fbd0d65535 100644 (file)
@@ -1,5 +1,4 @@
 import { UserRight } from './user-right.enum'
-import user from '../../../server/models/account/user'
 
 // Keep the order
 export enum UserRole {
index be910b309a00ed306741804874042e7f466452e0..71674e165cb8856ce6dcb9f06d3cffc43352b841 100644 (file)
@@ -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"
index 986a1643a0b57b63b408e6f1fcdfc539dce9b931..cdf224c5c3ac909271f73f19f9f2bcad399d7989 100644 (file)
--- 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"
 
   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"
   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"
     "@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"