Reorganize model files
authorChocobozzz <florian.bigard@gmail.com>
Fri, 16 Jun 2017 07:45:46 +0000 (09:45 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Fri, 16 Jun 2017 07:45:46 +0000 (09:45 +0200)
77 files changed:
server/initializers/database.ts
server/models/application-interface.ts [deleted file]
server/models/application.ts [deleted file]
server/models/application/application-interface.ts [new file with mode: 0644]
server/models/application/application.ts [new file with mode: 0644]
server/models/application/index.ts [new file with mode: 0644]
server/models/author-interface.ts [deleted file]
server/models/author.ts [deleted file]
server/models/index.ts
server/models/job-interface.ts [deleted file]
server/models/job.ts [deleted file]
server/models/job/index.ts [new file with mode: 0644]
server/models/job/job-interface.ts [new file with mode: 0644]
server/models/job/job.ts [new file with mode: 0644]
server/models/oauth-client-interface.ts [deleted file]
server/models/oauth-client.ts [deleted file]
server/models/oauth-token-interface.ts [deleted file]
server/models/oauth-token.ts [deleted file]
server/models/oauth/index.ts [new file with mode: 0644]
server/models/oauth/oauth-client-interface.ts [new file with mode: 0644]
server/models/oauth/oauth-client.ts [new file with mode: 0644]
server/models/oauth/oauth-token-interface.ts [new file with mode: 0644]
server/models/oauth/oauth-token.ts [new file with mode: 0644]
server/models/pod-interface.ts [deleted file]
server/models/pod.ts [deleted file]
server/models/pod/index.ts [new file with mode: 0644]
server/models/pod/pod-interface.ts [new file with mode: 0644]
server/models/pod/pod.ts [new file with mode: 0644]
server/models/request-interface.ts [deleted file]
server/models/request-to-pod-interface.ts [deleted file]
server/models/request-to-pod.ts [deleted file]
server/models/request-video-event-interface.ts [deleted file]
server/models/request-video-event.ts [deleted file]
server/models/request-video-qadu-interface.ts [deleted file]
server/models/request-video-qadu.ts [deleted file]
server/models/request.ts [deleted file]
server/models/request/index.ts [new file with mode: 0644]
server/models/request/request-interface.ts [new file with mode: 0644]
server/models/request/request-to-pod-interface.ts [new file with mode: 0644]
server/models/request/request-to-pod.ts [new file with mode: 0644]
server/models/request/request-video-event-interface.ts [new file with mode: 0644]
server/models/request/request-video-event.ts [new file with mode: 0644]
server/models/request/request-video-qadu-interface.ts [new file with mode: 0644]
server/models/request/request-video-qadu.ts [new file with mode: 0644]
server/models/request/request.ts [new file with mode: 0644]
server/models/tag-interface.ts [deleted file]
server/models/tag.ts [deleted file]
server/models/user-interface.ts [deleted file]
server/models/user-video-rate-interface.ts [deleted file]
server/models/user-video-rate.ts [deleted file]
server/models/user.ts [deleted file]
server/models/user/index.ts [new file with mode: 0644]
server/models/user/user-interface.ts [new file with mode: 0644]
server/models/user/user-video-rate-interface.ts [new file with mode: 0644]
server/models/user/user-video-rate.ts [new file with mode: 0644]
server/models/user/user.ts [new file with mode: 0644]
server/models/video-abuse-interface.ts [deleted file]
server/models/video-abuse.ts [deleted file]
server/models/video-blacklist-interface.ts [deleted file]
server/models/video-blacklist.ts [deleted file]
server/models/video-interface.ts [deleted file]
server/models/video-tag-interface.ts [deleted file]
server/models/video-tag.ts [deleted file]
server/models/video.ts [deleted file]
server/models/video/author-interface.ts [new file with mode: 0644]
server/models/video/author.ts [new file with mode: 0644]
server/models/video/index.ts [new file with mode: 0644]
server/models/video/tag-interface.ts [new file with mode: 0644]
server/models/video/tag.ts [new file with mode: 0644]
server/models/video/video-abuse-interface.ts [new file with mode: 0644]
server/models/video/video-abuse.ts [new file with mode: 0644]
server/models/video/video-blacklist-interface.ts [new file with mode: 0644]
server/models/video/video-blacklist.ts [new file with mode: 0644]
server/models/video/video-interface.ts [new file with mode: 0644]
server/models/video/video-tag-interface.ts [new file with mode: 0644]
server/models/video/video-tag.ts [new file with mode: 0644]
server/models/video/video.ts [new file with mode: 0644]

index 0ab9e98dbcbbd9473eb4a16b060fc5770b90b28a..1662c1968f2fb102111ac3ee7035c472233e8d2d 100644 (file)
@@ -1,6 +1,7 @@
 import * as fs from 'fs'
 import { join } from 'path'
 import * as Sequelize from 'sequelize'
+import { each } from 'async'
 
 import { CONFIG } from './constants'
 // Do not use barrel, we need to load database first
@@ -72,24 +73,13 @@ const sequelize = new Sequelize(dbname, username, password, {
 database.sequelize = sequelize
 
 database.init = function (silent: boolean, callback: (err: Error) => void) {
-
   const modelDirectory = join(__dirname, '..', 'models')
-  fs.readdir(modelDirectory, function (err, files) {
-    if (err) throw err
 
-    files.filter(function (file) {
-      // For all models but not utils.js
-      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
+  getModelFiles(modelDirectory, function (err, filePaths) {
+    if (err) throw err
 
-      return true
-    })
-    .forEach(function (file) {
-      const model = sequelize.import(join(modelDirectory, file))
+    filePaths.forEach(function (filePath) {
+      const model = sequelize.import(filePath)
 
       database[model['name']] = model
     })
@@ -111,3 +101,51 @@ database.init = function (silent: boolean, callback: (err: Error) => void) {
 export {
   database
 }
+
+// ---------------------------------------------------------------------------
+
+function getModelFiles (modelDirectory: string, callback: (err: Error, filePaths: string[]) => void) {
+  fs.readdir(modelDirectory, function (err, files) {
+    if (err) throw err
+
+    const directories = files.filter(function (directory) {
+      // For all models but not utils.js
+      if (
+        directory === 'index.js' || directory === 'index.ts' ||
+        directory === 'utils.js' || directory === 'utils.ts'
+      ) return false
+
+      return true
+    })
+
+    let modelFilePaths: string[] = []
+
+    // For each directory we read it and append model in the modelFilePaths array
+    each(directories, function (directory: string, eachCallback: ErrorCallback<Error>) {
+      const modelDirectoryPath = join(modelDirectory, directory)
+
+      fs.readdir(modelDirectoryPath, function (err, files) {
+        if (err) return eachCallback(err)
+
+        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 => {
+          return join(modelDirectoryPath, file)
+        })
+
+        modelFilePaths = modelFilePaths.concat(filteredFiles)
+
+        return eachCallback(null)
+      })
+    }, function(err: Error) {
+      return callback(err, modelFilePaths)
+    })
+  })
+}
diff --git a/server/models/application-interface.ts b/server/models/application-interface.ts
deleted file mode 100644 (file)
index c03513d..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace ApplicationMethods {
-  export type LoadMigrationVersionCallback = (err: Error, version: number) => void
-  export type LoadMigrationVersion = (callback: LoadMigrationVersionCallback) => void
-
-  export type UpdateMigrationVersionCallback = (err: Error, applicationInstance: ApplicationAttributes) => void
-  export type UpdateMigrationVersion = (newVersion: number, transaction: Sequelize.Transaction, callback: UpdateMigrationVersionCallback) => void
-}
-
-export interface ApplicationClass {
-  loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
-  updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
-}
-
-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> {}
diff --git a/server/models/application.ts b/server/models/application.ts
deleted file mode 100644 (file)
index 55bb40d..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from './utils'
-import {
-  ApplicationClass,
-  ApplicationAttributes,
-  ApplicationInstance,
-
-  ApplicationMethods
-} from './application-interface'
-
-let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes>
-let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
-let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
-
-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
-        }
-      }
-    }
-  )
-
-  const classMethods = [ loadMigrationVersion, updateMigrationVersion ]
-  addMethodsToModel(Application, classMethods)
-
-  return Application
-}
-
-// ---------------------------------------------------------------------------
-
-loadMigrationVersion = function (callback: ApplicationMethods.LoadMigrationVersionCallback) {
-  const query = {
-    attributes: [ 'migrationVersion' ]
-  }
-
-  return Application.findOne(query).asCallback(function (err, data) {
-    const version = data ? data.migrationVersion : null
-
-    return callback(err, version)
-  })
-}
-
-updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction, callback: ApplicationMethods.UpdateMigrationVersionCallback) {
-  const options: Sequelize.UpdateOptions = {
-    where: {},
-    transaction: transaction
-  }
-
-  return Application.update({ migrationVersion: newVersion }, options).asCallback(callback)
-}
diff --git a/server/models/application/application-interface.ts b/server/models/application/application-interface.ts
new file mode 100644 (file)
index 0000000..c03513d
--- /dev/null
@@ -0,0 +1,26 @@
+import * as Sequelize from 'sequelize'
+
+export namespace ApplicationMethods {
+  export type LoadMigrationVersionCallback = (err: Error, version: number) => void
+  export type LoadMigrationVersion = (callback: LoadMigrationVersionCallback) => void
+
+  export type UpdateMigrationVersionCallback = (err: Error, applicationInstance: ApplicationAttributes) => void
+  export type UpdateMigrationVersion = (newVersion: number, transaction: Sequelize.Transaction, callback: UpdateMigrationVersionCallback) => void
+}
+
+export interface ApplicationClass {
+  loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
+  updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
+}
+
+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> {}
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
new file mode 100644 (file)
index 0000000..0e9a1eb
--- /dev/null
@@ -0,0 +1,57 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import {
+  ApplicationClass,
+  ApplicationAttributes,
+  ApplicationInstance,
+
+  ApplicationMethods
+} from './application-interface'
+
+let Application: Sequelize.Model<ApplicationInstance, ApplicationAttributes>
+let loadMigrationVersion: ApplicationMethods.LoadMigrationVersion
+let updateMigrationVersion: ApplicationMethods.UpdateMigrationVersion
+
+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
+        }
+      }
+    }
+  )
+
+  const classMethods = [ loadMigrationVersion, updateMigrationVersion ]
+  addMethodsToModel(Application, classMethods)
+
+  return Application
+}
+
+// ---------------------------------------------------------------------------
+
+loadMigrationVersion = function (callback: ApplicationMethods.LoadMigrationVersionCallback) {
+  const query = {
+    attributes: [ 'migrationVersion' ]
+  }
+
+  return Application.findOne(query).asCallback(function (err, data) {
+    const version = data ? data.migrationVersion : null
+
+    return callback(err, version)
+  })
+}
+
+updateMigrationVersion = function (newVersion: number, transaction: Sequelize.Transaction, callback: ApplicationMethods.UpdateMigrationVersionCallback) {
+  const options: Sequelize.UpdateOptions = {
+    where: {},
+    transaction: transaction
+  }
+
+  return Application.update({ migrationVersion: newVersion }, options).asCallback(callback)
+}
diff --git a/server/models/application/index.ts b/server/models/application/index.ts
new file mode 100644 (file)
index 0000000..706f85c
--- /dev/null
@@ -0,0 +1 @@
+export * from './application-interface'
diff --git a/server/models/author-interface.ts b/server/models/author-interface.ts
deleted file mode 100644 (file)
index b57ce2a..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { PodInstance } from './pod-interface'
-
-export namespace AuthorMethods {
-  export type FindOrCreateAuthorCallback = (err: Error, authorInstance?: AuthorInstance) => void
-  export type FindOrCreateAuthor = (name: string, podId: number, userId: number, transaction: Sequelize.Transaction, callback: FindOrCreateAuthorCallback) => void
-}
-
-export interface AuthorClass {
-  findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
-}
-
-export interface AuthorAttributes {
-  name: string
-}
-
-export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  podId: number
-  Pod: PodInstance
-}
-
-export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {}
diff --git a/server/models/author.ts b/server/models/author.ts
deleted file mode 100644 (file)
index e0fb250..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { isUserUsernameValid } from '../helpers'
-
-import { addMethodsToModel } from './utils'
-import {
-  AuthorClass,
-  AuthorInstance,
-  AuthorAttributes,
-
-  AuthorMethods
-} from './author-interface'
-
-let Author: Sequelize.Model<AuthorInstance, AuthorAttributes>
-let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
-
-export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author',
-    {
-      name: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          usernameValid: function (value) {
-            const res = isUserUsernameValid(value)
-            if (res === false) throw new Error('Username is not valid.')
-          }
-        }
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'name' ]
-        },
-        {
-          fields: [ 'podId' ]
-        },
-        {
-          fields: [ 'userId' ],
-          unique: true
-        },
-        {
-          fields: [ 'name', 'podId' ],
-          unique: true
-        }
-      ]
-    }
-  )
-
-  const classMethods = [ associate, findOrCreateAuthor ]
-  addMethodsToModel(Author, classMethods)
-
-  return Author
-}
-
-// ---------------------------------------------------------------------------
-
-function associate (models) {
-  Author.belongsTo(models.Pod, {
-    foreignKey: {
-      name: 'podId',
-      allowNull: true
-    },
-    onDelete: 'cascade'
-  })
-
-  Author.belongsTo(models.User, {
-    foreignKey: {
-      name: 'userId',
-      allowNull: true
-    },
-    onDelete: 'cascade'
-  })
-}
-
-findOrCreateAuthor = function (
-  name: string,
-  podId: number,
-  userId: number,
-  transaction: Sequelize.Transaction,
-  callback: AuthorMethods.FindOrCreateAuthorCallback
-) {
-  const author = {
-    name,
-    podId,
-    userId
-  }
-
-  const query: any = {
-    where: author,
-    defaults: author
-  }
-
-  if (transaction !== null) query.transaction = transaction
-
-  Author.findOrCreate(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    // [ instance, wasCreated ]
-    return callback(null, result[0])
-  })
-}
index 432c0dc4ae459053523dcf37d6cf4060e860bbf0..b392a8a7761033a7ab50dfc752fabf5311e70e75 100644 (file)
@@ -1,17 +1,7 @@
-export * from './application-interface'
-export * from './author-interface'
-export * from './job-interface'
-export * from './oauth-client-interface'
-export * from './oauth-token-interface'
-export * from './pod-interface'
-export * from './request-interface'
-export * from './request-to-pod-interface'
-export * from './request-video-event-interface'
-export * from './request-video-qadu-interface'
-export * from './tag-interface'
-export * from './user-video-rate-interface'
-export * from './user-interface'
-export * from './video-abuse-interface'
-export * from './video-blacklist-interface'
-export * from './video-tag-interface'
-export * from './video-interface'
+export * from './application'
+export * from './job'
+export * from './oauth'
+export * from './pod'
+export * from './request'
+export * from './user'
+export * from './video'
diff --git a/server/models/job-interface.ts b/server/models/job-interface.ts
deleted file mode 100644 (file)
index ab66782..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace JobMethods {
-  export type ListWithLimitCallback = (err: Error, jobInstances: JobInstance[]) => void
-  export type ListWithLimit = (limit: number, state: string, callback: ListWithLimitCallback) => void
-}
-
-export interface JobClass {
-  listWithLimit: JobMethods.ListWithLimit
-}
-
-export interface JobAttributes {
-  state: string
-  handlerName: string
-  handlerInputData: object
-}
-
-export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance<JobAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {}
diff --git a/server/models/job.ts b/server/models/job.ts
deleted file mode 100644 (file)
index d4380a5..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { JOB_STATES } from '../initializers'
-
-import { addMethodsToModel } from './utils'
-import {
-  JobClass,
-  JobInstance,
-  JobAttributes,
-
-  JobMethods
-} from './job-interface'
-
-let Job: Sequelize.Model<JobInstance, JobAttributes>
-let listWithLimit: JobMethods.ListWithLimit
-
-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
-      },
-      handlerName: {
-        type: DataTypes.STRING,
-        allowNull: false
-      },
-      handlerInputData: {
-        type: DataTypes.JSON,
-        allowNull: true
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'state' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [ listWithLimit ]
-  addMethodsToModel(Job, classMethods)
-
-  return Job
-}
-
-// ---------------------------------------------------------------------------
-
-listWithLimit = function (limit: number, state: string, callback: JobMethods.ListWithLimitCallback) {
-  const query = {
-    order: [
-      [ 'id', 'ASC' ]
-    ],
-    limit: limit,
-    where: {
-      state
-    }
-  }
-
-  return Job.findAll(query).asCallback(callback)
-}
diff --git a/server/models/job/index.ts b/server/models/job/index.ts
new file mode 100644 (file)
index 0000000..56925fd
--- /dev/null
@@ -0,0 +1 @@
+export * from './job-interface'
diff --git a/server/models/job/job-interface.ts b/server/models/job/job-interface.ts
new file mode 100644 (file)
index 0000000..ab66782
--- /dev/null
@@ -0,0 +1,24 @@
+import * as Sequelize from 'sequelize'
+
+export namespace JobMethods {
+  export type ListWithLimitCallback = (err: Error, jobInstances: JobInstance[]) => void
+  export type ListWithLimit = (limit: number, state: string, callback: ListWithLimitCallback) => void
+}
+
+export interface JobClass {
+  listWithLimit: JobMethods.ListWithLimit
+}
+
+export interface JobAttributes {
+  state: string
+  handlerName: string
+  handlerInputData: object
+}
+
+export interface JobInstance extends JobClass, JobAttributes, Sequelize.Instance<JobAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface JobModel extends JobClass, Sequelize.Model<JobInstance, JobAttributes> {}
diff --git a/server/models/job/job.ts b/server/models/job/job.ts
new file mode 100644 (file)
index 0000000..60a6c55
--- /dev/null
@@ -0,0 +1,63 @@
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { JOB_STATES } from '../../initializers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  JobClass,
+  JobInstance,
+  JobAttributes,
+
+  JobMethods
+} from './job-interface'
+
+let Job: Sequelize.Model<JobInstance, JobAttributes>
+let listWithLimit: JobMethods.ListWithLimit
+
+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
+      },
+      handlerName: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      handlerInputData: {
+        type: DataTypes.JSON,
+        allowNull: true
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'state' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [ listWithLimit ]
+  addMethodsToModel(Job, classMethods)
+
+  return Job
+}
+
+// ---------------------------------------------------------------------------
+
+listWithLimit = function (limit: number, state: string, callback: JobMethods.ListWithLimitCallback) {
+  const query = {
+    order: [
+      [ 'id', 'ASC' ]
+    ],
+    limit: limit,
+    where: {
+      state
+    }
+  }
+
+  return Job.findAll(query).asCallback(callback)
+}
diff --git a/server/models/oauth-client-interface.ts b/server/models/oauth-client-interface.ts
deleted file mode 100644 (file)
index 3b43257..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace OAuthClientMethods {
-  export type CountTotalCallback = (err: Error, total: number) => void
-  export type CountTotal = (callback: CountTotalCallback) => void
-
-  export type LoadFirstClientCallback = (err: Error, client: OAuthClientInstance) => void
-  export type LoadFirstClient = (callback: LoadFirstClientCallback) => void
-
-  export type GetByIdAndSecret = (clientId, clientSecret) => void
-}
-
-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> {}
diff --git a/server/models/oauth-client.ts b/server/models/oauth-client.ts
deleted file mode 100644 (file)
index a5438f4..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from './utils'
-import {
-  OAuthClientClass,
-  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',
-    {
-      clientId: {
-        type: DataTypes.STRING,
-        allowNull: false
-      },
-      clientSecret: {
-        type: DataTypes.STRING,
-        allowNull: false
-      },
-      grants: {
-        type: DataTypes.ARRAY(DataTypes.STRING)
-      },
-      redirectUris: {
-        type: DataTypes.ARRAY(DataTypes.STRING)
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'clientId' ],
-          unique: true
-        },
-        {
-          fields: [ 'clientId', 'clientSecret' ],
-          unique: true
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    countTotal,
-    getByIdAndSecret,
-    loadFirstClient
-  ]
-  addMethodsToModel(OAuthClient, classMethods)
-
-  return OAuthClient
-}
-
-// ---------------------------------------------------------------------------
-
-function associate (models) {
-  OAuthClient.hasMany(models.OAuthToken, {
-    foreignKey: 'oAuthClientId',
-    onDelete: 'cascade'
-  })
-}
-
-countTotal = function (callback: OAuthClientMethods.CountTotalCallback) {
-  return OAuthClient.count().asCallback(callback)
-}
-
-loadFirstClient = function (callback: OAuthClientMethods.LoadFirstClientCallback) {
-  return OAuthClient.findOne().asCallback(callback)
-}
-
-getByIdAndSecret = function (clientId: string, clientSecret: string) {
-  const query = {
-    where: {
-      clientId: clientId,
-      clientSecret: clientSecret
-    }
-  }
-
-  return OAuthClient.findOne(query)
-}
diff --git a/server/models/oauth-token-interface.ts b/server/models/oauth-token-interface.ts
deleted file mode 100644 (file)
index 8852669..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-import * as Sequelize from 'sequelize'
-import * as Bluebird from 'bluebird'
-
-import { UserModel } from './user-interface'
-
-export type OAuthTokenInfo = {
-  refreshToken: string
-  refreshTokenExpiresAt: Date,
-  client: {
-    id: number
-  },
-  user: {
-    id: number
-  }
-}
-
-export namespace OAuthTokenMethods {
-  export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Bluebird<OAuthTokenInfo>
-  export type GetByTokenAndPopulateUser = (bearerToken: string) => Bluebird<OAuthTokenInstance>
-  export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Bluebird<OAuthTokenInstance>
-
-  export type RemoveByUserIdCallback = (err: Error) => void
-  export type RemoveByUserId = (userId, callback) => void
-}
-
-export interface OAuthTokenClass {
-  getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
-  getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
-  getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
-  removeByUserId: OAuthTokenMethods.RemoveByUserId
-}
-
-export interface OAuthTokenAttributes {
-  accessToken: string
-  accessTokenExpiresAt: Date
-  refreshToken: string
-  refreshTokenExpiresAt: Date
-
-  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> {}
diff --git a/server/models/oauth-token.ts b/server/models/oauth-token.ts
deleted file mode 100644 (file)
index 91cef11..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { logger } from '../helpers'
-
-import { addMethodsToModel } from './utils'
-import {
-  OAuthTokenClass,
-  OAuthTokenInstance,
-  OAuthTokenAttributes,
-
-  OAuthTokenMethods,
-  OAuthTokenInfo
-} from './oauth-token-interface'
-
-let OAuthToken: Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes>
-let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
-let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
-let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
-let removeByUserId: OAuthTokenMethods.RemoveByUserId
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  OAuthToken = sequelize.define<OAuthTokenInstance, OAuthTokenAttributes>('OAuthToken',
-    {
-      accessToken: {
-        type: DataTypes.STRING,
-        allowNull: false
-      },
-      accessTokenExpiresAt: {
-        type: DataTypes.DATE,
-        allowNull: false
-      },
-      refreshToken: {
-        type: DataTypes.STRING,
-        allowNull: false
-      },
-      refreshTokenExpiresAt: {
-        type: DataTypes.DATE,
-        allowNull: false
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'refreshToken' ],
-          unique: true
-        },
-        {
-          fields: [ 'accessToken' ],
-          unique: true
-        },
-        {
-          fields: [ 'userId' ]
-        },
-        {
-          fields: [ 'oAuthClientId' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    getByRefreshTokenAndPopulateClient,
-    getByTokenAndPopulateUser,
-    getByRefreshTokenAndPopulateUser,
-    removeByUserId
-  ]
-  addMethodsToModel(OAuthToken, classMethods)
-
-  return OAuthToken
-}
-
-// ---------------------------------------------------------------------------
-
-function associate (models) {
-  OAuthToken.belongsTo(models.User, {
-    foreignKey: {
-      name: 'userId',
-      allowNull: false
-    },
-    onDelete: 'cascade'
-  })
-
-  OAuthToken.belongsTo(models.OAuthClient, {
-    foreignKey: {
-      name: 'oAuthClientId',
-      allowNull: false
-    },
-    onDelete: 'cascade'
-  })
-}
-
-getByRefreshTokenAndPopulateClient = function (refreshToken: string) {
-  const query = {
-    where: {
-      refreshToken: refreshToken
-    },
-    include: [ OAuthToken['sequelize'].models.OAuthClient ]
-  }
-
-  return OAuthToken.findOne(query).then(function (token) {
-    if (!token) return null
-
-    const tokenInfos: OAuthTokenInfo = {
-      refreshToken: token.refreshToken,
-      refreshTokenExpiresAt: token.refreshTokenExpiresAt,
-      client: {
-        id: token['client'].id
-      },
-      user: {
-        id: token['user']
-      }
-    }
-
-    return tokenInfos
-  }).catch(function (err) {
-    logger.info('getRefreshToken error.', { error: err })
-  })
-}
-
-getByTokenAndPopulateUser = function (bearerToken: string) {
-  const query = {
-    where: {
-      accessToken: bearerToken
-    },
-    include: [ OAuthToken['sequelize'].models.User ]
-  }
-
-  return OAuthToken.findOne(query).then(function (token) {
-    if (token) token['user'] = token.User
-
-    return token
-  })
-}
-
-getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
-  const query = {
-    where: {
-      refreshToken: refreshToken
-    },
-    include: [ OAuthToken['sequelize'].models.User ]
-  }
-
-  return OAuthToken.findOne(query).then(function (token) {
-    token['user'] = token.User
-
-    return token
-  })
-}
-
-removeByUserId = function (userId, callback) {
-  const query = {
-    where: {
-      userId: userId
-    }
-  }
-
-  return OAuthToken.destroy(query).asCallback(callback)
-}
diff --git a/server/models/oauth/index.ts b/server/models/oauth/index.ts
new file mode 100644 (file)
index 0000000..a20d3a5
--- /dev/null
@@ -0,0 +1,2 @@
+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
new file mode 100644 (file)
index 0000000..3b43257
--- /dev/null
@@ -0,0 +1,32 @@
+import * as Sequelize from 'sequelize'
+
+export namespace OAuthClientMethods {
+  export type CountTotalCallback = (err: Error, total: number) => void
+  export type CountTotal = (callback: CountTotalCallback) => void
+
+  export type LoadFirstClientCallback = (err: Error, client: OAuthClientInstance) => void
+  export type LoadFirstClient = (callback: LoadFirstClientCallback) => void
+
+  export type GetByIdAndSecret = (clientId, clientSecret) => void
+}
+
+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> {}
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts
new file mode 100644 (file)
index 0000000..fbc2a33
--- /dev/null
@@ -0,0 +1,87 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import {
+  OAuthClientClass,
+  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',
+    {
+      clientId: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      clientSecret: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      grants: {
+        type: DataTypes.ARRAY(DataTypes.STRING)
+      },
+      redirectUris: {
+        type: DataTypes.ARRAY(DataTypes.STRING)
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'clientId' ],
+          unique: true
+        },
+        {
+          fields: [ 'clientId', 'clientSecret' ],
+          unique: true
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    countTotal,
+    getByIdAndSecret,
+    loadFirstClient
+  ]
+  addMethodsToModel(OAuthClient, classMethods)
+
+  return OAuthClient
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+  OAuthClient.hasMany(models.OAuthToken, {
+    foreignKey: 'oAuthClientId',
+    onDelete: 'cascade'
+  })
+}
+
+countTotal = function (callback: OAuthClientMethods.CountTotalCallback) {
+  return OAuthClient.count().asCallback(callback)
+}
+
+loadFirstClient = function (callback: OAuthClientMethods.LoadFirstClientCallback) {
+  return OAuthClient.findOne().asCallback(callback)
+}
+
+getByIdAndSecret = function (clientId: string, clientSecret: string) {
+  const query = {
+    where: {
+      clientId: clientId,
+      clientSecret: clientSecret
+    }
+  }
+
+  return OAuthClient.findOne(query)
+}
diff --git a/server/models/oauth/oauth-token-interface.ts b/server/models/oauth/oauth-token-interface.ts
new file mode 100644 (file)
index 0000000..815ad5e
--- /dev/null
@@ -0,0 +1,48 @@
+import * as Sequelize from 'sequelize'
+import * as Bluebird from 'bluebird'
+
+import { UserModel } from '../user'
+
+export type OAuthTokenInfo = {
+  refreshToken: string
+  refreshTokenExpiresAt: Date,
+  client: {
+    id: number
+  },
+  user: {
+    id: number
+  }
+}
+
+export namespace OAuthTokenMethods {
+  export type GetByRefreshTokenAndPopulateClient = (refreshToken: string) => Bluebird<OAuthTokenInfo>
+  export type GetByTokenAndPopulateUser = (bearerToken: string) => Bluebird<OAuthTokenInstance>
+  export type GetByRefreshTokenAndPopulateUser = (refreshToken: string) => Bluebird<OAuthTokenInstance>
+
+  export type RemoveByUserIdCallback = (err: Error) => void
+  export type RemoveByUserId = (userId, callback) => void
+}
+
+export interface OAuthTokenClass {
+  getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
+  getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
+  getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
+  removeByUserId: OAuthTokenMethods.RemoveByUserId
+}
+
+export interface OAuthTokenAttributes {
+  accessToken: string
+  accessTokenExpiresAt: Date
+  refreshToken: string
+  refreshTokenExpiresAt: Date
+
+  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> {}
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
new file mode 100644 (file)
index 0000000..eab9cf8
--- /dev/null
@@ -0,0 +1,160 @@
+import * as Sequelize from 'sequelize'
+
+import { logger } from '../../helpers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  OAuthTokenClass,
+  OAuthTokenInstance,
+  OAuthTokenAttributes,
+
+  OAuthTokenMethods,
+  OAuthTokenInfo
+} from './oauth-token-interface'
+
+let OAuthToken: Sequelize.Model<OAuthTokenInstance, OAuthTokenAttributes>
+let getByRefreshTokenAndPopulateClient: OAuthTokenMethods.GetByRefreshTokenAndPopulateClient
+let getByTokenAndPopulateUser: OAuthTokenMethods.GetByTokenAndPopulateUser
+let getByRefreshTokenAndPopulateUser: OAuthTokenMethods.GetByRefreshTokenAndPopulateUser
+let removeByUserId: OAuthTokenMethods.RemoveByUserId
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  OAuthToken = sequelize.define<OAuthTokenInstance, OAuthTokenAttributes>('OAuthToken',
+    {
+      accessToken: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      accessTokenExpiresAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      },
+      refreshToken: {
+        type: DataTypes.STRING,
+        allowNull: false
+      },
+      refreshTokenExpiresAt: {
+        type: DataTypes.DATE,
+        allowNull: false
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'refreshToken' ],
+          unique: true
+        },
+        {
+          fields: [ 'accessToken' ],
+          unique: true
+        },
+        {
+          fields: [ 'userId' ]
+        },
+        {
+          fields: [ 'oAuthClientId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    getByRefreshTokenAndPopulateClient,
+    getByTokenAndPopulateUser,
+    getByRefreshTokenAndPopulateUser,
+    removeByUserId
+  ]
+  addMethodsToModel(OAuthToken, classMethods)
+
+  return OAuthToken
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+  OAuthToken.belongsTo(models.User, {
+    foreignKey: {
+      name: 'userId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+
+  OAuthToken.belongsTo(models.OAuthClient, {
+    foreignKey: {
+      name: 'oAuthClientId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+}
+
+getByRefreshTokenAndPopulateClient = function (refreshToken: string) {
+  const query = {
+    where: {
+      refreshToken: refreshToken
+    },
+    include: [ OAuthToken['sequelize'].models.OAuthClient ]
+  }
+
+  return OAuthToken.findOne(query).then(function (token) {
+    if (!token) return null
+
+    const tokenInfos: OAuthTokenInfo = {
+      refreshToken: token.refreshToken,
+      refreshTokenExpiresAt: token.refreshTokenExpiresAt,
+      client: {
+        id: token['client'].id
+      },
+      user: {
+        id: token['user']
+      }
+    }
+
+    return tokenInfos
+  }).catch(function (err) {
+    logger.info('getRefreshToken error.', { error: err })
+  })
+}
+
+getByTokenAndPopulateUser = function (bearerToken: string) {
+  const query = {
+    where: {
+      accessToken: bearerToken
+    },
+    include: [ OAuthToken['sequelize'].models.User ]
+  }
+
+  return OAuthToken.findOne(query).then(function (token) {
+    if (token) token['user'] = token.User
+
+    return token
+  })
+}
+
+getByRefreshTokenAndPopulateUser = function (refreshToken: string) {
+  const query = {
+    where: {
+      refreshToken: refreshToken
+    },
+    include: [ OAuthToken['sequelize'].models.User ]
+  }
+
+  return OAuthToken.findOne(query).then(function (token) {
+    token['user'] = token.User
+
+    return token
+  })
+}
+
+removeByUserId = function (userId, callback) {
+  const query = {
+    where: {
+      userId: userId
+    }
+  }
+
+  return OAuthToken.destroy(query).asCallback(callback)
+}
diff --git a/server/models/pod-interface.ts b/server/models/pod-interface.ts
deleted file mode 100644 (file)
index 8f362bd..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-// Don't use barrel, import just what we need
-import { Pod as FormatedPod } from '../../shared/models/pod.model'
-
-export namespace PodMethods {
-  export type ToFormatedJSON = () => FormatedPod
-
-  export type CountAllCallback = (err: Error, total: number) => void
-  export type CountAll = (callback) => void
-
-  export type IncrementScoresCallback = (err: Error) => void
-  export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void
-
-  export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void
-  export type List = (callback: ListCallback) => void
-
-  export type ListAllIdsCallback = (err: Error, ids?: number[]) => void
-  export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void
-
-  export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void
-  export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void
-
-  export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void
-  export type ListBadPods = (callback: ListBadPodsCallback) => void
-
-  export type LoadCallback = (err: Error, podInstance: PodInstance) => void
-  export type Load = (id: number, callback: LoadCallback) => void
-
-  export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void
-  export type LoadByHost = (host: string, callback: LoadByHostCallback) => void
-
-  export type RemoveAllCallback = (err: Error) => void
-  export type RemoveAll = (callback: RemoveAllCallback) => void
-
-  export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void
-}
-
-export interface PodClass {
-  countAll: PodMethods.CountAll
-  incrementScores: PodMethods.IncrementScores
-  list: PodMethods.List
-  listAllIds: PodMethods.ListAllIds
-  listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest
-  listBadPods: PodMethods.ListBadPods
-  load: PodMethods.Load
-  loadByHost: PodMethods.LoadByHost
-  removeAll: PodMethods.RemoveAll
-  updatePodsScore: PodMethods.UpdatePodsScore
-}
-
-export interface PodAttributes {
-  host?: string
-  publicKey?: string
-  score?: number | Sequelize.literal // Sequelize literal for 'score +' + value
-  email?: string
-}
-
-export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance<PodAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  toFormatedJSON: PodMethods.ToFormatedJSON,
-}
-
-export interface PodModel extends PodClass, Sequelize.Model<PodInstance, PodAttributes> {}
diff --git a/server/models/pod.ts b/server/models/pod.ts
deleted file mode 100644 (file)
index fef9d4d..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-import { each, waterfall } from 'async'
-import { map } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { FRIEND_SCORE, PODS_SCORE } from '../initializers'
-import { logger, isHostValid } from '../helpers'
-
-import { addMethodsToModel } from './utils'
-import {
-  PodClass,
-  PodInstance,
-  PodAttributes,
-
-  PodMethods
-} from './pod-interface'
-
-let Pod: Sequelize.Model<PodInstance, PodAttributes>
-let toFormatedJSON: PodMethods.ToFormatedJSON
-let countAll: PodMethods.CountAll
-let incrementScores: PodMethods.IncrementScores
-let list: PodMethods.List
-let listAllIds: PodMethods.ListAllIds
-let listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest
-let listBadPods: PodMethods.ListBadPods
-let load: PodMethods.Load
-let loadByHost: PodMethods.LoadByHost
-let removeAll: PodMethods.RemoveAll
-let updatePodsScore: PodMethods.UpdatePodsScore
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Pod = sequelize.define<PodInstance, PodAttributes>('Pod',
-    {
-      host: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          isHost: function (value) {
-            const res = isHostValid(value)
-            if (res === false) throw new Error('Host not valid.')
-          }
-        }
-      },
-      publicKey: {
-        type: DataTypes.STRING(5000),
-        allowNull: false
-      },
-      score: {
-        type: DataTypes.INTEGER,
-        defaultValue: FRIEND_SCORE.BASE,
-        allowNull: false,
-        validate: {
-          isInt: true,
-          max: FRIEND_SCORE.MAX
-        }
-      },
-      email: {
-        type: DataTypes.STRING(400),
-        allowNull: false,
-        validate: {
-          isEmail: true
-        }
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'host' ],
-          unique: true
-        },
-        {
-          fields: [ 'score' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    countAll,
-    incrementScores,
-    list,
-    listAllIds,
-    listRandomPodIdsWithRequest,
-    listBadPods,
-    load,
-    loadByHost,
-    updatePodsScore,
-    removeAll
-  ]
-  const instanceMethods = [ toFormatedJSON ]
-  addMethodsToModel(Pod, classMethods, instanceMethods)
-
-  return Pod
-}
-
-// ------------------------------ METHODS ------------------------------
-
-toFormatedJSON = function () {
-  const json = {
-    id: this.id,
-    host: this.host,
-    email: this.email,
-    score: this.score,
-    createdAt: this.createdAt
-  }
-
-  return json
-}
-
-// ------------------------------ Statics ------------------------------
-
-function associate (models) {
-  Pod.belongsToMany(models.Request, {
-    foreignKey: 'podId',
-    through: models.RequestToPod,
-    onDelete: 'cascade'
-  })
-}
-
-countAll = function (callback: PodMethods.CountAllCallback) {
-  return Pod.count().asCallback(callback)
-}
-
-incrementScores = function (ids: number[], value: number, callback?: PodMethods.IncrementScoresCallback) {
-  if (!callback) callback = function () { /* empty */ }
-
-  const update = {
-    score: Sequelize.literal('score +' + value)
-  }
-
-  const options = {
-    where: {
-      id: {
-        $in: ids
-      }
-    },
-    // In this case score is a literal and not an integer so we do not validate it
-    validate: false
-  }
-
-  return Pod.update(update, options).asCallback(callback)
-}
-
-list = function (callback: PodMethods.ListCallback) {
-  return Pod.findAll().asCallback(callback)
-}
-
-listAllIds = function (transaction: Sequelize.Transaction, callback: PodMethods.ListAllIdsCallback) {
-  const query: any = {
-    attributes: [ 'id' ]
-  }
-
-  if (transaction !== null) query.transaction = transaction
-
-  return Pod.findAll(query).asCallback(function (err: Error, pods) {
-    if (err) return callback(err)
-
-    return callback(null, map(pods, 'id'))
-  })
-}
-
-listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: PodMethods.ListRandomPodIdsWithRequestCallback) {
-  Pod.count().asCallback(function (err, count) {
-    if (err) return callback(err)
-
-    // Optimization...
-    if (count === 0) return callback(null, [])
-
-    let start = Math.floor(Math.random() * count) - limit
-    if (start < 0) start = 0
-
-    const query = {
-      attributes: [ 'id' ],
-      order: [
-        [ 'id', 'ASC' ]
-      ],
-      offset: start,
-      limit: limit,
-      where: {
-        id: {
-          $in: [
-            Sequelize.literal(`SELECT DISTINCT "${tableWithPods}"."podId" FROM "${tableWithPods}" ${tableWithPodsJoins}`)
-          ]
-        }
-      }
-    }
-
-    return Pod.findAll(query).asCallback(function (err, pods) {
-      if (err) return callback(err)
-
-      return callback(null, map(pods, 'id'))
-    })
-  })
-}
-
-listBadPods = function (callback: PodMethods.ListBadPodsCallback) {
-  const query = {
-    where: {
-      score: { $lte: 0 }
-    }
-  }
-
-  return Pod.findAll(query).asCallback(callback)
-}
-
-load = function (id: number, callback: PodMethods.LoadCallback) {
-  return Pod.findById(id).asCallback(callback)
-}
-
-loadByHost = function (host: string, callback: PodMethods.LoadByHostCallback) {
-  const query = {
-    where: {
-      host: host
-    }
-  }
-
-  return Pod.findOne(query).asCallback(callback)
-}
-
-removeAll = function (callback: PodMethods.RemoveAllCallback) {
-  return Pod.destroy().asCallback(callback)
-}
-
-updatePodsScore = function (goodPods: number[], badPods: number[]) {
-  logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
-
-  if (goodPods.length !== 0) {
-    incrementScores(goodPods, PODS_SCORE.BONUS, function (err) {
-      if (err) logger.error('Cannot increment scores of good pods.', { error: err })
-    })
-  }
-
-  if (badPods.length !== 0) {
-    incrementScores(badPods, PODS_SCORE.MALUS, function (err) {
-      if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
-      removeBadPods()
-    })
-  }
-}
-
-// ---------------------------------------------------------------------------
-
-// Remove pods with a score of 0 (too many requests where they were unreachable)
-function removeBadPods () {
-  waterfall([
-    function findBadPods (callback) {
-      listBadPods(function (err, pods) {
-        if (err) {
-          logger.error('Cannot find bad pods.', { error: err })
-          return callback(err)
-        }
-
-        return callback(null, pods)
-      })
-    },
-
-    function removeTheseBadPods (pods, callback) {
-      each(pods, function (pod: any, callbackEach) {
-        pod.destroy().asCallback(callbackEach)
-      }, function (err) {
-        return callback(err, pods.length)
-      })
-    }
-  ], function (err, numberOfPodsRemoved) {
-    if (err) {
-      logger.error('Cannot remove bad pods.', { error: err })
-    } else if (numberOfPodsRemoved) {
-      logger.info('Removed %d pods.', numberOfPodsRemoved)
-    } else {
-      logger.info('No need to remove bad pods.')
-    }
-  })
-}
diff --git a/server/models/pod/index.ts b/server/models/pod/index.ts
new file mode 100644 (file)
index 0000000..d2bf50d
--- /dev/null
@@ -0,0 +1 @@
+export * from './pod-interface'
diff --git a/server/models/pod/pod-interface.ts b/server/models/pod/pod-interface.ts
new file mode 100644 (file)
index 0000000..01ccda6
--- /dev/null
@@ -0,0 +1,67 @@
+import * as Sequelize from 'sequelize'
+
+// Don't use barrel, import just what we need
+import { Pod as FormatedPod } from '../../../shared/models/pod.model'
+
+export namespace PodMethods {
+  export type ToFormatedJSON = () => FormatedPod
+
+  export type CountAllCallback = (err: Error, total: number) => void
+  export type CountAll = (callback) => void
+
+  export type IncrementScoresCallback = (err: Error) => void
+  export type IncrementScores = (ids: number[], value: number, callback?: IncrementScoresCallback) => void
+
+  export type ListCallback = (err: Error, podInstances?: PodInstance[]) => void
+  export type List = (callback: ListCallback) => void
+
+  export type ListAllIdsCallback = (err: Error, ids?: number[]) => void
+  export type ListAllIds = (transaction: Sequelize.Transaction, callback: ListAllIdsCallback) => void
+
+  export type ListRandomPodIdsWithRequestCallback = (err: Error, podInstanceIds?: number[]) => void
+  export type ListRandomPodIdsWithRequest = (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: ListRandomPodIdsWithRequestCallback) => void
+
+  export type ListBadPodsCallback = (err: Error, podInstances?: PodInstance[]) => void
+  export type ListBadPods = (callback: ListBadPodsCallback) => void
+
+  export type LoadCallback = (err: Error, podInstance: PodInstance) => void
+  export type Load = (id: number, callback: LoadCallback) => void
+
+  export type LoadByHostCallback = (err: Error, podInstance: PodInstance) => void
+  export type LoadByHost = (host: string, callback: LoadByHostCallback) => void
+
+  export type RemoveAllCallback = (err: Error) => void
+  export type RemoveAll = (callback: RemoveAllCallback) => void
+
+  export type UpdatePodsScore = (goodPods: number[], badPods: number[]) => void
+}
+
+export interface PodClass {
+  countAll: PodMethods.CountAll
+  incrementScores: PodMethods.IncrementScores
+  list: PodMethods.List
+  listAllIds: PodMethods.ListAllIds
+  listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest
+  listBadPods: PodMethods.ListBadPods
+  load: PodMethods.Load
+  loadByHost: PodMethods.LoadByHost
+  removeAll: PodMethods.RemoveAll
+  updatePodsScore: PodMethods.UpdatePodsScore
+}
+
+export interface PodAttributes {
+  host?: string
+  publicKey?: string
+  score?: number | Sequelize.literal // Sequelize literal for 'score +' + value
+  email?: string
+}
+
+export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance<PodAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+
+  toFormatedJSON: PodMethods.ToFormatedJSON,
+}
+
+export interface PodModel extends PodClass, Sequelize.Model<PodInstance, PodAttributes> {}
diff --git a/server/models/pod/pod.ts b/server/models/pod/pod.ts
new file mode 100644 (file)
index 0000000..4c6e630
--- /dev/null
@@ -0,0 +1,274 @@
+import { each, waterfall } from 'async'
+import { map } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { FRIEND_SCORE, PODS_SCORE } from '../../initializers'
+import { logger, isHostValid } from '../../helpers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  PodClass,
+  PodInstance,
+  PodAttributes,
+
+  PodMethods
+} from './pod-interface'
+
+let Pod: Sequelize.Model<PodInstance, PodAttributes>
+let toFormatedJSON: PodMethods.ToFormatedJSON
+let countAll: PodMethods.CountAll
+let incrementScores: PodMethods.IncrementScores
+let list: PodMethods.List
+let listAllIds: PodMethods.ListAllIds
+let listRandomPodIdsWithRequest: PodMethods.ListRandomPodIdsWithRequest
+let listBadPods: PodMethods.ListBadPods
+let load: PodMethods.Load
+let loadByHost: PodMethods.LoadByHost
+let removeAll: PodMethods.RemoveAll
+let updatePodsScore: PodMethods.UpdatePodsScore
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  Pod = sequelize.define<PodInstance, PodAttributes>('Pod',
+    {
+      host: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          isHost: function (value) {
+            const res = isHostValid(value)
+            if (res === false) throw new Error('Host not valid.')
+          }
+        }
+      },
+      publicKey: {
+        type: DataTypes.STRING(5000),
+        allowNull: false
+      },
+      score: {
+        type: DataTypes.INTEGER,
+        defaultValue: FRIEND_SCORE.BASE,
+        allowNull: false,
+        validate: {
+          isInt: true,
+          max: FRIEND_SCORE.MAX
+        }
+      },
+      email: {
+        type: DataTypes.STRING(400),
+        allowNull: false,
+        validate: {
+          isEmail: true
+        }
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'host' ],
+          unique: true
+        },
+        {
+          fields: [ 'score' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    countAll,
+    incrementScores,
+    list,
+    listAllIds,
+    listRandomPodIdsWithRequest,
+    listBadPods,
+    load,
+    loadByHost,
+    updatePodsScore,
+    removeAll
+  ]
+  const instanceMethods = [ toFormatedJSON ]
+  addMethodsToModel(Pod, classMethods, instanceMethods)
+
+  return Pod
+}
+
+// ------------------------------ METHODS ------------------------------
+
+toFormatedJSON = function () {
+  const json = {
+    id: this.id,
+    host: this.host,
+    email: this.email,
+    score: this.score,
+    createdAt: this.createdAt
+  }
+
+  return json
+}
+
+// ------------------------------ Statics ------------------------------
+
+function associate (models) {
+  Pod.belongsToMany(models.Request, {
+    foreignKey: 'podId',
+    through: models.RequestToPod,
+    onDelete: 'cascade'
+  })
+}
+
+countAll = function (callback: PodMethods.CountAllCallback) {
+  return Pod.count().asCallback(callback)
+}
+
+incrementScores = function (ids: number[], value: number, callback?: PodMethods.IncrementScoresCallback) {
+  if (!callback) callback = function () { /* empty */ }
+
+  const update = {
+    score: Sequelize.literal('score +' + value)
+  }
+
+  const options = {
+    where: {
+      id: {
+        $in: ids
+      }
+    },
+    // In this case score is a literal and not an integer so we do not validate it
+    validate: false
+  }
+
+  return Pod.update(update, options).asCallback(callback)
+}
+
+list = function (callback: PodMethods.ListCallback) {
+  return Pod.findAll().asCallback(callback)
+}
+
+listAllIds = function (transaction: Sequelize.Transaction, callback: PodMethods.ListAllIdsCallback) {
+  const query: any = {
+    attributes: [ 'id' ]
+  }
+
+  if (transaction !== null) query.transaction = transaction
+
+  return Pod.findAll(query).asCallback(function (err: Error, pods) {
+    if (err) return callback(err)
+
+    return callback(null, map(pods, 'id'))
+  })
+}
+
+listRandomPodIdsWithRequest = function (limit: number, tableWithPods: string, tableWithPodsJoins: string, callback: PodMethods.ListRandomPodIdsWithRequestCallback) {
+  Pod.count().asCallback(function (err, count) {
+    if (err) return callback(err)
+
+    // Optimization...
+    if (count === 0) return callback(null, [])
+
+    let start = Math.floor(Math.random() * count) - limit
+    if (start < 0) start = 0
+
+    const query = {
+      attributes: [ 'id' ],
+      order: [
+        [ 'id', 'ASC' ]
+      ],
+      offset: start,
+      limit: limit,
+      where: {
+        id: {
+          $in: [
+            Sequelize.literal(`SELECT DISTINCT "${tableWithPods}"."podId" FROM "${tableWithPods}" ${tableWithPodsJoins}`)
+          ]
+        }
+      }
+    }
+
+    return Pod.findAll(query).asCallback(function (err, pods) {
+      if (err) return callback(err)
+
+      return callback(null, map(pods, 'id'))
+    })
+  })
+}
+
+listBadPods = function (callback: PodMethods.ListBadPodsCallback) {
+  const query = {
+    where: {
+      score: { $lte: 0 }
+    }
+  }
+
+  return Pod.findAll(query).asCallback(callback)
+}
+
+load = function (id: number, callback: PodMethods.LoadCallback) {
+  return Pod.findById(id).asCallback(callback)
+}
+
+loadByHost = function (host: string, callback: PodMethods.LoadByHostCallback) {
+  const query = {
+    where: {
+      host: host
+    }
+  }
+
+  return Pod.findOne(query).asCallback(callback)
+}
+
+removeAll = function (callback: PodMethods.RemoveAllCallback) {
+  return Pod.destroy().asCallback(callback)
+}
+
+updatePodsScore = function (goodPods: number[], badPods: number[]) {
+  logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
+
+  if (goodPods.length !== 0) {
+    incrementScores(goodPods, PODS_SCORE.BONUS, function (err) {
+      if (err) logger.error('Cannot increment scores of good pods.', { error: err })
+    })
+  }
+
+  if (badPods.length !== 0) {
+    incrementScores(badPods, PODS_SCORE.MALUS, function (err) {
+      if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
+      removeBadPods()
+    })
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+// Remove pods with a score of 0 (too many requests where they were unreachable)
+function removeBadPods () {
+  waterfall([
+    function findBadPods (callback) {
+      listBadPods(function (err, pods) {
+        if (err) {
+          logger.error('Cannot find bad pods.', { error: err })
+          return callback(err)
+        }
+
+        return callback(null, pods)
+      })
+    },
+
+    function removeTheseBadPods (pods, callback) {
+      each(pods, function (pod: any, callbackEach) {
+        pod.destroy().asCallback(callbackEach)
+      }, function (err) {
+        return callback(err, pods.length)
+      })
+    }
+  ], function (err, numberOfPodsRemoved) {
+    if (err) {
+      logger.error('Cannot remove bad pods.', { error: err })
+    } else if (numberOfPodsRemoved) {
+      logger.info('Removed %d pods.', numberOfPodsRemoved)
+    } else {
+      logger.info('No need to remove bad pods.')
+    }
+  })
+}
diff --git a/server/models/request-interface.ts b/server/models/request-interface.ts
deleted file mode 100644 (file)
index 4bbd799..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { PodInstance, PodAttributes } from './pod-interface'
-
-export type RequestsGrouped = {
-  [ podId: number ]: {
-    request: RequestInstance,
-    pod: PodInstance
-  }[]
-}
-
-export namespace RequestMethods {
-  export type CountTotalRequestsCallback = (err: Error, total: number) => void
-  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
-
-  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsGrouped) => void
-  export type ListWithLimitAndRandom = (limitPods, limitRequestsPerPod, callback: ListWithLimitAndRandomCallback) => void
-
-  export type RemoveWithEmptyToCallback = (err: Error) => void
-  export type RemoveWithEmptyTo = (callback: RemoveWithEmptyToCallback) => void
-
-  export type RemoveAllCallback = (err: Error) => void
-  export type RemoveAll = (callback: RemoveAllCallback) => void
-}
-
-export interface RequestClass {
-  countTotalRequests: RequestMethods.CountTotalRequests
-  listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
-  removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
-  removeAll: RequestMethods.RemoveAll
-}
-
-export interface RequestAttributes {
-  request: object
-  endpoint: string
-}
-
-export interface RequestInstance extends RequestClass, RequestAttributes, Sequelize.Instance<RequestAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-
-  setPods: Sequelize.HasManySetAssociationsMixin<PodAttributes, number>
-  Pods: PodInstance[]
-}
-
-export interface RequestModel extends RequestClass, Sequelize.Model<RequestInstance, RequestAttributes> {}
diff --git a/server/models/request-to-pod-interface.ts b/server/models/request-to-pod-interface.ts
deleted file mode 100644 (file)
index 6d75ca6..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace RequestToPodMethods {
-  export type RemoveByRequestIdsAndPodCallback = (err: Error) => void
-  export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number, callback?: RemoveByRequestIdsAndPodCallback) => void
-}
-
-export interface RequestToPodClass {
-  removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
-}
-
-export interface RequestToPodAttributes {
-}
-
-export interface RequestToPodInstance extends RequestToPodClass, RequestToPodAttributes, Sequelize.Instance<RequestToPodAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface RequestToPodModel extends RequestToPodClass, Sequelize.Model<RequestToPodInstance, RequestToPodAttributes> {}
diff --git a/server/models/request-to-pod.ts b/server/models/request-to-pod.ts
deleted file mode 100644 (file)
index d2f3146..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from './utils'
-import {
-  RequestToPodClass,
-  RequestToPodInstance,
-  RequestToPodAttributes,
-
-  RequestToPodMethods
-} from './request-to-pod-interface'
-
-let RequestToPod: Sequelize.Model<RequestToPodInstance, RequestToPodAttributes>
-let removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  RequestToPod = sequelize.define<RequestToPodInstance, RequestToPodAttributes>('RequestToPod', {}, {
-    indexes: [
-      {
-        fields: [ 'requestId' ]
-      },
-      {
-        fields: [ 'podId' ]
-      },
-      {
-        fields: [ 'requestId', 'podId' ],
-        unique: true
-      }
-    ]
-  })
-
-  const classMethods = [
-    removeByRequestIdsAndPod
-  ]
-  addMethodsToModel(RequestToPod, classMethods)
-
-  return RequestToPod
-}
-
-// ---------------------------------------------------------------------------
-
-removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callback?: RequestToPodMethods.RemoveByRequestIdsAndPodCallback) {
-  if (!callback) callback = function () { /* empty */ }
-
-  const query = {
-    where: {
-      requestId: {
-        $in: requestsIds
-      },
-      podId: podId
-    }
-  }
-
-  RequestToPod.destroy(query).asCallback(callback)
-}
diff --git a/server/models/request-video-event-interface.ts b/server/models/request-video-event-interface.ts
deleted file mode 100644 (file)
index ad576a2..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { VideoInstance } from './video-interface'
-import { PodInstance } from './pod-interface'
-
-export type RequestsVideoEventGrouped = {
-  [ podId: number ]: {
-    id: number
-    type: string
-    count: number
-    video: VideoInstance
-    pod: PodInstance
-  }[]
-}
-
-export namespace RequestVideoEventMethods {
-  export type CountTotalRequestsCallback = (err: Error, total: number) => void
-  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
-
-  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoEventGrouped) => void
-  export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
-
-  export type RemoveByRequestIdsAndPodCallback = () => void
-  export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
-
-  export type RemoveAllCallback = () => void
-  export type RemoveAll = (callback: RemoveAllCallback) => void
-}
-
-export interface RequestVideoEventClass {
-  countTotalRequests: RequestVideoEventMethods.CountTotalRequests
-  listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
-  removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
-  removeAll: RequestVideoEventMethods.RemoveAll
-}
-
-export interface RequestVideoEventAttributes {
-  type: string
-  count: number
-}
-
-export interface RequestVideoEventInstance extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
-  id: number
-
-  Video: VideoInstance
-}
-
-export interface RequestVideoEventModel extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {}
diff --git a/server/models/request-video-event.ts b/server/models/request-video-event.ts
deleted file mode 100644 (file)
index 32438b5..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
-  Request Video events (likes, dislikes, views...)
-*/
-
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { database as db } from '../initializers/database'
-import { REQUEST_VIDEO_EVENT_TYPES } from '../initializers'
-import { isVideoEventCountValid } from '../helpers'
-import { addMethodsToModel } from './utils'
-import {
-  RequestVideoEventClass,
-  RequestVideoEventInstance,
-  RequestVideoEventAttributes,
-
-  RequestVideoEventMethods,
-  RequestsVideoEventGrouped
-} from './request-video-event-interface'
-
-let RequestVideoEvent: Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes>
-let countTotalRequests: RequestVideoEventMethods.CountTotalRequests
-let listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
-let removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
-let removeAll: RequestVideoEventMethods.RemoveAll
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  RequestVideoEvent = sequelize.define<RequestVideoEventInstance, RequestVideoEventAttributes>('RequestVideoEvent',
-    {
-      type: {
-        type: DataTypes.ENUM(values(REQUEST_VIDEO_EVENT_TYPES)),
-        allowNull: false
-      },
-      count: {
-        type: DataTypes.INTEGER,
-        allowNull: false,
-        validate: {
-          countValid: function (value) {
-            const res = isVideoEventCountValid(value)
-            if (res === false) throw new Error('Video event count is not valid.')
-          }
-        }
-      }
-    },
-    {
-      updatedAt: false,
-      indexes: [
-        {
-          fields: [ 'videoId' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    listWithLimitAndRandom,
-    countTotalRequests,
-    removeAll,
-    removeByRequestIdsAndPod
-  ]
-  addMethodsToModel(RequestVideoEvent, classMethods)
-
-  return RequestVideoEvent
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  RequestVideoEvent.belongsTo(models.Video, {
-    foreignKey: {
-      name: 'videoId',
-      allowNull: false
-    },
-    onDelete: 'CASCADE'
-  })
-}
-
-countTotalRequests = function (callback: RequestVideoEventMethods.CountTotalRequestsCallback) {
-  const query = {}
-  return RequestVideoEvent.count(query).asCallback(callback)
-}
-
-listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoEventMethods.ListWithLimitAndRandomCallback) {
-  const Pod = db.Pod
-
-  // We make a join between videos and authors to find the podId of our video event requests
-  const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
-                   'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
-
-  Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins, function (err, podIds) {
-    if (err) return callback(err)
-
-    // We don't have friends that have requests
-    if (podIds.length === 0) return callback(null, [])
-
-    const query = {
-      order: [
-        [ 'id', 'ASC' ]
-      ],
-      include: [
-        {
-          model: RequestVideoEvent['sequelize'].models.Video,
-          include: [
-            {
-              model: RequestVideoEvent['sequelize'].models.Author,
-              include: [
-                {
-                  model: RequestVideoEvent['sequelize'].models.Pod,
-                  where: {
-                    id: {
-                      $in: podIds
-                    }
-                  }
-                }
-              ]
-            }
-          ]
-        }
-      ]
-    }
-
-    RequestVideoEvent.findAll(query).asCallback(function (err, requests) {
-      if (err) return callback(err)
-
-      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
-      return callback(err, requestsGrouped)
-    })
-  })
-}
-
-removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoEventMethods.RemoveByRequestIdsAndPodCallback) {
-  const query = {
-    where: {
-      id: {
-        $in: ids
-      }
-    },
-    include: [
-      {
-        model: RequestVideoEvent['sequelize'].models.Video,
-        include: [
-          {
-            model: RequestVideoEvent['sequelize'].models.Author,
-            where: {
-              podId
-            }
-          }
-        ]
-      }
-    ]
-  }
-
-  RequestVideoEvent.destroy(query).asCallback(callback)
-}
-
-removeAll = function (callback: RequestVideoEventMethods.RemoveAllCallback) {
-  // Delete all requests
-  RequestVideoEvent.truncate({ cascade: true }).asCallback(callback)
-}
-
-// ---------------------------------------------------------------------------
-
-function groupAndTruncateRequests (events: RequestVideoEventInstance[], limitRequestsPerPod: number) {
-  const eventsGrouped: RequestsVideoEventGrouped = {}
-
-  events.forEach(function (event) {
-    const pod = event.Video.Author.Pod
-
-    if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
-
-    if (eventsGrouped[pod.id].length < limitRequestsPerPod) {
-      eventsGrouped[pod.id].push({
-        id: event.id,
-        type: event.type,
-        count: event.count,
-        video: event.Video,
-        pod
-      })
-    }
-  })
-
-  return eventsGrouped
-}
diff --git a/server/models/request-video-qadu-interface.ts b/server/models/request-video-qadu-interface.ts
deleted file mode 100644 (file)
index 04de7f1..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { VideoInstance } from './video-interface'
-import { PodInstance } from './pod-interface'
-
-export type RequestsVideoQaduGrouped = {
-  [ podId: number ]: {
-    request: RequestVideoQaduInstance
-    video: VideoInstance
-    pod: PodInstance
-  }
-}
-
-export namespace RequestVideoQaduMethods {
-  export type CountTotalRequestsCallback = (err: Error, total: number) => void
-  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
-
-  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoQaduGrouped) => void
-  export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
-
-  export type RemoveByRequestIdsAndPodCallback = () => void
-  export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
-
-  export type RemoveAllCallback = () => void
-  export type RemoveAll = (callback: RemoveAllCallback) => void
-}
-
-export interface RequestVideoQaduClass {
-  countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
-  listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
-  removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
-  removeAll: RequestVideoQaduMethods.RemoveAll
-}
-
-export interface RequestVideoQaduAttributes {
-  type: string
-}
-
-export interface RequestVideoQaduInstance extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
-  id: number
-
-  Pod: PodInstance
-  Video: VideoInstance
-}
-
-export interface RequestVideoQaduModel extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {}
diff --git a/server/models/request-video-qadu.ts b/server/models/request-video-qadu.ts
deleted file mode 100644 (file)
index 27ce0ff..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
-  Request Video for Quick And Dirty Updates like:
-   - views
-   - likes
-   - dislikes
-
-  We can't put it in the same system than basic requests for efficiency.
-  Moreover we don't want to slow down the basic requests with a lot of views/likes/dislikes requests.
-  So we put it an independant request scheduler.
-*/
-
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { database as db } from '../initializers/database'
-import { REQUEST_VIDEO_QADU_TYPES } from '../initializers'
-import { addMethodsToModel } from './utils'
-import {
-  RequestVideoQaduClass,
-  RequestVideoQaduInstance,
-  RequestVideoQaduAttributes,
-
-  RequestVideoQaduMethods
-} from './request-video-qadu-interface'
-
-let RequestVideoQadu: Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes>
-let countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
-let listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
-let removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
-let removeAll: RequestVideoQaduMethods.RemoveAll
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  RequestVideoQadu = sequelize.define<RequestVideoQaduInstance, RequestVideoQaduAttributes>('RequestVideoQadu',
-    {
-      type: {
-        type: DataTypes.ENUM(values(REQUEST_VIDEO_QADU_TYPES)),
-        allowNull: false
-      }
-    },
-    {
-      timestamps: false,
-      indexes: [
-        {
-          fields: [ 'podId' ]
-        },
-        {
-          fields: [ 'videoId' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    listWithLimitAndRandom,
-    countTotalRequests,
-    removeAll,
-    removeByRequestIdsAndPod
-  ]
-  addMethodsToModel(RequestVideoQadu, classMethods)
-
-  return RequestVideoQadu
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  RequestVideoQadu.belongsTo(models.Pod, {
-    foreignKey: {
-      name: 'podId',
-      allowNull: false
-    },
-    onDelete: 'CASCADE'
-  })
-
-  RequestVideoQadu.belongsTo(models.Video, {
-    foreignKey: {
-      name: 'videoId',
-      allowNull: false
-    },
-    onDelete: 'CASCADE'
-  })
-}
-
-countTotalRequests = function (callback: RequestVideoQaduMethods.CountTotalRequestsCallback) {
-  const query = {}
-  return RequestVideoQadu.count(query).asCallback(callback)
-}
-
-listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoQaduMethods.ListWithLimitAndRandomCallback) {
-  const Pod = db.Pod
-  const tableJoin = ''
-
-  Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin, function (err, podIds) {
-    if (err) return callback(err)
-
-    // We don't have friends that have requests
-    if (podIds.length === 0) return callback(null, [])
-
-    const query = {
-      include: [
-        {
-          model: RequestVideoQadu['sequelize'].models.Pod,
-          where: {
-            id: {
-              $in: podIds
-            }
-          }
-        },
-        {
-          model: RequestVideoQadu['sequelize'].models.Video
-        }
-      ]
-    }
-
-    RequestVideoQadu.findAll(query).asCallback(function (err, requests) {
-      if (err) return callback(err)
-
-      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
-      return callback(err, requestsGrouped)
-    })
-  })
-}
-
-removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoQaduMethods.RemoveByRequestIdsAndPodCallback) {
-  const query = {
-    where: {
-      id: {
-        $in: ids
-      },
-      podId
-    }
-  }
-
-  RequestVideoQadu.destroy(query).asCallback(callback)
-}
-
-removeAll = function (callback: RequestVideoQaduMethods.RemoveAllCallback) {
-  // Delete all requests
-  RequestVideoQadu.truncate({ cascade: true }).asCallback(callback)
-}
-
-// ---------------------------------------------------------------------------
-
-function groupAndTruncateRequests (requests: RequestVideoQaduInstance[], limitRequestsPerPod: number) {
-  const requestsGrouped = {}
-
-  requests.forEach(function (request) {
-    const pod = request.Pod
-
-    if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
-
-    if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
-      requestsGrouped[pod.id].push({
-        request: request,
-        video: request.Video,
-        pod
-      })
-    }
-  })
-
-  return requestsGrouped
-}
diff --git a/server/models/request.ts b/server/models/request.ts
deleted file mode 100644 (file)
index e6c367f..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { database as db } from '../initializers/database'
-import { REQUEST_ENDPOINTS } from '../initializers'
-import { addMethodsToModel } from './utils'
-import {
-  RequestClass,
-  RequestInstance,
-  RequestAttributes,
-
-  RequestMethods,
-  RequestsGrouped
-} from './request-interface'
-
-let Request: Sequelize.Model<RequestInstance, RequestAttributes>
-let countTotalRequests: RequestMethods.CountTotalRequests
-let listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
-let removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
-let removeAll: RequestMethods.RemoveAll
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Request = sequelize.define<RequestInstance, RequestAttributes>('Request',
-    {
-      request: {
-        type: DataTypes.JSON,
-        allowNull: false
-      },
-      endpoint: {
-        type: DataTypes.ENUM(values(REQUEST_ENDPOINTS)),
-        allowNull: false
-      }
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    listWithLimitAndRandom,
-
-    countTotalRequests,
-    removeAll,
-    removeWithEmptyTo
-  ]
-  addMethodsToModel(Request, classMethods)
-
-  return Request
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  Request.belongsToMany(models.Pod, {
-    foreignKey: {
-      name: 'requestId',
-      allowNull: false
-    },
-    through: models.RequestToPod,
-    onDelete: 'CASCADE'
-  })
-}
-
-countTotalRequests = function (callback: RequestMethods.CountTotalRequestsCallback) {
-  // We need to include Pod because there are no cascade delete when a pod is removed
-  // So we could count requests that do not have existing pod anymore
-  const query = {
-    include: [ Request['sequelize'].models.Pod ]
-  }
-
-  return Request.count(query).asCallback(callback)
-}
-
-listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestMethods.ListWithLimitAndRandomCallback) {
-  const Pod = db.Pod
-  const tableJoin = ''
-
-  Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', '', function (err, podIds) {
-    if (err) return callback(err)
-
-    // We don't have friends that have requests
-    if (podIds.length === 0) return callback(null, [])
-
-    // The first x requests of these pods
-    // It is very important to sort by id ASC to keep the requests order!
-    const query = {
-      order: [
-        [ 'id', 'ASC' ]
-      ],
-      include: [
-        {
-          model: Request['sequelize'].models.Pod,
-          where: {
-            id: {
-              $in: podIds
-            }
-          }
-        }
-      ]
-    }
-
-    Request.findAll(query).asCallback(function (err, requests) {
-      if (err) return callback(err)
-
-      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
-      return callback(err, requestsGrouped)
-    })
-  })
-}
-
-removeAll = function (callback: RequestMethods.RemoveAllCallback) {
-  // Delete all requests
-  Request.truncate({ cascade: true }).asCallback(callback)
-}
-
-removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallback) {
-  if (!callback) callback = function () { /* empty */ }
-
-  const query = {
-    where: {
-      id: {
-        $notIn: [
-          Sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
-        ]
-      }
-    }
-  }
-
-  Request.destroy(query).asCallback(callback)
-}
-
-// ---------------------------------------------------------------------------
-
-function groupAndTruncateRequests (requests: RequestInstance[], limitRequestsPerPod: number) {
-  const requestsGrouped: RequestsGrouped = {}
-
-  requests.forEach(function (request) {
-    request.Pods.forEach(function (pod) {
-      if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
-
-      if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
-        requestsGrouped[pod.id].push({
-          request,
-          pod
-        })
-      }
-    })
-  })
-
-  return requestsGrouped
-}
diff --git a/server/models/request/index.ts b/server/models/request/index.ts
new file mode 100644 (file)
index 0000000..824c140
--- /dev/null
@@ -0,0 +1,4 @@
+export * from './request-interface'
+export * from './request-to-pod-interface'
+export * from './request-video-event-interface'
+export * from './request-video-qadu-interface'
diff --git a/server/models/request/request-interface.ts b/server/models/request/request-interface.ts
new file mode 100644 (file)
index 0000000..70fd734
--- /dev/null
@@ -0,0 +1,47 @@
+import * as Sequelize from 'sequelize'
+
+import { PodInstance, PodAttributes } from '../pod'
+
+export type RequestsGrouped = {
+  [ podId: number ]: {
+    request: RequestInstance,
+    pod: PodInstance
+  }[]
+}
+
+export namespace RequestMethods {
+  export type CountTotalRequestsCallback = (err: Error, total: number) => void
+  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
+
+  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsGrouped) => void
+  export type ListWithLimitAndRandom = (limitPods, limitRequestsPerPod, callback: ListWithLimitAndRandomCallback) => void
+
+  export type RemoveWithEmptyToCallback = (err: Error) => void
+  export type RemoveWithEmptyTo = (callback: RemoveWithEmptyToCallback) => void
+
+  export type RemoveAllCallback = (err: Error) => void
+  export type RemoveAll = (callback: RemoveAllCallback) => void
+}
+
+export interface RequestClass {
+  countTotalRequests: RequestMethods.CountTotalRequests
+  listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
+  removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
+  removeAll: RequestMethods.RemoveAll
+}
+
+export interface RequestAttributes {
+  request: object
+  endpoint: string
+}
+
+export interface RequestInstance extends RequestClass, RequestAttributes, Sequelize.Instance<RequestAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+
+  setPods: Sequelize.HasManySetAssociationsMixin<PodAttributes, number>
+  Pods: PodInstance[]
+}
+
+export interface RequestModel extends RequestClass, Sequelize.Model<RequestInstance, RequestAttributes> {}
diff --git a/server/models/request/request-to-pod-interface.ts b/server/models/request/request-to-pod-interface.ts
new file mode 100644 (file)
index 0000000..6d75ca6
--- /dev/null
@@ -0,0 +1,21 @@
+import * as Sequelize from 'sequelize'
+
+export namespace RequestToPodMethods {
+  export type RemoveByRequestIdsAndPodCallback = (err: Error) => void
+  export type RemoveByRequestIdsAndPod = (requestsIds: number[], podId: number, callback?: RemoveByRequestIdsAndPodCallback) => void
+}
+
+export interface RequestToPodClass {
+  removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
+}
+
+export interface RequestToPodAttributes {
+}
+
+export interface RequestToPodInstance extends RequestToPodClass, RequestToPodAttributes, Sequelize.Instance<RequestToPodAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface RequestToPodModel extends RequestToPodClass, Sequelize.Model<RequestToPodInstance, RequestToPodAttributes> {}
diff --git a/server/models/request/request-to-pod.ts b/server/models/request/request-to-pod.ts
new file mode 100644 (file)
index 0000000..67331be
--- /dev/null
@@ -0,0 +1,54 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import {
+  RequestToPodClass,
+  RequestToPodInstance,
+  RequestToPodAttributes,
+
+  RequestToPodMethods
+} from './request-to-pod-interface'
+
+let RequestToPod: Sequelize.Model<RequestToPodInstance, RequestToPodAttributes>
+let removeByRequestIdsAndPod: RequestToPodMethods.RemoveByRequestIdsAndPod
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  RequestToPod = sequelize.define<RequestToPodInstance, RequestToPodAttributes>('RequestToPod', {}, {
+    indexes: [
+      {
+        fields: [ 'requestId' ]
+      },
+      {
+        fields: [ 'podId' ]
+      },
+      {
+        fields: [ 'requestId', 'podId' ],
+        unique: true
+      }
+    ]
+  })
+
+  const classMethods = [
+    removeByRequestIdsAndPod
+  ]
+  addMethodsToModel(RequestToPod, classMethods)
+
+  return RequestToPod
+}
+
+// ---------------------------------------------------------------------------
+
+removeByRequestIdsAndPod = function (requestsIds: number[], podId: number, callback?: RequestToPodMethods.RemoveByRequestIdsAndPodCallback) {
+  if (!callback) callback = function () { /* empty */ }
+
+  const query = {
+    where: {
+      requestId: {
+        $in: requestsIds
+      },
+      podId: podId
+    }
+  }
+
+  RequestToPod.destroy(query).asCallback(callback)
+}
diff --git a/server/models/request/request-video-event-interface.ts b/server/models/request/request-video-event-interface.ts
new file mode 100644 (file)
index 0000000..219d8ed
--- /dev/null
@@ -0,0 +1,48 @@
+import * as Sequelize from 'sequelize'
+
+import { VideoInstance } from '../video'
+import { PodInstance } from '../pod'
+
+export type RequestsVideoEventGrouped = {
+  [ podId: number ]: {
+    id: number
+    type: string
+    count: number
+    video: VideoInstance
+    pod: PodInstance
+  }[]
+}
+
+export namespace RequestVideoEventMethods {
+  export type CountTotalRequestsCallback = (err: Error, total: number) => void
+  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
+
+  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoEventGrouped) => void
+  export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
+
+  export type RemoveByRequestIdsAndPodCallback = () => void
+  export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
+
+  export type RemoveAllCallback = () => void
+  export type RemoveAll = (callback: RemoveAllCallback) => void
+}
+
+export interface RequestVideoEventClass {
+  countTotalRequests: RequestVideoEventMethods.CountTotalRequests
+  listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
+  removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
+  removeAll: RequestVideoEventMethods.RemoveAll
+}
+
+export interface RequestVideoEventAttributes {
+  type: string
+  count: number
+}
+
+export interface RequestVideoEventInstance extends RequestVideoEventClass, RequestVideoEventAttributes, Sequelize.Instance<RequestVideoEventAttributes> {
+  id: number
+
+  Video: VideoInstance
+}
+
+export interface RequestVideoEventModel extends RequestVideoEventClass, Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes> {}
diff --git a/server/models/request/request-video-event.ts b/server/models/request/request-video-event.ts
new file mode 100644 (file)
index 0000000..f552ef5
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+  Request Video events (likes, dislikes, views...)
+*/
+
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { database as db } from '../../initializers/database'
+import { REQUEST_VIDEO_EVENT_TYPES } from '../../initializers'
+import { isVideoEventCountValid } from '../../helpers'
+import { addMethodsToModel } from '../utils'
+import {
+  RequestVideoEventClass,
+  RequestVideoEventInstance,
+  RequestVideoEventAttributes,
+
+  RequestVideoEventMethods,
+  RequestsVideoEventGrouped
+} from './request-video-event-interface'
+
+let RequestVideoEvent: Sequelize.Model<RequestVideoEventInstance, RequestVideoEventAttributes>
+let countTotalRequests: RequestVideoEventMethods.CountTotalRequests
+let listWithLimitAndRandom: RequestVideoEventMethods.ListWithLimitAndRandom
+let removeByRequestIdsAndPod: RequestVideoEventMethods.RemoveByRequestIdsAndPod
+let removeAll: RequestVideoEventMethods.RemoveAll
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  RequestVideoEvent = sequelize.define<RequestVideoEventInstance, RequestVideoEventAttributes>('RequestVideoEvent',
+    {
+      type: {
+        type: DataTypes.ENUM(values(REQUEST_VIDEO_EVENT_TYPES)),
+        allowNull: false
+      },
+      count: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        validate: {
+          countValid: function (value) {
+            const res = isVideoEventCountValid(value)
+            if (res === false) throw new Error('Video event count is not valid.')
+          }
+        }
+      }
+    },
+    {
+      updatedAt: false,
+      indexes: [
+        {
+          fields: [ 'videoId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    listWithLimitAndRandom,
+    countTotalRequests,
+    removeAll,
+    removeByRequestIdsAndPod
+  ]
+  addMethodsToModel(RequestVideoEvent, classMethods)
+
+  return RequestVideoEvent
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  RequestVideoEvent.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+}
+
+countTotalRequests = function (callback: RequestVideoEventMethods.CountTotalRequestsCallback) {
+  const query = {}
+  return RequestVideoEvent.count(query).asCallback(callback)
+}
+
+listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoEventMethods.ListWithLimitAndRandomCallback) {
+  const Pod = db.Pod
+
+  // We make a join between videos and authors to find the podId of our video event requests
+  const podJoins = 'INNER JOIN "Videos" ON "Videos"."authorId" = "Authors"."id" ' +
+                   'INNER JOIN "RequestVideoEvents" ON "RequestVideoEvents"."videoId" = "Videos"."id"'
+
+  Pod.listRandomPodIdsWithRequest(limitPods, 'Authors', podJoins, function (err, podIds) {
+    if (err) return callback(err)
+
+    // We don't have friends that have requests
+    if (podIds.length === 0) return callback(null, [])
+
+    const query = {
+      order: [
+        [ 'id', 'ASC' ]
+      ],
+      include: [
+        {
+          model: RequestVideoEvent['sequelize'].models.Video,
+          include: [
+            {
+              model: RequestVideoEvent['sequelize'].models.Author,
+              include: [
+                {
+                  model: RequestVideoEvent['sequelize'].models.Pod,
+                  where: {
+                    id: {
+                      $in: podIds
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+
+    RequestVideoEvent.findAll(query).asCallback(function (err, requests) {
+      if (err) return callback(err)
+
+      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
+      return callback(err, requestsGrouped)
+    })
+  })
+}
+
+removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoEventMethods.RemoveByRequestIdsAndPodCallback) {
+  const query = {
+    where: {
+      id: {
+        $in: ids
+      }
+    },
+    include: [
+      {
+        model: RequestVideoEvent['sequelize'].models.Video,
+        include: [
+          {
+            model: RequestVideoEvent['sequelize'].models.Author,
+            where: {
+              podId
+            }
+          }
+        ]
+      }
+    ]
+  }
+
+  RequestVideoEvent.destroy(query).asCallback(callback)
+}
+
+removeAll = function (callback: RequestVideoEventMethods.RemoveAllCallback) {
+  // Delete all requests
+  RequestVideoEvent.truncate({ cascade: true }).asCallback(callback)
+}
+
+// ---------------------------------------------------------------------------
+
+function groupAndTruncateRequests (events: RequestVideoEventInstance[], limitRequestsPerPod: number) {
+  const eventsGrouped: RequestsVideoEventGrouped = {}
+
+  events.forEach(function (event) {
+    const pod = event.Video.Author.Pod
+
+    if (!eventsGrouped[pod.id]) eventsGrouped[pod.id] = []
+
+    if (eventsGrouped[pod.id].length < limitRequestsPerPod) {
+      eventsGrouped[pod.id].push({
+        id: event.id,
+        type: event.type,
+        count: event.count,
+        video: event.Video,
+        pod
+      })
+    }
+  })
+
+  return eventsGrouped
+}
diff --git a/server/models/request/request-video-qadu-interface.ts b/server/models/request/request-video-qadu-interface.ts
new file mode 100644 (file)
index 0000000..625b063
--- /dev/null
@@ -0,0 +1,46 @@
+import * as Sequelize from 'sequelize'
+
+import { VideoInstance } from '../video'
+import { PodInstance } from '../pod'
+
+export type RequestsVideoQaduGrouped = {
+  [ podId: number ]: {
+    request: RequestVideoQaduInstance
+    video: VideoInstance
+    pod: PodInstance
+  }
+}
+
+export namespace RequestVideoQaduMethods {
+  export type CountTotalRequestsCallback = (err: Error, total: number) => void
+  export type CountTotalRequests = (callback: CountTotalRequestsCallback) => void
+
+  export type ListWithLimitAndRandomCallback = (err: Error, requestsGrouped?: RequestsVideoQaduGrouped) => void
+  export type ListWithLimitAndRandom = (limitPods: number, limitRequestsPerPod: number, callback: ListWithLimitAndRandomCallback) => void
+
+  export type RemoveByRequestIdsAndPodCallback = () => void
+  export type RemoveByRequestIdsAndPod = (ids: number[], podId: number, callback: RemoveByRequestIdsAndPodCallback) => void
+
+  export type RemoveAllCallback = () => void
+  export type RemoveAll = (callback: RemoveAllCallback) => void
+}
+
+export interface RequestVideoQaduClass {
+  countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
+  listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
+  removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
+  removeAll: RequestVideoQaduMethods.RemoveAll
+}
+
+export interface RequestVideoQaduAttributes {
+  type: string
+}
+
+export interface RequestVideoQaduInstance extends RequestVideoQaduClass, RequestVideoQaduAttributes, Sequelize.Instance<RequestVideoQaduAttributes> {
+  id: number
+
+  Pod: PodInstance
+  Video: VideoInstance
+}
+
+export interface RequestVideoQaduModel extends RequestVideoQaduClass, Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes> {}
diff --git a/server/models/request/request-video-qadu.ts b/server/models/request/request-video-qadu.ts
new file mode 100644 (file)
index 0000000..da62239
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+  Request Video for Quick And Dirty Updates like:
+   - views
+   - likes
+   - dislikes
+
+  We can't put it in the same system than basic requests for efficiency.
+  Moreover we don't want to slow down the basic requests with a lot of views/likes/dislikes requests.
+  So we put it an independant request scheduler.
+*/
+
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { database as db } from '../../initializers/database'
+import { REQUEST_VIDEO_QADU_TYPES } from '../../initializers'
+import { addMethodsToModel } from '../utils'
+import {
+  RequestVideoQaduClass,
+  RequestVideoQaduInstance,
+  RequestVideoQaduAttributes,
+
+  RequestVideoQaduMethods
+} from './request-video-qadu-interface'
+
+let RequestVideoQadu: Sequelize.Model<RequestVideoQaduInstance, RequestVideoQaduAttributes>
+let countTotalRequests: RequestVideoQaduMethods.CountTotalRequests
+let listWithLimitAndRandom: RequestVideoQaduMethods.ListWithLimitAndRandom
+let removeByRequestIdsAndPod: RequestVideoQaduMethods.RemoveByRequestIdsAndPod
+let removeAll: RequestVideoQaduMethods.RemoveAll
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  RequestVideoQadu = sequelize.define<RequestVideoQaduInstance, RequestVideoQaduAttributes>('RequestVideoQadu',
+    {
+      type: {
+        type: DataTypes.ENUM(values(REQUEST_VIDEO_QADU_TYPES)),
+        allowNull: false
+      }
+    },
+    {
+      timestamps: false,
+      indexes: [
+        {
+          fields: [ 'podId' ]
+        },
+        {
+          fields: [ 'videoId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    listWithLimitAndRandom,
+    countTotalRequests,
+    removeAll,
+    removeByRequestIdsAndPod
+  ]
+  addMethodsToModel(RequestVideoQadu, classMethods)
+
+  return RequestVideoQadu
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  RequestVideoQadu.belongsTo(models.Pod, {
+    foreignKey: {
+      name: 'podId',
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+
+  RequestVideoQadu.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+}
+
+countTotalRequests = function (callback: RequestVideoQaduMethods.CountTotalRequestsCallback) {
+  const query = {}
+  return RequestVideoQadu.count(query).asCallback(callback)
+}
+
+listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestVideoQaduMethods.ListWithLimitAndRandomCallback) {
+  const Pod = db.Pod
+  const tableJoin = ''
+
+  Pod.listRandomPodIdsWithRequest(limitPods, 'RequestVideoQadus', tableJoin, function (err, podIds) {
+    if (err) return callback(err)
+
+    // We don't have friends that have requests
+    if (podIds.length === 0) return callback(null, [])
+
+    const query = {
+      include: [
+        {
+          model: RequestVideoQadu['sequelize'].models.Pod,
+          where: {
+            id: {
+              $in: podIds
+            }
+          }
+        },
+        {
+          model: RequestVideoQadu['sequelize'].models.Video
+        }
+      ]
+    }
+
+    RequestVideoQadu.findAll(query).asCallback(function (err, requests) {
+      if (err) return callback(err)
+
+      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
+      return callback(err, requestsGrouped)
+    })
+  })
+}
+
+removeByRequestIdsAndPod = function (ids: number[], podId: number, callback: RequestVideoQaduMethods.RemoveByRequestIdsAndPodCallback) {
+  const query = {
+    where: {
+      id: {
+        $in: ids
+      },
+      podId
+    }
+  }
+
+  RequestVideoQadu.destroy(query).asCallback(callback)
+}
+
+removeAll = function (callback: RequestVideoQaduMethods.RemoveAllCallback) {
+  // Delete all requests
+  RequestVideoQadu.truncate({ cascade: true }).asCallback(callback)
+}
+
+// ---------------------------------------------------------------------------
+
+function groupAndTruncateRequests (requests: RequestVideoQaduInstance[], limitRequestsPerPod: number) {
+  const requestsGrouped = {}
+
+  requests.forEach(function (request) {
+    const pod = request.Pod
+
+    if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
+
+    if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
+      requestsGrouped[pod.id].push({
+        request: request,
+        video: request.Video,
+        pod
+      })
+    }
+  })
+
+  return requestsGrouped
+}
diff --git a/server/models/request/request.ts b/server/models/request/request.ts
new file mode 100644 (file)
index 0000000..66e7da8
--- /dev/null
@@ -0,0 +1,150 @@
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { database as db } from '../../initializers/database'
+import { REQUEST_ENDPOINTS } from '../../initializers'
+import { addMethodsToModel } from '../utils'
+import {
+  RequestClass,
+  RequestInstance,
+  RequestAttributes,
+
+  RequestMethods,
+  RequestsGrouped
+} from './request-interface'
+
+let Request: Sequelize.Model<RequestInstance, RequestAttributes>
+let countTotalRequests: RequestMethods.CountTotalRequests
+let listWithLimitAndRandom: RequestMethods.ListWithLimitAndRandom
+let removeWithEmptyTo: RequestMethods.RemoveWithEmptyTo
+let removeAll: RequestMethods.RemoveAll
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  Request = sequelize.define<RequestInstance, RequestAttributes>('Request',
+    {
+      request: {
+        type: DataTypes.JSON,
+        allowNull: false
+      },
+      endpoint: {
+        type: DataTypes.ENUM(values(REQUEST_ENDPOINTS)),
+        allowNull: false
+      }
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    listWithLimitAndRandom,
+
+    countTotalRequests,
+    removeAll,
+    removeWithEmptyTo
+  ]
+  addMethodsToModel(Request, classMethods)
+
+  return Request
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  Request.belongsToMany(models.Pod, {
+    foreignKey: {
+      name: 'requestId',
+      allowNull: false
+    },
+    through: models.RequestToPod,
+    onDelete: 'CASCADE'
+  })
+}
+
+countTotalRequests = function (callback: RequestMethods.CountTotalRequestsCallback) {
+  // We need to include Pod because there are no cascade delete when a pod is removed
+  // So we could count requests that do not have existing pod anymore
+  const query = {
+    include: [ Request['sequelize'].models.Pod ]
+  }
+
+  return Request.count(query).asCallback(callback)
+}
+
+listWithLimitAndRandom = function (limitPods: number, limitRequestsPerPod: number, callback: RequestMethods.ListWithLimitAndRandomCallback) {
+  const Pod = db.Pod
+  const tableJoin = ''
+
+  Pod.listRandomPodIdsWithRequest(limitPods, 'RequestToPods', '', function (err, podIds) {
+    if (err) return callback(err)
+
+    // We don't have friends that have requests
+    if (podIds.length === 0) return callback(null, [])
+
+    // The first x requests of these pods
+    // It is very important to sort by id ASC to keep the requests order!
+    const query = {
+      order: [
+        [ 'id', 'ASC' ]
+      ],
+      include: [
+        {
+          model: Request['sequelize'].models.Pod,
+          where: {
+            id: {
+              $in: podIds
+            }
+          }
+        }
+      ]
+    }
+
+    Request.findAll(query).asCallback(function (err, requests) {
+      if (err) return callback(err)
+
+      const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
+      return callback(err, requestsGrouped)
+    })
+  })
+}
+
+removeAll = function (callback: RequestMethods.RemoveAllCallback) {
+  // Delete all requests
+  Request.truncate({ cascade: true }).asCallback(callback)
+}
+
+removeWithEmptyTo = function (callback?: RequestMethods.RemoveWithEmptyToCallback) {
+  if (!callback) callback = function () { /* empty */ }
+
+  const query = {
+    where: {
+      id: {
+        $notIn: [
+          Sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
+        ]
+      }
+    }
+  }
+
+  Request.destroy(query).asCallback(callback)
+}
+
+// ---------------------------------------------------------------------------
+
+function groupAndTruncateRequests (requests: RequestInstance[], limitRequestsPerPod: number) {
+  const requestsGrouped: RequestsGrouped = {}
+
+  requests.forEach(function (request) {
+    request.Pods.forEach(function (pod) {
+      if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
+
+      if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
+        requestsGrouped[pod.id].push({
+          request,
+          pod
+        })
+      }
+    })
+  })
+
+  return requestsGrouped
+}
diff --git a/server/models/tag-interface.ts b/server/models/tag-interface.ts
deleted file mode 100644 (file)
index e045e7c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace TagMethods {
-  export type FindOrCreateTagsCallback = (err: Error, tagInstances: TagInstance[]) => void
-  export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction, callback: FindOrCreateTagsCallback) => void
-}
-
-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> {}
diff --git a/server/models/tag.ts b/server/models/tag.ts
deleted file mode 100644 (file)
index 54a5f79..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-import { each } from 'async'
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from './utils'
-import {
-  TagClass,
-  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',
-    {
-      name: {
-        type: DataTypes.STRING,
-        allowNull: false
-      }
-    },
-    {
-      timestamps: false,
-      indexes: [
-        {
-          fields: [ 'name' ],
-          unique: true
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    findOrCreateTags
-  ]
-  addMethodsToModel(Tag, classMethods)
-
-  return Tag
-}
-
-// ---------------------------------------------------------------------------
-
-function associate (models) {
-  Tag.belongsToMany(models.Video, {
-    foreignKey: 'tagId',
-    through: models.VideoTag,
-    onDelete: 'cascade'
-  })
-}
-
-findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction, callback: TagMethods.FindOrCreateTagsCallback) {
-  const tagInstances = []
-
-  each<string, Error>(tags, function (tag, callbackEach) {
-    const query: any = {
-      where: {
-        name: tag
-      },
-      defaults: {
-        name: tag
-      }
-    }
-
-    if (transaction) query.transaction = transaction
-
-    Tag.findOrCreate(query).asCallback(function (err, res) {
-      if (err) return callbackEach(err)
-
-      // res = [ tag, isCreated ]
-      const tag = res[0]
-      tagInstances.push(tag)
-      return callbackEach()
-    })
-  }, function (err) {
-    return callback(err, tagInstances)
-  })
-}
diff --git a/server/models/user-interface.ts b/server/models/user-interface.ts
deleted file mode 100644 (file)
index 98963b7..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-import * as Sequelize from 'sequelize'
-import * as Bluebird from 'bluebird'
-
-// Don't use barrel, import just what we need
-import { User as FormatedUser } from '../../shared/models/user.model'
-
-export namespace UserMethods {
-  export type IsPasswordMatchCallback = (err: Error, same: boolean) => void
-  export type IsPasswordMatch = (password: string, callback: IsPasswordMatchCallback) => void
-
-  export type ToFormatedJSON = () => FormatedUser
-  export type IsAdmin = () => boolean
-
-  export type CountTotalCallback = (err: Error, total: number) => void
-  export type CountTotal = (callback: CountTotalCallback) => void
-
-  export type GetByUsername = (username: string) => Bluebird<UserInstance>
-
-  export type ListCallback = (err: Error, userInstances: UserInstance[]) => void
-  export type List = (callback: ListCallback) => void
-
-  export type ListForApiCallback = (err: Error, userInstances?: UserInstance[], total?: number) => void
-  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
-
-  export type LoadByIdCallback = (err: Error, userInstance: UserInstance) => void
-  export type LoadById = (id: number, callback: LoadByIdCallback) => void
-
-  export type LoadByUsernameCallback = (err: Error, userInstance: UserInstance) => void
-  export type LoadByUsername = (username: string, callback: LoadByUsernameCallback) => void
-
-  export type LoadByUsernameOrEmailCallback = (err: Error, userInstance: UserInstance) => void
-  export type LoadByUsernameOrEmail = (username: string, email: string, callback: LoadByUsernameOrEmailCallback) => void
-}
-
-export interface UserClass {
-  isPasswordMatch: UserMethods.IsPasswordMatch,
-  toFormatedJSON: UserMethods.ToFormatedJSON,
-  isAdmin: UserMethods.IsAdmin,
-
-  countTotal: UserMethods.CountTotal,
-  getByUsername: UserMethods.GetByUsername,
-  list: UserMethods.List,
-  listForApi: UserMethods.ListForApi,
-  loadById: UserMethods.LoadById,
-  loadByUsername: UserMethods.LoadByUsername,
-  loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
-}
-
-export interface UserAttributes {
-  password: string
-  username: string
-  email: string
-  displayNSFW?: boolean
-  role: string
-}
-
-export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
diff --git a/server/models/user-video-rate-interface.ts b/server/models/user-video-rate-interface.ts
deleted file mode 100644 (file)
index e48869f..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-export namespace UserVideoRateMethods {
-  export type LoadCallback = (err: Error, userVideoRateInstance: UserVideoRateInstance) => void
-  export type Load = (userId, videoId, transaction, callback) => void
-}
-
-export interface UserVideoRateClass {
-  load: UserVideoRateMethods.Load
-}
-
-export interface UserVideoRateAttributes {
-  type: string
-}
-
-export interface UserVideoRateInstance extends UserVideoRateClass, UserVideoRateAttributes, Sequelize.Instance<UserVideoRateAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface UserVideoRateModel extends UserVideoRateClass, Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes> {}
diff --git a/server/models/user-video-rate.ts b/server/models/user-video-rate.ts
deleted file mode 100644 (file)
index 0326e17..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
-  User rates per video.
-
-*/
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { VIDEO_RATE_TYPES } from '../initializers'
-
-import { addMethodsToModel } from './utils'
-import {
-  UserVideoRateClass,
-  UserVideoRateInstance,
-  UserVideoRateAttributes,
-
-  UserVideoRateMethods
-} from './user-video-rate-interface'
-
-let UserVideoRate: Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes>
-let load: UserVideoRateMethods.Load
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  UserVideoRate = sequelize.define<UserVideoRateInstance, UserVideoRateAttributes>('UserVideoRate',
-    {
-      type: {
-        type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)),
-        allowNull: false
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'videoId', 'userId', 'type' ],
-          unique: true
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    load
-  ]
-  addMethodsToModel(UserVideoRate, classMethods)
-
-  return UserVideoRate
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  UserVideoRate.belongsTo(models.Video, {
-    foreignKey: {
-      name: 'videoId',
-      allowNull: false
-    },
-    onDelete: 'CASCADE'
-  })
-
-  UserVideoRate.belongsTo(models.User, {
-    foreignKey: {
-      name: 'userId',
-      allowNull: false
-    },
-    onDelete: 'CASCADE'
-  })
-}
-
-load = function (userId: number, videoId: number, transaction: Sequelize.Transaction, callback: UserVideoRateMethods.LoadCallback) {
-  const options: Sequelize.FindOptions = {
-    where: {
-      userId,
-      videoId
-    }
-  }
-  if (transaction) options.transaction = transaction
-
-  return UserVideoRate.findOne(options).asCallback(callback)
-}
diff --git a/server/models/user.ts b/server/models/user.ts
deleted file mode 100644 (file)
index 1a56a6d..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-import { values } from 'lodash'
-import * as Sequelize from 'sequelize'
-
-import { getSort } from './utils'
-import { USER_ROLES } from '../initializers'
-import {
-  cryptPassword,
-  comparePassword,
-  isUserPasswordValid,
-  isUserUsernameValid,
-  isUserDisplayNSFWValid
-} from '../helpers'
-
-import { addMethodsToModel } from './utils'
-import {
-  UserClass,
-  UserInstance,
-  UserAttributes,
-
-  UserMethods
-} from './user-interface'
-
-let User: Sequelize.Model<UserInstance, UserAttributes>
-let isPasswordMatch: UserMethods.IsPasswordMatch
-let toFormatedJSON: UserMethods.ToFormatedJSON
-let isAdmin: UserMethods.IsAdmin
-let countTotal: UserMethods.CountTotal
-let getByUsername: UserMethods.GetByUsername
-let list: UserMethods.List
-let listForApi: UserMethods.ListForApi
-let loadById: UserMethods.LoadById
-let loadByUsername: UserMethods.LoadByUsername
-let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  User = sequelize.define<UserInstance, UserAttributes>('User',
-    {
-      password: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          passwordValid: function (value) {
-            const res = isUserPasswordValid(value)
-            if (res === false) throw new Error('Password not valid.')
-          }
-        }
-      },
-      username: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          usernameValid: function (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: function (value) {
-            const res = isUserDisplayNSFWValid(value)
-            if (res === false) throw new Error('Display NSFW is not valid.')
-          }
-        }
-      },
-      role: {
-        type: DataTypes.ENUM(values(USER_ROLES)),
-        allowNull: false
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'username' ],
-          unique: true
-        },
-        {
-          fields: [ 'email' ],
-          unique: true
-        }
-      ],
-      hooks: {
-        beforeCreate: beforeCreateOrUpdate,
-        beforeUpdate: beforeCreateOrUpdate
-      }
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    countTotal,
-    getByUsername,
-    list,
-    listForApi,
-    loadById,
-    loadByUsername,
-    loadByUsernameOrEmail
-  ]
-  const instanceMethods = [
-    isPasswordMatch,
-    toFormatedJSON,
-    isAdmin
-  ]
-  addMethodsToModel(User, classMethods, instanceMethods)
-
-  return User
-}
-
-function beforeCreateOrUpdate (user: UserInstance) {
-  return new Promise(function (resolve, reject) {
-    cryptPassword(user.password, function (err, hash) {
-      if (err) return reject(err)
-
-      user.password = hash
-
-      return resolve()
-    })
-  })
-}
-
-// ------------------------------ METHODS ------------------------------
-
-isPasswordMatch = function (password: string, callback: UserMethods.IsPasswordMatchCallback) {
-  return comparePassword(password, this.password, callback)
-}
-
-toFormatedJSON = function (this: UserInstance) {
-  return {
-    id: this.id,
-    username: this.username,
-    email: this.email,
-    displayNSFW: this.displayNSFW,
-    role: this.role,
-    createdAt: this.createdAt
-  }
-}
-
-isAdmin = function () {
-  return this.role === USER_ROLES.ADMIN
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  User.hasOne(models.Author, {
-    foreignKey: 'userId',
-    onDelete: 'cascade'
-  })
-
-  User.hasMany(models.OAuthToken, {
-    foreignKey: 'userId',
-    onDelete: 'cascade'
-  })
-}
-
-countTotal = function (callback: UserMethods.CountTotalCallback) {
-  return this.count().asCallback(callback)
-}
-
-getByUsername = function (username: string) {
-  const query = {
-    where: {
-      username: username
-    }
-  }
-
-  return User.findOne(query)
-}
-
-list = function (callback: UserMethods.ListCallback) {
-  return User.find().asCallback(callback)
-}
-
-listForApi = function (start: number, count: number, sort: string, callback: UserMethods.ListForApiCallback) {
-  const query = {
-    offset: start,
-    limit: count,
-    order: [ getSort(sort) ]
-  }
-
-  return User.findAndCountAll(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    return callback(null, result.rows, result.count)
-  })
-}
-
-loadById = function (id: number, callback: UserMethods.LoadByIdCallback) {
-  return User.findById(id).asCallback(callback)
-}
-
-loadByUsername = function (username: string, callback: UserMethods.LoadByUsernameCallback) {
-  const query = {
-    where: {
-      username: username
-    }
-  }
-
-  return User.findOne(query).asCallback(callback)
-}
-
-loadByUsernameOrEmail = function (username: string, email: string, callback: UserMethods.LoadByUsernameOrEmailCallback) {
-  const query = {
-    where: {
-      $or: [ { username }, { email } ]
-    }
-  }
-
-  return User.findOne(query).asCallback(callback)
-}
diff --git a/server/models/user/index.ts b/server/models/user/index.ts
new file mode 100644 (file)
index 0000000..ed36895
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './user-video-rate-interface'
+export * from './user-interface'
diff --git a/server/models/user/user-interface.ts b/server/models/user/user-interface.ts
new file mode 100644 (file)
index 0000000..1ba4bd8
--- /dev/null
@@ -0,0 +1,63 @@
+import * as Sequelize from 'sequelize'
+import * as Bluebird from 'bluebird'
+
+// Don't use barrel, import just what we need
+import { User as FormatedUser } from '../../../shared/models/user.model'
+
+export namespace UserMethods {
+  export type IsPasswordMatchCallback = (err: Error, same: boolean) => void
+  export type IsPasswordMatch = (password: string, callback: IsPasswordMatchCallback) => void
+
+  export type ToFormatedJSON = () => FormatedUser
+  export type IsAdmin = () => boolean
+
+  export type CountTotalCallback = (err: Error, total: number) => void
+  export type CountTotal = (callback: CountTotalCallback) => void
+
+  export type GetByUsername = (username: string) => Bluebird<UserInstance>
+
+  export type ListCallback = (err: Error, userInstances: UserInstance[]) => void
+  export type List = (callback: ListCallback) => void
+
+  export type ListForApiCallback = (err: Error, userInstances?: UserInstance[], total?: number) => void
+  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
+
+  export type LoadByIdCallback = (err: Error, userInstance: UserInstance) => void
+  export type LoadById = (id: number, callback: LoadByIdCallback) => void
+
+  export type LoadByUsernameCallback = (err: Error, userInstance: UserInstance) => void
+  export type LoadByUsername = (username: string, callback: LoadByUsernameCallback) => void
+
+  export type LoadByUsernameOrEmailCallback = (err: Error, userInstance: UserInstance) => void
+  export type LoadByUsernameOrEmail = (username: string, email: string, callback: LoadByUsernameOrEmailCallback) => void
+}
+
+export interface UserClass {
+  isPasswordMatch: UserMethods.IsPasswordMatch,
+  toFormatedJSON: UserMethods.ToFormatedJSON,
+  isAdmin: UserMethods.IsAdmin,
+
+  countTotal: UserMethods.CountTotal,
+  getByUsername: UserMethods.GetByUsername,
+  list: UserMethods.List,
+  listForApi: UserMethods.ListForApi,
+  loadById: UserMethods.LoadById,
+  loadByUsername: UserMethods.LoadByUsername,
+  loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
+}
+
+export interface UserAttributes {
+  password: string
+  username: string
+  email: string
+  displayNSFW?: boolean
+  role: string
+}
+
+export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface UserModel extends UserClass, Sequelize.Model<UserInstance, UserAttributes> {}
diff --git a/server/models/user/user-video-rate-interface.ts b/server/models/user/user-video-rate-interface.ts
new file mode 100644 (file)
index 0000000..e48869f
--- /dev/null
@@ -0,0 +1,22 @@
+import * as Sequelize from 'sequelize'
+
+export namespace UserVideoRateMethods {
+  export type LoadCallback = (err: Error, userVideoRateInstance: UserVideoRateInstance) => void
+  export type Load = (userId, videoId, transaction, callback) => void
+}
+
+export interface UserVideoRateClass {
+  load: UserVideoRateMethods.Load
+}
+
+export interface UserVideoRateAttributes {
+  type: string
+}
+
+export interface UserVideoRateInstance extends UserVideoRateClass, UserVideoRateAttributes, Sequelize.Instance<UserVideoRateAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface UserVideoRateModel extends UserVideoRateClass, Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes> {}
diff --git a/server/models/user/user-video-rate.ts b/server/models/user/user-video-rate.ts
new file mode 100644 (file)
index 0000000..68be62f
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+  User rates per video.
+
+*/
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { VIDEO_RATE_TYPES } from '../../initializers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  UserVideoRateClass,
+  UserVideoRateInstance,
+  UserVideoRateAttributes,
+
+  UserVideoRateMethods
+} from './user-video-rate-interface'
+
+let UserVideoRate: Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes>
+let load: UserVideoRateMethods.Load
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  UserVideoRate = sequelize.define<UserVideoRateInstance, UserVideoRateAttributes>('UserVideoRate',
+    {
+      type: {
+        type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)),
+        allowNull: false
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'videoId', 'userId', 'type' ],
+          unique: true
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    load
+  ]
+  addMethodsToModel(UserVideoRate, classMethods)
+
+  return UserVideoRate
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  UserVideoRate.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+
+  UserVideoRate.belongsTo(models.User, {
+    foreignKey: {
+      name: 'userId',
+      allowNull: false
+    },
+    onDelete: 'CASCADE'
+  })
+}
+
+load = function (userId: number, videoId: number, transaction: Sequelize.Transaction, callback: UserVideoRateMethods.LoadCallback) {
+  const options: Sequelize.FindOptions = {
+    where: {
+      userId,
+      videoId
+    }
+  }
+  if (transaction) options.transaction = transaction
+
+  return UserVideoRate.findOne(options).asCallback(callback)
+}
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
new file mode 100644 (file)
index 0000000..d78f5f8
--- /dev/null
@@ -0,0 +1,221 @@
+import { values } from 'lodash'
+import * as Sequelize from 'sequelize'
+
+import { getSort } from '../utils'
+import { USER_ROLES } from '../../initializers'
+import {
+  cryptPassword,
+  comparePassword,
+  isUserPasswordValid,
+  isUserUsernameValid,
+  isUserDisplayNSFWValid
+} from '../../helpers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  UserClass,
+  UserInstance,
+  UserAttributes,
+
+  UserMethods
+} from './user-interface'
+
+let User: Sequelize.Model<UserInstance, UserAttributes>
+let isPasswordMatch: UserMethods.IsPasswordMatch
+let toFormatedJSON: UserMethods.ToFormatedJSON
+let isAdmin: UserMethods.IsAdmin
+let countTotal: UserMethods.CountTotal
+let getByUsername: UserMethods.GetByUsername
+let list: UserMethods.List
+let listForApi: UserMethods.ListForApi
+let loadById: UserMethods.LoadById
+let loadByUsername: UserMethods.LoadByUsername
+let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  User = sequelize.define<UserInstance, UserAttributes>('User',
+    {
+      password: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          passwordValid: function (value) {
+            const res = isUserPasswordValid(value)
+            if (res === false) throw new Error('Password not valid.')
+          }
+        }
+      },
+      username: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          usernameValid: function (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: function (value) {
+            const res = isUserDisplayNSFWValid(value)
+            if (res === false) throw new Error('Display NSFW is not valid.')
+          }
+        }
+      },
+      role: {
+        type: DataTypes.ENUM(values(USER_ROLES)),
+        allowNull: false
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'username' ],
+          unique: true
+        },
+        {
+          fields: [ 'email' ],
+          unique: true
+        }
+      ],
+      hooks: {
+        beforeCreate: beforeCreateOrUpdate,
+        beforeUpdate: beforeCreateOrUpdate
+      }
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    countTotal,
+    getByUsername,
+    list,
+    listForApi,
+    loadById,
+    loadByUsername,
+    loadByUsernameOrEmail
+  ]
+  const instanceMethods = [
+    isPasswordMatch,
+    toFormatedJSON,
+    isAdmin
+  ]
+  addMethodsToModel(User, classMethods, instanceMethods)
+
+  return User
+}
+
+function beforeCreateOrUpdate (user: UserInstance) {
+  return new Promise(function (resolve, reject) {
+    cryptPassword(user.password, function (err, hash) {
+      if (err) return reject(err)
+
+      user.password = hash
+
+      return resolve()
+    })
+  })
+}
+
+// ------------------------------ METHODS ------------------------------
+
+isPasswordMatch = function (password: string, callback: UserMethods.IsPasswordMatchCallback) {
+  return comparePassword(password, this.password, callback)
+}
+
+toFormatedJSON = function (this: UserInstance) {
+  return {
+    id: this.id,
+    username: this.username,
+    email: this.email,
+    displayNSFW: this.displayNSFW,
+    role: this.role,
+    createdAt: this.createdAt
+  }
+}
+
+isAdmin = function () {
+  return this.role === USER_ROLES.ADMIN
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  User.hasOne(models.Author, {
+    foreignKey: 'userId',
+    onDelete: 'cascade'
+  })
+
+  User.hasMany(models.OAuthToken, {
+    foreignKey: 'userId',
+    onDelete: 'cascade'
+  })
+}
+
+countTotal = function (callback: UserMethods.CountTotalCallback) {
+  return this.count().asCallback(callback)
+}
+
+getByUsername = function (username: string) {
+  const query = {
+    where: {
+      username: username
+    }
+  }
+
+  return User.findOne(query)
+}
+
+list = function (callback: UserMethods.ListCallback) {
+  return User.find().asCallback(callback)
+}
+
+listForApi = function (start: number, count: number, sort: string, callback: UserMethods.ListForApiCallback) {
+  const query = {
+    offset: start,
+    limit: count,
+    order: [ getSort(sort) ]
+  }
+
+  return User.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+loadById = function (id: number, callback: UserMethods.LoadByIdCallback) {
+  return User.findById(id).asCallback(callback)
+}
+
+loadByUsername = function (username: string, callback: UserMethods.LoadByUsernameCallback) {
+  const query = {
+    where: {
+      username: username
+    }
+  }
+
+  return User.findOne(query).asCallback(callback)
+}
+
+loadByUsernameOrEmail = function (username: string, email: string, callback: UserMethods.LoadByUsernameOrEmailCallback) {
+  const query = {
+    where: {
+      $or: [ { username }, { email } ]
+    }
+  }
+
+  return User.findOne(query).asCallback(callback)
+}
diff --git a/server/models/video-abuse-interface.ts b/server/models/video-abuse-interface.ts
deleted file mode 100644 (file)
index d9cb93b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-// Don't use barrel, import just what we need
-import { VideoAbuse as FormatedVideoAbuse } from '../../shared/models/video-abuse.model'
-
-export namespace VideoAbuseMethods {
-  export type toFormatedJSON = () => FormatedVideoAbuse
-
-  export type ListForApiCallback = (err: Error, videoAbuseInstances?: VideoAbuseInstance[], total?: number) => void
-  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
-}
-
-export interface VideoAbuseClass {
-  listForApi: VideoAbuseMethods.ListForApi
-}
-
-export interface VideoAbuseAttributes {
-  reporterUsername: string
-  reason: string
-}
-
-export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface VideoAbuseModel extends VideoAbuseClass, Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes> {}
diff --git a/server/models/video-abuse.ts b/server/models/video-abuse.ts
deleted file mode 100644 (file)
index 5602ef9..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { CONFIG } from '../initializers'
-import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../helpers'
-
-import { addMethodsToModel, getSort } from './utils'
-import {
-  VideoAbuseClass,
-  VideoAbuseInstance,
-  VideoAbuseAttributes,
-
-  VideoAbuseMethods
-} from './video-abuse-interface'
-
-let VideoAbuse: Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes>
-let listForApi: VideoAbuseMethods.ListForApi
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse',
-    {
-      reporterUsername: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          reporterUsernameValid: function (value) {
-            const res = isVideoAbuseReporterUsernameValid(value)
-            if (res === false) throw new Error('Video abuse reporter username is not valid.')
-          }
-        }
-      },
-      reason: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          reasonValid: function (value) {
-            const res = isVideoAbuseReasonValid(value)
-            if (res === false) throw new Error('Video abuse reason is not valid.')
-          }
-        }
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'videoId' ]
-        },
-        {
-          fields: [ 'reporterPodId' ]
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    listForApi
-  ]
-  const instanceMethods = [
-    toFormatedJSON
-  ]
-  addMethodsToModel(VideoAbuse, classMethods, instanceMethods)
-
-  return VideoAbuse
-}
-
-// ------------------------------ METHODS ------------------------------
-
-function toFormatedJSON () {
-  let reporterPodHost
-
-  if (this.Pod) {
-    reporterPodHost = this.Pod.host
-  } else {
-    // It means it's our video
-    reporterPodHost = CONFIG.WEBSERVER.HOST
-  }
-
-  const json = {
-    id: this.id,
-    reporterPodHost,
-    reason: this.reason,
-    reporterUsername: this.reporterUsername,
-    videoId: this.videoId,
-    createdAt: this.createdAt
-  }
-
-  return json
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  VideoAbuse.belongsTo(models.Pod, {
-    foreignKey: {
-      name: 'reporterPodId',
-      allowNull: true
-    },
-    onDelete: 'cascade'
-  })
-
-  VideoAbuse.belongsTo(models.Video, {
-    foreignKey: {
-      name: 'videoId',
-      allowNull: false
-    },
-    onDelete: 'cascade'
-  })
-}
-
-listForApi = function (start, count, sort, callback) {
-  const query = {
-    offset: start,
-    limit: count,
-    order: [ getSort(sort) ],
-    include: [
-      {
-        model: VideoAbuse['sequelize'].models.Pod,
-        required: false
-      }
-    ]
-  }
-
-  return VideoAbuse.findAndCountAll(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    return callback(null, result.rows, result.count)
-  })
-}
-
-
diff --git a/server/models/video-blacklist-interface.ts b/server/models/video-blacklist-interface.ts
deleted file mode 100644 (file)
index 9747181..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-// Don't use barrel, import just what we need
-import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../shared/models/video-blacklist.model'
-
-export namespace BlacklistedVideoMethods {
-  export type ToFormatedJSON = () => FormatedBlacklistedVideo
-
-  export type CountTotalCallback = (err: Error, total: number) => void
-  export type CountTotal = (callback: CountTotalCallback) => void
-
-  export type ListCallback = (err: Error, backlistedVideoInstances: BlacklistedVideoInstance[]) => void
-  export type List = (callback: ListCallback) => void
-
-  export type ListForApiCallback = (err: Error, blacklistedVIdeoInstances?: BlacklistedVideoInstance[], total?: number) => void
-  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
-
-  export type LoadByIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
-  export type LoadById = (id: number, callback: LoadByIdCallback) => void
-
-  export type LoadByVideoIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
-  export type LoadByVideoId = (id: string, callback: LoadByVideoIdCallback) => void
-}
-
-export interface BlacklistedVideoClass {
-  toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON
-  countTotal: BlacklistedVideoMethods.CountTotal
-  list: BlacklistedVideoMethods.List
-  listForApi: BlacklistedVideoMethods.ListForApi
-  loadById: BlacklistedVideoMethods.LoadById
-  loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
-}
-
-export interface BlacklistedVideoAttributes {
-}
-
-export interface BlacklistedVideoInstance extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
-  id: number
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface BlacklistedVideoModel extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}
diff --git a/server/models/video-blacklist.ts b/server/models/video-blacklist.ts
deleted file mode 100644 (file)
index 040ed03..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel, getSort } from './utils'
-import {
-  BlacklistedVideoClass,
-  BlacklistedVideoInstance,
-  BlacklistedVideoAttributes,
-
-  BlacklistedVideoMethods
-} from './video-blacklist-interface'
-
-let BlacklistedVideo: Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes>
-let toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON
-let countTotal: BlacklistedVideoMethods.CountTotal
-let list: BlacklistedVideoMethods.List
-let listForApi: BlacklistedVideoMethods.ListForApi
-let loadById: BlacklistedVideoMethods.LoadById
-let loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  BlacklistedVideo = sequelize.define<BlacklistedVideoInstance, BlacklistedVideoAttributes>('BlacklistedVideo',
-    {},
-    {
-      indexes: [
-        {
-          fields: [ 'videoId' ],
-          unique: true
-        }
-      ]
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    countTotal,
-    list,
-    listForApi,
-    loadById,
-    loadByVideoId
-  ]
-  const instanceMethods = [
-    toFormatedJSON
-  ]
-  addMethodsToModel(BlacklistedVideo, classMethods, instanceMethods)
-
-  return BlacklistedVideo
-}
-
-// ------------------------------ METHODS ------------------------------
-
-toFormatedJSON = function () {
-  return {
-    id: this.id,
-    videoId: this.videoId,
-    createdAt: this.createdAt
-  }
-}
-
-// ------------------------------ STATICS ------------------------------
-
-function associate (models) {
-  BlacklistedVideo.belongsTo(models.Video, {
-    foreignKey: 'videoId',
-    onDelete: 'cascade'
-  })
-}
-
-countTotal = function (callback: BlacklistedVideoMethods.CountTotalCallback) {
-  return BlacklistedVideo.count().asCallback(callback)
-}
-
-list = function (callback: BlacklistedVideoMethods.ListCallback) {
-  return BlacklistedVideo.findAll().asCallback(callback)
-}
-
-listForApi = function (start: number, count: number, sort: string, callback: BlacklistedVideoMethods.ListForApiCallback) {
-  const query = {
-    offset: start,
-    limit: count,
-    order: [ getSort(sort) ]
-  }
-
-  return BlacklistedVideo.findAndCountAll(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    return callback(null, result.rows, result.count)
-  })
-}
-
-loadById = function (id: number, callback: BlacklistedVideoMethods.LoadByIdCallback) {
-  return BlacklistedVideo.findById(id).asCallback(callback)
-}
-
-loadByVideoId = function (id: string, callback: BlacklistedVideoMethods.LoadByIdCallback) {
-  const query = {
-    where: {
-      videoId: id
-    }
-  }
-
-  return BlacklistedVideo.find(query).asCallback(callback)
-}
diff --git a/server/models/video-interface.ts b/server/models/video-interface.ts
deleted file mode 100644 (file)
index 7120f91..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { AuthorInstance } from './author-interface'
-import { VideoTagInstance } from './video-tag-interface'
-
-// Don't use barrel, import just what we need
-import { Video as FormatedVideo } from '../../shared/models/video.model'
-
-export type FormatedAddRemoteVideo = {
-  name: string
-  category: number
-  licence: number
-  language: number
-  nsfw: boolean
-  description: string
-  infoHash: string
-  remoteId: string
-  author: string
-  duration: number
-  thumbnailData: string
-  tags: string[]
-  createdAt: Date
-  updatedAt: Date
-  extname: string
-  views: number
-  likes: number
-  dislikes: number
-}
-
-export type FormatedUpdateRemoteVideo = {
-  name: string
-  category: number
-  licence: number
-  language: number
-  nsfw: boolean
-  description: string
-  infoHash: string
-  remoteId: string
-  author: string
-  duration: number
-  tags: string[]
-  createdAt: Date
-  updatedAt: Date
-  extname: string
-  views: number
-  likes: number
-  dislikes: number
-}
-
-export namespace VideoMethods {
-  export type GenerateMagnetUri = () => string
-  export type GetVideoFilename = () => string
-  export type GetThumbnailName = () => string
-  export type GetPreviewName = () => string
-  export type GetTorrentName = () => string
-  export type IsOwned = () => boolean
-  export type ToFormatedJSON = () => FormatedVideo
-
-  export type ToAddRemoteJSONCallback = (err: Error, videoFormated?: FormatedAddRemoteVideo) => void
-  export type ToAddRemoteJSON = (callback: ToAddRemoteJSONCallback) => void
-
-  export type ToUpdateRemoteJSON = () => FormatedUpdateRemoteVideo
-
-  export type TranscodeVideofileCallback = (err: Error) => void
-  export type TranscodeVideofile = (callback: TranscodeVideofileCallback) => void
-
-  export type GenerateThumbnailFromDataCallback = (err: Error, thumbnailName?: string) => void
-  export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string, callback: GenerateThumbnailFromDataCallback) => void
-
-  export type GetDurationFromFileCallback = (err: Error, duration?: number) => void
-  export type GetDurationFromFile = (videoPath, callback) => void
-
-  export type ListCallback = (err: Error, videoInstances: VideoInstance[]) => void
-  export type List = (callback: ListCallback) => void
-
-  export type ListForApiCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
-  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
-
-  export type LoadByHostAndRemoteIdCallback = (err: Error, videoInstance: VideoInstance) => void
-  export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string, callback: LoadByHostAndRemoteIdCallback) => void
-
-  export type ListOwnedAndPopulateAuthorAndTagsCallback = (err: Error, videoInstances: VideoInstance[]) => void
-  export type ListOwnedAndPopulateAuthorAndTags = (callback: ListOwnedAndPopulateAuthorAndTagsCallback) => void
-
-  export type ListOwnedByAuthorCallback = (err: Error, videoInstances: VideoInstance[]) => void
-  export type ListOwnedByAuthor = (author: string, callback: ListOwnedByAuthorCallback) => void
-
-  export type LoadCallback = (err: Error, videoInstance: VideoInstance) => void
-  export type Load = (id: string, callback: LoadCallback) => void
-
-  export type LoadAndPopulateAuthorCallback = (err: Error, videoInstance: VideoInstance) => void
-  export type LoadAndPopulateAuthor = (id: string, callback: LoadAndPopulateAuthorCallback) => void
-
-  export type LoadAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstance: VideoInstance) => void
-  export type LoadAndPopulateAuthorAndPodAndTags = (id: string, callback: LoadAndPopulateAuthorAndPodAndTagsCallback) => void
-
-  export type SearchAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
-  export type SearchAndPopulateAuthorAndPodAndTags = (value: string, field: string, start: number, count: number, sort: string, callback: SearchAndPopulateAuthorAndPodAndTagsCallback) => void
-}
-
-export interface VideoClass {
-  generateMagnetUri: VideoMethods.GenerateMagnetUri
-  getVideoFilename: VideoMethods.GetVideoFilename
-  getThumbnailName: VideoMethods.GetThumbnailName
-  getPreviewName: VideoMethods.GetPreviewName
-  getTorrentName: VideoMethods.GetTorrentName
-  isOwned: VideoMethods.IsOwned
-  toFormatedJSON: VideoMethods.ToFormatedJSON
-  toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
-  toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
-  transcodeVideofile: VideoMethods.TranscodeVideofile
-
-  generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
-  getDurationFromFile: VideoMethods.GetDurationFromFile
-  list: VideoMethods.List
-  listForApi: VideoMethods.ListForApi
-  loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
-  listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
-  listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
-  load: VideoMethods.Load
-  loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
-  loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
-  searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
-}
-
-export interface VideoAttributes {
-  name: string
-  extname: string
-  remoteId: string
-  category: number
-  licence: number
-  language: number
-  nsfw: boolean
-  description: string
-  infoHash?: string
-  duration: number
-  views?: number
-  likes?: number
-  dislikes?: number
-
-  Author?: AuthorInstance
-  Tags?: VideoTagInstance[]
-}
-
-export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
-  id: string
-  createdAt: Date
-  updatedAt: Date
-}
-
-export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
diff --git a/server/models/video-tag-interface.ts b/server/models/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> {}
diff --git a/server/models/video-tag.ts b/server/models/video-tag.ts
deleted file mode 100644 (file)
index 514eede..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-import * as Sequelize from 'sequelize'
-
-import { addMethodsToModel } from './utils'
-import {
-  VideoTagClass,
-  VideoTagInstance,
-  VideoTagAttributes,
-
-  VideoTagMethods
-} from './video-tag-interface'
-
-let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes>
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  VideoTag = sequelize.define<VideoTagInstance, VideoTagAttributes>('VideoTag', {}, {
-    indexes: [
-      {
-        fields: [ 'videoId' ]
-      },
-      {
-        fields: [ 'tagId' ]
-      }
-    ]
-  })
-
-  return VideoTag
-}
diff --git a/server/models/video.ts b/server/models/video.ts
deleted file mode 100644 (file)
index 78119f5..0000000
+++ /dev/null
@@ -1,921 +0,0 @@
-import * as safeBuffer from 'safe-buffer'
-const Buffer = safeBuffer.Buffer
-import * as createTorrent from 'create-torrent'
-import * as ffmpeg from 'fluent-ffmpeg'
-import * as fs from 'fs'
-import * as magnetUtil from 'magnet-uri'
-import { map, values } from 'lodash'
-import { parallel, series } from 'async'
-import * as parseTorrent from 'parse-torrent'
-import { join } from 'path'
-import * as Sequelize from 'sequelize'
-
-import { database as db } from '../initializers/database'
-import { VideoTagInstance } from './video-tag-interface'
-import {
-  logger,
-  isVideoNameValid,
-  isVideoCategoryValid,
-  isVideoLicenceValid,
-  isVideoLanguageValid,
-  isVideoNSFWValid,
-  isVideoDescriptionValid,
-  isVideoInfoHashValid,
-  isVideoDurationValid
-} from '../helpers'
-import {
-  CONSTRAINTS_FIELDS,
-  CONFIG,
-  REMOTE_SCHEME,
-  STATIC_PATHS,
-  VIDEO_CATEGORIES,
-  VIDEO_LICENCES,
-  VIDEO_LANGUAGES,
-  THUMBNAILS_SIZE
-} from '../initializers'
-import { JobScheduler, removeVideoToFriends } from '../lib'
-
-import { addMethodsToModel, getSort } from './utils'
-import {
-  VideoClass,
-  VideoInstance,
-  VideoAttributes,
-
-  VideoMethods
-} from './video-interface'
-
-let Video: Sequelize.Model<VideoInstance, VideoAttributes>
-let generateMagnetUri: VideoMethods.GenerateMagnetUri
-let getVideoFilename: VideoMethods.GetVideoFilename
-let getThumbnailName: VideoMethods.GetThumbnailName
-let getPreviewName: VideoMethods.GetPreviewName
-let getTorrentName: VideoMethods.GetTorrentName
-let isOwned: VideoMethods.IsOwned
-let toFormatedJSON: VideoMethods.ToFormatedJSON
-let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
-let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
-let transcodeVideofile: VideoMethods.TranscodeVideofile
-
-let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
-let getDurationFromFile: VideoMethods.GetDurationFromFile
-let list: VideoMethods.List
-let listForApi: VideoMethods.ListForApi
-let loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
-let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
-let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
-let load: VideoMethods.Load
-let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
-let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
-let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
-
-export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
-  Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
-    {
-      id: {
-        type: DataTypes.UUID,
-        defaultValue: DataTypes.UUIDV4,
-        primaryKey: true,
-        validate: {
-          isUUID: 4
-        }
-      },
-      name: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          nameValid: function (value) {
-            const res = isVideoNameValid(value)
-            if (res === false) throw new Error('Video name is not valid.')
-          }
-        }
-      },
-      extname: {
-        type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
-        allowNull: false
-      },
-      remoteId: {
-        type: DataTypes.UUID,
-        allowNull: true,
-        validate: {
-          isUUID: 4
-        }
-      },
-      category: {
-        type: DataTypes.INTEGER,
-        allowNull: false,
-        validate: {
-          categoryValid: function (value) {
-            const res = isVideoCategoryValid(value)
-            if (res === false) throw new Error('Video category is not valid.')
-          }
-        }
-      },
-      licence: {
-        type: DataTypes.INTEGER,
-        allowNull: false,
-        defaultValue: null,
-        validate: {
-          licenceValid: function (value) {
-            const res = isVideoLicenceValid(value)
-            if (res === false) throw new Error('Video licence is not valid.')
-          }
-        }
-      },
-      language: {
-        type: DataTypes.INTEGER,
-        allowNull: true,
-        validate: {
-          languageValid: function (value) {
-            const res = isVideoLanguageValid(value)
-            if (res === false) throw new Error('Video language is not valid.')
-          }
-        }
-      },
-      nsfw: {
-        type: DataTypes.BOOLEAN,
-        allowNull: false,
-        validate: {
-          nsfwValid: function (value) {
-            const res = isVideoNSFWValid(value)
-            if (res === false) throw new Error('Video nsfw attribute is not valid.')
-          }
-        }
-      },
-      description: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          descriptionValid: function (value) {
-            const res = isVideoDescriptionValid(value)
-            if (res === false) throw new Error('Video description is not valid.')
-          }
-        }
-      },
-      infoHash: {
-        type: DataTypes.STRING,
-        allowNull: false,
-        validate: {
-          infoHashValid: function (value) {
-            const res = isVideoInfoHashValid(value)
-            if (res === false) throw new Error('Video info hash is not valid.')
-          }
-        }
-      },
-      duration: {
-        type: DataTypes.INTEGER,
-        allowNull: false,
-        validate: {
-          durationValid: function (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
-        }
-      }
-    },
-    {
-      indexes: [
-        {
-          fields: [ 'authorId' ]
-        },
-        {
-          fields: [ 'remoteId' ]
-        },
-        {
-          fields: [ 'name' ]
-        },
-        {
-          fields: [ 'createdAt' ]
-        },
-        {
-          fields: [ 'duration' ]
-        },
-        {
-          fields: [ 'infoHash' ]
-        },
-        {
-          fields: [ 'views' ]
-        },
-        {
-          fields: [ 'likes' ]
-        }
-      ],
-      hooks: {
-        beforeValidate,
-        beforeCreate,
-        afterDestroy
-      }
-    }
-  )
-
-  const classMethods = [
-    associate,
-
-    generateThumbnailFromData,
-    getDurationFromFile,
-    list,
-    listForApi,
-    listOwnedAndPopulateAuthorAndTags,
-    listOwnedByAuthor,
-    load,
-    loadByHostAndRemoteId,
-    loadAndPopulateAuthor,
-    loadAndPopulateAuthorAndPodAndTags,
-    searchAndPopulateAuthorAndPodAndTags
-  ]
-  const instanceMethods = [
-    generateMagnetUri,
-    getVideoFilename,
-    getThumbnailName,
-    getPreviewName,
-    getTorrentName,
-    isOwned,
-    toFormatedJSON,
-    toAddRemoteJSON,
-    toUpdateRemoteJSON,
-    transcodeVideofile,
-    removeFromBlacklist
-  ]
-  addMethodsToModel(Video, classMethods, instanceMethods)
-
-  return Video
-}
-
-function beforeValidate (video: VideoInstance) {
-  // Put a fake infoHash if it does not exists yet
-  if (video.isOwned() && !video.infoHash) {
-    // 40 hexa length
-    video.infoHash = '0123456789abcdef0123456789abcdef01234567'
-  }
-}
-
-function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
-  return new Promise(function (resolve, reject) {
-    const tasks = []
-
-    if (video.isOwned()) {
-      const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
-
-      tasks.push(
-        function createVideoTorrent (callback) {
-          createTorrentFromVideo(video, videoPath, callback)
-        },
-
-        function createVideoThumbnail (callback) {
-          createThumbnail(video, videoPath, callback)
-        },
-
-        function createVideoPreview (callback) {
-          createPreview(video, videoPath, callback)
-        }
-      )
-
-      if (CONFIG.TRANSCODING.ENABLED === true) {
-        tasks.push(
-          function createVideoTranscoderJob (callback) {
-            const dataInput = {
-              id: video.id
-            }
-
-            JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback)
-          }
-        )
-      }
-
-      return parallel(tasks, function (err) {
-        if (err) return reject(err)
-
-        return resolve()
-      })
-    }
-
-    return resolve()
-  })
-}
-
-function afterDestroy (video: VideoInstance) {
-  return new Promise(function (resolve, reject) {
-    const tasks = []
-
-    tasks.push(
-      function (callback) {
-        removeThumbnail(video, callback)
-      }
-    )
-
-    if (video.isOwned()) {
-      tasks.push(
-        function removeVideoFile (callback) {
-          removeFile(video, callback)
-        },
-
-        function removeVideoTorrent (callback) {
-          removeTorrent(video, callback)
-        },
-
-        function removeVideoPreview (callback) {
-          removePreview(video, callback)
-        },
-
-        function notifyFriends (callback) {
-          const params = {
-            remoteId: video.id
-          }
-
-          removeVideoToFriends(params)
-
-          return callback()
-        }
-      )
-    }
-
-    parallel(tasks, function (err) {
-      if (err) return reject(err)
-
-      return resolve()
-    })
-  })
-}
-
-// ------------------------------ METHODS ------------------------------
-
-function associate (models) {
-  Video.belongsTo(models.Author, {
-    foreignKey: {
-      name: 'authorId',
-      allowNull: false
-    },
-    onDelete: 'cascade'
-  })
-
-  Video.belongsToMany(models.Tag, {
-    foreignKey: 'videoId',
-    through: models.VideoTag,
-    onDelete: 'cascade'
-  })
-
-  Video.hasMany(models.VideoAbuse, {
-    foreignKey: {
-      name: 'videoId',
-      allowNull: false
-    },
-    onDelete: 'cascade'
-  })
-}
-
-generateMagnetUri = function () {
-  let baseUrlHttp
-  let baseUrlWs
-
-  if (this.isOwned()) {
-    baseUrlHttp = CONFIG.WEBSERVER.URL
-    baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
-  } else {
-    baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
-    baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
-  }
-
-  const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentName()
-  const announce = [ baseUrlWs + '/tracker/socket' ]
-  const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
-
-  const magnetHash = {
-    xs,
-    announce,
-    urlList,
-    infoHash: this.infoHash,
-    name: this.name
-  }
-
-  return magnetUtil.encode(magnetHash)
-}
-
-getVideoFilename = function () {
-  if (this.isOwned()) return this.id + this.extname
-
-  return this.remoteId + this.extname
-}
-
-getThumbnailName = function () {
-  // We always have a copy of the thumbnail
-  return this.id + '.jpg'
-}
-
-getPreviewName = function () {
-  const extension = '.jpg'
-
-  if (this.isOwned()) return this.id + extension
-
-  return this.remoteId + extension
-}
-
-getTorrentName = function () {
-  const extension = '.torrent'
-
-  if (this.isOwned()) return this.id + extension
-
-  return this.remoteId + extension
-}
-
-isOwned = function () {
-  return this.remoteId === null
-}
-
-toFormatedJSON = function (this: VideoInstance) {
-  let podHost
-
-  if (this.Author.Pod) {
-    podHost = this.Author.Pod.host
-  } else {
-    // It means it's our video
-    podHost = CONFIG.WEBSERVER.HOST
-  }
-
-  // Maybe our pod is not up to date and there are new categories since our version
-  let categoryLabel = VIDEO_CATEGORIES[this.category]
-  if (!categoryLabel) categoryLabel = 'Misc'
-
-  // Maybe our pod is not up to date and there are new licences since our version
-  let licenceLabel = VIDEO_LICENCES[this.licence]
-  if (!licenceLabel) licenceLabel = 'Unknown'
-
-  // Language is an optional attribute
-  let languageLabel = VIDEO_LANGUAGES[this.language]
-  if (!languageLabel) languageLabel = 'Unknown'
-
-  const json = {
-    id: this.id,
-    name: this.name,
-    category: this.category,
-    categoryLabel,
-    licence: this.licence,
-    licenceLabel,
-    language: this.language,
-    languageLabel,
-    nsfw: this.nsfw,
-    description: this.description,
-    podHost,
-    isLocal: this.isOwned(),
-    magnetUri: this.generateMagnetUri(),
-    author: this.Author.name,
-    duration: this.duration,
-    views: this.views,
-    likes: this.likes,
-    dislikes: this.dislikes,
-    tags: map<VideoTagInstance, string>(this.Tags, 'name'),
-    thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
-    createdAt: this.createdAt,
-    updatedAt: this.updatedAt
-  }
-
-  return json
-}
-
-toAddRemoteJSON = function (callback: VideoMethods.ToAddRemoteJSONCallback) {
-  // Get thumbnail data to send to the other pod
-  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
-  fs.readFile(thumbnailPath, (err, thumbnailData) => {
-    if (err) {
-      logger.error('Cannot read the thumbnail of the video')
-      return callback(err)
-    }
-
-    const remoteVideo = {
-      name: this.name,
-      category: this.category,
-      licence: this.licence,
-      language: this.language,
-      nsfw: this.nsfw,
-      description: this.description,
-      infoHash: this.infoHash,
-      remoteId: this.id,
-      author: this.Author.name,
-      duration: this.duration,
-      thumbnailData: thumbnailData.toString('binary'),
-      tags: map<VideoTagInstance, string>(this.Tags, 'name'),
-      createdAt: this.createdAt,
-      updatedAt: this.updatedAt,
-      extname: this.extname,
-      views: this.views,
-      likes: this.likes,
-      dislikes: this.dislikes
-    }
-
-    return callback(null, remoteVideo)
-  })
-}
-
-toUpdateRemoteJSON = function () {
-  const json = {
-    name: this.name,
-    category: this.category,
-    licence: this.licence,
-    language: this.language,
-    nsfw: this.nsfw,
-    description: this.description,
-    infoHash: this.infoHash,
-    remoteId: this.id,
-    author: this.Author.name,
-    duration: this.duration,
-    tags: map<VideoTagInstance, string>(this.Tags, 'name'),
-    createdAt: this.createdAt,
-    updatedAt: this.updatedAt,
-    extname: this.extname,
-    views: this.views,
-    likes: this.likes,
-    dislikes: this.dislikes
-  }
-
-  return json
-}
-
-transcodeVideofile = function (finalCallback: VideoMethods.TranscodeVideofileCallback) {
-  const video = this
-
-  const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
-  const newExtname = '.mp4'
-  const videoInputPath = join(videosDirectory, video.getVideoFilename())
-  const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
-
-  ffmpeg(videoInputPath)
-    .output(videoOutputPath)
-    .videoCodec('libx264')
-    .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
-    .outputOption('-movflags faststart')
-    .on('error', finalCallback)
-    .on('end', function () {
-      series([
-        function removeOldFile (callback) {
-          fs.unlink(videoInputPath, callback)
-        },
-
-        function moveNewFile (callback) {
-          // Important to do this before getVideoFilename() to take in account the new file extension
-          video.set('extname', newExtname)
-
-          const newVideoPath = join(videosDirectory, video.getVideoFilename())
-          fs.rename(videoOutputPath, newVideoPath, callback)
-        },
-
-        function torrent (callback) {
-          const newVideoPath = join(videosDirectory, video.getVideoFilename())
-          createTorrentFromVideo(video, newVideoPath, callback)
-        },
-
-        function videoExtension (callback) {
-          video.save().asCallback(callback)
-        }
-
-      ], function (err: Error) {
-        if (err) {
-          // Autodesctruction...
-          video.destroy().asCallback(function (err) {
-            if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
-          })
-
-          return finalCallback(err)
-        }
-
-        return finalCallback(null)
-      })
-    })
-    .run()
-}
-
-// ------------------------------ STATICS ------------------------------
-
-generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) {
-  // Creating the thumbnail for a remote video
-
-  const thumbnailName = video.getThumbnailName()
-  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
-  fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) {
-    if (err) return callback(err)
-
-    return callback(null, thumbnailName)
-  })
-}
-
-getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) {
-  ffmpeg.ffprobe(videoPath, function (err, metadata) {
-    if (err) return callback(err)
-
-    return callback(null, Math.floor(metadata.format.duration))
-  })
-}
-
-list = function (callback: VideoMethods.ListCallback) {
-  return Video.findAll().asCallback(callback)
-}
-
-listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) {
-  // Exclude Blakclisted videos from the list
-  const query = {
-    distinct: true,
-    offset: start,
-    limit: count,
-    order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
-    include: [
-      {
-        model: Video['sequelize'].models.Author,
-        include: [ { model: Video['sequelize'].models.Pod, required: false } ]
-      },
-
-      Video['sequelize'].models.Tag
-    ],
-    where: createBaseVideosWhere()
-  }
-
-  return Video.findAndCountAll(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    return callback(null, result.rows, result.count)
-  })
-}
-
-loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) {
-  const query = {
-    where: {
-      remoteId: remoteId
-    },
-    include: [
-      {
-        model: Video['sequelize'].models.Author,
-        include: [
-          {
-            model: Video['sequelize'].models.Pod,
-            required: true,
-            where: {
-              host: fromHost
-            }
-          }
-        ]
-      }
-    ]
-  }
-
-  return Video.findOne(query).asCallback(callback)
-}
-
-listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) {
-  // If remoteId is null this is *our* video
-  const query = {
-    where: {
-      remoteId: null
-    },
-    include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
-  }
-
-  return Video.findAll(query).asCallback(callback)
-}
-
-listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) {
-  const query = {
-    where: {
-      remoteId: null
-    },
-    include: [
-      {
-        model: Video['sequelize'].models.Author,
-        where: {
-          name: author
-        }
-      }
-    ]
-  }
-
-  return Video.findAll(query).asCallback(callback)
-}
-
-load = function (id: string, callback: VideoMethods.LoadCallback) {
-  return Video.findById(id).asCallback(callback)
-}
-
-loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) {
-  const options = {
-    include: [ Video['sequelize'].models.Author ]
-  }
-
-  return Video.findById(id, options).asCallback(callback)
-}
-
-loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) {
-  const options = {
-    include: [
-      {
-        model: Video['sequelize'].models.Author,
-        include: [ { model: Video['sequelize'].models.Pod, required: false } ]
-      },
-      Video['sequelize'].models.Tag
-    ]
-  }
-
-  return Video.findById(id, options).asCallback(callback)
-}
-
-searchAndPopulateAuthorAndPodAndTags = function (
-  value: string,
-  field: string,
-  start: number,
-  count: number,
-  sort: string,
-  callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback
-) {
-  const podInclude: any = {
-    model: Video['sequelize'].models.Pod,
-    required: false
-  }
-
-  const authorInclude: any = {
-    model: Video['sequelize'].models.Author,
-    include: [
-      podInclude
-    ]
-  }
-
-  const tagInclude: any = {
-    model: Video['sequelize'].models.Tag
-  }
-
-  const query: any = {
-    distinct: true,
-    where: createBaseVideosWhere(),
-    offset: start,
-    limit: count,
-    order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
-  }
-
-  // Make an exact search with the magnet
-  if (field === 'magnetUri') {
-    const infoHash = magnetUtil.decode(value).infoHash
-    query.where.infoHash = infoHash
-  } else if (field === 'tags') {
-    const escapedValue = Video['sequelize'].escape('%' + value + '%')
-    query.where.id.$in = Video['sequelize'].literal(
-      '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
-    )
-  } else if (field === 'host') {
-    // FIXME: Include our pod? (not stored in the database)
-    podInclude.where = {
-      host: {
-        $like: '%' + value + '%'
-      }
-    }
-    podInclude.required = true
-  } else if (field === 'author') {
-    authorInclude.where = {
-      name: {
-        $like: '%' + value + '%'
-      }
-    }
-
-    // authorInclude.or = true
-  } else {
-    query.where[field] = {
-      $like: '%' + value + '%'
-    }
-  }
-
-  query.include = [
-    authorInclude, tagInclude
-  ]
-
-  if (tagInclude.where) {
-    // query.include.push([ Video['sequelize'].models.Tag ])
-  }
-
-  return Video.findAndCountAll(query).asCallback(function (err, result) {
-    if (err) return callback(err)
-
-    return callback(null, result.rows, result.count)
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-function createBaseVideosWhere () {
-  return {
-    id: {
-      $notIn: Video['sequelize'].literal(
-        '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
-      )
-    }
-  }
-}
-
-function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) {
-  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
-  fs.unlink(thumbnailPath, callback)
-}
-
-function removeFile (video: VideoInstance, callback: (err: Error) => void) {
-  const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
-  fs.unlink(filePath, callback)
-}
-
-function removeTorrent (video: VideoInstance, callback: (err: Error) => void) {
-  const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
-  fs.unlink(torrenPath, callback)
-}
-
-function removePreview (video: VideoInstance, callback: (err: Error) => void) {
-  // Same name than video thumnail
-  fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
-}
-
-function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
-  const options = {
-    announceList: [
-      [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
-    ],
-    urlList: [
-      CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + video.getVideoFilename()
-    ]
-  }
-
-  createTorrent(videoPath, options, function (err, torrent) {
-    if (err) return callback(err)
-
-    const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
-    fs.writeFile(filePath, torrent, function (err) {
-      if (err) return callback(err)
-
-      const parsedTorrent = parseTorrent(torrent)
-      video.set('infoHash', parsedTorrent.infoHash)
-      video.validate().asCallback(callback)
-    })
-  })
-}
-
-function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
-  generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback)
-}
-
-function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
-  generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback)
-}
-
-type GenerateImageCallback = (err: Error, imageName: string) => void
-function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) {
-  const options: any = {
-    filename: imageName,
-    count: 1,
-    folder
-  }
-
-  if (size) {
-    options.size = size
-  }
-
-  ffmpeg(videoPath)
-    .on('error', callback)
-    .on('end', function () {
-      callback(null, imageName)
-    })
-    .thumbnail(options)
-}
-
-function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) {
-  // Find the blacklisted video
-  db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
-    // If an error occured, stop here
-    if (err) {
-      logger.error('Error when fetching video from blacklist.', { error: err })
-      return callback(err)
-    }
-
-    // If we found the video, remove it from the blacklist
-    if (video) {
-      video.destroy().asCallback(callback)
-    } else {
-      // If haven't found it, simply ignore it and do nothing
-      return callback(null)
-    }
-  })
-}
diff --git a/server/models/video/author-interface.ts b/server/models/video/author-interface.ts
new file mode 100644 (file)
index 0000000..c1b3084
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+import { PodInstance } from '../pod'
+
+export namespace AuthorMethods {
+  export type FindOrCreateAuthorCallback = (err: Error, authorInstance?: AuthorInstance) => void
+  export type FindOrCreateAuthor = (name: string, podId: number, userId: number, transaction: Sequelize.Transaction, callback: FindOrCreateAuthorCallback) => void
+}
+
+export interface AuthorClass {
+  findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
+}
+
+export interface AuthorAttributes {
+  name: string
+}
+
+export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+
+  podId: number
+  Pod: PodInstance
+}
+
+export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {}
diff --git a/server/models/video/author.ts b/server/models/video/author.ts
new file mode 100644 (file)
index 0000000..4a115e3
--- /dev/null
@@ -0,0 +1,103 @@
+import * as Sequelize from 'sequelize'
+
+import { isUserUsernameValid } from '../../helpers'
+
+import { addMethodsToModel } from '../utils'
+import {
+  AuthorClass,
+  AuthorInstance,
+  AuthorAttributes,
+
+  AuthorMethods
+} from './author-interface'
+
+let Author: Sequelize.Model<AuthorInstance, AuthorAttributes>
+let findOrCreateAuthor: AuthorMethods.FindOrCreateAuthor
+
+export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author',
+    {
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          usernameValid: function (value) {
+            const res = isUserUsernameValid(value)
+            if (res === false) throw new Error('Username is not valid.')
+          }
+        }
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'name' ]
+        },
+        {
+          fields: [ 'podId' ]
+        },
+        {
+          fields: [ 'userId' ],
+          unique: true
+        },
+        {
+          fields: [ 'name', 'podId' ],
+          unique: true
+        }
+      ]
+    }
+  )
+
+  const classMethods = [ associate, findOrCreateAuthor ]
+  addMethodsToModel(Author, classMethods)
+
+  return Author
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+  Author.belongsTo(models.Pod, {
+    foreignKey: {
+      name: 'podId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+
+  Author.belongsTo(models.User, {
+    foreignKey: {
+      name: 'userId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+}
+
+findOrCreateAuthor = function (
+  name: string,
+  podId: number,
+  userId: number,
+  transaction: Sequelize.Transaction,
+  callback: AuthorMethods.FindOrCreateAuthorCallback
+) {
+  const author = {
+    name,
+    podId,
+    userId
+  }
+
+  const query: any = {
+    where: author,
+    defaults: author
+  }
+
+  if (transaction !== null) query.transaction = transaction
+
+  Author.findOrCreate(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    // [ instance, wasCreated ]
+    return callback(null, result[0])
+  })
+}
diff --git a/server/models/video/index.ts b/server/models/video/index.ts
new file mode 100644 (file)
index 0000000..84b801c
--- /dev/null
@@ -0,0 +1,6 @@
+export * from './author-interface'
+export * from './tag-interface'
+export * from './video-abuse-interface'
+export * from './video-blacklist-interface'
+export * from './video-tag-interface'
+export * from './video-interface'
diff --git a/server/models/video/tag-interface.ts b/server/models/video/tag-interface.ts
new file mode 100644 (file)
index 0000000..e045e7c
--- /dev/null
@@ -0,0 +1,20 @@
+import * as Sequelize from 'sequelize'
+
+export namespace TagMethods {
+  export type FindOrCreateTagsCallback = (err: Error, tagInstances: TagInstance[]) => void
+  export type FindOrCreateTags = (tags: string[], transaction: Sequelize.Transaction, callback: FindOrCreateTagsCallback) => void
+}
+
+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> {}
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
new file mode 100644 (file)
index 0000000..3c657d7
--- /dev/null
@@ -0,0 +1,81 @@
+import { each } from 'async'
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import {
+  TagClass,
+  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',
+    {
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false
+      }
+    },
+    {
+      timestamps: false,
+      indexes: [
+        {
+          fields: [ 'name' ],
+          unique: true
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    findOrCreateTags
+  ]
+  addMethodsToModel(Tag, classMethods)
+
+  return Tag
+}
+
+// ---------------------------------------------------------------------------
+
+function associate (models) {
+  Tag.belongsToMany(models.Video, {
+    foreignKey: 'tagId',
+    through: models.VideoTag,
+    onDelete: 'cascade'
+  })
+}
+
+findOrCreateTags = function (tags: string[], transaction: Sequelize.Transaction, callback: TagMethods.FindOrCreateTagsCallback) {
+  const tagInstances = []
+
+  each<string, Error>(tags, function (tag, callbackEach) {
+    const query: any = {
+      where: {
+        name: tag
+      },
+      defaults: {
+        name: tag
+      }
+    }
+
+    if (transaction) query.transaction = transaction
+
+    Tag.findOrCreate(query).asCallback(function (err, res) {
+      if (err) return callbackEach(err)
+
+      // res = [ tag, isCreated ]
+      const tag = res[0]
+      tagInstances.push(tag)
+      return callbackEach()
+    })
+  }, function (err) {
+    return callback(err, tagInstances)
+  })
+}
diff --git a/server/models/video/video-abuse-interface.ts b/server/models/video/video-abuse-interface.ts
new file mode 100644 (file)
index 0000000..4b7f2a2
--- /dev/null
@@ -0,0 +1,28 @@
+import * as Sequelize from 'sequelize'
+
+// Don't use barrel, import just what we need
+import { VideoAbuse as FormatedVideoAbuse } from '../../../shared/models/video-abuse.model'
+
+export namespace VideoAbuseMethods {
+  export type toFormatedJSON = () => FormatedVideoAbuse
+
+  export type ListForApiCallback = (err: Error, videoAbuseInstances?: VideoAbuseInstance[], total?: number) => void
+  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
+}
+
+export interface VideoAbuseClass {
+  listForApi: VideoAbuseMethods.ListForApi
+}
+
+export interface VideoAbuseAttributes {
+  reporterUsername: string
+  reason: string
+}
+
+export interface VideoAbuseInstance extends VideoAbuseClass, VideoAbuseAttributes, Sequelize.Instance<VideoAbuseAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface VideoAbuseModel extends VideoAbuseClass, Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes> {}
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
new file mode 100644 (file)
index 0000000..e0e0bcf
--- /dev/null
@@ -0,0 +1,131 @@
+import * as Sequelize from 'sequelize'
+
+import { CONFIG } from '../../initializers'
+import { isVideoAbuseReporterUsernameValid, isVideoAbuseReasonValid } from '../../helpers'
+
+import { addMethodsToModel, getSort } from '../utils'
+import {
+  VideoAbuseClass,
+  VideoAbuseInstance,
+  VideoAbuseAttributes,
+
+  VideoAbuseMethods
+} from './video-abuse-interface'
+
+let VideoAbuse: Sequelize.Model<VideoAbuseInstance, VideoAbuseAttributes>
+let listForApi: VideoAbuseMethods.ListForApi
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  VideoAbuse = sequelize.define<VideoAbuseInstance, VideoAbuseAttributes>('VideoAbuse',
+    {
+      reporterUsername: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          reporterUsernameValid: function (value) {
+            const res = isVideoAbuseReporterUsernameValid(value)
+            if (res === false) throw new Error('Video abuse reporter username is not valid.')
+          }
+        }
+      },
+      reason: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          reasonValid: function (value) {
+            const res = isVideoAbuseReasonValid(value)
+            if (res === false) throw new Error('Video abuse reason is not valid.')
+          }
+        }
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'videoId' ]
+        },
+        {
+          fields: [ 'reporterPodId' ]
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    listForApi
+  ]
+  const instanceMethods = [
+    toFormatedJSON
+  ]
+  addMethodsToModel(VideoAbuse, classMethods, instanceMethods)
+
+  return VideoAbuse
+}
+
+// ------------------------------ METHODS ------------------------------
+
+function toFormatedJSON () {
+  let reporterPodHost
+
+  if (this.Pod) {
+    reporterPodHost = this.Pod.host
+  } else {
+    // It means it's our video
+    reporterPodHost = CONFIG.WEBSERVER.HOST
+  }
+
+  const json = {
+    id: this.id,
+    reporterPodHost,
+    reason: this.reason,
+    reporterUsername: this.reporterUsername,
+    videoId: this.videoId,
+    createdAt: this.createdAt
+  }
+
+  return json
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  VideoAbuse.belongsTo(models.Pod, {
+    foreignKey: {
+      name: 'reporterPodId',
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+
+  VideoAbuse.belongsTo(models.Video, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+}
+
+listForApi = function (start, count, sort, callback) {
+  const query = {
+    offset: start,
+    limit: count,
+    order: [ getSort(sort) ],
+    include: [
+      {
+        model: VideoAbuse['sequelize'].models.Pod,
+        required: false
+      }
+    ]
+  }
+
+  return VideoAbuse.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+
diff --git a/server/models/video/video-blacklist-interface.ts b/server/models/video/video-blacklist-interface.ts
new file mode 100644 (file)
index 0000000..37f5794
--- /dev/null
@@ -0,0 +1,43 @@
+import * as Sequelize from 'sequelize'
+
+// Don't use barrel, import just what we need
+import { BlacklistedVideo as FormatedBlacklistedVideo } from '../../../shared/models/video-blacklist.model'
+
+export namespace BlacklistedVideoMethods {
+  export type ToFormatedJSON = () => FormatedBlacklistedVideo
+
+  export type CountTotalCallback = (err: Error, total: number) => void
+  export type CountTotal = (callback: CountTotalCallback) => void
+
+  export type ListCallback = (err: Error, backlistedVideoInstances: BlacklistedVideoInstance[]) => void
+  export type List = (callback: ListCallback) => void
+
+  export type ListForApiCallback = (err: Error, blacklistedVIdeoInstances?: BlacklistedVideoInstance[], total?: number) => void
+  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
+
+  export type LoadByIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
+  export type LoadById = (id: number, callback: LoadByIdCallback) => void
+
+  export type LoadByVideoIdCallback = (err: Error, blacklistedVideoInstance: BlacklistedVideoInstance) => void
+  export type LoadByVideoId = (id: string, callback: LoadByVideoIdCallback) => void
+}
+
+export interface BlacklistedVideoClass {
+  toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON
+  countTotal: BlacklistedVideoMethods.CountTotal
+  list: BlacklistedVideoMethods.List
+  listForApi: BlacklistedVideoMethods.ListForApi
+  loadById: BlacklistedVideoMethods.LoadById
+  loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
+}
+
+export interface BlacklistedVideoAttributes {
+}
+
+export interface BlacklistedVideoInstance extends BlacklistedVideoClass, BlacklistedVideoAttributes, Sequelize.Instance<BlacklistedVideoAttributes> {
+  id: number
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface BlacklistedVideoModel extends BlacklistedVideoClass, Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes> {}
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
new file mode 100644 (file)
index 0000000..f447998
--- /dev/null
@@ -0,0 +1,103 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel, getSort } from '../utils'
+import {
+  BlacklistedVideoClass,
+  BlacklistedVideoInstance,
+  BlacklistedVideoAttributes,
+
+  BlacklistedVideoMethods
+} from './video-blacklist-interface'
+
+let BlacklistedVideo: Sequelize.Model<BlacklistedVideoInstance, BlacklistedVideoAttributes>
+let toFormatedJSON: BlacklistedVideoMethods.ToFormatedJSON
+let countTotal: BlacklistedVideoMethods.CountTotal
+let list: BlacklistedVideoMethods.List
+let listForApi: BlacklistedVideoMethods.ListForApi
+let loadById: BlacklistedVideoMethods.LoadById
+let loadByVideoId: BlacklistedVideoMethods.LoadByVideoId
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  BlacklistedVideo = sequelize.define<BlacklistedVideoInstance, BlacklistedVideoAttributes>('BlacklistedVideo',
+    {},
+    {
+      indexes: [
+        {
+          fields: [ 'videoId' ],
+          unique: true
+        }
+      ]
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    countTotal,
+    list,
+    listForApi,
+    loadById,
+    loadByVideoId
+  ]
+  const instanceMethods = [
+    toFormatedJSON
+  ]
+  addMethodsToModel(BlacklistedVideo, classMethods, instanceMethods)
+
+  return BlacklistedVideo
+}
+
+// ------------------------------ METHODS ------------------------------
+
+toFormatedJSON = function () {
+  return {
+    id: this.id,
+    videoId: this.videoId,
+    createdAt: this.createdAt
+  }
+}
+
+// ------------------------------ STATICS ------------------------------
+
+function associate (models) {
+  BlacklistedVideo.belongsTo(models.Video, {
+    foreignKey: 'videoId',
+    onDelete: 'cascade'
+  })
+}
+
+countTotal = function (callback: BlacklistedVideoMethods.CountTotalCallback) {
+  return BlacklistedVideo.count().asCallback(callback)
+}
+
+list = function (callback: BlacklistedVideoMethods.ListCallback) {
+  return BlacklistedVideo.findAll().asCallback(callback)
+}
+
+listForApi = function (start: number, count: number, sort: string, callback: BlacklistedVideoMethods.ListForApiCallback) {
+  const query = {
+    offset: start,
+    limit: count,
+    order: [ getSort(sort) ]
+  }
+
+  return BlacklistedVideo.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+loadById = function (id: number, callback: BlacklistedVideoMethods.LoadByIdCallback) {
+  return BlacklistedVideo.findById(id).asCallback(callback)
+}
+
+loadByVideoId = function (id: string, callback: BlacklistedVideoMethods.LoadByIdCallback) {
+  const query = {
+    where: {
+      videoId: id
+    }
+  }
+
+  return BlacklistedVideo.find(query).asCallback(callback)
+}
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
new file mode 100644 (file)
index 0000000..71b9b0a
--- /dev/null
@@ -0,0 +1,151 @@
+import * as Sequelize from 'sequelize'
+
+import { AuthorInstance } from './author-interface'
+import { VideoTagInstance } from './video-tag-interface'
+
+// Don't use barrel, import just what we need
+import { Video as FormatedVideo } from '../../../shared/models/video.model'
+
+export type FormatedAddRemoteVideo = {
+  name: string
+  category: number
+  licence: number
+  language: number
+  nsfw: boolean
+  description: string
+  infoHash: string
+  remoteId: string
+  author: string
+  duration: number
+  thumbnailData: string
+  tags: string[]
+  createdAt: Date
+  updatedAt: Date
+  extname: string
+  views: number
+  likes: number
+  dislikes: number
+}
+
+export type FormatedUpdateRemoteVideo = {
+  name: string
+  category: number
+  licence: number
+  language: number
+  nsfw: boolean
+  description: string
+  infoHash: string
+  remoteId: string
+  author: string
+  duration: number
+  tags: string[]
+  createdAt: Date
+  updatedAt: Date
+  extname: string
+  views: number
+  likes: number
+  dislikes: number
+}
+
+export namespace VideoMethods {
+  export type GenerateMagnetUri = () => string
+  export type GetVideoFilename = () => string
+  export type GetThumbnailName = () => string
+  export type GetPreviewName = () => string
+  export type GetTorrentName = () => string
+  export type IsOwned = () => boolean
+  export type ToFormatedJSON = () => FormatedVideo
+
+  export type ToAddRemoteJSONCallback = (err: Error, videoFormated?: FormatedAddRemoteVideo) => void
+  export type ToAddRemoteJSON = (callback: ToAddRemoteJSONCallback) => void
+
+  export type ToUpdateRemoteJSON = () => FormatedUpdateRemoteVideo
+
+  export type TranscodeVideofileCallback = (err: Error) => void
+  export type TranscodeVideofile = (callback: TranscodeVideofileCallback) => void
+
+  export type GenerateThumbnailFromDataCallback = (err: Error, thumbnailName?: string) => void
+  export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string, callback: GenerateThumbnailFromDataCallback) => void
+
+  export type GetDurationFromFileCallback = (err: Error, duration?: number) => void
+  export type GetDurationFromFile = (videoPath, callback) => void
+
+  export type ListCallback = (err: Error, videoInstances: VideoInstance[]) => void
+  export type List = (callback: ListCallback) => void
+
+  export type ListForApiCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
+  export type ListForApi = (start: number, count: number, sort: string, callback: ListForApiCallback) => void
+
+  export type LoadByHostAndRemoteIdCallback = (err: Error, videoInstance: VideoInstance) => void
+  export type LoadByHostAndRemoteId = (fromHost: string, remoteId: string, callback: LoadByHostAndRemoteIdCallback) => void
+
+  export type ListOwnedAndPopulateAuthorAndTagsCallback = (err: Error, videoInstances: VideoInstance[]) => void
+  export type ListOwnedAndPopulateAuthorAndTags = (callback: ListOwnedAndPopulateAuthorAndTagsCallback) => void
+
+  export type ListOwnedByAuthorCallback = (err: Error, videoInstances: VideoInstance[]) => void
+  export type ListOwnedByAuthor = (author: string, callback: ListOwnedByAuthorCallback) => void
+
+  export type LoadCallback = (err: Error, videoInstance: VideoInstance) => void
+  export type Load = (id: string, callback: LoadCallback) => void
+
+  export type LoadAndPopulateAuthorCallback = (err: Error, videoInstance: VideoInstance) => void
+  export type LoadAndPopulateAuthor = (id: string, callback: LoadAndPopulateAuthorCallback) => void
+
+  export type LoadAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstance: VideoInstance) => void
+  export type LoadAndPopulateAuthorAndPodAndTags = (id: string, callback: LoadAndPopulateAuthorAndPodAndTagsCallback) => void
+
+  export type SearchAndPopulateAuthorAndPodAndTagsCallback = (err: Error, videoInstances?: VideoInstance[], total?: number) => void
+  export type SearchAndPopulateAuthorAndPodAndTags = (value: string, field: string, start: number, count: number, sort: string, callback: SearchAndPopulateAuthorAndPodAndTagsCallback) => void
+}
+
+export interface VideoClass {
+  generateMagnetUri: VideoMethods.GenerateMagnetUri
+  getVideoFilename: VideoMethods.GetVideoFilename
+  getThumbnailName: VideoMethods.GetThumbnailName
+  getPreviewName: VideoMethods.GetPreviewName
+  getTorrentName: VideoMethods.GetTorrentName
+  isOwned: VideoMethods.IsOwned
+  toFormatedJSON: VideoMethods.ToFormatedJSON
+  toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
+  toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
+  transcodeVideofile: VideoMethods.TranscodeVideofile
+
+  generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
+  getDurationFromFile: VideoMethods.GetDurationFromFile
+  list: VideoMethods.List
+  listForApi: VideoMethods.ListForApi
+  loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
+  listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
+  listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
+  load: VideoMethods.Load
+  loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
+  loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
+  searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
+}
+
+export interface VideoAttributes {
+  name: string
+  extname: string
+  remoteId: string
+  category: number
+  licence: number
+  language: number
+  nsfw: boolean
+  description: string
+  infoHash?: string
+  duration: number
+  views?: number
+  likes?: number
+  dislikes?: number
+
+  Author?: AuthorInstance
+  Tags?: VideoTagInstance[]
+}
+
+export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.Instance<VideoAttributes> {
+  id: string
+  createdAt: Date
+  updatedAt: Date
+}
+
+export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {}
diff --git a/server/models/video/video-tag-interface.ts b/server/models/video/video-tag-interface.ts
new file mode 100644 (file)
index 0000000..f928cec
--- /dev/null
@@ -0,0 +1,18 @@
+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> {}
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts
new file mode 100644 (file)
index 0000000..71ca853
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+import { addMethodsToModel } from '../utils'
+import {
+  VideoTagClass,
+  VideoTagInstance,
+  VideoTagAttributes,
+
+  VideoTagMethods
+} from './video-tag-interface'
+
+let VideoTag: Sequelize.Model<VideoTagInstance, VideoTagAttributes>
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  VideoTag = sequelize.define<VideoTagInstance, VideoTagAttributes>('VideoTag', {}, {
+    indexes: [
+      {
+        fields: [ 'videoId' ]
+      },
+      {
+        fields: [ 'tagId' ]
+      }
+    ]
+  })
+
+  return VideoTag
+}
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
new file mode 100644 (file)
index 0000000..866b380
--- /dev/null
@@ -0,0 +1,921 @@
+import * as safeBuffer from 'safe-buffer'
+const Buffer = safeBuffer.Buffer
+import * as createTorrent from 'create-torrent'
+import * as ffmpeg from 'fluent-ffmpeg'
+import * as fs from 'fs'
+import * as magnetUtil from 'magnet-uri'
+import { map, values } from 'lodash'
+import { parallel, series } from 'async'
+import * as parseTorrent from 'parse-torrent'
+import { join } from 'path'
+import * as Sequelize from 'sequelize'
+
+import { database as db } from '../../initializers/database'
+import { VideoTagInstance } from './video-tag-interface'
+import {
+  logger,
+  isVideoNameValid,
+  isVideoCategoryValid,
+  isVideoLicenceValid,
+  isVideoLanguageValid,
+  isVideoNSFWValid,
+  isVideoDescriptionValid,
+  isVideoInfoHashValid,
+  isVideoDurationValid
+} from '../../helpers'
+import {
+  CONSTRAINTS_FIELDS,
+  CONFIG,
+  REMOTE_SCHEME,
+  STATIC_PATHS,
+  VIDEO_CATEGORIES,
+  VIDEO_LICENCES,
+  VIDEO_LANGUAGES,
+  THUMBNAILS_SIZE
+} from '../../initializers'
+import { JobScheduler, removeVideoToFriends } from '../../lib'
+
+import { addMethodsToModel, getSort } from '../utils'
+import {
+  VideoClass,
+  VideoInstance,
+  VideoAttributes,
+
+  VideoMethods
+} from './video-interface'
+
+let Video: Sequelize.Model<VideoInstance, VideoAttributes>
+let generateMagnetUri: VideoMethods.GenerateMagnetUri
+let getVideoFilename: VideoMethods.GetVideoFilename
+let getThumbnailName: VideoMethods.GetThumbnailName
+let getPreviewName: VideoMethods.GetPreviewName
+let getTorrentName: VideoMethods.GetTorrentName
+let isOwned: VideoMethods.IsOwned
+let toFormatedJSON: VideoMethods.ToFormatedJSON
+let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON
+let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON
+let transcodeVideofile: VideoMethods.TranscodeVideofile
+
+let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
+let getDurationFromFile: VideoMethods.GetDurationFromFile
+let list: VideoMethods.List
+let listForApi: VideoMethods.ListForApi
+let loadByHostAndRemoteId: VideoMethods.LoadByHostAndRemoteId
+let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
+let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor
+let load: VideoMethods.Load
+let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor
+let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags
+let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags
+
+export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
+  Video = sequelize.define<VideoInstance, VideoAttributes>('Video',
+    {
+      id: {
+        type: DataTypes.UUID,
+        defaultValue: DataTypes.UUIDV4,
+        primaryKey: true,
+        validate: {
+          isUUID: 4
+        }
+      },
+      name: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          nameValid: function (value) {
+            const res = isVideoNameValid(value)
+            if (res === false) throw new Error('Video name is not valid.')
+          }
+        }
+      },
+      extname: {
+        type: DataTypes.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
+        allowNull: false
+      },
+      remoteId: {
+        type: DataTypes.UUID,
+        allowNull: true,
+        validate: {
+          isUUID: 4
+        }
+      },
+      category: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        validate: {
+          categoryValid: function (value) {
+            const res = isVideoCategoryValid(value)
+            if (res === false) throw new Error('Video category is not valid.')
+          }
+        }
+      },
+      licence: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        defaultValue: null,
+        validate: {
+          licenceValid: function (value) {
+            const res = isVideoLicenceValid(value)
+            if (res === false) throw new Error('Video licence is not valid.')
+          }
+        }
+      },
+      language: {
+        type: DataTypes.INTEGER,
+        allowNull: true,
+        validate: {
+          languageValid: function (value) {
+            const res = isVideoLanguageValid(value)
+            if (res === false) throw new Error('Video language is not valid.')
+          }
+        }
+      },
+      nsfw: {
+        type: DataTypes.BOOLEAN,
+        allowNull: false,
+        validate: {
+          nsfwValid: function (value) {
+            const res = isVideoNSFWValid(value)
+            if (res === false) throw new Error('Video nsfw attribute is not valid.')
+          }
+        }
+      },
+      description: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          descriptionValid: function (value) {
+            const res = isVideoDescriptionValid(value)
+            if (res === false) throw new Error('Video description is not valid.')
+          }
+        }
+      },
+      infoHash: {
+        type: DataTypes.STRING,
+        allowNull: false,
+        validate: {
+          infoHashValid: function (value) {
+            const res = isVideoInfoHashValid(value)
+            if (res === false) throw new Error('Video info hash is not valid.')
+          }
+        }
+      },
+      duration: {
+        type: DataTypes.INTEGER,
+        allowNull: false,
+        validate: {
+          durationValid: function (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
+        }
+      }
+    },
+    {
+      indexes: [
+        {
+          fields: [ 'authorId' ]
+        },
+        {
+          fields: [ 'remoteId' ]
+        },
+        {
+          fields: [ 'name' ]
+        },
+        {
+          fields: [ 'createdAt' ]
+        },
+        {
+          fields: [ 'duration' ]
+        },
+        {
+          fields: [ 'infoHash' ]
+        },
+        {
+          fields: [ 'views' ]
+        },
+        {
+          fields: [ 'likes' ]
+        }
+      ],
+      hooks: {
+        beforeValidate,
+        beforeCreate,
+        afterDestroy
+      }
+    }
+  )
+
+  const classMethods = [
+    associate,
+
+    generateThumbnailFromData,
+    getDurationFromFile,
+    list,
+    listForApi,
+    listOwnedAndPopulateAuthorAndTags,
+    listOwnedByAuthor,
+    load,
+    loadByHostAndRemoteId,
+    loadAndPopulateAuthor,
+    loadAndPopulateAuthorAndPodAndTags,
+    searchAndPopulateAuthorAndPodAndTags
+  ]
+  const instanceMethods = [
+    generateMagnetUri,
+    getVideoFilename,
+    getThumbnailName,
+    getPreviewName,
+    getTorrentName,
+    isOwned,
+    toFormatedJSON,
+    toAddRemoteJSON,
+    toUpdateRemoteJSON,
+    transcodeVideofile,
+    removeFromBlacklist
+  ]
+  addMethodsToModel(Video, classMethods, instanceMethods)
+
+  return Video
+}
+
+function beforeValidate (video: VideoInstance) {
+  // Put a fake infoHash if it does not exists yet
+  if (video.isOwned() && !video.infoHash) {
+    // 40 hexa length
+    video.infoHash = '0123456789abcdef0123456789abcdef01234567'
+  }
+}
+
+function beforeCreate (video: VideoInstance, options: { transaction: Sequelize.Transaction }) {
+  return new Promise(function (resolve, reject) {
+    const tasks = []
+
+    if (video.isOwned()) {
+      const videoPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
+
+      tasks.push(
+        function createVideoTorrent (callback) {
+          createTorrentFromVideo(video, videoPath, callback)
+        },
+
+        function createVideoThumbnail (callback) {
+          createThumbnail(video, videoPath, callback)
+        },
+
+        function createVideoPreview (callback) {
+          createPreview(video, videoPath, callback)
+        }
+      )
+
+      if (CONFIG.TRANSCODING.ENABLED === true) {
+        tasks.push(
+          function createVideoTranscoderJob (callback) {
+            const dataInput = {
+              id: video.id
+            }
+
+            JobScheduler.Instance.createJob(options.transaction, 'videoTranscoder', dataInput, callback)
+          }
+        )
+      }
+
+      return parallel(tasks, function (err) {
+        if (err) return reject(err)
+
+        return resolve()
+      })
+    }
+
+    return resolve()
+  })
+}
+
+function afterDestroy (video: VideoInstance) {
+  return new Promise(function (resolve, reject) {
+    const tasks = []
+
+    tasks.push(
+      function (callback) {
+        removeThumbnail(video, callback)
+      }
+    )
+
+    if (video.isOwned()) {
+      tasks.push(
+        function removeVideoFile (callback) {
+          removeFile(video, callback)
+        },
+
+        function removeVideoTorrent (callback) {
+          removeTorrent(video, callback)
+        },
+
+        function removeVideoPreview (callback) {
+          removePreview(video, callback)
+        },
+
+        function notifyFriends (callback) {
+          const params = {
+            remoteId: video.id
+          }
+
+          removeVideoToFriends(params)
+
+          return callback()
+        }
+      )
+    }
+
+    parallel(tasks, function (err) {
+      if (err) return reject(err)
+
+      return resolve()
+    })
+  })
+}
+
+// ------------------------------ METHODS ------------------------------
+
+function associate (models) {
+  Video.belongsTo(models.Author, {
+    foreignKey: {
+      name: 'authorId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+
+  Video.belongsToMany(models.Tag, {
+    foreignKey: 'videoId',
+    through: models.VideoTag,
+    onDelete: 'cascade'
+  })
+
+  Video.hasMany(models.VideoAbuse, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+}
+
+generateMagnetUri = function () {
+  let baseUrlHttp
+  let baseUrlWs
+
+  if (this.isOwned()) {
+    baseUrlHttp = CONFIG.WEBSERVER.URL
+    baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
+  } else {
+    baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
+    baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
+  }
+
+  const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentName()
+  const announce = [ baseUrlWs + '/tracker/socket' ]
+  const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename() ]
+
+  const magnetHash = {
+    xs,
+    announce,
+    urlList,
+    infoHash: this.infoHash,
+    name: this.name
+  }
+
+  return magnetUtil.encode(magnetHash)
+}
+
+getVideoFilename = function () {
+  if (this.isOwned()) return this.id + this.extname
+
+  return this.remoteId + this.extname
+}
+
+getThumbnailName = function () {
+  // We always have a copy of the thumbnail
+  return this.id + '.jpg'
+}
+
+getPreviewName = function () {
+  const extension = '.jpg'
+
+  if (this.isOwned()) return this.id + extension
+
+  return this.remoteId + extension
+}
+
+getTorrentName = function () {
+  const extension = '.torrent'
+
+  if (this.isOwned()) return this.id + extension
+
+  return this.remoteId + extension
+}
+
+isOwned = function () {
+  return this.remoteId === null
+}
+
+toFormatedJSON = function (this: VideoInstance) {
+  let podHost
+
+  if (this.Author.Pod) {
+    podHost = this.Author.Pod.host
+  } else {
+    // It means it's our video
+    podHost = CONFIG.WEBSERVER.HOST
+  }
+
+  // Maybe our pod is not up to date and there are new categories since our version
+  let categoryLabel = VIDEO_CATEGORIES[this.category]
+  if (!categoryLabel) categoryLabel = 'Misc'
+
+  // Maybe our pod is not up to date and there are new licences since our version
+  let licenceLabel = VIDEO_LICENCES[this.licence]
+  if (!licenceLabel) licenceLabel = 'Unknown'
+
+  // Language is an optional attribute
+  let languageLabel = VIDEO_LANGUAGES[this.language]
+  if (!languageLabel) languageLabel = 'Unknown'
+
+  const json = {
+    id: this.id,
+    name: this.name,
+    category: this.category,
+    categoryLabel,
+    licence: this.licence,
+    licenceLabel,
+    language: this.language,
+    languageLabel,
+    nsfw: this.nsfw,
+    description: this.description,
+    podHost,
+    isLocal: this.isOwned(),
+    magnetUri: this.generateMagnetUri(),
+    author: this.Author.name,
+    duration: this.duration,
+    views: this.views,
+    likes: this.likes,
+    dislikes: this.dislikes,
+    tags: map<VideoTagInstance, string>(this.Tags, 'name'),
+    thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
+    createdAt: this.createdAt,
+    updatedAt: this.updatedAt
+  }
+
+  return json
+}
+
+toAddRemoteJSON = function (callback: VideoMethods.ToAddRemoteJSONCallback) {
+  // Get thumbnail data to send to the other pod
+  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
+  fs.readFile(thumbnailPath, (err, thumbnailData) => {
+    if (err) {
+      logger.error('Cannot read the thumbnail of the video')
+      return callback(err)
+    }
+
+    const remoteVideo = {
+      name: this.name,
+      category: this.category,
+      licence: this.licence,
+      language: this.language,
+      nsfw: this.nsfw,
+      description: this.description,
+      infoHash: this.infoHash,
+      remoteId: this.id,
+      author: this.Author.name,
+      duration: this.duration,
+      thumbnailData: thumbnailData.toString('binary'),
+      tags: map<VideoTagInstance, string>(this.Tags, 'name'),
+      createdAt: this.createdAt,
+      updatedAt: this.updatedAt,
+      extname: this.extname,
+      views: this.views,
+      likes: this.likes,
+      dislikes: this.dislikes
+    }
+
+    return callback(null, remoteVideo)
+  })
+}
+
+toUpdateRemoteJSON = function () {
+  const json = {
+    name: this.name,
+    category: this.category,
+    licence: this.licence,
+    language: this.language,
+    nsfw: this.nsfw,
+    description: this.description,
+    infoHash: this.infoHash,
+    remoteId: this.id,
+    author: this.Author.name,
+    duration: this.duration,
+    tags: map<VideoTagInstance, string>(this.Tags, 'name'),
+    createdAt: this.createdAt,
+    updatedAt: this.updatedAt,
+    extname: this.extname,
+    views: this.views,
+    likes: this.likes,
+    dislikes: this.dislikes
+  }
+
+  return json
+}
+
+transcodeVideofile = function (finalCallback: VideoMethods.TranscodeVideofileCallback) {
+  const video = this
+
+  const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
+  const newExtname = '.mp4'
+  const videoInputPath = join(videosDirectory, video.getVideoFilename())
+  const videoOutputPath = join(videosDirectory, video.id + '-transcoded' + newExtname)
+
+  ffmpeg(videoInputPath)
+    .output(videoOutputPath)
+    .videoCodec('libx264')
+    .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
+    .outputOption('-movflags faststart')
+    .on('error', finalCallback)
+    .on('end', function () {
+      series([
+        function removeOldFile (callback) {
+          fs.unlink(videoInputPath, callback)
+        },
+
+        function moveNewFile (callback) {
+          // Important to do this before getVideoFilename() to take in account the new file extension
+          video.set('extname', newExtname)
+
+          const newVideoPath = join(videosDirectory, video.getVideoFilename())
+          fs.rename(videoOutputPath, newVideoPath, callback)
+        },
+
+        function torrent (callback) {
+          const newVideoPath = join(videosDirectory, video.getVideoFilename())
+          createTorrentFromVideo(video, newVideoPath, callback)
+        },
+
+        function videoExtension (callback) {
+          video.save().asCallback(callback)
+        }
+
+      ], function (err: Error) {
+        if (err) {
+          // Autodesctruction...
+          video.destroy().asCallback(function (err) {
+            if (err) logger.error('Cannot destruct video after transcoding failure.', { error: err })
+          })
+
+          return finalCallback(err)
+        }
+
+        return finalCallback(null)
+      })
+    })
+    .run()
+}
+
+// ------------------------------ STATICS ------------------------------
+
+generateThumbnailFromData = function (video: VideoInstance, thumbnailData: string, callback: VideoMethods.GenerateThumbnailFromDataCallback) {
+  // Creating the thumbnail for a remote video
+
+  const thumbnailName = video.getThumbnailName()
+  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
+  fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) {
+    if (err) return callback(err)
+
+    return callback(null, thumbnailName)
+  })
+}
+
+getDurationFromFile = function (videoPath: string, callback: VideoMethods.GetDurationFromFileCallback) {
+  ffmpeg.ffprobe(videoPath, function (err, metadata) {
+    if (err) return callback(err)
+
+    return callback(null, Math.floor(metadata.format.duration))
+  })
+}
+
+list = function (callback: VideoMethods.ListCallback) {
+  return Video.findAll().asCallback(callback)
+}
+
+listForApi = function (start: number, count: number, sort: string, callback: VideoMethods.ListForApiCallback) {
+  // Exclude Blakclisted videos from the list
+  const query = {
+    distinct: true,
+    offset: start,
+    limit: count,
+    order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ],
+    include: [
+      {
+        model: Video['sequelize'].models.Author,
+        include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+      },
+
+      Video['sequelize'].models.Tag
+    ],
+    where: createBaseVideosWhere()
+  }
+
+  return Video.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+loadByHostAndRemoteId = function (fromHost: string, remoteId: string, callback: VideoMethods.LoadByHostAndRemoteIdCallback) {
+  const query = {
+    where: {
+      remoteId: remoteId
+    },
+    include: [
+      {
+        model: Video['sequelize'].models.Author,
+        include: [
+          {
+            model: Video['sequelize'].models.Pod,
+            required: true,
+            where: {
+              host: fromHost
+            }
+          }
+        ]
+      }
+    ]
+  }
+
+  return Video.findOne(query).asCallback(callback)
+}
+
+listOwnedAndPopulateAuthorAndTags = function (callback: VideoMethods.ListOwnedAndPopulateAuthorAndTagsCallback) {
+  // If remoteId is null this is *our* video
+  const query = {
+    where: {
+      remoteId: null
+    },
+    include: [ Video['sequelize'].models.Author, Video['sequelize'].models.Tag ]
+  }
+
+  return Video.findAll(query).asCallback(callback)
+}
+
+listOwnedByAuthor = function (author: string, callback: VideoMethods.ListOwnedByAuthorCallback) {
+  const query = {
+    where: {
+      remoteId: null
+    },
+    include: [
+      {
+        model: Video['sequelize'].models.Author,
+        where: {
+          name: author
+        }
+      }
+    ]
+  }
+
+  return Video.findAll(query).asCallback(callback)
+}
+
+load = function (id: string, callback: VideoMethods.LoadCallback) {
+  return Video.findById(id).asCallback(callback)
+}
+
+loadAndPopulateAuthor = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorCallback) {
+  const options = {
+    include: [ Video['sequelize'].models.Author ]
+  }
+
+  return Video.findById(id, options).asCallback(callback)
+}
+
+loadAndPopulateAuthorAndPodAndTags = function (id: string, callback: VideoMethods.LoadAndPopulateAuthorAndPodAndTagsCallback) {
+  const options = {
+    include: [
+      {
+        model: Video['sequelize'].models.Author,
+        include: [ { model: Video['sequelize'].models.Pod, required: false } ]
+      },
+      Video['sequelize'].models.Tag
+    ]
+  }
+
+  return Video.findById(id, options).asCallback(callback)
+}
+
+searchAndPopulateAuthorAndPodAndTags = function (
+  value: string,
+  field: string,
+  start: number,
+  count: number,
+  sort: string,
+  callback: VideoMethods.SearchAndPopulateAuthorAndPodAndTagsCallback
+) {
+  const podInclude: any = {
+    model: Video['sequelize'].models.Pod,
+    required: false
+  }
+
+  const authorInclude: any = {
+    model: Video['sequelize'].models.Author,
+    include: [
+      podInclude
+    ]
+  }
+
+  const tagInclude: any = {
+    model: Video['sequelize'].models.Tag
+  }
+
+  const query: any = {
+    distinct: true,
+    where: createBaseVideosWhere(),
+    offset: start,
+    limit: count,
+    order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
+  }
+
+  // Make an exact search with the magnet
+  if (field === 'magnetUri') {
+    const infoHash = magnetUtil.decode(value).infoHash
+    query.where.infoHash = infoHash
+  } else if (field === 'tags') {
+    const escapedValue = Video['sequelize'].escape('%' + value + '%')
+    query.where.id.$in = Video['sequelize'].literal(
+      '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
+    )
+  } else if (field === 'host') {
+    // FIXME: Include our pod? (not stored in the database)
+    podInclude.where = {
+      host: {
+        $like: '%' + value + '%'
+      }
+    }
+    podInclude.required = true
+  } else if (field === 'author') {
+    authorInclude.where = {
+      name: {
+        $like: '%' + value + '%'
+      }
+    }
+
+    // authorInclude.or = true
+  } else {
+    query.where[field] = {
+      $like: '%' + value + '%'
+    }
+  }
+
+  query.include = [
+    authorInclude, tagInclude
+  ]
+
+  if (tagInclude.where) {
+    // query.include.push([ Video['sequelize'].models.Tag ])
+  }
+
+  return Video.findAndCountAll(query).asCallback(function (err, result) {
+    if (err) return callback(err)
+
+    return callback(null, result.rows, result.count)
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+function createBaseVideosWhere () {
+  return {
+    id: {
+      $notIn: Video['sequelize'].literal(
+        '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")'
+      )
+    }
+  }
+}
+
+function removeThumbnail (video: VideoInstance, callback: (err: Error) => void) {
+  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName())
+  fs.unlink(thumbnailPath, callback)
+}
+
+function removeFile (video: VideoInstance, callback: (err: Error) => void) {
+  const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
+  fs.unlink(filePath, callback)
+}
+
+function removeTorrent (video: VideoInstance, callback: (err: Error) => void) {
+  const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
+  fs.unlink(torrenPath, callback)
+}
+
+function removePreview (video: VideoInstance, callback: (err: Error) => void) {
+  // Same name than video thumnail
+  fs.unlink(CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
+}
+
+function createTorrentFromVideo (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
+  const options = {
+    announceList: [
+      [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
+    ],
+    urlList: [
+      CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + video.getVideoFilename()
+    ]
+  }
+
+  createTorrent(videoPath, options, function (err, torrent) {
+    if (err) return callback(err)
+
+    const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, video.getTorrentName())
+    fs.writeFile(filePath, torrent, function (err) {
+      if (err) return callback(err)
+
+      const parsedTorrent = parseTorrent(torrent)
+      video.set('infoHash', parsedTorrent.infoHash)
+      video.validate().asCallback(callback)
+    })
+  })
+}
+
+function createPreview (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
+  generateImage(video, videoPath, CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), null, callback)
+}
+
+function createThumbnail (video: VideoInstance, videoPath: string, callback: (err: Error) => void) {
+  generateImage(video, videoPath, CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), THUMBNAILS_SIZE, callback)
+}
+
+type GenerateImageCallback = (err: Error, imageName: string) => void
+function generateImage (video: VideoInstance, videoPath: string, folder: string, imageName: string, size: string, callback?: GenerateImageCallback) {
+  const options: any = {
+    filename: imageName,
+    count: 1,
+    folder
+  }
+
+  if (size) {
+    options.size = size
+  }
+
+  ffmpeg(videoPath)
+    .on('error', callback)
+    .on('end', function () {
+      callback(null, imageName)
+    })
+    .thumbnail(options)
+}
+
+function removeFromBlacklist (video: VideoInstance, callback: (err: Error) => void) {
+  // Find the blacklisted video
+  db.BlacklistedVideo.loadByVideoId(video.id, function (err, video) {
+    // If an error occured, stop here
+    if (err) {
+      logger.error('Error when fetching video from blacklist.', { error: err })
+      return callback(err)
+    }
+
+    // If we found the video, remove it from the blacklist
+    if (video) {
+      video.destroy().asCallback(callback)
+    } else {
+      // If haven't found it, simply ignore it and do nothing
+      return callback(null)
+    }
+  })
+}