Browse Source

Stronger model typings

Chocobozzz 4 years ago
parent
commit
453e83ea5d
100 changed files with 919 additions and 733 deletions
  1. 19 18
      server/controllers/activitypub/client.ts
  2. 3 2
      server/controllers/activitypub/inbox.ts
  3. 2 8
      server/controllers/activitypub/outbox.ts
  4. 3 2
      server/controllers/api/search.ts
  5. 2 1
      server/controllers/api/users/index.ts
  6. 1 1
      server/controllers/api/users/me.ts
  7. 0 1
      server/controllers/api/users/my-history.ts
  8. 1 1
      server/controllers/api/video-channel.ts
  9. 18 18
      server/controllers/api/video-playlist.ts
  10. 4 3
      server/controllers/api/videos/abuse.ts
  11. 9 8
      server/controllers/api/videos/blacklist.ts
  12. 5 4
      server/controllers/api/videos/captions.ts
  13. 7 10
      server/controllers/api/videos/comment.ts
  14. 18 16
      server/controllers/api/videos/import.ts
  15. 8 7
      server/controllers/api/videos/index.ts
  16. 3 2
      server/controllers/api/videos/ownership.ts
  17. 1 1
      server/controllers/api/videos/rate.ts
  18. 1 1
      server/controllers/api/videos/watching.ts
  19. 1 1
      server/controllers/feeds.ts
  20. 1 1
      server/controllers/services.ts
  21. 3 3
      server/controllers/static.ts
  22. 1 1
      server/controllers/webfinger.ts
  23. 2 1
      server/helpers/activitypub.ts
  24. 6 3
      server/helpers/actor.ts
  25. 3 3
      server/helpers/captions-utils.ts
  26. 5 17
      server/helpers/custom-validators/video-ownership.ts
  27. 5 2
      server/helpers/middlewares/accounts.ts
  28. 13 31
      server/helpers/middlewares/video-abuses.ts
  29. 2 2
      server/helpers/middlewares/video-captions.ts
  30. 32 13
      server/helpers/middlewares/video-channels.ts
  31. 24 11
      server/helpers/middlewares/video-playlists.ts
  32. 21 5
      server/helpers/middlewares/videos.ts
  33. 5 4
      server/helpers/peertube-crypto.ts
  34. 27 2
      server/helpers/video.ts
  35. 2 1
      server/helpers/webfinger.ts
  36. 52 23
      server/lib/activitypub/actor.ts
  37. 14 13
      server/lib/activitypub/audience.ts
  38. 7 7
      server/lib/activitypub/cache-file.ts
  39. 9 10
      server/lib/activitypub/playlist.ts
  40. 2 3
      server/lib/activitypub/process/process-accept.ts
  41. 3 4
      server/lib/activitypub/process/process-announce.ts
  42. 6 8
      server/lib/activitypub/process/process-create.ts
  43. 14 12
      server/lib/activitypub/process/process-delete.ts
  44. 2 2
      server/lib/activitypub/process/process-dislike.ts
  45. 3 3
      server/lib/activitypub/process/process-flag.ts
  46. 13 8
      server/lib/activitypub/process/process-follow.ts
  47. 2 2
      server/lib/activitypub/process/process-like.ts
  48. 2 2
      server/lib/activitypub/process/process-reject.ts
  49. 6 6
      server/lib/activitypub/process/process-undo.ts
  50. 6 6
      server/lib/activitypub/process/process-update.ts
  51. 3 3
      server/lib/activitypub/process/process-view.ts
  52. 5 6
      server/lib/activitypub/process/process.ts
  53. 3 4
      server/lib/activitypub/send/send-accept.ts
  54. 7 8
      server/lib/activitypub/send/send-announce.ts
  55. 21 13
      server/lib/activitypub/send/send-create.ts
  56. 6 6
      server/lib/activitypub/send/send-delete.ts
  57. 3 4
      server/lib/activitypub/send/send-dislike.ts
  58. 4 5
      server/lib/activitypub/send/send-flag.ts
  59. 3 3
      server/lib/activitypub/send/send-follow.ts
  60. 3 4
      server/lib/activitypub/send/send-like.ts
  61. 3 4
      server/lib/activitypub/send/send-reject.ts
  62. 20 14
      server/lib/activitypub/send/send-undo.ts
  63. 19 12
      server/lib/activitypub/send/send-update.ts
  64. 3 3
      server/lib/activitypub/send/send-view.ts
  65. 17 18
      server/lib/activitypub/send/utils.ts
  66. 12 9
      server/lib/activitypub/share.ts
  67. 33 27
      server/lib/activitypub/url.ts
  68. 8 8
      server/lib/activitypub/video-comments.ts
  69. 13 11
      server/lib/activitypub/video-rates.ts
  70. 85 58
      server/lib/activitypub/videos.ts
  71. 5 3
      server/lib/avatar.ts
  72. 3 2
      server/lib/blocklist.ts
  73. 4 3
      server/lib/client-html.ts
  74. 17 19
      server/lib/emailer.ts
  75. 3 3
      server/lib/hls.ts
  76. 5 4
      server/lib/job-queue/handlers/activitypub-follow.ts
  77. 3 2
      server/lib/job-queue/handlers/activitypub-http-fetcher.ts
  78. 3 1
      server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
  79. 6 5
      server/lib/job-queue/handlers/video-file-import.ts
  80. 21 17
      server/lib/job-queue/handlers/video-import.ts
  81. 4 3
      server/lib/job-queue/handlers/video-transcoding.ts
  82. 54 47
      server/lib/notifier.ts
  83. 2 1
      server/lib/oauth-model.ts
  84. 2 2
      server/lib/peertube-socket.ts
  85. 2 1
      server/lib/redundancy.ts
  86. 35 17
      server/lib/schedulers/videos-redundancy-scheduler.ts
  87. 13 13
      server/lib/thumbnail.ts
  88. 14 12
      server/lib/user.ts
  89. 6 7
      server/lib/video-blacklist.ts
  90. 12 5
      server/lib/video-channel.ts
  91. 5 6
      server/lib/video-comment.ts
  92. 4 3
      server/lib/video-playlist.ts
  93. 8 8
      server/lib/video-transcoding.ts
  94. 2 1
      server/middlewares/validators/follows.ts
  95. 2 2
      server/middlewares/validators/redundancy.ts
  96. 3 2
      server/middlewares/validators/users.ts
  97. 2 2
      server/middlewares/validators/videos/video-abuses.ts
  98. 3 3
      server/middlewares/validators/videos/video-blacklist.ts
  99. 3 3
      server/middlewares/validators/videos/video-captions.ts
  100. 3 2
      server/middlewares/validators/videos/video-channels.ts

+ 19 - 18
server/controllers/activitypub/client.ts

@@ -16,7 +16,6 @@ import {
 } from '../../middlewares'
 import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators'
 import { AccountModel } from '../../models/account/account'
-import { ActorModel } from '../../models/activitypub/actor'
 import { ActorFollowModel } from '../../models/activitypub/actor-follow'
 import { VideoModel } from '../../models/video/video'
 import { VideoCommentModel } from '../../models/video/video-comment'
@@ -38,6 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
 import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
 import { VideoPlaylistModel } from '../../models/video/video-playlist'
 import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
+import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models'
 
 const activityPubClientRouter = express.Router()
 
@@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT
 
 activityPubClientRouter.get('/video-playlists/:playlistId',
   executeIfActivityPub,
-  asyncMiddleware(videoPlaylistsGetValidator),
+  asyncMiddleware(videoPlaylistsGetValidator('all')),
   asyncMiddleware(videoPlaylistController)
 )
 activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
@@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) {
 
 async function videoController (req: express.Request, res: express.Response) {
   // We need more attributes
-  const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id })
+  const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption
 
   if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)
 
   // We need captions to render AP object
-  video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id)
+  const captions = await VideoCaptionModel.listVideoCaptions(video.id)
+  const videoWithCaptions: MVideoAPWithoutCaption = Object.assign(video, { VideoCaptions: captions })
 
-  const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC)
-  const videoObject = audiencify(video.toActivityPubObject(), audience)
+  const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC)
+  const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience)
 
   if (req.path.endsWith('/activity')) {
-    const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience)
+    const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
     return activityPubResponse(activityPubContextify(data), res)
   }
 
@@ -231,13 +232,13 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
 
   if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url)
 
-  const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined)
+  const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined)
 
   return activityPubResponse(activityPubContextify(activity), res)
 }
 
 async function videoAnnouncesController (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
 
   const handler = async (start: number, count: number) => {
     const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
@@ -252,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
 }
 
 async function videoLikesController (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
   const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
 
   return activityPubResponse(activityPubContextify(json), res)
 }
 
 async function videoDislikesController (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
   const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
 
   return activityPubResponse(activityPubContextify(json), res)
 }
 
 async function videoCommentsController (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
 
   const handler = async (start: number, count: number) => {
     const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
@@ -301,7 +302,7 @@ async function videoChannelFollowingController (req: express.Request, res: expre
 }
 
 async function videoCommentController (req: express.Request, res: express.Response) {
-  const videoComment = res.locals.videoComment
+  const videoComment = res.locals.videoCommentFull
 
   if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url)
 
@@ -337,7 +338,7 @@ async function videoRedundancyController (req: express.Request, res: express.Res
 }
 
 async function videoPlaylistController (req: express.Request, res: express.Response) {
-  const playlist = res.locals.videoPlaylist
+  const playlist = res.locals.videoPlaylistFull
 
   // We need more attributes
   playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId)
@@ -358,7 +359,7 @@ async function videoPlaylistElementController (req: express.Request, res: expres
 
 // ---------------------------------------------------------------------------
 
-async function actorFollowing (req: express.Request, actor: ActorModel) {
+async function actorFollowing (req: express.Request, actor: MActorId) {
   const handler = (start: number, count: number) => {
     return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count)
   }
@@ -366,7 +367,7 @@ async function actorFollowing (req: express.Request, actor: ActorModel) {
   return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
 }
 
-async function actorFollowers (req: express.Request, actor: ActorModel) {
+async function actorFollowers (req: express.Request, actor: MActorId) {
   const handler = (start: number, count: number) => {
     return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count)
   }
@@ -374,7 +375,7 @@ async function actorFollowers (req: express.Request, actor: ActorModel) {
   return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
 }
 
-async function actorPlaylists (req: express.Request, account: AccountModel) {
+async function actorPlaylists (req: express.Request, account: MAccountId) {
   const handler = (start: number, count: number) => {
     return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count)
   }
@@ -382,7 +383,7 @@ async function actorPlaylists (req: express.Request, account: AccountModel) {
   return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
 }
 
-function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) {
+function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) {
   const handler = async (start: number, count: number) => {
     const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
     return {

+ 3 - 2
server/controllers/activitypub/inbox.ts

@@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann
 import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
 import { queue } from 'async'
 import { ActorModel } from '../../models/activitypub/actor'
-import { SignatureActorModel } from '../../typings/models'
+import { MActorDefault, MActorSignature } from '../../typings/models'
 
 const inboxRouter = express.Router()
 
@@ -41,7 +41,8 @@ export {
 
 // ---------------------------------------------------------------------------
 
-const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => {
+type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault }
+const inboxQueue = queue<QueueParam, Error>((task, cb) => {
   const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor }
 
   processActivities(task.activities, options)

+ 2 - 8
server/controllers/activitypub/outbox.ts

@@ -6,11 +6,9 @@ import { logger } from '../../helpers/logger'
 import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
 import { buildAudience } from '../../lib/activitypub/audience'
 import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares'
-import { AccountModel } from '../../models/account/account'
-import { ActorModel } from '../../models/activitypub/actor'
 import { VideoModel } from '../../models/video/video'
 import { activityPubResponse } from './utils'
-import { VideoChannelModel } from '../../models/video/video-channel'
+import { MActorLight } from '@server/typings/models'
 
 const outboxRouter = express.Router()
 
@@ -45,14 +43,10 @@ async function outboxController (req: express.Request, res: express.Response) {
   return activityPubResponse(activityPubContextify(json), res)
 }
 
-async function buildActivities (actor: ActorModel, start: number, count: number) {
+async function buildActivities (actor: MActorLight, start: number, count: number) {
   const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
   const activities: Activity[] = []
 
-  // Avoid too many SQL requests
-  const actors = data.data.map(v => v.VideoChannel.Account.Actor)
-  actors.push(actor)
-
   for (const video of data.data) {
     const byActor = video.VideoChannel.Account.Actor
     const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC)

+ 3 - 2
server/controllers/api/search.ts

@@ -19,6 +19,7 @@ import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel
 import { logger } from '../../helpers/logger'
 import { VideoChannelModel } from '../../models/video/video-channel'
 import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
+import { MChannelAccountDefault, MVideoAccountAllFiles } from '../../typings/models'
 
 const searchRouter = express.Router()
 
@@ -84,7 +85,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr
 }
 
 async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) {
-  let videoChannel: VideoChannelModel
+  let videoChannel: MChannelAccountDefault
   let uri = search
 
   if (isWebfingerSearch) {
@@ -137,7 +138,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response)
 }
 
 async function searchVideoURI (url: string, res: express.Response) {
-  let video: VideoModel
+  let video: MVideoAccountAllFiles
 
   // Check if we can fetch a remote video with the URL
   if (isUserAbleToSearchRemoteURI(res)) {

+ 2 - 1
server/controllers/api/users/index.ts

@@ -48,6 +48,7 @@ import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
 import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
 import { UserRegister } from '../../../../shared/models/users/user-register.model'
+import { MUser, MUserAccountDefault } from '@server/typings/models'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -359,7 +360,7 @@ function success (req: express.Request, res: express.Response) {
   res.end()
 }
 
-async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) {
+async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
   const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
 
   user.blocked = block

+ 1 - 1
server/controllers/api/users/me.ts

@@ -147,7 +147,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
 }
 
 async function getUserVideoRating (req: express.Request, res: express.Response) {
-  const videoId = res.locals.video.id
+  const videoId = res.locals.videoId.id
   const accountId = +res.locals.oauth.token.User.Account.id
 
   const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)

+ 0 - 1
server/controllers/api/users/my-history.ts

@@ -7,7 +7,6 @@ import {
   setDefaultPagination,
   userHistoryRemoveValidator
 } from '../../../middlewares'
-import { UserModel } from '../../../models/account/user'
 import { getFormattedObjects } from '../../../helpers/utils'
 import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
 import { sequelizeTypescript } from '../../../initializers'

+ 1 - 1
server/controllers/api/video-channel.ts

@@ -136,7 +136,7 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp
 async function addVideoChannel (req: express.Request, res: express.Response) {
   const videoChannelInfo: VideoChannelCreate = req.body
 
-  const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
+  const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
     const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
 
     return createVideoChannel(videoChannelInfo, account, t)

+ 18 - 18
server/controllers/api/video-playlist.ts

@@ -40,7 +40,7 @@ import { JobQueue } from '../../lib/job-queue'
 import { CONFIG } from '../../initializers/config'
 import { sequelizeTypescript } from '../../initializers/database'
 import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
-import { VideoModel } from '../../models/video/video'
+import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models'
 
 const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
 
@@ -58,7 +58,7 @@ videoPlaylistRouter.get('/',
 )
 
 videoPlaylistRouter.get('/:playlistId',
-  asyncMiddleware(videoPlaylistsGetValidator),
+  asyncMiddleware(videoPlaylistsGetValidator('summary')),
   getVideoPlaylist
 )
 
@@ -83,7 +83,7 @@ videoPlaylistRouter.delete('/:playlistId',
 )
 
 videoPlaylistRouter.get('/:playlistId/videos',
-  asyncMiddleware(videoPlaylistsGetValidator),
+  asyncMiddleware(videoPlaylistsGetValidator('summary')),
   paginationValidator,
   setDefaultPagination,
   optionalAuthenticate,
@@ -140,7 +140,7 @@ async function listVideoPlaylists (req: express.Request, res: express.Response)
 }
 
 function getVideoPlaylist (req: express.Request, res: express.Response) {
-  const videoPlaylist = res.locals.videoPlaylist
+  const videoPlaylist = res.locals.videoPlaylistSummary
 
   if (videoPlaylist.isOutdated()) {
     JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
@@ -159,7 +159,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
     description: videoPlaylistInfo.description,
     privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE,
     ownerAccountId: user.Account.id
-  })
+  }) as MVideoPlaylistFull
 
   videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
 
@@ -175,8 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
     ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
     : undefined
 
-  const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
-    const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
+  const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => {
+    const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull
 
     if (thumbnailModel) {
       thumbnailModel.automaticallyGenerated = false
@@ -201,7 +201,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
 }
 
 async function updateVideoPlaylist (req: express.Request, res: express.Response) {
-  const videoPlaylistInstance = res.locals.videoPlaylist
+  const videoPlaylistInstance = res.locals.videoPlaylistFull
   const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
   const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
 
@@ -275,7 +275,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
 }
 
 async function removeVideoPlaylist (req: express.Request, res: express.Response) {
-  const videoPlaylistInstance = res.locals.videoPlaylist
+  const videoPlaylistInstance = res.locals.videoPlaylistSummary
 
   await sequelizeTypescript.transaction(async t => {
     await videoPlaylistInstance.destroy({ transaction: t })
@@ -290,10 +290,10 @@ async function removeVideoPlaylist (req: express.Request, res: express.Response)
 
 async function addVideoInPlaylist (req: express.Request, res: express.Response) {
   const body: VideoPlaylistElementCreate = req.body
-  const videoPlaylist = res.locals.videoPlaylist
-  const video = res.locals.video
+  const videoPlaylist = res.locals.videoPlaylistFull
+  const video = res.locals.onlyVideo
 
-  const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
+  const playlistElement = await sequelizeTypescript.transaction(async t => {
     const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
 
     const playlistElement = await VideoPlaylistElementModel.create({
@@ -330,7 +330,7 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
 
 async function updateVideoPlaylistElement (req: express.Request, res: express.Response) {
   const body: VideoPlaylistElementUpdate = req.body
-  const videoPlaylist = res.locals.videoPlaylist
+  const videoPlaylist = res.locals.videoPlaylistFull
   const videoPlaylistElement = res.locals.videoPlaylistElement
 
   const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
@@ -354,7 +354,7 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re
 
 async function removeVideoFromPlaylist (req: express.Request, res: express.Response) {
   const videoPlaylistElement = res.locals.videoPlaylistElement
-  const videoPlaylist = res.locals.videoPlaylist
+  const videoPlaylist = res.locals.videoPlaylistFull
   const positionToDelete = videoPlaylistElement.position
 
   await sequelizeTypescript.transaction(async t => {
@@ -381,7 +381,7 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo
 }
 
 async function reorderVideosPlaylist (req: express.Request, res: express.Response) {
-  const videoPlaylist = res.locals.videoPlaylist
+  const videoPlaylist = res.locals.videoPlaylistFull
   const body: VideoPlaylistReorder = req.body
 
   const start: number = body.startPosition
@@ -434,7 +434,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
 }
 
 async function getVideoPlaylistVideos (req: express.Request, res: express.Response) {
-  const videoPlaylistInstance = res.locals.videoPlaylist
+  const videoPlaylistInstance = res.locals.videoPlaylistSummary
   const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
   const server = await getServerActor()
 
@@ -453,7 +453,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon
   return res.json(getFormattedObjects(resultList.data, resultList.total, options))
 }
 
-async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) {
+async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) {
   await videoPlaylist.Thumbnail.destroy()
   videoPlaylist.Thumbnail = null
 
@@ -461,7 +461,7 @@ async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) {
   if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video)
 }
 
-async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) {
   logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
 
   const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)

+ 4 - 3
server/controllers/api/videos/abuse.ts

@@ -21,6 +21,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
 import { Notifier } from '../../../lib/notifier'
 import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
+import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
 
 const auditLogger = auditLoggerFactory('abuse')
 const abuseVideoRouter = express.Router()
@@ -94,10 +95,10 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) {
 }
 
 async function reportVideoAbuse (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   const body: VideoAbuseCreate = req.body
 
-  const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => {
+  const videoAbuse = await sequelizeTypescript.transaction(async t => {
     const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
 
     const abuseToCreate = {
@@ -107,7 +108,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
       state: VideoAbuseState.PENDING
     }
 
-    const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
+    const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
     videoAbuseInstance.Video = videoInstance
     videoAbuseInstance.Account = reporterAccount
 

+ 9 - 8
server/controllers/api/videos/blacklist.ts

@@ -1,5 +1,5 @@
 import * as express from 'express'
-import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
+import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
 import { logger } from '../../../helpers/logger'
 import { getFormattedObjects } from '../../../helpers/utils'
 import {
@@ -11,15 +11,16 @@ import {
   setBlacklistSort,
   setDefaultPagination,
   videosBlacklistAddValidator,
+  videosBlacklistFiltersValidator,
   videosBlacklistRemoveValidator,
-  videosBlacklistUpdateValidator,
-  videosBlacklistFiltersValidator
+  videosBlacklistUpdateValidator
 } from '../../../middlewares'
 import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
 import { sequelizeTypescript } from '../../../initializers'
 import { Notifier } from '../../../lib/notifier'
 import { sendDeleteVideo } from '../../../lib/activitypub/send'
 import { federateVideoIfNeeded } from '../../../lib/activitypub'
+import { MVideoBlacklistVideo } from '@server/typings/models'
 
 const blacklistRouter = express.Router()
 
@@ -64,7 +65,7 @@ export {
 // ---------------------------------------------------------------------------
 
 async function addVideoToBlacklist (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   const body: VideoBlacklistCreate = req.body
 
   const toCreate = {
@@ -74,7 +75,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
     type: VideoBlacklistType.MANUAL
   }
 
-  const blacklist = await VideoBlacklistModel.create(toCreate)
+  const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate)
   blacklist.Video = videoInstance
 
   if (body.unfederate === true) {
@@ -83,7 +84,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
 
   Notifier.Instance.notifyOnVideoBlacklist(blacklist)
 
-  logger.info('Video %s blacklisted.', res.locals.video.uuid)
+  logger.info('Video %s blacklisted.', videoInstance.uuid)
 
   return res.type('json').status(204).end()
 }
@@ -108,7 +109,7 @@ async function listBlacklist (req: express.Request, res: express.Response) {
 
 async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) {
   const videoBlacklist = res.locals.videoBlacklist
-  const video = res.locals.video
+  const video = res.locals.videoAll
 
   const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
     const unfederated = videoBlacklist.unfederated
@@ -135,7 +136,7 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex
     Notifier.Instance.notifyOnNewVideoIfNeeded(video)
   }
 
-  logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
+  logger.info('Video %s removed from blacklist.', video.uuid)
 
   return res.type('json').status(204).end()
 }

+ 5 - 4
server/controllers/api/videos/captions.ts

@@ -10,6 +10,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub'
 import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
 import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
+import { MVideoCaptionVideo } from '@server/typings/models'
 
 const reqVideoCaptionAdd = createReqFiles(
   [ 'captionfile' ],
@@ -46,19 +47,19 @@ export {
 // ---------------------------------------------------------------------------
 
 async function listVideoCaptions (req: express.Request, res: express.Response) {
-  const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id)
+  const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id)
 
   return res.json(getFormattedObjects(data, data.length))
 }
 
 async function addVideoCaption (req: express.Request, res: express.Response) {
   const videoCaptionPhysicalFile = req.files['captionfile'][0]
-  const video = res.locals.video
+  const video = res.locals.videoAll
 
   const videoCaption = new VideoCaptionModel({
     videoId: video.id,
     language: req.params.captionLanguage
-  })
+  }) as MVideoCaptionVideo
   videoCaption.Video = video
 
   // Move physical file
@@ -75,7 +76,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
 }
 
 async function deleteVideoCaption (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.videoAll
   const videoCaption = res.locals.videoCaption
 
   await sequelizeTypescript.transaction(async t => {

+ 7 - 10
server/controllers/api/videos/comment.ts

@@ -27,9 +27,6 @@ import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../.
 import { AccountModel } from '../../../models/account/account'
 import { Notifier } from '../../../lib/notifier'
 import { Hooks } from '../../../lib/plugins/hooks'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoChannelModel } from '../../../models/video/video-channel'
-import { VideoModel } from '../../../models/video/video'
 import { sendDeleteVideoComment } from '../../../lib/activitypub/send'
 
 const auditLogger = auditLoggerFactory('comments')
@@ -75,7 +72,7 @@ export {
 // ---------------------------------------------------------------------------
 
 async function listVideoThreads (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
   const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
 
   let resultList: ResultList<VideoCommentModel>
@@ -86,7 +83,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
       start: req.query.start,
       count: req.query.count,
       sort: req.query.sort,
-      user: user
+      user
     }, 'filter:api.video-threads.list.params')
 
     resultList = await Hooks.wrapPromiseFun(
@@ -105,7 +102,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
 }
 
 async function listVideoThreadComments (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.onlyVideo
   const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
 
   let resultList: ResultList<VideoCommentModel>
@@ -141,7 +138,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
     return createVideoComment({
       text: videoCommentInfo.text,
       inReplyToComment: null,
-      video: res.locals.video,
+      video: res.locals.videoAll,
       account
     }, t)
   })
@@ -164,8 +161,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
 
     return createVideoComment({
       text: videoCommentInfo.text,
-      inReplyToComment: res.locals.videoComment,
-      video: res.locals.video,
+      inReplyToComment: res.locals.videoCommentFull,
+      video: res.locals.videoAll,
       account
     }, t)
   })
@@ -179,7 +176,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
 }
 
 async function removeVideoComment (req: express.Request, res: express.Response) {
-  const videoCommentInstance = res.locals.videoComment
+  const videoCommentInstance = res.locals.videoCommentFull
 
   await sequelizeTypescript.transaction(async t => {
     await videoCommentInstance.destroy({ transaction: t })

+ 18 - 16
server/controllers/api/videos/import.ts

@@ -15,7 +15,6 @@ import { VideoImportModel } from '../../../models/video/video-import'
 import { JobQueue } from '../../../lib/job-queue/job-queue'
 import { join } from 'path'
 import { isArray } from '../../../helpers/custom-validators/misc'
-import { VideoChannelModel } from '../../../models/video/video-channel'
 import * as Bluebird from 'bluebird'
 import * as parseTorrent from 'parse-torrent'
 import { getSecureTorrentName } from '../../../helpers/utils'
@@ -25,8 +24,14 @@ import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
 import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
-import { ThumbnailModel } from '../../../models/video/thumbnail'
-import { UserModel } from '../../../models/account/user'
+import {
+  MChannelActorAccountDefault,
+  MThumbnail,
+  MUser,
+  MVideoThumbnailAccountDefault,
+  MVideoWithBlacklistLight
+} from '@server/typings/models'
+import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
 
 const auditLogger = auditLoggerFactory('video-imports')
 const videoImportsRouter = express.Router()
@@ -225,28 +230,28 @@ async function processPreview (req: express.Request, video: VideoModel) {
 }
 
 function insertIntoDB (parameters: {
-  video: VideoModel,
-  thumbnailModel: ThumbnailModel,
-  previewModel: ThumbnailModel,
-  videoChannel: VideoChannelModel,
+  video: MVideoThumbnailAccountDefault,
+  thumbnailModel: MThumbnail,
+  previewModel: MThumbnail,
+  videoChannel: MChannelActorAccountDefault,
   tags: string[],
-  videoImportAttributes: Partial<VideoImportModel>,
-  user: UserModel
-}): Bluebird<VideoImportModel> {
+  videoImportAttributes: Partial<MVideoImport>,
+  user: MUser
+}): Bluebird<MVideoImportVideo> {
   const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
 
   return sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
     // Save video object in database
-    const videoCreated = await video.save(sequelizeOptions)
+    const videoCreated = await video.save(sequelizeOptions) as (MVideoThumbnailAccountDefault & MVideoWithBlacklistLight)
     videoCreated.VideoChannel = videoChannel
 
     if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
     if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
 
     await autoBlacklistVideoIfNeeded({
-      video,
+      video: videoCreated,
       user,
       notify: false,
       isRemote: false,
@@ -259,16 +264,13 @@ function insertIntoDB (parameters: {
       const tagInstances = await TagModel.findOrCreateTags(tags, t)
 
       await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
-      videoCreated.Tags = tagInstances
-    } else {
-      videoCreated.Tags = []
     }
 
     // Create video import object in database
     const videoImport = await VideoImportModel.create(
       Object.assign({ videoId: videoCreated.id }, videoImportAttributes),
       sequelizeOptions
-    )
+    ) as MVideoImportVideo
     videoImport.Video = videoCreated
 
     return videoImport

+ 8 - 7
server/controllers/api/videos/index.ts

@@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
 import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
 import { Hooks } from '../../../lib/plugins/hooks'
+import { MVideoFullLight } from '@server/typings/models'
 
 const auditLogger = auditLoggerFactory('videos')
 const videosRouter = express.Router()
@@ -238,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) {
   const { videoCreated } = await sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
-    const videoCreated = await video.save(sequelizeOptions)
+    const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
 
     await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
     await videoCreated.addAndSaveThumbnail(previewModel, t)
@@ -318,7 +319,7 @@ async function addVideo (req: express.Request, res: express.Response) {
 }
 
 async function updateVideo (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   const videoFieldsSave = videoInstance.toJSON()
   const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
   const videoInfoToUpdate: VideoUpdate = req.body
@@ -371,7 +372,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
         }
       }
 
-      const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
+      const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight
 
       if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
       if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
@@ -447,7 +448,7 @@ async function getVideo (req: express.Request, res: express.Response) {
 
   const video = await Hooks.wrapPromiseFun(
     VideoModel.loadForGetAPI,
-    { id: res.locals.video.id, userId },
+    { id: res.locals.onlyVideoWithRights.id, userId },
     'filter:api.video.get.result'
   )
 
@@ -460,7 +461,7 @@ async function getVideo (req: express.Request, res: express.Response) {
 }
 
 async function viewVideo (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
 
   const ip = req.ip
   const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
@@ -483,7 +484,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
 }
 
 async function getVideoDescription (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   let description = ''
 
   if (videoInstance.isOwned()) {
@@ -522,7 +523,7 @@ async function listVideos (req: express.Request, res: express.Response) {
 }
 
 async function removeVideo (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
 
   await sequelizeTypescript.transaction(async t => {
     await videoInstance.destroy({ transaction: t })

+ 3 - 2
server/controllers/api/videos/ownership.ts

@@ -18,6 +18,7 @@ import { getFormattedObjects } from '../../../helpers/utils'
 import { changeVideoChannelShare } from '../../../lib/activitypub'
 import { sendUpdateVideo } from '../../../lib/activitypub/send'
 import { VideoModel } from '../../../models/video/video'
+import { MVideoFullLight } from '@server/typings/models'
 
 const ownershipVideoRouter = express.Router()
 
@@ -56,7 +57,7 @@ export {
 // ---------------------------------------------------------------------------
 
 async function giveVideoOwnership (req: express.Request, res: express.Response) {
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   const initiatorAccountId = res.locals.oauth.token.User.Account.id
   const nextOwner = res.locals.nextOwner
 
@@ -107,7 +108,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
 
     targetVideo.channelId = channel.id
 
-    const targetVideoUpdated = await targetVideo.save({ transaction: t })
+    const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
     targetVideoUpdated.VideoChannel = channel
 
     if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) {

+ 1 - 1
server/controllers/api/videos/rate.ts

@@ -27,7 +27,7 @@ export {
 async function rateVideo (req: express.Request, res: express.Response) {
   const body: UserVideoRateUpdate = req.body
   const rateType = body.rating
-  const videoInstance = res.locals.video
+  const videoInstance = res.locals.videoAll
   const userAccount = res.locals.oauth.token.User.Account
 
   await sequelizeTypescript.transaction(async t => {

+ 1 - 1
server/controllers/api/videos/watching.ts

@@ -23,7 +23,7 @@ async function userWatchVideo (req: express.Request, res: express.Response) {
   const user = res.locals.oauth.token.User
 
   const body: UserWatchingVideo = req.body
-  const { id: videoId } = res.locals.video as { id: number }
+  const { id: videoId } = res.locals.videoId
 
   await UserVideoHistoryModel.upsert({
     videoId,

+ 1 - 1
server/controllers/feeds.ts

@@ -43,7 +43,7 @@ export {
 async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
   const start = 0
 
-  const video = res.locals.video
+  const video = res.locals.videoAll
   const videoId: number = video ? video.id : undefined
 
   const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)

+ 1 - 1
server/controllers/services.ts

@@ -23,7 +23,7 @@ export {
 // ---------------------------------------------------------------------------
 
 function generateOEmbed (req: express.Request, res: express.Response) {
-  const video = res.locals.video
+  const video = res.locals.videoAll
   const webserverUrl = WEBSERVER.URL
   const maxHeight = parseInt(req.query.maxheight, 10)
   const maxWidth = parseInt(req.query.maxwidth, 10)

+ 3 - 3
server/controllers/static.ts

@@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
   return res.send(json).end()
 }
 
-async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function downloadTorrent (req: express.Request, res: express.Response) {
   const { video, videoFile } = getVideoAndFile(req, res)
   if (!videoFile) return res.status(404).end()
 
   return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
 }
 
-async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function downloadVideoFile (req: express.Request, res: express.Response) {
   const { video, videoFile } = getVideoAndFile(req, res)
   if (!videoFile) return res.status(404).end()
 
@@ -242,7 +242,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n
 
 function getVideoAndFile (req: express.Request, res: express.Response) {
   const resolution = parseInt(req.params.resolution, 10)
-  const video = res.locals.video
+  const video = res.locals.videoAll
 
   const videoFile = video.VideoFiles.find(f => f.resolution === resolution)
 

+ 1 - 1
server/controllers/webfinger.ts

@@ -18,7 +18,7 @@ export {
 // ---------------------------------------------------------------------------
 
 function webfingerController (req: express.Request, res: express.Response) {
-  const actor = res.locals.actor
+  const actor = res.locals.actorFull
 
   const json = {
     subject: req.query.resource,

+ 2 - 1
server/helpers/activitypub.ts

@@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor'
 import { signJsonLDObject } from './peertube-crypto'
 import { pageToStartAndCount } from './core-utils'
 import { parse } from 'url'
+import { MActor } from '../typings/models'
 
 function activityPubContextify <T> (data: T) {
   return Object.assign(data, {
@@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi
 
 }
 
-function buildSignedActivity (byActor: ActorModel, data: Object) {
+function buildSignedActivity (byActor: MActor, data: Object) {
   const activity = activityPubContextify(data)
 
   return signJsonLDObject(byActor, activity) as Promise<Activity>

+ 6 - 3
server/helpers/actor.ts

@@ -1,10 +1,13 @@
 import { ActorModel } from '../models/activitypub/actor'
+import * as Bluebird from 'bluebird'
+import { MActorFull, MActorAccountChannelId } from '../typings/models'
 
-type ActorFetchByUrlType = 'all' | 'actor-and-association-ids'
-function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) {
+type ActorFetchByUrlType = 'all' | 'association-ids'
+
+function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> {
   if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url)
 
-  if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url)
+  if (fetchType === 'association-ids') return ActorModel.loadByUrl(url)
 }
 
 export {

+ 3 - 3
server/helpers/captions-utils.ts

@@ -1,10 +1,10 @@
 import { join } from 'path'
 import { CONFIG } from '../initializers/config'
-import { VideoCaptionModel } from '../models/video/video-caption'
 import * as srt2vtt from 'srt-to-vtt'
-import { createReadStream, createWriteStream, remove, move } from 'fs-extra'
+import { createReadStream, createWriteStream, move, remove } from 'fs-extra'
+import { MVideoCaption } from '@server/typings/models'
 
-async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) {
+async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaption) {
   const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
   const destination = join(videoCaptionsDir, videoCaption.getCaptionName())
 

+ 5 - 17
server/helpers/custom-validators/video-ownership.ts

@@ -1,10 +1,10 @@
 import { Response } from 'express'
-import * as validator from 'validator'
 import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
-import { UserModel } from '../../models/account/user'
+import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
+import { MUserId } from '@server/typings/models'
 
-export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> {
-  const videoChangeOwnership = await loadVideoChangeOwnership(id)
+export async function doesChangeVideoOwnershipExist (id: number, res: Response) {
+  const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
 
   if (!videoChangeOwnership) {
     res.status(404)
@@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response):
   return true
 }
 
-async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> {
-  if (validator.isInt(id)) {
-    return VideoChangeOwnershipModel.load(parseInt(id, 10))
-  }
-
-  return undefined
-}
-
-export function checkUserCanTerminateOwnershipChange (
-  user: UserModel,
-  videoChangeOwnership: VideoChangeOwnershipModel,
-  res: Response
-): boolean {
+export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) {
   if (videoChangeOwnership.NextOwner.userId === user.id) {
     return true
   }

+ 5 - 2
server/helpers/middlewares/accounts.ts

@@ -1,6 +1,7 @@
 import { Response } from 'express'
 import { AccountModel } from '../../models/account/account'
 import * as Bluebird from 'bluebird'
+import { MAccountDefault } from '../../typings/models'
 
 function doesAccountIdExist (id: number, res: Response, sendNotFound = true) {
   const promise = AccountModel.load(id)
@@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound =
 }
 
 function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) {
-  return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound)
+  const promise = AccountModel.loadByNameWithHost(nameWithDomain)
+
+  return doesAccountExist(promise, res, sendNotFound)
 }
 
-async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) {
+async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) {
   const account = await p
 
   if (!account) {

+ 13 - 31
server/helpers/middlewares/video-abuses.ts

@@ -1,41 +1,23 @@
-import * as express from 'express'
-import { VideoChannelModel } from '../../models/video/video-channel'
+import { Response } from 'express'
+import { VideoAbuseModel } from '../../models/video/video-abuse'
 
-async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
-  const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
+async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
+  const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
 
-  return processVideoChannelExist(videoChannel, res)
-}
-
-async function doesVideoChannelIdExist (id: number, res: express.Response) {
-  const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
-
-  return processVideoChannelExist(videoChannel, res)
-}
-
-async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
-  const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
-
-  return processVideoChannelExist(videoChannel, res)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
-  doesLocalVideoChannelNameExist,
-  doesVideoChannelIdExist,
-  doesVideoChannelNameWithHostExist
-}
-
-function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
-  if (!videoChannel) {
+  if (videoAbuse === null) {
     res.status(404)
-       .json({ error: 'Video channel not found' })
+       .json({ error: 'Video abuse not found' })
        .end()
 
     return false
   }
 
-  res.locals.videoChannel = videoChannel
+  res.locals.videoAbuse = videoAbuse
   return true
 }
+
+// ---------------------------------------------------------------------------
+
+export {
+  doesVideoAbuseExist
+}

+ 2 - 2
server/helpers/middlewares/video-captions.ts

@@ -1,8 +1,8 @@
-import { VideoModel } from '../../models/video/video'
 import { Response } from 'express'
 import { VideoCaptionModel } from '../../models/video/video-caption'
+import { MVideoId } from '@server/typings/models'
 
-async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) {
+async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) {
   const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
 
   if (!videoCaption) {

+ 32 - 13
server/helpers/middlewares/video-channels.ts

@@ -1,23 +1,42 @@
-import { Response } from 'express'
-import { VideoAbuseModel } from '../../models/video/video-abuse'
+import * as express from 'express'
+import { VideoChannelModel } from '../../models/video/video-channel'
+import { MChannelActorAccountDefault } from '../../typings/models'
 
-async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
-  const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
+async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
+  const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
 
-  if (videoAbuse === null) {
-    res.status(404)
-       .json({ error: 'Video abuse not found' })
-       .end()
+  return processVideoChannelExist(videoChannel, res)
+}
 
-    return false
-  }
+async function doesVideoChannelIdExist (id: number, res: express.Response) {
+  const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
 
-  res.locals.videoAbuse = videoAbuse
-  return true
+  return processVideoChannelExist(videoChannel, res)
+}
+
+async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
+  const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
+
+  return processVideoChannelExist(videoChannel, res)
 }
 
 // ---------------------------------------------------------------------------
 
 export {
-  doesVideoAbuseExist
+  doesLocalVideoChannelNameExist,
+  doesVideoChannelIdExist,
+  doesVideoChannelNameWithHostExist
+}
+
+function processVideoChannelExist (videoChannel: MChannelActorAccountDefault, res: express.Response) {
+  if (!videoChannel) {
+    res.status(404)
+       .json({ error: 'Video channel not found' })
+       .end()
+
+    return false
+  }
+
+  res.locals.videoChannel = videoChannel
+  return true
 }

+ 24 - 11
server/helpers/middlewares/video-playlists.ts

@@ -1,11 +1,31 @@
 import * as express from 'express'
 import { VideoPlaylistModel } from '../../models/video/video-playlist'
+import { MVideoPlaylist } from '../../typings/models/video/video-playlist'
 
-async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') {
-  const videoPlaylist = fetchType === 'summary'
-    ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
-    : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
+export type VideoPlaylistFetchType = 'summary' | 'all'
+async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') {
+  if (fetchType === 'summary') {
+    const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
+    res.locals.videoPlaylistSummary = videoPlaylist
 
+    return handleVideoPlaylist(videoPlaylist, res)
+  }
+
+  const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
+  res.locals.videoPlaylistFull = videoPlaylist
+
+  return handleVideoPlaylist(videoPlaylist, res)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  doesVideoPlaylistExist
+}
+
+// ---------------------------------------------------------------------------
+
+function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) {
   if (!videoPlaylist) {
     res.status(404)
        .json({ error: 'Video playlist not found' })
@@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons
     return false
   }
 
-  res.locals.videoPlaylist = videoPlaylist
   return true
 }
-
-// ---------------------------------------------------------------------------
-
-export {
-  doesVideoPlaylistExist
-}

+ 21 - 5
server/helpers/middlewares/videos.ts

@@ -1,9 +1,8 @@
 import { Response } from 'express'
 import { fetchVideo, VideoFetchType } from '../video'
-import { UserModel } from '../../models/account/user'
 import { UserRight } from '../../../shared/models/users'
 import { VideoChannelModel } from '../../models/video/video-channel'
-import { VideoModel } from '../../models/video/video'
+import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoWithRights } from '@server/typings/models'
 
 async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
   const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
@@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
     return false
   }
 
-  if (fetchType !== 'none') res.locals.video = video
+  switch (fetchType) {
+    case 'all':
+      res.locals.videoAll = video as MVideoFullLight
+      break
+
+    case 'id':
+      res.locals.videoId = video
+      break
+
+    case 'only-video':
+      res.locals.onlyVideo = video
+      break
+
+    case 'only-video-with-rights':
+      res.locals.onlyVideoWithRights = video as MVideoWithRights
+      break
+  }
+
   return true
 }
 
-async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
+async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) {
   if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
     const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
     if (videoChannel === null) {
@@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode
   return true
 }
 
-function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
+function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) {
   // Retrieve the user who did the request
   if (video.isOwned() === false) {
     res.status(403)

+ 5 - 4
server/helpers/peertube-crypto.ts

@@ -8,6 +8,7 @@ import { cloneDeep } from 'lodash'
 import { createVerify } from 'crypto'
 import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
 import * as bcrypt from 'bcrypt'
+import { MActor } from '../typings/models'
 
 const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
 const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
@@ -46,7 +47,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean {
   return true
 }
 
-function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean {
+function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
   return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
 }
 
@@ -56,7 +57,7 @@ function parseHTTPSignature (req: Request, clockSkew?: number) {
 
 // JSONLD
 
-async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> {
+async function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
   if (signedDocument.signature.type === 'RsaSignature2017') {
     // Mastodon algorithm
     const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
@@ -93,7 +94,7 @@ async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument:
 }
 
 // Backward compatibility with "other" implementations
-async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) {
+async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
   function hash (obj: any): Promise<any> {
     return jsonld.promises
                  .normalize(obj, {
@@ -130,7 +131,7 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a
   return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
 }
 
-function signJsonLDObject (byActor: ActorModel, data: any) {
+function signJsonLDObject (byActor: MActor, data: any) {
   const options = {
     privateKeyPem: byActor.privateKey,
     creator: byActor.url,

+ 27 - 2
server/helpers/video.ts

@@ -1,8 +1,24 @@
 import { VideoModel } from '../models/video/video'
+import * as Bluebird from 'bluebird'
+import { MVideoAccountAllFiles, MVideoFullLight, MVideoThumbnail, MVideoWithRights, MVideoIdThumbnail } from '@server/typings/models'
+import { Response } from 'express'
 
 type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
 
-function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) {
+function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
+function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
+function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
+function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
+function fetchVideo (
+  id: number | string,
+  fetchType: VideoFetchType,
+  userId?: number
+): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail>
+function fetchVideo (
+  id: number | string,
+  fetchType: VideoFetchType,
+  userId?: number
+): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> {
   if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
 
   if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
@@ -13,15 +29,24 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu
 }
 
 type VideoFetchByUrlType = 'all' | 'only-video'
-function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) {
+
+function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountAllFiles>
+function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail>
+function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountAllFiles> | Bluebird<MVideoThumbnail>
+function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountAllFiles> | Bluebird<MVideoThumbnail> {
   if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url)
 
   if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
 }
 
+function getVideo (res: Response) {
+  return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId
+}
+
 export {
   VideoFetchType,
   VideoFetchByUrlType,
   fetchVideo,
+  getVideo,
   fetchVideoByUrl
 }

+ 2 - 1
server/helpers/webfinger.ts

@@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor'
 import { isTestInstance } from './core-utils'
 import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
 import { WEBSERVER } from '../initializers/constants'
+import { MActorFull } from '../typings/models'
 
 const webfinger = new WebFinger({
   webfist_fallback: false,
@@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) {
   const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg
 
   const [ name, host ] = uri.split('@')
-  let actor: ActorModel
+  let actor: MActorFull
 
   if (!host || host === WEBSERVER.HOST) {
     actor = await ActorModel.loadLocalByName(name)

+ 52 - 23
server/lib/activitypub/actor.ts

@@ -22,13 +22,25 @@ import { JobQueue } from '../job-queue'
 import { getServerActor } from '../../helpers/utils'
 import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
 import { sequelizeTypescript } from '../../initializers/database'
+import {
+  MAccount,
+  MActor,
+  MActorAccountChannelId,
+  MActorAccountId,
+  MActorDefault,
+  MActorFull,
+  MActorId,
+  MActorAccountChannelIdActor,
+  MChannel,
+  MActorFullActor, MAccountActorDefault, MChannelActorDefault, MChannelActorAccountDefault
+} from '../../typings/models'
 
 // Set account keys, this could be long so process after the account creation and do not block the client
-function setAsyncActorKeys (actor: ActorModel) {
+function setAsyncActorKeys (actor: MActor) {
   return createPrivateAndPublicKeys()
     .then(({ publicKey, privateKey }) => {
-      actor.set('publicKey', publicKey)
-      actor.set('privateKey', privateKey)
+      actor.publicKey = publicKey
+      actor.privateKey = privateKey
       return actor.save()
     })
     .catch(err => {
@@ -37,12 +49,26 @@ function setAsyncActorKeys (actor: ActorModel) {
     })
 }
 
+function getOrCreateActorAndServerAndModel (
+  activityActor: string | ActivityPubActor,
+  fetchType: 'all',
+  recurseIfNeeded?: boolean,
+  updateCollections?: boolean
+): Promise<MActorFullActor>
+
+function getOrCreateActorAndServerAndModel (
+  activityActor: string | ActivityPubActor,
+  fetchType?: 'association-ids',
+  recurseIfNeeded?: boolean,
+  updateCollections?: boolean
+): Promise<MActorAccountChannelId>
+
 async function getOrCreateActorAndServerAndModel (
   activityActor: string | ActivityPubActor,
-  fetchType: ActorFetchByUrlType = 'actor-and-association-ids',
+  fetchType: ActorFetchByUrlType = 'association-ids',
   recurseIfNeeded = true,
   updateCollections = false
-) {
+): Promise<MActorFullActor | MActorAccountChannelId> {
   const actorUrl = getAPId(activityActor)
   let created = false
   let accountPlaylistsUrl: string
@@ -61,7 +87,7 @@ async function getOrCreateActorAndServerAndModel (
 
     // Create the attributed to actor
     // In PeerTube a video channel is owned by an account
-    let ownerActor: ActorModel = undefined
+    let ownerActor: MActorFullActor
     if (recurseIfNeeded === true && result.actor.type === 'Group') {
       const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
       if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
@@ -85,8 +111,8 @@ async function getOrCreateActorAndServerAndModel (
     accountPlaylistsUrl = result.playlists
   }
 
-  if (actor.Account) actor.Account.Actor = actor
-  if (actor.VideoChannel) actor.VideoChannel.Actor = actor
+  if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
+  if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
 
   const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
   if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.')
@@ -140,7 +166,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
   actorInstance.followingUrl = attributes.following
 }
 
-async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) {
+type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
+async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) {
   if (info.name !== undefined) {
     if (actor.avatarId) {
       try {
@@ -212,14 +239,16 @@ async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) {
   return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
 }
 
-async function refreshActorIfNeeded (
-  actorArg: ActorModel,
+async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (
+  actorArg: T,
   fetchedType: ActorFetchByUrlType
-): Promise<{ actor: ActorModel, refreshed: boolean }> {
+): Promise<{ actor: T | MActorFull, refreshed: boolean }> {
   if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
 
   // We need more attributes
-  const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
+  const actor = fetchedType === 'all'
+    ? actorArg as MActorFull
+    : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
 
   try {
     let actorUrl: string
@@ -297,9 +326,9 @@ export {
 
 function saveActorAndServerAndModelIfNotExist (
   result: FetchRemoteActorResult,
-  ownerActor?: ActorModel,
+  ownerActor?: MActorFullActor,
   t?: Transaction
-): Bluebird<ActorModel> | Promise<ActorModel> {
+): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
   let actor = result.actor
 
   if (t !== undefined) return save(t)
@@ -336,7 +365,7 @@ function saveActorAndServerAndModelIfNotExist (
 
     // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
     // (which could be false in a retried query)
-    const [ actorCreated ] = await ActorModel.findOrCreate({
+    const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({
       defaults: actor.toJSON(),
       where: {
         url: actor.url
@@ -345,10 +374,10 @@ function saveActorAndServerAndModelIfNotExist (
     })
 
     if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
-      actorCreated.Account = await saveAccount(actorCreated, result, t)
+      actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountActorDefault
       actorCreated.Account.Actor = actorCreated
     } else if (actorCreated.type === 'Group') { // Video channel
-      actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
+      actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) as MChannelActorAccountDefault
       actorCreated.VideoChannel.Actor = actorCreated
       actorCreated.VideoChannel.Account = ownerActor.Account
     }
@@ -360,7 +389,7 @@ function saveActorAndServerAndModelIfNotExist (
 }
 
 type FetchRemoteActorResult = {
-  actor: ActorModel
+  actor: MActor
   name: string
   summary: string
   support?: string
@@ -429,7 +458,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
   }
 }
 
-async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
+async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) {
   const [ accountCreated ] = await AccountModel.findOrCreate({
     defaults: {
       name: result.name,
@@ -442,10 +471,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t
     transaction: t
   })
 
-  return accountCreated
+  return accountCreated as MAccount
 }
 
-async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
+async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) {
   const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
     defaults: {
       name: result.name,
@@ -460,5 +489,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
     transaction: t
   })
 
-  return videoChannelCreated
+  return videoChannelCreated as MChannel
 }

+ 14 - 13
server/lib/activitypub/audience.ts

@@ -3,11 +3,10 @@ import { ActivityAudience } from '../../../shared/models/activitypub'
 import { ACTIVITY_PUB } from '../../initializers/constants'
 import { ActorModel } from '../../models/activitypub/actor'
 import { VideoModel } from '../../models/video/video'
-import { VideoCommentModel } from '../../models/video/video-comment'
 import { VideoShareModel } from '../../models/video/video-share'
-import { ActorModelOnly } from '../../typings/models'
+import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models'
 
-function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience {
+function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience {
   return {
     to: [ video.VideoChannel.Account.Actor.url ],
     cc: actorsInvolvedInVideo.map(a => a.followersUrl)
@@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor
 }
 
 function getVideoCommentAudience (
-  videoComment: VideoCommentModel,
-  threadParentComments: VideoCommentModel[],
-  actorsInvolvedInVideo: ActorModel[],
+  videoComment: MCommentOwnerVideo,
+  threadParentComments: MCommentOwner[],
+  actorsInvolvedInVideo: MActorFollowersUrl[],
   isOrigin = false
 ): ActivityAudience {
   const to = [ ACTIVITY_PUB.PUBLIC ]
@@ -42,26 +41,28 @@ function getVideoCommentAudience (
   }
 }
 
-function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience {
+function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
   return {
     to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
     cc: []
   }
 }
 
-async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
-  const actors = await VideoShareModel.loadActorsByShare(video.id, t)
+async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) {
+  const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t)
 
-  const videoActor = video.VideoChannel && video.VideoChannel.Account
-    ? video.VideoChannel.Account.Actor
-    : await ActorModel.loadAccountActorByVideoId(video.id, t)
+  const videoAll = video as VideoModel
+
+  const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account
+    ? videoAll.VideoChannel.Account.Actor
+    : await ActorModel.loadFromAccountByVideoId(video.id, t)
 
   actors.push(videoActor)
 
   return actors
 }
 
-function getAudience (actorSender: ActorModelOnly, isPublic = true) {
+function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
   return buildAudience([ actorSender.followersUrl ], isPublic)
 }
 

+ 7 - 7
server/lib/activitypub/cache-file.ts

@@ -1,10 +1,10 @@
 import { CacheFileObject } from '../../../shared/index'
-import { VideoModel } from '../../models/video/video'
 import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
 import { Transaction } from 'sequelize'
 import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
+import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models'
 
-function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) {
+function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) {
 
   if (cacheFileObject.url.mediaType === 'application/x-mpegURL') {
     const url = cacheFileObject.url
@@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
   }
 }
 
-async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) {
+async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
   const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
 
   if (!redundancyModel) {
@@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video:
   }
 }
 
-function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) {
+function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
   const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor)
 
   return VideoRedundancyModel.create(attributes, { transaction: t })
@@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b
 
 function updateCacheFile (
   cacheFileObject: CacheFileObject,
-  redundancyModel: VideoRedundancyModel,
-  video: VideoModel,
-  byActor: { id?: number },
+  redundancyModel: MVideoRedundancy,
+  video: MVideoWithAllFiles,
+  byActor: MActorId,
   t: Transaction
 ) {
   if (redundancyModel.actorId !== byActor.id) {

+ 9 - 10
server/lib/activitypub/playlist.ts

@@ -1,7 +1,6 @@
 import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
 import { crawlCollectionPage } from './crawl'
 import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { AccountModel } from '../../models/account/account'
 import { isArray } from '../../helpers/custom-validators/misc'
 import { getOrCreateActorAndServerAndModel } from './actor'
 import { logger } from '../../helpers/logger'
@@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object
 import { getOrCreateVideoAndAccountAndChannel } from './videos'
 import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
 import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
-import { VideoModel } from '../../models/video/video'
 import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
 import { sequelizeTypescript } from '../../initializers/database'
 import { createPlaylistMiniatureFromUrl } from '../thumbnail'
 import { FilteredModelAttributes } from '../../typings/sequelize'
-import { AccountModelId } from '../../typings/models'
+import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models'
+import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist'
 
-function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) {
+function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
   const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
 
   return {
@@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount
   }
 }
 
-function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
   return {
     position: elementObject.position,
     url: elementObject.id,
@@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
   }
 }
 
-async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) {
+async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) {
   await Bluebird.map(playlistUrls, async playlistUrl => {
     try {
       const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl)
@@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM
   }, { concurrency: CRAWL_REQUEST_CONCURRENCY })
 }
 
-async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) {
+async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
   const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to)
 
   if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) {
@@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
     }
   }
 
-  const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true })
+  const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true })
 
   let accItems: string[] = []
   await crawlCollectionPage<string>(playlistObject.id, items => {
@@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
   return resetVideoPlaylistElements(accItems, refreshedPlaylist)
 }
 
-async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> {
+async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> {
   if (!videoPlaylist.isOutdated()) return videoPlaylist
 
   try {
@@ -157,7 +156,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
+async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) {
   const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
 
   await Bluebird.map(elementUrls, async elementUrl => {

+ 2 - 3
server/lib/activitypub/process/process-accept.ts

@@ -1,9 +1,8 @@
 import { ActivityAccept } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { addFetchOutboxJob } from '../actor'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorDefault, MActorSignature } from '../../../typings/models'
 
 async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
   const { byActor: targetActor, inboxActor } = options
@@ -20,7 +19,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) {
+async function processAccept (actor: MActorDefault, targetActor: MActorSignature) {
   const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
   if (!follow) throw new Error('Cannot find associated follow.')
 

+ 3 - 4
server/lib/activitypub/process/process-announce.ts

@@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { Notifier } from '../../notifier'
-import { VideoModel } from '../../../models/video/video'
 import { logger } from '../../../helpers/logger'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MVideoAccountAllFiles } from '../../../typings/models'
 
 async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
   const { activity, byActor: actorAnnouncer } = options
@@ -26,10 +25,10 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) {
+async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) {
   const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
 
-  let video: VideoModel
+  let video: MVideoAccountAllFiles
   let videoCreated: boolean
 
   try {

+ 6 - 8
server/lib/activitypub/process/process-create.ts

@@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file'
 import { Notifier } from '../../notifier'
 import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
 import { createOrUpdateVideoPlaylist } from '../playlist'
-import { VideoModel } from '../../../models/video/video'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { VideoCommentModel } from '../../../models/video/video-comment'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MCommentOwnerVideo, MVideoAccountAllFiles } from '../../../typings/models'
 
 async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
   const { activity, byActor } = options
@@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
   return video
 }
 
-async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) {
+async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) {
   const cacheFile = activity.object as CacheFileObject
 
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
@@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat
   }
 }
 
-async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) {
+async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) {
   const commentObject = activity.object as VideoCommentObject
   const byAccount = byActor.Account
 
   if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
 
-  let video: VideoModel
+  let video: MVideoAccountAllFiles
   let created: boolean
-  let comment: VideoCommentModel
+  let comment: MCommentOwnerVideo
   try {
     const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
     video = resolveThreadResult.video
@@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig
   if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
 }
 
-async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) {
+async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) {
   const playlistObject = activity.object as PlaylistObject
   const byAccount = byActor.Account
 

+ 14 - 12
server/lib/activitypub/process/process-delete.ts

@@ -2,15 +2,13 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
 import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { logger } from '../../../helpers/logger'
 import { sequelizeTypescript } from '../../../initializers'
-import { AccountModel } from '../../../models/account/account'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoModel } from '../../../models/video/video'
-import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { VideoPlaylistModel } from '../../../models/video/video-playlist'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models'
 
 async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
   const { activity, byActor } = options
@@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete
     if (byActorFull.type === 'Person') {
       if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.')
 
-      byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel
-      return retryTransactionWrapper(processDeleteAccount, byActorFull.Account)
+      const accountToDelete = byActorFull.Account as MAccountActor
+      accountToDelete.Actor = byActorFull
+
+      return retryTransactionWrapper(processDeleteAccount, accountToDelete)
     } else if (byActorFull.type === 'Group') {
       if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.')
 
-      byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel
-      return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel)
+      const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor
+      channelToDelete.Actor = byActorFull
+
+      return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete)
     }
   }
 
@@ -70,7 +72,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
+async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) {
   logger.debug('Removing remote video "%s".', videoToDelete.uuid)
 
   await sequelizeTypescript.transaction(async t => {
@@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel)
   logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
 }
 
-async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) {
+async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) {
   logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
 
   await sequelizeTypescript.transaction(async t => {
@@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete:
   logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
 }
 
-async function processDeleteAccount (accountToRemove: AccountModel) {
+async function processDeleteAccount (accountToRemove: MAccountActor) {
   logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
 
   await sequelizeTypescript.transaction(async t => {
@@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) {
   logger.info('Remote account %s removed.', accountToRemove.Actor.url)
 }
 
-async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
+async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) {
   logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
 
   await sequelizeTypescript.transaction(async t => {
@@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode
   logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
 }
 
-function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
+function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) {
   logger.debug('Removing remote video comment "%s".', videoComment.url)
 
   return sequelizeTypescript.transaction(async t => {

+ 2 - 2
server/lib/activitypub/process/process-dislike.ts

@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { getVideoDislikeActivityPubUrl } from '../url'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
 
 async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
   const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) {
+async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) {
   const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object
   const byAccount = byActor.Account
 

+ 3 - 3
server/lib/activitypub/process/process-flag.ts

@@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { Notifier } from '../../notifier'
 import { getAPId } from '../../../helpers/activitypub'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models'
 
 async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
   const { activity, byActor } = options
@@ -23,7 +23,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) {
+async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) {
   const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
 
   logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
@@ -41,7 +41,7 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag,
       state: VideoAbuseState.PENDING
     }
 
-    const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t })
+    const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
     videoAbuseInstance.Video = video
 
     logger.info('Remote abuse for video uuid %s created', flag.object)

+ 13 - 8
server/lib/activitypub/process/process-follow.ts

@@ -10,8 +10,7 @@ import { getAPId } from '../../../helpers/activitypub'
 import { getServerActor } from '../../../helpers/utils'
 import { CONFIG } from '../../../initializers/config'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
-import { ActorFollowModelLight } from '../../../typings/models/actor-follow'
+import { MAccount, MActorFollowActors, MActorFollowFull, MActorSignature } from '../../../typings/models'
 
 async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
   const { activity, byActor } = options
@@ -28,7 +27,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processFollow (byActor: SignatureActorModel, targetActorURL: string) {
+async function processFollow (byActor: MActorSignature, targetActorURL: string) {
   const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => {
     const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
 
@@ -43,10 +42,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
 
       await sendReject(byActor, targetActor)
 
-      return { actorFollow: undefined }
+      return { actorFollow: undefined as MActorFollowActors }
     }
 
-    const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({
+    const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
       where: {
         actorId: byActor.id,
         targetActorId: targetActor.id
@@ -57,7 +56,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
         state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted'
       },
       transaction: t
-    }) as [ ActorFollowModelLight, boolean ]
+    })
 
     if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) {
       actorFollow.state = 'accepted'
@@ -77,8 +76,14 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri
   if (!actorFollow) return
 
   if (created) {
-    if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
-    else Notifier.Instance.notifyOfNewUserFollow(actorFollow)
+    if (isFollowingInstance) {
+      Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
+    } else {
+      const actorFollowFull = actorFollow as MActorFollowFull
+      actorFollowFull.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as MAccount
+
+      Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
+    }
   }
 
   logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url)

+ 2 - 2
server/lib/activitypub/process/process-like.ts

@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { getVideoLikeActivityPubUrl } from '../url'
 import { getAPId } from '../../../helpers/activitypub'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
 
 async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
   const { activity, byActor } = options
@@ -22,7 +22,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) {
+async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) {
   const videoUrl = getAPId(activity.object)
 
   const byAccount = byActor.Account

+ 2 - 2
server/lib/activitypub/process/process-reject.ts

@@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity'
 import { sequelizeTypescript } from '../../../initializers'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor } from '../../../typings/models'
 
 async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
   const { byActor: targetActor, inboxActor } = options
@@ -19,7 +19,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) {
+async function processReject (follower: MActor, targetActor: MActor) {
   return sequelizeTypescript.transaction(async t => {
     const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t)
 

+ 6 - 6
server/lib/activitypub/process/process-undo.ts

@@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
 
 async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
   const { activity, byActor } = options
@@ -54,7 +54,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
   const likeActivity = activity.object as ActivityLike
 
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
@@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity
   })
 }
 
-async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) {
   const dislike = activity.object.type === 'Dislike'
     ? activity.object
     : activity.object.object as DislikeObject
@@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ
   })
 }
 
-async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
   const cacheFileObject = activity.object.object as CacheFileObject
 
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
@@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act
   })
 }
 
-function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) {
+function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
   return sequelizeTypescript.transaction(async t => {
     const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
     const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
@@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ
   })
 }
 
-function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) {
+function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
   return sequelizeTypescript.transaction(async t => {
     const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
     if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)

+ 6 - 6
server/lib/activitypub/process/process-update.ts

@@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
 import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
 import { createOrUpdateVideoPlaylist } from '../playlist'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
 
 async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
   const { activity, byActor } = options
@@ -53,7 +53,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) {
   const videoObject = activity.object as VideoTorrentObject
 
   if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@@ -61,20 +61,20 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit
     return undefined
   }
 
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false })
+  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' })
   const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
 
   const updateOptions = {
     video,
     videoObject,
-    account: actor.Account,
+    account: channelActor.VideoChannel.Account,
     channel: channelActor.VideoChannel,
     overrideTo: activity.to
   }
   return updateVideoFromAP(updateOptions)
 }
 
-async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {
   const cacheFileObject = activity.object as CacheFileObject
 
   if (!isCacheFileObjectValid(cacheFileObject)) {
@@ -150,7 +150,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate)
   }
 }
 
-async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) {
   const playlistObject = activity.object as PlaylistObject
   const byAccount = byActor.Account
 

+ 3 - 3
server/lib/activitypub/process/process-view.ts

@@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils'
 import { Redis } from '../../redis'
 import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
 
 async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
   const { activity, byActor } = options
@@ -18,11 +18,11 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) {
+async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) {
   const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
 
   const options = {
-    videoObject: videoObject,
+    videoObject,
     fetchType: 'only-video' as 'only-video'
   }
   const { video } = await getOrCreateVideoAndAccountAndChannel(options)

+ 5 - 6
server/lib/activitypub/process/process.ts

@@ -1,7 +1,6 @@
 import { Activity, ActivityType } from '../../../../shared/models/activitypub'
 import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
 import { logger } from '../../../helpers/logger'
-import { ActorModel } from '../../../models/activitypub/actor'
 import { processAcceptActivity } from './process-accept'
 import { processAnnounceActivity } from './process-announce'
 import { processCreateActivity } from './process-create'
@@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike'
 import { processFlagActivity } from './process-flag'
 import { processViewActivity } from './process-view'
 import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorDefault, MActorSignature } from '../../../typings/models'
 
 const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
   Create: processCreateActivity,
@@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
 async function processActivities (
   activities: Activity[],
   options: {
-    signatureActor?: SignatureActorModel
-    inboxActor?: ActorModel
+    signatureActor?: MActorSignature
+    inboxActor?: MActorDefault
     outboxUrl?: string
     fromFetch?: boolean
   } = {}
 ) {
   const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
 
-  const actorsCache: { [ url: string ]: SignatureActorModel } = {}
+  const actorsCache: { [ url: string ]: MActorSignature } = {}
 
   for (const activity of activities) {
     if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
@@ -75,7 +74,7 @@ async function processActivities (
     }
 
     try {
-      await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch })
+      await activityProcessor({ activity, byActor, inboxActor, fromFetch })
     } catch (err) {
       logger.warn('Cannot process activity %s.', activity.type, { err })
     }

+ 3 - 4
server/lib/activitypub/send/send-accept.ts

@@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from
 import { unicastTo } from './utils'
 import { buildFollowActivity } from './send-follow'
 import { logger } from '../../../helpers/logger'
-import { ActorFollowModelLight } from '../../../typings/models/actor-follow'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor, MActorFollowActors } from '../../../typings/models'
 
-async function sendAccept (actorFollow: ActorFollowModelLight) {
+async function sendAccept (actorFollow: MActorFollowActors) {
   const follower = actorFollow.ActorFollower
   const me = actorFollow.ActorFollowing
 
@@ -34,7 +33,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept {
+function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept {
   return {
     type: 'Accept',
     id: url,

+ 7 - 8
server/lib/activitypub/send/send-announce.ts

@@ -1,16 +1,15 @@
 import { Transaction } from 'sequelize'
 import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub'
-import { VideoModel } from '../../../models/video/video'
 import { broadcastToFollowers } from './utils'
 import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
 import { logger } from '../../../helpers/logger'
-import { ActorModelOnly } from '../../../typings/models'
-import { VideoShareModelOnly } from '../../../typings/models/video-share'
+import { MActorLight, MVideo } from '../../../typings/models'
+import { MVideoShare } from '../../../typings/models/video'
 
 async function buildAnnounceWithVideoAudience (
-  byActor: ActorModelOnly,
-  videoShare: VideoShareModelOnly,
-  video: VideoModel,
+  byActor: MActorLight,
+  videoShare: MVideoShare,
+  video: MVideo,
   t: Transaction
 ) {
   const announcedObject = video.url
@@ -23,7 +22,7 @@ async function buildAnnounceWithVideoAudience (
   return { activity, actorsInvolvedInVideo }
 }
 
-async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) {
+async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
   const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
 
   logger.info('Creating job to send announce %s.', videoShare.url)
@@ -32,7 +31,7 @@ async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShar
   return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException)
 }
 
-function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce {
+function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify({

+ 21 - 13
server/lib/activitypub/send/send-create.ts

@@ -1,19 +1,23 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
 import { VideoPrivacy } from '../../../../shared/models/videos'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
 import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
 import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
 import { getServerActor } from '../../../helpers/utils'
-import * as Bluebird from 'bluebird'
-
-async function sendCreateVideo (video: VideoModel, t: Transaction) {
+import {
+  MActorLight,
+  MCommentOwnerVideo,
+  MVideoAccountLight,
+  MVideoAP,
+  MVideoPlaylistFull,
+  MVideoRedundancyFileVideo,
+  MVideoRedundancyStreamingPlaylistVideo
+} from '../../../typings/models'
+
+async function sendCreateVideo (video: MVideoAP, t: Transaction) {
   if (video.privacy === VideoPrivacy.PRIVATE) return undefined
 
   logger.info('Creating job to send video creation of %s.', video.url)
@@ -27,7 +31,11 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) {
   return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
 }
 
-async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) {
+async function sendCreateCacheFile (
+  byActor: MActorLight,
+  video: MVideoAccountLight,
+  fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
+) {
   logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
 
   return sendVideoRelatedCreateActivity({
@@ -38,7 +46,7 @@ async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, file
   })
 }
 
-async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) {
+async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
   if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
 
   logger.info('Creating job to send create video playlist of %s.', playlist.url)
@@ -57,7 +65,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac
   return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
 }
 
-async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) {
+async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
   logger.info('Creating job to send comment %s.', comment.url)
 
   const isOrigin = comment.Video.isOwned()
@@ -95,7 +103,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
   t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
 }
 
-function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate {
+function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify(
@@ -122,8 +130,8 @@ export {
 // ---------------------------------------------------------------------------
 
 async function sendVideoRelatedCreateActivity (options: {
-  byActor: ActorModel,
-  video: VideoModel,
+  byActor: MActorLight,
+  video: MVideoAccountLight,
   url: string,
   object: any,
   transaction?: Transaction

+ 6 - 6
server/lib/activitypub/send/send-delete.ts

@@ -1,17 +1,17 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
 import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { getDeleteActivityPubUrl } from '../url'
 import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
 import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
 import { getServerActor } from '../../../helpers/utils'
+import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video'
+import { MActorUrl } from '../../../typings/models'
 
-async function sendDeleteVideo (video: VideoModel, transaction: Transaction) {
+async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) {
   logger.info('Creating job to broadcast delete of video %s.', video.url)
 
   const byActor = video.VideoChannel.Account.Actor
@@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
   return broadcastToFollowers(activity, byActor, actorsInvolved, t)
 }
 
-async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
+async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) {
   logger.info('Creating job to send delete of comment %s.', videoComment.url)
 
   const isVideoOrigin = videoComment.Video.isOwned()
@@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans
   t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
 }
 
-async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) {
+async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
   logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
 
   const byActor = videoPlaylist.OwnerAccount.Actor
@@ -101,7 +101,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete {
+function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete {
   const activity = {
     type: 'Delete' as 'Delete',
     id: url,

+ 3 - 4
server/lib/activitypub/send/send-dislike.ts

@@ -1,13 +1,12 @@
 import { Transaction } from 'sequelize'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
 import { getVideoDislikeActivityPubUrl } from '../url'
 import { logger } from '../../../helpers/logger'
 import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub'
 import { sendVideoRelatedActivity } from './utils'
 import { audiencify, getAudience } from '../audience'
+import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
 
-async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
   logger.info('Creating job to dislike %s.', video.url)
 
   const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti
   return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
 }
 
-function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike {
+function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify(

+ 4 - 5
server/lib/activitypub/send/send-flag.ts

@@ -1,14 +1,13 @@
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
-import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { getVideoAbuseActivityPubUrl } from '../url'
 import { unicastTo } from './utils'
 import { logger } from '../../../helpers/logger'
 import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub'
 import { audiencify, getAudience } from '../audience'
 import { Transaction } from 'sequelize'
+import { MActor, MVideoFullLight } from '../../../typings/models'
+import { MVideoAbuseVideo } from '../../../typings/models/video'
 
-async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
+async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
   if (!video.VideoChannel.Account.Actor.serverId) return // Local user
 
   const url = getVideoAbuseActivityPubUrl(videoAbuse)
@@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
   t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
 }
 
-function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag {
+function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
   if (!audience) audience = getAudience(byActor)
 
   const activity = Object.assign(

+ 3 - 3
server/lib/activitypub/send/send-follow.ts

@@ -4,9 +4,9 @@ import { getActorFollowActivityPubUrl } from '../url'
 import { unicastTo } from './utils'
 import { logger } from '../../../helpers/logger'
 import { Transaction } from 'sequelize'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor, MActorFollowActors } from '../../../typings/models'
 
-function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
+function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
   const me = actorFollow.ActorFollower
   const following = actorFollow.ActorFollowing
 
@@ -21,7 +21,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
   t.afterCommit(() => unicastTo(data, me, following.inboxUrl))
 }
 
-function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow {
+function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
   return {
     type: 'Follow',
     id: url,

+ 3 - 4
server/lib/activitypub/send/send-like.ts

@@ -1,13 +1,12 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
 import { getVideoLikeActivityPubUrl } from '../url'
 import { sendVideoRelatedActivity } from './utils'
 import { audiencify, getAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
+import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
 
-async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
   logger.info('Creating job to like %s.', video.url)
 
   const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction)
   return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
 }
 
-function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike {
+function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify(

+ 3 - 4
server/lib/activitypub/send/send-reject.ts

@@ -1,12 +1,11 @@
 import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
 import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url'
 import { unicastTo } from './utils'
 import { buildFollowActivity } from './send-follow'
 import { logger } from '../../../helpers/logger'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActor } from '../../../typings/models'
 
-async function sendReject (follower: SignatureActorModel, following: ActorModel) {
+async function sendReject (follower: MActor, following: MActor) {
   if (!follower.serverId) { // This should never happen
     logger.warn('Do not sending reject to local follower.')
     return
@@ -31,7 +30,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject {
+function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject {
   return {
     type: 'Reject',
     id: url,

+ 20 - 14
server/lib/activitypub/send/send-undo.ts

@@ -2,13 +2,12 @@ import { Transaction } from 'sequelize'
 import {
   ActivityAnnounce,
   ActivityAudience,
-  ActivityCreate, ActivityDislike,
+  ActivityCreate,
+  ActivityDislike,
   ActivityFollow,
   ActivityLike,
   ActivityUndo
 } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { VideoModel } from '../../../models/video/video'
 import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
 import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
@@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience'
 import { buildCreateActivity } from './send-create'
 import { buildFollowActivity } from './send-follow'
 import { buildLikeActivity } from './send-like'
-import { VideoShareModel } from '../../../models/video/video-share'
 import { buildAnnounceWithVideoAudience } from './send-announce'
 import { logger } from '../../../helpers/logger'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
 import { buildDislikeActivity } from './send-dislike'
-
-async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
+import {
+  MActor, MActorAudience,
+  MActorFollowActors,
+  MActorLight,
+  MVideo,
+  MVideoAccountLight,
+  MVideoRedundancyVideo,
+  MVideoShare
+} from '../../../typings/models'
+
+async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
   const me = actorFollow.ActorFollower
   const following = actorFollow.ActorFollowing
 
@@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
   t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl))
 }
 
-async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
+async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
   logger.info('Creating job to undo announce %s.', videoShare.url)
 
   const undoUrl = getUndoActivityPubUrl(videoShare.url)
@@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode
   return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
 }
 
-async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
   logger.info('Creating job to undo a like of video %s.', video.url)
 
   const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
@@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact
   return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
 }
 
-async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
   logger.info('Creating job to undo a dislike of video %s.', video.url)
 
   const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
@@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans
   return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
 }
 
-async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) {
+async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) {
   logger.info('Creating job to undo cache file %s.', redundancyModel.url)
 
   const videoId = redundancyModel.getVideo().id
@@ -94,7 +100,7 @@ export {
 
 function undoActivityData (
   url: string,
-  byActor: ActorModel,
+  byActor: MActorAudience,
   object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
   audience?: ActivityAudience
 ): ActivityUndo {
@@ -112,8 +118,8 @@ function undoActivityData (
 }
 
 async function sendUndoVideoRelatedActivity (options: {
-  byActor: ActorModel,
-  video: VideoModel,
+  byActor: MActor,
+  video: MVideoAccountLight,
   url: string,
   activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
   transaction: Transaction

+ 19 - 12
server/lib/activitypub/send/send-update.ts

@@ -2,21 +2,29 @@ import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
 import { VideoPrivacy } from '../../../../shared/models/videos'
 import { AccountModel } from '../../../models/account/account'
-import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoModel } from '../../../models/video/video'
-import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { getUpdateActivityPubUrl } from '../url'
 import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
 import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
 import { VideoCaptionModel } from '../../../models/video/video-caption'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
 import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
 import { getServerActor } from '../../../helpers/utils'
+import {
+  MAccountActor,
+  MActor,
+  MActorLight,
+  MChannelActor,
+  MVideoAP,
+  MVideoAPWithoutCaption,
+  MVideoPlaylistFull,
+  MVideoRedundancyVideo
+} from '../../../typings/models'
+
+async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) {
+  const video = videoArg as MVideoAP
 
-async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
   if (video.privacy === VideoPrivacy.PRIVATE) return undefined
 
   logger.info('Creating job to update video %s.', video.url)
@@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct
   return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
 }
 
-async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) {
+async function sendUpdateActor (accountOrChannel: MAccountActor | MChannelActor, t: Transaction) {
   const byActor = accountOrChannel.Actor
 
   logger.info('Creating job to update actor %s.', byActor.url)
@@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
   const audience = getAudience(byActor)
   const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience)
 
-  let actorsInvolved: ActorModel[]
+  let actorsInvolved: MActor[]
   if (accountOrChannel instanceof AccountModel) {
     // Actors that shared my videos are involved too
     actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
@@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
   return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
 }
 
-async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) {
+async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
   logger.info('Creating job to update cache file %s.', redundancyModel.url)
 
   const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id)
@@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR
   return sendVideoRelatedActivity(activityBuilder, { byActor, video })
 }
 
-async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) {
+async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
   if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
 
   const byActor = videoPlaylist.OwnerAccount.Actor
@@ -113,7 +121,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate {
+function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify(
@@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud
       type: 'Update' as 'Update',
       id: url,
       actor: byActor.url,
-      object: audiencify(object, audience
-      )
+      object: audiencify(object, audience)
     },
     audience
   )

+ 3 - 3
server/lib/activitypub/send/send-view.ts

@@ -1,13 +1,13 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub'
 import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
 import { getVideoLikeActivityPubUrl } from '../url'
 import { sendVideoRelatedActivity } from './utils'
 import { audiencify, getAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
+import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models'
 
-async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) {
   logger.info('Creating job to send view of %s.', video.url)
 
   const activityBuilder = (audience: ActivityAudience) => {
@@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction)
   return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
 }
 
-function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView {
+function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
   if (!audience) audience = getAudience(byActor)
 
   return audiencify(

+ 17 - 18
server/lib/activitypub/send/utils.ts

@@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { JobQueue } from '../../job-queue'
-import { VideoModel } from '../../../models/video/video'
 import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
 import { getServerActor } from '../../../helpers/utils'
 import { afterCommitIfTransaction } from '../../../helpers/database-utils'
-import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models'
+import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
 
 async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
-  byActor: ActorModelOnly,
-  video: VideoModel,
+  byActor: MActorLight,
+  video: MVideoAccountLight,
   transaction?: Transaction
 }) {
   const { byActor, video, transaction } = options
@@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
 async function forwardVideoRelatedActivity (
   activity: Activity,
   t: Transaction,
-  followersException: ActorFollowerException[] = [],
-  video: VideoModel
+  followersException: MActorFollowerException[] = [],
+  video: MVideo
 ) {
   // Mastodon does not add our announces in audience, so we forward to them manually
   const additionalActors = await getActorsInvolvedInVideo(video, t)
@@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity (
 async function forwardActivity (
   activity: Activity,
   t: Transaction,
-  followersException: ActorFollowerException[] = [],
+  followersException: MActorFollowerException[] = [],
   additionalFollowerUrls: string[] = []
 ) {
   logger.info('Forwarding activity %s.', activity.id)
@@ -88,10 +87,10 @@ async function forwardActivity (
 
 async function broadcastToFollowers (
   data: any,
-  byActor: ActorModelId,
-  toFollowersOf: ActorModelId[],
+  byActor: MActorId,
+  toFollowersOf: MActorId[],
   t: Transaction,
-  actorsException: ActorFollowerException[] = []
+  actorsException: MActorFollowerException[] = []
 ) {
   const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
 
@@ -100,16 +99,16 @@ async function broadcastToFollowers (
 
 async function broadcastToActors (
   data: any,
-  byActor: ActorModelId,
-  toActors: ActorModelOnly[],
+  byActor: MActorId,
+  toActors: MActor[],
   t?: Transaction,
-  actorsException: ActorFollowerException[] = []
+  actorsException: MActorFollowerException[] = []
 ) {
   const uris = await computeUris(toActors, actorsException)
   return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
 }
 
-function broadcastTo (uris: string[], data: any, byActor: ActorModelId) {
+function broadcastTo (uris: string[], data: any, byActor: MActorId) {
   if (uris.length === 0) return undefined
 
   logger.debug('Creating broadcast job.', { uris })
@@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) {
   return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
 }
 
-function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) {
+function unicastTo (data: any, byActor: MActorId, toActorUrl: string) {
   logger.debug('Creating unicast job.', { uri: toActorUrl })
 
   const payload = {
@@ -148,7 +147,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) {
+async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
   const toActorFollowerIds = toFollowersOf.map(a => a.id)
 
   const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
@@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti
   return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
 }
 
-async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) {
+async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
   const serverActor = await getServerActor()
   const targetUrls = toActors
     .filter(a => a.id !== serverActor.id) // Don't send to ourselves
@@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo
               .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
 }
 
-async function buildSharedInboxesException (actorsException: ActorFollowerException[]) {
+async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
   const serverActor = await getServerActor()
 
   return actorsException

+ 12 - 9
server/lib/activitypub/share.ts

@@ -1,19 +1,18 @@
 import { Transaction } from 'sequelize'
 import { VideoPrivacy } from '../../../shared/models/videos'
 import { getServerActor } from '../../helpers/utils'
-import { VideoModel } from '../../models/video/video'
 import { VideoShareModel } from '../../models/video/video-share'
 import { sendUndoAnnounce, sendVideoAnnounce } from './send'
 import { getVideoAnnounceActivityPubUrl } from './url'
-import { VideoChannelModel } from '../../models/video/video-channel'
 import * as Bluebird from 'bluebird'
 import { doRequest } from '../../helpers/requests'
 import { getOrCreateActorAndServerAndModel } from './actor'
 import { logger } from '../../helpers/logger'
 import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
 import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
+import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video'
 
-async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
+async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
   if (video.privacy === VideoPrivacy.PRIVATE) return undefined
 
   return Promise.all([
@@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction)
   ])
 }
 
-async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+async function changeVideoChannelShare (
+  video: MVideoAccountLight,
+  oldVideoChannel: MChannelActorLight,
+  t: Transaction
+) {
   logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
 
   await undoShareByVideoChannel(video, oldVideoChannel, t)
@@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide
   await shareByVideoChannel(video, t)
 }
 
-async function addVideoShares (shareUrls: string[], instance: VideoModel) {
+async function addVideoShares (shareUrls: string[], video: MVideoId) {
   await Bluebird.map(shareUrls, async shareUrl => {
     try {
       // Fetch url
@@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
 
       const entry = {
         actorId: actor.id,
-        videoId: instance.id,
+        videoId: video.id,
         url: shareUrl
       }
 
@@ -69,7 +72,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function shareByServer (video: VideoModel, t: Transaction) {
+async function shareByServer (video: MVideo, t: Transaction) {
   const serverActor = await getServerActor()
 
   const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
@@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) {
   return sendVideoAnnounce(serverActor, serverShare, video, t)
 }
 
-async function shareByVideoChannel (video: VideoModel, t: Transaction) {
+async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
   const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
   const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
     defaults: {
@@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) {
   return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
 }
 
-async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
   // Load old share
   const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
   if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id)

+ 33 - 27
server/lib/activitypub/url.ts

@@ -1,36 +1,42 @@
 import { WEBSERVER } from '../../initializers/constants'
-import { VideoModel } from '../../models/video/video'
-import { VideoAbuseModel } from '../../models/video/video-abuse'
-import { VideoCommentModel } from '../../models/video/video-comment'
-import { VideoFileModel } from '../../models/video/video-file'
-import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
-import { VideoPlaylistModel } from '../../models/video/video-playlist'
-import { ActorModelOnly, ActorModelUrl } from '../../typings/models'
-import { ActorFollowModelLight } from '../../typings/models/actor-follow'
-
-function getVideoActivityPubUrl (video: VideoModel) {
+import {
+  MActor,
+  MActorFollowActors,
+  MActorId,
+  MActorUrl,
+  MCommentId,
+  MVideoAbuseId,
+  MVideoId,
+  MVideoUrl,
+  MVideoUUID
+} from '../../typings/models'
+import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist'
+import { MVideoFileVideoUUID } from '../../typings/models/video/video-file'
+import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist'
+
+function getVideoActivityPubUrl (video: MVideoUUID) {
   return WEBSERVER.URL + '/videos/watch/' + video.uuid
 }
 
-function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) {
+function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) {
   return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid
 }
 
-function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) {
   return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid
 }
 
-function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) {
+function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
   const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : ''
 
   return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
 }
 
-function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) {
+function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) {
   return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}`
 }
 
-function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) {
+function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) {
   return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
 }
 
@@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) {
   return WEBSERVER.URL + '/accounts/' + accountName
 }
 
-function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
+function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) {
   return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
 }
 
-function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) {
+function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
   return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString()
 }
 
-function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) {
+function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
   return byActor.url + '/likes/' + video.id
 }
 
-function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) {
+function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
   return byActor.url + '/dislikes/' + video.id
 }
 
-function getVideoSharesActivityPubUrl (video: VideoModel) {
+function getVideoSharesActivityPubUrl (video: MVideoUrl) {
   return video.url + '/announces'
 }
 
-function getVideoCommentsActivityPubUrl (video: VideoModel) {
+function getVideoCommentsActivityPubUrl (video: MVideoUrl) {
   return video.url + '/comments'
 }
 
-function getVideoLikesActivityPubUrl (video: VideoModel) {
+function getVideoLikesActivityPubUrl (video: MVideoUrl) {
   return video.url + '/likes'
 }
 
-function getVideoDislikesActivityPubUrl (video: VideoModel) {
+function getVideoDislikesActivityPubUrl (video: MVideoUrl) {
   return video.url + '/dislikes'
 }
 
-function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) {
+function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) {
   return follower.url + '/follows/' + following.id
 }
 
-function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) {
+function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) {
   const follower = actorFollow.ActorFollower
   const me = actorFollow.ActorFollowing
 
   return follower.url + '/accepts/follows/' + me.id
 }
 
-function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) {
+function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) {
   return follower.url + '/rejects/follows/' + following.id
 }
 
-function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) {
+function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) {
   return video.url + '/announces/' + byActor.id
 }
 

+ 8 - 8
server/lib/activitypub/video-comments.ts

@@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat
 import { logger } from '../../helpers/logger'
 import { doRequest } from '../../helpers/requests'
 import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { VideoModel } from '../../models/video/video'
 import { VideoCommentModel } from '../../models/video/video-comment'
 import { getOrCreateActorAndServerAndModel } from './actor'
 import { getOrCreateVideoAndAccountAndChannel } from './videos'
 import * as Bluebird from 'bluebird'
 import { checkUrlsSameHost } from '../../helpers/activitypub'
+import { MCommentOwner, MCommentOwnerVideo, MVideoAccountAllFiles } from '../../typings/models/video'
 
 type ResolveThreadParams = {
   url: string,
-  comments?: VideoCommentModel[],
+  comments?: MCommentOwner[],
   isVideo?: boolean,
   commentCreated?: boolean
 }
-type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }>
+type ResolveThreadResult = Promise<{ video: MVideoAccountAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
 
 async function addVideoComments (commentUrls: string[]) {
   return Bluebird.map(commentUrls, commentUrl => {
@@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
   const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false }
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
 
-  let resultComment: VideoCommentModel
+  let resultComment: MCommentOwnerVideo
   if (comments.length !== 0) {
-    const firstReply = comments[ comments.length - 1 ]
+    const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo
     firstReply.inReplyToCommentId = null
     firstReply.originCommentId = null
     firstReply.videoId = video.id
@@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
     comments[comments.length - 1] = await firstReply.save()
 
     for (let i = comments.length - 2; i >= 0; i--) {
-      const comment = comments[ i ]
+      const comment = comments[ i ] as MCommentOwnerVideo
       comment.originCommentId = firstReply.id
       comment.inReplyToCommentId = comments[ i + 1 ].id
       comment.videoId = video.id
@@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
       comments[i] = await comment.save()
     }
 
-    resultComment = comments[0]
+    resultComment = comments[0] as MCommentOwnerVideo
   }
 
   return { video, comment: resultComment, commentCreated }
@@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) {
     originCommentId: null,
     createdAt: new Date(body.published),
     updatedAt: new Date(body.updated)
-  })
+  }) as MCommentOwner
   comment.Account = actor.Account
 
   return resolveThread({

+ 13 - 11
server/lib/activitypub/video-rates.ts

@@ -1,6 +1,4 @@
 import { Transaction } from 'sequelize'
-import { AccountModel } from '../../models/account/account'
-import { VideoModel } from '../../models/video/video'
 import { sendLike, sendUndoDislike, sendUndoLike } from './send'
 import { VideoRateType } from '../../../shared/models/videos'
 import * as Bluebird from 'bluebird'
@@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger'
 import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
 import { doRequest } from '../../helpers/requests'
 import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
-import { ActorModel } from '../../models/activitypub/actor'
 import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
 import { sendDislike } from './send/send-dislike'
+import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models'
 
-async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) {
+async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) {
   let rateCounts = 0
 
   await Bluebird.map(ratesUrl, async rateUrl => {
@@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa
   return
 }
 
-async function sendVideoRateChange (account: AccountModel,
-                              video: VideoModel,
-                              likes: number,
-                              dislikes: number,
-                              t: Transaction) {
+async function sendVideoRateChange (
+  account: MAccountActor,
+  video: MVideoAccountLight,
+  likes: number,
+  dislikes: number,
+  t: Transaction
+) {
   const actor = account.Actor
 
   // Keep the order: first we undo and then we create
@@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel,
   if (dislikes > 0) await sendDislike(actor, video, t)
 }
 
-function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) {
-  return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video)
+function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) {
+  return rateType === 'like'
+    ? getVideoLikeActivityPubUrl(actor, video)
+    : getVideoDislikeActivityPubUrl(actor, video)
 }
 
 export {

+ 85 - 58
server/lib/activitypub/videos.ts

@@ -24,7 +24,6 @@ import {
   REMOTE_SCHEME,
   STATIC_PATHS
 } from '../../initializers/constants'
-import { ActorModel } from '../../models/activitypub/actor'
 import { TagModel } from '../../models/video/tag'
 import { VideoModel } from '../../models/video/video'
 import { VideoFileModel } from '../../models/video/video-file'
@@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue'
 import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
 import { createRates } from './video-rates'
 import { addVideoShares, shareVideoByServerAndChannel } from './share'
-import { AccountModel } from '../../models/account/account'
 import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
 import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
 import { Notifier } from '../notifier'
@@ -49,15 +47,33 @@ import { VideoShareModel } from '../../models/video/video-share'
 import { VideoCommentModel } from '../../models/video/video-comment'
 import { sequelizeTypescript } from '../../initializers/database'
 import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
-import { ThumbnailModel } from '../../models/video/thumbnail'
 import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
 import { join } from 'path'
 import { FilteredModelAttributes } from '../../typings/sequelize'
 import { autoBlacklistVideoIfNeeded } from '../video-blacklist'
 import { ActorFollowScoreCache } from '../files-cache'
-import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models'
+import {
+  MAccountActor,
+  MChannelAccountLight,
+  MChannelDefault,
+  MChannelId,
+  MVideo,
+  MVideoAccountAllFiles,
+  MVideoAccountLight,
+  MVideoAP,
+  MVideoAPWithoutCaption,
+  MVideoFile,
+  MVideoFullLight,
+  MVideoId,
+  MVideoTag,
+  MVideoThumbnail,
+  MVideoWithAllFiles
+} from '../../typings/models'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
+
+async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
+  const video = videoArg as MVideoAP
 
-async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
   if (
     // Check this is not a blacklisted video, or unfederated blacklisted video
     (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
@@ -102,7 +118,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request.
   return { response, videoObject: body }
 }
 
-async function fetchRemoteVideoDescription (video: VideoModel) {
+async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
   const host = video.VideoChannel.Account.Actor.Server.host
   const path = video.getDescriptionAPIPath()
   const options = {
@@ -114,14 +130,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) {
   return body.description ? body.description : ''
 }
 
-function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) {
+function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
   const url = buildRemoteBaseUrl(video, path)
 
   // We need to provide a callback, if no we could have an uncaught exception
   return doRequestAndSaveToFile({ uri: url }, destPath)
 }
 
-function buildRemoteBaseUrl (video: VideoModel, path: string) {
+function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
   const host = video.VideoChannel.Account.Actor.Server.host
 
   return REMOTE_SCHEME.HTTP + '://' + host + path
@@ -146,7 +162,7 @@ type SyncParam = {
   thumbnail: boolean
   refreshVideo?: boolean
 }
-async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
+async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
   logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid)
 
   const jobPayloads: ActivitypubHttpFetcherPayload[] = []
@@ -194,12 +210,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid
   await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }))
 }
 
+function getOrCreateVideoAndAccountAndChannel (options: {
+  videoObject: { id: string } | string,
+  syncParam?: SyncParam,
+  fetchType?: 'all',
+  allowRefresh?: boolean
+}): Promise<{ video: MVideoAccountAllFiles, created: boolean, autoBlacklisted?: boolean }>
+function getOrCreateVideoAndAccountAndChannel (options: {
+  videoObject: { id: string } | string,
+  syncParam?: SyncParam,
+  fetchType?: VideoFetchByUrlType,
+  allowRefresh?: boolean
+}): Promise<{ video: MVideoAccountAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }>
 async function getOrCreateVideoAndAccountAndChannel (options: {
   videoObject: { id: string } | string,
   syncParam?: SyncParam,
   fetchType?: VideoFetchByUrlType,
   allowRefresh?: boolean // true by default
-}) {
+}): Promise<{ video: MVideoAccountAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> {
   // Default params
   const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
   const fetchType = options.fetchType || 'all'
@@ -227,8 +255,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
   const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl)
   if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
 
-  const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
-  const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail)
+  const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
+  const videoChannel = actor.VideoChannel
+  const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
 
   await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
 
@@ -236,22 +265,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
 }
 
 async function updateVideoFromAP (options: {
-  video: VideoModel,
+  video: MVideoAccountAllFiles,
   videoObject: VideoTorrentObject,
-  account: AccountModelIdActor,
-  channel: VideoChannelModelIdActor,
+  account: MAccountActor,
+  channel: MChannelDefault,
   overrideTo?: string[]
 }) {
   const { video, videoObject, account, channel, overrideTo } = options
 
-  logger.debug('Updating remote video "%s".', options.videoObject.uuid)
+  logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel })
 
   let videoFieldsSave: any
   const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE
   const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED
 
   try {
-    let thumbnailModel: ThumbnailModel
+    let thumbnailModel: MThumbnail
 
     try {
       thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
@@ -259,7 +288,7 @@ async function updateVideoFromAP (options: {
       logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
     }
 
-    await sequelizeTypescript.transaction(async t => {
+    const videoUpdated = await sequelizeTypescript.transaction(async t => {
       const sequelizeOptions = { transaction: t }
 
       videoFieldsSave = video.toJSON()
@@ -293,21 +322,21 @@ async function updateVideoFromAP (options: {
       video.channelId = videoData.channelId
       video.views = videoData.views
 
-      await video.save(sequelizeOptions)
+      const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight
 
-      if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t)
+      if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
 
       // FIXME: use icon URL instead
-      const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename))
+      const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
       const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
-      await video.addAndSaveThumbnail(previewModel, t)
+      await videoUpdated.addAndSaveThumbnail(previewModel, t)
 
       {
-        const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject)
+        const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject)
         const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
 
         // Remove video files that do not exist anymore
-        const destroyTasks = video.VideoFiles
+        const destroyTasks = videoUpdated.VideoFiles
                                   .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f)))
                                   .map(f => f.destroy(sequelizeOptions))
         await Promise.all(destroyTasks)
@@ -318,15 +347,15 @@ async function updateVideoFromAP (options: {
             .then(([ file ]) => file)
         })
 
-        video.VideoFiles = await Promise.all(upsertTasks)
+        videoUpdated.VideoFiles = await Promise.all(upsertTasks)
       }
 
       {
-        const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles)
+        const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles)
         const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
 
         // Remove video files that do not exist anymore
-        const destroyTasks = video.VideoStreamingPlaylists
+        const destroyTasks = videoUpdated.VideoStreamingPlaylists
                                   .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f)))
                                   .map(f => f.destroy(sequelizeOptions))
         await Promise.all(destroyTasks)
@@ -337,38 +366,42 @@ async function updateVideoFromAP (options: {
                                .then(([ streamingPlaylist ]) => streamingPlaylist)
         })
 
-        video.VideoStreamingPlaylists = await Promise.all(upsertTasks)
+        videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks)
       }
 
       {
         // Update Tags
         const tags = videoObject.tag.map(tag => tag.name)
         const tagInstances = await TagModel.findOrCreateTags(tags, t)
-        await video.$set('Tags', tagInstances, sequelizeOptions)
+        await videoUpdated.$set('Tags', tagInstances, sequelizeOptions)
       }
 
       {
         // Update captions
-        await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t)
+        await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
 
         const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
-          return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t)
+          return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t)
         })
-        video.VideoCaptions = await Promise.all(videoCaptionsPromises)
+        await Promise.all(videoCaptionsPromises)
       }
+
+      return videoUpdated
     })
 
     await autoBlacklistVideoIfNeeded({
-      video,
+      video: videoUpdated,
       user: undefined,
       isRemote: true,
       isNew: false,
       transaction: undefined
     })
 
-    if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users?
+    if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users?
 
     logger.info('Remote video with uuid %s updated', videoObject.uuid)
+
+    return videoUpdated
   } catch (err) {
     if (video !== undefined && videoFieldsSave !== undefined) {
       resetSequelizeInstance(video, videoFieldsSave)
@@ -381,15 +414,15 @@ async function updateVideoFromAP (options: {
 }
 
 async function refreshVideoIfNeeded (options: {
-  video: VideoModel,
+  video: MVideoThumbnail,
   fetchedType: VideoFetchByUrlType,
   syncParam: SyncParam
-}): Promise<VideoModel> {
+}): Promise<MVideoThumbnail> {
   if (!options.video.isOutdated()) return options.video
 
   // We need more attributes if the argument video was fetched with not enough joints
   const video = options.fetchedType === 'all'
-    ? options.video
+    ? options.video as MVideoAccountAllFiles
     : await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
 
   try {
@@ -410,12 +443,11 @@ async function refreshVideoIfNeeded (options: {
     }
 
     const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
-    const account = await AccountModel.load(channelActor.VideoChannel.accountId)
 
     const updateOptions = {
       video,
       videoObject,
-      account,
+      account: channelActor.VideoChannel.Account,
       channel: channelActor.VideoChannel
     }
     await retryTransactionWrapper(updateVideoFromAP, updateOptions)
@@ -467,15 +499,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS
   return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json'
 }
 
-async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) {
+async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
   logger.debug('Adding remote video %s.', videoObject.id)
 
-  const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
-  const video = VideoModel.build(videoData)
+  const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
+  const video = VideoModel.build(videoData) as MVideoThumbnail
 
   const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
 
-  let thumbnailModel: ThumbnailModel
+  let thumbnailModel: MThumbnail
   if (waitThumbnail === true) {
     thumbnailModel = await promiseThumbnail
   }
@@ -483,8 +515,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
   const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
     const sequelizeOptions = { transaction: t }
 
-    const videoCreated = await video.save(sequelizeOptions)
-    videoCreated.VideoChannel = channelActor.VideoChannel
+    const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
+    videoCreated.VideoChannel = channel
 
     if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
 
@@ -517,15 +549,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
     const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
       return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t)
     })
-    const captions = await Promise.all(videoCaptionsPromises)
+    await Promise.all(videoCaptionsPromises)
 
-    video.VideoFiles = videoFiles
-    video.VideoStreamingPlaylists = streamingPlaylists
-    video.Tags = tagInstances
-    video.VideoCaptions = captions
+    videoCreated.VideoFiles = videoFiles
+    videoCreated.VideoStreamingPlaylists = streamingPlaylists
+    videoCreated.Tags = tagInstances
 
     const autoBlacklisted = await autoBlacklistVideoIfNeeded({
-      video,
+      video: videoCreated,
       user: undefined,
       isRemote: true,
       isNew: true,
@@ -548,11 +579,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
   return { autoBlacklisted, videoCreated }
 }
 
-async function videoActivityObjectToDBAttributes (
-  videoChannel: VideoChannelModelId,
-  videoObject: VideoTorrentObject,
-  to: string[] = []
-) {
+async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
   const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
   const duration = videoObject.duration.replace(/[^\d]+/, '')
 
@@ -603,7 +630,7 @@ async function videoActivityObjectToDBAttributes (
   }
 }
 
-function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) {
+function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) {
   const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[]
 
   if (fileUrls.length === 0) {
@@ -641,7 +668,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
   return attributes
 }
 
-function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) {
+function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) {
   const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
   if (playlistUrls.length === 0) return []
 

+ 5 - 3
server/lib/avatar.ts

@@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send'
 import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
 import { updateActorAvatarInstance } from './activitypub'
 import { processImage } from '../helpers/image-utils'
-import { AccountModel } from '../models/account/account'
-import { VideoChannelModel } from '../models/video/video-channel'
 import { extname, join } from 'path'
 import { retryTransactionWrapper } from '../helpers/database-utils'
 import * as uuidv4 from 'uuid/v4'
@@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database'
 import * as LRUCache from 'lru-cache'
 import { queue } from 'async'
 import { downloadImage } from '../helpers/requests'
+import { MAccountActorDefault, MChannelActorDefault } from '../typings/models'
 
-async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) {
+async function updateActorAvatarFile (
+  avatarPhysicalFile: Express.Multer.File,
+  accountOrChannel: MAccountActorDefault | MChannelActorDefault
+) {
   const extension = extname(avatarPhysicalFile.filename)
   const avatarName = uuidv4() + extension
   const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)

+ 3 - 2
server/lib/blocklist.ts

@@ -1,6 +1,7 @@
 import { sequelizeTypescript } from '../initializers'
 import { AccountBlocklistModel } from '../models/account/account-blocklist'
 import { ServerBlocklistModel } from '../models/server/server-blocklist'
+import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models'
 
 function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
   return sequelizeTypescript.transaction(async t => {
@@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) {
   })
 }
 
-function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) {
+function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) {
   return sequelizeTypescript.transaction(async t => {
     return accountBlock.destroy({ transaction: t })
   })
 }
 
-function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) {
+function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
   return sequelizeTypescript.transaction(async t => {
     return serverBlock.destroy({ transaction: t })
   })

+ 4 - 3
server/lib/client-html.ts

@@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel'
 import * as Bluebird from 'bluebird'
 import { CONFIG } from '../initializers/config'
 import { logger } from '../helpers/logger'
+import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
 
 export class ClientHtml {
 
@@ -65,7 +66,7 @@ export class ClientHtml {
   }
 
   private static async getAccountOrChannelHTMLPage (
-    loader: () => Bluebird<AccountModel | VideoChannelModel>,
+    loader: () => Bluebird<MAccountActor | MChannelActor>,
     req: express.Request,
     res: express.Response
   ) {
@@ -157,7 +158,7 @@ export class ClientHtml {
     return htmlStringPage.replace('</head>', linkTag + '</head>')
   }
 
-  private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
+  private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) {
     const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath()
     const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
 
@@ -236,7 +237,7 @@ export class ClientHtml {
     return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
   }
 
-  private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) {
+  private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) {
     // SEO, use origin account or channel URL
     const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
 

+ 17 - 19
server/lib/emailer.ts

@@ -3,16 +3,14 @@ import { isTestInstance } from '../helpers/core-utils'
 import { bunyanLogger, logger } from '../helpers/logger'
 import { CONFIG } from '../initializers/config'
 import { UserModel } from '../models/account/user'
-import { VideoModel } from '../models/video/video'
 import { JobQueue } from './job-queue'
 import { EmailPayload } from './job-queue/handlers/email'
 import { readFileSync } from 'fs-extra'
-import { VideoCommentModel } from '../models/video/video-comment'
-import { VideoAbuseModel } from '../models/video/video-abuse'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
-import { VideoImportModel } from '../models/video/video-import'
-import { ActorFollowModel } from '../models/activitypub/actor-follow'
 import { WEBSERVER } from '../initializers/constants'
+import { MCommentOwnerVideo, MVideo, MVideoAbuseVideo, MVideoAccountLight, MVideoBlacklistVideo } from '../typings/models/video'
+import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models'
+import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
 
 type SendEmailOptions = {
   to: string[]
@@ -90,7 +88,7 @@ class Emailer {
     }
   }
 
-  addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
+  addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
     const channelName = video.VideoChannel.getDisplayName()
     const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
 
@@ -111,7 +109,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') {
+  addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') {
     const followerName = actorFollow.ActorFollower.Account.getDisplayName()
     const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
 
@@ -130,7 +128,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) {
+  addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
     const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
 
     const text = `Hi dear admin,\n\n` +
@@ -148,7 +146,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  myVideoPublishedNotification (to: string[], video: VideoModel) {
+  myVideoPublishedNotification (to: string[], video: MVideo) {
     const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
 
     const text = `Hi dear user,\n\n` +
@@ -168,7 +166,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
+  myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
     const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
 
     const text = `Hi dear user,\n\n` +
@@ -188,7 +186,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
+  myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
     const importUrl = WEBSERVER.URL + '/my-account/video-imports'
 
     const text = `Hi dear user,\n\n` +
@@ -208,7 +206,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
+  addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
     const accountName = comment.Account.getDisplayName()
     const video = comment.Video
     const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -230,7 +228,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) {
+  addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
     const accountName = comment.Account.getDisplayName()
     const video = comment.Video
     const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
@@ -252,7 +250,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
+  addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
     const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
 
     const text = `Hi,\n\n` +
@@ -269,7 +267,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) {
+  addVideoAutoBlacklistModeratorsNotification (to: string[], video: MVideo) {
     const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
     const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
 
@@ -292,7 +290,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addNewUserRegistrationNotification (to: string[], user: UserModel) {
+  addNewUserRegistrationNotification (to: string[], user: MUser) {
     const text = `Hi,\n\n` +
       `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
       `Cheers,\n` +
@@ -307,7 +305,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
+  addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
     const videoName = videoBlacklist.Video.name
     const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
 
@@ -329,7 +327,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVideoUnblacklistNotification (to: string[], video: VideoModel) {
+  addVideoUnblacklistNotification (to: string[], video: MVideo) {
     const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
 
     const text = 'Hi,\n\n' +
@@ -381,7 +379,7 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
+  addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
     const reasonString = reason ? ` for the following reason: ${reason}` : ''
     const blockedWord = blocked ? 'blocked' : 'unblocked'
     const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`

+ 3 - 3
server/lib/hls.ts

@@ -1,4 +1,3 @@
-import { VideoModel } from '../models/video/video'
 import { basename, dirname, join } from 'path'
 import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants'
 import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
@@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash'
 import { VideoFileModel } from '../models/video/video-file'
 import { CONFIG } from '../initializers/config'
 import { sequelizeTypescript } from '../initializers/database'
+import { MVideoWithFile } from '@server/typings/models'
 
 async function updateStreamingPlaylistsInfohashesIfNeeded () {
   const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
@@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () {
   }
 }
 
-async function updateMasterHLSPlaylist (video: VideoModel) {
+async function updateMasterHLSPlaylist (video: MVideoWithFile) {
   const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
   const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
   const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
@@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) {
   await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
 }
 
-async function updateSha256Segments (video: VideoModel) {
+async function updateSha256Segments (video: MVideoWithFile) {
   const json: { [filename: string]: { [range: string]: string } } = {}
 
   const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)

+ 5 - 4
server/lib/job-queue/handlers/activitypub-follow.ts

@@ -10,6 +10,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { Notifier } from '../../notifier'
 import { sequelizeTypescript } from '../../../initializers/database'
+import { MActorFollowFull, MActorFull } from '../../../typings/models'
 
 export type ActivitypubFollowPayload = {
   followerActorId: number
@@ -23,13 +24,13 @@ async function processActivityPubFollow (job: Bull.Job) {
 
   logger.info('Processing ActivityPub follow in job %d.', job.id)
 
-  let targetActor: ActorModel
+  let targetActor: MActorFull
   if (!host || host === WEBSERVER.HOST) {
     targetActor = await ActorModel.loadLocalByName(payload.name)
   } else {
     const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP)
     const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost)
-    targetActor = await getOrCreateActorAndServerAndModel(actorUrl)
+    targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all')
   }
 
   const fromActor = await ActorModel.load(payload.followerActorId)
@@ -44,7 +45,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function follow (fromActor: ActorModel, targetActor: ActorModel) {
+async function follow (fromActor: MActorFull, targetActor: MActorFull) {
   if (fromActor.id === targetActor.id) {
     throw new Error('Follower is the same than target actor.')
   }
@@ -53,7 +54,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) {
   const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending'
 
   const actorFollow = await sequelizeTypescript.transaction(async t => {
-    const [ actorFollow ] = await ActorFollowModel.findOrCreate({
+    const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowFull>({
       where: {
         actorId: fromActor.id,
         targetActorId: targetActor.id

+ 3 - 2
server/lib/job-queue/handlers/activitypub-http-fetcher.ts

@@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account'
 import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { VideoCommentModel } from '../../../models/video/video-comment'
+import { MAccountDefault, MVideoFullLight } from '../../../typings/models'
 
 type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
 
@@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) {
 
   const payload = job.data as ActivitypubHttpFetcherPayload
 
-  let video: VideoModel
+  let video: MVideoFullLight
   if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
 
-  let account: AccountModel
+  let account: MAccountDefault
   if (payload.accountId) account = await AccountModel.load(payload.accountId)
 
   const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {

+ 3 - 1
server/lib/job-queue/handlers/utils/activitypub-http-utils.ts

@@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils'
 import { ActorModel } from '../../../../models/activitypub/actor'
 import { sha256 } from '../../../../helpers/core-utils'
 import { HTTP_SIGNATURE } from '../../../../initializers/constants'
+import { MActor } from '../../../../typings/models'
 
 type Payload = { body: any, signatureActorId?: number }
 
@@ -19,7 +20,8 @@ async function computeBody (payload: Payload) {
 }
 
 async function buildSignedRequestOptions (payload: Payload) {
-  let actor: ActorModel | null
+  let actor: MActor | null
+
   if (payload.signatureActorId) {
     actor = await ActorModel.load(payload.signatureActorId)
     if (!actor) throw new Error('Unknown signature actor id.')

+ 6 - 5
server/lib/job-queue/handlers/video-file-import.ts

@@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg
 import { copy, stat } from 'fs-extra'
 import { VideoFileModel } from '../../../models/video/video-file'
 import { extname } from 'path'
+import { MVideoFile, MVideoWithFile } from '@server/typings/models'
 
 export type VideoFileImportPayload = {
   videoUUID: string,
@@ -37,7 +38,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function updateVideoFile (video: VideoModel, inputFilePath: string) {
+async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) {
   const { videoFileResolution } = await getVideoFileResolution(inputFilePath)
   const { size } = await stat(inputFilePath)
   const fps = await getVideoFileFPS(inputFilePath)
@@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
     size,
     fps,
     videoId: video.id
-  })
+  }) as MVideoFile
 
   const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution)
 
@@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) {
     video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
 
     // Update the database
-    currentVideoFile.set('extname', updatedVideoFile.extname)
-    currentVideoFile.set('size', updatedVideoFile.size)
-    currentVideoFile.set('fps', updatedVideoFile.fps)
+    currentVideoFile.extname = updatedVideoFile.extname
+    currentVideoFile.size = updatedVideoFile.size
+    currentVideoFile.fps = updatedVideoFile.fps
 
     updatedVideoFile = currentVideoFile
   }

+ 21 - 17
server/lib/job-queue/handlers/video-import.ts

@@ -17,9 +17,10 @@ import { move, remove, stat } from 'fs-extra'
 import { Notifier } from '../../notifier'
 import { CONFIG } from '../../../initializers/config'
 import { sequelizeTypescript } from '../../../initializers/database'
-import { ThumbnailModel } from '../../../models/video/thumbnail'
 import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
+import { MThumbnail } from '../../../typings/models/video/thumbnail'
+import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import'
 
 type VideoImportYoutubeDLPayload = {
   type: 'youtube-dl'
@@ -110,11 +111,13 @@ type ProcessFileOptions = {
   generateThumbnail: boolean
   generatePreview: boolean
 }
-async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) {
+async function processFile (downloader: () => Promise<string>, videoImportArg: MVideoImportDefault, options: ProcessFileOptions) {
   let tempVideoPath: string
   let videoDestFile: string
   let videoFile: VideoFileModel
 
+  const videoImport = videoImportArg as MVideoImportDefaultFiles
+
   try {
     // Download video from youtubeDL
     tempVideoPath = await downloader()
@@ -148,7 +151,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     tempVideoPath = null // This path is not used anymore
 
     // Process thumbnail
-    let thumbnailModel: ThumbnailModel
+    let thumbnailModel: MThumbnail
     if (options.downloadThumbnail && options.thumbnailUrl) {
       thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE)
     } else if (options.generateThumbnail || options.downloadThumbnail) {
@@ -156,7 +159,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     }
 
     // Process preview
-    let previewModel: ThumbnailModel
+    let previewModel: MThumbnail
     if (options.downloadPreview && options.thumbnailUrl) {
       previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
     } else if (options.generatePreview || options.downloadPreview) {
@@ -166,14 +169,15 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     // Create torrent
     await videoImport.Video.createTorrentAndSetInfoHash(videoFile)
 
-    const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => {
+    const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => {
+      const videoImportToUpdate = videoImport as MVideoImportVideo
+
       // Refresh video
-      const video = await VideoModel.load(videoImport.videoId, t)
-      if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.')
-      videoImport.Video = video
+      const video = await VideoModel.load(videoImportToUpdate.videoId, t)
+      if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.')
 
       const videoFileCreated = await videoFile.save({ transaction: t })
-      video.VideoFiles = [ videoFileCreated ]
+      videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] })
 
       // Update video DB object
       video.duration = duration
@@ -188,25 +192,25 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
       await federateVideoIfNeeded(videoForFederation, true, t)
 
       // Update video import object
-      videoImport.state = VideoImportState.SUCCESS
-      const videoImportUpdated = await videoImport.save({ transaction: t })
+      videoImportToUpdate.state = VideoImportState.SUCCESS
+      const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo
+      videoImportUpdated.Video = video
 
       logger.info('Video %s imported.', video.uuid)
 
-      videoImportUpdated.Video = videoForFederation
-      return videoImportUpdated
+      return { videoImportUpdated, video: videoForFederation }
     })
 
     Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
 
-    if (videoImportUpdated.Video.isBlacklisted()) {
-      Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video)
+    if (video.isBlacklisted()) {
+      Notifier.Instance.notifyOnVideoAutoBlacklist(video)
     } else {
-      Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video)
+      Notifier.Instance.notifyOnNewVideoIfNeeded(video)
     }
 
     // Create transcoding jobs?
-    if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
+    if (video.state === VideoState.TO_TRANSCODE) {
       // Put uuid because we don't have id auto incremented for now
       const dataInput = {
         type: 'optimize' as 'optimize',

+ 4 - 3
server/lib/job-queue/handlers/video-transcoding.ts

@@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
 import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
 import { Notifier } from '../../notifier'
 import { CONFIG } from '../../../initializers/config'
+import { MVideoUUID, MVideoWithFile } from '@server/typings/models'
 
 interface BaseTranscodingPayload {
   videoUUID: string
@@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) {
   return video
 }
 
-async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
+async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) {
   if (video === undefined) return undefined
 
   await sequelizeTypescript.transaction(async t => {
@@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
   })
 }
 
-async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
+async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
   const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
     let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes
   await createHlsJobIfEnabled(payload)
 }
 
-async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) {
+async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) {
   if (videoArg === undefined) return undefined
 
   // Outside the transaction (IO on disk)

+ 54 - 47
server/lib/notifier.ts

@@ -8,13 +8,23 @@ import { UserModel } from '../models/account/user'
 import { PeerTubeSocket } from './peertube-socket'
 import { CONFIG } from '../initializers/config'
 import { VideoPrivacy, VideoState } from '../../shared/models/videos'
-import { VideoAbuseModel } from '../models/video/video-abuse'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
 import * as Bluebird from 'bluebird'
 import { VideoImportModel } from '../models/video/video-import'
 import { AccountBlocklistModel } from '../models/account/account-blocklist'
+import {
+  MCommentOwnerVideo,
+  MVideo,
+  MVideoAbuseVideo,
+  MVideoAccountLight,
+  MVideoBlacklistVideo,
+  MVideoFullLight
+} from '../typings/models/video'
+import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user'
+import { MActorFollowActors, MActorFollowFull } from '../typings/models'
 import { ActorFollowModel } from '../models/activitypub/actor-follow'
-import { AccountModel } from '../models/account/account'
+import { MVideoImportVideo } from '@server/typings/models/video/video-import'
+import { AccountModel } from '@server/models/account/account'
 
 class Notifier {
 
@@ -22,7 +32,7 @@ class Notifier {
 
   private constructor () {}
 
-  notifyOnNewVideoIfNeeded (video: VideoModel): void {
+  notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
     // Only notify on public and published videos which are not blacklisted
     if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
 
@@ -30,7 +40,7 @@ class Notifier {
       .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
   }
 
-  notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void {
+  notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
     // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
     if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
 
@@ -38,7 +48,7 @@ class Notifier {
         .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
   }
 
-  notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void {
+  notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
     // don't notify if video is still blacklisted or waiting for transcoding
     if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
 
@@ -46,7 +56,7 @@ class Notifier {
         .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
   }
 
-  notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void {
+  notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
     // don't notify if video is still waiting for transcoding or scheduled update
     if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
 
@@ -54,7 +64,7 @@ class Notifier {
         .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
   }
 
-  notifyOnNewComment (comment: VideoCommentModel): void {
+  notifyOnNewComment (comment: MCommentOwnerVideo): void {
     this.notifyVideoOwnerOfNewComment(comment)
         .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
 
@@ -62,37 +72,37 @@ class Notifier {
         .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
   }
 
-  notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void {
+  notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
     this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
       .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
   }
 
-  notifyOnVideoAutoBlacklist (video: VideoModel): void {
+  notifyOnVideoAutoBlacklist (video: MVideo): void {
     this.notifyModeratorsOfVideoAutoBlacklist(video)
       .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
   }
 
-  notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
+  notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
     this.notifyVideoOwnerOfBlacklist(videoBlacklist)
       .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
   }
 
-  notifyOnVideoUnblacklist (video: VideoModel): void {
+  notifyOnVideoUnblacklist (video: MVideo): void {
     this.notifyVideoOwnerOfUnblacklist(video)
         .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
   }
 
-  notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
+  notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
     this.notifyOwnerVideoImportIsFinished(videoImport, success)
       .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
   }
 
-  notifyOnNewUserRegistration (user: UserModel): void {
+  notifyOnNewUserRegistration (user: MUserAccount): void {
     this.notifyModeratorsOfNewUserRegistration(user)
         .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
   }
 
-  notifyOfNewUserFollow (actorFollow: ActorFollowModel): void {
+  notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
     this.notifyUserOfNewActorFollow(actorFollow)
       .catch(err => {
         logger.error(
@@ -104,14 +114,14 @@ class Notifier {
       })
   }
 
-  notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void {
+  notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void {
     this.notifyAdminsOfNewInstanceFollow(actorFollow)
         .catch(err => {
           logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
         })
   }
 
-  private async notifySubscribersOfNewVideo (video: VideoModel) {
+  private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
     // List all followers that are users
     const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
 
@@ -127,7 +137,7 @@ class Notifier {
         userId: user.id,
         videoId: video.id
       })
-      notification.Video = video
+      notification.Video = video as VideoModel
 
       return notification
     }
@@ -139,7 +149,7 @@ class Notifier {
     return this.notify({ users, settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) {
+  private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
     if (comment.Video.isOwned() === false) return
 
     const user = await UserModel.loadByVideoId(comment.videoId)
@@ -162,7 +172,7 @@ class Notifier {
         userId: user.id,
         commentId: comment.id
       })
-      notification.Comment = comment
+      notification.Comment = comment as VideoCommentModel
 
       return notification
     }
@@ -174,7 +184,7 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyOfCommentMention (comment: VideoCommentModel) {
+  private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
     const extractedUsernames = comment.extractMentions()
     logger.debug(
       'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
@@ -209,7 +219,7 @@ class Notifier {
         userId: user.id,
         commentId: comment.id
       })
-      notification.Comment = comment
+      notification.Comment = comment as VideoCommentModel
 
       return notification
     }
@@ -221,7 +231,7 @@ class Notifier {
     return this.notify({ users, settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) {
+  private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) {
     if (actorFollow.ActorFollowing.isOwned() === false) return
 
     // Account follows one of our account?
@@ -236,9 +246,6 @@ class Notifier {
 
     if (!user) return
 
-    if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
-      actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
-    }
     const followerAccount = actorFollow.ActorFollower.Account
 
     const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
@@ -256,7 +263,7 @@ class Notifier {
         userId: user.id,
         actorFollowId: actorFollow.id
       })
-      notification.ActorFollow = actorFollow
+      notification.ActorFollow = actorFollow as ActorFollowModel
 
       return notification
     }
@@ -268,7 +275,7 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) {
+  private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) {
     const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
 
     logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
@@ -283,7 +290,7 @@ class Notifier {
         userId: user.id,
         actorFollowId: actorFollow.id
       })
-      notification.ActorFollow = actorFollow
+      notification.ActorFollow = actorFollow as ActorFollowModel
 
       return notification
     }
@@ -295,7 +302,7 @@ class Notifier {
     return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) {
+  private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
     const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
     if (moderators.length === 0) return
 
@@ -306,7 +313,7 @@ class Notifier {
     }
 
     async function notificationCreator (user: UserModel) {
-      const notification = await UserNotificationModel.create({
+      const notification: UserNotificationModelForApi = await UserNotificationModel.create({
         type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
         userId: user.id,
         videoAbuseId: videoAbuse.id
@@ -323,7 +330,7 @@ class Notifier {
     return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) {
+  private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) {
     const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
     if (moderators.length === 0) return
 
@@ -339,7 +346,7 @@ class Notifier {
         userId: user.id,
         videoId: video.id
       })
-      notification.Video = video
+      notification.Video = video as VideoModel
 
       return notification
     }
@@ -351,7 +358,7 @@ class Notifier {
     return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
+  private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
     const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
     if (!user) return
 
@@ -367,7 +374,7 @@ class Notifier {
         userId: user.id,
         videoBlacklistId: videoBlacklist.id
       })
-      notification.VideoBlacklist = videoBlacklist
+      notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel
 
       return notification
     }
@@ -379,7 +386,7 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyVideoOwnerOfUnblacklist (video: VideoModel) {
+  private async notifyVideoOwnerOfUnblacklist (video: MVideo) {
     const user = await UserModel.loadByVideoId(video.id)
     if (!user) return
 
@@ -395,7 +402,7 @@ class Notifier {
         userId: user.id,
         videoId: video.id
       })
-      notification.Video = video
+      notification.Video = video as VideoModel
 
       return notification
     }
@@ -407,7 +414,7 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
+  private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
     const user = await UserModel.loadByVideoId(video.id)
     if (!user) return
 
@@ -423,7 +430,7 @@ class Notifier {
         userId: user.id,
         videoId: video.id
       })
-      notification.Video = video
+      notification.Video = video as VideoModel
 
       return notification
     }
@@ -435,7 +442,7 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
+  private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
     const user = await UserModel.loadByVideoImportId(videoImport.id)
     if (!user) return
 
@@ -451,7 +458,7 @@ class Notifier {
         userId: user.id,
         videoImportId: videoImport.id
       })
-      notification.VideoImport = videoImport
+      notification.VideoImport = videoImport as VideoImportModel
 
       return notification
     }
@@ -465,13 +472,13 @@ class Notifier {
     return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
   }
 
-  private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) {
+  private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) {
     const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
     if (moderators.length === 0) return
 
     logger.info(
       'Notifying %s moderators of new user registration of %s.',
-      moderators.length, registeredUser.Account.Actor.preferredUsername
+      moderators.length, registeredUser.username
     )
 
     function settingGetter (user: UserModel) {
@@ -484,7 +491,7 @@ class Notifier {
         userId: user.id,
         accountId: registeredUser.Account.id
       })
-      notification.Account = registeredUser.Account
+      notification.Account = registeredUser.Account as AccountModel
 
       return notification
     }
@@ -497,10 +504,10 @@ class Notifier {
   }
 
   private async notify (options: {
-    users: UserModel[],
-    notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
+    users: MUserWithNotificationSetting[],
+    notificationCreator: (user: MUserWithNotificationSetting) => Promise<UserNotificationModelForApi>,
     emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
-    settingGetter: (user: UserModel) => UserNotificationSettingValue
+    settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue
   }) {
     const emails: string[] = []
 
@@ -521,7 +528,7 @@ class Notifier {
     }
   }
 
-  private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) {
+  private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
     if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
 
     return value & UserNotificationSettingValue.EMAIL

+ 2 - 1
server/lib/oauth-model.ts

@@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants'
 import { Transaction } from 'sequelize'
 import { CONFIG } from '../initializers/config'
 import * as LRUCache from 'lru-cache'
+import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
 
 type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
 
-const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
+const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
 const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
 
 // ---------------------------------------------------------------------------

+ 2 - 2
server/lib/peertube-socket.ts

@@ -1,8 +1,8 @@
 import * as SocketIO from 'socket.io'
 import { authenticateSocket } from '../middlewares'
-import { UserNotificationModel } from '../models/account/user-notification'
 import { logger } from '../helpers/logger'
 import { Server } from 'http'
+import { UserNotificationModelForApi } from '@server/typings/models/user'
 
 class PeerTubeSocket {
 
@@ -32,7 +32,7 @@ class PeerTubeSocket {
       })
   }
 
-  sendNotification (userId: number, notification: UserNotificationModel) {
+  sendNotification (userId: number, notification: UserNotificationModelForApi) {
     const socket = this.userNotificationSockets[userId]
 
     if (!socket) return

+ 2 - 1
server/lib/redundancy.ts

@@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
 import { sendUndoCacheFile } from './activitypub/send'
 import { Transaction } from 'sequelize'
 import { getServerActor } from '../helpers/utils'
+import { MVideoRedundancyVideo } from '@server/typings/models'
 
-async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) {
+async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) {
   const serverActor = await getServerActor()
 
   // Local cache, send undo to remote instances

+ 35 - 17
server/lib/schedulers/videos-redundancy-scheduler.ts

@@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER }
 import { logger } from '../../helpers/logger'
 import { VideosRedundancy } from '../../../shared/models/redundancy'
 import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
-import { VideoFileModel } from '../../models/video/video-file'
 import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
 import { join } from 'path'
 import { move } from 'fs-extra'
@@ -12,16 +11,29 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
 import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url'
 import { removeVideoRedundancy } from '../redundancy'
 import { getOrCreateVideoAndAccountAndChannel } from '../activitypub'
-import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
-import { VideoModel } from '../../models/video/video'
 import { downloadPlaylistSegments } from '../hls'
 import { CONFIG } from '../../initializers/config'
+import {
+  MStreamingPlaylist,
+  MStreamingPlaylistVideo,
+  MVideoAccountLight,
+  MVideoFile,
+  MVideoFileVideo,
+  MVideoRedundancyFileVideo,
+  MVideoRedundancyStreamingPlaylistVideo,
+  MVideoRedundancyVideo,
+  MVideoWithAllFiles
+} from '@server/typings/models'
 
 type CandidateToDuplicate = {
   redundancy: VideosRedundancy,
-  video: VideoModel,
-  files: VideoFileModel[],
-  streamingPlaylists: VideoStreamingPlaylistModel[]
+  video: MVideoWithAllFiles,
+  files: MVideoFile[],
+  streamingPlaylists: MStreamingPlaylist[]
+}
+
+function isMVideoRedundancyFileVideo (o: MVideoRedundancyVideo): o is MVideoRedundancyFileVideo {
+  return !!(o as MVideoRedundancyFileVideo).VideoFile
 }
 
 export class VideosRedundancyScheduler extends AbstractScheduler {
@@ -102,7 +114,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     }
   }
 
-  private async extendsRedundancy (redundancyModel: VideoRedundancyModel) {
+  private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) {
     const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
     // Redundancy strategy disabled, remove our redundancy instead of extending expiration
     if (!redundancy) {
@@ -172,7 +184,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     }
   }
 
-  private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) {
+  private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) {
+    const file = fileArg as MVideoFileVideo
     file.Video = video
 
     const serverActor = await getServerActor()
@@ -187,7 +200,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file))
     await move(tmpPath, destPath)
 
-    const createdModel = await VideoRedundancyModel.create({
+    const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
       expiresOn: this.buildNewExpiration(redundancy.minLifetime),
       url: getVideoCacheFileActivityPubUrl(file),
       fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
@@ -203,7 +216,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url)
   }
 
-  private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) {
+  private async createStreamingPlaylistRedundancy (
+    redundancy: VideosRedundancy,
+    video: MVideoAccountLight,
+    playlistArg: MStreamingPlaylist
+  ) {
+    const playlist = playlistArg as MStreamingPlaylistVideo
     playlist.Video = video
 
     const serverActor = await getServerActor()
@@ -213,7 +231,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
     await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
 
-    const createdModel = await VideoRedundancyModel.create({
+    const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
       expiresOn: this.buildNewExpiration(redundancy.minLifetime),
       url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
       fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
@@ -229,7 +247,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url)
   }
 
-  private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) {
+  private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) {
     logger.info('Extending expiration of %s.', redundancy.url)
 
     const serverActor = await getServerActor()
@@ -243,7 +261,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
   private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) {
     while (this.isTooHeavy(candidateToDuplicate)) {
       const redundancy = candidateToDuplicate.redundancy
-      const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime)
+      const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime)
       if (!toDelete) return
 
       await removeVideoRedundancy(toDelete)
@@ -263,14 +281,14 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     return new Date(Date.now() + expiresAfterMs)
   }
 
-  private buildEntryLogId (object: VideoRedundancyModel) {
-    if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
+  private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
+    if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
 
     return `${object.VideoStreamingPlaylist.playlistUrl}`
   }
 
-  private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) {
-    const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size
+  private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) {
+    const fileReducer = (previous: number, current: MVideoFile) => previous + current.size
 
     const totalSize = files.reduce(fileReducer, 0)
     if (playlists.length === 0) return totalSize

+ 13 - 13
server/lib/thumbnail.ts

@@ -1,20 +1,20 @@
-import { VideoFileModel } from '../models/video/video-file'
 import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
 import { CONFIG } from '../initializers/config'
-import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants'
-import { VideoModel } from '../models/video/video'
+import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
 import { ThumbnailModel } from '../models/video/thumbnail'
 import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
 import { processImage } from '../helpers/image-utils'
 import { join } from 'path'
 import { downloadImage } from '../helpers/requests'
-import { VideoPlaylistModel } from '../models/video/video-playlist'
+import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist'
+import { MVideoFile, MVideoThumbnail } from '../typings/models'
+import { MThumbnail } from '../typings/models/video/thumbnail'
 
 type ImageSize = { height: number, width: number }
 
 function createPlaylistMiniatureFromExisting (
   inputPath: string,
-  playlist: VideoPlaylistModel,
+  playlist: MVideoPlaylistThumbnail,
   automaticallyGenerated: boolean,
   keepOriginal = false,
   size?: ImageSize
@@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting (
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
 }
 
-function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) {
+function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) {
   const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
   const type = ThumbnailType.MINIATURE
 
@@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
 }
 
-function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
+function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
   const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
   const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height })
 
@@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type:
 
 function createVideoMiniatureFromExisting (
   inputPath: string,
-  video: VideoModel,
+  video: MVideoThumbnail,
   type: ThumbnailType,
   automaticallyGenerated: boolean,
   size?: ImageSize
@@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting (
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
 }
 
-function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
+function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) {
   const input = video.getVideoFilePath(videoFile)
 
   const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
@@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
 }
 
-function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) {
+function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
   const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
 
   const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel()
@@ -90,7 +90,7 @@ export {
   createPlaylistMiniatureFromExisting
 }
 
-function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
+function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) {
   const filename = playlist.generateThumbnailName()
   const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
 
@@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz
   }
 }
 
-function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) {
+function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
   const existingThumbnail = Array.isArray(video.Thumbnails)
     ? video.Thumbnails.find(t => t.type === type)
     : undefined
@@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: {
   type: ThumbnailType,
   automaticallyGenerated?: boolean,
   fileUrl?: string,
-  existingThumbnail?: ThumbnailModel
+  existingThumbnail?: MThumbnail
 }) {
   const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
 

+ 14 - 12
server/lib/user.ts

@@ -5,7 +5,6 @@ import { AccountModel } from '../models/account/account'
 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 { ActorModel } from '../models/activitypub/actor'
 import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
 import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
@@ -14,14 +13,17 @@ import { sequelizeTypescript } from '../initializers/database'
 import { Transaction } from 'sequelize/types'
 import { Redis } from './redis'
 import { Emailer } from './emailer'
+import { MAccountActor, MActor, MChannelActor } from '../typings/models'
+import { MUser, MUserId, MUserNotifSettingAccount } from '../typings/models/user'
 
 type ChannelNames = { name: string, displayName: string }
+
 async function createUserAccountAndChannelAndPlaylist (parameters: {
   userToCreate: UserModel,
   userDisplayName?: string,
   channelNames?: ChannelNames,
   validateUser?: boolean
-}) {
+}): Promise<{ user: MUserNotifSettingAccount, account: MAccountActor, videoChannel: MChannelActor }> {
   const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
 
   const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -30,7 +32,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
       validate: validateUser
     }
 
-    const userCreated = await userToCreate.save(userOptions)
+    const userCreated: MUserNotifSettingAccount = await userToCreate.save(userOptions)
     userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
 
     const accountCreated = await createLocalAccountWithoutKeys({
@@ -50,15 +52,15 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
     return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist }
   })
 
-  const [ accountKeys, channelKeys ] = await Promise.all([
+  const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([
     setAsyncActorKeys(account.Actor),
     setAsyncActorKeys(videoChannel.Actor)
   ])
 
-  account.Actor = accountKeys
-  videoChannel.Actor = channelKeys
+  account.Actor = accountActorWithKeys
+  videoChannel.Actor = channelActorWithKeys
 
-  return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel }
+  return { user, account, videoChannel }
 }
 
 async function createLocalAccountWithoutKeys (parameters: {
@@ -73,7 +75,7 @@ async function createLocalAccountWithoutKeys (parameters: {
   const url = getAccountActivityPubUrl(name)
 
   const actorInstance = buildActorInstance(type, url, name)
-  const actorInstanceCreated = await actorInstance.save({ transaction: t })
+  const actorInstanceCreated: MActor = await actorInstance.save({ transaction: t })
 
   const accountInstance = new AccountModel({
     name: displayName || name,
@@ -82,7 +84,7 @@ async function createLocalAccountWithoutKeys (parameters: {
     actorId: actorInstanceCreated.id
   })
 
-  const accountInstanceCreated = await accountInstance.save({ transaction: t })
+  const accountInstanceCreated: MAccountActor = await accountInstance.save({ transaction: t })
   accountInstanceCreated.Actor = actorInstanceCreated
 
   return accountInstanceCreated
@@ -102,7 +104,7 @@ async function createApplicationActor (applicationId: number) {
   return accountCreated
 }
 
-async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) {
+async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) {
   const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
   let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
 
@@ -124,7 +126,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) {
+function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) {
   const values: UserNotificationSetting & { userId: number } = {
     userId: user.id,
     newVideoFromSubscription: UserNotificationSettingValue.WEB,
@@ -143,7 +145,7 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction
   return UserNotificationSettingModel.create(values, { transaction: t })
 }
 
-async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) {
+async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) {
   if (channelNames) return channelNames
 
   let channelName = user.username + '_channel'

+ 6 - 7
server/lib/video-blacklist.ts

@@ -2,16 +2,15 @@ import { Transaction } from 'sequelize'
 import { CONFIG } from '../initializers/config'
 import { UserRight, VideoBlacklistType } from '../../shared/models'
 import { VideoBlacklistModel } from '../models/video/video-blacklist'
-import { UserModel } from '../models/account/user'
-import { VideoModel } from '../models/video/video'
 import { logger } from '../helpers/logger'
 import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
 import { Hooks } from './plugins/hooks'
 import { Notifier } from './notifier'
+import { MUser, MVideoBlacklist, MVideoWithBlacklistLight } from '@server/typings/models'
 
 async function autoBlacklistVideoIfNeeded (parameters: {
-  video: VideoModel,
-  user?: UserModel,
+  video: MVideoWithBlacklistLight,
+  user?: MUser,
   isRemote: boolean,
   isNew: boolean,
   notify?: boolean,
@@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: {
     reason: 'Auto-blacklisted. Moderator review required.',
     type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
   }
-  const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({
+  const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklist>({
     where: {
       videoId: video.id
     },
@@ -49,10 +48,10 @@ async function autoBlacklistVideoIfNeeded (parameters: {
 }
 
 async function autoBlacklistNeeded (parameters: {
-  video: VideoModel,
+  video: MVideoWithBlacklistLight,
   isRemote: boolean,
   isNew: boolean,
-  user?: UserModel
+  user?: MUser
 }) {
   const { user, video, isRemote, isNew } = parameters
 

+ 12 - 5
server/lib/video-channel.ts

@@ -1,12 +1,19 @@
 import * as Sequelize from 'sequelize'
 import * as uuidv4 from 'uuid/v4'
 import { VideoChannelCreate } from '../../shared/models'
-import { AccountModel } from '../models/account/account'
 import { VideoChannelModel } from '../models/video/video-channel'
 import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
 import { VideoModel } from '../models/video/video'
+import { MAccountId, MChannelActor, MChannelId } from '../typings/models'
 
-async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
+type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelActor &
+  { Account?: T }
+
+async function createVideoChannel <T extends MAccountId> (
+  videoChannelInfo: VideoChannelCreate,
+  account: T,
+  t: Sequelize.Transaction
+): Promise<CustomVideoChannelModelAccount<T>> {
   const uuid = uuidv4()
   const url = getVideoChannelActivityPubUrl(videoChannelInfo.name)
   const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid)
@@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
     actorId: actorInstanceCreated.id
   }
 
-  const videoChannel = VideoChannelModel.build(videoChannelData)
+  const videoChannel = new VideoChannelModel(videoChannelData)
 
   const options = { transaction: t }
-  const videoChannelCreated = await videoChannel.save(options)
+  const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelActor
 
   // Do not forget to add Account/Actor information to the created video channel
   videoChannelCreated.Account = account
@@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
   return videoChannelCreated
 }
 
-async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
+async function federateAllVideosOfChannel (videoChannel: MChannelId) {
   const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
 
   for (const videoId of videoIds) {

+ 5 - 6
server/lib/video-comment.ts

@@ -1,17 +1,16 @@
 import * as Sequelize from 'sequelize'
 import { ResultList } from '../../shared/models'
 import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
-import { AccountModel } from '../models/account/account'
-import { VideoModel } from '../models/video/video'
 import { VideoCommentModel } from '../models/video/video-comment'
 import { getVideoCommentActivityPubUrl } from './activitypub'
 import { sendCreateVideoComment } from './activitypub/send'
+import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
 
 async function createVideoComment (obj: {
   text: string,
-  inReplyToComment: VideoCommentModel | null,
-  video: VideoModel
-  account: AccountModel
+  inReplyToComment: MComment | null,
+  video: MVideoFullLight,
+  account: MAccountDefault
 }, t: Sequelize.Transaction) {
   let originCommentId: number | null = null
   let inReplyToCommentId: number | null = null
@@ -32,7 +31,7 @@ async function createVideoComment (obj: {
 
   comment.url = getVideoCommentActivityPubUrl(obj.video, comment)
 
-  const savedComment = await comment.save({ transaction: t })
+  const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
   savedComment.InReplyToVideoComment = obj.inReplyToComment
   savedComment.Video = obj.video
   savedComment.Account = obj.account

+ 4 - 3
server/lib/video-playlist.ts

@@ -1,12 +1,13 @@
 import * as Sequelize from 'sequelize'
-import { AccountModel } from '../models/account/account'
 import { VideoPlaylistModel } from '../models/video/video-playlist'
 import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
 import { getVideoPlaylistActivityPubUrl } from './activitypub'
 import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
+import { MAccount } from '../typings/models'
+import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist'
 
-async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) {
-  const videoPlaylist = new VideoPlaylistModel({
+async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) {
+  const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({
     name: 'Watch later',
     privacy: VideoPlaylistPrivacy.PRIVATE,
     type: VideoPlaylistType.WATCH_LATER,

+ 8 - 8
server/lib/video-transcoding.ts

@@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra'
 import { logger } from '../helpers/logger'
 import { VideoResolution } from '../../shared/models/videos'
 import { VideoFileModel } from '../models/video/video-file'
-import { VideoModel } from '../models/video/video'
 import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
 import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
 import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
 import { CONFIG } from '../initializers/config'
+import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models'
 
 /**
  * Optimize the original video file and replace it. The resolution is not changed.
  */
-async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
+async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
   const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
   const newExtname = '.mp4'
@@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
 /**
  * Transcode the original video file to a lower resolution.
  */
-async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
+async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
   const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
   const extname = '.mp4'
@@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
   return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
 }
 
-async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
+async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) {
   const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
   const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
   const newExtname = '.mp4'
@@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti
   return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
 }
 
-async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
+async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) {
   const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
   await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
 
@@ -165,14 +165,14 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
+async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
   const stats = await stat(transcodingPath)
   const fps = await getVideoFileFPS(transcodingPath)
 
   await move(transcodingPath, outputPath)
 
-  videoFile.set('size', stats.size)
-  videoFile.set('fps', fps)
+  videoFile.size = stats.size
+  videoFile.fps = fps
 
   await video.createTorrentAndSetInfoHash(videoFile)
 

+ 2 - 1
server/middlewares/validators/follows.ts

@@ -10,6 +10,7 @@ import { areValidationErrors } from './utils'
 import { ActorModel } from '../../models/activitypub/actor'
 import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
 import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
+import { MActorFollowActorsDefault } from '@server/typings/models'
 
 const followValidator = [
   body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
@@ -65,7 +66,7 @@ const getFollowerValidator = [
 
     if (areValidationErrors(req, res)) return
 
-    let follow: ActorFollowModel
+    let follow: MActorFollowActorsDefault
     try {
       const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost)
       const actor = await ActorModel.loadByUrl(actorUrl)

+ 2 - 2
server/middlewares/validators/redundancy.ts

@@ -24,7 +24,7 @@ const videoFileRedundancyGetValidator = [
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
 
-    const video = res.locals.video
+    const video = res.locals.videoAll
     const videoFile = video.VideoFiles.find(f => {
       return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps)
     })
@@ -50,7 +50,7 @@ const videoPlaylistRedundancyGetValidator = [
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
 
-    const video = res.locals.video
+    const video = res.locals.videoAll
     const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType)
 
     if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' })

+ 3 - 2
server/middlewares/validators/users.ts

@@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird'
 import * as express from 'express'
 import { body, param } from 'express-validator'
 import { omit } from 'lodash'
-import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
+import { isIdOrUUIDValid, toBooleanOrNull } from '../../helpers/custom-validators/misc'
 import {
   isUserAdminFlagsValid,
   isUserAutoPlayVideoValid,
@@ -31,6 +31,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
 import { isThemeRegistered } from '../../lib/plugins/theme-utils'
 import { doesVideoExist } from '../../helpers/middlewares'
 import { UserRole } from '../../../shared/models/users'
+import { MUserDefault } from '@server/typings/models'
 
 const usersAddValidator = [
   body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -457,7 +458,7 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email:
   return true
 }
 
-async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
+async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) {
   const user = await finder()
 
   if (!user) {

+ 2 - 2
server/middlewares/validators/videos/video-abuses.ts

@@ -33,7 +33,7 @@ const videoAbuseGetValidator = [
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
-    if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+    if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
 
     return next()
   }
@@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
-    if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+    if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
 
     return next()
   }

+ 3 - 3
server/middlewares/validators/videos/video-blacklist.ts

@@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
-    if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return
+    if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
 
     return next()
   }
@@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
 
-    const video = res.locals.video
+    const video = res.locals.videoAll
     if (req.body.unfederate === true && video.remote === true) {
       return res
         .status(409)
@@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
-    if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return
+    if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
 
     return next()
   }

+ 3 - 3
server/middlewares/validators/videos/video-captions.ts

@@ -26,7 +26,7 @@ const addVideoCaptionValidator = [
 
     // Check if the user who did the request is able to update the video
     const user = res.locals.oauth.token.User
-    if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
+    if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
 
     return next()
   }
@@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [
 
     if (areValidationErrors(req, res)) return
     if (!await doesVideoExist(req.params.videoId, res)) return
-    if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return
+    if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return
 
     // Check if the user who did the request is able to update the video
     const user = res.locals.oauth.token.User
-    if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
+    if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return
 
     return next()
   }

+ 3 - 2
server/middlewares/validators/videos/video-channels.ts

@@ -7,13 +7,14 @@ import {
   isVideoChannelSupportValid
 } from '../../../helpers/custom-validators/video-channels'
 import { logger } from '../../../helpers/logger'
-import { UserModel } from '../../../models/account/user'
 import { VideoChannelModel } from '../../../models/video/video-channel'
 import { areValidationErrors } from '../utils'
 import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { isBooleanValid } from '../../../helpers/custom-validators/misc'
 import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares'
+import { MChannelActorAccountDefault } from '../../../typings/models/video'
+import { MUser } from '@server/typings/models'
 
 const videoChannelsAddValidator = [
   body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
@@ -131,7 +132,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
+function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelActorAccountDefault, res: express.Response) {
   if (videoChannel.Actor.isOwned() === false) {
     res.status(403)
               .json({ error: 'Cannot remove video channel of another server.' })

Some files were not shown because too many files changed in this diff