See our REST API documentation:
* OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml)
- * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html)
+ * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html)
See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub).
.vjs-dock-description {
font-size: 11px;
- .text::before, .text::after {
- display: inline-block;
- content: '\1F308';
- }
-
.text::before {
margin-right: 4px;
}
const config: ServerConfig = await configResponse.json()
const description = config.tracker.enabled && this.warningTitle
- ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
+ ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>'
: undefined
this.player.dock({
# From the project root directory
storage:
- tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
+ tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before processing...
avatars: 'storage/avatars/'
videos: 'storage/videos/'
streaming_playlists: 'storage/streaming-playlists/'
log:
level: 'info' # debug/info/warning/error
rotation:
- enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
+ enabled : true
search:
# Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
# From the project root directory
storage:
- tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
+ tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before processing...
avatars: '/var/www/peertube/storage/avatars/'
videos: '/var/www/peertube/storage/videos/'
streaming_playlists: '/var/www/peertube/storage/streaming-playlists/'
- redundancy: '/var/www/peertube/storage/videos/'
+ redundancy: '/var/www/peertube/storage/redundancy/'
logs: '/var/www/peertube/storage/logs/'
previews: '/var/www/peertube/storage/previews/'
thumbnails: '/var/www/peertube/storage/thumbnails/'
log:
level: 'info' # debug/info/warning/error
rotation:
- enabled : true
+ enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
search:
# Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
max_age: -1
plugins:
- # The website PeerTube will ask for available PeerTube plugins
- # This is an unmoderated plugin index, so only install plugins you trust
+ # The website PeerTube will ask for available PeerTube plugins and themes
+ # This is an unmoderated plugin index, so only install plugins/themes you trust
index:
enabled: true
check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions
username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
# If true, a video player will be embedded in the Twitter feed on PeerTube video share
# If false, we use an image link card that will redirect on your PeerTube instance
- # Test on https://cards-dev.twitter.com/validator to see if you are whitelisted
+ # Change it to "true", and then test on https://cards-dev.twitter.com/validator to see if you are whitelisted
whitelisted: false
followers:
"iso-639-3": "^1.0.1",
"js-yaml": "^3.5.4",
"jsonld": "~1.1.0",
- "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017",
"lodash": "^4.17.10",
"lru-cache": "^5.1.1",
"magnet-uri": "^5.1.4",
gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json
npm run concurrently -- -k \
- "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats.json" \
- "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json"
\ No newline at end of file
+ "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats-es2015.json" \
+ "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json"
} from '../../middlewares'
import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators'
import { AccountModel } from '../../models/account/account'
-import { ActorModel } from '../../models/activitypub/actor'
import { ActorFollowModel } from '../../models/activitypub/actor-follow'
import { VideoModel } from '../../models/video/video'
import { VideoCommentModel } from '../../models/video/video-comment'
import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
import { VideoPlaylistModel } from '../../models/video/video-playlist'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
+import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models'
const activityPubClientRouter = express.Router()
activityPubClientRouter.get('/video-playlists/:playlistId',
executeIfActivityPub,
- asyncMiddleware(videoPlaylistsGetValidator),
+ asyncMiddleware(videoPlaylistsGetValidator('all')),
asyncMiddleware(videoPlaylistController)
)
activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
async function videoController (req: express.Request, res: express.Response) {
// We need more attributes
- const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id })
+ const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption
if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)
// We need captions to render AP object
- video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id)
+ const captions = await VideoCaptionModel.listVideoCaptions(video.id)
+ const videoWithCaptions = Object.assign(video, { VideoCaptions: captions })
- const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC)
- const videoObject = audiencify(video.toActivityPubObject(), audience)
+ const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC)
+ const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience)
if (req.path.endsWith('/activity')) {
- const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience)
+ const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
return activityPubResponse(activityPubContextify(data), res)
}
if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url)
- const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined)
+ const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined)
return activityPubResponse(activityPubContextify(activity), res)
}
async function videoAnnouncesController (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const handler = async (start: number, count: number) => {
const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
}
async function videoLikesController (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res)
}
async function videoDislikesController (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res)
}
async function videoCommentsController (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const handler = async (start: number, count: number) => {
const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
}
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)
}
async function videoPlaylistController (req: express.Request, res: express.Response) {
- const playlist = res.locals.videoPlaylist
+ const playlist = res.locals.videoPlaylistFull
// We need more attributes
playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId)
}
async function videoPlaylistElementController (req: express.Request, res: express.Response) {
- const videoPlaylistElement = res.locals.videoPlaylistElement
+ const videoPlaylistElement = res.locals.videoPlaylistElementAP
const json = videoPlaylistElement.toActivityPubObject()
return activityPubResponse(activityPubContextify(json), res)
// ---------------------------------------------------------------------------
-async function actorFollowing (req: express.Request, actor: ActorModel) {
+async function actorFollowing (req: express.Request, actor: MActorId) {
const handler = (start: number, count: number) => {
return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count)
}
return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
}
-async function actorFollowers (req: express.Request, actor: ActorModel) {
+async function actorFollowers (req: express.Request, actor: MActorId) {
const handler = (start: number, count: number) => {
return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count)
}
return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
}
-async function actorPlaylists (req: express.Request, account: AccountModel) {
+async function actorPlaylists (req: express.Request, account: MAccountId) {
const handler = (start: number, count: number) => {
return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count)
}
return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
}
-function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) {
+function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) {
const handler = async (start: number, count: number) => {
const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count)
return {
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
import { queue } from 'async'
import { ActorModel } from '../../models/activitypub/actor'
-import { SignatureActorModel } from '../../typings/models'
+import { MActorDefault, MActorSignature } from '../../typings/models'
const inboxRouter = express.Router()
// ---------------------------------------------------------------------------
-const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => {
+type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault }
+const inboxQueue = queue<QueueParam, Error>((task, cb) => {
const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor }
processActivities(task.activities, options)
import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
import { buildAudience } from '../../lib/activitypub/audience'
import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares'
-import { AccountModel } from '../../models/account/account'
-import { ActorModel } from '../../models/activitypub/actor'
import { VideoModel } from '../../models/video/video'
import { activityPubResponse } from './utils'
-import { VideoChannelModel } from '../../models/video/video-channel'
+import { MActorLight } from '@server/typings/models'
const outboxRouter = express.Router()
return activityPubResponse(activityPubContextify(json), res)
}
-async function buildActivities (actor: ActorModel, start: number, count: number) {
+async function buildActivities (actor: MActorLight, start: number, count: number) {
const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count)
const activities: Activity[] = []
- // Avoid too many SQL requests
- const actors = data.data.map(v => v.VideoChannel.Account.Actor)
- actors.push(actor)
-
for (const video of data.data) {
const byActor = video.VideoChannel.Account.Actor
const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC)
import { logger } from '../../helpers/logger'
import { VideoChannelModel } from '../../models/video/video-channel'
import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
+import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models'
const searchRouter = express.Router()
}
async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) {
- let videoChannel: VideoChannelModel
+ let videoChannel: MChannelAccountDefault
let uri = search
if (isWebfingerSearch) {
}
async function searchVideoURI (url: string, res: express.Response) {
- let video: VideoModel
+ let video: MVideoAccountLightBlacklistAllFiles
// Check if we can fetch a remote video with the URL
if (isUserAbleToSearchRemoteURI(res)) {
import { sequelizeTypescript } from '../../../initializers/database'
import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
import { UserRegister } from '../../../../shared/models/users/user-register.model'
+import { MUser, MUserAccountDefault } from '@server/typings/models'
const auditLogger = auditLoggerFactory('users')
videoQuota: body.videoQuota,
videoQuotaDaily: body.videoQuotaDaily,
adminFlags: body.adminFlags || UserAdminFlag.NONE
- })
+ }) as MUser
const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
res.end()
}
-async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) {
+async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) {
const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
user.blocked = block
import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
import { updateAvatarValidator } from '../../../middlewares/validators/avatar'
import { updateActorAvatarFile } from '../../../lib/avatar'
-import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { VideoImportModel } from '../../../models/video/video-import'
import { AccountModel } from '../../../models/account/account'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
import { sendVerifyUserEmail } from '../../../lib/user'
-const auditLogger = auditLoggerFactory('users-me')
-
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
const meRouter = express.Router()
}
async function getUserVideoRating (req: express.Request, res: express.Response) {
- const videoId = res.locals.video.id
+ const videoId = res.locals.videoId.id
const accountId = +res.locals.oauth.token.User.Account.id
const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null)
await user.destroy()
- auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})))
-
return res.sendStatus(204)
}
let sendVerificationEmail = false
const user = res.locals.oauth.token.user
- const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
if (body.password !== undefined) user.password = body.password
if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
await userAccount.save({ transaction: t })
await sendUpdateActor(userAccount, t)
-
- auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
})
if (sendVerificationEmail === true) {
async function updateMyAvatar (req: express.Request, res: express.Response) {
const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
const user = res.locals.oauth.token.user
- const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
const userAccount = await AccountModel.load(user.Account.id)
const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
- auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
-
return res.json({ avatar: avatar.toFormattedJSON() })
}
setDefaultPagination,
userHistoryRemoveValidator
} from '../../../middlewares'
-import { UserModel } from '../../../models/account/user'
import { getFormattedObjects } from '../../../helpers/utils'
import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
import { sequelizeTypescript } from '../../../initializers'
import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
import { sendUpdateActor } from '../../lib/activitypub/send'
import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
-import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
+import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
import { setAsyncActorKeys } from '../../lib/activitypub'
import { AccountModel } from '../../models/account/account'
import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
import { CONFIG } from '../../initializers/config'
import { sequelizeTypescript } from '../../initializers/database'
+import { MChannelAccountDefault } from '@server/typings/models'
const auditLogger = auditLoggerFactory('channels')
const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
async function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body
- const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
+ const videoChannelCreated = await sequelizeTypescript.transaction(async t => {
const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
- return createVideoChannel(videoChannelInfo, account, t)
+ return createLocalVideoChannel(videoChannelInfo, account, t)
})
setAsyncActorKeys(videoChannelCreated.Actor)
}
}
- const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
+ const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault
await sendUpdateActor(videoChannelInstanceUpdated, t)
auditLogger.update(
import { CONFIG } from '../../initializers/config'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
-import { VideoModel } from '../../models/video/video'
+import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models'
const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
)
videoPlaylistRouter.get('/:playlistId',
- asyncMiddleware(videoPlaylistsGetValidator),
+ asyncMiddleware(videoPlaylistsGetValidator('summary')),
getVideoPlaylist
)
)
videoPlaylistRouter.get('/:playlistId/videos',
- asyncMiddleware(videoPlaylistsGetValidator),
+ asyncMiddleware(videoPlaylistsGetValidator('summary')),
paginationValidator,
setDefaultPagination,
optionalAuthenticate,
}
function getVideoPlaylist (req: express.Request, res: express.Response) {
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = res.locals.videoPlaylistSummary
if (videoPlaylist.isOutdated()) {
JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
description: videoPlaylistInfo.description,
privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE,
ownerAccountId: user.Account.id
- })
+ }) as MVideoPlaylistFull
videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
: undefined
- const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
- const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
+ const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => {
+ const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull
if (thumbnailModel) {
thumbnailModel.automaticallyGenerated = false
}
async function updateVideoPlaylist (req: express.Request, res: express.Response) {
- const videoPlaylistInstance = res.locals.videoPlaylist
+ const videoPlaylistInstance = res.locals.videoPlaylistFull
const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
}
async function removeVideoPlaylist (req: express.Request, res: express.Response) {
- const videoPlaylistInstance = res.locals.videoPlaylist
+ const videoPlaylistInstance = res.locals.videoPlaylistSummary
await sequelizeTypescript.transaction(async t => {
await videoPlaylistInstance.destroy({ transaction: t })
async function addVideoInPlaylist (req: express.Request, res: express.Response) {
const body: VideoPlaylistElementCreate = req.body
- const videoPlaylist = res.locals.videoPlaylist
- const video = res.locals.video
+ const videoPlaylist = res.locals.videoPlaylistFull
+ const video = res.locals.onlyVideo
- const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
+ const playlistElement = await sequelizeTypescript.transaction(async t => {
const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
const playlistElement = await VideoPlaylistElementModel.create({
async function updateVideoPlaylistElement (req: express.Request, res: express.Response) {
const body: VideoPlaylistElementUpdate = req.body
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = res.locals.videoPlaylistFull
const videoPlaylistElement = res.locals.videoPlaylistElement
const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
async function removeVideoFromPlaylist (req: express.Request, res: express.Response) {
const videoPlaylistElement = res.locals.videoPlaylistElement
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = res.locals.videoPlaylistFull
const positionToDelete = videoPlaylistElement.position
await sequelizeTypescript.transaction(async t => {
}
async function reorderVideosPlaylist (req: express.Request, res: express.Response) {
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = res.locals.videoPlaylistFull
const body: VideoPlaylistReorder = req.body
const start: number = body.startPosition
}
async function getVideoPlaylistVideos (req: express.Request, res: express.Response) {
- const videoPlaylistInstance = res.locals.videoPlaylist
+ const videoPlaylistInstance = res.locals.videoPlaylistSummary
const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
const server = await getServerActor()
return res.json(getFormattedObjects(resultList.data, resultList.total, options))
}
-async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) {
+async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) {
await videoPlaylist.Thumbnail.destroy()
videoPlaylist.Thumbnail = null
if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video)
}
-async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) {
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
import * as express from 'express'
import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared'
import { logger } from '../../../helpers/logger'
-import { getFormattedObjects } from '../../../helpers/utils'
+import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
import { sequelizeTypescript } from '../../../initializers'
import {
asyncMiddleware,
import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
import { Notifier } from '../../../lib/notifier'
import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
+import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
const auditLogger = auditLoggerFactory('abuse')
const abuseVideoRouter = express.Router()
// ---------------------------------------------------------------------------
async function listVideoAbuses (req: express.Request, res: express.Response) {
- const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort)
+ const user = res.locals.oauth.token.user
+ const serverActor = await getServerActor()
+
+ const resultList = await VideoAbuseModel.listForApi({
+ start: req.query.start,
+ count: req.query.count,
+ sort: req.query.sort,
+ serverAccountId: serverActor.Account.id,
+ user
+ })
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
}
async function reportVideoAbuse (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const body: VideoAbuseCreate = req.body
- const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => {
+ const videoAbuse = await sequelizeTypescript.transaction(async t => {
const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
const abuseToCreate = {
state: VideoAbuseState.PENDING
}
- const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
+ const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
videoAbuseInstance.Video = videoInstance
videoAbuseInstance.Account = reporterAccount
import * as express from 'express'
-import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
+import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
import { logger } from '../../../helpers/logger'
import { getFormattedObjects } from '../../../helpers/utils'
import {
setBlacklistSort,
setDefaultPagination,
videosBlacklistAddValidator,
+ videosBlacklistFiltersValidator,
videosBlacklistRemoveValidator,
- videosBlacklistUpdateValidator,
- videosBlacklistFiltersValidator
+ videosBlacklistUpdateValidator
} from '../../../middlewares'
import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
import { sequelizeTypescript } from '../../../initializers'
import { Notifier } from '../../../lib/notifier'
import { sendDeleteVideo } from '../../../lib/activitypub/send'
import { federateVideoIfNeeded } from '../../../lib/activitypub'
+import { MVideoBlacklistVideo } from '@server/typings/models'
const blacklistRouter = express.Router()
// ---------------------------------------------------------------------------
async function addVideoToBlacklist (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const body: VideoBlacklistCreate = req.body
const toCreate = {
type: VideoBlacklistType.MANUAL
}
- const blacklist = await VideoBlacklistModel.create(toCreate)
+ const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate)
blacklist.Video = videoInstance
if (body.unfederate === true) {
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()
}
async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) {
const videoBlacklist = res.locals.videoBlacklist
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
const unfederated = videoBlacklist.unfederated
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()
}
import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
+import { MVideoCaptionVideo } from '@server/typings/models'
const reqVideoCaptionAdd = createReqFiles(
[ 'captionfile' ],
// ---------------------------------------------------------------------------
async function listVideoCaptions (req: express.Request, res: express.Response) {
- const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id)
+ const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id)
return res.json(getFormattedObjects(data, data.length))
}
async function addVideoCaption (req: express.Request, res: express.Response) {
const videoCaptionPhysicalFile = req.files['captionfile'][0]
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoCaption = new VideoCaptionModel({
videoId: video.id,
language: req.params.captionLanguage
- })
+ }) as MVideoCaptionVideo
videoCaption.Video = video
// Move physical file
}
async function deleteVideoCaption (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoCaption = res.locals.videoCaption
await sequelizeTypescript.transaction(async t => {
import { AccountModel } from '../../../models/account/account'
import { Notifier } from '../../../lib/notifier'
import { Hooks } from '../../../lib/plugins/hooks'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoChannelModel } from '../../../models/video/video-channel'
-import { VideoModel } from '../../../models/video/video'
import { sendDeleteVideoComment } from '../../../lib/activitypub/send'
const auditLogger = auditLoggerFactory('comments')
// ---------------------------------------------------------------------------
async function listVideoThreads (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
let resultList: ResultList<VideoCommentModel>
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
- user: user
+ user
}, 'filter:api.video-threads.list.params')
resultList = await Hooks.wrapPromiseFun(
}
async function listVideoThreadComments (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.onlyVideo
const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
let resultList: ResultList<VideoCommentModel>
return createVideoComment({
text: videoCommentInfo.text,
inReplyToComment: null,
- video: res.locals.video,
+ video: res.locals.videoAll,
account
}, t)
})
return createVideoComment({
text: videoCommentInfo.text,
- inReplyToComment: res.locals.videoComment,
- video: res.locals.video,
+ inReplyToComment: res.locals.videoCommentFull,
+ video: res.locals.videoAll,
account
}, t)
})
}
async function removeVideoComment (req: express.Request, res: express.Response) {
- const videoCommentInstance = res.locals.videoComment
+ const videoCommentInstance = res.locals.videoCommentFull
await sequelizeTypescript.transaction(async t => {
await videoCommentInstance.destroy({ transaction: t })
import * as express from 'express'
import * as magnetUtil from 'magnet-uri'
-import 'multer'
import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
import { MIMETYPES } from '../../../initializers/constants'
import { JobQueue } from '../../../lib/job-queue/job-queue'
import { join } from 'path'
import { isArray } from '../../../helpers/custom-validators/misc'
-import { VideoChannelModel } from '../../../models/video/video-channel'
import * as Bluebird from 'bluebird'
import * as parseTorrent from 'parse-torrent'
import { getSecureTorrentName } from '../../../helpers/utils'
import { sequelizeTypescript } from '../../../initializers/database'
import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
-import { ThumbnailModel } from '../../../models/video/thumbnail'
-import { UserModel } from '../../../models/account/user'
+import {
+ MChannelAccountDefault,
+ MThumbnail,
+ MUser,
+ MVideoAccountDefault,
+ MVideoTag,
+ MVideoThumbnailAccountDefault,
+ MVideoWithBlacklistLight
+} from '@server/typings/models'
+import { MVideoImport, MVideoImportFormattable } from '@server/typings/models/video/video-import'
const auditLogger = auditLoggerFactory('video-imports')
const videoImportsRouter = express.Router()
category: body.category || importData.category,
licence: body.licence || importData.licence,
language: body.language || undefined,
- commentsEnabled: body.commentsEnabled || true,
- downloadEnabled: body.downloadEnabled || true,
+ commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true"
+ downloadEnabled: body.downloadEnabled !== false,
waitTranscoding: body.waitTranscoding || false,
state: VideoState.TO_IMPORT,
nsfw: body.nsfw || importData.nsfw || false,
}
function insertIntoDB (parameters: {
- video: VideoModel,
- thumbnailModel: ThumbnailModel,
- previewModel: ThumbnailModel,
- videoChannel: VideoChannelModel,
+ video: MVideoThumbnailAccountDefault,
+ thumbnailModel: MThumbnail,
+ previewModel: MThumbnail,
+ videoChannel: MChannelAccountDefault,
tags: string[],
- videoImportAttributes: Partial<VideoImportModel>,
- user: UserModel
-}): Bluebird<VideoImportModel> {
+ videoImportAttributes: Partial<MVideoImport>,
+ user: MUser
+}): Bluebird<MVideoImportFormattable> {
const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
return sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
// Save video object in database
- const videoCreated = await video.save(sequelizeOptions)
+ const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag)
videoCreated.VideoChannel = videoChannel
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
await autoBlacklistVideoIfNeeded({
- video,
+ video: videoCreated,
user,
notify: false,
isRemote: false,
const videoImport = await VideoImportModel.create(
Object.assign({ videoId: videoCreated.id }, videoImportAttributes),
sequelizeOptions
- )
+ ) as MVideoImportFormattable
videoImport.Video = videoCreated
return videoImport
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
import { Hooks } from '../../../lib/plugins/hooks'
+import { MVideoDetails, MVideoFullLight } from '@server/typings/models'
const auditLogger = auditLoggerFactory('videos')
const videosRouter = express.Router()
licence: videoInfo.licence,
language: videoInfo.language,
commentsEnabled: videoInfo.commentsEnabled || false,
- downloadEnabled: videoInfo.downloadEnabled || true,
+ downloadEnabled: videoInfo.downloadEnabled !== false, // If the value is not "false", the default is "true"
waitTranscoding: videoInfo.waitTranscoding || false,
state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED,
nsfw: videoInfo.nsfw || false,
originallyPublishedAt: videoInfo.originallyPublishedAt
}
- const video = new VideoModel(videoData)
+ const video = new VideoModel(videoData) as MVideoDetails
video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
const videoFile = new VideoFileModel({
const { videoCreated } = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
- const videoCreated = await video.save(sequelizeOptions)
+ const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
await videoCreated.addAndSaveThumbnail(previewModel, t)
}
async function updateVideo (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const videoFieldsSave = videoInstance.toJSON()
const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
const videoInfoToUpdate: VideoUpdate = req.body
}
}
- const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
+ const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight
if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
const video = await Hooks.wrapPromiseFun(
VideoModel.loadForGetAPI,
- { id: res.locals.video.id, userId },
+ { id: res.locals.onlyVideoWithRights.id, userId },
'filter:api.video.get.result'
)
}
async function viewVideo (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const ip = req.ip
const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
}
async function getVideoDescription (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
let description = ''
if (videoInstance.isOwned()) {
}
async function removeVideo (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
await sequelizeTypescript.transaction(async t => {
await videoInstance.destroy({ transaction: t })
import { changeVideoChannelShare } from '../../../lib/activitypub'
import { sendUpdateVideo } from '../../../lib/activitypub/send'
import { VideoModel } from '../../../models/video/video'
+import { MVideoFullLight } from '@server/typings/models'
const ownershipVideoRouter = express.Router()
// ---------------------------------------------------------------------------
async function giveVideoOwnership (req: express.Request, res: express.Response) {
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const initiatorAccountId = res.locals.oauth.token.User.Account.id
const nextOwner = res.locals.nextOwner
targetVideo.channelId = channel.id
- const targetVideoUpdated = await targetVideo.save({ transaction: t })
+ const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
targetVideoUpdated.VideoChannel = channel
if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) {
async function rateVideo (req: express.Request, res: express.Response) {
const body: UserVideoRateUpdate = req.body
const rateType = body.rating
- const videoInstance = res.locals.video
+ const videoInstance = res.locals.videoAll
const userAccount = res.locals.oauth.token.User.Account
await sequelizeTypescript.transaction(async t => {
const user = res.locals.oauth.token.User
const body: UserWatchingVideo = req.body
- const { id: videoId } = res.locals.video as { id: number }
+ const { id: videoId } = res.locals.videoId
await UserVideoHistoryModel.upsert({
videoId,
async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
const start = 0
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoId: number = video ? video.id : undefined
const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
// ---------------------------------------------------------------------------
function generateOEmbed (req: express.Request, res: express.Response) {
- const video = res.locals.video
+ const video = res.locals.videoAll
const webserverUrl = WEBSERVER.URL
const maxHeight = parseInt(req.query.maxheight, 10)
const maxWidth = parseInt(req.query.maxwidth, 10)
return res.send(json).end()
}
-async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function downloadTorrent (req: express.Request, res: express.Response) {
const { video, videoFile } = getVideoAndFile(req, res)
if (!videoFile) return res.status(404).end()
return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
}
-async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function downloadVideoFile (req: express.Request, res: express.Response) {
const { video, videoFile } = getVideoAndFile(req, res)
if (!videoFile) return res.status(404).end()
function getVideoAndFile (req: express.Request, res: express.Response) {
const resolution = parseInt(req.params.resolution, 10)
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoFile = video.VideoFiles.find(f => f.resolution === resolution)
// ---------------------------------------------------------------------------
function webfingerController (req: express.Request, res: express.Response) {
- const actor = res.locals.actor
+ const actor = res.locals.actorFull
const json = {
subject: req.query.resource,
import { signJsonLDObject } from './peertube-crypto'
import { pageToStartAndCount } from './core-utils'
import { parse } from 'url'
+import { MActor } from '../typings/models'
function activityPubContextify <T> (data: T) {
return Object.assign(data, {
}
-function buildSignedActivity (byActor: ActorModel, data: Object) {
+function buildSignedActivity (byActor: MActor, data: Object) {
const activity = activityPubContextify(data)
return signJsonLDObject(byActor, activity) as Promise<Activity>
import { ActorModel } from '../models/activitypub/actor'
+import * as Bluebird from 'bluebird'
+import { MActorFull, MActorAccountChannelId } from '../typings/models'
-type ActorFetchByUrlType = 'all' | 'actor-and-association-ids'
-function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) {
+type ActorFetchByUrlType = 'all' | 'association-ids'
+
+function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> {
if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url)
- if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url)
+ if (fetchType === 'association-ids') return ActorModel.loadByUrl(url)
}
export {
import { join } from 'path'
import { CONFIG } from '../initializers/config'
-import { VideoCaptionModel } from '../models/video/video-caption'
import * as srt2vtt from 'srt-to-vtt'
-import { createReadStream, createWriteStream, remove, move } from 'fs-extra'
+import { createReadStream, createWriteStream, move, remove } from 'fs-extra'
+import { MVideoCaptionFormattable } from '@server/typings/models'
-async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) {
+async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) {
const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
const destination = join(videoCaptionsDir, videoCaption.getCaptionName())
import * as AsyncLRU from 'async-lru'
import * as jsonld from 'jsonld'
-import * as jsig from 'jsonld-signatures'
import { logger } from './logger'
const CACHE = {
lru.get(url, cb)
}
-jsig.use('jsonld', jsonld)
-
-export { jsig, jsonld }
+export { jsonld }
validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
}
-const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]'
+const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]'
const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
function isActorPreferredUsernameValid (preferredUsername: string) {
return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
return exists(actor) &&
isActivityPubUrlValid(actor.id) &&
isActorTypeValid(actor.type) &&
- isActivityPubUrlValid(actor.following) &&
- isActivityPubUrlValid(actor.followers) &&
isActivityPubUrlValid(actor.inbox) &&
- isActivityPubUrlValid(actor.outbox) &&
isActorPreferredUsernameValid(actor.preferredUsername) &&
isActivityPubUrlValid(actor.url) &&
isActorPublicKeyObjectValid(actor.publicKey) &&
isActorEndpointsObjectValid(actor.endpoints) &&
- setValidAttributedTo(actor) &&
- // If this is not an account, it should be attributed to an account
+ (!actor.outbox || isActivityPubUrlValid(actor.outbox)) &&
+ (!actor.following || isActivityPubUrlValid(actor.following)) &&
+ (!actor.followers || isActivityPubUrlValid(actor.followers)) &&
+
+ setValidAttributedTo(actor) &&
+ // If this is a group (a channel), it should be attributed to an account
// In PeerTube we use this to attach a video channel to a specific account
- (actor.type === 'Person' || actor.attributedTo.length !== 0)
+ (actor.type !== 'Group' || actor.attributedTo.length !== 0)
}
function isActorFollowingCountValid (value: string) {
import { Response } from 'express'
-import * as validator from 'validator'
import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership'
-import { UserModel } from '../../models/account/user'
+import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
+import { MUserId } from '@server/typings/models'
-export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> {
- const videoChangeOwnership = await loadVideoChangeOwnership(id)
+export async function doesChangeVideoOwnershipExist (id: number, res: Response) {
+ const videoChangeOwnership = await VideoChangeOwnershipModel.load(id)
if (!videoChangeOwnership) {
res.status(404)
return true
}
-async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> {
- if (validator.isInt(id)) {
- return VideoChangeOwnershipModel.load(parseInt(id, 10))
- }
-
- return undefined
-}
-
-export function checkUserCanTerminateOwnershipChange (
- user: UserModel,
- videoChangeOwnership: VideoChangeOwnershipModel,
- res: Response
-): boolean {
+export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) {
if (videoChangeOwnership.NextOwner.userId === user.id) {
return true
}
import { Response } from 'express'
import { AccountModel } from '../../models/account/account'
import * as Bluebird from 'bluebird'
+import { MAccountDefault } from '../../typings/models'
function doesAccountIdExist (id: number, res: Response, sendNotFound = true) {
const promise = AccountModel.load(id)
}
function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) {
- return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound)
+ const promise = AccountModel.loadByNameWithHost(nameWithDomain)
+
+ return doesAccountExist(promise, res, sendNotFound)
}
-async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) {
+async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) {
const account = await p
if (!account) {
-import * as express from 'express'
-import { VideoChannelModel } from '../../models/video/video-channel'
+import { Response } from 'express'
+import { VideoAbuseModel } from '../../models/video/video-abuse'
-async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
- const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
+async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
+ const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
- return processVideoChannelExist(videoChannel, res)
-}
-
-async function doesVideoChannelIdExist (id: number, res: express.Response) {
- const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
-
- return processVideoChannelExist(videoChannel, res)
-}
-
-async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
- const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
-
- return processVideoChannelExist(videoChannel, res)
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- doesLocalVideoChannelNameExist,
- doesVideoChannelIdExist,
- doesVideoChannelNameWithHostExist
-}
-
-function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) {
- if (!videoChannel) {
+ if (videoAbuse === null) {
res.status(404)
- .json({ error: 'Video channel not found' })
+ .json({ error: 'Video abuse not found' })
.end()
return false
}
- res.locals.videoChannel = videoChannel
+ res.locals.videoAbuse = videoAbuse
return true
}
+
+// ---------------------------------------------------------------------------
+
+export {
+ doesVideoAbuseExist
+}
-import { VideoModel } from '../../models/video/video'
import { Response } from 'express'
import { VideoCaptionModel } from '../../models/video/video-caption'
+import { MVideoId } from '@server/typings/models'
-async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) {
+async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) {
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language)
if (!videoCaption) {
-import { Response } from 'express'
-import { VideoAbuseModel } from '../../models/video/video-abuse'
+import * as express from 'express'
+import { VideoChannelModel } from '../../models/video/video-channel'
+import { MChannelAccountDefault } from '@server/typings/models'
-async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
- const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
+async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
+ const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
- if (videoAbuse === null) {
- res.status(404)
- .json({ error: 'Video abuse not found' })
- .end()
+ return processVideoChannelExist(videoChannel, res)
+}
- return false
- }
+async function doesVideoChannelIdExist (id: number, res: express.Response) {
+ const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
- res.locals.videoAbuse = videoAbuse
- return true
+ return processVideoChannelExist(videoChannel, res)
+}
+
+async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) {
+ const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain)
+
+ return processVideoChannelExist(videoChannel, res)
}
// ---------------------------------------------------------------------------
export {
- doesVideoAbuseExist
+ doesLocalVideoChannelNameExist,
+ doesVideoChannelIdExist,
+ doesVideoChannelNameWithHostExist
+}
+
+function processVideoChannelExist (videoChannel: MChannelAccountDefault, res: express.Response) {
+ if (!videoChannel) {
+ res.status(404)
+ .json({ error: 'Video channel not found' })
+ .end()
+
+ return false
+ }
+
+ res.locals.videoChannel = videoChannel
+ return true
}
import * as express from 'express'
import { VideoPlaylistModel } from '../../models/video/video-playlist'
+import { MVideoPlaylist } from '../../typings/models/video/video-playlist'
-async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') {
- const videoPlaylist = fetchType === 'summary'
- ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
- : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
+export type VideoPlaylistFetchType = 'summary' | 'all'
+async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') {
+ if (fetchType === 'summary') {
+ const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined)
+ res.locals.videoPlaylistSummary = videoPlaylist
+ return handleVideoPlaylist(videoPlaylist, res)
+ }
+
+ const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined)
+ res.locals.videoPlaylistFull = videoPlaylist
+
+ return handleVideoPlaylist(videoPlaylist, res)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ doesVideoPlaylistExist
+}
+
+// ---------------------------------------------------------------------------
+
+function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) {
if (!videoPlaylist) {
res.status(404)
.json({ error: 'Video playlist not found' })
return false
}
- res.locals.videoPlaylist = videoPlaylist
return true
}
-
-// ---------------------------------------------------------------------------
-
-export {
- doesVideoPlaylistExist
-}
import { Response } from 'express'
import { fetchVideo, VideoFetchType } from '../video'
-import { UserModel } from '../../models/account/user'
import { UserRight } from '../../../shared/models/users'
import { VideoChannelModel } from '../../models/video/video-channel'
-import { VideoModel } from '../../models/video/video'
+import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models'
async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') {
const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined
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 as MVideoThumbnail
+ break
+
+ case 'only-video-with-rights':
+ res.locals.onlyVideoWithRights = video as MVideoWithRights
+ break
+ }
+
return true
}
-async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) {
+async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) {
if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) {
const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId)
if (videoChannel === null) {
return true
}
-function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) {
+function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) {
// Retrieve the user who did the request
if (video.isOwned() === false) {
res.status(403)
import { Request } from 'express'
import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
-import { ActorModel } from '../models/activitypub/actor'
import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils'
-import { jsig, jsonld } from './custom-jsonld-signature'
+import { jsonld } from './custom-jsonld-signature'
import { logger } from './logger'
import { cloneDeep } from 'lodash'
-import { createVerify } from 'crypto'
+import { createSign, createVerify } from 'crypto'
import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils'
import * as bcrypt from 'bcrypt'
+import { MActor } from '../typings/models'
const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
return true
}
-function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean {
+function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean {
return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true
}
// JSONLD
-async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> {
+function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
if (signedDocument.signature.type === 'RsaSignature2017') {
- // Mastodon algorithm
- const res = await isJsonLDRSA2017Verified(fromActor, signedDocument)
- // Success? If no, try with our library
- if (res === true) return true
+ return isJsonLDRSA2017Verified(fromActor, signedDocument)
}
- const publicKeyObject = {
- '@context': jsig.SECURITY_CONTEXT_URL,
- id: fromActor.url,
- type: 'CryptographicKey',
- owner: fromActor.url,
- publicKeyPem: fromActor.publicKey
- }
-
- const publicKeyOwnerObject = {
- '@context': jsig.SECURITY_CONTEXT_URL,
- id: fromActor.url,
- publicKey: [ publicKeyObject ]
- }
+ logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
- const options = {
- publicKey: publicKeyObject,
- publicKeyOwner: publicKeyOwnerObject
- }
-
- return jsig.promises
- .verify(signedDocument, options)
- .then((result: { verified: boolean }) => result.verified)
- .catch(err => {
- logger.error('Cannot check signature.', { err })
- return false
- })
+ return Promise.resolve(false)
}
// Backward compatibility with "other" implementations
-async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) {
- function hash (obj: any): Promise<any> {
- return jsonld.promises
- .normalize(obj, {
- algorithm: 'URDNA2015',
- format: 'application/n-quads'
- })
- .then(res => sha256(res))
- }
-
- const signatureCopy = cloneDeep(signedDocument.signature)
- Object.assign(signatureCopy, {
- '@context': [
- 'https://w3id.org/security/v1',
- { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
- ]
- })
- delete signatureCopy.type
- delete signatureCopy.id
- delete signatureCopy.signatureValue
-
- const docWithoutSignature = cloneDeep(signedDocument)
- delete docWithoutSignature.signature
-
+async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
const [ documentHash, optionsHash ] = await Promise.all([
- hash(docWithoutSignature),
- hash(signatureCopy)
+ createDocWithoutSignatureHash(signedDocument),
+ createSignatureHash(signedDocument.signature)
])
const toVerify = optionsHash + documentHash
return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
}
-function signJsonLDObject (byActor: ActorModel, data: any) {
- const options = {
- privateKeyPem: byActor.privateKey,
+async function signJsonLDObject (byActor: MActor, data: any) {
+ const signature = {
+ type: 'RsaSignature2017',
creator: byActor.url,
- algorithm: 'RsaSignature2017'
+ created: new Date().toISOString()
}
- return jsig.promises.sign(data, options)
+ const [ documentHash, optionsHash ] = await Promise.all([
+ createDocWithoutSignatureHash(data),
+ createSignatureHash(signature)
+ ])
+
+ const toSign = optionsHash + documentHash
+
+ const sign = createSign('RSA-SHA256')
+ sign.update(toSign, 'utf8')
+
+ const signatureValue = sign.sign(byActor.privateKey, 'base64')
+ Object.assign(signature, { signatureValue })
+
+ return Object.assign(data, { signature })
}
// ---------------------------------------------------------------------------
}
// ---------------------------------------------------------------------------
+
+function hash (obj: any): Promise<any> {
+ return jsonld.promises
+ .normalize(obj, {
+ algorithm: 'URDNA2015',
+ format: 'application/n-quads'
+ })
+ .then(res => sha256(res))
+}
+
+function createSignatureHash (signature: any) {
+ const signatureCopy = cloneDeep(signature)
+ Object.assign(signatureCopy, {
+ '@context': [
+ 'https://w3id.org/security/v1',
+ { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
+ ]
+ })
+
+ delete signatureCopy.type
+ delete signatureCopy.id
+ delete signatureCopy.signatureValue
+
+ return hash(signatureCopy)
+}
+
+function createDocWithoutSignatureHash (doc: any) {
+ const docWithoutSignature = cloneDeep(doc)
+ delete docWithoutSignature.signature
+
+ return hash(docWithoutSignature)
+}
return raw.toString('hex')
}
-interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V }
+interface FormattableToJSON<U, V> {
+ toFormattedJSON (args?: U): V
+}
+
function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) {
const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg))
import { VideoModel } from '../models/video/video'
+import * as Bluebird from 'bluebird'
+import {
+ MVideoAccountLightBlacklistAllFiles,
+ MVideoFullLight,
+ MVideoIdThumbnail,
+ MVideoThumbnail,
+ MVideoWithRights
+} from '@server/typings/models'
+import { Response } from 'express'
type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none'
-function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) {
+function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight>
+function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail>
+function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights>
+function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail>
+function fetchVideo (
+ id: number | string,
+ fetchType: VideoFetchType,
+ userId?: number
+): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail>
+function fetchVideo (
+ id: number | string,
+ fetchType: VideoFetchType,
+ userId?: number
+): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> {
if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId)
if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
}
type VideoFetchByUrlType = 'all' | 'only-video'
-function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) {
+
+function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles>
+function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail>
+function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
+function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> {
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url)
if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
}
+function getVideo (res: Response) {
+ return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId
+}
+
+function getVideoWithAttributes (res: Response) {
+ return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights
+}
+
export {
VideoFetchType,
VideoFetchByUrlType,
fetchVideo,
+ getVideo,
+ getVideoWithAttributes,
fetchVideoByUrl
}
import { isTestInstance } from './core-utils'
import { isActivityPubUrlValid } from './custom-validators/activitypub/misc'
import { WEBSERVER } from '../initializers/constants'
+import { MActorFull } from '../typings/models'
const webfinger = new WebFinger({
webfist_fallback: false,
const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg
const [ name, host ] = uri.split('@')
- let actor: ActorModel
+ let actor: MActorFull
if (!host || host === WEBSERVER.HOST) {
actor = await ActorModel.loadLocalByName(name)
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 420
+const LAST_MIGRATION_VERSION = 425
// ---------------------------------------------------------------------------
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize,
+ db: any
+}): Promise<void> {
+ const data = {
+ type: Sequelize.STRING,
+ allowNull: true
+ }
+
+ await utils.queryInterface.changeColumn('actor', 'outboxUrl', data)
+ await utils.queryInterface.changeColumn('actor', 'followersUrl', data)
+ await utils.queryInterface.changeColumn('actor', 'followingUrl', data)
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
import { getServerActor } from '../../helpers/utils'
import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor'
import { sequelizeTypescript } from '../../initializers/database'
+import {
+ MAccount,
+ MAccountDefault,
+ MActor,
+ MActorAccountChannelId,
+ MActorAccountChannelIdActor,
+ MActorAccountId,
+ MActorDefault,
+ MActorFull,
+ MActorFullActor,
+ MActorId,
+ MChannel,
+ MChannelAccountDefault
+} from '../../typings/models'
// Set account keys, this could be long so process after the account creation and do not block the client
-function setAsyncActorKeys (actor: ActorModel) {
+function setAsyncActorKeys <T extends MActor> (actor: T) {
return createPrivateAndPublicKeys()
.then(({ publicKey, privateKey }) => {
- actor.set('publicKey', publicKey)
- actor.set('privateKey', privateKey)
+ actor.publicKey = publicKey
+ actor.privateKey = privateKey
return actor.save()
})
.catch(err => {
})
}
+function getOrCreateActorAndServerAndModel (
+ activityActor: string | ActivityPubActor,
+ fetchType: 'all',
+ recurseIfNeeded?: boolean,
+ updateCollections?: boolean
+): Promise<MActorFullActor>
+
+function getOrCreateActorAndServerAndModel (
+ activityActor: string | ActivityPubActor,
+ fetchType?: 'association-ids',
+ recurseIfNeeded?: boolean,
+ updateCollections?: boolean
+): Promise<MActorAccountChannelId>
+
async function getOrCreateActorAndServerAndModel (
activityActor: string | ActivityPubActor,
- fetchType: ActorFetchByUrlType = 'actor-and-association-ids',
+ fetchType: ActorFetchByUrlType = 'association-ids',
recurseIfNeeded = true,
updateCollections = false
-) {
+): Promise<MActorFullActor | MActorAccountChannelId> {
const actorUrl = getAPId(activityActor)
let created = false
let accountPlaylistsUrl: string
// Create the attributed to actor
// In PeerTube a video channel is owned by an account
- let ownerActor: ActorModel = undefined
+ let ownerActor: MActorFullActor
if (recurseIfNeeded === true && result.actor.type === 'Group') {
const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person')
if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url)
accountPlaylistsUrl = result.playlists
}
- if (actor.Account) actor.Account.Actor = actor
- if (actor.VideoChannel) actor.VideoChannel.Actor = actor
+ if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor
+ if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor
const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType)
if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.')
sharedInboxUrl: WEBSERVER.URL + '/inbox',
followersUrl: url + '/followers',
followingUrl: url + '/following'
- })
+ }) as MActor
}
async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) {
actorInstance.followingUrl = attributes.following
}
-async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) {
+type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string }
+async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) {
if (info.name !== undefined) {
if (actor.avatarId) {
try {
return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
}
-async function refreshActorIfNeeded (
- actorArg: ActorModel,
+async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> (
+ actorArg: T,
fetchedType: ActorFetchByUrlType
-): Promise<{ actor: ActorModel, refreshed: boolean }> {
+): Promise<{ actor: T | MActorFull, refreshed: boolean }> {
if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
// We need more attributes
- const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
+ const actor = fetchedType === 'all'
+ ? actorArg as MActorFull
+ : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
try {
let actorUrl: string
function saveActorAndServerAndModelIfNotExist (
result: FetchRemoteActorResult,
- ownerActor?: ActorModel,
+ ownerActor?: MActorFullActor,
t?: Transaction
-): Bluebird<ActorModel> | Promise<ActorModel> {
+): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
let actor = result.actor
if (t !== undefined) return save(t)
// Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists
// (which could be false in a retried query)
- const [ actorCreated ] = await ActorModel.findOrCreate({
+ const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({
defaults: actor.toJSON(),
where: {
url: actor.url
})
if (actorCreated.type === 'Person' || actorCreated.type === 'Application') {
- actorCreated.Account = await saveAccount(actorCreated, result, t)
+ actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountDefault
actorCreated.Account.Actor = actorCreated
} else if (actorCreated.type === 'Group') { // Video channel
- actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t)
- actorCreated.VideoChannel.Actor = actorCreated
- actorCreated.VideoChannel.Account = ownerActor.Account
+ const channel = await saveVideoChannel(actorCreated, result, ownerActor, t)
+ actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: ownerActor.Account })
}
actorCreated.Server = server
}
type FetchRemoteActorResult = {
- actor: ActorModel
+ actor: MActor
name: string
summary: string
support?: string
}
}
-async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
+async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) {
const [ accountCreated ] = await AccountModel.findOrCreate({
defaults: {
name: result.name,
transaction: t
})
- return accountCreated
+ return accountCreated as MAccount
}
-async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) {
+async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) {
const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
defaults: {
name: result.name,
transaction: t
})
- return videoChannelCreated
+ return videoChannelCreated as MChannel
}
import { ACTIVITY_PUB } from '../../initializers/constants'
import { ActorModel } from '../../models/activitypub/actor'
import { VideoModel } from '../../models/video/video'
-import { VideoCommentModel } from '../../models/video/video-comment'
import { VideoShareModel } from '../../models/video/video-share'
-import { ActorModelOnly } from '../../typings/models'
+import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models'
-function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience {
+function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience {
return {
to: [ video.VideoChannel.Account.Actor.url ],
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
}
function getVideoCommentAudience (
- videoComment: VideoCommentModel,
- threadParentComments: VideoCommentModel[],
- actorsInvolvedInVideo: ActorModel[],
+ videoComment: MCommentOwnerVideo,
+ threadParentComments: MCommentOwner[],
+ actorsInvolvedInVideo: MActorFollowersUrl[],
isOrigin = false
): ActivityAudience {
const to = [ ACTIVITY_PUB.PUBLIC ]
}
}
-function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience {
+function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
return {
to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
cc: []
}
}
-async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) {
- const actors = await VideoShareModel.loadActorsByShare(video.id, t)
+async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) {
+ const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t)
- const videoActor = video.VideoChannel && video.VideoChannel.Account
- ? video.VideoChannel.Account.Actor
- : await ActorModel.loadAccountActorByVideoId(video.id, t)
+ const videoAll = video as VideoModel
+
+ const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account
+ ? videoAll.VideoChannel.Account.Actor
+ : await ActorModel.loadFromAccountByVideoId(video.id, t)
actors.push(videoActor)
return actors
}
-function getAudience (actorSender: ActorModelOnly, isPublic = true) {
+function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
return buildAudience([ actorSender.followersUrl ], isPublic)
}
import { CacheFileObject } from '../../../shared/index'
-import { VideoModel } from '../../models/video/video'
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
import { Transaction } from 'sequelize'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
+import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models'
-function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) {
+function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) {
if (cacheFileObject.url.mediaType === 'application/x-mpegURL') {
const url = cacheFileObject.url
}
}
-async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) {
+async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
if (!redundancyModel) {
}
}
-function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) {
+function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor)
return VideoRedundancyModel.create(attributes, { transaction: t })
function updateCacheFile (
cacheFileObject: CacheFileObject,
- redundancyModel: VideoRedundancyModel,
- video: VideoModel,
- byActor: { id?: number },
+ redundancyModel: MVideoRedundancy,
+ video: MVideoWithAllFiles,
+ byActor: MActorId,
t: Transaction
) {
if (redundancyModel.actorId !== byActor.id) {
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
import { crawlCollectionPage } from './crawl'
import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { AccountModel } from '../../models/account/account'
import { isArray } from '../../helpers/custom-validators/misc'
import { getOrCreateActorAndServerAndModel } from './actor'
import { logger } from '../../helpers/logger'
import { getOrCreateVideoAndAccountAndChannel } from './videos'
import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist'
import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
-import { VideoModel } from '../../models/video/video'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaylistMiniatureFromUrl } from '../thumbnail'
import { FilteredModelAttributes } from '../../typings/sequelize'
-import { AccountModelId } from '../../typings/models'
+import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models'
+import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist'
-function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) {
+function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
return {
}
}
-function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
return {
position: elementObject.position,
url: elementObject.id,
}
}
-async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) {
+async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) {
await Bluebird.map(playlistUrls, async playlistUrl => {
try {
const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl)
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
}
-async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) {
+async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to)
if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) {
}
}
- const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true })
+ const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true })
let accItems: string[] = []
await crawlCollectionPage<string>(playlistObject.id, items => {
return resetVideoPlaylistElements(accItems, refreshedPlaylist)
}
-async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> {
+async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> {
if (!videoPlaylist.isOutdated()) return videoPlaylist
try {
// ---------------------------------------------------------------------------
-async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
+async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) {
const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
await Bluebird.map(elementUrls, async elementUrl => {
import { ActivityAccept } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { addFetchOutboxJob } from '../actor'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorDefault, MActorSignature } from '../../../typings/models'
async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
const { byActor: targetActor, inboxActor } = options
// ---------------------------------------------------------------------------
-async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) {
+async function processAccept (actor: MActorDefault, targetActor: MActorSignature) {
const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
if (!follow) throw new Error('Cannot find associated follow.')
import { forwardVideoRelatedActivity } from '../send/utils'
import { getOrCreateVideoAndAccountAndChannel } from '../videos'
import { Notifier } from '../../notifier'
-import { VideoModel } from '../../../models/video/video'
import { logger } from '../../../helpers/logger'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
const { activity, byActor: actorAnnouncer } = options
// ---------------------------------------------------------------------------
-async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) {
+async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) {
const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
- let video: VideoModel
+ let video: MVideoAccountLightBlacklistAllFiles
let videoCreated: boolean
try {
import { Notifier } from '../../notifier'
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
import { createOrUpdateVideoPlaylist } from '../playlist'
-import { VideoModel } from '../../../models/video/video'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { VideoCommentModel } from '../../../models/video/video-comment'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
const { activity, byActor } = options
return video
}
-async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) {
+async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) {
const cacheFile = activity.object as CacheFileObject
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
}
}
-async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) {
+async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) {
const commentObject = activity.object as VideoCommentObject
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
- let video: VideoModel
+ let video: MVideoAccountLightBlacklistAllFiles
let created: boolean
- let comment: VideoCommentModel
+ let comment: MCommentOwnerVideo
try {
const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
video = resolveThreadResult.video
if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
}
-async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) {
+async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) {
const playlistObject = activity.object as PlaylistObject
const byAccount = byActor.Account
import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers'
-import { AccountModel } from '../../../models/account/account'
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
-import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { forwardVideoRelatedActivity } from '../send/utils'
import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models'
async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
const { activity, byActor } = options
if (byActorFull.type === 'Person') {
if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.')
- byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel
- return retryTransactionWrapper(processDeleteAccount, byActorFull.Account)
+ const accountToDelete = byActorFull.Account as MAccountActor
+ accountToDelete.Actor = byActorFull
+
+ return retryTransactionWrapper(processDeleteAccount, accountToDelete)
} else if (byActorFull.type === 'Group') {
if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.')
- byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel
- return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel)
+ const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor
+ channelToDelete.Actor = byActorFull
+
+ return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete)
}
}
// ---------------------------------------------------------------------------
-async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
+async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) {
logger.debug('Removing remote video "%s".', videoToDelete.uuid)
await sequelizeTypescript.transaction(async t => {
logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
}
-async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) {
+async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) {
logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
await sequelizeTypescript.transaction(async t => {
logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
}
-async function processDeleteAccount (accountToRemove: AccountModel) {
+async function processDeleteAccount (accountToRemove: MAccountActor) {
logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
await sequelizeTypescript.transaction(async t => {
logger.info('Remote account %s removed.', accountToRemove.Actor.url)
}
-async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
+async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) {
logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
await sequelizeTypescript.transaction(async t => {
logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
}
-function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
+function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) {
logger.debug('Removing remote video comment "%s".', videoComment.url)
return sequelizeTypescript.transaction(async t => {
import { forwardVideoRelatedActivity } from '../send/utils'
import { getVideoDislikeActivityPubUrl } from '../url'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) {
+async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) {
const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object
const byAccount = byActor.Account
import { Notifier } from '../../notifier'
import { getAPId } from '../../../helpers/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models'
async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) {
+async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) {
const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
- logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
-
const account = byActor.Account
if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url)
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object })
+ const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ]
- const videoAbuse = await sequelizeTypescript.transaction(async t => {
- const videoAbuseData = {
- reporterAccountId: account.id,
- reason: flag.content,
- videoId: video.id,
- state: VideoAbuseState.PENDING
- }
+ for (const object of objects) {
+ try {
+ logger.debug('Reporting remote abuse for video %s.', getAPId(object))
+
+ const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object })
- const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t })
- videoAbuseInstance.Video = video
+ const videoAbuse = await sequelizeTypescript.transaction(async t => {
+ const videoAbuseData = {
+ reporterAccountId: account.id,
+ reason: flag.content,
+ videoId: video.id,
+ state: VideoAbuseState.PENDING
+ }
- logger.info('Remote abuse for video uuid %s created', flag.object)
+ const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo
+ videoAbuseInstance.Video = video
- return videoAbuseInstance
- })
+ logger.info('Remote abuse for video uuid %s created', flag.object)
- Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
+ return videoAbuseInstance
+ })
+
+ Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse)
+ } catch (err) {
+ logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err })
+ }
+ }
}
import { getServerActor } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
-import { ActorFollowModelLight } from '../../../typings/models/actor-follow'
+import { MAccount, MActorFollowActors, MActorFollowFull, MActorSignature } from '../../../typings/models'
async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processFollow (byActor: SignatureActorModel, targetActorURL: string) {
+async function processFollow (byActor: MActorSignature, targetActorURL: string) {
const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => {
const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
await sendReject(byActor, targetActor)
- return { actorFollow: undefined }
+ return { actorFollow: undefined as MActorFollowActors }
}
- const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({
+ const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
where: {
actorId: byActor.id,
targetActorId: targetActor.id
state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted'
},
transaction: t
- }) as [ ActorFollowModelLight, boolean ]
+ })
if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) {
actorFollow.state = 'accepted'
if (!actorFollow) return
if (created) {
- if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
- else Notifier.Instance.notifyOfNewUserFollow(actorFollow)
+ if (isFollowingInstance) {
+ Notifier.Instance.notifyOfNewInstanceFollow(actorFollow)
+ } else {
+ const actorFollowFull = actorFollow as MActorFollowFull
+ actorFollowFull.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as MAccount
+
+ Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
+ }
}
logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url)
import { getVideoLikeActivityPubUrl } from '../url'
import { getAPId } from '../../../helpers/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) {
+async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) {
const videoUrl = getAPId(activity.object)
const byAccount = byActor.Account
import { sequelizeTypescript } from '../../../initializers'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor } from '../../../typings/models'
async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
const { byActor: targetActor, inboxActor } = options
// ---------------------------------------------------------------------------
-async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) {
+async function processReject (follower: MActor, targetActor: MActor) {
return sequelizeTypescript.transaction(async t => {
const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t)
import { VideoShareModel } from '../../../models/video/video-share'
import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
const likeActivity = activity.object as ActivityLike
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
})
}
-async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) {
const dislike = activity.object.type === 'Dislike'
? activity.object
: activity.object.object as DislikeObject
})
}
-async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) {
+async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
const cacheFileObject = activity.object.object as CacheFileObject
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
})
}
-function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) {
+function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
return sequelizeTypescript.transaction(async t => {
const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
})
}
-function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) {
+function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
return sequelizeTypescript.transaction(async t => {
const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`)
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
import { createOrUpdateVideoPlaylist } from '../playlist'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature, MAccountIdActor } from '../../../typings/models'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) {
const videoObject = activity.object as VideoTorrentObject
if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
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 account = actor.Account as MAccountIdActor
+ account.Actor = actor
+
const updateOptions = {
video,
videoObject,
- account: actor.Account,
+ account,
channel: channelActor.VideoChannel,
overrideTo: activity.to
}
return updateVideoFromAP(updateOptions)
}
-async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) {
const cacheFileObject = activity.object as CacheFileObject
if (!isCacheFileObjectValid(cacheFileObject)) {
}
}
-async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) {
+async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) {
const playlistObject = activity.object as PlaylistObject
const byAccount = byActor.Account
import { Redis } from '../../redis'
import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorSignature } from '../../../typings/models'
async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) {
const { activity, byActor } = options
// ---------------------------------------------------------------------------
-async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) {
+async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) {
const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
const options = {
- videoObject: videoObject,
+ videoObject,
fetchType: 'only-video' as 'only-video'
}
const { video } = await getOrCreateVideoAndAccountAndChannel(options)
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
import { logger } from '../../../helpers/logger'
-import { ActorModel } from '../../../models/activitypub/actor'
import { processAcceptActivity } from './process-accept'
import { processAnnounceActivity } from './process-announce'
import { processCreateActivity } from './process-create'
import { processFlagActivity } from './process-flag'
import { processViewActivity } from './process-view'
import { APProcessorOptions } from '../../../typings/activitypub-processor.model'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActorDefault, MActorSignature } from '../../../typings/models'
const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
Create: processCreateActivity,
async function processActivities (
activities: Activity[],
options: {
- signatureActor?: SignatureActorModel
- inboxActor?: ActorModel
+ signatureActor?: MActorSignature
+ inboxActor?: MActorDefault
outboxUrl?: string
fromFetch?: boolean
} = {}
) {
const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
- const actorsCache: { [ url: string ]: SignatureActorModel } = {}
+ const actorsCache: { [ url: string ]: MActorSignature } = {}
for (const activity of activities) {
if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
}
try {
- await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch })
+ await activityProcessor({ activity, byActor, inboxActor, fromFetch })
} catch (err) {
logger.warn('Cannot process activity %s.', activity.type, { err })
}
import { unicastTo } from './utils'
import { buildFollowActivity } from './send-follow'
import { logger } from '../../../helpers/logger'
-import { ActorFollowModelLight } from '../../../typings/models/actor-follow'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor, MActorFollowActors } from '../../../typings/models'
-async function sendAccept (actorFollow: ActorFollowModelLight) {
+async function sendAccept (actorFollow: MActorFollowActors) {
const follower = actorFollow.ActorFollower
const me = actorFollow.ActorFollowing
// ---------------------------------------------------------------------------
-function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept {
+function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept {
return {
type: 'Accept',
id: url,
import { Transaction } from 'sequelize'
import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub'
-import { VideoModel } from '../../../models/video/video'
import { broadcastToFollowers } from './utils'
import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
import { logger } from '../../../helpers/logger'
-import { ActorModelOnly } from '../../../typings/models'
-import { VideoShareModelOnly } from '../../../typings/models/video-share'
+import { MActorLight, MVideo } from '../../../typings/models'
+import { MVideoShare } from '../../../typings/models/video'
async function buildAnnounceWithVideoAudience (
- byActor: ActorModelOnly,
- videoShare: VideoShareModelOnly,
- video: VideoModel,
+ byActor: MActorLight,
+ videoShare: MVideoShare,
+ video: MVideo,
t: Transaction
) {
const announcedObject = video.url
return { activity, actorsInvolvedInVideo }
}
-async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) {
+async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
logger.info('Creating job to send announce %s.', videoShare.url)
return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException)
}
-function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce {
+function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
if (!audience) audience = getAudience(byActor)
return audiencify({
import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
import { VideoPrivacy } from '../../../../shared/models/videos'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
import { logger } from '../../../helpers/logger'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { getServerActor } from '../../../helpers/utils'
-import * as Bluebird from 'bluebird'
-
-async function sendCreateVideo (video: VideoModel, t: Transaction) {
+import {
+ MActorLight,
+ MCommentOwnerVideo,
+ MVideoAccountLight,
+ MVideoAP,
+ MVideoPlaylistFull,
+ MVideoRedundancyFileVideo,
+ MVideoRedundancyStreamingPlaylistVideo
+} from '../../../typings/models'
+
+async function sendCreateVideo (video: MVideoAP, t: Transaction) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
logger.info('Creating job to send video creation of %s.', video.url)
return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
}
-async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) {
+async function sendCreateCacheFile (
+ byActor: MActorLight,
+ video: MVideoAccountLight,
+ fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
+) {
logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
return sendVideoRelatedCreateActivity({
})
}
-async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) {
+async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
logger.info('Creating job to send create video playlist of %s.', playlist.url)
return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
}
-async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) {
+async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
logger.info('Creating job to send comment %s.', comment.url)
const isOrigin = comment.Video.isOwned()
t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
}
-function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate {
+function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
if (!audience) audience = getAudience(byActor)
return audiencify(
// ---------------------------------------------------------------------------
async function sendVideoRelatedCreateActivity (options: {
- byActor: ActorModel,
- video: VideoModel,
+ byActor: MActorLight,
+ video: MVideoAccountLight,
url: string,
object: any,
transaction?: Transaction
import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoShareModel } from '../../../models/video/video-share'
import { getDeleteActivityPubUrl } from '../url'
import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
import { logger } from '../../../helpers/logger'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { getServerActor } from '../../../helpers/utils'
+import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video'
+import { MActorUrl } from '../../../typings/models'
-async function sendDeleteVideo (video: VideoModel, transaction: Transaction) {
+async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) {
logger.info('Creating job to broadcast delete of video %s.', video.url)
const byActor = video.VideoChannel.Account.Actor
return broadcastToFollowers(activity, byActor, actorsInvolved, t)
}
-async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
+async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) {
logger.info('Creating job to send delete of comment %s.', videoComment.url)
const isVideoOrigin = videoComment.Video.isOwned()
t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl))
}
-async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) {
+async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
const byActor = videoPlaylist.OwnerAccount.Actor
// ---------------------------------------------------------------------------
-function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete {
+function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete {
const activity = {
type: 'Delete' as 'Delete',
id: url,
import { Transaction } from 'sequelize'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
import { getVideoDislikeActivityPubUrl } from '../url'
import { logger } from '../../../helpers/logger'
import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub'
import { sendVideoRelatedActivity } from './utils'
import { audiencify, getAudience } from '../audience'
+import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
-async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
logger.info('Creating job to dislike %s.', video.url)
const activityBuilder = (audience: ActivityAudience) => {
return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
}
-function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike {
+function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
if (!audience) audience = getAudience(byActor)
return audiencify(
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
-import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { getVideoAbuseActivityPubUrl } from '../url'
import { unicastTo } from './utils'
import { logger } from '../../../helpers/logger'
import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub'
import { audiencify, getAudience } from '../audience'
import { Transaction } from 'sequelize'
+import { MActor, MVideoFullLight } from '../../../typings/models'
+import { MVideoAbuseVideo } from '../../../typings/models/video'
-async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) {
+async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
if (!video.VideoChannel.Account.Actor.serverId) return // Local user
const url = getVideoAbuseActivityPubUrl(videoAbuse)
t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl))
}
-function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag {
+function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag {
if (!audience) audience = getAudience(byActor)
const activity = Object.assign(
import { unicastTo } from './utils'
import { logger } from '../../../helpers/logger'
import { Transaction } from 'sequelize'
-import { ActorModelOnly } from '../../../typings/models'
+import { MActor, MActorFollowActors } from '../../../typings/models'
-function sendFollow (actorFollow: ActorFollowModel, t: Transaction) {
+function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
const me = actorFollow.ActorFollower
const following = actorFollow.ActorFollowing
t.afterCommit(() => unicastTo(data, me, following.inboxUrl))
}
-function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow {
+function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
return {
type: 'Follow',
id: url,
import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
import { getVideoLikeActivityPubUrl } from '../url'
import { sendVideoRelatedActivity } from './utils'
import { audiencify, getAudience } from '../audience'
import { logger } from '../../../helpers/logger'
+import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
-async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
logger.info('Creating job to like %s.', video.url)
const activityBuilder = (audience: ActivityAudience) => {
return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
}
-function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike {
+function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
if (!audience) audience = getAudience(byActor)
return audiencify(
import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url'
import { unicastTo } from './utils'
import { buildFollowActivity } from './send-follow'
import { logger } from '../../../helpers/logger'
-import { SignatureActorModel } from '../../../typings/models'
+import { MActor } from '../../../typings/models'
-async function sendReject (follower: SignatureActorModel, following: ActorModel) {
+async function sendReject (follower: MActor, following: MActor) {
if (!follower.serverId) { // This should never happen
logger.warn('Do not sending reject to local follower.')
return
// ---------------------------------------------------------------------------
-function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject {
+function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject {
return {
type: 'Reject',
id: url,
import {
ActivityAnnounce,
ActivityAudience,
- ActivityCreate, ActivityDislike,
+ ActivityCreate,
+ ActivityDislike,
ActivityFollow,
ActivityLike,
ActivityUndo
} from '../../../../shared/models/activitypub'
-import { ActorModel } from '../../../models/activitypub/actor'
-import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { VideoModel } from '../../../models/video/video'
import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
import { buildCreateActivity } from './send-create'
import { buildFollowActivity } from './send-follow'
import { buildLikeActivity } from './send-like'
-import { VideoShareModel } from '../../../models/video/video-share'
import { buildAnnounceWithVideoAudience } from './send-announce'
import { logger } from '../../../helpers/logger'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
import { buildDislikeActivity } from './send-dislike'
-
-async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
+import {
+ MActor, MActorAudience,
+ MActorFollowActors,
+ MActorLight,
+ MVideo,
+ MVideoAccountLight,
+ MVideoRedundancyVideo,
+ MVideoShare
+} from '../../../typings/models'
+
+async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
const me = actorFollow.ActorFollower
const following = actorFollow.ActorFollowing
t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl))
}
-async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
+async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
logger.info('Creating job to undo announce %s.', videoShare.url)
const undoUrl = getUndoActivityPubUrl(videoShare.url)
return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
}
-async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
logger.info('Creating job to undo a like of video %s.', video.url)
const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
}
-async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
logger.info('Creating job to undo a dislike of video %s.', video.url)
const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
}
-async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) {
+async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) {
logger.info('Creating job to undo cache file %s.', redundancyModel.url)
const videoId = redundancyModel.getVideo().id
function undoActivityData (
url: string,
- byActor: ActorModel,
+ byActor: MActorAudience,
object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
audience?: ActivityAudience
): ActivityUndo {
}
async function sendUndoVideoRelatedActivity (options: {
- byActor: ActorModel,
- video: VideoModel,
+ byActor: MActor,
+ video: MVideoAccountLight,
url: string,
activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce,
transaction: Transaction
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
import { VideoPrivacy } from '../../../../shared/models/videos'
import { AccountModel } from '../../../models/account/account'
-import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
-import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoShareModel } from '../../../models/video/video-share'
import { getUpdateActivityPubUrl } from '../url'
import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
import { logger } from '../../../helpers/logger'
import { VideoCaptionModel } from '../../../models/video/video-caption'
-import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { getServerActor } from '../../../helpers/utils'
+import {
+ MAccountDefault,
+ MActor,
+ MActorLight,
+ MChannelDefault,
+ MVideoAP,
+ MVideoAPWithoutCaption,
+ MVideoPlaylistFull,
+ MVideoRedundancyVideo
+} from '../../../typings/models'
+
+async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) {
+ const video = videoArg as MVideoAP
-async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
logger.info('Creating job to update video %s.', video.url)
return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
}
-async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) {
+async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) {
const byActor = accountOrChannel.Actor
logger.info('Creating job to update actor %s.', byActor.url)
const audience = getAudience(byActor)
const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience)
- let actorsInvolved: ActorModel[]
+ let actorsInvolved: MActor[]
if (accountOrChannel instanceof AccountModel) {
// Actors that shared my videos are involved too
actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
}
-async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) {
+async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
logger.info('Creating job to update cache file %s.', redundancyModel.url)
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id)
return sendVideoRelatedActivity(activityBuilder, { byActor, video })
}
-async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) {
+async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
const byActor = videoPlaylist.OwnerAccount.Actor
// ---------------------------------------------------------------------------
-function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate {
+function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate {
if (!audience) audience = getAudience(byActor)
return audiencify(
type: 'Update' as 'Update',
id: url,
actor: byActor.url,
- object: audiencify(object, audience
- )
+ object: audiencify(object, audience)
},
audience
)
import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoModel } from '../../../models/video/video'
import { getVideoLikeActivityPubUrl } from '../url'
import { sendVideoRelatedActivity } from './utils'
import { audiencify, getAudience } from '../audience'
import { logger } from '../../../helpers/logger'
+import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models'
-async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) {
+async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) {
logger.info('Creating job to send view of %s.', video.url)
const activityBuilder = (audience: ActivityAudience) => {
return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
}
-function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView {
+function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
if (!audience) audience = getAudience(byActor)
return audiencify(
import { ActorModel } from '../../../models/activitypub/actor'
import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
import { JobQueue } from '../../job-queue'
-import { VideoModel } from '../../../models/video/video'
import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
import { getServerActor } from '../../../helpers/utils'
import { afterCommitIfTransaction } from '../../../helpers/database-utils'
-import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models'
+import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
- byActor: ActorModelOnly,
- video: VideoModel,
+ byActor: MActorLight,
+ video: MVideoAccountLight,
transaction?: Transaction
}) {
const { byActor, video, transaction } = options
async function forwardVideoRelatedActivity (
activity: Activity,
t: Transaction,
- followersException: ActorFollowerException[] = [],
- video: VideoModel
+ followersException: MActorFollowerException[] = [],
+ video: MVideo
) {
// Mastodon does not add our announces in audience, so we forward to them manually
const additionalActors = await getActorsInvolvedInVideo(video, t)
async function forwardActivity (
activity: Activity,
t: Transaction,
- followersException: ActorFollowerException[] = [],
+ followersException: MActorFollowerException[] = [],
additionalFollowerUrls: string[] = []
) {
logger.info('Forwarding activity %s.', activity.id)
async function broadcastToFollowers (
data: any,
- byActor: ActorModelId,
- toFollowersOf: ActorModelId[],
+ byActor: MActorId,
+ toFollowersOf: MActorId[],
t: Transaction,
- actorsException: ActorFollowerException[] = []
+ actorsException: MActorFollowerException[] = []
) {
const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
async function broadcastToActors (
data: any,
- byActor: ActorModelId,
- toActors: ActorModelOnly[],
+ byActor: MActorId,
+ toActors: MActor[],
t?: Transaction,
- actorsException: ActorFollowerException[] = []
+ actorsException: MActorFollowerException[] = []
) {
const uris = await computeUris(toActors, actorsException)
return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
}
-function broadcastTo (uris: string[], data: any, byActor: ActorModelId) {
+function broadcastTo (uris: string[], data: any, byActor: MActorId) {
if (uris.length === 0) return undefined
logger.debug('Creating broadcast job.', { uris })
return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
}
-function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) {
+function unicastTo (data: any, byActor: MActorId, toActorUrl: string) {
logger.debug('Creating unicast job.', { uri: toActorUrl })
const payload = {
// ---------------------------------------------------------------------------
-async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) {
+async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
const toActorFollowerIds = toFollowersOf.map(a => a.id)
const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
}
-async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) {
+async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
const serverActor = await getServerActor()
const targetUrls = toActors
.filter(a => a.id !== serverActor.id) // Don't send to ourselves
.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
}
-async function buildSharedInboxesException (actorsException: ActorFollowerException[]) {
+async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
const serverActor = await getServerActor()
return actorsException
import { Transaction } from 'sequelize'
import { VideoPrivacy } from '../../../shared/models/videos'
import { getServerActor } from '../../helpers/utils'
-import { VideoModel } from '../../models/video/video'
import { VideoShareModel } from '../../models/video/video-share'
import { sendUndoAnnounce, sendVideoAnnounce } from './send'
import { getVideoAnnounceActivityPubUrl } from './url'
-import { VideoChannelModel } from '../../models/video/video-channel'
import * as Bluebird from 'bluebird'
import { doRequest } from '../../helpers/requests'
import { getOrCreateActorAndServerAndModel } from './actor'
import { logger } from '../../helpers/logger'
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
+import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video'
-async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
+async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
if (video.privacy === VideoPrivacy.PRIVATE) return undefined
return Promise.all([
])
}
-async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+async function changeVideoChannelShare (
+ video: MVideoAccountLight,
+ oldVideoChannel: MChannelActorLight,
+ t: Transaction
+) {
logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name)
await undoShareByVideoChannel(video, oldVideoChannel, t)
await shareByVideoChannel(video, t)
}
-async function addVideoShares (shareUrls: string[], instance: VideoModel) {
+async function addVideoShares (shareUrls: string[], video: MVideoId) {
await Bluebird.map(shareUrls, async shareUrl => {
try {
// Fetch url
const entry = {
actorId: actor.id,
- videoId: instance.id,
+ videoId: video.id,
url: shareUrl
}
// ---------------------------------------------------------------------------
-async function shareByServer (video: VideoModel, t: Transaction) {
+async function shareByServer (video: MVideo, t: Transaction) {
const serverActor = await getServerActor()
const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
return sendVideoAnnounce(serverActor, serverShare, video, t)
}
-async function shareByVideoChannel (video: VideoModel, t: Transaction) {
+async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
defaults: {
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
}
-async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
+async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
// Load old share
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id)
import { WEBSERVER } from '../../initializers/constants'
-import { VideoModel } from '../../models/video/video'
-import { VideoAbuseModel } from '../../models/video/video-abuse'
-import { VideoCommentModel } from '../../models/video/video-comment'
-import { VideoFileModel } from '../../models/video/video-file'
-import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
-import { VideoPlaylistModel } from '../../models/video/video-playlist'
-import { ActorModelOnly, ActorModelUrl } from '../../typings/models'
-import { ActorFollowModelLight } from '../../typings/models/actor-follow'
-
-function getVideoActivityPubUrl (video: VideoModel) {
+import {
+ MActor,
+ MActorFollowActors,
+ MActorId,
+ MActorUrl,
+ MCommentId,
+ MVideoAbuseId,
+ MVideoId,
+ MVideoUrl,
+ MVideoUUID
+} from '../../typings/models'
+import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist'
+import { MVideoFileVideoUUID } from '../../typings/models/video/video-file'
+import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist'
+
+function getVideoActivityPubUrl (video: MVideoUUID) {
return WEBSERVER.URL + '/videos/watch/' + video.uuid
}
-function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) {
+function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) {
return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid
}
-function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) {
+function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) {
return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid
}
-function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) {
+function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) {
const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : ''
return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}`
}
-function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) {
+function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) {
return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}`
}
-function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) {
+function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) {
return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id
}
return WEBSERVER.URL + '/accounts/' + accountName
}
-function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) {
+function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) {
return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id
}
-function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) {
+function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString()
}
-function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) {
+function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
return byActor.url + '/likes/' + video.id
}
-function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) {
+function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) {
return byActor.url + '/dislikes/' + video.id
}
-function getVideoSharesActivityPubUrl (video: VideoModel) {
+function getVideoSharesActivityPubUrl (video: MVideoUrl) {
return video.url + '/announces'
}
-function getVideoCommentsActivityPubUrl (video: VideoModel) {
+function getVideoCommentsActivityPubUrl (video: MVideoUrl) {
return video.url + '/comments'
}
-function getVideoLikesActivityPubUrl (video: VideoModel) {
+function getVideoLikesActivityPubUrl (video: MVideoUrl) {
return video.url + '/likes'
}
-function getVideoDislikesActivityPubUrl (video: VideoModel) {
+function getVideoDislikesActivityPubUrl (video: MVideoUrl) {
return video.url + '/dislikes'
}
-function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) {
+function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) {
return follower.url + '/follows/' + following.id
}
-function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) {
+function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) {
const follower = actorFollow.ActorFollower
const me = actorFollow.ActorFollowing
return follower.url + '/accepts/follows/' + me.id
}
-function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) {
+function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) {
return follower.url + '/rejects/follows/' + following.id
}
-function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) {
+function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) {
return video.url + '/announces/' + byActor.id
}
import { logger } from '../../helpers/logger'
import { doRequest } from '../../helpers/requests'
import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
-import { VideoModel } from '../../models/video/video'
import { VideoCommentModel } from '../../models/video/video-comment'
import { getOrCreateActorAndServerAndModel } from './actor'
import { getOrCreateVideoAndAccountAndChannel } from './videos'
import * as Bluebird from 'bluebird'
import { checkUrlsSameHost } from '../../helpers/activitypub'
+import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video'
type ResolveThreadParams = {
url: string,
- comments?: VideoCommentModel[],
+ comments?: MCommentOwner[],
isVideo?: boolean,
commentCreated?: boolean
}
-type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }>
+type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
async function addVideoComments (commentUrls: string[]) {
return Bluebird.map(commentUrls, commentUrl => {
const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false }
const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
- let resultComment: VideoCommentModel
+ let resultComment: MCommentOwnerVideo
if (comments.length !== 0) {
- const firstReply = comments[ comments.length - 1 ]
+ const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo
firstReply.inReplyToCommentId = null
firstReply.originCommentId = null
firstReply.videoId = video.id
comments[comments.length - 1] = await firstReply.save()
for (let i = comments.length - 2; i >= 0; i--) {
- const comment = comments[ i ]
+ const comment = comments[ i ] as MCommentOwnerVideo
comment.originCommentId = firstReply.id
comment.inReplyToCommentId = comments[ i + 1 ].id
comment.videoId = video.id
comments[i] = await comment.save()
}
- resultComment = comments[0]
+ resultComment = comments[0] as MCommentOwnerVideo
}
return { video, comment: resultComment, commentCreated }
originCommentId: null,
createdAt: new Date(body.published),
updatedAt: new Date(body.updated)
- })
+ }) as MCommentOwner
comment.Account = actor.Account
return resolveThread({
import { Transaction } from 'sequelize'
-import { AccountModel } from '../../models/account/account'
-import { VideoModel } from '../../models/video/video'
import { sendLike, sendUndoDislike, sendUndoLike } from './send'
import { VideoRateType } from '../../../shared/models/videos'
import * as Bluebird from 'bluebird'
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants'
import { doRequest } from '../../helpers/requests'
import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
-import { ActorModel } from '../../models/activitypub/actor'
import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
import { sendDislike } from './send/send-dislike'
+import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models'
-async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) {
+async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) {
let rateCounts = 0
await Bluebird.map(ratesUrl, async rateUrl => {
return
}
-async function sendVideoRateChange (account: AccountModel,
- video: VideoModel,
- likes: number,
- dislikes: number,
- t: Transaction) {
+async function sendVideoRateChange (
+ account: MAccountActor,
+ video: MVideoAccountLight,
+ likes: number,
+ dislikes: number,
+ t: Transaction
+) {
const actor = account.Actor
// Keep the order: first we undo and then we create
if (dislikes > 0) await sendDislike(actor, video, t)
}
-function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) {
- return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video)
+function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) {
+ return rateType === 'like'
+ ? getVideoLikeActivityPubUrl(actor, video)
+ : getVideoDislikeActivityPubUrl(actor, video)
}
export {
REMOTE_SCHEME,
STATIC_PATHS
} from '../../initializers/constants'
-import { ActorModel } from '../../models/activitypub/actor'
import { TagModel } from '../../models/video/tag'
import { VideoModel } from '../../models/video/video'
import { VideoFileModel } from '../../models/video/video-file'
import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher'
import { createRates } from './video-rates'
import { addVideoShares, shareVideoByServerAndChannel } from './share'
-import { AccountModel } from '../../models/account/account'
import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
import { Notifier } from '../notifier'
import { VideoCommentModel } from '../../models/video/video-comment'
import { sequelizeTypescript } from '../../initializers/database'
import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
-import { ThumbnailModel } from '../../models/video/thumbnail'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
import { join } from 'path'
import { FilteredModelAttributes } from '../../typings/sequelize'
import { autoBlacklistVideoIfNeeded } from '../video-blacklist'
import { ActorFollowScoreCache } from '../files-cache'
-import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models'
+import {
+ MAccountIdActor,
+ MChannelAccountLight,
+ MChannelDefault,
+ MChannelId,
+ MVideo,
+ MVideoAccountLight,
+ MVideoAccountLightBlacklistAllFiles,
+ MVideoAP,
+ MVideoAPWithoutCaption,
+ MVideoFile,
+ MVideoFullLight,
+ MVideoId,
+ MVideoThumbnail
+} from '../../typings/models'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
+
+async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
+ const video = videoArg as MVideoAP
-async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
if (
// Check this is not a blacklisted video, or unfederated blacklisted video
(video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
return { response, videoObject: body }
}
-async function fetchRemoteVideoDescription (video: VideoModel) {
+async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
const host = video.VideoChannel.Account.Actor.Server.host
const path = video.getDescriptionAPIPath()
const options = {
return body.description ? body.description : ''
}
-function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) {
+function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
const url = buildRemoteBaseUrl(video, path)
// We need to provide a callback, if no we could have an uncaught exception
return doRequestAndSaveToFile({ uri: url }, destPath)
}
-function buildRemoteBaseUrl (video: VideoModel, path: string) {
+function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
const host = video.VideoChannel.Account.Actor.Server.host
return REMOTE_SCHEME.HTTP + '://' + host + path
thumbnail: boolean
refreshVideo?: boolean
}
-async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
+async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) {
logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid)
const jobPayloads: ActivitypubHttpFetcherPayload[] = []
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: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }>
+function getOrCreateVideoAndAccountAndChannel (options: {
+ videoObject: { id: string } | string,
+ syncParam?: SyncParam,
+ fetchType?: VideoFetchByUrlType,
+ allowRefresh?: boolean
+}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }>
async function getOrCreateVideoAndAccountAndChannel (options: {
videoObject: { id: string } | string,
syncParam?: SyncParam,
fetchType?: VideoFetchByUrlType,
allowRefresh?: boolean // true by default
-}) {
+}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> {
// Default params
const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
const fetchType = options.fetchType || 'all'
const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl)
if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
- const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
- const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail)
+ const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo)
+ const videoChannel = actor.VideoChannel
+ const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail)
await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam)
}
async function updateVideoFromAP (options: {
- video: VideoModel,
+ video: MVideoAccountLightBlacklistAllFiles,
videoObject: VideoTorrentObject,
- account: AccountModelIdActor,
- channel: VideoChannelModelIdActor,
+ account: MAccountIdActor,
+ channel: MChannelDefault,
overrideTo?: string[]
}) {
const { video, videoObject, account, channel, overrideTo } = options
- logger.debug('Updating remote video "%s".', options.videoObject.uuid)
+ logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel })
let videoFieldsSave: any
const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE
const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED
try {
- let thumbnailModel: ThumbnailModel
+ let thumbnailModel: MThumbnail
try {
thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
}
- await sequelizeTypescript.transaction(async t => {
+ const videoUpdated = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
videoFieldsSave = video.toJSON()
video.channelId = videoData.channelId
video.views = videoData.views
- await video.save(sequelizeOptions)
+ const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight
- if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t)
+ if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
// FIXME: use icon URL instead
- const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename))
+ const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
- await video.addAndSaveThumbnail(previewModel, t)
+ await videoUpdated.addAndSaveThumbnail(previewModel, t)
{
- const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject)
+ const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject)
const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a))
// Remove video files that do not exist anymore
- const destroyTasks = video.VideoFiles
+ const destroyTasks = videoUpdated.VideoFiles
.filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f)))
.map(f => f.destroy(sequelizeOptions))
await Promise.all(destroyTasks)
.then(([ file ]) => file)
})
- video.VideoFiles = await Promise.all(upsertTasks)
+ videoUpdated.VideoFiles = await Promise.all(upsertTasks)
}
{
- const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles)
+ const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles)
const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a))
// Remove video files that do not exist anymore
- const destroyTasks = video.VideoStreamingPlaylists
+ const destroyTasks = videoUpdated.VideoStreamingPlaylists
.filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f)))
.map(f => f.destroy(sequelizeOptions))
await Promise.all(destroyTasks)
.then(([ streamingPlaylist ]) => streamingPlaylist)
})
- video.VideoStreamingPlaylists = await Promise.all(upsertTasks)
+ videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks)
}
{
// Update Tags
const tags = videoObject.tag.map(tag => tag.name)
const tagInstances = await TagModel.findOrCreateTags(tags, t)
- await video.$set('Tags', tagInstances, sequelizeOptions)
+ await videoUpdated.$set('Tags', tagInstances, sequelizeOptions)
}
{
// Update captions
- await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t)
+ await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
- return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t)
+ return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t)
})
- video.VideoCaptions = await Promise.all(videoCaptionsPromises)
+ await Promise.all(videoCaptionsPromises)
}
+
+ return videoUpdated
})
await autoBlacklistVideoIfNeeded({
- video,
+ video: videoUpdated,
user: undefined,
isRemote: true,
isNew: false,
transaction: undefined
})
- if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users?
+ if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users?
logger.info('Remote video with uuid %s updated', videoObject.uuid)
+
+ return videoUpdated
} catch (err) {
if (video !== undefined && videoFieldsSave !== undefined) {
resetSequelizeInstance(video, videoFieldsSave)
}
async function refreshVideoIfNeeded (options: {
- video: VideoModel,
+ video: MVideoThumbnail,
fetchedType: VideoFetchByUrlType,
syncParam: SyncParam
-}): Promise<VideoModel> {
+}): Promise<MVideoThumbnail> {
if (!options.video.isOutdated()) return options.video
// We need more attributes if the argument video was fetched with not enough joints
const video = options.fetchedType === 'all'
- ? options.video
+ ? options.video as MVideoAccountLightBlacklistAllFiles
: await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
try {
}
const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
- const account = await AccountModel.load(channelActor.VideoChannel.accountId)
const updateOptions = {
video,
videoObject,
- account,
+ account: channelActor.VideoChannel.Account,
channel: channelActor.VideoChannel
}
await retryTransactionWrapper(updateVideoFromAP, updateOptions)
return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json'
}
-async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) {
+async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) {
logger.debug('Adding remote video %s.', videoObject.id)
- const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
- const video = VideoModel.build(videoData)
+ const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
+ const video = VideoModel.build(videoData) as MVideoThumbnail
const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
- let thumbnailModel: ThumbnailModel
+ let thumbnailModel: MThumbnail
if (waitThumbnail === true) {
thumbnailModel = await promiseThumbnail
}
const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
- const videoCreated = await video.save(sequelizeOptions)
- videoCreated.VideoChannel = channelActor.VideoChannel
+ const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
+ videoCreated.VideoChannel = channel
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t)
})
- const captions = await Promise.all(videoCaptionsPromises)
+ await Promise.all(videoCaptionsPromises)
- video.VideoFiles = videoFiles
- video.VideoStreamingPlaylists = streamingPlaylists
- video.Tags = tagInstances
- video.VideoCaptions = captions
+ videoCreated.VideoFiles = videoFiles
+ videoCreated.VideoStreamingPlaylists = streamingPlaylists
+ videoCreated.Tags = tagInstances
const autoBlacklisted = await autoBlacklistVideoIfNeeded({
- video,
+ video: videoCreated,
user: undefined,
isRemote: true,
isNew: true,
return { autoBlacklisted, videoCreated }
}
-async function videoActivityObjectToDBAttributes (
- videoChannel: VideoChannelModelId,
- videoObject: VideoTorrentObject,
- to: string[] = []
-) {
+async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
const duration = videoObject.duration.replace(/[^\d]+/, '')
}
}
-function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) {
+function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) {
const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[]
if (fileUrls.length === 0) {
return attributes
}
-function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) {
+function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) {
const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
if (playlistUrls.length === 0) return []
import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
import { updateActorAvatarInstance } from './activitypub'
import { processImage } from '../helpers/image-utils'
-import { AccountModel } from '../models/account/account'
-import { VideoChannelModel } from '../models/video/video-channel'
import { extname, join } from 'path'
import { retryTransactionWrapper } from '../helpers/database-utils'
import * as uuidv4 from 'uuid/v4'
import * as LRUCache from 'lru-cache'
import { queue } from 'async'
import { downloadImage } from '../helpers/requests'
+import { MAccountDefault, MChannelDefault } from '../typings/models'
-async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) {
+async function updateActorAvatarFile (
+ avatarPhysicalFile: Express.Multer.File,
+ accountOrChannel: MAccountDefault | MChannelDefault
+) {
const extension = extname(avatarPhysicalFile.filename)
const avatarName = uuidv4() + extension
const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
import { sequelizeTypescript } from '../initializers'
import { AccountBlocklistModel } from '../models/account/account-blocklist'
import { ServerBlocklistModel } from '../models/server/server-blocklist'
+import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models'
function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
return sequelizeTypescript.transaction(async t => {
})
}
-function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) {
+function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) {
return sequelizeTypescript.transaction(async t => {
return accountBlock.destroy({ transaction: t })
})
}
-function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) {
+function removeServerFromBlocklist (serverBlock: MServerBlocklist) {
return sequelizeTypescript.transaction(async t => {
return serverBlock.destroy({ transaction: t })
})
import * as Bluebird from 'bluebird'
import { CONFIG } from '../initializers/config'
import { logger } from '../helpers/logger'
+import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
export class ClientHtml {
const [ html, video ] = await Promise.all([
ClientHtml.getIndexHTML(req, res),
- VideoModel.load(videoId)
+ VideoModel.loadWithBlacklist(videoId)
])
// Let Angular application handle errors
- if (!video || video.privacy === VideoPrivacy.PRIVATE) {
+ if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
return ClientHtml.getIndexHTML(req, res)
}
}
private static async getAccountOrChannelHTMLPage (
- loader: () => Bluebird<AccountModel | VideoChannelModel>,
+ loader: () => Bluebird<MAccountActor | MChannelActor>,
req: express.Request,
res: express.Response
) {
return htmlStringPage.replace('</head>', linkTag + '</head>')
}
- private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
+ private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) {
const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath()
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString)
}
- private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) {
+ private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) {
// SEO, use origin account or channel URL
const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />`
import { isTestInstance } from '../helpers/core-utils'
import { bunyanLogger, logger } from '../helpers/logger'
import { CONFIG } from '../initializers/config'
-import { UserModel } from '../models/account/user'
-import { VideoModel } from '../models/video/video'
import { JobQueue } from './job-queue'
import { EmailPayload } from './job-queue/handlers/email'
import { readFileSync } from 'fs-extra'
-import { VideoCommentModel } from '../models/video/video-comment'
-import { VideoAbuseModel } from '../models/video/video-abuse'
-import { VideoBlacklistModel } from '../models/video/video-blacklist'
-import { VideoImportModel } from '../models/video/video-import'
-import { ActorFollowModel } from '../models/activitypub/actor-follow'
import { WEBSERVER } from '../initializers/constants'
+import { MCommentOwnerVideo, MVideo, MVideoAbuseVideo, MVideoAccountLight, MVideoBlacklistVideo } from '../typings/models/video'
+import { MActorFollowActors, MActorFollowFollowingFullFollowerAccount, MUser } from '../typings/models'
+import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import'
type SendEmailOptions = {
to: string[]
}
}
- addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
+ addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) {
const channelName = video.VideoChannel.getDisplayName()
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') {
+ addNewFollowNotification (to: string[], actorFollow: MActorFollowFollowingFullFollowerAccount, followType: 'account' | 'channel') {
const followerName = actorFollow.ActorFollower.Account.getDisplayName()
const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) {
+ addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
const text = `Hi dear admin,\n\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- myVideoPublishedNotification (to: string[], video: VideoModel) {
+ myVideoPublishedNotification (to: string[], video: MVideo) {
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
const text = `Hi dear user,\n\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
+ myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
const text = `Hi dear user,\n\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
+ myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
const importUrl = WEBSERVER.URL + '/my-account/video-imports'
const text = `Hi dear user,\n\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
+ addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) {
const accountName = comment.Account.getDisplayName()
const video = comment.Video
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) {
+ addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) {
const accountName = comment.Account.getDisplayName()
const video = comment.Video
const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
+ addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
const text = `Hi,\n\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) {
+ addVideoAutoBlacklistModeratorsNotification (to: string[], video: MVideo) {
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addNewUserRegistrationNotification (to: string[], user: UserModel) {
+ addNewUserRegistrationNotification (to: string[], user: MUser) {
const text = `Hi,\n\n` +
`User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
`Cheers,\n` +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
+ addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) {
const videoName = videoBlacklist.Video.name
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addVideoUnblacklistNotification (to: string[], video: VideoModel) {
+ addVideoUnblacklistNotification (to: string[], video: MVideo) {
const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
const text = 'Hi,\n\n' +
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
}
- addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
+ addUserBlockJob (user: MUser, blocked: boolean, reason?: string) {
const reasonString = reason ? ` for the following reason: ${reason}` : ''
const blockedWord = blocked ? 'blocked' : 'unblocked'
const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.`
-import { VideoModel } from '../models/video/video'
import { basename, dirname, join } from 'path'
import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants'
import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra'
import { VideoFileModel } from '../models/video/video-file'
import { CONFIG } from '../initializers/config'
import { sequelizeTypescript } from '../initializers/database'
+import { MVideoWithFile } from '@server/typings/models'
async function updateStreamingPlaylistsInfohashesIfNeeded () {
const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion()
}
}
-async function updateMasterHLSPlaylist (video: VideoModel) {
+async function updateMasterHLSPlaylist (video: MVideoWithFile) {
const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ]
const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename())
await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n')
}
-async function updateSha256Segments (video: VideoModel) {
+async function updateSha256Segments (video: MVideoWithFile) {
const json: { [filename: string]: { [range: string]: string } } = {}
const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
import { ActorModel } from '../../../models/activitypub/actor'
import { Notifier } from '../../notifier'
import { sequelizeTypescript } from '../../../initializers/database'
+import { MAccount, MActor, MActorFollowActors, MActorFollowFull, MActorFull } from '../../../typings/models'
export type ActivitypubFollowPayload = {
followerActorId: number
logger.info('Processing ActivityPub follow in job %d.', job.id)
- let targetActor: ActorModel
+ let targetActor: MActorFull
if (!host || host === WEBSERVER.HOST) {
targetActor = await ActorModel.loadLocalByName(payload.name)
} else {
const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP)
const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost)
- targetActor = await getOrCreateActorAndServerAndModel(actorUrl)
+ targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all')
}
const fromActor = await ActorModel.load(payload.followerActorId)
// ---------------------------------------------------------------------------
-async function follow (fromActor: ActorModel, targetActor: ActorModel) {
+async function follow (fromActor: MActor, targetActor: MActorFull) {
if (fromActor.id === targetActor.id) {
throw new Error('Follower is the same than target actor.')
}
const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending'
const actorFollow = await sequelizeTypescript.transaction(async t => {
- const [ actorFollow ] = await ActorFollowModel.findOrCreate({
+ const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({
where: {
actorId: fromActor.id,
targetActorId: targetActor.id
return actorFollow
})
- if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow)
+ if (actorFollow.state === 'accepted') {
+ const followerFull = Object.assign(fromActor, { Account: await actorFollow.ActorFollower.$get('Account') as MAccount })
+
+ const actorFollowFull = Object.assign(actorFollow, {
+ ActorFollowing: targetActor,
+ ActorFollower: followerFull
+ })
+
+ Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
+ }
}
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { VideoShareModel } from '../../../models/video/video-share'
import { VideoCommentModel } from '../../../models/video/video-comment'
+import { MAccountDefault, MVideoFullLight } from '../../../typings/models'
type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
const payload = job.data as ActivitypubHttpFetcherPayload
- let video: VideoModel
+ let video: MVideoFullLight
if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId)
- let account: AccountModel
+ let account: MAccountDefault
if (payload.accountId) account = await AccountModel.load(payload.accountId)
const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = {
import { ActorModel } from '../../../../models/activitypub/actor'
import { sha256 } from '../../../../helpers/core-utils'
import { HTTP_SIGNATURE } from '../../../../initializers/constants'
+import { MActor } from '../../../../typings/models'
type Payload = { body: any, signatureActorId?: number }
}
async function buildSignedRequestOptions (payload: Payload) {
- let actor: ActorModel | null
+ let actor: MActor | null
+
if (payload.signatureActorId) {
actor = await ActorModel.load(payload.signatureActorId)
if (!actor) throw new Error('Unknown signature actor id.')
import { copy, stat } from 'fs-extra'
import { VideoFileModel } from '../../../models/video/video-file'
import { extname } from 'path'
+import { MVideoFile, MVideoWithFile } from '@server/typings/models'
export type VideoFileImportPayload = {
videoUUID: string,
// ---------------------------------------------------------------------------
-async function updateVideoFile (video: VideoModel, inputFilePath: string) {
+async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) {
const { videoFileResolution } = await getVideoFileResolution(inputFilePath)
const { size } = await stat(inputFilePath)
const fps = await getVideoFileFPS(inputFilePath)
size,
fps,
videoId: video.id
- })
+ }) as MVideoFile
const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution)
video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile)
// Update the database
- currentVideoFile.set('extname', updatedVideoFile.extname)
- currentVideoFile.set('size', updatedVideoFile.size)
- currentVideoFile.set('fps', updatedVideoFile.fps)
+ currentVideoFile.extname = updatedVideoFile.extname
+ currentVideoFile.size = updatedVideoFile.size
+ currentVideoFile.fps = updatedVideoFile.fps
updatedVideoFile = currentVideoFile
}
import { Notifier } from '../../notifier'
import { CONFIG } from '../../../initializers/config'
import { sequelizeTypescript } from '../../../initializers/database'
-import { ThumbnailModel } from '../../../models/video/thumbnail'
import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
+import { MThumbnail } from '../../../typings/models/video/thumbnail'
+import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import'
type VideoImportYoutubeDLPayload = {
type: 'youtube-dl'
generateThumbnail: boolean
generatePreview: boolean
}
-async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) {
+async function processFile (downloader: () => Promise<string>, videoImport: MVideoImportDefault, options: ProcessFileOptions) {
let tempVideoPath: string
let videoDestFile: string
let videoFile: VideoFileModel
videoId: videoImport.videoId
}
videoFile = new VideoFileModel(videoFileData)
+
+ const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ] })
// To clean files if the import fails
- videoImport.Video.VideoFiles = [ videoFile ]
+ const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles })
// Move file
- videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile))
+ videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImportWithFiles.Video.getVideoFilename(videoFile))
await move(tempVideoPath, videoDestFile)
tempVideoPath = null // This path is not used anymore
// Process thumbnail
- let thumbnailModel: ThumbnailModel
+ let thumbnailModel: MThumbnail
if (options.downloadThumbnail && options.thumbnailUrl) {
- thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE)
+ thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE)
} else if (options.generateThumbnail || options.downloadThumbnail) {
- thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE)
+ thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE)
}
// Process preview
- let previewModel: ThumbnailModel
+ let previewModel: MThumbnail
if (options.downloadPreview && options.thumbnailUrl) {
- previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
+ previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW)
} else if (options.generatePreview || options.downloadPreview) {
- previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
+ previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW)
}
// Create torrent
- await videoImport.Video.createTorrentAndSetInfoHash(videoFile)
+ await videoImportWithFiles.Video.createTorrentAndSetInfoHash(videoFile)
+
+ const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => {
+ const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo
- const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => {
// Refresh video
- const video = await VideoModel.load(videoImport.videoId, t)
- if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.')
- videoImport.Video = video
+ const video = await VideoModel.load(videoImportToUpdate.videoId, t)
+ if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.')
const videoFileCreated = await videoFile.save({ transaction: t })
- video.VideoFiles = [ videoFileCreated ]
+ videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] })
// Update video DB object
video.duration = duration
await federateVideoIfNeeded(videoForFederation, true, t)
// Update video import object
- videoImport.state = VideoImportState.SUCCESS
- const videoImportUpdated = await videoImport.save({ transaction: t })
+ videoImportToUpdate.state = VideoImportState.SUCCESS
+ const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo
+ videoImportUpdated.Video = video
logger.info('Video %s imported.', video.uuid)
- videoImportUpdated.Video = videoForFederation
- return videoImportUpdated
+ return { videoImportUpdated, video: videoForFederation }
})
Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
- if (videoImportUpdated.Video.isBlacklisted()) {
- Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video)
+ if (video.isBlacklisted()) {
+ Notifier.Instance.notifyOnVideoAutoBlacklist(video)
} else {
- Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video)
+ Notifier.Instance.notifyOnNewVideoIfNeeded(video)
}
// Create transcoding jobs?
- if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
+ if (video.state === VideoState.TO_TRANSCODE) {
// Put uuid because we don't have id auto incremented for now
const dataInput = {
type: 'optimize' as 'optimize',
import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
import { Notifier } from '../../notifier'
import { CONFIG } from '../../../initializers/config'
+import { MVideoUUID, MVideoWithFile } from '@server/typings/models'
interface BaseTranscodingPayload {
videoUUID: string
return video
}
-async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
+async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) {
if (video === undefined) return undefined
await sequelizeTypescript.transaction(async t => {
})
}
-async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
+async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
// Maybe the video changed in database, refresh it
let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
await createHlsJobIfEnabled(payload)
}
-async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) {
+async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) {
if (videoArg === undefined) return undefined
// Outside the transaction (IO on disk)
import { PeerTubeSocket } from './peertube-socket'
import { CONFIG } from '../initializers/config'
import { VideoPrivacy, VideoState } from '../../shared/models/videos'
-import { VideoAbuseModel } from '../models/video/video-abuse'
import { VideoBlacklistModel } from '../models/video/video-blacklist'
import * as Bluebird from 'bluebird'
import { VideoImportModel } from '../models/video/video-import'
import { AccountBlocklistModel } from '../models/account/account-blocklist'
+import {
+ MCommentOwnerVideo,
+ MVideo,
+ MVideoAbuseVideo,
+ MVideoAccountLight,
+ MVideoBlacklistVideo,
+ MVideoFullLight
+} from '../typings/models/video'
+import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user'
+import { MActorFollowActors, MActorFollowFull, MActorFollowFollowingFullFollowerAccount } from '../typings/models'
import { ActorFollowModel } from '../models/activitypub/actor-follow'
-import { AccountModel } from '../models/account/account'
+import { MVideoImportVideo } from '@server/typings/models/video/video-import'
+import { AccountModel } from '@server/models/account/account'
class Notifier {
private constructor () {}
- notifyOnNewVideoIfNeeded (video: VideoModel): void {
+ notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
// Only notify on public and published videos which are not blacklisted
if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
.catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
}
- notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void {
+ notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
// don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update
if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return
.catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err }))
}
- notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void {
+ notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void {
// don't notify if video is still blacklisted or waiting for transcoding
if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
.catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err }))
}
- notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void {
+ notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void {
// don't notify if video is still waiting for transcoding or scheduled update
if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
.catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length
}
- notifyOnNewComment (comment: VideoCommentModel): void {
+ notifyOnNewComment (comment: MCommentOwnerVideo): void {
this.notifyVideoOwnerOfNewComment(comment)
.catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
.catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
}
- notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void {
+ notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
.catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
}
- notifyOnVideoAutoBlacklist (video: VideoModel): void {
+ notifyOnVideoAutoBlacklist (video: MVideo): void {
this.notifyModeratorsOfVideoAutoBlacklist(video)
.catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err }))
}
- notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
+ notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
this.notifyVideoOwnerOfBlacklist(videoBlacklist)
.catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
}
- notifyOnVideoUnblacklist (video: VideoModel): void {
+ notifyOnVideoUnblacklist (video: MVideo): void {
this.notifyVideoOwnerOfUnblacklist(video)
.catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err }))
}
- notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
+ notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
this.notifyOwnerVideoImportIsFinished(videoImport, success)
.catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
}
- notifyOnNewUserRegistration (user: UserModel): void {
+ notifyOnNewUserRegistration (user: MUserAccount): void {
this.notifyModeratorsOfNewUserRegistration(user)
.catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
}
- notifyOfNewUserFollow (actorFollow: ActorFollowModel): void {
+ notifyOfNewUserFollow (actorFollow: MActorFollowFollowingFullFollowerAccount): void {
this.notifyUserOfNewActorFollow(actorFollow)
.catch(err => {
logger.error(
})
}
- notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void {
+ notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void {
this.notifyAdminsOfNewInstanceFollow(actorFollow)
.catch(err => {
logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err })
})
}
- private async notifySubscribersOfNewVideo (video: VideoModel) {
+ private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
// List all followers that are users
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
userId: user.id,
videoId: video.id
})
- notification.Video = video
+ notification.Video = video as VideoModel
return notification
}
return this.notify({ users, settingGetter, notificationCreator, emailSender })
}
- private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) {
+ private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) {
if (comment.Video.isOwned() === false) return
const user = await UserModel.loadByVideoId(comment.videoId)
userId: user.id,
commentId: comment.id
})
- notification.Comment = comment
+ notification.Comment = comment as VideoCommentModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyOfCommentMention (comment: VideoCommentModel) {
+ private async notifyOfCommentMention (comment: MCommentOwnerVideo) {
const extractedUsernames = comment.extractMentions()
logger.debug(
'Extracted %d username from comment %s.', extractedUsernames.length, comment.url,
userId: user.id,
commentId: comment.id
})
- notification.Comment = comment
+ notification.Comment = comment as VideoCommentModel
return notification
}
return this.notify({ users, settingGetter, notificationCreator, emailSender })
}
- private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) {
+ private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFollowingFullFollowerAccount) {
if (actorFollow.ActorFollowing.isOwned() === false) return
// Account follows one of our account?
if (!user) return
- if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
- actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
- }
const followerAccount = actorFollow.ActorFollower.Account
const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
userId: user.id,
actorFollowId: actorFollow.id
})
- notification.ActorFollow = actorFollow
+ notification.ActorFollow = actorFollow as ActorFollowModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) {
+ private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) {
const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW)
logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url)
userId: user.id,
actorFollowId: actorFollow.id
})
- notification.ActorFollow = actorFollow
+ notification.ActorFollow = actorFollow as ActorFollowModel
return notification
}
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
}
- private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) {
+ private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) {
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
if (moderators.length === 0) return
}
async function notificationCreator (user: UserModel) {
- const notification = await UserNotificationModel.create({
+ const notification: UserNotificationModelForApi = await UserNotificationModel.create({
type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
userId: user.id,
videoAbuseId: videoAbuse.id
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
}
- private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) {
+ private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) {
const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST)
if (moderators.length === 0) return
userId: user.id,
videoId: video.id
})
- notification.Video = video
+ notification.Video = video as VideoModel
return notification
}
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
}
- private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
+ private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) {
const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
if (!user) return
userId: user.id,
videoBlacklistId: videoBlacklist.id
})
- notification.VideoBlacklist = videoBlacklist
+ notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyVideoOwnerOfUnblacklist (video: VideoModel) {
+ private async notifyVideoOwnerOfUnblacklist (video: MVideo) {
const user = await UserModel.loadByVideoId(video.id)
if (!user) return
userId: user.id,
videoId: video.id
})
- notification.Video = video
+ notification.Video = video as VideoModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
+ private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) {
const user = await UserModel.loadByVideoId(video.id)
if (!user) return
userId: user.id,
videoId: video.id
})
- notification.Video = video
+ notification.Video = video as VideoModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
+ private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) {
const user = await UserModel.loadByVideoImportId(videoImport.id)
if (!user) return
userId: user.id,
videoImportId: videoImport.id
})
- notification.VideoImport = videoImport
+ notification.VideoImport = videoImport as VideoImportModel
return notification
}
return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
}
- private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) {
+ private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) {
const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
if (moderators.length === 0) return
logger.info(
'Notifying %s moderators of new user registration of %s.',
- moderators.length, registeredUser.Account.Actor.preferredUsername
+ moderators.length, registeredUser.username
)
function settingGetter (user: UserModel) {
userId: user.id,
accountId: registeredUser.Account.id
})
- notification.Account = registeredUser.Account
+ notification.Account = registeredUser.Account as AccountModel
return notification
}
}
private async notify (options: {
- users: UserModel[],
- notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
+ users: MUserWithNotificationSetting[],
+ notificationCreator: (user: MUserWithNotificationSetting) => Promise<UserNotificationModelForApi>,
emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
- settingGetter: (user: UserModel) => UserNotificationSettingValue
+ settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue
}) {
const emails: string[] = []
}
}
- private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) {
+ private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) {
if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false
return value & UserNotificationSettingValue.EMAIL
import { Transaction } from 'sequelize'
import { CONFIG } from '../initializers/config'
import * as LRUCache from 'lru-cache'
+import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date }
-const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
+const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE })
// ---------------------------------------------------------------------------
import * as SocketIO from 'socket.io'
import { authenticateSocket } from '../middlewares'
-import { UserNotificationModel } from '../models/account/user-notification'
import { logger } from '../helpers/logger'
import { Server } from 'http'
+import { UserNotificationModelForApi } from '@server/typings/models/user'
class PeerTubeSocket {
})
}
- sendNotification (userId: number, notification: UserNotificationModel) {
+ sendNotification (userId: number, notification: UserNotificationModelForApi) {
const sockets = this.userNotificationSockets[userId]
if (!sockets) return
+ const notificationMessage = notification.toFormattedJSON()
for (const socket of sockets) {
- socket.emit('new-notification', notification.toFormattedJSON())
+ socket.emit('new-notification', notificationMessage)
}
}
import { sendUndoCacheFile } from './activitypub/send'
import { Transaction } from 'sequelize'
import { getServerActor } from '../helpers/utils'
+import { MVideoRedundancyVideo } from '@server/typings/models'
-async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) {
+async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) {
const serverActor = await getServerActor()
// Local cache, send undo to remote instances
import { logger } from '../../helpers/logger'
import { VideosRedundancy } from '../../../shared/models/redundancy'
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
-import { VideoFileModel } from '../../models/video/video-file'
import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
import { join } from 'path'
import { move } from 'fs-extra'
import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url'
import { removeVideoRedundancy } from '../redundancy'
import { getOrCreateVideoAndAccountAndChannel } from '../activitypub'
-import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
-import { VideoModel } from '../../models/video/video'
import { downloadPlaylistSegments } from '../hls'
import { CONFIG } from '../../initializers/config'
+import {
+ MStreamingPlaylist,
+ MStreamingPlaylistVideo,
+ MVideoAccountLight,
+ MVideoFile,
+ MVideoFileVideo,
+ MVideoRedundancyFileVideo,
+ MVideoRedundancyStreamingPlaylistVideo,
+ MVideoRedundancyVideo,
+ MVideoWithAllFiles
+} from '@server/typings/models'
type CandidateToDuplicate = {
redundancy: VideosRedundancy,
- video: VideoModel,
- files: VideoFileModel[],
- streamingPlaylists: VideoStreamingPlaylistModel[]
+ video: MVideoWithAllFiles,
+ files: MVideoFile[],
+ streamingPlaylists: MStreamingPlaylist[]
+}
+
+function isMVideoRedundancyFileVideo (
+ o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo
+): o is MVideoRedundancyFileVideo {
+ return !!(o as MVideoRedundancyFileVideo).VideoFile
}
export class VideosRedundancyScheduler extends AbstractScheduler {
}
}
- private async extendsRedundancy (redundancyModel: VideoRedundancyModel) {
+ private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) {
const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
// Redundancy strategy disabled, remove our redundancy instead of extending expiration
if (!redundancy) {
}
}
- private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) {
+ private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) {
+ const file = fileArg as MVideoFileVideo
file.Video = video
const serverActor = await getServerActor()
const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file))
await move(tmpPath, destPath, { overwrite: true })
- const createdModel = await VideoRedundancyModel.create({
+ const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
expiresOn: this.buildNewExpiration(redundancy.minLifetime),
url: getVideoCacheFileActivityPubUrl(file),
fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url)
}
- private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) {
+ private async createStreamingPlaylistRedundancy (
+ redundancy: VideosRedundancy,
+ video: MVideoAccountLight,
+ playlistArg: MStreamingPlaylist
+ ) {
+ const playlist = playlistArg as MStreamingPlaylistVideo
playlist.Video = video
const serverActor = await getServerActor()
const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
- const createdModel = await VideoRedundancyModel.create({
+ const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
expiresOn: this.buildNewExpiration(redundancy.minLifetime),
url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url)
}
- private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) {
+ private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) {
logger.info('Extending expiration of %s.', redundancy.url)
const serverActor = await getServerActor()
private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) {
while (this.isTooHeavy(candidateToDuplicate)) {
const redundancy = candidateToDuplicate.redundancy
- const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime)
+ const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime)
if (!toDelete) return
await removeVideoRedundancy(toDelete)
return new Date(Date.now() + expiresAfterMs)
}
- private buildEntryLogId (object: VideoRedundancyModel) {
- if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
+ private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) {
+ if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}`
return `${object.VideoStreamingPlaylist.playlistUrl}`
}
- private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) {
- const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size
+ private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) {
+ const fileReducer = (previous: number, current: MVideoFile) => previous + current.size
const totalSize = files.reduce(fileReducer, 0)
- if (playlists.length === 0) return totalSize
- return totalSize * playlists.length
+ return totalSize + (totalSize * playlists.length)
}
private async loadAndRefreshVideo (videoUrl: string) {
-import { VideoFileModel } from '../models/video/video-file'
import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
import { CONFIG } from '../initializers/config'
-import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants'
-import { VideoModel } from '../models/video/video'
+import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
import { ThumbnailModel } from '../models/video/thumbnail'
import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
import { processImage } from '../helpers/image-utils'
import { join } from 'path'
import { downloadImage } from '../helpers/requests'
-import { VideoPlaylistModel } from '../models/video/video-playlist'
+import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist'
+import { MVideoFile, MVideoThumbnail } from '../typings/models'
+import { MThumbnail } from '../typings/models/video/thumbnail'
type ImageSize = { height: number, width: number }
function createPlaylistMiniatureFromExisting (
inputPath: string,
- playlist: VideoPlaylistModel,
+ playlist: MVideoPlaylistThumbnail,
automaticallyGenerated: boolean,
keepOriginal = false,
size?: ImageSize
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
}
-function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) {
+function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) {
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
const type = ThumbnailType.MINIATURE
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
}
-function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
+function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height })
function createVideoMiniatureFromExisting (
inputPath: string,
- video: VideoModel,
+ video: MVideoThumbnail,
type: ThumbnailType,
automaticallyGenerated: boolean,
size?: ImageSize
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
}
-function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
+function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) {
const input = video.getVideoFilePath(videoFile)
const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
}
-function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) {
+function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel()
createPlaylistMiniatureFromExisting
}
-function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
+function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) {
const filename = playlist.generateThumbnailName()
const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
}
}
-function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) {
+function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
const existingThumbnail = Array.isArray(video.Thumbnails)
? video.Thumbnails.find(t => t.type === type)
: undefined
type: ThumbnailType,
automaticallyGenerated?: boolean,
fileUrl?: string,
- existingThumbnail?: ThumbnailModel
+ existingThumbnail?: MThumbnail
}) {
const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
import { ActivityPubActorType } from '../../shared/models/activitypub'
import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
import { AccountModel } from '../models/account/account'
-import { UserModel } from '../models/account/user'
import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
-import { createVideoChannel } from './video-channel'
-import { VideoChannelModel } from '../models/video/video-channel'
+import { createLocalVideoChannel } from './video-channel'
import { ActorModel } from '../models/activitypub/actor'
import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
import { Transaction } from 'sequelize/types'
import { Redis } from './redis'
import { Emailer } from './emailer'
+import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models'
+import { MUser, MUserDefault, MUserId } from '../typings/models/user'
type ChannelNames = { name: string, displayName: string }
+
async function createUserAccountAndChannelAndPlaylist (parameters: {
- userToCreate: UserModel,
+ userToCreate: MUser,
userDisplayName?: string,
channelNames?: ChannelNames,
validateUser?: boolean
-}) {
+}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> {
const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
validate: validateUser
}
- const userCreated = await userToCreate.save(userOptions)
+ const userCreated: MUserDefault = await userToCreate.save(userOptions)
userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
const accountCreated = await createLocalAccountWithoutKeys({
userCreated.Account = accountCreated
const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
- const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t)
+ const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t)
const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist }
})
- const [ accountKeys, channelKeys ] = await Promise.all([
+ const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([
setAsyncActorKeys(account.Actor),
setAsyncActorKeys(videoChannel.Actor)
])
- account.Actor = accountKeys
- videoChannel.Actor = channelKeys
+ account.Actor = accountActorWithKeys
+ videoChannel.Actor = channelActorWithKeys
- return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel }
+ return { user, account, videoChannel }
}
async function createLocalAccountWithoutKeys (parameters: {
const url = getAccountActivityPubUrl(name)
const actorInstance = buildActorInstance(type, url, name)
- const actorInstanceCreated = await actorInstance.save({ transaction: t })
+ const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t })
const accountInstance = new AccountModel({
name: displayName || name,
actorId: actorInstanceCreated.id
})
- const accountInstanceCreated = await accountInstance.save({ transaction: t })
+ const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t })
accountInstanceCreated.Actor = actorInstanceCreated
return accountInstanceCreated
return accountCreated
}
-async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) {
+async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) {
const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
// ---------------------------------------------------------------------------
-function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) {
+function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) {
const values: UserNotificationSetting & { userId: number } = {
userId: user.id,
newVideoFromSubscription: UserNotificationSettingValue.WEB,
return UserNotificationSettingModel.create(values, { transaction: t })
}
-async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) {
+async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) {
if (channelNames) return channelNames
let channelName = user.username + '_channel'
import { CONFIG } from '../initializers/config'
import { UserRight, VideoBlacklistType } from '../../shared/models'
import { VideoBlacklistModel } from '../models/video/video-blacklist'
-import { UserModel } from '../models/account/user'
-import { VideoModel } from '../models/video/video'
import { logger } from '../helpers/logger'
import { UserAdminFlag } from '../../shared/models/users/user-flag.model'
import { Hooks } from './plugins/hooks'
import { Notifier } from './notifier'
+import { MUser, MVideoBlacklist, MVideoWithBlacklistLight } from '@server/typings/models'
async function autoBlacklistVideoIfNeeded (parameters: {
- video: VideoModel,
- user?: UserModel,
+ video: MVideoWithBlacklistLight,
+ user?: MUser,
isRemote: boolean,
isNew: boolean,
notify?: boolean,
reason: 'Auto-blacklisted. Moderator review required.',
type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
}
- const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({
+ const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklist>({
where: {
videoId: video.id
},
}
async function autoBlacklistNeeded (parameters: {
- video: VideoModel,
+ video: MVideoWithBlacklistLight,
isRemote: boolean,
isNew: boolean,
- user?: UserModel
+ user?: MUser
}) {
const { user, video, isRemote, isNew } = parameters
import * as Sequelize from 'sequelize'
import * as uuidv4 from 'uuid/v4'
import { VideoChannelCreate } from '../../shared/models'
-import { AccountModel } from '../models/account/account'
import { VideoChannelModel } from '../models/video/video-channel'
import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
import { VideoModel } from '../models/video/video'
+import { MAccountId, MChannelDefault, MChannelId } from '../typings/models'
-async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
+type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault &
+ { Account?: T }
+
+async function createLocalVideoChannel <T extends MAccountId> (
+ videoChannelInfo: VideoChannelCreate,
+ account: T,
+ t: Sequelize.Transaction
+): Promise<CustomVideoChannelModelAccount<T>> {
const uuid = uuidv4()
const url = getVideoChannelActivityPubUrl(videoChannelInfo.name)
const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid)
actorId: actorInstanceCreated.id
}
- const videoChannel = VideoChannelModel.build(videoChannelData)
+ const videoChannel = new VideoChannelModel(videoChannelData)
const options = { transaction: t }
- const videoChannelCreated = await videoChannel.save(options)
+ const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelDefault
// Do not forget to add Account/Actor information to the created video channel
videoChannelCreated.Account = account
return videoChannelCreated
}
-async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
+async function federateAllVideosOfChannel (videoChannel: MChannelId) {
const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
for (const videoId of videoIds) {
// ---------------------------------------------------------------------------
export {
- createVideoChannel,
+ createLocalVideoChannel,
federateAllVideosOfChannel
}
import * as Sequelize from 'sequelize'
import { ResultList } from '../../shared/models'
import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model'
-import { AccountModel } from '../models/account/account'
-import { VideoModel } from '../models/video/video'
import { VideoCommentModel } from '../models/video/video-comment'
import { getVideoCommentActivityPubUrl } from './activitypub'
import { sendCreateVideoComment } from './activitypub/send'
+import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
async function createVideoComment (obj: {
text: string,
- inReplyToComment: VideoCommentModel | null,
- video: VideoModel
- account: AccountModel
+ inReplyToComment: MComment | null,
+ video: MVideoFullLight,
+ account: MAccountDefault
}, t: Sequelize.Transaction) {
let originCommentId: number | null = null
let inReplyToCommentId: number | null = null
comment.url = getVideoCommentActivityPubUrl(obj.video, comment)
- const savedComment = await comment.save({ transaction: t })
+ const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t })
savedComment.InReplyToVideoComment = obj.inReplyToComment
savedComment.Video = obj.video
savedComment.Account = obj.account
import * as Sequelize from 'sequelize'
-import { AccountModel } from '../models/account/account'
import { VideoPlaylistModel } from '../models/video/video-playlist'
import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
import { getVideoPlaylistActivityPubUrl } from './activitypub'
import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
+import { MAccount } from '../typings/models'
+import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist'
-async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) {
- const videoPlaylist = new VideoPlaylistModel({
+async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) {
+ const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({
name: 'Watch later',
privacy: VideoPlaylistPrivacy.PRIVATE,
type: VideoPlaylistType.WATCH_LATER,
import { logger } from '../helpers/logger'
import { VideoResolution } from '../../shared/models/videos'
import { VideoFileModel } from '../models/video/video-file'
-import { VideoModel } from '../models/video/video'
import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
import { CONFIG } from '../initializers/config'
+import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models'
/**
* Optimize the original video file and replace it. The resolution is not changed.
*/
-async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
+async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const newExtname = '.mp4'
/**
* Transcode the original video file to a lower resolution.
*/
-async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
+async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const extname = '.mp4'
return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
}
-async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
+async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) {
const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
const newExtname = '.mp4'
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
}
-async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
+async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) {
const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
// ---------------------------------------------------------------------------
-async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
+async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
const stats = await stat(transcodingPath)
const fps = await getVideoFileFPS(transcodingPath)
await move(transcodingPath, outputPath)
- videoFile.set('size', stats.size)
- videoFile.set('fps', fps)
+ videoFile.size = stats.size
+ videoFile.fps = fps
await video.createTorrentAndSetInfoHash(videoFile)
const verified = await isJsonLDSignatureVerified(actor, req.body)
if (verified !== true) {
+ logger.warn('Signature not verified.', req.body)
+
res.sendStatus(403)
return false
}
import { ActorModel } from '../../models/activitypub/actor'
import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor'
+import { MActorFollowActorsDefault } from '@server/typings/models'
const followValidator = [
body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'),
if (areValidationErrors(req, res)) return
- let follow: ActorFollowModel
+ let follow: MActorFollowActorsDefault
try {
const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost)
const actor = await ActorModel.loadByUrl(actorUrl)
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoFile = video.VideoFiles.find(f => {
return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps)
})
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- const video = res.locals.video
+ const video = res.locals.videoAll
const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType)
if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' })
import { isThemeRegistered } from '../../lib/plugins/theme-utils'
import { doesVideoExist } from '../../helpers/middlewares'
import { UserRole } from '../../../shared/models/users'
+import { MUserDefault } from '@server/typings/models'
const usersAddValidator = [
body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
return true
}
-async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) {
+async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) {
const user = await finder()
if (!user) {
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+ if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+ if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return
+ if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- const video = res.locals.video
+ const video = res.locals.videoAll
if (req.body.unfederate === true && video.remote === true) {
return res
.status(409)
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return
+ if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return
return next()
}
// Check if the user who did the request is able to update the video
const user = res.locals.oauth.token.User
- if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
+ if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return
+ if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return
// Check if the user who did the request is able to update the video
const user = res.locals.oauth.token.User
- if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return
+ if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return
return next()
}
isVideoChannelSupportValid
} from '../../../helpers/custom-validators/video-channels'
import { logger } from '../../../helpers/logger'
-import { UserModel } from '../../../models/account/user'
import { VideoChannelModel } from '../../../models/video/video-channel'
import { areValidationErrors } from '../utils'
import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
import { ActorModel } from '../../../models/activitypub/actor'
import { isBooleanValid } from '../../../helpers/custom-validators/misc'
import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares'
+import { MChannelAccountDefault, MUser } from '@server/typings/models'
const videoChannelsAddValidator = [
body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
// ---------------------------------------------------------------------------
-function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) {
+function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) {
if (videoChannel.Actor.isOwned() === false) {
res.status(403)
.json({ error: 'Cannot remove video channel of another server.' })
import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc'
import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
import { logger } from '../../../helpers/logger'
-import { UserModel } from '../../../models/account/user'
-import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { areValidationErrors } from '../utils'
import { Hooks } from '../../../lib/plugins/hooks'
-import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation'
+import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation'
import { doesVideoExist } from '../../../helpers/middlewares'
+import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video'
+import { MUser } from '@server/typings/models'
const listVideoCommentThreadsValidator = [
param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
- if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return
+ if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!isVideoCommentsEnabled(res.locals.video, res)) return
- if (!await isVideoCommentAccepted(req, res, false)) return
+ if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
+ if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!isVideoCommentsEnabled(res.locals.video, res)) return
- if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
- if (!await isVideoCommentAccepted(req, res, true)) return
+ if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
+ if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
+ if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res, 'id')) return
- if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
+ if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return
return next()
}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.videoId, res)) return
- if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return
+ if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return
// Check if the user who did the request is able to delete the video
- if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return
+ if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return
return next()
}
// ---------------------------------------------------------------------------
-async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) {
+async function doesVideoCommentThreadExist (id: number, video: MVideoId, res: express.Response) {
const videoComment = await VideoCommentModel.loadById(id)
if (!videoComment) {
return true
}
-async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) {
+async function doesVideoCommentExist (id: number, video: MVideoId, res: express.Response) {
const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id)
if (!videoComment) {
return false
}
- res.locals.videoComment = videoComment
+ res.locals.videoCommentFull = videoComment
return true
}
-function isVideoCommentsEnabled (video: VideoModel, res: express.Response) {
+function isVideoCommentsEnabled (video: MVideo, res: express.Response) {
if (video.commentsEnabled !== true) {
res.status(409)
.json({ error: 'Video comments are disabled for this video.' })
return true
}
-function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) {
+function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) {
const account = videoComment.Account
if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) {
res.status(403)
return true
}
-async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) {
+async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) {
const acceptParameters = {
- video: res.locals.video,
+ video,
commentBody: req.body,
user: res.locals.oauth.token.User
}
let acceptedResult: AcceptResult
if (isReply) {
- const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment })
+ const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull })
acceptedResult = await Hooks.wrapFun(
isLocalVideoCommentReplyAccepted,
import { body, param, query, ValidationChain } from 'express-validator'
import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared'
import { logger } from '../../../helpers/logger'
-import { UserModel } from '../../../models/account/user'
import { areValidationErrors } from '../utils'
import { isVideoImage } from '../../../helpers/custom-validators/videos'
import { CONSTRAINTS_FIELDS } from '../../../initializers/constants'
isVideoPlaylistTimestampValid,
isVideoPlaylistTypeValid
} from '../../../helpers/custom-validators/video-playlists'
-import { VideoPlaylistModel } from '../../../models/video/video-playlist'
import { cleanUpReqFiles } from '../../../helpers/express-utils'
import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element'
import { authenticatePromiseIfNeeded } from '../../oauth'
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
-import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist } from '../../../helpers/middlewares'
+import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares'
+import { MVideoPlaylist } from '../../../typings/models/video/video-playlist'
+import { MUserAccountId } from '@server/typings/models'
const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
body('displayName')
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req)
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = getPlaylist(res)
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
+ if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
return cleanUpReqFiles(req)
}
if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = getPlaylist(res)
if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) {
return res.status(400)
.json({ error: 'Cannot delete a watch later playlist.' })
}
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
+ if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) {
return
}
}
]
-const videoPlaylistsGetValidator = [
- param('playlistId')
- .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
+const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => {
+ return [
+ param('playlistId')
+ .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
- async (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params })
- if (areValidationErrors(req, res)) return
+ if (areValidationErrors(req, res)) return
- if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return
+ if (!await doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
- // Video is unlisted, check we used the uuid to fetch it
- if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
- if (isUUIDValid(req.params.playlistId)) return next()
+ // Video is unlisted, check we used the uuid to fetch it
+ if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) {
+ if (isUUIDValid(req.params.playlistId)) return next()
- return res.status(404).end()
- }
+ return res.status(404).end()
+ }
+
+ if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
+ await authenticatePromiseIfNeeded(req, res)
- if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) {
- await authenticatePromiseIfNeeded(req, res)
+ const user = res.locals.oauth ? res.locals.oauth.token.User : null
- const user = res.locals.oauth ? res.locals.oauth.token.User : null
+ if (
+ !user ||
+ (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
+ ) {
+ return res.status(403)
+ .json({ error: 'Cannot get this private video playlist.' })
+ }
- if (
- !user ||
- (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST))
- ) {
- return res.status(403)
- .json({ error: 'Cannot get this private video playlist.' })
+ return next()
}
return next()
}
-
- return next()
- }
-]
+ ]
+}
const videoPlaylistsAddVideoValidator = [
param('playlistId')
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
- const videoPlaylist = res.locals.videoPlaylist
- const video = res.locals.video
+ const videoPlaylist = getPlaylist(res)
+ const video = res.locals.onlyVideo
const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id)
if (videoPlaylistElement) {
return
}
- if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
+ if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) {
return
}
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = getPlaylist(res)
const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId)
if (!videoPlaylistElement) {
return res.status(403).end()
}
- res.locals.videoPlaylistElement = videoPlaylistElement
+ res.locals.videoPlaylistElementAP = videoPlaylistElement
return next()
}
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
- const videoPlaylist = res.locals.videoPlaylist
+ const videoPlaylist = getPlaylist(res)
if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return
const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id)
] as (ValidationChain | express.Handler)[]
}
-function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) {
+function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) {
if (videoPlaylist.isOwned() === false) {
res.status(403)
.json({ error: 'Cannot manage video playlist of another server.' })
return true
}
+
+function getPlaylist (res: express.Response) {
+ return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary
+}
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.id, res)) return
- const video = res.locals.video
+ const video = res.locals.videoAll
const share = await VideoShareModel.load(req.params.actorId, video.id)
if (!share) {
import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model'
import { AccountModel } from '../../../models/account/account'
-import { VideoFetchType } from '../../../helpers/video'
import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
import { getServerActor } from '../../../helpers/utils'
import { CONFIG } from '../../../initializers/config'
import { isLocalVideoAccepted } from '../../../lib/moderation'
import { Hooks } from '../../../lib/plugins/hooks'
import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares'
+import { MVideoFullLight } from '@server/typings/models'
+import { getVideoWithAttributes } from '../../../helpers/video'
const videosAddValidator = getCommonVideoEditAttributes().concat([
body('videofile')
// Check if the user who did the request is able to update the video
const user = res.locals.oauth.token.User
- if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
+ if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
])
async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) {
- const video = res.locals.video
+ const video = getVideoWithAttributes(res)
// Anybody can watch local videos
if (video.isOwned() === true) return next()
})
}
-const videosCustomGetValidator = (fetchType: VideoFetchType) => {
+const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights') => {
return [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
if (areValidationErrors(req, res)) return
if (!await doesVideoExist(req.params.id, res, fetchType)) return
- const video = res.locals.video
+ const video = getVideoWithAttributes(res)
+ const videoAll = video as MVideoFullLight
// Video private or blacklisted
- if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) {
+ if (video.privacy === VideoPrivacy.PRIVATE || videoAll.VideoBlacklist) {
await authenticatePromiseIfNeeded(req, res)
const user = res.locals.oauth ? res.locals.oauth.token.User : null
// Only the owner or a user that have blacklist rights can see the video
if (
!user ||
- (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST))
+ (videoAll.VideoChannel && videoAll.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST))
) {
return res.status(403)
.json({ error: 'Cannot get this private or blacklisted video.' })
if (!await doesVideoExist(req.params.id, res)) return
// Check if the user who did the request is able to delete the video
- if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return
+ if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return
return next()
}
if (!await doesVideoExist(req.params.videoId, res)) return
// Check if the user who did the request is able to change the ownership of the video
- if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
+ if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return
const nextOwner = await AccountModel.loadLocalByName(req.body.username)
if (!nextOwner) {
const nameWithHost = getHostWithPort(req.query.resource.substr(5))
const [ name ] = nameWithHost.split('@')
+ // FIXME: we don't need the full actor
const actor = await ActorModel.loadLocalByName(name)
if (!actor) {
return res.status(404)
.end()
}
- res.locals.actor = actor
+ res.locals.actorFull = actor
return next()
}
]
import { getSort } from '../utils'
import { AccountBlock } from '../../../shared/models/blocklist'
import { Op } from 'sequelize'
+import * as Bluebird from 'bluebird'
+import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/typings/models'
enum ScopeNames {
WITH_ACCOUNTS = 'WITH_ACCOUNTS'
})
}
- static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
+ static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird<MAccountBlocklist> {
const query = {
where: {
accountId,
return AccountBlocklistModel
.scope([ ScopeNames.WITH_ACCOUNTS ])
- .findAndCountAll(query)
+ .findAndCountAll<MAccountBlocklistAccounts>(query)
.then(({ rows, count }) => {
return { total: count, data: rows }
})
}
- toFormattedJSON (): AccountBlock {
+ toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock {
return {
byAccount: this.ByAccount.toFormattedJSON(),
blockedAccount: this.BlockedAccount.toFormattedJSON(),
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { AccountVideoRate } from '../../../shared'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
+import * as Bluebird from 'bluebird'
+import {
+ MAccountVideoRate,
+ MAccountVideoRateAccountUrl,
+ MAccountVideoRateAccountVideo,
+ MAccountVideoRateFormattable
+} from '@server/typings/models/video/video-rate'
/*
Account rates per video.
})
Account: AccountModel
- static load (accountId: number, videoId: number, transaction?: Transaction) {
+ static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird<MAccountVideoRate> {
const options: FindOptions = {
where: {
accountId,
return AccountVideoRateModel.findOne(options)
}
- static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, transaction?: Transaction) {
+ static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> {
const options: FindOptions = {
where: {
[ Op.or]: [
]
}
}
- if (transaction) options.transaction = transaction
+ if (t) options.transaction = t
return AccountVideoRateModel.findOne(options)
}
return AccountVideoRateModel.findAndCountAll(query)
}
- static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) {
+ static loadLocalAndPopulateVideo (
+ rateType: VideoRateType,
+ accountName: string,
+ videoId: number,
+ t?: Transaction
+ ): Bluebird<MAccountVideoRateAccountVideo> {
const options: FindOptions = {
where: {
videoId,
required: true,
include: [
{
- attributes: [ 'id', 'url', 'preferredUsername' ],
+ attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ],
model: ActorModel.unscoped(),
required: true,
where: {
}
]
}
- if (transaction) options.transaction = transaction
+ if (t) options.transaction = t
return AccountVideoRateModel.findOne(options)
}
]
}
- return AccountVideoRateModel.findAndCountAll(query)
+ return AccountVideoRateModel.findAndCountAll<MAccountVideoRateAccountUrl>(query)
}
static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
})
}
- toFormattedJSON (): AccountVideoRate {
+ toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate {
return {
video: this.Video.toFormattedJSON(),
rating: this.type
BeforeDestroy,
BelongsTo,
Column,
- CreatedAt, DataType,
+ CreatedAt,
+ DataType,
Default,
DefaultScope,
ForeignKey,
import { AccountBlocklistModel } from './account-blocklist'
import { ServerBlocklistModel } from '../server/server-blocklist'
import { ActorFollowModel } from '../activitypub/actor-follow'
+import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models'
+import * as Bluebird from 'bluebird'
export enum ScopeNames {
SUMMARY = 'SUMMARY'
return undefined
}
- static load (id: number, transaction?: Transaction) {
+ static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> {
return AccountModel.findByPk(id, { transaction })
}
- static loadByNameWithHost (nameWithHost: string) {
+ static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> {
const [ accountName, host ] = nameWithHost.split('@')
if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName)
return AccountModel.loadByNameAndHost(accountName, host)
}
- static loadLocalByName (name: string) {
+ static loadLocalByName (name: string): Bluebird<MAccountDefault> {
const query = {
where: {
[ Op.or ]: [
return AccountModel.findOne(query)
}
- static loadByNameAndHost (name: string, host: string) {
+ static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
const query = {
include: [
{
return AccountModel.findOne(query)
}
- static loadByUrl (url: string, transaction?: Transaction) {
+ static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> {
const query = {
include: [
{
})
}
- static listLocalsForSitemap (sort: string) {
+ static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> {
const query = {
attributes: [ ],
offset: 0,
.findAll(query)
}
- toFormattedJSON (): Account {
+ toFormattedJSON (this: MAccountFormattable): Account {
const actor = this.Actor.toFormattedJSON()
const account = {
id: this.id,
return Object.assign(actor, account)
}
- toFormattedSummaryJSON (): AccountSummary {
- const actor = this.Actor.toFormattedJSON()
+ toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary {
+ const actor = this.Actor.toFormattedSummaryJSON()
return {
id: this.id,
}
}
- toActivityPubObject () {
+ toActivityPubObject (this: MAccountAP) {
const obj = this.Actor.toActivityPubObject(this.name, 'Account')
return Object.assign(obj, {
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
import { clearCacheByUserId } from '../../lib/oauth-model'
+import { MNotificationSettingFormattable } from '@server/typings/models'
@Table({
tableName: 'userNotificationSetting',
return clearCacheByUserId(instance.userId)
}
- toFormattedJSON (): UserNotificationSetting {
+ toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting {
return {
newCommentOnMyVideo: this.newCommentOnMyVideo,
newVideoFromSubscription: this.newVideoFromSubscription,
import { ActorFollowModel } from '../activitypub/actor-follow'
import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server'
+import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user'
enum ScopeNames {
WITH_ALL = 'WITH_ALL'
return UserNotificationModel.update({ read: true }, query)
}
- toFormattedJSON (): UserNotification {
+ toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
const video = this.Video
? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
: undefined
}
}
- private formatVideo (video: VideoModel) {
+ formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
return {
id: video.id,
uuid: video.uuid,
}
}
- private formatActor (accountOrChannel: AccountModel | VideoChannelModel) {
+ formatActor (
+ this: UserNotificationModelForApi,
+ accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
+ ) {
const avatar = accountOrChannel.Actor.Avatar
? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
: undefined
import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { VideoModel } from '../video/video'
import { UserModel } from './user'
-import { Transaction, Op, DestroyOptions } from 'sequelize'
+import { DestroyOptions, Op, Transaction } from 'sequelize'
+import { MUserAccountId, MUserId } from '@server/typings/models'
@Table({
tableName: 'userVideoHistory',
})
User: UserModel
- static listForApi (user: UserModel, start: number, count: number) {
+ static listForApi (user: MUserAccountId, start: number, count: number) {
return VideoModel.listForApi({
start,
count,
})
}
- static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) {
+ static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) {
const query: DestroyOptions = {
where: {
userId: user.id
import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
+import * as Bluebird from 'bluebird'
+import {
+ MUserDefault,
+ MUserFormattable,
+ MUserId,
+ MUserNotifSettingChannelDefault,
+ MUserWithNotificationSetting
+} from '@server/typings/models'
enum ScopeNames {
WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
})
}
- static listWithRight (right: UserRight) {
+ static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
const roles = Object.keys(USER_ROLE_LABELS)
.map(k => parseInt(k, 10) as UserRole)
.filter(role => hasUserRight(role, right))
return UserModel.findAll(query)
}
- static listUserSubscribersOf (actorId: number) {
+ static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> {
const query = {
include: [
{
return UserModel.unscoped().findAll(query)
}
- static listByUsernames (usernames: string[]) {
+ static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> {
const query = {
where: {
username: usernames
return UserModel.findAll(query)
}
- static loadById (id: number) {
+ static loadById (id: number): Bluebird<MUserDefault> {
return UserModel.findByPk(id)
}
- static loadByUsername (username: string) {
+ static loadByUsername (username: string): Bluebird<MUserDefault> {
const query = {
where: {
username: { [ Op.iLike ]: username }
return UserModel.findOne(query)
}
- static loadByUsernameAndPopulateChannels (username: string) {
+ static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> {
const query = {
where: {
username: { [ Op.iLike ]: username }
return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query)
}
- static loadByEmail (email: string) {
+ static loadByEmail (email: string): Bluebird<MUserDefault> {
const query = {
where: {
email
return UserModel.findOne(query)
}
- static loadByUsernameOrEmail (username: string, email?: string) {
+ static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> {
if (!email) email = username
const query = {
return UserModel.findOne(query)
}
- static loadByVideoId (videoId: number) {
+ static loadByVideoId (videoId: number): Bluebird<MUserDefault> {
const query = {
include: [
{
return UserModel.findOne(query)
}
- static loadByVideoImportId (videoImportId: number) {
+ static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> {
const query = {
include: [
{
return UserModel.findOne(query)
}
- static loadByChannelActorId (videoChannelActorId: number) {
+ static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> {
const query = {
include: [
{
return UserModel.findOne(query)
}
- static loadByAccountActorId (accountActorId: number) {
+ static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> {
const query = {
include: [
{
return UserModel.findOne(query)
}
- static getOriginalVideoFileTotalFromUser (user: UserModel) {
+ static getOriginalVideoFileTotalFromUser (user: MUserId) {
// Don't use sequelize because we need to use a sub query
const query = UserModel.generateUserQuotaBaseSQL()
}
// Returns cumulative size of all video files uploaded in the last 24 hours.
- static getOriginalVideoFileTotalDailyFromUser (user: UserModel) {
+ static getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
// Don't use sequelize because we need to use a sub query
const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'')
return comparePassword(password, this.password)
}
- toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User {
+ toSummaryJSON
+
+ toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
const videoQuotaUsed = this.get('videoQuotaUsed')
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
import { ActorModel, unusedActorAttributesForAPI } from './actor'
import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from '../account/account'
-import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize'
+import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize'
+import {
+ MActorFollowActorsDefault,
+ MActorFollowActorsDefaultSubscription,
+ MActorFollowFollowingHost,
+ MActorFollowFormattable,
+ MActorFollowSubscriptions
+} from '@server/typings/models'
@Table({
tableName: 'actorFollow',
if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
}
- static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) {
+ static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> {
const query = {
where: {
actorId,
return ActorFollowModel.findOne(query)
}
- static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) {
+ static loadByActorAndTargetNameAndHostForAPI (
+ actorId: number,
+ targetName: string,
+ targetHost: string,
+ t?: Transaction
+ ): Bluebird<MActorFollowActorsDefaultSubscription> {
const actorFollowingPartInclude: IncludeOptions = {
model: ActorModel,
required: true,
})
}
- static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) {
+ static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird<MActorFollowFollowingHost[]> {
const whereTab = targets
.map(t => {
if (t.host) {
]
}
- return ActorFollowModel.findAndCountAll(query)
+ return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
.then(({ rows, count }) => {
return {
data: rows,
]
}
- return ActorFollowModel.findAndCountAll(query)
+ return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
.then(({ rows, count }) => {
return {
data: rows,
]
}
- return ActorFollowModel.findAndCountAll(query)
+ return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query)
.then(({ rows, count }) => {
return {
data: rows.map(r => r.ActorFollowing.VideoChannel),
return ActorFollowModel.findAll(query)
}
- toFormattedJSON (): ActorFollow {
+ toFormattedJSON (this: MActorFollowFormattable): ActorFollow {
const follower = this.ActorFollower.toFormattedJSON()
const following = this.ActorFollowing.toFormattedJSON()
import { VideoChannelModel } from '../video/video-channel'
import { ActorFollowModel } from './actor-follow'
import { VideoModel } from '../video/video'
+import {
+ MActor,
+ MActorAccountChannelId,
+ MActorAP,
+ MActorFormattable,
+ MActorFull,
+ MActorHost,
+ MActorRedundancyAllowedOpt,
+ MActorServer,
+ MActorSummaryFormattable
+} from '../../typings/models'
+import * as Bluebird from 'bluebird'
enum ScopeNames {
FULL = 'FULL'
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
inboxUrl: string
- @AllowNull(false)
- @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
+ @AllowNull(true)
+ @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true))
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
outboxUrl: string
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
sharedInboxUrl: string
- @AllowNull(false)
- @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
+ @AllowNull(true)
+ @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true))
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
followersUrl: string
- @AllowNull(false)
- @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
+ @AllowNull(true)
+ @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true))
@Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
followingUrl: string
})
VideoChannel: VideoChannelModel
- static load (id: number) {
+ static load (id: number): Bluebird<MActor> {
return ActorModel.unscoped().findByPk(id)
}
- static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) {
+ static loadFull (id: number): Bluebird<MActorFull> {
+ return ActorModel.scope(ScopeNames.FULL).findByPk(id)
+ }
+
+ static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> {
const query = {
include: [
{
.then(a => !!a)
}
- static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
+ static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> {
const query = {
where: {
followersUrl: {
return ActorModel.scope(ScopeNames.FULL).findAll(query)
}
- static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) {
+ static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
const query = {
where: {
preferredUsername,
return ActorModel.scope(ScopeNames.FULL).findOne(query)
}
- static loadByNameAndHost (preferredUsername: string, host: string) {
+ static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> {
const query = {
where: {
preferredUsername
return ActorModel.scope(ScopeNames.FULL).findOne(query)
}
- static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
+ static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> {
const query = {
where: {
url
return ActorModel.unscoped().findOne(query)
}
- static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) {
+ static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
const query = {
where: {
url
})
}
- toFormattedJSON () {
+ toFormattedSummaryJSON (this: MActorSummaryFormattable) {
let avatar: Avatar = null
if (this.Avatar) {
avatar = this.Avatar.toFormattedJSON()
}
return {
- id: this.id,
url: this.url,
name: this.preferredUsername,
host: this.getHost(),
+ avatar
+ }
+ }
+
+ toFormattedJSON (this: MActorFormattable) {
+ const base = this.toFormattedSummaryJSON()
+
+ return Object.assign(base, {
+ id: this.id,
hostRedundancyAllowed: this.getRedundancyAllowed(),
followingCount: this.followingCount,
followersCount: this.followersCount,
- avatar,
createdAt: this.createdAt,
updatedAt: this.updatedAt
- }
+ })
}
- toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') {
+ toActivityPubObject (this: MActorAP, name: string, type: 'Account' | 'Application' | 'VideoChannel') {
let activityPubType
if (type === 'Account') {
activityPubType = 'Person' as 'Person'
return this.serverId === null
}
- getWebfingerUrl () {
+ getWebfingerUrl (this: MActorServer) {
return 'acct:' + this.preferredUsername + '@' + this.getHost()
}
return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername
}
- getHost () {
+ getHost (this: MActorHost) {
return this.Server ? this.Server.host : WEBSERVER.HOST
}
import { CONFIG } from '../../initializers/config'
import { throwIfNotValid } from '../utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
+import { MAvatarFormattable } from '@server/typings/models'
@Table({
tableName: 'avatar',
return AvatarModel.findOne(query)
}
- toFormattedJSON (): Avatar {
+ toFormattedJSON (this: MAvatarFormattable): Avatar {
return {
path: this.getStaticPath(),
createdAt: this.createdAt,
import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor'
import { clearCacheByToken } from '../../lib/oauth-model'
+import * as Bluebird from 'bluebird'
+import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
export type OAuthTokenInfo = {
refreshToken: string
})
}
- static getByTokenAndPopulateUser (bearerToken: string) {
+ static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> {
const query = {
where: {
accessToken: bearerToken
return OAuthTokenModel.scope(ScopeNames.WITH_USER)
.findOne(query)
.then(token => {
- if (token) token[ 'user' ] = token.User
+ if (!token) return null
- return token
+ return Object.assign(token, { user: token.User })
})
}
- static getByRefreshTokenAndPopulateUser (refreshToken: string) {
+ static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> {
const query = {
where: {
refreshToken: refreshToken
return OAuthTokenModel.scope(ScopeNames.WITH_USER)
.findOne(query)
.then(token => {
- if (token) {
- token['user'] = token.User
- return token
- } else {
- return new OAuthTokenModel()
- }
+ if (!token) return new OAuthTokenModel()
+
+ return Object.assign(token, { user: token.User })
})
}
import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize'
import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
import { CONFIG } from '../../initializers/config'
+import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models'
export enum ScopeNames {
WITH_VIDEO = 'WITH_VIDEO'
return undefined
}
- static async loadLocalByFileId (videoFileId: number) {
+ static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> {
const actor = await getServerActor()
const query = {
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
}
- static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) {
+ static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> {
const actor = await getServerActor()
const query = {
return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
}
- static loadByUrl (url: string, transaction?: Transaction) {
+ static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> {
const query = {
where: {
url
return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
}
- static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) {
+ static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> {
const expiredDate = new Date()
expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs)
return !!this.strategy
}
- toActivityPubObject (): CacheFileObject {
+ toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject {
if (this.VideoStreamingPlaylist) {
return {
id: this.url,
import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
import { FindAndCountOptions, json } from 'sequelize'
import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
+import * as Bluebird from 'bluebird'
+import { MPlugin, MPluginFormattable } from '@server/typings/models'
@DefaultScope(() => ({
attributes: {
@UpdatedAt
updatedAt: Date
- static listEnabledPluginsAndThemes () {
+ static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> {
const query = {
where: {
enabled: true,
return PluginModel.findAll(query)
}
- static loadByNpmName (npmName: string) {
+ static loadByNpmName (npmName: string): Bluebird<MPlugin> {
const name = this.normalizePluginName(npmName)
const type = this.getTypeFromNpmName(npmName)
if (options.pluginType) query.where['type'] = options.pluginType
return PluginModel
- .findAndCountAll(query)
+ .findAndCountAll<MPlugin>(query)
.then(({ rows, count }) => {
return { total: count, data: rows }
})
}
- static listInstalled () {
+ static listInstalled (): Bluebird<MPlugin[]> {
const query = {
where: {
uninstalled: false
return result
}
- toFormattedJSON (): PeerTubePlugin {
+ toFormattedJSON (this: MPluginFormattable): PeerTubePlugin {
return {
name: this.name,
type: this.type,
import { ServerModel } from './server'
import { ServerBlock } from '../../../shared/models/blocklist'
import { getSort } from '../utils'
+import * as Bluebird from 'bluebird'
+import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models'
enum ScopeNames {
WITH_ACCOUNT = 'WITH_ACCOUNT',
})
BlockedServer: ServerModel
- static loadByAccountAndHost (accountId: number, host: string) {
+ static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> {
const query = {
where: {
accountId
return ServerBlocklistModel
.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ])
- .findAndCountAll(query)
+ .findAndCountAll<MServerBlocklistAccountServer>(query)
.then(({ rows, count }) => {
return { total: count, data: rows }
})
}
- toFormattedJSON (): ServerBlock {
+ toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock {
return {
byAccount: this.ByAccount.toFormattedJSON(),
blockedServer: this.BlockedServer.toFormattedJSON(),
import { isHostValid } from '../../helpers/custom-validators/servers'
import { ActorModel } from '../activitypub/actor'
import { throwIfNotValid } from '../utils'
-import { AccountBlocklistModel } from '../account/account-blocklist'
import { ServerBlocklistModel } from './server-blocklist'
+import * as Bluebird from 'bluebird'
+import { MServer, MServerFormattable } from '@server/typings/models/server'
@Table({
tableName: 'server',
})
BlockedByAccounts: ServerBlocklistModel[]
- static loadByHost (host: string) {
+ static loadByHost (host: string): Bluebird<MServer> {
const query = {
where: {
host
return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0
}
- toFormattedJSON () {
+ toFormattedJSON (this: MServerFormattable) {
return {
host: this.host
}
import { ScopeNames as VideoScopeNames, VideoModel } from './video'
import { VideoPrivacy } from '../../../shared/models/videos'
import { Op, Transaction } from 'sequelize'
+import { MScheduleVideoUpdateFormattable } from '@server/typings/models'
@Table({
tableName: 'scheduleVideoUpdate',
return ScheduleVideoUpdateModel.destroy(query)
}
- toFormattedJSON () {
+ toFormattedJSON (this: MScheduleVideoUpdateFormattable) {
return {
updateAt: this.updateAt,
privacy: this.privacy || undefined
import * as Bluebird from 'bluebird'
-import { QueryTypes, Transaction } from 'sequelize'
+import { fn, QueryTypes, Transaction, col } from 'sequelize'
import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { isVideoTagValid } from '../../helpers/custom-validators/videos'
import { throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import { VideoTagModel } from './video-tag'
import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
+import { MTag } from '@server/typings/models'
@Table({
tableName: 'tag',
{
fields: [ 'name' ],
unique: true
+ },
+ {
+ name: 'tag_lower_name',
+ fields: [ fn('lower', col('name')) ] as any // FIXME: typings
}
]
})
})
Videos: VideoModel[]
- static findOrCreateTags (tags: string[], transaction: Transaction) {
- if (tags === null) return []
+ static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> {
+ if (tags === null) return Promise.resolve([])
- const tasks: Bluebird<TagModel>[] = []
+ const tasks: Bluebird<MTag>[] = []
tags.forEach(tag => {
const query = {
where: {
transaction
}
- const promise = TagModel.findOrCreate(query)
+ const promise = TagModel.findOrCreate<MTag>(query)
.then(([ tagInstance ]) => tagInstance)
tasks.push(promise)
})
isVideoAbuseStateValid
} from '../../helpers/custom-validators/video-abuses'
import { AccountModel } from '../account/account'
-import { getSort, throwIfNotValid } from '../utils'
+import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import { VideoAbuseState } from '../../../shared'
import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
+import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
+import * as Bluebird from 'bluebird'
+import { literal, Op } from 'sequelize'
@Table({
tableName: 'videoAbuse',
})
Video: VideoModel
- static loadByIdAndVideoId (id: number, videoId: number) {
+ static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> {
const query = {
where: {
id,
return VideoAbuseModel.findOne(query)
}
- static listForApi (start: number, count: number, sort: string) {
+ static listForApi (parameters: {
+ start: number,
+ count: number,
+ sort: string,
+ serverAccountId: number
+ user?: MUserAccountId
+ }) {
+ const { start, count, sort, user, serverAccountId } = parameters
+ const userAccountId = user ? user.Account.id : undefined
+
const query = {
offset: start,
limit: count,
order: getSort(sort),
+ where: {
+ reporterAccountId: {
+ [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')')
+ }
+ },
include: [
{
model: AccountModel,
})
}
- toFormattedJSON (): VideoAbuse {
+ toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse {
return {
id: this.id,
reason: this.reason,
}
}
- toActivityPubObject (): VideoAbuseObject {
+ toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject {
return {
type: 'Flag' as 'Flag',
content: this.reason,
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
-import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
+import { VideoModel } from './video'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
import { FindOptions } from 'sequelize'
import { ThumbnailModel } from './thumbnail'
+import * as Bluebird from 'bluebird'
+import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/typings/models'
@Table({
tableName: 'videoBlacklist',
})
}
- static loadByVideoId (id: number) {
+ static loadByVideoId (id: number): Bluebird<MVideoBlacklist> {
const query = {
where: {
videoId: id
return VideoBlacklistModel.findOne(query)
}
- toFormattedJSON (): VideoBlacklist {
+ toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist {
return {
id: this.id,
createdAt: this.createdAt,
import { logger } from '../../helpers/logger'
import { remove } from 'fs-extra'
import { CONFIG } from '../../initializers/config'
+import * as Bluebird from 'bluebird'
+import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models'
export enum ScopeNames {
WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
[ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
include: [
{
- attributes: [ 'uuid', 'remote' ],
+ attributes: [ 'id', 'uuid', 'remote' ],
model: VideoModel.unscoped(),
required: true
}
return undefined
}
- static loadByVideoIdAndLanguage (videoId: string | number, language: string) {
+ static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird<MVideoCaptionVideo> {
const videoInclude = {
model: VideoModel.unscoped(),
attributes: [ 'id', 'remote', 'uuid' ],
.then(([ caption ]) => caption)
}
- static listVideoCaptions (videoId: number) {
+ static listVideoCaptions (videoId: number): Bluebird<MVideoCaptionVideo[]> {
const query = {
order: [ [ 'language', 'ASC' ] ] as OrderItem[],
where: {
return this.Video.remote === false
}
- toFormattedJSON (): VideoCaption {
+ toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption {
return {
language: {
id: this.language,
}
}
- getCaptionStaticPath () {
+ getCaptionStaticPath (this: MVideoCaptionFormattable) {
return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
}
- getCaptionName () {
+ getCaptionName (this: MVideoCaptionFormattable) {
return `${this.Video.uuid}-${this.language}.vtt`
}
- removeCaptionFile () {
+ removeCaptionFile (this: MVideoCaptionFormattable) {
return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
}
}
import { ScopeNames as VideoScopeNames, VideoModel } from './video'
import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
import { getSort } from '../utils'
+import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
+import * as Bluebird from 'bluebird'
enum ScopeNames {
WITH_ACCOUNTS = 'WITH_ACCOUNTS',
return Promise.all([
VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query),
- VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query)
+ VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll<MVideoChangeOwnershipFull>(query)
]).then(([ count, rows ]) => ({ total: count, data: rows }))
}
- static load (id: number) {
+ static load (id: number): Bluebird<MVideoChangeOwnershipFull> {
return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ])
.findByPk(id)
}
- toFormattedJSON (): VideoChangeOwnership {
+ toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership {
return {
id: this.id,
status: this.status,
import { FindOptions, ModelIndexesOptions, Op } from 'sequelize'
import { AvatarModel } from '../avatar/avatar'
import { VideoPlaylistModel } from './video-playlist'
+import * as Bluebird from 'bluebird'
+import {
+ MChannelAccountDefault,
+ MChannelActor,
+ MChannelActorAccountDefaultVideos,
+ MChannelAP,
+ MChannelFormattable,
+ MChannelSummaryFormattable
+} from '../../typings/models/video'
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
const indexes: ModelIndexesOptions[] = [
]
export enum ScopeNames {
- AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
+ FOR_API = 'FOR_API',
WITH_ACCOUNT = 'WITH_ACCOUNT',
WITH_ACTOR = 'WITH_ACTOR',
WITH_VIDEOS = 'WITH_VIDEOS',
@Scopes(() => ({
[ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
const base: FindOptions = {
- attributes: [ 'name', 'description', 'id', 'actorId' ],
+ attributes: [ 'id', 'name', 'description', 'actorId' ],
include: [
{
- attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
+ attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
model: ActorModel.unscoped(),
required: true,
include: [
return base
},
- [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
+ [ScopeNames.FOR_API]: (options: AvailableForListOptions) => {
// Only list local channels OR channels that are on an instance followed by actorId
const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
}
const scopes = {
- method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ]
+ method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ]
}
return VideoChannelModel
.scope(scopes)
})
}
- static listLocalsForSitemap (sort: string) {
+ static listLocalsForSitemap (sort: string): Bluebird<MChannelActor[]> {
const query = {
attributes: [ ],
offset: 0,
}
const scopes = {
- method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId: options.actorId } as AvailableForListOptions ]
+ method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ]
}
return VideoChannelModel
.scope(scopes)
})
}
- static loadByIdAndPopulateAccount (id: number) {
+ static loadByIdAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findByPk(id)
}
- static loadByIdAndAccount (id: number, accountId: number) {
+ static loadByIdAndAccount (id: number, accountId: number): Bluebird<MChannelAccountDefault> {
const query = {
where: {
id,
.findOne(query)
}
- static loadAndPopulateAccount (id: number) {
+ static loadAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
return VideoChannelModel.unscoped()
.scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
.findByPk(id)
}
- static loadByUrlAndPopulateAccount (url: string) {
+ static loadByUrlAndPopulateAccount (url: string): Bluebird<MChannelAccountDefault> {
const query = {
include: [
{
return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host)
}
- static loadLocalByNameAndPopulateAccount (name: string) {
+ static loadLocalByNameAndPopulateAccount (name: string): Bluebird<MChannelAccountDefault> {
const query = {
include: [
{
.findOne(query)
}
- static loadByNameAndHostAndPopulateAccount (name: string, host: string) {
+ static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird<MChannelAccountDefault> {
const query = {
include: [
{
.findOne(query)
}
- static loadAndPopulateAccountAndVideos (id: number) {
+ static loadAndPopulateAccountAndVideos (id: number): Bluebird<MChannelActorAccountDefaultVideos> {
const options = {
include: [
VideoModel
.findByPk(id, options)
}
- toFormattedJSON (): VideoChannel {
+ toFormattedSummaryJSON (this: MChannelSummaryFormattable): VideoChannelSummary {
+ const actor = this.Actor.toFormattedSummaryJSON()
+
+ return {
+ id: this.id,
+ name: actor.name,
+ displayName: this.getDisplayName(),
+ url: actor.url,
+ host: actor.host,
+ avatar: actor.avatar
+ }
+ }
+
+ toFormattedJSON (this: MChannelFormattable): VideoChannel {
const actor = this.Actor.toFormattedJSON()
const videoChannel = {
id: this.id,
return Object.assign(actor, videoChannel)
}
- toFormattedSummaryJSON (): VideoChannelSummary {
- const actor = this.Actor.toFormattedJSON()
-
- return {
- id: this.id,
- name: actor.name,
- displayName: this.getDisplayName(),
- url: actor.url,
- host: actor.host,
- avatar: actor.avatar
- }
- }
-
- toActivityPubObject (): ActivityPubActor {
+ toActivityPubObject (this: MChannelAP): ActivityPubActor {
const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
return Object.assign(obj, {
-import {
- AllowNull,
- BeforeDestroy,
- BelongsTo,
- Column,
- CreatedAt,
- DataType,
- ForeignKey,
- Is,
- Model,
- Scopes,
- Table,
- UpdatedAt
-} from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects'
import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
import { VideoComment } from '../../../shared/models/videos/video-comment.model'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
-import { sendDeleteVideoComment } from '../../lib/activitypub/send'
import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor'
-import { AvatarModel } from '../avatar/avatar'
-import { ServerModel } from '../server/server'
import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import { VideoChannelModel } from './video-channel'
import { getServerActor } from '../../helpers/utils'
-import { UserModel } from '../account/user'
import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
import { regexpCapture } from '../../helpers/regexp'
import { uniq } from 'lodash'
-import { FindOptions, literal, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
+import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
+import * as Bluebird from 'bluebird'
+import {
+ MComment,
+ MCommentAP,
+ MCommentFormattable,
+ MCommentId,
+ MCommentOwner,
+ MCommentOwnerReplyVideoLight,
+ MCommentOwnerVideo,
+ MCommentOwnerVideoFeed,
+ MCommentOwnerVideoReply
+} from '../../typings/models/video'
+import { MUserAccountId } from '@server/typings/models'
enum ScopeNames {
WITH_ACCOUNT = 'WITH_ACCOUNT',
[ScopeNames.WITH_ACCOUNT]: {
include: [
{
- model: AccountModel,
- include: [
- {
- model: ActorModel,
- include: [
- {
- model: ServerModel,
- required: false
- },
- {
- model: AvatarModel,
- required: false
- }
- ]
- }
- ]
+ model: AccountModel
}
]
},
required: true,
include: [
{
- model: VideoChannelModel.unscoped(),
+ model: VideoChannelModel,
required: true,
include: [
- {
- model: ActorModel,
- required: true
- },
{
model: AccountModel,
- required: true,
- include: [
- {
- model: ActorModel,
- required: true
- }
- ]
+ required: true
}
]
}
})
Account: AccountModel
- static loadById (id: number, t?: Transaction) {
+ static loadById (id: number, t?: Transaction): Bluebird<MComment> {
const query: FindOptions = {
where: {
id
return VideoCommentModel.findOne(query)
}
- static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) {
+ static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird<MCommentOwnerVideoReply> {
const query: FindOptions = {
where: {
id
.findOne(query)
}
- static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) {
+ static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird<MCommentOwnerVideo> {
const query: FindOptions = {
where: {
url
return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query)
}
- static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) {
+ static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird<MCommentOwnerReplyVideoLight> {
const query: FindOptions = {
where: {
url
start: number,
count: number,
sort: string,
- user?: UserModel
+ user?: MUserAccountId
}) {
const { videoId, start, count, sort, user } = parameters
static async listThreadCommentsForApi (parameters: {
videoId: number,
threadId: number,
- user?: UserModel
+ user?: MUserAccountId
}) {
const { videoId, threadId, user } = parameters
})
}
- static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') {
+ static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird<MCommentOwner[]> {
const query = {
order: [ [ 'createdAt', order ] ] as Order,
where: {
transaction: t
}
- return VideoCommentModel.findAndCountAll(query)
+ return VideoCommentModel.findAndCountAll<MComment>(query)
}
- static listForFeed (start: number, count: number, videoId?: number) {
+ static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> {
const query = {
order: [ [ 'createdAt', 'DESC' ] ] as Order,
offset: start,
return uniq(result)
}
- toFormattedJSON () {
+ toFormattedJSON (this: MCommentFormattable) {
return {
id: this.id,
url: this.url,
} as VideoComment
}
- toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject {
+ toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject {
let inReplyTo: string
// New thread, so in AS we reply to the video
if (this.inReplyToCommentId === null) {
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { FindOptions, QueryTypes, Transaction } from 'sequelize'
import { MIMETYPES } from '../../initializers/constants'
+import { MVideoFile } from '@server/typings/models'
@Table({
tableName: 'videoFile',
return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
}
- hasSameUniqueKeysThan (other: VideoFileModel) {
+ hasSameUniqueKeysThan (other: MVideoFile) {
return this.fps === other.fps &&
this.resolution === other.resolution &&
this.videoId === other.videoId
import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
import { VideoModel } from './video'
-import { VideoFileModel } from './video-file'
import {
ActivityPlaylistInfohashesObject,
ActivityPlaylistSegmentHashesObject,
} from '../../lib/activitypub'
import { isArray } from '../../helpers/custom-validators/misc'
import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model'
-import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
+import { MStreamingPlaylistRedundanciesOpt, MVideo, MVideoAP, MVideoFormattable, MVideoFormattableDetails } from '../../typings/models'
+import { MStreamingPlaylistRedundancies } from '../../typings/models/video/video-streaming-playlist'
+import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
export type VideoFormattingJSONOptions = {
completeDescription?: boolean
blacklistInfo?: boolean
}
}
-function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video {
+function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
const videoObject: Video = {
return videoObject
}
-function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
+function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
const formattedJson = video.toFormattedJSON({
additionalAttributes: {
scheduledUpdate: true,
const tags = video.Tags ? video.Tags.map(t => t.name) : []
- const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists)
+ const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video.VideoStreamingPlaylists)
const detailsJson = {
support: video.support,
return Object.assign(formattedJson, detailsJson)
}
-function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] {
+function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] {
if (isArray(playlists) === false) return []
return playlists
})
}
-function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] {
+function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] {
const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
return videoFiles
})
}
-function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
+function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
if (!video.Tags) video.Tags = []
import { VideoImport, VideoImportState } from '../../../shared'
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
import { UserModel } from '../account/user'
+import * as Bluebird from 'bluebird'
+import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import'
@DefaultScope(() => ({
include: [
required: true
},
{
- model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]),
+ model: VideoModel.scope([
+ VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
+ VideoModelScopeNames.WITH_TAGS,
+ VideoModelScopeNames.WITH_THUMBNAILS
+ ]),
required: false
}
]
return undefined
}
- static loadAndPopulateVideo (id: number) {
+ static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> {
return VideoImportModel.findByPk(id)
}
}
}
- return VideoImportModel.findAndCountAll(query)
+ return VideoImportModel.findAndCountAll<MVideoImportDefault>(query)
.then(({ rows, count }) => {
return {
data: rows,
return this.targetUrl || this.magnetUri || this.torrentName
}
- toFormattedJSON (): VideoImport {
+ toFormattedJSON (this: MVideoImportFormattable): VideoImport {
const videoFormatOptions = {
completeDescription: true,
additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
import * as validator from 'validator'
import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize'
-import { UserModel } from '../account/user'
import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model'
import { AccountModel } from '../account/account'
import { VideoPrivacy } from '../../../shared/models/videos'
+import * as Bluebird from 'bluebird'
+import {
+ MVideoPlaylistElement,
+ MVideoPlaylistElementAP,
+ MVideoPlaylistElementFormattable,
+ MVideoPlaylistElementVideoUrlPlaylistPrivacy,
+ MVideoPlaylistVideoThumbnail
+} from '@server/typings/models/video/video-playlist-element'
+import { MUserAccountId } from '@server/typings/models'
@Table({
tableName: 'videoPlaylistElement',
count: number,
videoPlaylistId: number,
serverAccount: AccountModel,
- user?: UserModel
+ user?: MUserAccountId
}) {
const accountIds = [ options.serverAccount.id ]
const videoScope: (ScopeOptions | string)[] = [
]).then(([ total, data ]) => ({ total, data }))
}
- static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) {
+ static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird<MVideoPlaylistElement> {
const query = {
where: {
videoPlaylistId,
return VideoPlaylistElementModel.findOne(query)
}
- static loadById (playlistElementId: number) {
+ static loadById (playlistElementId: number): Bluebird<MVideoPlaylistElement> {
return VideoPlaylistElementModel.findByPk(playlistElementId)
}
- static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) {
+ static loadByPlaylistAndVideoForAP (
+ playlistId: number | string,
+ videoId: number | string
+ ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> {
const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
})
}
- static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) {
+ static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird<MVideoPlaylistVideoThumbnail> {
const query = {
order: getSort('position'),
where: {
return VideoPlaylistElementModel.increment({ position: by }, query)
}
- getType (displayNSFW?: boolean, accountId?: number) {
+ getType (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
const video = this.Video
if (!video) return VideoPlaylistElementType.DELETED
return VideoPlaylistElementType.REGULAR
}
- getVideoElement (displayNSFW?: boolean, accountId?: number) {
+ getVideoElement (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
if (!this.Video) return null
if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null
return this.Video.toFormattedJSON()
}
- toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement {
+ toFormattedJSON (
+ this: MVideoPlaylistElementFormattable,
+ options: { displayNSFW?: boolean, accountId?: number } = {}
+ ): VideoPlaylistElement {
return {
id: this.id,
position: this.position,
}
}
- toActivityPubObject (): PlaylistElementObject {
+ toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject {
const base: PlaylistElementObject = {
id: this.url,
type: 'PlaylistElement',
import { ThumbnailModel } from './thumbnail'
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
+import * as Bluebird from 'bluebird'
+import {
+ MVideoPlaylistAccountThumbnail, MVideoPlaylistAP,
+ MVideoPlaylistFormattable,
+ MVideoPlaylistFull,
+ MVideoPlaylistFullSummary,
+ MVideoPlaylistIdWithElements
+} from '../../typings/models/video/video-playlist'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
enum ScopeNames {
AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
})
}
- static listPlaylistIdsOf (accountId: number, videoIds: number[]) {
+ static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> {
const query = {
attributes: [ 'id' ],
where: {
.then(e => !!e)
}
- static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) {
+ static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> {
const where = buildWhereIdOrUUID(id)
const query = {
.findOne(query)
}
- static loadWithAccountAndChannel (id: number | string, transaction: Transaction) {
+ static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> {
const where = buildWhereIdOrUUID(id)
const query = {
.findOne(query)
}
- static loadByUrlAndPopulateAccount (url: string) {
+ static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> {
const query = {
where: {
url
return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
}
- async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) {
+ async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) {
thumbnail.videoPlaylistId = this.id
this.Thumbnail = await thumbnail.save({ transaction: t })
return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL)
}
- toFormattedJSON (): VideoPlaylist {
+ toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist {
return {
id: this.id,
uuid: this.uuid,
}
}
- toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> {
+ toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> {
const handler = (start: number, count: number) => {
return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
}
import { VideoModel } from './video'
import { VideoChannelModel } from './video-channel'
import { Op, Transaction } from 'sequelize'
+import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video'
+import { MActorDefault } from '../../typings/models'
enum ScopeNames {
FULL = 'FULL',
})
Video: VideoModel
- static load (actorId: number, videoId: number, t?: Transaction) {
+ static load (actorId: number, videoId: number, t?: Transaction): Bluebird<MVideoShareActor> {
return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
where: {
actorId,
})
}
- static loadByUrl (url: string, t: Transaction) {
+ static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> {
return VideoShareModel.scope(ScopeNames.FULL).findOne({
where: {
url
})
}
- static loadActorsByShare (videoId: number, t: Transaction) {
+ static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> {
const query = {
where: {
videoId
}
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
- .then(res => res.map(r => r.Actor))
+ .then((res: MVideoShareFull[]) => res.map(r => r.Actor))
}
- static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<ActorModel[]> {
+ static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> {
const query = {
attributes: [],
include: [
.then(res => res.map(r => r.Actor))
}
- static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<ActorModel[]> {
+ static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> {
const query = {
attributes: [],
include: [
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
import { throwIfNotValid } from '../utils'
import { VideoModel } from './video'
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
-import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants'
-import { VideoFileModel } from './video-file'
+import { CONSTRAINTS_FIELDS, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants'
import { join } from 'path'
import { sha1 } from '../../helpers/core-utils'
import { isArrayOf } from '../../helpers/custom-validators/misc'
-import { QueryTypes, Op } from 'sequelize'
+import { Op, QueryTypes } from 'sequelize'
+import { MStreamingPlaylist, MVideoFile } from '@server/typings/models'
@Table({
tableName: 'videoStreamingPlaylist',
.then(results => results.length === 1)
}
- static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) {
+ static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: MVideoFile[]) {
const hashes: string[] = []
// https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115
return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid
}
- hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) {
+ hasSameUniqueKeysThan (other: MStreamingPlaylist) {
return this.type === other.type &&
this.videoId === other.videoId
}
Table,
UpdatedAt
} from 'sequelize-typescript'
-import { UserRight, VideoPrivacy, VideoResolution, VideoState } from '../../../shared'
+import { UserRight, VideoPrivacy, VideoState } from '../../../shared'
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
import { VideoFilter } from '../../../shared/models/videos/video-query.type'
videoModelToFormattedJSON
} from './video-format-utils'
import { UserVideoHistoryModel } from '../account/user-video-history'
-import { UserModel } from '../account/user'
import { VideoImportModel } from './video-import'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoPlaylistElementModel } from './video-playlist-element'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
import { createTorrentPromise } from '../../helpers/webtorrent'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
+import {
+ MChannel,
+ MChannelAccountDefault,
+ MChannelId,
+ MUserAccountId,
+ MUserId,
+ MVideoAccountLight,
+ MVideoAccountLightBlacklistAllFiles,
+ MVideoAP,
+ MVideoDetails,
+ MVideoFormattable,
+ MVideoFormattableDetails,
+ MVideoForUser,
+ MVideoFullLight,
+ MVideoIdThumbnail,
+ MVideoThumbnail,
+ MVideoThumbnailBlacklist,
+ MVideoWithAllFiles,
+ MVideoWithFile,
+ MVideoWithRights
+} from '../../typings/models'
+import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
+import { MThumbnail } from '../../typings/models/video/thumbnail'
// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
videoPlaylistId?: number
trendingDays?: number
- user?: UserModel,
- historyOfUser?: UserModel
+ user?: MUserAccountId
+ historyOfUser?: MUserId
baseWhere?: WhereOptions[]
}
// FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN()
if (options.tagsAllOf || options.tagsOneOf) {
if (options.tagsOneOf) {
+ const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase())
+
whereAnd.push({
id: {
[ Op.in ]: Sequelize.literal(
'(' +
'SELECT "videoId" FROM "videoTag" ' +
'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
- 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' +
+ 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' +
')'
)
}
}
if (options.tagsAllOf) {
+ const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase())
+
whereAnd.push({
id: {
[ Op.in ]: Sequelize.literal(
'(' +
'SELECT "videoId" FROM "videoTag" ' +
'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
- 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' +
- 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length +
+ 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' +
+ 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length +
')'
)
}
[ ScopeNames.WITH_BLACKLISTED ]: {
include: [
{
- attributes: [ 'id', 'reason' ],
+ attributes: [ 'id', 'reason', 'unfederated' ],
model: VideoBlacklistModel,
required: false
}
VideoCaptions: VideoCaptionModel[]
@BeforeDestroy
- static async sendDelete (instance: VideoModel, options) {
+ static async sendDelete (instance: MVideoAccountLight, options) {
if (instance.isOwned()) {
if (!instance.VideoChannel) {
instance.VideoChannel = await instance.$get('VideoChannel', {
include: [
- {
- model: AccountModel,
- include: [ ActorModel ]
- }
+ ActorModel,
+ AccountModel
],
transaction: options.transaction
- }) as VideoChannelModel
+ }) as MChannelAccountDefault
}
return sendDeleteVideo(instance, options.transaction)
return undefined
}
- static listLocal () {
+ static listLocal (): Bluebird<MVideoWithAllFiles[]> {
const query = {
where: {
remote: false
})
}
- static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
+ static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) {
function buildBaseQuery (): FindOptions {
return {
offset: start,
ScopeNames.WITH_THUMBNAILS
]
- if (withFiles === true) {
- findQuery.include.push({
- model: VideoFileModel.unscoped(),
- required: true
- })
- }
-
return Promise.all([
VideoModel.count(countQuery),
- VideoModel.scope(findScopes).findAll(findQuery)
+ VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery)
]).then(([ count, rows ]) => {
return {
data: rows,
followerActorId?: number
videoPlaylistId?: number,
trendingDays?: number,
- user?: UserModel,
- historyOfUser?: UserModel
+ user?: MUserAccountId,
+ historyOfUser?: MUserId
}, countVideos = true) {
if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
throw new Error('Try to filter all-local but no user has not the see all videos right')
tagsAllOf?: string[]
durationMin?: number // seconds
durationMax?: number // seconds
- user?: UserModel,
+ user?: MUserAccountId,
filter?: VideoFilter
}) {
const whereAnd = []
return VideoModel.getAvailableForApi(query, queryOptions)
}
- static load (id: number | string, t?: Transaction) {
+ static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithRights (id: number | string, t?: Transaction) {
+ static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> {
+ const where = buildWhereIdOrUUID(id)
+ const options = {
+ where,
+ transaction: t
+ }
+
+ return VideoModel.scope([
+ ScopeNames.WITH_THUMBNAILS,
+ ScopeNames.WITH_BLACKLISTED
+ ]).findOne(options)
+ }
+
+ static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
const where = buildWhereIdOrUUID(id)
const options = {
where,
]).findOne(options)
}
- static loadOnlyId (id: number | string, t?: Transaction) {
+ static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> {
const where = buildWhereIdOrUUID(id)
const options = {
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean) {
+ static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> {
const where = buildWhereIdOrUUID(id)
const query = {
]).findOne(query)
}
- static loadByUUID (uuid: string) {
+ static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> {
const options = {
where: {
uuid
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
}
- static loadByUrl (url: string, transaction?: Transaction) {
+ static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> {
const query: FindOptions = {
where: {
url
return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
}
- static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) {
+ static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
const query: FindOptions = {
where: {
url
ScopeNames.WITH_ACCOUNT_DETAILS,
ScopeNames.WITH_FILES,
ScopeNames.WITH_STREAMING_PLAYLISTS,
- ScopeNames.WITH_THUMBNAILS
+ ScopeNames.WITH_THUMBNAILS,
+ ScopeNames.WITH_BLACKLISTED
]).findOne(query)
}
- static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) {
+ static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> {
const where = buildWhereIdOrUUID(id)
const options = {
id: number | string,
t?: Transaction,
userId?: number
- }) {
+ }): Bluebird<MVideoDetails> {
const { id, t, userId } = parameters
const where = buildWhereIdOrUUID(id)
.then(results => results.length === 1)
}
- static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) {
+ static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) {
const options = {
where: {
channelId: videoChannel.id
return VideoModel.update({ support: videoChannel.support }, options)
}
- static getAllIdsFromChannel (videoChannel: VideoChannelModel) {
+ static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> {
const query = {
attributes: [ 'id' ],
where: {
this.VideoChannel.Account.isBlocked()
}
- getOriginalFile () {
+ getOriginalFile <T extends MVideoWithFile> (this: T) {
if (Array.isArray(this.VideoFiles) === false) return undefined
// The original file is the file that have the higher resolution
return maxBy(this.VideoFiles, file => file.resolution)
}
- getFile (resolution: number) {
+ getFile <T extends MVideoWithFile> (this: T, resolution: number) {
if (Array.isArray(this.VideoFiles) === false) return undefined
return this.VideoFiles.find(f => f.resolution === resolution)
}
- async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) {
+ async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
thumbnail.videoId = this.id
const savedThumbnail = await thumbnail.save({ transaction })
this.Thumbnails.push(savedThumbnail)
}
- getVideoFilename (videoFile: VideoFileModel) {
+ getVideoFilename (videoFile: MVideoFile) {
return this.uuid + '-' + videoFile.resolution + videoFile.extname
}
return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
}
- getTorrentFileName (videoFile: VideoFileModel) {
+ getTorrentFileName (videoFile: MVideoFile) {
const extension = '.torrent'
return this.uuid + '-' + videoFile.resolution + extension
}
return this.remote === false
}
- getTorrentFilePath (videoFile: VideoFileModel) {
+ getTorrentFilePath (videoFile: MVideoFile) {
return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
}
- getVideoFilePath (videoFile: VideoFileModel) {
+ getVideoFilePath (videoFile: MVideoFile) {
return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
}
- async createTorrentAndSetInfoHash (videoFile: VideoFileModel) {
+ async createTorrentAndSetInfoHash (videoFile: MVideoFile) {
const options = {
// Keep the extname, it's used by the client to stream the file inside a web browser
name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`,
return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename)
}
- toFormattedJSON (options?: VideoFormattingJSONOptions): Video {
+ toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
return videoModelToFormattedJSON(this, options)
}
- toFormattedDetailsJSON (): VideoDetails {
+ toFormattedDetailsJSON (this: MVideoFormattableDetails): VideoDetails {
return videoModelToFormattedDetailsJSON(this)
}
return videoFilesModelToFormattedJSON(this, this.VideoFiles)
}
- toActivityPubObject (): VideoTorrentObject {
+ toActivityPubObject (this: MVideoAP): VideoTorrentObject {
return videoModelToActivityPubObject(this)
}
return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
}
- removeFile (videoFile: VideoFileModel, isRedundancy = false) {
+ removeFile (videoFile: MVideoFile, isRedundancy = false) {
const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
const filePath = join(baseDir, this.getVideoFilename(videoFile))
.catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
}
- removeTorrent (videoFile: VideoFileModel) {
+ removeTorrent (videoFile: MVideoFile) {
const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
return remove(torrentPath)
.catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
return { baseUrlHttp, baseUrlWs }
}
- generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) {
+ generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) {
const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs)
let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
}
- getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
}
- getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
}
- getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
}
- getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
}
- getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+ getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
}
- getBandwidthBits (videoFile: VideoFileModel) {
+ getBandwidthBits (videoFile: MVideoFile) {
return Math.ceil((videoFile.size * 8) / this.duration)
}
}
expect(result).to.be.false
})
- it('Should fail with an invalid PeerTube URL', async function () {
- const keys = require('./json/peertube/keys.json')
- const body = require('./json/peertube/announce-without-context.json')
-
- const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
- const signedBody = await buildSignedActivity(actorSignature as any, body)
-
- const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' }
- const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
-
- expect(result).to.be.false
- })
-
it('Should succeed with a valid PeerTube signature', async function () {
const keys = require('./json/peertube/keys.json')
const body = require('./json/peertube/announce-without-context.json')
const query = {
search: '9999',
categoryOneOf: [ 1 ],
- tagsOneOf: [ 'aaaa', 'ffff' ]
+ tagsOneOf: [ 'aAaa', 'ffff' ]
}
const res1 = await advancedVideosSearch(server.url, query)
expect(res1.body.total).to.equal(2)
const query = {
search: '9999',
categoryOneOf: [ 1 ],
- tagsAllOf: [ 'cccc' ]
+ tagsAllOf: [ 'CCcc' ]
}
const res1 = await advancedVideosSearch(server.url, query)
expect(res1.body.total).to.equal(2)
- const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blabla' ] }))
+ const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blAbla' ] }))
expect(res2.body.total).to.equal(0)
- const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'cccc' ] }))
+ const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'CCCC' ] }))
expect(res3.body.total).to.equal(1)
})
} from '../../../../shared/extra-utils/index'
import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
+import {
+ addAccountToServerBlocklist,
+ addServerToServerBlocklist,
+ removeAccountFromServerBlocklist,
+ removeServerFromServerBlocklist
+} from '../../../../shared/extra-utils/users/blocklist'
const expect = chai.expect
expect(res.body.data[0].moderationComment).to.equal('It is valid')
})
+ it('Should hide video abuses from blocked accounts', async function () {
+ this.timeout(10000)
+
+ {
+ await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this')
+ await waitJobs(servers)
+
+ const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
+ expect(res.body.total).to.equal(3)
+ }
+
+ const accountToBlock = 'root@localhost:' + servers[1].port
+
+ {
+ await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock)
+
+ const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
+ expect(res.body.total).to.equal(2)
+
+ const abuse = res.body.data.find(a => a.reason === 'will mute this')
+ expect(abuse).to.be.undefined
+ }
+
+ {
+ await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock)
+
+ const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
+ expect(res.body.total).to.equal(3)
+ }
+ })
+
+ it('Should hide video abuses from blocked servers', async function () {
+ const serverToBlock = servers[1].host
+
+ {
+ await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host)
+
+ const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
+ expect(res.body.total).to.equal(2)
+
+ const abuse = res.body.data.find(a => a.reason === 'will mute this')
+ expect(abuse).to.be.undefined
+ }
+
+ {
+ await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock)
+
+ const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken)
+ expect(res.body.total).to.equal(3)
+ }
+ })
+
it('Should delete the video abuse', async function () {
+ this.timeout(10000)
+
await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id)
- const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
- expect(res.body.total).to.equal(0)
- expect(res.body.data).to.be.an('array')
- expect(res.body.data.length).to.equal(0)
+ await waitJobs(servers)
+
+ {
+ const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
+ expect(res.body.total).to.equal(1)
+ expect(res.body.data.length).to.equal(1)
+ expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
+ }
+
+ {
+ const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
+ expect(res.body.total).to.equal(3)
+ }
})
after(async function () {
await waitJobs(servers)
})
- it('Should have video channel updated', async function () {
+ it('Should have the channel of the video updated', async function () {
for (const server of servers) {
const res = await getVideo(server.url, servers[0].video.uuid)
import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
import { Command } from 'commander'
import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
+import { createLogger, format, transports } from 'winston'
let configName = 'PeerTube/CLI'
if (isTestInstance()) configName += `-${getAppNumber()}`
.option('-m, --comments-enabled', 'Enable comments')
.option('-s, --support <support>', 'Video support text')
.option('-w, --wait-transcoding', 'Wait transcoding before publishing the video')
+ .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
}
async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) {
})
}
+function getLogger (logLevel = 'info') {
+ const logLevels = {
+ 0: 0,
+ error: 0,
+ 1: 1,
+ warn: 1,
+ 2: 2,
+ info: 2,
+ 3: 3,
+ verbose: 3,
+ 4: 4,
+ debug: 4
+ }
+
+ const logger = createLogger({
+ levels: logLevels,
+ format: format.combine(
+ format.splat(),
+ format.simple()
+ ),
+ transports: [
+ new (transports.Console)({
+ level: logLevel
+ })
+ ]
+ })
+
+ return logger
+}
+
// ---------------------------------------------------------------------------
export {
version,
config,
+ getLogger,
getSettings,
getNetrc,
getRemoteObjectOrDie,
import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index'
import { truncate } from 'lodash'
import * as prompt from 'prompt'
+import { accessSync, constants } from 'fs'
import { remove } from 'fs-extra'
import { sha256 } from '../helpers/core-utils'
import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
-import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli'
+import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli'
type UserInfo = {
username: string
}
const processOptions = {
- cwd: __dirname,
maxBuffer: Infinity
}
.option('--target-url <targetUrl>', 'Video target URL')
.option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate)
.option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate)
- .option('-v, --verbose', 'Verbose mode')
+ .option('--first <first>', 'Process first n elements of returned playlist')
+ .option('--last <last>', 'Process last n elements of returned playlist')
+ .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname)
.parse(process.argv)
+let log = getLogger(program[ 'verbose' ])
+
getServerCredentials(command)
.then(({ url, username, password }) => {
if (!program[ 'targetUrl' ]) {
- console.error('--targetUrl field is required.')
+ exitError('--target-url field is required.')
+ }
- process.exit(-1)
+ try {
+ accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK)
+ } catch (e) {
+ exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ])
}
removeEndSlashes(url)
run(url, user)
.catch(err => {
- console.error(err)
- process.exit(-1)
+ exitError(err)
})
})
const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => {
if (err) {
- console.log(err.message)
- process.exit(1)
+ exitError(err.message)
}
let infoArray: any[]
// Normalize utf8 fields
- if (Array.isArray(info) === true) {
- infoArray = info.map(i => normalizeObject(i))
- } else {
- infoArray = [ normalizeObject(info) ]
+ infoArray = [].concat(info);
+ if (program[ 'first' ]) {
+ infoArray = infoArray.slice(0, program[ 'first' ])
+ } else if (program[ 'last' ]) {
+ infoArray = infoArray.slice(- program[ 'last' ])
}
- console.log('Will download and upload %d videos.\n', infoArray.length)
+ infoArray = infoArray.map(i => normalizeObject(i))
+
+ log.info('Will download and upload %d videos.\n', infoArray.length)
for (const info of infoArray) {
await processVideo({
- cwd: processOptions.cwd,
+ cwd: program[ 'tmpdir' ],
url,
user,
youtubeInfo: info
})
}
- console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ])
+ log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ])
process.exit(0)
})
}
const { youtubeInfo, cwd, url, user } = parameters
return new Promise(async res => {
- if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo)
+ log.debug('Fetching object.', youtubeInfo)
const videoInfo = await fetchObject(youtubeInfo)
- if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo)
+ log.debug('Fetched object.', videoInfo)
if (program[ 'since' ]) {
if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) {
- console.log('Video "%s" has been published before "%s", don\'t upload it.\n',
+ log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
videoInfo.title, formatDate(program[ 'since' ]));
return res();
}
}
if (program[ 'until' ]) {
if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) {
- console.log('Video "%s" has been published after "%s", don\'t upload it.\n',
+ log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
videoInfo.title, formatDate(program[ 'until' ]));
return res();
}
const result = await searchVideoWithSort(url, videoInfo.title, '-match')
- console.log('############################################################\n')
+ log.info('############################################################\n')
if (result.body.data.find(v => v.name === videoInfo.title)) {
- console.log('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title)
+ log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title)
return res()
}
const path = join(cwd, sha256(videoInfo.url) + '.mp4')
- console.log('Downloading video "%s"...', videoInfo.title)
+ log.info('Downloading video "%s"...', videoInfo.title)
const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ]
try {
const youtubeDL = await safeGetYoutubeDL()
youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => {
if (err) {
- console.error(err)
+ log.error(err)
return res()
}
- console.log(output.join('\n'))
+ log.info(output.join('\n'))
await uploadVideoOnPeerTube({
cwd,
url,
return res()
})
} catch (err) {
- console.log(err.message)
+ log.error(err.message)
return res()
}
})
fixture: videoPath
})
- console.log('\nUploading on PeerTube video "%s".', videoAttributes.name)
+ log.info('\nUploading on PeerTube video "%s".', videoAttributes.name)
let accessToken = await getAccessTokenOrDie(url, user)
await uploadVideo(url, accessToken, videoAttributes)
} catch (err) {
if (err.message.indexOf('401') !== -1) {
- console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.')
+ log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.')
accessToken = await getAccessTokenOrDie(url, user)
await uploadVideo(url, accessToken, videoAttributes)
} else {
- console.log(err.message)
- process.exit(1)
+ exitError(err.message)
}
}
await remove(videoPath)
if (thumbnailfile) await remove(thumbnailfile)
- console.log('Uploaded video "%s"!\n', videoAttributes.name)
+ log.warn('Uploaded video "%s"!\n', videoAttributes.name)
}
/* ---------------------------------------------------------- */
const res = await login(url, client, user)
return res.body.access_token
} catch (err) {
- console.error('Cannot authenticate. Please check your username/password.')
- process.exit(-1)
+ exitError('Cannot authenticate. Please check your username/password.')
}
}
function parseDate (dateAsStr: string): Date {
if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) {
- console.error(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`);
- process.exit(-1);
+ exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`);
}
const date = new Date(dateAsStr);
if (isNaN(date.getTime())) {
- console.error(`Invalid date passed: ${dateAsStr}. See help for usage.`);
- process.exit(-1);
+ exitError(`Invalid date passed: ${dateAsStr}. See help for usage.`);
}
return date;
}
function formatDate (date: Date): string {
return date.toISOString().split('T')[0];
}
+
+function exitError (message:string, ...meta: any[]) {
+ // use console.error instead of log.error here
+ console.error(message, ...meta)
+ process.exit(-1)
+}
import { Activity } from '../../shared/models/activitypub'
-import { ActorModel } from '../models/activitypub/actor'
-import { SignatureActorModel } from './models'
+import { MActorDefault, MActorSignature } from './models'
export type APProcessorOptions<T extends Activity> = {
activity: T
- byActor: SignatureActorModel
- inboxActor?: ActorModel
+ byActor: MActorSignature
+ inboxActor?: MActorDefault
fromFetch?: boolean
}
-import { VideoChannelModel } from '../models/video/video-channel'
-import { VideoPlaylistModel } from '../models/video/video-playlist'
-import { VideoPlaylistElementModel } from '../models/video/video-playlist-element'
-import { UserModel } from '../models/account/user'
-import { VideoModel } from '../models/video/video'
-import { AccountModel } from '../models/account/account'
-import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership'
-import { ActorModel } from '../models/activitypub/actor'
-import { VideoCommentModel } from '../models/video/video-comment'
-import { VideoShareModel } from '../models/video/video-share'
-import { AccountVideoRateModel } from '../models/account/account-video-rate'
-import { ActorFollowModel } from '../models/activitypub/actor-follow'
-import { ServerModel } from '../models/server/server'
-import { VideoFileModel } from '../models/video/video-file'
-import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
-import { ServerBlocklistModel } from '../models/server/server-blocklist'
-import { AccountBlocklistModel } from '../models/account/account-blocklist'
-import { VideoImportModel } from '../models/video/video-import'
-import { VideoAbuseModel } from '../models/video/video-abuse'
-import { VideoBlacklistModel } from '../models/video/video-blacklist'
-import { VideoCaptionModel } from '../models/video/video-caption'
-import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
-import { PluginModel } from '../models/server/plugin'
-import { SignatureActorModel } from './models'
+import {
+ MAccountDefault,
+ MActorAccountChannelId,
+ MActorFollowActorsDefault,
+ MActorFollowActorsDefaultSubscription,
+ MActorFull,
+ MChannelAccountDefault,
+ MComment,
+ MCommentOwnerVideoReply,
+ MUserDefault,
+ MVideoAbuse,
+ MVideoBlacklist,
+ MVideoCaptionVideo,
+ MVideoFullLight,
+ MVideoIdThumbnail,
+ MVideoRedundancyVideo,
+ MVideoShareActor,
+ MVideoThumbnail,
+ MVideoWithRights
+} from './models'
+import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist'
+import { MVideoImportDefault } from '@server/typings/models/video/video-import'
+import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models'
+import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element'
+import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate'
+import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership'
+import { MPlugin, MServer } from '@server/typings/models/server'
+import { MServerBlocklist } from './models/server/server-blocklist'
+import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
declare module 'express' {
interface Response {
+
locals: {
- video?: VideoModel
- videoShare?: VideoShareModel
- videoFile?: VideoFileModel
+ videoAll?: MVideoFullLight
+ onlyVideo?: MVideoThumbnail
+ onlyVideoWithRights?: MVideoWithRights
+ videoId?: MVideoIdThumbnail
+
+ videoShare?: MVideoShareActor
+
+ videoFile?: MVideoFile
+
+ videoImport?: MVideoImportDefault
+
+ videoBlacklist?: MVideoBlacklist
+
+ videoCaption?: MVideoCaptionVideo
+
+ videoAbuse?: MVideoAbuse
- videoImport?: VideoImportModel
+ videoStreamingPlaylist?: MStreamingPlaylist
- videoBlacklist?: VideoBlacklistModel
+ videoChannel?: MChannelAccountDefault
- videoCaption?: VideoCaptionModel
+ videoPlaylistFull?: MVideoPlaylistFull
+ videoPlaylistSummary?: MVideoPlaylistFullSummary
- videoAbuse?: VideoAbuseModel
+ videoPlaylistElement?: MVideoPlaylistElement
+ videoPlaylistElementAP?: MVideoPlaylistElementVideoUrlPlaylistPrivacy
- videoStreamingPlaylist?: VideoStreamingPlaylistModel
+ accountVideoRate?: MAccountVideoRateAccountVideo
- videoChannel?: VideoChannelModel
+ videoCommentFull?: MCommentOwnerVideoReply
+ videoCommentThread?: MComment
- videoPlaylist?: VideoPlaylistModel
- videoPlaylistElement?: VideoPlaylistElementModel
+ follow?: MActorFollowActorsDefault
+ subscription?: MActorFollowActorsDefaultSubscription
- accountVideoRate?: AccountVideoRateModel
+ nextOwner?: MAccountDefault
+ videoChangeOwnership?: MVideoChangeOwnershipFull
- videoComment?: VideoCommentModel
- videoCommentThread?: VideoCommentModel
+ account?: MAccountDefault
- follow?: ActorFollowModel
- subscription?: ActorFollowModel
+ actorFull?: MActorFull
- nextOwner?: AccountModel
- videoChangeOwnership?: VideoChangeOwnershipModel
- account?: AccountModel
- actor?: ActorModel
- user?: UserModel
+ user?: MUserDefault
- server?: ServerModel
+ server?: MServer
- videoRedundancy?: VideoRedundancyModel
+ videoRedundancy?: MVideoRedundancyVideo
- accountBlock?: AccountBlocklistModel
- serverBlock?: ServerBlocklistModel
+ accountBlock?: MAccountBlocklist
+ serverBlock?: MServerBlocklist
oauth?: {
- token: {
- User: UserModel
- user: UserModel
- }
+ token: MOAuthTokenUser
}
signature?: {
- actor: SignatureActorModel
+ actor: MActorAccountChannelId
}
authenticated?: boolean
registeredPlugin?: RegisteredPlugin
- plugin?: PluginModel
+ plugin?: MPlugin
}
}
}
--- /dev/null
+import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
+import { PickWith } from '../../utils'
+import { MAccountDefault, MAccountFormattable } from './account'
+
+type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M>
+
+// ############################################################################
+
+export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'BlockedAccount'>
+
+// ############################################################################
+
+export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'>
+
+export type MAccountBlocklistAccounts = MAccountBlocklist &
+ Use<'ByAccount', MAccountDefault> &
+ Use<'BlockedAccount', MAccountDefault>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> &
+ Use<'ByAccount', MAccountFormattable> &
+ Use<'BlockedAccount', MAccountFormattable>
--- /dev/null
+import { AccountModel } from '../../../models/account/account'
+import {
+ MActor,
+ MActorAP,
+ MActorAPI,
+ MActorAudience,
+ MActorDefault,
+ MActorDefaultLight,
+ MActorFormattable,
+ MActorId,
+ MActorServer,
+ MActorSummary,
+ MActorSummaryFormattable,
+ MActorUrl
+} from './actor'
+import { FunctionProperties, PickWith } from '../../utils'
+import { MAccountBlocklistId } from './account-blocklist'
+import { MChannelDefault } from '@server/typings/models'
+
+type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
+
+// ############################################################################
+
+export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
+ 'VideoComments' | 'BlockedAccounts'>
+
+// ############################################################################
+
+// Only some attributes
+export type MAccountId = Pick<MAccount, 'id'>
+export type MAccountUserId = Pick<MAccount, 'userId'>
+
+// Only some Actor attributes
+export type MAccountUrl = Use<'Actor', MActorUrl>
+export type MAccountAudience = Use<'Actor', MActorAudience>
+
+export type MAccountIdActor = MAccountId &
+ Use<'Actor', MActor>
+
+export type MAccountIdActorId = MAccountId &
+ Use<'Actor', MActorId>
+
+// ############################################################################
+
+// Default scope
+export type MAccountDefault = MAccount &
+ Use<'Actor', MActorDefault>
+
+// Default with default association scopes
+export type MAccountDefaultChannelDefault = MAccount &
+ Use<'Actor', MActorDefault> &
+ Use<'VideoChannels', MChannelDefault[]>
+
+// We don't need some actors attributes
+export type MAccountLight = MAccount &
+ Use<'Actor', MActorDefaultLight>
+
+// ############################################################################
+
+// Full actor
+export type MAccountActor = MAccount &
+ Use<'Actor', MActor>
+
+// Full actor with server
+export type MAccountServer = MAccount &
+ Use<'Actor', MActorServer>
+
+// ############################################################################
+
+// For API
+
+export type MAccountSummary = FunctionProperties<MAccount> &
+ Pick<MAccount, 'id' | 'name'> &
+ Use<'Actor', MActorSummary>
+
+export type MAccountSummaryBlocks = MAccountSummary &
+ Use<'BlockedAccounts', MAccountBlocklistId[]>
+
+export type MAccountAPI = MAccount &
+ Use<'Actor', MActorAPI>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MAccountSummaryFormattable = FunctionProperties<MAccount> &
+ Pick<MAccount, 'id' | 'name'> &
+ Use<'Actor', MActorSummaryFormattable>
+
+export type MAccountFormattable = FunctionProperties<MAccount> &
+ Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> &
+ Use<'Actor', MActorFormattable>
+
+export type MAccountAP = Pick<MAccount, 'name' | 'description'> &
+ Use<'Actor', MActorAP>
--- /dev/null
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+import {
+ MActor,
+ MActorAccount,
+ MActorAccountChannel,
+ MActorChannelAccountActor,
+ MActorDefault,
+ MActorFormattable,
+ MActorHost,
+ MActorUsername
+} from './actor'
+import { PickWith } from '../../utils'
+import { ActorModel } from '@server/models/activitypub/actor'
+import { MChannelDefault } from '@server/typings/models'
+
+type Use<K extends keyof ActorFollowModel, M> = PickWith<ActorFollowModel, K, M>
+
+// ############################################################################
+
+export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'>
+
+// ############################################################################
+
+export type MActorFollowFollowingHost = MActorFollow &
+ Use<'ActorFollowing', MActorUsername & MActorHost>
+
+// ############################################################################
+
+// With actors or actors default
+
+export type MActorFollowActors = MActorFollow &
+ Use<'ActorFollower', MActor> &
+ Use<'ActorFollowing', MActor>
+
+export type MActorFollowActorsDefault = MActorFollow &
+ Use<'ActorFollower', MActorDefault> &
+ Use<'ActorFollowing', MActorDefault>
+
+export type MActorFollowFull = MActorFollow &
+ Use<'ActorFollower', MActorAccountChannel> &
+ Use<'ActorFollowing', MActorAccountChannel>
+
+// ############################################################################
+
+// For subscriptions
+
+type SubscriptionFollowing = MActorDefault &
+ PickWith<ActorModel, 'VideoChannel', MChannelDefault>
+
+export type MActorFollowActorsDefaultSubscription = MActorFollow &
+ Use<'ActorFollower', MActorDefault> &
+ Use<'ActorFollowing', SubscriptionFollowing>
+
+export type MActorFollowFollowingFullFollowerAccount = MActorFollow &
+ Use<'ActorFollower', MActorAccount> &
+ Use<'ActorFollowing', MActorAccountChannel>
+
+export type MActorFollowSubscriptions = MActorFollow &
+ Use<'ActorFollowing', MActorChannelAccountActor>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> &
+ Use<'ActorFollower', MActorFormattable> &
+ Use<'ActorFollowing', MActorFormattable>
--- /dev/null
+import { ActorModel } from '../../../models/activitypub/actor'
+import { FunctionProperties, PickWith, PickWithOpt } from '../../utils'
+import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account'
+import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server'
+import { MAvatar, MAvatarFormattable } from './avatar'
+import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video'
+
+type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M>
+
+// ############################################################################
+
+export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'>
+
+// ############################################################################
+
+export type MActorUrl = Pick<MActor, 'url'>
+export type MActorId = Pick<MActor, 'id'>
+export type MActorUsername = Pick<MActor, 'preferredUsername'>
+
+export type MActorFollowersUrl = Pick<MActor, 'followersUrl'>
+export type MActorAudience = MActorUrl & MActorFollowersUrl
+export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
+export type MActorSignature = MActorAccountChannelId
+
+export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
+
+// ############################################################################
+
+// Some association attributes
+
+export type MActorHost = Use<'Server', MServerHost>
+export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed>
+
+export type MActorDefaultLight = MActorLight &
+ Use<'Server', MServerHost> &
+ Use<'Avatar', MAvatar>
+
+export type MActorAccountId = MActor &
+ Use<'Account', MAccountId>
+export type MActorAccountIdActor = MActor &
+ Use<'Account', MAccountIdActor>
+
+export type MActorChannelId = MActor &
+ Use<'VideoChannel', MChannelId>
+export type MActorChannelIdActor = MActor &
+ Use<'VideoChannel', MChannelIdActor>
+
+export type MActorAccountChannelId = MActorAccountId & MActorChannelId
+export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelIdActor
+
+// ############################################################################
+
+// Include raw account/channel/server
+
+export type MActorAccount = MActor &
+ Use<'Account', MAccount>
+
+export type MActorChannel = MActor &
+ Use<'VideoChannel', MChannel>
+
+export type MActorAccountChannel = MActorAccount & MActorChannel
+
+export type MActorServer = MActor &
+ Use<'Server', MServer>
+
+// ############################################################################
+
+// Complex actor associations
+
+export type MActorDefault = MActor &
+ Use<'Server', MServer> &
+ Use<'Avatar', MAvatar>
+
+// Actor with channel that is associated to an account and its actor
+// Actor -> VideoChannel -> Account -> Actor
+export type MActorChannelAccountActor = MActor &
+ Use<'VideoChannel', MChannelAccountActor>
+
+export type MActorFull = MActor &
+ Use<'Server', MServer> &
+ Use<'Avatar', MAvatar> &
+ Use<'Account', MAccount> &
+ Use<'VideoChannel', MChannelAccountActor>
+
+// Same than ActorFull, but the account and the channel have their actor
+export type MActorFullActor = MActor &
+ Use<'Server', MServer> &
+ Use<'Avatar', MAvatar> &
+ Use<'Account', MAccountDefault> &
+ Use<'VideoChannel', MChannelAccountDefault>
+
+// ############################################################################
+
+// API
+
+export type MActorSummary = FunctionProperties<MActor> &
+ Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
+ Use<'Server', MServerHost> &
+ Use<'Avatar', MAvatar>
+
+export type MActorSummaryBlocks = MActorSummary &
+ Use<'Server', MServerHostBlocks>
+
+export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' |
+ 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MActorSummaryFormattable = FunctionProperties<MActor> &
+ Pick<MActor, 'url' | 'preferredUsername'> &
+ Use<'Server', MServerHost> &
+ Use<'Avatar', MAvatarFormattable>
+
+export type MActorFormattable = MActorSummaryFormattable &
+ Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> &
+ Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>>
+
+export type MActorAP = MActor &
+ Use<'Avatar', MAvatar>
--- /dev/null
+import { AvatarModel } from '../../../models/avatar/avatar'
+import { FunctionProperties } from '@server/typings/utils'
+
+export type MAvatar = AvatarModel
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MAvatarFormattable = FunctionProperties<MAvatar> &
+ Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'>
--- /dev/null
+export * from './account'
+export * from './account-blocklist'
+export * from './actor'
+export * from './actor-follow'
+export * from './avatar'
+++ /dev/null
-import { ActorFollowModel } from '../../models/activitypub/actor-follow'
-import { ActorModelOnly } from './actor'
-
-export type ActorFollowModelOnly = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'>
-export type ActorFollowModelLight = ActorFollowModelOnly & {
- ActorFollower: ActorModelOnly
- ActorFollowing: ActorModelOnly
-}
+++ /dev/null
-import { ActorModel } from '../../models/activitypub/actor'
-import { VideoChannelModel } from '../../models/video/video-channel'
-import { AccountModel } from '../../models/account/account'
-import { FunctionProperties } from '../utils'
-
-export type VideoChannelModelId = FunctionProperties<VideoChannelModel>
-export type AccountModelId = FunctionProperties<AccountModel> | Pick<AccountModel, 'id'>
-
-export type VideoChannelModelIdActor = VideoChannelModelId & Pick<VideoChannelModel, 'Actor'>
-export type AccountModelIdActor = AccountModelId & Pick<AccountModel, 'Actor'>
-
-export type ActorModelUrl = Pick<ActorModel, 'url'>
-export type ActorModelOnly = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'>
-export type ActorModelId = Pick<ActorModelOnly, 'id'>
-
-export type SignatureActorModel = ActorModelOnly & {
- VideoChannel: VideoChannelModelIdActor
-
- Account: AccountModelIdActor
-}
-
-export type ActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'>
-export * from './actor'
+export * from './account'
+export * from './oauth'
+export * from './server'
+export * from './user'
+export * from './video'
--- /dev/null
+export * from './oauth-client'
+export * from './oauth-token'
--- /dev/null
+import { OAuthClientModel } from '@server/models/oauth/oauth-client'
+
+export type MOAuthClient = Omit<OAuthClientModel, 'OAuthTokens'>
--- /dev/null
+import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
+import { PickWith } from '@server/typings/utils'
+import { MUserAccountUrl } from '@server/typings/models'
+
+type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M>
+
+// ############################################################################
+
+export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'>
+
+export type MOAuthTokenUser = MOAuthToken &
+ Use<'User', MUserAccountUrl> &
+ { user?: MUserAccountUrl }
--- /dev/null
+export * from './plugin'
+export * from './server'
+export * from './server-blocklist'
--- /dev/null
+import { PluginModel } from '@server/models/server/plugin'
+
+export type MPlugin = PluginModel
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled'
+ | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'>
--- /dev/null
+import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
+import { PickWith } from '@server/typings/utils'
+import { MAccountDefault, MAccountFormattable, MServer, MServerFormattable } from '@server/typings/models'
+
+type Use<K extends keyof ServerBlocklistModel, M> = PickWith<ServerBlocklistModel, K, M>
+
+// ############################################################################
+
+export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'BlockedServer'>
+
+// ############################################################################
+
+export type MServerBlocklistAccountServer = MServerBlocklist &
+ Use<'ByAccount', MAccountDefault> &
+ Use<'BlockedServer', MServer>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> &
+ Use<'ByAccount', MAccountFormattable> &
+ Use<'BlockedServer', MServerFormattable>
--- /dev/null
+import { ServerModel } from '../../../models/server/server'
+import { FunctionProperties, PickWith } from '../../utils'
+import { MAccountBlocklistId } from '../account'
+
+type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M>
+
+// ############################################################################
+
+export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'>
+
+// ############################################################################
+
+export type MServerHost = Pick<MServer, 'host'>
+export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'>
+
+export type MServerHostBlocks = MServerHost &
+ Use<'BlockedByAccounts', MAccountBlocklistId[]>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MServerFormattable = FunctionProperties<MServer> &
+ Pick<MServer, 'host'>
--- /dev/null
+export * from './user'
+export * from './user-notification'
+export * from './user-notification-setting'
+export * from './user-video-history'
--- /dev/null
+import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting'
+
+export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MNotificationSettingFormattable = MNotificationSetting
--- /dev/null
+import { UserNotificationModel } from '../../../models/account/user-notification'
+import { PickWith } from '../../utils'
+import { VideoModel } from '../../../models/video/video'
+import { ActorModel } from '../../../models/activitypub/actor'
+import { ServerModel } from '../../../models/server/server'
+import { AvatarModel } from '../../../models/avatar/avatar'
+import { VideoChannelModel } from '../../../models/video/video-channel'
+import { AccountModel } from '../../../models/account/account'
+import { VideoCommentModel } from '../../../models/video/video-comment'
+import { VideoAbuseModel } from '../../../models/video/video-abuse'
+import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
+import { VideoImportModel } from '../../../models/video/video-import'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+
+type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M>
+
+// ############################################################################
+
+export namespace UserNotificationIncludes {
+ export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'>
+ export type VideoIncludeChannel = VideoInclude &
+ PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
+
+ export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> &
+ PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
+ PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
+
+ export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
+ export type VideoChannelIncludeActor = VideoChannelInclude &
+ PickWith<VideoChannelModel, 'Actor', ActorInclude>
+
+ export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'>
+ export type AccountIncludeActor = AccountInclude &
+ PickWith<AccountModel, 'Actor', ActorInclude>
+
+ export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
+ PickWith<VideoCommentModel, 'Account', AccountIncludeActor> &
+ PickWith<VideoCommentModel, 'Video', VideoInclude>
+
+ export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> &
+ PickWith<VideoAbuseModel, 'Video', VideoInclude>
+
+ export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> &
+ PickWith<VideoAbuseModel, 'Video', VideoInclude>
+
+ export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> &
+ PickWith<VideoImportModel, 'Video', VideoInclude>
+
+ export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> &
+ PickWith<ActorModel, 'Account', AccountInclude> &
+ PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
+ PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
+
+ export type ActorFollowing = Pick<ActorModel, 'preferredUsername'> &
+ PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> &
+ PickWith<ActorModel, 'Account', AccountInclude>
+
+ export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> &
+ PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
+ PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
+}
+
+// ############################################################################
+
+export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' |
+ 'VideoImport' | 'Account' | 'ActorFollow'>
+
+// ############################################################################
+
+export type UserNotificationModelForApi = MUserNotification &
+ Use<'Video', UserNotificationIncludes.VideoIncludeChannel> &
+ Use<'Comment', UserNotificationIncludes.VideoCommentInclude> &
+ Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> &
+ Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> &
+ Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> &
+ Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> &
+ Use<'Account', UserNotificationIncludes.AccountIncludeActor>
--- /dev/null
+import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
+
+export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'>
+
+export type MUserVideoHistoryTime = Pick<MUserVideoHistory, 'currentTime'>
--- /dev/null
+import { UserModel } from '../../../models/account/user'
+import { PickWith, PickWithOpt } from '../../utils'
+import {
+ MAccount,
+ MAccountDefault,
+ MAccountDefaultChannelDefault,
+ MAccountFormattable,
+ MAccountId,
+ MAccountIdActorId,
+ MAccountUrl
+} from '../account'
+import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting'
+import { AccountModel } from '@server/models/account/account'
+import { MChannelFormattable } from '@server/typings/models'
+
+type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M>
+
+// ############################################################################
+
+export type MUser = Omit<UserModel, 'Account' | 'NotificationSetting' | 'VideoImports' | 'OAuthTokens'>
+
+// ############################################################################
+
+export type MUserQuotaUsed = MUser & { videoQuotaUsed?: number, videoQuotaUsedDaily?: number }
+export type MUserId = Pick<UserModel, 'id'>
+
+// ############################################################################
+
+// With account
+
+export type MUserAccountId = MUser &
+ Use<'Account', MAccountId>
+
+export type MUserAccountUrl = MUser &
+ Use<'Account', MAccountUrl & MAccountIdActorId>
+
+export type MUserAccount = MUser &
+ Use<'Account', MAccount>
+
+export type MUserAccountDefault = MUser &
+ Use<'Account', MAccountDefault>
+
+// With channel
+
+export type MUserNotifSettingChannelDefault = MUser &
+ Use<'NotificationSetting', MNotificationSetting> &
+ Use<'Account', MAccountDefaultChannelDefault>
+
+// With notification settings
+
+export type MUserWithNotificationSetting = MUser &
+ Use<'NotificationSetting', MNotificationSetting>
+
+export type MUserNotifSettingAccount = MUser &
+ Use<'NotificationSetting', MNotificationSetting> &
+ Use<'Account', MAccount>
+
+// Default scope
+
+export type MUserDefault = MUser &
+ Use<'NotificationSetting', MNotificationSetting> &
+ Use<'Account', MAccountDefault>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MUserFormattable = MUserQuotaUsed &
+ Use<'Account', MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>> &
+ PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable>
+++ /dev/null
-import { VideoShareModel } from '../../models/video/video-share'
-
-export type VideoShareModelOnly = Omit<VideoShareModel, 'Actor' | 'Video'>
--- /dev/null
+export * from './schedule-video-update'
+export * from './tag'
+export * from './thumbnail'
+export * from './video'
+export * from './video-abuse'
+export * from './video-blacklist'
+export * from './video-caption'
+export * from './video-change-ownership'
+export * from './video-channels'
+export * from './video-comment'
+export * from './video-file'
+export * from './video-import'
+export * from './video-playlist'
+export * from './video-playlist-element'
+export * from './video-rate'
+export * from './video-redundancy'
+export * from './video-share'
+export * from './video-streaming-playlist'
--- /dev/null
+import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
+
+export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MScheduleVideoUpdateFormattable = Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>
--- /dev/null
+import { TagModel } from '../../../models/video/tag'
+
+export type MTag = Omit<TagModel, 'Videos'>
--- /dev/null
+import { ThumbnailModel } from '../../../models/video/thumbnail'
+
+export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'>
--- /dev/null
+import { VideoAbuseModel } from '../../../models/video/video-abuse'
+import { PickWith } from '../../utils'
+import { MVideo } from './video'
+import { MAccountDefault, MAccountFormattable } from '../account'
+
+type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M>
+
+// ############################################################################
+
+export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'>
+
+// ############################################################################
+
+export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'>
+
+export type MVideoAbuseVideo = MVideoAbuse &
+ Pick<VideoAbuseModel, 'toActivityPubObject'> &
+ Use<'Video', MVideo>
+
+export type MVideoAbuseAccountVideo = MVideoAbuse &
+ Pick<VideoAbuseModel, 'toActivityPubObject'> &
+ Use<'Video', MVideo> &
+ Use<'Account', MAccountDefault>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoAbuseFormattable = MVideoAbuse &
+ Use<'Account', MAccountFormattable> &
+ Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>
--- /dev/null
+import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
+import { PickWith } from '@server/typings/utils'
+import { MVideo, MVideoFormattable } from '@server/typings/models'
+
+type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M>
+
+// ############################################################################
+
+export type MVideoBlacklist = Omit<VideoBlacklistModel, 'Video'>
+
+export type MVideoBlacklistLight = Pick<MVideoBlacklist, 'id' | 'reason' | 'unfederated'>
+export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'>
+
+// ############################################################################
+
+export type MVideoBlacklistVideo = MVideoBlacklist &
+ Use<'Video', MVideo>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoBlacklistFormattable = MVideoBlacklist &
+ Use<'Video', MVideoFormattable>
--- /dev/null
+import { VideoCaptionModel } from '../../../models/video/video-caption'
+import { FunctionProperties, PickWith } from '@server/typings/utils'
+import { MVideo, MVideoUUID } from '@server/typings/models'
+
+type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M>
+
+// ############################################################################
+
+export type MVideoCaption = Omit<VideoCaptionModel, 'Video'>
+
+// ############################################################################
+
+export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'>
+
+export type MVideoCaptionVideo = MVideoCaption &
+ Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> &
+ Pick<MVideoCaption, 'language'> &
+ Use<'Video', MVideoUUID>
--- /dev/null
+import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership'
+import { PickWith } from '@server/typings/utils'
+import { MAccountDefault, MAccountFormattable, MVideo, MVideoWithFileThumbnail } from '@server/typings/models'
+
+type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M>
+
+// ############################################################################
+
+export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'>
+
+export type MVideoChangeOwnershipFull = MVideoChangeOwnership &
+ Use<'Initiator', MAccountDefault> &
+ Use<'NextOwner', MAccountDefault> &
+ Use<'Video', MVideoWithFileThumbnail>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> &
+ Use<'Initiator', MAccountFormattable> &
+ Use<'NextOwner', MAccountFormattable> &
+ Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>>
--- /dev/null
+import { FunctionProperties, PickWith, PickWithOpt } from '../../utils'
+import { VideoChannelModel } from '../../../models/video/video-channel'
+import {
+ MAccountActor,
+ MAccountAPI,
+ MAccountDefault,
+ MAccountFormattable,
+ MAccountLight,
+ MAccountSummaryBlocks,
+ MAccountSummaryFormattable,
+ MAccountUrl,
+ MAccountUserId,
+ MActor,
+ MActorAccountChannelId,
+ MActorAP,
+ MActorAPI,
+ MActorDefault,
+ MActorDefaultLight,
+ MActorFormattable,
+ MActorLight,
+ MActorSummary,
+ MActorSummaryFormattable, MActorUrl
+} from '../account'
+import { MVideo } from './video'
+
+type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M>
+
+// ############################################################################
+
+export type MChannel = Omit<VideoChannelModel, 'Actor' | 'Account' | 'Videos' | 'VideoPlaylists'>
+
+// ############################################################################
+
+export type MChannelId = Pick<MChannel, 'id'>
+
+// ############################################################################
+
+export type MChannelIdActor = MChannelId &
+ Use<'Actor', MActorAccountChannelId>
+
+export type MChannelUserId = Pick<MChannel, 'accountId'> &
+ Use<'Account', MAccountUserId>
+
+export type MChannelActor = MChannel &
+ Use<'Actor', MActor>
+
+export type MChannelUrl = Use<'Actor', MActorUrl>
+
+// Default scope
+export type MChannelDefault = MChannel &
+ Use<'Actor', MActorDefault>
+
+// ############################################################################
+
+// Not all association attributes
+
+export type MChannelLight = MChannel &
+ Use<'Actor', MActorDefaultLight>
+
+export type MChannelActorLight = MChannel &
+ Use<'Actor', MActorLight>
+
+export type MChannelAccountLight = MChannel &
+ Use<'Actor', MActorDefaultLight> &
+ Use<'Account', MAccountLight>
+
+// ############################################################################
+
+// Account associations
+
+export type MChannelAccountActor = MChannel &
+ Use<'Account', MAccountActor>
+
+export type MChannelAccountDefault = MChannel &
+ Use<'Actor', MActorDefault> &
+ Use<'Account', MAccountDefault>
+
+export type MChannelActorAccountActor = MChannel &
+ Use<'Account', MAccountActor> &
+ Use<'Actor', MActor>
+
+// ############################################################################
+
+// Videos associations
+export type MChannelVideos = MChannel &
+ Use<'Videos', MVideo[]>
+
+export type MChannelActorAccountDefaultVideos = MChannel &
+ Use<'Actor', MActorDefault> &
+ Use<'Account', MAccountDefault> &
+ Use<'Videos', MVideo[]>
+
+// ############################################################################
+
+// For API
+
+export type MChannelSummary = FunctionProperties<MChannel> &
+ Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> &
+ Use<'Actor', MActorSummary>
+
+export type MChannelSummaryAccount = MChannelSummary &
+ Use<'Account', MAccountSummaryBlocks>
+
+export type MChannelAPI = MChannel &
+ Use<'Actor', MActorAPI> &
+ Use<'Account', MAccountAPI>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MChannelSummaryFormattable = FunctionProperties<MChannel> &
+ Pick<MChannel, 'id' | 'name'> &
+ Use<'Actor', MActorSummaryFormattable>
+
+export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable &
+ Use<'Account', MAccountSummaryFormattable>
+
+export type MChannelFormattable = FunctionProperties<MChannel> &
+ Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> &
+ Use<'Actor', MActorFormattable> &
+ PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable>
+
+export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> &
+ Use<'Actor', MActorAP> &
+ Use<'Account', MAccountUrl>
--- /dev/null
+import { VideoCommentModel } from '../../../models/video/video-comment'
+import { PickWith, PickWithOpt } from '../../utils'
+import { MAccountDefault, MAccountFormattable, MAccountUrl, MActorUrl } from '../account'
+import { MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video'
+
+type Use<K extends keyof VideoCommentModel, M> = PickWith<VideoCommentModel, K, M>
+
+// ############################################################################
+
+export type MComment = Omit<VideoCommentModel, 'OriginVideoComment' | 'InReplyToVideoComment' | 'Video' | 'Account'>
+export type MCommentTotalReplies = MComment & { totalReplies?: number }
+export type MCommentId = Pick<MComment, 'id'>
+export type MCommentUrl = Pick<MComment, 'url'>
+
+// ############################################################################
+
+export type MCommentOwner = MComment &
+ Use<'Account', MAccountDefault>
+
+export type MCommentVideo = MComment &
+ Use<'Video', MVideoAccountLight>
+
+export type MCommentReply = MComment &
+ Use<'InReplyToVideoComment', MComment>
+
+export type MCommentOwnerVideo = MComment &
+ Use<'Account', MAccountDefault> &
+ Use<'Video', MVideoAccountLight>
+
+export type MCommentOwnerVideoReply = MComment &
+ Use<'Account', MAccountDefault> &
+ Use<'Video', MVideoAccountLight> &
+ Use<'InReplyToVideoComment', MComment>
+
+export type MCommentOwnerReplyVideoLight = MComment &
+ Use<'Account', MAccountDefault> &
+ Use<'InReplyToVideoComment', MComment> &
+ Use<'Video', MVideoIdUrl>
+
+export type MCommentOwnerVideoFeed = MCommentOwner &
+ Use<'Video', MVideoFeed>
+
+// ############################################################################
+
+export type MCommentAPI = MComment & { totalReplies: number }
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MCommentFormattable = MCommentTotalReplies &
+ Use<'Account', MAccountFormattable>
+
+export type MCommentAP = MComment &
+ Use<'Account', MAccountUrl> &
+ PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> &
+ PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl>
--- /dev/null
+import { VideoFileModel } from '../../../models/video/video-file'
+import { PickWith, PickWithOpt } from '../../utils'
+import { MVideo, MVideoUUID } from './video'
+import { MVideoRedundancyFileUrl } from './video-redundancy'
+
+type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
+
+// ############################################################################
+
+export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos'>
+
+export type MVideoFileVideo = MVideoFile &
+ Use<'Video', MVideo>
+
+export type MVideoFileVideoUUID = MVideoFile &
+ Use<'Video', MVideoUUID>
+
+export type MVideoFileRedundanciesOpt = MVideoFile &
+ PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
--- /dev/null
+import { VideoImportModel } from '@server/models/video/video-import'
+import { PickWith, PickWithOpt } from '@server/typings/utils'
+import { MUser, MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from '@server/typings/models'
+
+type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M>
+
+// ############################################################################
+
+export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'>
+
+export type MVideoImportVideo = MVideoImport &
+ Use<'Video', MVideo>
+
+// ############################################################################
+
+type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail
+
+export type MVideoImportDefault = MVideoImport &
+ Use<'User', MUser> &
+ Use<'Video', VideoAssociation>
+
+export type MVideoImportDefaultFiles = MVideoImport &
+ Use<'User', MUser> &
+ Use<'Video', VideoAssociation & MVideoWithFile>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoImportFormattable = MVideoImport &
+ PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag>
--- /dev/null
+import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element'
+import { PickWith } from '@server/typings/utils'
+import { MVideoFormattable, MVideoPlaylistPrivacy, MVideoThumbnail, MVideoUrl } from '@server/typings/models'
+
+type Use<K extends keyof VideoPlaylistElementModel, M> = PickWith<VideoPlaylistElementModel, K, M>
+
+// ############################################################################
+
+export type MVideoPlaylistElement = Omit<VideoPlaylistElementModel, 'VideoPlaylist' | 'Video'>
+
+// ############################################################################
+
+export type MVideoPlaylistElementId = Pick<MVideoPlaylistElement, 'id'>
+
+export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'videoId' | 'startTimestamp' | 'stopTimestamp'>
+
+// ############################################################################
+
+export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement &
+ Use<'Video', MVideoThumbnail>
+
+export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement &
+ Use<'Video', MVideoUrl> &
+ Use<'VideoPlaylist', MVideoPlaylistPrivacy>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoPlaylistElementFormattable = MVideoPlaylistElement &
+ Use<'Video', MVideoFormattable>
+
+export type MVideoPlaylistElementAP = MVideoPlaylistElement &
+ Use<'Video', MVideoUrl>
--- /dev/null
+import { VideoPlaylistModel } from '../../../models/video/video-playlist'
+import { PickWith } from '../../utils'
+import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account'
+import { MThumbnail } from './thumbnail'
+import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels'
+import { MVideoPlaylistElementLight } from '@server/typings/models/video/video-playlist-element'
+
+type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M>
+
+// ############################################################################
+
+export type MVideoPlaylist = Omit<VideoPlaylistModel, 'OwnerAccount' | 'VideoChannel' | 'VideoPlaylistElements' | 'Thumbnail'>
+
+// ############################################################################
+
+export type MVideoPlaylistId = Pick<MVideoPlaylist, 'id'>
+export type MVideoPlaylistPrivacy = Pick<MVideoPlaylist, 'privacy'>
+export type MVideoPlaylistUUID = Pick<MVideoPlaylist, 'uuid'>
+export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: number }
+
+// ############################################################################
+
+// With elements
+
+export type MVideoPlaylistWithElements = MVideoPlaylist &
+ Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
+
+export type MVideoPlaylistIdWithElements = MVideoPlaylistId &
+ Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
+
+// ############################################################################
+
+// With account
+
+export type MVideoPlaylistOwner = MVideoPlaylist &
+ Use<'OwnerAccount', MAccount>
+
+export type MVideoPlaylistOwnerDefault = MVideoPlaylist &
+ Use<'OwnerAccount', MAccountDefault>
+
+// ############################################################################
+
+// With thumbnail
+
+export type MVideoPlaylistThumbnail = MVideoPlaylist &
+ Use<'Thumbnail', MThumbnail>
+
+export type MVideoPlaylistAccountThumbnail = MVideoPlaylist &
+ Use<'OwnerAccount', MAccountDefault> &
+ Use<'Thumbnail', MThumbnail>
+
+// ############################################################################
+
+// With channel
+
+export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist &
+ Use<'OwnerAccount', MAccountDefault> &
+ Use<'VideoChannel', MChannelDefault>
+
+// ############################################################################
+
+// With all associations
+
+export type MVideoPlaylistFull = MVideoPlaylist &
+ Use<'OwnerAccount', MAccountDefault> &
+ Use<'VideoChannel', MChannelDefault> &
+ Use<'Thumbnail', MThumbnail>
+
+// ############################################################################
+
+// For API
+
+export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist &
+ Use<'OwnerAccount', MAccountSummary> &
+ Use<'VideoChannel', MChannelSummary>
+
+export type MVideoPlaylistFullSummary = MVideoPlaylist &
+ Use<'Thumbnail', MThumbnail> &
+ Use<'OwnerAccount', MAccountSummary> &
+ Use<'VideoChannel', MChannelSummary>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength &
+ Use<'OwnerAccount', MAccountSummaryFormattable> &
+ Use<'VideoChannel', MChannelSummaryFormattable>
+
+export type MVideoPlaylistAP = MVideoPlaylist &
+ Use<'Thumbnail', MThumbnail> &
+ Use<'VideoChannel', MChannelUrl>
--- /dev/null
+import { AccountVideoRateModel } from '@server/models/account/account-video-rate'
+import { PickWith } from '@server/typings/utils'
+import { MAccountAudience, MAccountUrl, MVideo, MVideoFormattable } from '..'
+
+type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateModel, K, M>
+
+// ############################################################################
+
+export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'>
+
+export type MAccountVideoRateAccountUrl = MAccountVideoRate &
+ Use<'Account', MAccountUrl>
+
+export type MAccountVideoRateAccountVideo = MAccountVideoRate &
+ Use<'Account', MAccountAudience> &
+ Use<'Video', MVideo>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> &
+ Use<'Video', MVideoFormattable>
--- /dev/null
+import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
+import { PickWith, PickWithOpt } from '@server/typings/utils'
+import { MStreamingPlaylistVideo, MVideoFile, MVideoFileVideo, MVideoUrl } from '@server/typings/models'
+import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model'
+import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
+import { VideoFile } from '../../../../shared/models/videos'
+import { VideoFileModel } from '@server/models/video/video-file'
+
+type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M>
+
+// ############################################################################
+
+export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoFile' | 'VideoStreamingPlaylist' | 'Actor'>
+
+export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'>
+
+// ############################################################################
+
+export type MVideoRedundancyFile = MVideoRedundancy &
+ Use<'VideoFile', MVideoFile>
+
+export type MVideoRedundancyFileVideo = MVideoRedundancy &
+ Use<'VideoFile', MVideoFileVideo>
+
+export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy &
+ Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
+
+export type MVideoRedundancyVideo = MVideoRedundancy &
+ Use<'VideoFile', MVideoFileVideo> &
+ Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoRedundancyAP = MVideoRedundancy &
+ PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> &
+ PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>>
--- /dev/null
+import { VideoShareModel } from '../../../models/video/video-share'
+import { PickWith } from '../../utils'
+import { MActorDefault } from '../account'
+import { MVideo } from './video'
+
+type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M>
+
+// ############################################################################
+
+export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'>
+
+export type MVideoShareActor = MVideoShare &
+ Use<'Actor', MActorDefault>
+
+export type MVideoShareFull = MVideoShare &
+ Use<'Actor', MActorDefault> &
+ Use<'Video', MVideo>
--- /dev/null
+import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist'
+import { PickWith, PickWithOpt } from '../../utils'
+import { MVideoRedundancyFileUrl } from './video-redundancy'
+import { MVideo, MVideoUrl } from '@server/typings/models'
+
+type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M>
+
+// ############################################################################
+
+export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos'>
+
+export type MStreamingPlaylistVideo = MStreamingPlaylist &
+ Use<'Video', MVideo>
+
+export type MStreamingPlaylistRedundancies = MStreamingPlaylist &
+ Use<'RedundancyVideos', MVideoRedundancyFileUrl[]>
+
+export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist &
+ PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
--- /dev/null
+import { VideoModel } from '../../../models/video/video'
+import { PickWith, PickWithOpt } from '../../utils'
+import {
+ MChannelAccountDefault,
+ MChannelAccountLight,
+ MChannelAccountSummaryFormattable,
+ MChannelActor,
+ MChannelFormattable,
+ MChannelUserId
+} from './video-channels'
+import { MTag } from './tag'
+import { MVideoCaptionLanguage } from './video-caption'
+import { MStreamingPlaylist, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist'
+import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file'
+import { MThumbnail } from './thumbnail'
+import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist'
+import { MScheduleVideoUpdate } from './schedule-video-update'
+import { MUserVideoHistoryTime } from '../user/user-video-history'
+
+type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M>
+
+// ############################################################################
+
+export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' |
+ 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' |
+ 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'>
+
+// ############################################################################
+
+export type MVideoId = Pick<MVideo, 'id'>
+export type MVideoUrl = Pick<MVideo, 'url'>
+export type MVideoUUID = Pick<MVideo, 'uuid'>
+
+export type MVideoIdUrl = MVideoId & MVideoUrl
+export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
+
+// ############################################################################
+
+// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists
+
+// "With" to not confuse with the VideoFile model
+export type MVideoWithFile = MVideo &
+ Use<'VideoFiles', MVideoFile[]>
+
+export type MVideoThumbnail = MVideo &
+ Use<'Thumbnails', MThumbnail[]>
+
+export type MVideoIdThumbnail = MVideoId &
+ Use<'Thumbnails', MThumbnail[]>
+
+export type MVideoWithFileThumbnail = MVideo &
+ Use<'VideoFiles', MVideoFile[]> &
+ Use<'Thumbnails', MThumbnail[]>
+
+export type MVideoThumbnailBlacklist = MVideo &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoBlacklist', MVideoBlacklistLight>
+
+export type MVideoTag = MVideo &
+ Use<'Tags', MTag[]>
+
+export type MVideoWithSchedule = MVideo &
+ PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate>
+
+export type MVideoWithCaptions = MVideo &
+ Use<'VideoCaptions', MVideoCaptionLanguage[]>
+
+export type MVideoWithStreamingPlaylist = MVideo &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
+
+// ############################################################################
+
+// Associations with not all their attributes
+
+export type MVideoUserHistory = MVideo &
+ Use<'UserVideoHistories', MUserVideoHistoryTime[]>
+
+export type MVideoWithBlacklistLight = MVideo &
+ Use<'VideoBlacklist', MVideoBlacklistLight>
+
+export type MVideoAccountLight = MVideo &
+ Use<'VideoChannel', MChannelAccountLight>
+
+export type MVideoWithRights = MVideo &
+ Use<'VideoBlacklist', MVideoBlacklistLight> &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoChannel', MChannelUserId>
+
+// ############################################################################
+
+// All files with some additional associations
+
+export type MVideoWithAllFiles = MVideo &
+ Use<'VideoFiles', MVideoFile[]> &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
+
+export type MVideoAccountLightBlacklistAllFiles = MVideo &
+ Use<'VideoFiles', MVideoFile[]> &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> &
+ Use<'VideoChannel', MChannelAccountLight> &
+ Use<'VideoBlacklist', MVideoBlacklistLight>
+
+// ############################################################################
+
+// With account
+
+export type MVideoAccountDefault = MVideo &
+ Use<'VideoChannel', MChannelAccountDefault>
+
+export type MVideoThumbnailAccountDefault = MVideo &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoChannel', MChannelAccountDefault>
+
+export type MVideoWithChannelActor = MVideo &
+ Use<'VideoChannel', MChannelActor>
+
+export type MVideoFullLight = MVideo &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'VideoBlacklist', MVideoBlacklistLight> &
+ Use<'Tags', MTag[]> &
+ Use<'VideoChannel', MChannelAccountLight> &
+ Use<'UserVideoHistories', MUserVideoHistoryTime[]> &
+ Use<'VideoFiles', MVideoFile[]> &
+ Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylist[]>
+
+// ############################################################################
+
+// API
+
+export type MVideoAP = MVideo &
+ Use<'Tags', MTag[]> &
+ Use<'VideoChannel', MChannelAccountLight> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> &
+ Use<'VideoCaptions', MVideoCaptionLanguage[]> &
+ Use<'VideoBlacklist', MVideoBlacklistUnfederated> &
+ Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
+
+export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'>
+
+export type MVideoDetails = MVideo &
+ Use<'VideoBlacklist', MVideoBlacklistLight> &
+ Use<'Tags', MTag[]> &
+ Use<'VideoChannel', MChannelAccountLight> &
+ Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
+ Use<'Thumbnails', MThumbnail[]> &
+ Use<'UserVideoHistories', MUserVideoHistoryTime[]> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> &
+ Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
+
+export type MVideoForUser = MVideo &
+ Use<'VideoChannel', MChannelAccountDefault> &
+ Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
+ Use<'VideoBlacklist', MVideoBlacklistLight> &
+ Use<'Thumbnails', MThumbnail[]>
+
+// ############################################################################
+
+// Format for API or AP object
+
+export type MVideoFormattable = MVideo &
+ PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
+ Use<'VideoChannel', MChannelAccountSummaryFormattable> &
+ PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
+ PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>>
+
+export type MVideoFormattableDetails = MVideoFormattable &
+ Use<'VideoChannel', MChannelFormattable> &
+ Use<'Tags', MTag[]> &
+ Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> &
+ Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
-export type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
+export type FunctionPropertyNames<T> = {
+ [K in keyof T]: T[K] extends Function ? K : never
+}[keyof T]
export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>
+
+export type PickWith<T, KT extends keyof T, V> = {
+ [P in KT]: T[P] extends V ? V : never
+}
+
+export type PickWithOpt<T, KT extends keyof T, V> = {
+ [P in KT]?: T[P] extends V ? V : never
+}
export interface ActivityFlag extends BaseActivity {
type: 'Flag',
content: string,
- object: APObject
+ object: APObject | APObject[]
}
export interface VideoAbuseObject {
type: 'Flag',
content: string
- object: string
+ object: string | string[]
}
summary: Get the account by name
parameters:
- $ref: '#/components/parameters/name'
- - $ref: '#/components/parameters/start'
- - $ref: '#/components/parameters/count'
- - $ref: '#/components/parameters/sort'
responses:
'200':
description: successful operation
tags:
- Accounts
summary: Get all accounts
+ parameters:
+ - $ref: '#/components/parameters/start'
+ - $ref: '#/components/parameters/count'
+ - $ref: '#/components/parameters/sort'
responses:
'200':
description: successful operation
responses:
'200':
description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ServerConfigAbout'
/config/custom:
get:
summary: Get the runtime configuration of the server
responses:
'200':
description: successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ServerConfigCustom'
put:
summary: Set the runtime configuration of the server
tags:
type: string
format: binary
encoding:
- profileImage:
- # only accept png/jpeg
+ avatarfile:
contentType: image/png, image/jpeg
/videos:
get:
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
category:
description: Video category
type: string
format: date-time
scheduleUpdate:
$ref: '#/components/schemas/VideoScheduledUpdate'
+ encoding:
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
get:
summary: Get a video by its id
tags:
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
privacy:
$ref: '#/components/schemas/VideoPrivacySet'
category:
- videofile
- channelId
- name
+ encoding:
+ videofile:
+ contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
x-code-samples:
- lang: Shell
source: |
thumbnailfile:
description: Video thumbnail file
type: string
+ format: binary
previewfile:
description: Video preview file
type: string
+ format: binary
privacy:
$ref: '#/components/schemas/VideoPrivacySet'
category:
required:
- channelId
- name
+ encoding:
+ torrentfile:
+ contentType: application/x-bittorrent
+ thumbnailfile:
+ contentType: image/jpeg
+ previewfile:
+ contentType: image/jpeg
/videos/abuse:
get:
summary: Get list of reported video abuses
description: The file to upload.
type: string
format: binary
+ encoding:
+ captionfile:
+ contentType: text/vtt, application/x-subrip
responses:
'204':
$ref: '#/paths/~1users~1me/put/responses/204'
description: 'Video file size in bytes'
torrentUrl:
type: string
- torrentDownaloadUrl:
+ torrentDownloadUrl:
type: string
fileUrl:
type: string
properties:
id:
type: number
- uuid:
- type: string
url:
type: string
name:
allOf:
- $ref: '#/components/schemas/Actor'
- properties:
+ userId:
+ type: string
displayName:
type: string
+ description:
+ type: string
User:
properties:
id:
type: number
ServerConfig:
properties:
+ instance:
+ type: object
+ properties:
+ name:
+ type: string
+ shortDescription:
+ type: string
+ defaultClientRoute:
+ type: string
+ isNSFW:
+ type: boolean
+ defaultNSFWPolicy:
+ type: string
+ customizations:
+ type: object
+ properties:
+ javascript:
+ type: string
+ css:
+ type: string
+ plugin:
+ type: object
+ properties:
+ registered:
+ type: array
+ items:
+ type: string
+ theme:
+ type: object
+ properties:
+ registered:
+ type: array
+ items:
+ type: string
+ email:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ contactForm:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ serverVersion:
+ type: string
+ serverCommit:
+ type: string
signup:
type: object
properties:
allowed:
type: boolean
+ allowedForCurrentIP:
+ type: boolean
+ requiresEmailVerification:
+ type: boolean
transcoding:
type: object
properties:
+ hls:
+ type: object
+ properties:
+ enabled:
+ type: boolean
enabledResolutions:
type: array
items:
type: number
+ import:
+ type: object
+ properties:
+ videos:
+ type: object
+ properties:
+ http:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ torrent:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ autoBlacklist:
+ type: object
+ properties:
+ videos:
+ type: object
+ properties:
+ ofUsers:
+ type: object
+ properties:
+ enabled:
+ type: boolean
avatar:
type: object
properties:
video:
type: object
properties:
+ image:
+ type: object
+ properties:
+ extensions:
+ type: array
+ items:
+ type: string
+ size:
+ type: object
+ properties:
+ max:
+ type: number
file:
type: object
properties:
type: array
items:
type: string
+ videoCaption:
+ type: object
+ properties:
+ file:
+ type: object
+ properties:
+ size:
+ type: object
+ properties:
+ max:
+ type: number
+ extensions:
+ type: array
+ items:
+ type: string
+ user:
+ type: object
+ properties:
+ videoQuota:
+ type: number
+ videoQuotaDaily:
+ type: number
+ trending:
+ type: object
+ properties:
+ videos:
+ type: object
+ properties:
+ intervalDays:
+ type: number
+ tracker:
+ ype: object
+ properties:
+ enabled:
+ type: boolean
+ ServerConfigAbout:
+ properties:
+ instance:
+ type: object
+ properties:
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ terms:
+ type: string
+ ServerConfigCustom:
+ properties:
+ instance:
+ type: object
+ properties:
+ name:
+ type: string
+ shortDescription:
+ type: string
+ description:
+ type: string
+ terms:
+ type: string
+ defaultClientRoute:
+ type: string
+ isNSFW:
+ type: boolean
+ defaultNSFWPolicy:
+ type: string
+ customizations:
+ type: object
+ properties:
+ javascript:
+ type: string
+ css:
+ type: string
+ theme:
+ type: object
+ properties:
+ default:
+ type: string
+ services:
+ type: object
+ properties:
+ twitter:
+ type: object
+ properties:
+ username:
+ type: string
+ whitelisted:
+ type: boolean
+ cache:
+ type: object
+ properties:
+ previews:
+ type: object
+ properties:
+ size:
+ type: number
+ captions:
+ type: object
+ properties:
+ size:
+ type: number
+ signup:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ limit:
+ type: number
+ requiresEmailVerification:
+ type: boolean
+ admin:
+ type: object
+ properties:
+ email:
+ type: string
+ contactForm:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ user:
+ type: object
+ properties:
+ videoQuota:
+ type: number
+ videoQuotaDaily:
+ type: number
+ transcoding:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ allowAdditionalExtensions:
+ type: boolean
+ allowAudioFiles:
+ type: boolean
+ threads:
+ type: number
+ resolutions:
+ type: object
+ properties:
+ 240p:
+ type: boolean
+ 360p:
+ type: boolean
+ 480p:
+ type: boolean
+ 720p:
+ type: boolean
+ 1080p:
+ type: boolean
+ 2160p:
+ type: boolean
+ hls:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ import:
+ type: object
+ properties:
+ videos:
+ type: object
+ properties:
+ http:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ torrent:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ autoBlacklist:
+ type: object
+ properties:
+ videos:
+ type: object
+ properties:
+ ofUsers:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ followers:
+ type: object
+ properties:
+ instance:
+ type: object
+ properties:
+ enabled:
+ type: boolean
+ manualApproval:
+ type: boolean
Follow:
properties:
id:
- [peertube-import-videos.js](#peertube-import-videosjs)
- [peertube-upload.js](#peertube-uploadjs)
- [peertube-watch.js](#peertube-watchjs)
+ - [peertube-plugins.js](#peertube-pluginsjs)
- [Server tools](#server-tools)
- [parse-log](#parse-log)
- [create-transcoding-job.js](#create-transcoding-jobjs)
- [optimize-old-videos.js](#optimize-old-videosjs)
- [update-host.js](#update-hostjs)
- [reset-password.js](#reset-passwordjs)
+ - [plugin install/uninstall](#plugin-installuninstall)
- [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop)
- [.help](#help)
- [Lodash example](#lodash-example)
- chromecast
+#### peertube-plugins.js
+
+Install/update/uninstall or list local or NPM PeerTube plugins:
+
+```
+$ cd ${CLONE}
+$ node dist/server/tools/peertube-plugins.js --help
+$ node dist/server/tools/peertube-plugins.js list --help
+$ node dist/server/tools/peertube-plugins.js install --help
+$ node dist/server/tools/peertube-plugins.js update --help
+$ node dist/server/tools/peertube-plugins.js uninstall --help
+
+$ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path
+$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example
+```
+
## Server tools
These scripts should be run on the server, in `peertube-latest` directory.
The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server).
-To install a plugin or a theme from the disk:
+To install/update a plugin or a theme from the disk:
```
-$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --plugin-path /local/plugin/path
```
From NPM:
```
-$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --npm-name peertube-plugin-myplugin
```
To uninstall a plugin or a theme:
```
-$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
+$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin
```
### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html))
"es2016",
"es2017"
],
- "typeRoots": [ "node_modules/@types", "server/typings" ]
+ "typeRoots": [ "node_modules/@types", "server/typings" ],
+ "baseUrl": "./",
+ "paths": {
+ "@server/typings/*": [ "server/typings/*" ],
+ "@server/models/*": [ "server/models/*" ]
+ }
},
"exclude": [
"server/tools/",
- "client/node_modules",
"node_modules",
"dist",
"storage",
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
-bitcore-lib@^0.13.7:
- version "0.13.19"
- resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc"
- integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w=
- dependencies:
- bn.js "=2.0.4"
- bs58 "=2.0.0"
- buffer-compare "=1.0.0"
- elliptic "=3.0.3"
- inherits "=2.0.1"
- lodash "=3.10.1"
-
-"bitcore-message@github:CoMakery/bitcore-message#dist":
- version "1.0.2"
- resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf"
- dependencies:
- bitcore-lib "^0.13.7"
-
bitfield@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
-bn.js@=2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480"
- integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA=
-
-bn.js@^2.0.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625"
- integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU=
-
bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
dependencies:
fill-range "^7.0.1"
-brorand@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
- integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
-
browser-stdout@1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea"
integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo=
-bs58@=2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5"
- integrity sha1-crcTvtIjoKxRi72g484/SBfznrU=
-
buffer-alloc-unsafe@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
-buffer-compare@=1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2"
- integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI=
-
-buffer-equal-constant-time@1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
- integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
-
buffer-equals@^1.0.3, buffer-equals@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5"
jsbn "~0.1.0"
safer-buffer "^2.1.0"
-ecdsa-sig-formatter@1.0.11:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
- integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
- dependencies:
- safe-buffer "^5.0.1"
-
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
-elliptic@=3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595"
- integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU=
- dependencies:
- bn.js "^2.0.0"
- brorand "^1.0.1"
- hash.js "^1.0.0"
- inherits "^2.0.1"
-
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
dependencies:
function-bind "^1.1.1"
-hash.js@^1.0.0:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
- integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
- dependencies:
- inherits "^2.0.3"
- minimalistic-assert "^1.0.1"
-
hashish@~0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-inherits@=2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
- integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
-
ini@^1.3.4, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
-"jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017":
- version "1.2.2-2"
- resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f"
- dependencies:
- bitcore-message "github:CoMakery/bitcore-message#dist"
- jsonld "^0.5.12"
- jws "^3.1.4"
- node-forge "^0.7.1"
-
-jsonld@^0.5.12:
- version "0.5.21"
- resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf"
- integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw==
- dependencies:
- rdf-canonize "^0.2.1"
- request "^2.83.0"
- semver "^5.5.0"
- xmldom "0.1.19"
-
jsonld@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9"
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
-jwa@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
- integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
- dependencies:
- buffer-equal-constant-time "1.0.1"
- ecdsa-sig-formatter "1.0.11"
- safe-buffer "^5.0.1"
-
-jws@^3.1.4:
- version "3.2.2"
- resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
- integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
- dependencies:
- jwa "^1.4.1"
- safe-buffer "^5.0.1"
-
k-bucket@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=
-lodash@=3.10.1:
- version "3.10.1"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
- integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=
-
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
-minimalistic-assert@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
- integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
-
minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"