expliciting type checks and predicates (server only)
authorRigel Kent <sendmemail@rigelk.eu>
Wed, 25 Jul 2018 20:01:25 +0000 (22:01 +0200)
committerRigel Kent <sendmemail@rigelk.eu>
Wed, 25 Jul 2018 20:01:25 +0000 (22:01 +0200)
34 files changed:
server/helpers/activitypub.ts
server/helpers/core-utils.ts
server/helpers/custom-validators/misc.ts
server/helpers/custom-validators/videos.ts
server/helpers/express-utils.ts
server/helpers/logger.ts
server/helpers/utils.ts
server/initializers/installer.ts
server/initializers/migrations/0005-email-pod.ts
server/initializers/migrations/0010-email-user.ts
server/initializers/migrations/0030-video-category.ts
server/initializers/migrations/0035-video-licence.ts
server/initializers/migrations/0040-video-nsfw.ts
server/initializers/migrations/0055-video-uuid.ts
server/initializers/migrations/0070-user-video-quota.ts
server/initializers/migrations/0155-video-comments-enabled.ts
server/initializers/migrations/0230-kue-to-bull.ts
server/initializers/migrator.ts
server/lib/activitypub/audience.ts
server/lib/activitypub/videos.ts
server/lib/emailer.ts
server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
server/lib/job-queue/handlers/video-file.ts
server/lib/job-queue/job-queue.ts
server/lib/redis.ts
server/lib/user.ts
server/lib/video-comment.ts
server/middlewares/sort.ts
server/models/activitypub/actor-follow.ts
server/models/migrations/index.ts [new file with mode: 0644]
server/models/oauth/oauth-token.ts
server/models/video/video-comment.ts
server/tests/utils/users/login.ts
server/tools/get-access-token.ts

index c49142a0462f522798812719c6d2d0abff3987ee..d710f5c9766ffb71f660470a3755937e385d7c70 100644 (file)
@@ -67,8 +67,8 @@ async function activityPubCollectionPagination (url: string, handler: ActivityPu
   const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
   const result = await handler(start, count)
 
-  let next: string
-  let prev: string
+  let next: string | undefined
+  let prev: string | undefined
 
   // Assert page is a number
   page = parseInt(page, 10)
index c560222d3d39fd3a39ce57c477e78c71adc1cdca..2951aef1eebc86abc90e032d63c3f63f6d51501b 100644 (file)
@@ -42,7 +42,7 @@ function root () {
   const paths = [ __dirname, '..', '..' ]
 
   // We are under /dist directory
-  if (process.mainModule.filename.endsWith('.ts') === false) {
+  if (process.mainModule && process.mainModule.filename.endsWith('.ts') === false) {
     paths.push('..')
   }
 
@@ -143,6 +143,7 @@ const renamePromise = promisify2WithVoid<string, string>(rename)
 const writeFilePromise = promisify2WithVoid<string, any>(writeFile)
 const readdirPromise = promisify1<string, string[]>(readdir)
 const mkdirpPromise = promisify1<string, string>(mkdirp)
+// we cannot modify the Promise types, so we should make the promisify instance check mkdirp
 const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
 const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
 const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
index 151fc852bae5d24c6ab99db14482a728859c9576..6d10a65a881ee75b9f656ebe76aed2d363f1db2e 100644 (file)
@@ -51,7 +51,7 @@ function isFileValid (
   files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[],
   mimeTypeRegex: string,
   field: string,
-  maxSize: number,
+  maxSize: number | null,
   optional = false
 ) {
   // Should have files
@@ -69,7 +69,7 @@ function isFileValid (
   if (!file || !file.originalname) return false
 
   // Check size
-  if (maxSize && file.size > maxSize) return false
+  if ((maxSize !== null) && file.size > maxSize) return false
 
   return new RegExp(`^${mimeTypeRegex}$`, 'i').test(file.mimetype)
 }
index b5cb126d9d3367984623937fb2befe1eef9b5c76..70904af0c74509b8fb4c0b8a3244f7d5ccc3b201 100644 (file)
@@ -150,7 +150,7 @@ function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: Use
 }
 
 async function isVideoExist (id: string, res: Response) {
-  let video: VideoModel
+  let video: VideoModel | null
 
   if (validator.isInt(id)) {
     video = await VideoModel.loadAndPopulateAccountAndServerAndTags(+id)
@@ -158,7 +158,7 @@ async function isVideoExist (id: string, res: Response) {
     video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(id)
   }
 
-  if (!video) {
+  if (video && video !== null) {
     res.status(404)
        .json({ error: 'Video not found' })
        .end()
@@ -173,7 +173,7 @@ async function isVideoExist (id: string, res: Response) {
 async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
-    if (!videoChannel) {
+    if (videoChannel && videoChannel !== null) {
       res.status(400)
          .json({ error: 'Unknown video video channel on this instance.' })
          .end()
@@ -186,7 +186,7 @@ async function isVideoChannelOfAccountExist (channelId: number, user: UserModel,
   }
 
   const videoChannel = await VideoChannelModel.loadByIdAndAccount(channelId, user.Account.id)
-  if (!videoChannel) {
+  if (videoChannel && videoChannel !== null) {
     res.status(400)
        .json({ error: 'Unknown video video channel for this account.' })
        .end()
index 76440348f32ccc38bcdd8adb625e34695530d1c7..f136a4329b67da2033ecb7dc2c3cebd1a85cbaf2 100644 (file)
@@ -64,7 +64,7 @@ function createReqFiles (
     }
   })
 
-  const fields = []
+  let fields: { name: string, maxCount: number }[] = []
   for (const fieldName of fieldNames) {
     fields.push({
       name: fieldName,
index 04ddf01a661f7e93d526212c18ffc7d7f7df57e0..7fdfe212520729bf480604a9f9f537d58058dd9c 100644 (file)
@@ -80,7 +80,8 @@ const logger = winston.createLogger({
 function bunyanLogFactory (level: string) {
   return function () {
     let meta = null
-    let args = [].concat(arguments)
+    let args: any[] = []
+    args.concat(arguments)
 
     if (arguments[ 0 ] instanceof Error) {
       meta = arguments[ 0 ].toString()
index 9efc89d92ec1f7df574eeb4e61e80d5703f71146..7ff1556e3c1620f5aec997ff7c138ea90668c622 100644 (file)
@@ -52,7 +52,7 @@ async function isSignupAllowed () {
 function isSignupAllowedForCurrentIP (ip: string) {
   const addr = ipaddr.parse(ip)
   let excludeList = [ 'blacklist' ]
-  let matched: string
+  let matched = ''
 
   // if there is a valid, non-empty whitelist, we exclude all unknown adresses too
   if (CONFIG.SIGNUP.FILTERS.CIDR.WHITELIST.filter(cidr => isCidr(cidr)).length > 0) {
@@ -144,6 +144,7 @@ let serverActor: ActorModel
 async function getServerActor () {
   if (serverActor === undefined) {
     const application = await ApplicationModel.load()
+    if (!application) throw Error('Could not application.')
     serverActor = application.Account.Actor
   }
 
index 1f513a9c37354f312537ca625dd7c4e03cebf4e6..1926c40dd6dfeaba9ae48054032c73ed3af44a4e 100644 (file)
@@ -52,7 +52,7 @@ function createDirectoriesIfNotExist () {
   const cacheDirectories = Object.keys(CACHE)
                                  .map(k => CACHE[k].DIRECTORY)
 
-  const tasks = []
+  const tasks: Promise<string>[] = []
   for (const key of Object.keys(storage)) {
     const dir = storage[key]
     tasks.push(mkdirpPromise(dir))
index ab60f3adb4c9af5fa31eca8abc7b6534d031539f..c34a122559898c25f8da2b3cd5f1b6322f2d822a 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -12,7 +13,7 @@ function up (utils: {
     type: Sequelize.STRING(400),
     allowNull: false,
     defaultValue: ''
-  }
+  } as Migration.String
 
   return q.addColumn('Pods', 'email', data)
     .then(() => {
index 33d13ce5518c6ff9d2ac658132788ce426965210..37a7b0bb34750880a0645d3298ff36839883e5e9 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -12,7 +13,7 @@ function up (utils: {
     type: Sequelize.STRING(400),
     allowNull: false,
     defaultValue: ''
-  }
+  } as Migration.String
   return q.addColumn('Users', 'email', data)
     .then(() => {
       const query = 'UPDATE "Users" SET "email" = CONCAT("username", \'@example.com\')'
index 41bc1aa9821e37048015e0b61b6e57aecfe9605e..f784f820da8e09362da2e87f699e8687483f4264 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -12,7 +13,7 @@ function up (utils: {
     type: Sequelize.INTEGER,
     allowNull: false,
     defaultValue: 0
-  }
+  } as Migration.Integer
 
   return q.addColumn('Videos', 'category', data)
     .then(() => {
index 7ab49e1476663e7d6e5619203ab46f2f5c9254e9..3d0b0bac98318fc3c3911484491237d4259e09ce 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -12,7 +13,7 @@ function up (utils: {
     type: Sequelize.INTEGER,
     allowNull: false,
     defaultValue: 0
-  }
+  } as Migration.Integer
 
   return q.addColumn('Videos', 'licence', data)
     .then(() => {
index 0460e661da4a7bd0201c7d5873ddd6e195bad8f8..f7f70d3c4b4bea26b4d5fd8a54d84f3dd2c77acd 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -12,7 +13,7 @@ function up (utils: {
     type: Sequelize.BOOLEAN,
     allowNull: false,
     defaultValue: false
-  }
+  } as Migration.Boolean
 
   return q.addColumn('Videos', 'nsfw', data)
     .then(() => {
index 9bc65917c8374d299c60f354b951387ee06a08b1..6db25f1938142fc3b2cb460148ee6bec10cad65e 100644 (file)
@@ -24,7 +24,7 @@ function up (utils: {
       return utils.sequelize.query(query)
     })
     .then(() => {
-      dataUUID.defaultValue = null
+      dataUUID.defaultValue = null // FIXME:default value cannot be null if string
 
       return q.changeColumn('Videos', 'uuid', dataUUID)
     })
index dec4d46ddfa8fef12f53143cc543fc6f816f310b..37683432f8f5ef673e7c1b1b32ba165e3da88c6f 100644 (file)
@@ -1,5 +1,6 @@
 import * as Sequelize from 'sequelize'
 import * as Promise from 'bluebird'
+import { Migration } from '../../models/migrations'
 
 function up (utils: {
   transaction: Sequelize.Transaction,
@@ -13,7 +14,7 @@ function up (utils: {
     type: Sequelize.BIGINT,
     allowNull: false,
     defaultValue: -1
-  }
+  } as Migration.BigInteger
 
   return q.addColumn('Users', 'videoQuota', data)
     .then(() => {
index 59f4110de12ff3a4d4d3afdbf502961488bcbcb8..6949d3a0c2974880ccc38e93fa080155e1bc2647 100644 (file)
@@ -1,4 +1,5 @@
 import * as Sequelize from 'sequelize'
+import { Migration } from '../../models/migrations'
 
 async function up (utils: {
   transaction: Sequelize.Transaction,
@@ -9,7 +10,7 @@ async function up (utils: {
     type: Sequelize.BOOLEAN,
     allowNull: false,
     defaultValue: true
-  }
+  } as Migration.Boolean
   await utils.queryInterface.addColumn('video', 'commentsEnabled', data)
 
   data.defaultValue = null
index 5fad87a610e29f363625ff82946f3c386f7f4a5f..5f4d88bef7d64fbd1cc65e69368512b5be0614cb 100644 (file)
@@ -2,6 +2,7 @@ import * as Sequelize from 'sequelize'
 import { createClient } from 'redis'
 import { CONFIG } from '../constants'
 import { JobQueue } from '../../lib/job-queue'
+import { Redis } from '../../lib/redis'
 import { initDatabaseModels } from '../database'
 
 async function up (utils: {
@@ -12,11 +13,7 @@ async function up (utils: {
   await initDatabaseModels(false)
 
   return new Promise((res, rej) => {
-    const client = createClient({
-      host: CONFIG.REDIS.HOSTNAME,
-      port: CONFIG.REDIS.PORT,
-      db: CONFIG.REDIS.DB
-    })
+    const client = createClient(Redis.getRedisClient())
 
     const jobsPrefix = 'q-' + CONFIG.WEBSERVER.HOST
 
@@ -36,7 +33,7 @@ async function up (utils: {
                 return res({ type: job.type, payload: parsedData })
               } catch (err) {
                 console.error('Cannot parse data %s.', job.data)
-                return res(null)
+                return res(undefined)
               }
             })
           })
index 4663697299ff830f2825e7e5096f53f42d051deb..539e2bc8fbaeb17a6ba52af3334db7296086fb6e 100644 (file)
@@ -11,7 +11,7 @@ async function migrate () {
   // The installer will do that
   if (tables.length === 0) return
 
-  let actualVersion: number = null
+  let actualVersion: number | null = null
 
   const [ rows ] = await sequelizeTypescript.query('SELECT "migrationVersion" FROM "application"')
   if (rows && rows[0] && rows[0].migrationVersion) {
index 7164135b6ff77c2bfdab16113e90b184fb978b75..7b4067c11c62eeee8318f8667274e5fbb8211729 100644 (file)
@@ -20,7 +20,7 @@ function getVideoCommentAudience (
   isOrigin = false
 ) {
   const to = [ ACTIVITY_PUB.PUBLIC ]
-  const cc = []
+  const cc: string[] = []
 
   // Owner of the video we comment
   if (isOrigin === false) {
@@ -60,8 +60,8 @@ function getAudience (actorSender: ActorModel, isPublic = true) {
 }
 
 function buildAudience (followerUrls: string[], isPublic = true) {
-  let to = []
-  let cc = []
+  let to: string[] = []
+  let cc: string[] = []
 
   if (isPublic) {
     to = [ ACTIVITY_PUB.PUBLIC ]
index fdc082b6155150bdefdc545e2f633516abf424ed..2944cb7295de8512892734561caa6ca0c66cd040 100644 (file)
@@ -88,17 +88,17 @@ async function videoActivityObjectToDBAttributes (
   const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
   const duration = videoObject.duration.replace(/[^\d]+/, '')
 
-  let language: string = null
+  let language: string | undefined
   if (videoObject.language) {
     language = videoObject.language.identifier
   }
 
-  let category: number = null
+  let category: number | undefined
   if (videoObject.category) {
     category = parseInt(videoObject.category.identifier, 10)
   }
 
-  let licence: number = null
+  let licence: number | undefined
   if (videoObject.licence) {
     licence = parseInt(videoObject.licence.identifier, 10)
   }
@@ -143,7 +143,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObje
     throw new Error('Cannot find video files for ' + videoCreated.url)
   }
 
-  const attributes = []
+  const attributes: VideoFileModel[] = []
   for (const fileUrl of fileUrls) {
     // Fetch associated magnet uri
     const magnet = videoObject.url.find(u => {
@@ -153,7 +153,11 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObje
     if (!magnet) throw new Error('Cannot find associated magnet uri for file ' + fileUrl.href)
 
     const parsed = magnetUtil.decode(magnet.href)
-    if (!parsed || isVideoFileInfoHashValid(parsed.infoHash) === false) throw new Error('Cannot parse magnet URI ' + magnet.href)
+    if (!parsed ||
+        (parsed.infoHash &&
+          (isVideoFileInfoHashValid(parsed.infoHash) === false))) {
+      throw new Error('Cannot parse magnet URI ' + magnet.href)
+    }
 
     const attribute = {
       extname: VIDEO_MIMETYPE_EXT[ fileUrl.mimeType ],
@@ -161,7 +165,7 @@ function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObje
       resolution: fileUrl.width,
       size: fileUrl.size,
       videoId: videoCreated.id
-    }
+    } as VideoFileModel
     attributes.push(attribute)
   }
 
index 88a06cb795e25c460d3039ff5408d0bf1e0fc5be..ded321bf70c4acd186c0225779a10810d7af5d56 100644 (file)
@@ -91,9 +91,10 @@ class Emailer {
 
   async addVideoAbuseReport (videoId: number) {
     const video = await VideoModel.load(videoId)
+    if (!video) throw new Error('Unknown Video id during Abuse report.')
 
     const text = `Hi,\n\n` +
-      `Your instance received an abuse for video the following video ${video.url}\n\n` +
+      `Your instance received an abuse for the following video ${video.url}\n\n` +
       `Cheers,\n` +
       `PeerTube.`
 
index c087371c6ce19794faca79e1d09732e6d6228bcd..36092665e6ee2fddc48bcdf2b4ea81740e320fec 100644 (file)
@@ -15,7 +15,7 @@ async function computeBody (payload: { body: any, signatureActorId?: number }) {
 }
 
 async function buildSignedRequestOptions (payload: { signatureActorId?: number }) {
-  let actor: ActorModel
+  let actor: ActorModel | null
   if (payload.signatureActorId) {
     actor = await ActorModel.load(payload.signatureActorId)
     if (!actor) throw new Error('Unknown signature actor id.')
index bd68dd78b8d6f0685b2815663aed84465bc0d5e1..6b1a8f1328a4cc04c541f1452e3e0c59f4c66e98 100644 (file)
@@ -28,7 +28,7 @@ async function processVideoFileImport (job: Bull.Job) {
   const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(payload.videoUUID)
   // No video, maybe deleted?
   if (!video) {
-    logger.info('Do not process job %d, video does not exist.', job.id, { videoUUID: video.uuid })
+    logger.info('Do not process job %d, video does not exist.', job.id)
     return undefined
   }
 
@@ -45,13 +45,13 @@ async function processVideoFile (job: Bull.Job) {
   const video = await VideoModel.loadByUUIDAndPopulateAccountAndServerAndTags(payload.videoUUID)
   // No video, maybe deleted?
   if (!video) {
-    logger.info('Do not process job %d, video does not exist.', job.id, { videoUUID: video.uuid })
+    logger.info('Do not process job %d, video does not exist.', job.id)
     return undefined
   }
 
   // Transcoding in other resolution
   if (payload.resolution) {
-    await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode)
+    await video.transcodeOriginalVideofile(payload.resolution, payload.isPortraitMode || false)
 
     await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, video)
   } else {
index 1b46180e8eea3cea5972e663b97b5e22897bb014..b018d0e8a6291a6278a947fc53b659071d899977 100644 (file)
@@ -87,7 +87,7 @@ class JobQueue {
     const queue = this.queues[obj.type]
     if (queue === undefined) {
       logger.error('Unknown queue %s: cannot create job.', obj.type)
-      return
+      throw Error('Unknown queue, cannot create job')
     }
 
     const jobArgs: Bull.JobOptions = {
index 06a34006012f5e0e6ae96a2e56ff23e16d1af5f9..e547537c33ca602755df4bdaef3e1886f3564fc7 100644 (file)
@@ -6,8 +6,8 @@ import { CONFIG, USER_PASSWORD_RESET_LIFETIME, VIDEO_VIEW_LIFETIME } from '../in
 
 type CachedRoute = {
   body: string,
-  contentType?: string
-  statusCode?: string
+  contentType: string
+  statusCode: string
 }
 
 class Redis {
@@ -75,11 +75,12 @@ class Redis {
   }
 
   setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) {
-    const cached: CachedRoute = {
-      body: body.toString(),
-      contentType,
-      statusCode: statusCode.toString()
-    }
+    const cached: CachedRoute = Object.assign({}, {
+      body: body.toString()
+    },
+    (contentType) ? { contentType } : null,
+    (statusCode) ? { statusCode: statusCode.toString() } : null
+    )
 
     return this.setObject(this.buildCachedRouteKey(req), cached, lifetime)
   }
index 51050de9b2a927a7fcb3f238d7f5f3f95680b71a..ac5f55260ea5b68e290b463cbcd072f13578ff30 100644 (file)
@@ -6,6 +6,7 @@ import { UserModel } from '../models/account/user'
 import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
 import { createVideoChannel } from './video-channel'
 import { VideoChannelModel } from '../models/video/video-channel'
+import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
 
 async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) {
   const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -34,9 +35,9 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse
 
 async function createLocalAccountWithoutKeys (
   name: string,
-  userId: number,
-  applicationId: number,
-  t: Sequelize.Transaction,
+  userId: number | null,
+  applicationId: number | null,
+  t: Sequelize.Transaction | undefined,
   type: ActivityPubActorType= 'Person'
 ) {
   const url = getAccountActivityPubUrl(name)
@@ -49,7 +50,7 @@ async function createLocalAccountWithoutKeys (
     userId,
     applicationId,
     actorId: actorInstanceCreated.id
-  })
+  } as FilteredModelAttributes<AccountModel>)
 
   const accountInstanceCreated = await accountInstance.save({ transaction: t })
   accountInstanceCreated.Actor = actorInstanceCreated
index f88e5cfdf9474f8b035614104a716afac2fcd0f5..70ba7c3039cd947c3636d65d64418e80f810d111 100644 (file)
@@ -9,14 +9,14 @@ import { sendCreateVideoComment } from './activitypub/send'
 
 async function createVideoComment (obj: {
   text: string,
-  inReplyToComment: VideoCommentModel,
+  inReplyToComment: VideoCommentModel | null,
   video: VideoModel
   account: AccountModel
 }, t: Sequelize.Transaction) {
-  let originCommentId: number = null
-  let inReplyToCommentId: number = null
+  let originCommentId: number | null = null
+  let inReplyToCommentId: number | null = null
 
-  if (obj.inReplyToComment) {
+  if (obj.inReplyToComment && obj.inReplyToComment !== null) {
     originCommentId = obj.inReplyToComment.originCommentId || obj.inReplyToComment.id
     inReplyToCommentId = obj.inReplyToComment.id
   }
index 8a62c8be65a8d5e5c78c16a72ecaab69097cdfd3..5120804b3a34b27dc88753ff86cffd5906062ff9 100644 (file)
@@ -15,7 +15,7 @@ function setDefaultSearchSort (req: express.Request, res: express.Response, next
 }
 
 function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
-  let newSort: SortType = { sortModel: undefined, sortValue: undefined }
+  let newSort: SortType = { sortModel: undefined, sortValue: '' }
 
   if (!req.query.sort) req.query.sort = '-createdAt'
 
index b8ce6de1d7fb8ddd67afa1c80c987909491d8601..adec5e92b894bd89b07450dd5adefcd087b76edb 100644 (file)
@@ -111,7 +111,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
   }
 
-  static updateActorFollowsScore (goodInboxes: string[], badInboxes: string[], t: Sequelize.Transaction) {
+  static updateActorFollowsScore (goodInboxes: string[], badInboxes: string[], t: Sequelize.Transaction | undefined) {
     if (goodInboxes.length === 0 && badInboxes.length === 0) return
 
     logger.info('Updating %d good actor follows and %d bad actor follows scores.', goodInboxes.length, badInboxes.length)
@@ -344,7 +344,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     }
   }
 
-  private static incrementScores (inboxUrls: string[], value: number, t: Sequelize.Transaction) {
+  private static incrementScores (inboxUrls: string[], value: number, t: Sequelize.Transaction | undefined) {
     const inboxUrlsString = inboxUrls.map(url => `'${url}'`).join(',')
 
     const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` +
@@ -354,10 +354,10 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
         'WHERE "actor"."inboxUrl" IN (' + inboxUrlsString + ') OR "actor"."sharedInboxUrl" IN (' + inboxUrlsString + ')' +
       ')'
 
-    const options = {
+    const options = t ? {
       type: Sequelize.QueryTypes.BULKUPDATE,
       transaction: t
-    }
+    } : undefined
 
     return ActorFollowModel.sequelize.query(query, options)
   }
diff --git a/server/models/migrations/index.ts b/server/models/migrations/index.ts
new file mode 100644 (file)
index 0000000..c2b31b0
--- /dev/null
@@ -0,0 +1,23 @@
+import * as Sequelize from 'sequelize'
+
+declare namespace Migration {
+  interface Boolean extends Sequelize.DefineAttributeColumnOptions {
+    defaultValue: boolean | null
+  }
+
+  interface String extends Sequelize.DefineAttributeColumnOptions {
+    defaultValue: string | null
+  }
+
+  interface Integer extends Sequelize.DefineAttributeColumnOptions {
+    defaultValue: number | null
+  }
+
+  interface BigInteger extends Sequelize.DefineAttributeColumnOptions {
+    defaultValue: Sequelize.DataTypeBigInt | number | null
+  }
+}
+
+export {
+  Migration
+}
index 759aa27797aa16efb555a1d7b7fa85c1299dbf55..026c301359d9b47e1b477bac2c97013aeb623627 100644 (file)
@@ -154,9 +154,12 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
     return OAuthTokenModel.scope(ScopeNames.WITH_ACCOUNT)
       .findOne(query)
       .then(token => {
-        token['user'] = token.User
-
-        return token
+        if (token) {
+          token['user'] = token.User
+          return token
+        } else {
+          return new OAuthTokenModel()
+        }
       })
   }
 
index e79aff20939467746736f4dcbb8556a6958ab13b..03122dc0312ccabf894aa9c2981daf60e50afc7d 100644 (file)
@@ -156,7 +156,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
     as: 'InReplyToVideoComment',
     onDelete: 'CASCADE'
   })
-  InReplyToVideoComment: VideoCommentModel
+  InReplyToVideoComment: VideoCommentModel | null
 
   @ForeignKey(() => VideoModel)
   @Column
@@ -417,7 +417,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
   toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject {
     let inReplyTo: string
     // New thread, so in AS we reply to the video
-    if (this.inReplyToCommentId === null) {
+    if ((this.inReplyToCommentId !== null) || (this.InReplyToVideoComment !== null)) {
       inReplyTo = this.Video.url
     } else {
       inReplyTo = this.InReplyToVideoComment.url
index 338ae1c00b91fee8c837512e3eb1f6b193694a72..ddeb9df2adedc8f925c2ab141de4c13869d8b551 100644 (file)
@@ -55,5 +55,8 @@ export {
   login,
   serverLogin,
   userLogin,
-  setAccessTokensToServers
+  setAccessTokensToServers,
+  Server,
+  Client,
+  User
 }
index 66fa7081449b5d606ea416db69110601a01c8701..d86c84c8dcc671a4a83580c4a6ea2c53775ed411 100644 (file)
@@ -2,7 +2,10 @@ import * as program from 'commander'
 
 import {
   getClient,
-  serverLogin
+  serverLogin,
+  Server,
+  Client,
+  User
 } from '../tests/utils/index'
 
 program
@@ -19,22 +22,19 @@ if (
   throw new Error('All arguments are required.')
 }
 
-const server = {
-  url: program['url'],
-  user: {
-    username: program['username'],
-    password: program['password']
-  },
-  client: {
-    id: null,
-    secret: null
-  }
-}
-
 getClient(program.url)
   .then(res => {
-    server.client.id = res.body.client_id
-    server.client.secret = res.body.client_secret
+    const server = {
+      url: program['url'],
+      user: {
+        username: program['username'],
+        password: program['password']
+      } as User,
+      client: {
+        id: res.body.client_id as string,
+        secret: res.body.client_secret as string
+      } as Client
+    } as Server
 
     return serverLogin(server)
   })