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
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
})
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)
+ })
+ })
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
--- /dev/null
+export * from './application-interface'
+++ /dev/null
-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> {}
+++ /dev/null
-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])
- })
-}
-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'
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
--- /dev/null
+export * from './job-interface'
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
--- /dev/null
+export * from './oauth-client-interface'
+export * from './oauth-token-interface'
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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.')
- }
- })
-}
--- /dev/null
+export * from './pod-interface'
--- /dev/null
+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> {}
--- /dev/null
+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.')
+ }
+ })
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
+++ /dev/null
-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> {}
+++ /dev/null
-/*
- 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
-}
+++ /dev/null
-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> {}
+++ /dev/null
-/*
- 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
-}
+++ /dev/null
-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
-}
--- /dev/null
+export * from './request-interface'
+export * from './request-to-pod-interface'
+export * from './request-video-event-interface'
+export * from './request-video-qadu-interface'
--- /dev/null
+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> {}
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
--- /dev/null
+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> {}
--- /dev/null
+/*
+ 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
+}
--- /dev/null
+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> {}
--- /dev/null
+/*
+ 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
+}
--- /dev/null
+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
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
- })
-}
+++ /dev/null
-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> {}
+++ /dev/null
-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> {}
+++ /dev/null
-/*
- 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)
-}
+++ /dev/null
-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)
-}
--- /dev/null
+export * from './user-video-rate-interface'
+export * from './user-interface'
--- /dev/null
+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> {}
--- /dev/null
+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> {}
--- /dev/null
+/*
+ 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)
+}
--- /dev/null
+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)
+}
+++ /dev/null
-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> {}
+++ /dev/null
-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)
- })
-}
-
-
+++ /dev/null
-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> {}
+++ /dev/null
-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)
-}
+++ /dev/null
-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> {}
+++ /dev/null
-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> {}
+++ /dev/null
-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
-}
+++ /dev/null
-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)
- }
- })
-}
--- /dev/null
+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> {}
--- /dev/null
+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])
+ })
+}
--- /dev/null
+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'
--- /dev/null
+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> {}
--- /dev/null
+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)
+ })
+}
--- /dev/null
+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> {}
--- /dev/null
+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)
+ })
+}
+
+
--- /dev/null
+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> {}
--- /dev/null
+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)
+}
--- /dev/null
+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> {}
--- /dev/null
+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> {}
--- /dev/null
+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
+}
--- /dev/null
+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)
+ }
+ })
+}