Stronger model typings

This commit is contained in:
Chocobozzz 2019-08-15 11:53:26 +02:00
parent 13176a07a9
commit 453e83ea5d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
176 changed files with 2118 additions and 1133 deletions

View File

@ -16,7 +16,6 @@ import {
} from '../../middlewares' } from '../../middlewares'
import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators'
import { AccountModel } from '../../models/account/account' import { AccountModel } from '../../models/account/account'
import { ActorModel } from '../../models/activitypub/actor'
import { ActorFollowModel } from '../../models/activitypub/actor-follow' import { ActorFollowModel } from '../../models/activitypub/actor-follow'
import { VideoModel } from '../../models/video/video' import { VideoModel } from '../../models/video/video'
import { VideoCommentModel } from '../../models/video/video-comment' 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 { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
import { VideoPlaylistModel } from '../../models/video/video-playlist' import { VideoPlaylistModel } from '../../models/video/video-playlist'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models'
const activityPubClientRouter = express.Router() const activityPubClientRouter = express.Router()
@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT
activityPubClientRouter.get('/video-playlists/:playlistId', activityPubClientRouter.get('/video-playlists/:playlistId',
executeIfActivityPub, executeIfActivityPub,
asyncMiddleware(videoPlaylistsGetValidator), asyncMiddleware(videoPlaylistsGetValidator('all')),
asyncMiddleware(videoPlaylistController) asyncMiddleware(videoPlaylistController)
) )
activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) {
async function videoController (req: express.Request, res: express.Response) { async function videoController (req: express.Request, res: express.Response) {
// We need more attributes // 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) if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)
// We need captions to render AP object // 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 audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC)
const videoObject = audiencify(video.toActivityPubObject(), audience) const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience)
if (req.path.endsWith('/activity')) { 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) 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) 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) return activityPubResponse(activityPubContextify(activity), res)
} }
async function videoAnnouncesController (req: express.Request, res: express.Response) { 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 handler = async (start: number, count: number) => {
const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) 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) { 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)) const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res) return activityPubResponse(activityPubContextify(json), res)
} }
async function videoDislikesController (req: express.Request, res: express.Response) { 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)) const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res) return activityPubResponse(activityPubContextify(json), res)
} }
async function videoCommentsController (req: express.Request, res: express.Response) { 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 handler = async (start: number, count: number) => {
const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) 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) { 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) 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) { async function videoPlaylistController (req: express.Request, res: express.Response) {
const playlist = res.locals.videoPlaylist const playlist = res.locals.videoPlaylistFull
// We need more attributes // We need more attributes
playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) 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) => { const handler = (start: number, count: number) => {
return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) 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) 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) => { const handler = (start: number, count: number) => {
return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) 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) 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) => { const handler = (start: number, count: number) => {
return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) 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) 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 handler = async (start: number, count: number) => {
const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
return { return {

View File

@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
import { queue } from 'async' import { queue } from 'async'
import { ActorModel } from '../../models/activitypub/actor' import { ActorModel } from '../../models/activitypub/actor'
import { SignatureActorModel } from '../../typings/models' import { MActorDefault, MActorSignature } from '../../typings/models'
const inboxRouter = express.Router() 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 } const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor }
processActivities(task.activities, options) processActivities(task.activities, options)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
import { Hooks } from '../../../lib/plugins/hooks' import { Hooks } from '../../../lib/plugins/hooks'
import { MVideoFullLight } from '@server/typings/models'
const auditLogger = auditLoggerFactory('videos') const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router() 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 { videoCreated } = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: 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(thumbnailModel, t)
await videoCreated.addAndSaveThumbnail(previewModel, 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) { async function updateVideo (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video const videoInstance = res.locals.videoAll
const videoFieldsSave = videoInstance.toJSON() const videoFieldsSave = videoInstance.toJSON()
const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
const videoInfoToUpdate: VideoUpdate = req.body 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 (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, 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( const video = await Hooks.wrapPromiseFun(
VideoModel.loadForGetAPI, VideoModel.loadForGetAPI,
{ id: res.locals.video.id, userId }, { id: res.locals.onlyVideoWithRights.id, userId },
'filter:api.video.get.result' '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) { async function viewVideo (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video const videoInstance = res.locals.videoAll
const ip = req.ip const ip = req.ip
const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) 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) { async function getVideoDescription (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video const videoInstance = res.locals.videoAll
let description = '' let description = ''
if (videoInstance.isOwned()) { 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) { 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 sequelizeTypescript.transaction(async t => {
await videoInstance.destroy({ transaction: t }) await videoInstance.destroy({ transaction: t })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
return res.send(json).end() 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) const { video, videoFile } = getVideoAndFile(req, res)
if (!videoFile) return res.status(404).end() if (!videoFile) return res.status(404).end()
return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) 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) const { video, videoFile } = getVideoAndFile(req, res)
if (!videoFile) return res.status(404).end() 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) { function getVideoAndFile (req: express.Request, res: express.Response) {
const resolution = parseInt(req.params.resolution, 10) 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) const videoFile = video.VideoFiles.find(f => f.resolution === resolution)

View File

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

View File

@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor'
import { signJsonLDObject } from './peertube-crypto' import { signJsonLDObject } from './peertube-crypto'
import { pageToStartAndCount } from './core-utils' import { pageToStartAndCount } from './core-utils'
import { parse } from 'url' import { parse } from 'url'
import { MActor } from '../typings/models'
function activityPubContextify <T> (data: T) { function activityPubContextify <T> (data: T) {
return Object.assign(data, { 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) const activity = activityPubContextify(data)
return signJsonLDObject(byActor, activity) as Promise<Activity> return signJsonLDObject(byActor, activity) as Promise<Activity>

View File

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

View File

@ -1,10 +1,10 @@
import { join } from 'path' import { join } from 'path'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
import { VideoCaptionModel } from '../models/video/video-caption'
import * as srt2vtt from 'srt-to-vtt' 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 videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) const destination = join(videoCaptionsDir, videoCaption.getCaptionName())

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import { VideoModel } from '../../models/video/video'
import { Response } from 'express' import { Response } from 'express'
import { VideoCaptionModel } from '../../models/video/video-caption' 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) const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
if (!videoCaption) { if (!videoCaption) {

View File

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

View File

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

View File

@ -1,9 +1,8 @@
import { Response } from 'express' import { Response } from 'express'
import { fetchVideo, VideoFetchType } from '../video' import { fetchVideo, VideoFetchType } from '../video'
import { UserModel } from '../../models/account/user'
import { UserRight } from '../../../shared/models/users' import { UserRight } from '../../../shared/models/users'
import { VideoChannelModel } from '../../models/video/video-channel' 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') { async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined 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 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 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) { if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
if (videoChannel === null) { if (videoChannel === null) {
@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode
return true 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 // Retrieve the user who did the request
if (video.isOwned() === false) { if (video.isOwned() === false) {
res.status(403) res.status(403)

View File

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

View File

@ -1,8 +1,24 @@
import { VideoModel } from '../models/video/video' 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' 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 === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) 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' 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 === 'all') return VideoModel.loadByUrlAndPopulateAccount(url)
if (fetchType === 'only-video') return VideoModel.loadByUrl(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 { export {
VideoFetchType, VideoFetchType,
VideoFetchByUrlType, VideoFetchByUrlType,
fetchVideo, fetchVideo,
getVideo,
fetchVideoByUrl fetchVideoByUrl
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model' import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
import { SignatureActorModel } from '../../../typings/models' import { MActorSignature } from '../../../typings/models'
async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
const { activity, byActor } = options 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 likeActivity = activity.object as ActivityLike
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) 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' const dislike = activity.object.type === 'Dislike'
? activity.object ? activity.object
: activity.object.object as DislikeObject : 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 cacheFileObject = activity.object.object as CacheFileObject
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) 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 => { return sequelizeTypescript.transaction(async t => {
const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, 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 => { return sequelizeTypescript.transaction(async t => {
const share = await VideoShareModel.loadByUrl(announceActivity.id, t) const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)

View File

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

View File

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

View File

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

View File

@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from
import { unicastTo } from './utils' import { unicastTo } from './utils'
import { buildFollowActivity } from './send-follow' import { buildFollowActivity } from './send-follow'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { ActorFollowModelLight } from '../../../typings/models/actor-follow' import { MActor, MActorFollowActors } from '../../../typings/models'
import { ActorModelOnly } from '../../../typings/models'
async function sendAccept (actorFollow: ActorFollowModelLight) { async function sendAccept (actorFollow: MActorFollowActors) {
const follower = actorFollow.ActorFollower const follower = actorFollow.ActorFollower
const me = actorFollow.ActorFollowing 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 { return {
type: 'Accept', type: 'Accept',
id: url, id: url,

View File

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

View File

@ -1,19 +1,23 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
import { VideoPrivacy } from '../../../../shared/models/videos' 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 { VideoCommentModel } from '../../../models/video/video-comment'
import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
import { logger } from '../../../helpers/logger' 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 { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { getServerActor } from '../../../helpers/utils' import { getServerActor } from '../../../helpers/utils'
import * as Bluebird from 'bluebird' import {
MActorLight,
MCommentOwnerVideo,
MVideoAccountLight,
MVideoAP,
MVideoPlaylistFull,
MVideoRedundancyFileVideo,
MVideoRedundancyStreamingPlaylistVideo
} from '../../../typings/models'
async function sendCreateVideo (video: VideoModel, t: Transaction) { async function sendCreateVideo (video: MVideoAP, t: Transaction) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined if (video.privacy === VideoPrivacy.PRIVATE) return undefined
logger.info('Creating job to send video creation of %s.', video.url) 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) 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) logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
return sendVideoRelatedCreateActivity({ 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 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
logger.info('Creating job to send create video playlist of %s.', playlist.url) 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) 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) logger.info('Creating job to send comment %s.', comment.url)
const isOrigin = comment.Video.isOwned() 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)) 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) if (!audience) audience = getAudience(byActor)
return audiencify( return audiencify(
@ -122,8 +130,8 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function sendVideoRelatedCreateActivity (options: { async function sendVideoRelatedCreateActivity (options: {
byActor: ActorModel, byActor: MActorLight,
video: VideoModel, video: MVideoAccountLight,
url: string, url: string,
object: any, object: any,
transaction?: Transaction transaction?: Transaction

View File

@ -1,17 +1,17 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { getDeleteActivityPubUrl } from '../url' import { getDeleteActivityPubUrl } from '../url'
import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { getServerActor } from '../../../helpers/utils' 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) logger.info('Creating job to broadcast delete of video %s.', video.url)
const byActor = video.VideoChannel.Account.Actor const byActor = video.VideoChannel.Account.Actor
@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
return broadcastToFollowers(activity, byActor, actorsInvolved, t) 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) logger.info('Creating job to send delete of comment %s.', videoComment.url)
const isVideoOrigin = videoComment.Video.isOwned() 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)) 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) logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
const byActor = videoPlaylist.OwnerAccount.Actor 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 = { const activity = {
type: 'Delete' as 'Delete', type: 'Delete' as 'Delete',
id: url, id: url,

View File

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

View File

@ -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 { getVideoAbuseActivityPubUrl } from '../url'
import { unicastTo } from './utils' import { unicastTo } from './utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub'
import { audiencify, getAudience } from '../audience' import { audiencify, getAudience } from '../audience'
import { Transaction } from 'sequelize' 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 if (!video.VideoChannel.Account.Actor.serverId) return // Local user
const url = getVideoAbuseActivityPubUrl(videoAbuse) 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)) 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) if (!audience) audience = getAudience(byActor)
const activity = Object.assign( const activity = Object.assign(

View File

@ -4,9 +4,9 @@ import { getActorFollowActivityPubUrl } from '../url'
import { unicastTo } from './utils' import { unicastTo } from './utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { Transaction } from 'sequelize' 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 me = actorFollow.ActorFollower
const following = actorFollow.ActorFollowing const following = actorFollow.ActorFollowing
@ -21,7 +21,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) 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 { return {
type: 'Follow', type: 'Follow',
id: url, id: url,

View File

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

View File

@ -1,12 +1,11 @@
import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url'
import { unicastTo } from './utils' import { unicastTo } from './utils'
import { buildFollowActivity } from './send-follow' import { buildFollowActivity } from './send-follow'
import { logger } from '../../../helpers/logger' 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 if (!follower.serverId) { // This should never happen
logger.warn('Do not sending reject to local follower.') logger.warn('Do not sending reject to local follower.')
return 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 { return {
type: 'Reject', type: 'Reject',
id: url, id: url,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send'
import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
import { updateActorAvatarInstance } from './activitypub' import { updateActorAvatarInstance } from './activitypub'
import { processImage } from '../helpers/image-utils' 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 { extname, join } from 'path'
import { retryTransactionWrapper } from '../helpers/database-utils' import { retryTransactionWrapper } from '../helpers/database-utils'
import * as uuidv4 from 'uuid/v4' import * as uuidv4 from 'uuid/v4'
@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database'
import * as LRUCache from 'lru-cache' import * as LRUCache from 'lru-cache'
import { queue } from 'async' import { queue } from 'async'
import { downloadImage } from '../helpers/requests' 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 extension = extname(avatarPhysicalFile.filename)
const avatarName = uuidv4() + extension const avatarName = uuidv4() + extension
const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)

View File

@ -1,6 +1,7 @@
import { sequelizeTypescript } from '../initializers' import { sequelizeTypescript } from '../initializers'
import { AccountBlocklistModel } from '../models/account/account-blocklist' import { AccountBlocklistModel } from '../models/account/account-blocklist'
import { ServerBlocklistModel } from '../models/server/server-blocklist' import { ServerBlocklistModel } from '../models/server/server-blocklist'
import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models'
function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
return sequelizeTypescript.transaction(async t => { 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 sequelizeTypescript.transaction(async t => {
return accountBlock.destroy({ transaction: t }) return accountBlock.destroy({ transaction: t })
}) })
} }
function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {
return serverBlock.destroy({ transaction: t }) return serverBlock.destroy({ transaction: t })
}) })

View File

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

View File

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

View File

@ -1,4 +1,3 @@
import { VideoModel } from '../models/video/video'
import { basename, dirname, join } from 'path' import { basename, dirname, join } from 'path'
import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' 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' 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 { VideoFileModel } from '../models/video/video-file'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
import { sequelizeTypescript } from '../initializers/database' import { sequelizeTypescript } from '../initializers/database'
import { MVideoWithFile } from '@server/typings/models'
async function updateStreamingPlaylistsInfohashesIfNeeded () { async function updateStreamingPlaylistsInfohashesIfNeeded () {
const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() 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 directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) {
await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') 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 json: { [filename: string]: { [range: string]: string } } = {}
const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)

View File

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

View File

@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { VideoCommentModel } from '../../../models/video/video-comment' 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' 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 const payload = job.data as ActivitypubHttpFetcherPayload
let video: VideoModel let video: MVideoFullLight
if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
let account: AccountModel let account: MAccountDefault
if (payload.accountId) account = await AccountModel.load(payload.accountId) if (payload.accountId) account = await AccountModel.load(payload.accountId)
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants'
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { CONFIG } from '../initializers/config' import { CONFIG } from '../initializers/config'
import * as LRUCache from 'lru-cache' 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 } 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 }) const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

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

View File

@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
import { sendUndoCacheFile } from './activitypub/send' import { sendUndoCacheFile } from './activitypub/send'
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { getServerActor } from '../helpers/utils' 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() const serverActor = await getServerActor()
// Local cache, send undo to remote instances // Local cache, send undo to remote instances

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra'
import { logger } from '../helpers/logger' import { logger } from '../helpers/logger'
import { VideoResolution } from '../../shared/models/videos' import { VideoResolution } from '../../shared/models/videos'
import { VideoFileModel } from '../models/video/video-file' import { VideoFileModel } from '../models/video/video-file'
import { VideoModel } from '../models/video/video'
import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
import { CONFIG } from '../initializers/config' 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. * 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 videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const newExtname = '.mp4' const newExtname = '.mp4'
@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
/** /**
* Transcode the original video file to a lower resolution. * 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 videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const extname = '.mp4' const extname = '.mp4'
@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) 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 videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const newExtname = '.mp4' const newExtname = '.mp4'
@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) 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) const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
await ensureDir(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 stats = await stat(transcodingPath)
const fps = await getVideoFileFPS(transcodingPath) const fps = await getVideoFileFPS(transcodingPath)
await move(transcodingPath, outputPath) await move(transcodingPath, outputPath)
videoFile.set('size', stats.size) videoFile.size = stats.size
videoFile.set('fps', fps) videoFile.fps = fps
await video.createTorrentAndSetInfoHash(videoFile) await video.createTorrentAndSetInfoHash(videoFile)

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird'
import * as express from 'express' import * as express from 'express'
import { body, param } from 'express-validator' import { body, param } from 'express-validator'
import { omit } from 'lodash' import { omit } from 'lodash'
import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' import { isIdOrUUIDValid, toBooleanOrNull } from '../../helpers/custom-validators/misc'
import { import {
isUserAdminFlagsValid, isUserAdminFlagsValid,
isUserAutoPlayVideoValid, isUserAutoPlayVideoValid,
@ -31,6 +31,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
import { isThemeRegistered } from '../../lib/plugins/theme-utils' import { isThemeRegistered } from '../../lib/plugins/theme-utils'
import { doesVideoExist } from '../../helpers/middlewares' import { doesVideoExist } from '../../helpers/middlewares'
import { UserRole } from '../../../shared/models/users' import { UserRole } from '../../../shared/models/users'
import { MUserDefault } from '@server/typings/models'
const usersAddValidator = [ const usersAddValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 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 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() const user = await finder()
if (!user) { if (!user) {

View File

@ -33,7 +33,7 @@ const videoAbuseGetValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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() return next()
} }
@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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() return next()
} }

View File

@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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() return next()
} }
@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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) { if (req.body.unfederate === true && video.remote === true) {
return res return res
.status(409) .status(409)
@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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() return next()
} }

View File

@ -26,7 +26,7 @@ const addVideoCaptionValidator = [
// Check if the user who did the request is able to update the video // Check if the user who did the request is able to update the video
const user = res.locals.oauth.token.User 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() return next()
} }
@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [
if (areValidationErrors(req, res)) return if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, 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 // Check if the user who did the request is able to update the video
const user = res.locals.oauth.token.User 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() return next()
} }

View File

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

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