From: Chocobozzz Date: Tue, 3 Sep 2019 13:43:47 +0000 (+0200) Subject: Merge branch 'release/1.4.0' into develop X-Git-Tag: v2.0.0-rc.1~95 X-Git-Url: https://git.immae.eu/?a=commitdiff_plain;h=282e61e6c11f79e919c543871783fe1a00298d18;hp=f01dc977ae3afc152f305cd14cb39c8207cf9ca4;p=github%2FChocobozzz%2FPeerTube.git Merge branch 'release/1.4.0' into develop --- diff --git a/README.md b/README.md index 29478b085..4a785478c 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch 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). diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 1a5144b11..4bf48a570 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss @@ -26,11 +26,6 @@ body { .vjs-dock-description { font-size: 11px; - .text::before, .text::after { - display: inline-block; - content: '\1F308'; - } - .text::before { margin-right: 4px; } diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 6ff3efef1..19d2a1d02 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -239,7 +239,7 @@ export class PeerTubeEmbed { const config: ServerConfig = await configResponse.json() const description = config.tracker.enabled && this.warningTitle - ? '' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '' + ? '' + this.player.localize('Watching this video may reveal your IP address to others.') + '' : undefined this.player.dock({ diff --git a/config/default.yaml b/config/default.yaml index dfba23f59..b8ba7006a 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -69,7 +69,7 @@ email: # 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/' @@ -85,7 +85,7 @@ storage: 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 diff --git a/config/production.yaml.example b/config/production.yaml.example index 267186e08..2dec5ef0c 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -70,11 +70,11 @@ email: # 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/' @@ -86,7 +86,7 @@ storage: 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 @@ -157,8 +157,8 @@ views: 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 @@ -278,7 +278,7 @@ services: 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: diff --git a/package.json b/package.json index ce689a4b3..0c1ec93d1 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,6 @@ "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", diff --git a/scripts/client-report.sh b/scripts/client-report.sh index df7ccda27..a758a211c 100755 --- a/scripts/client-report.sh +++ b/scripts/client-report.sh @@ -5,5 +5,5 @@ set -eu 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" diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 11504b354..453ced8bf 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts @@ -16,7 +16,6 @@ import { } from '../../middlewares' import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' import { AccountModel } from '../../models/account/account' -import { ActorModel } from '../../models/activitypub/actor' import { ActorFollowModel } from '../../models/activitypub/actor-follow' import { VideoModel } from '../../models/video/video' import { VideoCommentModel } from '../../models/video/video-comment' @@ -38,6 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' import { VideoPlaylistModel } from '../../models/video/video-playlist' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' +import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' const activityPubClientRouter = express.Router() @@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT activityPubClientRouter.get('/video-playlists/:playlistId', executeIfActivityPub, - asyncMiddleware(videoPlaylistsGetValidator), + asyncMiddleware(videoPlaylistsGetValidator('all')), asyncMiddleware(videoPlaylistController) ) activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', @@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) { async function videoController (req: express.Request, res: express.Response) { // We need more attributes - const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id }) + const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) // We need captions to render AP object - video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) + const captions = await VideoCaptionModel.listVideoCaptions(video.id) + const videoWithCaptions = Object.assign(video, { VideoCaptions: captions }) - const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) - const videoObject = audiencify(video.toActivityPubObject(), audience) + const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC) + const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience) if (req.path.endsWith('/activity')) { - const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) + const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) return activityPubResponse(activityPubContextify(data), res) } @@ -231,13 +232,13 @@ async function videoAnnounceController (req: express.Request, res: express.Respo if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) - const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) + const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) return activityPubResponse(activityPubContextify(activity), res) } async function videoAnnouncesController (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const handler = async (start: number, count: number) => { const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) @@ -252,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp } async function videoLikesController (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) return activityPubResponse(activityPubContextify(json), res) } async function videoDislikesController (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) return activityPubResponse(activityPubContextify(json), res) } async function videoCommentsController (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const handler = async (start: number, count: number) => { const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) @@ -301,7 +302,7 @@ async function videoChannelFollowingController (req: express.Request, res: expre } async function videoCommentController (req: express.Request, res: express.Response) { - const videoComment = res.locals.videoComment + const videoComment = res.locals.videoCommentFull if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) @@ -337,7 +338,7 @@ async function videoRedundancyController (req: express.Request, res: express.Res } async function videoPlaylistController (req: express.Request, res: express.Response) { - const playlist = res.locals.videoPlaylist + const playlist = res.locals.videoPlaylistFull // We need more attributes playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) @@ -350,7 +351,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo } 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) @@ -358,7 +359,7 @@ async function videoPlaylistElementController (req: express.Request, res: expres // --------------------------------------------------------------------------- -async function actorFollowing (req: express.Request, actor: ActorModel) { +async function actorFollowing (req: express.Request, actor: MActorId) { const handler = (start: number, count: number) => { return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) } @@ -366,7 +367,7 @@ async function actorFollowing (req: express.Request, actor: ActorModel) { return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) } -async function actorFollowers (req: express.Request, actor: ActorModel) { +async function actorFollowers (req: express.Request, actor: MActorId) { const handler = (start: number, count: number) => { return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) } @@ -374,7 +375,7 @@ async function actorFollowers (req: express.Request, actor: ActorModel) { return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) } -async function actorPlaylists (req: express.Request, account: AccountModel) { +async function actorPlaylists (req: express.Request, account: MAccountId) { const handler = (start: number, count: number) => { return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) } @@ -382,7 +383,7 @@ async function actorPlaylists (req: express.Request, account: AccountModel) { return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) } -function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { +function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { const handler = async (start: number, count: number) => { const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) return { diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index 2d3eef222..d9df253aa 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts @@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' import { queue } from 'async' import { ActorModel } from '../../models/activitypub/actor' -import { SignatureActorModel } from '../../typings/models' +import { MActorDefault, MActorSignature } from '../../typings/models' const inboxRouter = express.Router() @@ -41,7 +41,8 @@ export { // --------------------------------------------------------------------------- -const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => { +type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault } +const inboxQueue = queue((task, cb) => { const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } processActivities(task.activities, options) diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 38b6ec976..f3dd2ad7d 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts @@ -6,11 +6,9 @@ import { logger } from '../../helpers/logger' import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' import { buildAudience } from '../../lib/activitypub/audience' import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' -import { AccountModel } from '../../models/account/account' -import { ActorModel } from '../../models/activitypub/actor' import { VideoModel } from '../../models/video/video' import { activityPubResponse } from './utils' -import { VideoChannelModel } from '../../models/video/video-channel' +import { MActorLight } from '@server/typings/models' const outboxRouter = express.Router() @@ -45,14 +43,10 @@ async function outboxController (req: express.Request, res: express.Response) { return activityPubResponse(activityPubContextify(json), res) } -async function buildActivities (actor: ActorModel, start: number, count: number) { +async function buildActivities (actor: MActorLight, start: number, count: number) { const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) const activities: Activity[] = [] - // Avoid too many SQL requests - const actors = data.data.map(v => v.VideoChannel.Account.Actor) - actors.push(actor) - for (const video of data.data) { const byActor = video.VideoChannel.Account.Actor const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 9a1e30b83..349650aca 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts @@ -19,6 +19,7 @@ import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel import { logger } from '../../helpers/logger' import { VideoChannelModel } from '../../models/video/video-channel' import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' +import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' const searchRouter = express.Router() @@ -84,7 +85,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr } async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { - let videoChannel: VideoChannelModel + let videoChannel: MChannelAccountDefault let uri = search if (isWebfingerSearch) { @@ -137,7 +138,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response) } async function searchVideoURI (url: string, res: express.Response) { - let video: VideoModel + let video: MVideoAccountLightBlacklistAllFiles // Check if we can fetch a remote video with the URL if (isUserAbleToSearchRemoteURI(res)) { diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index ae40e86f8..27351c1a9 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts @@ -48,6 +48,7 @@ import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' import { UserRegister } from '../../../../shared/models/users/user-register.model' +import { MUser, MUserAccountDefault } from '@server/typings/models' const auditLogger = auditLoggerFactory('users') @@ -195,7 +196,7 @@ async function createUser (req: express.Request, res: express.Response) { videoQuota: body.videoQuota, videoQuotaDaily: body.videoQuotaDaily, adminFlags: body.adminFlags || UserAdminFlag.NONE - }) + }) as MUser const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) @@ -359,7 +360,7 @@ function success (req: express.Request, res: express.Response) { res.end() } -async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) { +async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) user.blocked = block diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index e7ed3de64..78e1e7fa3 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts @@ -23,15 +23,12 @@ import { createReqFiles } from '../../../helpers/express-utils' 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() @@ -147,7 +144,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons } async function getUserVideoRating (req: express.Request, res: express.Response) { - const videoId = res.locals.video.id + const videoId = res.locals.videoId.id const accountId = +res.locals.oauth.token.User.Account.id const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) @@ -165,8 +162,6 @@ async function deleteMe (req: express.Request, res: express.Response) { await user.destroy() - auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({}))) - return res.sendStatus(204) } @@ -175,7 +170,6 @@ async function updateMe (req: express.Request, res: express.Response) { 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 @@ -204,8 +198,6 @@ async function updateMe (req: express.Request, res: express.Response) { await userAccount.save({ transaction: t }) await sendUpdateActor(userAccount, t) - - auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) }) if (sendVerificationEmail === true) { @@ -218,13 +210,10 @@ async function updateMe (req: express.Request, res: express.Response) { 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() }) } diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 7025c0ff1..4da1f3496 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts @@ -7,7 +7,6 @@ import { setDefaultPagination, userHistoryRemoveValidator } from '../../../middlewares' -import { UserModel } from '../../../models/account/user' import { getFormattedObjects } from '../../../helpers/utils' import { UserVideoHistoryModel } from '../../../models/account/user-video-history' import { sequelizeTypescript } from '../../../initializers' diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 81a03a62b..acc5b2987 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts @@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel' 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' @@ -35,6 +35,7 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist' 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 }) @@ -136,10 +137,10 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp async function addVideoChannel (req: express.Request, res: express.Response) { const videoChannelInfo: VideoChannelCreate = req.body - const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { + const videoChannelCreated = await sequelizeTypescript.transaction(async t => { const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) - return createVideoChannel(videoChannelInfo, account, t) + return createLocalVideoChannel(videoChannelInfo, account, t) }) setAsyncActorKeys(videoChannelCreated.Actor) @@ -181,7 +182,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response) } } - const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) + const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault await sendUpdateActor(videoChannelInstanceUpdated, t) auditLogger.update( diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index bd454f553..d9f0ff925 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts @@ -40,7 +40,7 @@ import { JobQueue } from '../../lib/job-queue' import { CONFIG } from '../../initializers/config' import { sequelizeTypescript } from '../../initializers/database' import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' -import { VideoModel } from '../../models/video/video' +import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) @@ -58,7 +58,7 @@ videoPlaylistRouter.get('/', ) videoPlaylistRouter.get('/:playlistId', - asyncMiddleware(videoPlaylistsGetValidator), + asyncMiddleware(videoPlaylistsGetValidator('summary')), getVideoPlaylist ) @@ -83,7 +83,7 @@ videoPlaylistRouter.delete('/:playlistId', ) videoPlaylistRouter.get('/:playlistId/videos', - asyncMiddleware(videoPlaylistsGetValidator), + asyncMiddleware(videoPlaylistsGetValidator('summary')), paginationValidator, setDefaultPagination, optionalAuthenticate, @@ -140,7 +140,7 @@ async function listVideoPlaylists (req: express.Request, res: express.Response) } function getVideoPlaylist (req: express.Request, res: express.Response) { - const videoPlaylist = res.locals.videoPlaylist + const videoPlaylist = res.locals.videoPlaylistSummary if (videoPlaylist.isOutdated()) { JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) @@ -159,7 +159,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { description: videoPlaylistInfo.description, privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, ownerAccountId: user.Account.id - }) + }) as MVideoPlaylistFull videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object @@ -175,8 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) : undefined - const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { - const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) + const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => { + const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull if (thumbnailModel) { thumbnailModel.automaticallyGenerated = false @@ -201,7 +201,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { } async function updateVideoPlaylist (req: express.Request, res: express.Response) { - const videoPlaylistInstance = res.locals.videoPlaylist + const videoPlaylistInstance = res.locals.videoPlaylistFull const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate @@ -275,7 +275,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response) } async function removeVideoPlaylist (req: express.Request, res: express.Response) { - const videoPlaylistInstance = res.locals.videoPlaylist + const videoPlaylistInstance = res.locals.videoPlaylistSummary await sequelizeTypescript.transaction(async t => { await videoPlaylistInstance.destroy({ transaction: t }) @@ -290,10 +290,10 @@ async function removeVideoPlaylist (req: express.Request, res: express.Response) async function addVideoInPlaylist (req: express.Request, res: express.Response) { const body: VideoPlaylistElementCreate = req.body - const videoPlaylist = res.locals.videoPlaylist - const video = res.locals.video + const videoPlaylist = res.locals.videoPlaylistFull + const video = res.locals.onlyVideo - const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { + const playlistElement = await sequelizeTypescript.transaction(async t => { const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) const playlistElement = await VideoPlaylistElementModel.create({ @@ -330,7 +330,7 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { const body: VideoPlaylistElementUpdate = req.body - const videoPlaylist = res.locals.videoPlaylist + const videoPlaylist = res.locals.videoPlaylistFull const videoPlaylistElement = res.locals.videoPlaylistElement const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { @@ -354,7 +354,7 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { const videoPlaylistElement = res.locals.videoPlaylistElement - const videoPlaylist = res.locals.videoPlaylist + const videoPlaylist = res.locals.videoPlaylistFull const positionToDelete = videoPlaylistElement.position await sequelizeTypescript.transaction(async t => { @@ -381,7 +381,7 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo } async function reorderVideosPlaylist (req: express.Request, res: express.Response) { - const videoPlaylist = res.locals.videoPlaylist + const videoPlaylist = res.locals.videoPlaylistFull const body: VideoPlaylistReorder = req.body const start: number = body.startPosition @@ -434,7 +434,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons } async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { - const videoPlaylistInstance = res.locals.videoPlaylist + const videoPlaylistInstance = res.locals.videoPlaylistSummary const user = res.locals.oauth ? res.locals.oauth.token.User : undefined const server = await getServerActor() @@ -453,7 +453,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon return res.json(getFormattedObjects(resultList.data, resultList.total, options)) } -async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { +async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) { await videoPlaylist.Thumbnail.destroy() videoPlaylist.Thumbnail = null @@ -461,7 +461,7 @@ async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) } -async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) { +async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 77808466c..4ae899b7e 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -1,7 +1,7 @@ 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, @@ -21,6 +21,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse' import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' import { Notifier } from '../../../lib/notifier' import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' +import { MVideoAbuseAccountVideo } from '../../../typings/models/video' const auditLogger = auditLoggerFactory('abuse') const abuseVideoRouter = express.Router() @@ -61,7 +62,16 @@ export { // --------------------------------------------------------------------------- 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)) } @@ -94,10 +104,10 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) { } async function reportVideoAbuse (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const body: VideoAbuseCreate = req.body - const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { + const videoAbuse = await sequelizeTypescript.transaction(async t => { const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) const abuseToCreate = { @@ -107,7 +117,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { state: VideoAbuseState.PENDING } - const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) + const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) videoAbuseInstance.Video = videoInstance videoAbuseInstance.Account = reporterAccount diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 9ff494def..2a667480d 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts @@ -1,5 +1,5 @@ import * as express from 'express' -import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' +import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' import { @@ -11,15 +11,16 @@ import { setBlacklistSort, setDefaultPagination, videosBlacklistAddValidator, + videosBlacklistFiltersValidator, videosBlacklistRemoveValidator, - videosBlacklistUpdateValidator, - videosBlacklistFiltersValidator + videosBlacklistUpdateValidator } from '../../../middlewares' import { VideoBlacklistModel } from '../../../models/video/video-blacklist' import { sequelizeTypescript } from '../../../initializers' import { Notifier } from '../../../lib/notifier' import { sendDeleteVideo } from '../../../lib/activitypub/send' import { federateVideoIfNeeded } from '../../../lib/activitypub' +import { MVideoBlacklistVideo } from '@server/typings/models' const blacklistRouter = express.Router() @@ -64,7 +65,7 @@ export { // --------------------------------------------------------------------------- async function addVideoToBlacklist (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const body: VideoBlacklistCreate = req.body const toCreate = { @@ -74,7 +75,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) type: VideoBlacklistType.MANUAL } - const blacklist = await VideoBlacklistModel.create(toCreate) + const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate) blacklist.Video = videoInstance if (body.unfederate === true) { @@ -83,7 +84,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) Notifier.Instance.notifyOnVideoBlacklist(blacklist) - logger.info('Video %s blacklisted.', res.locals.video.uuid) + logger.info('Video %s blacklisted.', videoInstance.uuid) return res.type('json').status(204).end() } @@ -108,7 +109,7 @@ async function listBlacklist (req: express.Request, res: express.Response) { async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { const videoBlacklist = res.locals.videoBlacklist - const video = res.locals.video + const video = res.locals.videoAll const videoBlacklistType = await sequelizeTypescript.transaction(async t => { const unfederated = videoBlacklist.unfederated @@ -135,7 +136,7 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex Notifier.Instance.notifyOnNewVideoIfNeeded(video) } - logger.info('Video %s removed from blacklist.', res.locals.video.uuid) + logger.info('Video %s removed from blacklist.', video.uuid) return res.type('json').status(204).end() } diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 44c255232..37481d12f 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts @@ -10,6 +10,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub' import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' +import { MVideoCaptionVideo } from '@server/typings/models' const reqVideoCaptionAdd = createReqFiles( [ 'captionfile' ], @@ -46,19 +47,19 @@ export { // --------------------------------------------------------------------------- async function listVideoCaptions (req: express.Request, res: express.Response) { - const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id) + const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id) return res.json(getFormattedObjects(data, data.length)) } async function addVideoCaption (req: express.Request, res: express.Response) { const videoCaptionPhysicalFile = req.files['captionfile'][0] - const video = res.locals.video + const video = res.locals.videoAll const videoCaption = new VideoCaptionModel({ videoId: video.id, language: req.params.captionLanguage - }) + }) as MVideoCaptionVideo videoCaption.Video = video // Move physical file @@ -75,7 +76,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) { } async function deleteVideoCaption (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.videoAll const videoCaption = res.locals.videoCaption await sequelizeTypescript.transaction(async t => { diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index bc6d81a7c..b2b06b170 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts @@ -27,9 +27,6 @@ import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../. import { AccountModel } from '../../../models/account/account' import { Notifier } from '../../../lib/notifier' import { Hooks } from '../../../lib/plugins/hooks' -import { ActorModel } from '../../../models/activitypub/actor' -import { VideoChannelModel } from '../../../models/video/video-channel' -import { VideoModel } from '../../../models/video/video' import { sendDeleteVideoComment } from '../../../lib/activitypub/send' const auditLogger = auditLoggerFactory('comments') @@ -75,7 +72,7 @@ export { // --------------------------------------------------------------------------- async function listVideoThreads (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const user = res.locals.oauth ? res.locals.oauth.token.User : undefined let resultList: ResultList @@ -86,7 +83,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { start: req.query.start, count: req.query.count, sort: req.query.sort, - user: user + user }, 'filter:api.video-threads.list.params') resultList = await Hooks.wrapPromiseFun( @@ -105,7 +102,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { } async function listVideoThreadComments (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.onlyVideo const user = res.locals.oauth ? res.locals.oauth.token.User : undefined let resultList: ResultList @@ -141,7 +138,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons return createVideoComment({ text: videoCommentInfo.text, inReplyToComment: null, - video: res.locals.video, + video: res.locals.videoAll, account }, t) }) @@ -164,8 +161,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response return createVideoComment({ text: videoCommentInfo.text, - inReplyToComment: res.locals.videoComment, - video: res.locals.video, + inReplyToComment: res.locals.videoCommentFull, + video: res.locals.videoAll, account }, t) }) @@ -179,7 +176,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response } async function removeVideoComment (req: express.Request, res: express.Response) { - const videoCommentInstance = res.locals.videoComment + const videoCommentInstance = res.locals.videoCommentFull await sequelizeTypescript.transaction(async t => { await videoCommentInstance.destroy({ transaction: t }) diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 04c9b547b..28ced5836 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts @@ -1,6 +1,5 @@ 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' @@ -15,7 +14,6 @@ import { VideoImportModel } from '../../../models/video/video-import' import { JobQueue } from '../../../lib/job-queue/job-queue' import { join } from 'path' import { isArray } from '../../../helpers/custom-validators/misc' -import { VideoChannelModel } from '../../../models/video/video-channel' import * as Bluebird from 'bluebird' import * as parseTorrent from 'parse-torrent' import { getSecureTorrentName } from '../../../helpers/utils' @@ -25,8 +23,16 @@ import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' -import { ThumbnailModel } from '../../../models/video/thumbnail' -import { UserModel } from '../../../models/account/user' +import { + 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() @@ -184,8 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You 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, @@ -225,28 +231,28 @@ async function processPreview (req: express.Request, video: VideoModel) { } function insertIntoDB (parameters: { - video: VideoModel, - thumbnailModel: ThumbnailModel, - previewModel: ThumbnailModel, - videoChannel: VideoChannelModel, + video: MVideoThumbnailAccountDefault, + thumbnailModel: MThumbnail, + previewModel: MThumbnail, + videoChannel: MChannelAccountDefault, tags: string[], - videoImportAttributes: Partial, - user: UserModel -}): Bluebird { + videoImportAttributes: Partial, + user: MUser +}): Bluebird { 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, @@ -268,7 +274,7 @@ function insertIntoDB (parameters: { const videoImport = await VideoImportModel.create( Object.assign({ videoId: videoCreated.id }, videoImportAttributes), sequelizeOptions - ) + ) as MVideoImportFormattable videoImport.Video = videoCreated return videoImport diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 155ca4678..19da504c7 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../ import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' import { Hooks } from '../../../lib/plugins/hooks' +import { MVideoDetails, MVideoFullLight } from '@server/typings/models' const auditLogger = auditLoggerFactory('videos') const videosRouter = express.Router() @@ -185,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response) { 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, @@ -197,7 +198,7 @@ async function addVideo (req: express.Request, res: express.Response) { 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({ @@ -238,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) { const { videoCreated } = await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } - const videoCreated = await video.save(sequelizeOptions) + const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight await videoCreated.addAndSaveThumbnail(thumbnailModel, t) await videoCreated.addAndSaveThumbnail(previewModel, t) @@ -318,7 +319,7 @@ async function addVideo (req: express.Request, res: express.Response) { } async function updateVideo (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const videoFieldsSave = videoInstance.toJSON() const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) const videoInfoToUpdate: VideoUpdate = req.body @@ -371,7 +372,7 @@ async function updateVideo (req: express.Request, res: express.Response) { } } - const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) + const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) @@ -447,7 +448,7 @@ async function getVideo (req: express.Request, res: express.Response) { const video = await Hooks.wrapPromiseFun( VideoModel.loadForGetAPI, - { id: res.locals.video.id, userId }, + { id: res.locals.onlyVideoWithRights.id, userId }, 'filter:api.video.get.result' ) @@ -460,7 +461,7 @@ async function getVideo (req: express.Request, res: express.Response) { } async function viewVideo (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const ip = req.ip const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) @@ -483,7 +484,7 @@ async function viewVideo (req: express.Request, res: express.Response) { } async function getVideoDescription (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll let description = '' if (videoInstance.isOwned()) { @@ -522,7 +523,7 @@ async function listVideos (req: express.Request, res: express.Response) { } async function removeVideo (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll await sequelizeTypescript.transaction(async t => { await videoInstance.destroy({ transaction: t }) diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index 5272c1385..abb34082e 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts @@ -18,6 +18,7 @@ import { getFormattedObjects } from '../../../helpers/utils' import { changeVideoChannelShare } from '../../../lib/activitypub' import { sendUpdateVideo } from '../../../lib/activitypub/send' import { VideoModel } from '../../../models/video/video' +import { MVideoFullLight } from '@server/typings/models' const ownershipVideoRouter = express.Router() @@ -56,7 +57,7 @@ export { // --------------------------------------------------------------------------- async function giveVideoOwnership (req: express.Request, res: express.Response) { - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const initiatorAccountId = res.locals.oauth.token.User.Account.id const nextOwner = res.locals.nextOwner @@ -107,7 +108,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) { targetVideo.channelId = channel.id - const targetVideoUpdated = await targetVideo.save({ transaction: t }) + const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight targetVideoUpdated.VideoChannel = channel if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index b65babedf..3d2f3d728 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts @@ -27,7 +27,7 @@ export { async function rateVideo (req: express.Request, res: express.Response) { const body: UserVideoRateUpdate = req.body const rateType = body.rating - const videoInstance = res.locals.video + const videoInstance = res.locals.videoAll const userAccount = res.locals.oauth.token.User.Account await sequelizeTypescript.transaction(async t => { diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts index dcd1f070d..036e16f3a 100644 --- a/server/controllers/api/videos/watching.ts +++ b/server/controllers/api/videos/watching.ts @@ -23,7 +23,7 @@ async function userWatchVideo (req: express.Request, res: express.Response) { const user = res.locals.oauth.token.User const body: UserWatchingVideo = req.body - const { id: videoId } = res.locals.video as { id: number } + const { id: videoId } = res.locals.videoId await UserVideoHistoryModel.upsert({ videoId, diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index d3f581615..468f7a668 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts @@ -43,7 +43,7 @@ export { async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { const start = 0 - const video = res.locals.video + const video = res.locals.videoAll const videoId: number = video ? video.id : undefined const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) diff --git a/server/controllers/services.ts b/server/controllers/services.ts index c1c53c3fc..ec057235f 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts @@ -23,7 +23,7 @@ export { // --------------------------------------------------------------------------- function generateOEmbed (req: express.Request, res: express.Response) { - const video = res.locals.video + const video = res.locals.videoAll const webserverUrl = WEBSERVER.URL const maxHeight = parseInt(req.query.maxheight, 10) const maxWidth = parseInt(req.query.maxwidth, 10) diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 8979ef5f3..0f4772310 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts @@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { return res.send(json).end() } -async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) { +async function downloadTorrent (req: express.Request, res: express.Response) { const { video, videoFile } = getVideoAndFile(req, res) if (!videoFile) return res.status(404).end() return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) } -async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) { +async function downloadVideoFile (req: express.Request, res: express.Response) { const { video, videoFile } = getVideoAndFile(req, res) if (!videoFile) return res.status(404).end() @@ -242,7 +242,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n function getVideoAndFile (req: express.Request, res: express.Response) { const resolution = parseInt(req.params.resolution, 10) - const video = res.locals.video + const video = res.locals.videoAll const videoFile = video.VideoFiles.find(f => f.resolution === resolution) diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts index f2ba3c826..fc9575160 100644 --- a/server/controllers/webfinger.ts +++ b/server/controllers/webfinger.ts @@ -18,7 +18,7 @@ export { // --------------------------------------------------------------------------- function webfingerController (req: express.Request, res: express.Response) { - const actor = res.locals.actor + const actor = res.locals.actorFull const json = { subject: req.query.resource, diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 951a25669..97c809a0c 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts @@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor' import { signJsonLDObject } from './peertube-crypto' import { pageToStartAndCount } from './core-utils' import { parse } from 'url' +import { MActor } from '../typings/models' function activityPubContextify (data: T) { return Object.assign(data, { @@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi } -function buildSignedActivity (byActor: ActorModel, data: Object) { +function buildSignedActivity (byActor: MActor, data: Object) { const activity = activityPubContextify(data) return signJsonLDObject(byActor, activity) as Promise diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts index 12a7ace9f..117548a60 100644 --- a/server/helpers/actor.ts +++ b/server/helpers/actor.ts @@ -1,10 +1,13 @@ import { ActorModel } from '../models/activitypub/actor' +import * as Bluebird from 'bluebird' +import { MActorFull, MActorAccountChannelId } from '../typings/models' -type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' -function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { +type ActorFetchByUrlType = 'all' | 'association-ids' + +function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird { 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 { diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 7174d4654..2830ae017 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts @@ -1,10 +1,10 @@ import { join } from 'path' import { CONFIG } from '../initializers/config' -import { VideoCaptionModel } from '../models/video/video-caption' import * as srt2vtt from 'srt-to-vtt' -import { createReadStream, createWriteStream, remove, move } from 'fs-extra' +import { createReadStream, createWriteStream, move, remove } from 'fs-extra' +import { 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()) diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a3bceb047..cb07fa3b2 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts @@ -1,6 +1,5 @@ import * as AsyncLRU from 'async-lru' import * as jsonld from 'jsonld' -import * as jsig from 'jsonld-signatures' import { logger } from './logger' const CACHE = { @@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => { lru.get(url, cb) } -jsig.use('jsonld', jsonld) - -export { jsig, jsonld } +export { jsonld } diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index deb331abb..55bc8cc96 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts @@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) { 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) @@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { 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) { diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts index a7771e07b..9570b2799 100644 --- a/server/helpers/custom-validators/video-ownership.ts +++ b/server/helpers/custom-validators/video-ownership.ts @@ -1,10 +1,10 @@ import { Response } from 'express' -import * as validator from 'validator' import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' -import { UserModel } from '../../models/account/user' +import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' +import { MUserId } from '@server/typings/models' -export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise { - const videoChangeOwnership = await loadVideoChangeOwnership(id) +export async function doesChangeVideoOwnershipExist (id: number, res: Response) { + const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) if (!videoChangeOwnership) { res.status(404) @@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response): return true } -async function loadVideoChangeOwnership (id: string): Promise { - 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 } diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 791022b97..f5aa0bada 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts @@ -1,6 +1,7 @@ import { Response } from 'express' import { AccountModel } from '../../models/account/account' import * as Bluebird from 'bluebird' +import { MAccountDefault } from '../../typings/models' function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { const promise = AccountModel.load(id) @@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = } function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { - return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) + const promise = AccountModel.loadByNameWithHost(nameWithDomain) + + return doesAccountExist(promise, res, sendNotFound) } -async function doesAccountExist (p: Bluebird, res: Response, sendNotFound: boolean) { +async function doesAccountExist (p: Bluebird, res: Response, sendNotFound: boolean) { const account = await p if (!account) { diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index b23f1f021..1b573ca37 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts @@ -1,41 +1,23 @@ -import * as express from 'express' -import { VideoChannelModel } from '../../models/video/video-channel' +import { Response } from 'express' +import { VideoAbuseModel } from '../../models/video/video-abuse' -async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { - const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) +async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { + const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) - return processVideoChannelExist(videoChannel, res) -} - -async function doesVideoChannelIdExist (id: number, res: express.Response) { - const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) - - return processVideoChannelExist(videoChannel, res) -} - -async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { - const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) - - return processVideoChannelExist(videoChannel, res) -} - -// --------------------------------------------------------------------------- - -export { - doesLocalVideoChannelNameExist, - doesVideoChannelIdExist, - doesVideoChannelNameWithHostExist -} - -function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { - if (!videoChannel) { + if (videoAbuse === null) { res.status(404) - .json({ error: 'Video channel not found' }) + .json({ error: 'Video abuse not found' }) .end() return false } - res.locals.videoChannel = videoChannel + res.locals.videoAbuse = videoAbuse return true } + +// --------------------------------------------------------------------------- + +export { + doesVideoAbuseExist +} diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts index dc3d0144b..1b2513b60 100644 --- a/server/helpers/middlewares/video-captions.ts +++ b/server/helpers/middlewares/video-captions.ts @@ -1,8 +1,8 @@ -import { VideoModel } from '../../models/video/video' import { Response } from 'express' import { VideoCaptionModel } from '../../models/video/video-caption' +import { MVideoId } from '@server/typings/models' -async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { +async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) { const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) if (!videoCaption) { diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts index 1b573ca37..1595ecd94 100644 --- a/server/helpers/middlewares/video-channels.ts +++ b/server/helpers/middlewares/video-channels.ts @@ -1,23 +1,42 @@ -import { Response } from 'express' -import { VideoAbuseModel } from '../../models/video/video-abuse' +import * as express from 'express' +import { VideoChannelModel } from '../../models/video/video-channel' +import { 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 } diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts index 735bf362f..8e7484483 100644 --- a/server/helpers/middlewares/video-playlists.ts +++ b/server/helpers/middlewares/video-playlists.ts @@ -1,11 +1,31 @@ import * as express from 'express' import { VideoPlaylistModel } from '../../models/video/video-playlist' +import { MVideoPlaylist } from '../../typings/models/video/video-playlist' -async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { - const videoPlaylist = fetchType === 'summary' - ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) - : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) +export type VideoPlaylistFetchType = 'summary' | 'all' +async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') { + if (fetchType === 'summary') { + const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) + res.locals.videoPlaylistSummary = videoPlaylist + return handleVideoPlaylist(videoPlaylist, res) + } + + const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) + res.locals.videoPlaylistFull = videoPlaylist + + return handleVideoPlaylist(videoPlaylist, res) +} + +// --------------------------------------------------------------------------- + +export { + doesVideoPlaylistExist +} + +// --------------------------------------------------------------------------- + +function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { if (!videoPlaylist) { res.status(404) .json({ error: 'Video playlist not found' }) @@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons return false } - res.locals.videoPlaylist = videoPlaylist return true } - -// --------------------------------------------------------------------------- - -export { - doesVideoPlaylistExist -} diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index ceb1058ec..74f529804 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts @@ -1,9 +1,8 @@ import { Response } from 'express' import { fetchVideo, VideoFetchType } from '../video' -import { UserModel } from '../../models/account/user' import { UserRight } from '../../../shared/models/users' import { VideoChannelModel } from '../../models/video/video-channel' -import { VideoModel } from '../../models/video/video' +import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, 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 @@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi return false } - if (fetchType !== 'none') res.locals.video = video + switch (fetchType) { + case 'all': + res.locals.videoAll = video as MVideoFullLight + break + + case 'id': + res.locals.videoId = video + break + + case 'only-video': + res.locals.onlyVideo = video 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) { @@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode return true } -function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { +function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) { // Retrieve the user who did the request if (video.isOwned() === false) { res.status(403) diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 1424949d0..9eb782302 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts @@ -1,13 +1,13 @@ 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(bcrypt.compare) const bcryptGenSaltPromise = promisify1(bcrypt.genSalt) @@ -46,7 +46,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { return true } -function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { +function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean { return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true } @@ -56,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) { // JSONLD -async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise { +function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise { 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 { - 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 @@ -130,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a 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 }) } // --------------------------------------------------------------------------- @@ -154,3 +118,35 @@ export { } // --------------------------------------------------------------------------- + +function hash (obj: any): Promise { + 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) +} diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 1464b1477..ba07eaaf3 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts @@ -19,7 +19,10 @@ async function generateRandomString (size: number) { return raw.toString('hex') } -interface FormattableToJSON { toFormattedJSON (args?: U): V } +interface FormattableToJSON { + toFormattedJSON (args?: U): V +} + function getFormattedObjects> (objects: T[], objectsTotal: number, formattedArg?: U) { const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) diff --git a/server/helpers/video.ts b/server/helpers/video.ts index c90fe06c7..d066e2b1f 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts @@ -1,8 +1,30 @@ 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 +function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird +function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird +function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird +function fetchVideo ( + id: number | string, + fetchType: VideoFetchType, + userId?: number +): Bluebird +function fetchVideo ( + id: number | string, + fetchType: VideoFetchType, + userId?: number +): Bluebird { if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) @@ -13,15 +35,29 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu } type VideoFetchByUrlType = 'all' | 'only-video' -function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { + +function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird +function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird +function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird +function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird { 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 } diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index d1229e28f..5443a266b 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts @@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor' import { isTestInstance } from './core-utils' import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' import { WEBSERVER } from '../initializers/constants' +import { MActorFull } from '../typings/models' const webfinger = new WebFinger({ webfist_fallback: false, @@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg const [ name, host ] = uri.split('@') - let actor: ActorModel + let actor: MActorFull if (!host || host === WEBSERVER.HOST) { actor = await ActorModel.loadLocalByName(name) diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3dc178b11..908231a88 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 420 +const LAST_MIGRATION_VERSION = 425 // --------------------------------------------------------------------------- diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts new file mode 100644 index 000000000..4e5f9e6ab --- /dev/null +++ b/server/initializers/migrations/0425-nullable-actor-fields.ts @@ -0,0 +1,26 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize, + db: any +}): Promise { + 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 +} diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 9f5d12eb4..13b73077e 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts @@ -22,13 +22,27 @@ import { JobQueue } from '../job-queue' 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 (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 => { @@ -37,12 +51,26 @@ function setAsyncActorKeys (actor: ActorModel) { }) } +function getOrCreateActorAndServerAndModel ( + activityActor: string | ActivityPubActor, + fetchType: 'all', + recurseIfNeeded?: boolean, + updateCollections?: boolean +): Promise + +function getOrCreateActorAndServerAndModel ( + activityActor: string | ActivityPubActor, + fetchType?: 'association-ids', + recurseIfNeeded?: boolean, + updateCollections?: boolean +): Promise + async function getOrCreateActorAndServerAndModel ( activityActor: string | ActivityPubActor, - fetchType: ActorFetchByUrlType = 'actor-and-association-ids', + fetchType: ActorFetchByUrlType = 'association-ids', recurseIfNeeded = true, updateCollections = false -) { +): Promise { const actorUrl = getAPId(activityActor) let created = false let accountPlaylistsUrl: string @@ -61,7 +89,7 @@ async function getOrCreateActorAndServerAndModel ( // Create the attributed to actor // In PeerTube a video channel is owned by an account - let ownerActor: ActorModel = undefined + let ownerActor: MActorFullActor if (recurseIfNeeded === true && result.actor.type === 'Group') { const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) @@ -85,8 +113,8 @@ async function getOrCreateActorAndServerAndModel ( accountPlaylistsUrl = result.playlists } - if (actor.Account) actor.Account.Actor = actor - if (actor.VideoChannel) actor.VideoChannel.Actor = actor + if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor + if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') @@ -120,7 +148,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU sharedInboxUrl: WEBSERVER.URL + '/inbox', followersUrl: url + '/followers', followingUrl: url + '/following' - }) + }) as MActor } async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { @@ -140,7 +168,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ actorInstance.followingUrl = attributes.following } -async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) { +type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string } +async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) { if (info.name !== undefined) { if (actor.avatarId) { try { @@ -212,14 +241,16 @@ async function addFetchOutboxJob (actor: Pick) { return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) } -async function refreshActorIfNeeded ( - actorArg: ActorModel, +async function refreshActorIfNeeded ( + actorArg: T, fetchedType: ActorFetchByUrlType -): Promise<{ actor: ActorModel, refreshed: boolean }> { +): Promise<{ actor: T | MActorFull, refreshed: boolean }> { if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } // We need more attributes - const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) + const actor = fetchedType === 'all' + ? actorArg as MActorFull + : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) try { let actorUrl: string @@ -297,9 +328,9 @@ export { function saveActorAndServerAndModelIfNotExist ( result: FetchRemoteActorResult, - ownerActor?: ActorModel, + ownerActor?: MActorFullActor, t?: Transaction -): Bluebird | Promise { +): Bluebird | Promise { let actor = result.actor if (t !== undefined) return save(t) @@ -336,7 +367,7 @@ function saveActorAndServerAndModelIfNotExist ( // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists // (which could be false in a retried query) - const [ actorCreated ] = await ActorModel.findOrCreate({ + const [ actorCreated ] = await ActorModel.findOrCreate({ defaults: actor.toJSON(), where: { url: actor.url @@ -345,12 +376,11 @@ function saveActorAndServerAndModelIfNotExist ( }) 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 @@ -360,7 +390,7 @@ function saveActorAndServerAndModelIfNotExist ( } type FetchRemoteActorResult = { - actor: ActorModel + actor: MActor name: string summary: string support?: string @@ -429,7 +459,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe } } -async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { +async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) { const [ accountCreated ] = await AccountModel.findOrCreate({ defaults: { name: result.name, @@ -442,10 +472,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t transaction: t }) - return accountCreated + return accountCreated as MAccount } -async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { +async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) { const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ defaults: { name: result.name, @@ -460,5 +490,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu transaction: t }) - return videoChannelCreated + return videoChannelCreated as MChannel } diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index 0e3d78590..f2ab54cf7 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts @@ -3,11 +3,10 @@ import { ActivityAudience } from '../../../shared/models/activitypub' import { ACTIVITY_PUB } from '../../initializers/constants' import { ActorModel } from '../../models/activitypub/actor' import { VideoModel } from '../../models/video/video' -import { VideoCommentModel } from '../../models/video/video-comment' import { VideoShareModel } from '../../models/video/video-share' -import { ActorModelOnly } from '../../typings/models' +import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' -function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience { +function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { return { to: [ video.VideoChannel.Account.Actor.url ], cc: actorsInvolvedInVideo.map(a => a.followersUrl) @@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor } function getVideoCommentAudience ( - videoComment: VideoCommentModel, - threadParentComments: VideoCommentModel[], - actorsInvolvedInVideo: ActorModel[], + videoComment: MCommentOwnerVideo, + threadParentComments: MCommentOwner[], + actorsInvolvedInVideo: MActorFollowersUrl[], isOrigin = false ): ActivityAudience { const to = [ ACTIVITY_PUB.PUBLIC ] @@ -42,26 +41,28 @@ function getVideoCommentAudience ( } } -function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience { +function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience { return { to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), cc: [] } } -async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { - const actors = await VideoShareModel.loadActorsByShare(video.id, t) +async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { + const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) - const videoActor = video.VideoChannel && video.VideoChannel.Account - ? video.VideoChannel.Account.Actor - : await ActorModel.loadAccountActorByVideoId(video.id, t) + const videoAll = video as VideoModel + + const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account + ? videoAll.VideoChannel.Account.Actor + : await ActorModel.loadFromAccountByVideoId(video.id, t) actors.push(videoActor) return actors } -function getAudience (actorSender: ActorModelOnly, isPublic = true) { +function getAudience (actorSender: MActorFollowersUrl, isPublic = true) { return buildAudience([ actorSender.followersUrl ], isPublic) } diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index de5cc54ac..65b2dcb49 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts @@ -1,10 +1,10 @@ import { CacheFileObject } from '../../../shared/index' -import { VideoModel } from '../../models/video/video' import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' import { Transaction } from 'sequelize' import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' +import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models' -function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { +function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) { if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { const url = cacheFileObject.url @@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject } } -async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { +async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) if (!redundancyModel) { @@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: } } -function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { +function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) return VideoRedundancyModel.create(attributes, { transaction: t }) @@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b function updateCacheFile ( cacheFileObject: CacheFileObject, - redundancyModel: VideoRedundancyModel, - video: VideoModel, - byActor: { id?: number }, + redundancyModel: MVideoRedundancy, + video: MVideoWithAllFiles, + byActor: MActorId, t: Transaction ) { if (redundancyModel.actorId !== byActor.id) { diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index c2e2a3283..c52b715ef 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts @@ -1,7 +1,6 @@ import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' import { crawlCollectionPage } from './crawl' import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' -import { AccountModel } from '../../models/account/account' import { isArray } from '../../helpers/custom-validators/misc' import { getOrCreateActorAndServerAndModel } from './actor' import { logger } from '../../helpers/logger' @@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object import { getOrCreateVideoAndAccountAndChannel } from './videos' import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' -import { VideoModel } from '../../models/video/video' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' import { sequelizeTypescript } from '../../initializers/database' import { createPlaylistMiniatureFromUrl } from '../thumbnail' import { FilteredModelAttributes } from '../../typings/sequelize' -import { AccountModelId } from '../../typings/models' +import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models' +import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist' -function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { +function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED return { @@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount } } -function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { +function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) { return { position: elementObject.position, url: elementObject.id, @@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje } } -async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { +async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) { await Bluebird.map(playlistUrls, async playlistUrl => { try { const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) @@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) } -async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { +async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { @@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc } } - const [ playlist ] = await VideoPlaylistModel.upsert(playlistAttributes, { returning: true }) + const [ playlist ] = await VideoPlaylistModel.upsert(playlistAttributes, { returning: true }) let accItems: string[] = [] await crawlCollectionPage(playlistObject.id, items => { @@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc return resetVideoPlaylistElements(accItems, refreshedPlaylist) } -async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise { +async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise { if (!videoPlaylist.isOutdated()) return videoPlaylist try { @@ -157,7 +156,7 @@ export { // --------------------------------------------------------------------------- -async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { +async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) { const elementsToCreate: FilteredModelAttributes[] = [] await Bluebird.map(elementUrls, async elementUrl => { diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts index cf27e6c32..86f7c764d 100644 --- a/server/lib/activitypub/process/process-accept.ts +++ b/server/lib/activitypub/process/process-accept.ts @@ -1,9 +1,8 @@ import { ActivityAccept } from '../../../../shared/models/activitypub' -import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { addFetchOutboxJob } from '../actor' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorDefault, MActorSignature } from '../../../typings/models' async function processAcceptActivity (options: APProcessorOptions) { const { byActor: targetActor, inboxActor } = options @@ -20,7 +19,7 @@ export { // --------------------------------------------------------------------------- -async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) { +async function processAccept (actor: MActorDefault, targetActor: MActorSignature) { const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) if (!follow) throw new Error('Cannot find associated follow.') diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index b3cdc4441..7e22125d5 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts @@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share' import { forwardVideoRelatedActivity } from '../send/utils' import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { Notifier } from '../../notifier' -import { VideoModel } from '../../../models/video/video' import { logger } from '../../../helpers/logger' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' async function processAnnounceActivity (options: APProcessorOptions) { const { activity, byActor: actorAnnouncer } = options @@ -26,10 +25,10 @@ export { // --------------------------------------------------------------------------- -async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) { +async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) { const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id - let video: VideoModel + let video: MVideoAccountLightBlacklistAllFiles let videoCreated: boolean try { diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 6815c6997..bee853721 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts @@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file' import { Notifier } from '../../notifier' import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' import { createOrUpdateVideoPlaylist } from '../playlist' -import { VideoModel } from '../../../models/video/video' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { VideoCommentModel } from '../../../models/video/video-comment' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' async function processCreateActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) { return video } -async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) { +async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { const cacheFile = activity.object as CacheFileObject const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) @@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat } } -async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) { +async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) { const commentObject = activity.object as VideoCommentObject const byAccount = byActor.Account if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) - let video: VideoModel + let video: MVideoAccountLightBlacklistAllFiles let created: boolean - let comment: VideoCommentModel + let comment: MCommentOwnerVideo try { const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) video = resolveThreadResult.video @@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig if (created && notify) Notifier.Instance.notifyOnNewComment(comment) } -async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) { +async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) { const playlistObject = activity.object as PlaylistObject const byAccount = byActor.Account diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 344d14322..79d0e0d79 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts @@ -2,15 +2,13 @@ import { ActivityDelete } from '../../../../shared/models/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' import { sequelizeTypescript } from '../../../initializers' -import { AccountModel } from '../../../models/account/account' import { ActorModel } from '../../../models/activitypub/actor' import { VideoModel } from '../../../models/video/video' -import { VideoChannelModel } from '../../../models/video/video-channel' import { VideoCommentModel } from '../../../models/video/video-comment' import { forwardVideoRelatedActivity } from '../send/utils' import { VideoPlaylistModel } from '../../../models/video/video-playlist' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models' async function processDeleteActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions { @@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) } -async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { +async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) { logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) await sequelizeTypescript.transaction(async t => { @@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) } -async function processDeleteAccount (accountToRemove: AccountModel) { +async function processDeleteAccount (accountToRemove: MAccountActor) { logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) await sequelizeTypescript.transaction(async t => { @@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) { logger.info('Remote account %s removed.', accountToRemove.Actor.url) } -async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { +async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) { logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) await sequelizeTypescript.transaction(async t => { @@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) } -function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { +function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) { logger.debug('Removing remote video comment "%s".', videoComment.url) return sequelizeTypescript.transaction(async t => { diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts index 727fcfee0..debd8a67c 100644 --- a/server/lib/activitypub/process/process-dislike.ts +++ b/server/lib/activitypub/process/process-dislike.ts @@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { forwardVideoRelatedActivity } from '../send/utils' import { getVideoDislikeActivityPubUrl } from '../url' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature } from '../../../typings/models' async function processDislikeActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -22,7 +22,7 @@ export { // --------------------------------------------------------------------------- -async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) { +async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) { const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object const byAccount = byActor.Account diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1f8a80c14..e6e9084de 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts @@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { Notifier } from '../../notifier' import { getAPId } from '../../../helpers/activitypub' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models' async function processFlagActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -23,31 +23,39 @@ export { // --------------------------------------------------------------------------- -async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) { +async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) - logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object)) - 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 }) + } + } } diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 240aa5799..bc5660395 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts @@ -10,8 +10,7 @@ import { getAPId } from '../../../helpers/activitypub' import { getServerActor } from '../../../helpers/utils' import { CONFIG } from '../../../initializers/config' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' -import { ActorFollowModelLight } from '../../../typings/models/actor-follow' +import { MAccount, MActorFollowActors, MActorFollowFull, MActorSignature } from '../../../typings/models' async function processFollowActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -28,7 +27,7 @@ export { // --------------------------------------------------------------------------- -async function processFollow (byActor: SignatureActorModel, targetActorURL: string) { +async function processFollow (byActor: MActorSignature, targetActorURL: string) { const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) @@ -43,10 +42,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri await sendReject(byActor, targetActor) - return { actorFollow: undefined } + return { actorFollow: undefined as MActorFollowActors } } - const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ + const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ where: { actorId: byActor.id, targetActorId: targetActor.id @@ -57,7 +56,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' }, transaction: t - }) as [ ActorFollowModelLight, boolean ] + }) if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { actorFollow.state = 'accepted' @@ -77,8 +76,14 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri if (!actorFollow) return if (created) { - if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) - else Notifier.Instance.notifyOfNewUserFollow(actorFollow) + if (isFollowingInstance) { + Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) + } else { + const actorFollowFull = actorFollow as MActorFollowFull + actorFollowFull.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as MAccount + + Notifier.Instance.notifyOfNewUserFollow(actorFollowFull) + } } logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index cf559af72..62be0de42 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts @@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { getVideoLikeActivityPubUrl } from '../url' import { getAPId } from '../../../helpers/activitypub' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature } from '../../../typings/models' async function processLikeActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -22,7 +22,7 @@ export { // --------------------------------------------------------------------------- -async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) { +async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) { const videoUrl = getAPId(activity.object) const byAccount = byActor.Account diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 22e311ceb..00e9afa10 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts @@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity' import { sequelizeTypescript } from '../../../initializers' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { ActorModelOnly } from '../../../typings/models' +import { MActor } from '../../../typings/models' async function processRejectActivity (options: APProcessorOptions) { const { byActor: targetActor, inboxActor } = options @@ -19,7 +19,7 @@ export { // --------------------------------------------------------------------------- -async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) { +async function processReject (follower: MActor, targetActor: MActor) { return sequelizeTypescript.transaction(async t => { const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index c37ee38bb..10643b2e9 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts @@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { VideoShareModel } from '../../../models/video/video-share' import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature } from '../../../typings/models' async function processUndoActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -54,7 +54,7 @@ export { // --------------------------------------------------------------------------- -async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) { +async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) { const likeActivity = activity.object as ActivityLike const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) @@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity }) } -async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) { +async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) { const dislike = activity.object.type === 'Dislike' ? activity.object : activity.object.object as DislikeObject @@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ }) } -async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) { +async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) { const cacheFileObject = activity.object.object as CacheFileObject const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) @@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act }) } -function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) { +function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) { return sequelizeTypescript.transaction(async t => { const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) @@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ }) } -function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) { +function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) { return sequelizeTypescript.transaction(async t => { const share = await VideoShareModel.loadByUrl(announceActivity.id, t) if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 414f9e375..a47d605d8 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts @@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' import { createOrUpdateVideoPlaylist } from '../playlist' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature, MAccountIdActor } from '../../../typings/models' async function processUpdateActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -53,7 +53,7 @@ export { // --------------------------------------------------------------------------- -async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) { +async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) { const videoObject = activity.object as VideoTorrentObject if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { @@ -61,20 +61,23 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit return undefined } - const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) + const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' }) const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) + const 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)) { @@ -150,7 +153,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) } } -async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) { +async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) { const playlistObject = activity.object as PlaylistObject const byAccount = byActor.Account diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index e4997b828..df29ee968 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts @@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' import { Redis } from '../../redis' import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorSignature } from '../../../typings/models' async function processViewActivity (options: APProcessorOptions) { const { activity, byActor } = options @@ -18,11 +18,11 @@ export { // --------------------------------------------------------------------------- -async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) { +async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) { const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object const options = { - videoObject: videoObject, + videoObject, fetchType: 'only-video' as 'only-video' } const { video } = await getOrCreateVideoAndAccountAndChannel(options) diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts index d108fe321..c602bf218 100644 --- a/server/lib/activitypub/process/process.ts +++ b/server/lib/activitypub/process/process.ts @@ -1,7 +1,6 @@ import { Activity, ActivityType } from '../../../../shared/models/activitypub' import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' import { logger } from '../../../helpers/logger' -import { ActorModel } from '../../../models/activitypub/actor' import { processAcceptActivity } from './process-accept' import { processAnnounceActivity } from './process-announce' import { processCreateActivity } from './process-create' @@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike' import { processFlagActivity } from './process-flag' import { processViewActivity } from './process-view' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' -import { SignatureActorModel } from '../../../typings/models' +import { MActorDefault, MActorSignature } from '../../../typings/models' const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions) => Promise } = { Create: processCreateActivity, @@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) } -function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { +function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { if (!audience) audience = getAudience(byActor) return audiencify( @@ -122,8 +130,8 @@ export { // --------------------------------------------------------------------------- async function sendVideoRelatedCreateActivity (options: { - byActor: ActorModel, - video: VideoModel, + byActor: MActorLight, + video: MVideoAccountLight, url: string, object: any, transaction?: Transaction diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index 6c7fb8449..4b1ff8dc5 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts @@ -1,17 +1,17 @@ import { Transaction } from 'sequelize' import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' import { ActorModel } from '../../../models/activitypub/actor' -import { VideoModel } from '../../../models/video/video' import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoShareModel } from '../../../models/video/video-share' import { getDeleteActivityPubUrl } from '../url' import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' import { logger } from '../../../helpers/logger' -import { VideoPlaylistModel } from '../../../models/video/video-playlist' import { getServerActor } from '../../../helpers/utils' +import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video' +import { MActorUrl } from '../../../typings/models' -async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { +async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { logger.info('Creating job to broadcast delete of video %s.', video.url) const byActor = video.VideoChannel.Account.Actor @@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) { return broadcastToFollowers(activity, byActor, actorsInvolved, t) } -async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { +async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) { logger.info('Creating job to send delete of comment %s.', videoComment.url) const isVideoOrigin = videoComment.Video.isOwned() @@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) } -async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { +async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) const byActor = videoPlaylist.OwnerAccount.Actor @@ -101,7 +101,7 @@ export { // --------------------------------------------------------------------------- -function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { +function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete { const activity = { type: 'Delete' as 'Delete', id: url, diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts index a88436f2c..6e41f241f 100644 --- a/server/lib/activitypub/send/send-dislike.ts +++ b/server/lib/activitypub/send/send-dislike.ts @@ -1,13 +1,12 @@ import { Transaction } from 'sequelize' -import { ActorModel } from '../../../models/activitypub/actor' -import { VideoModel } from '../../../models/video/video' import { getVideoDislikeActivityPubUrl } from '../url' import { logger } from '../../../helpers/logger' import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' import { sendVideoRelatedActivity } from './utils' import { audiencify, getAudience } from '../audience' +import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' -async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { +async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { logger.info('Creating job to dislike %s.', video.url) const activityBuilder = (audience: ActivityAudience) => { @@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) } -function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { +function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { if (!audience) audience = getAudience(byActor) return audiencify( diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 61ee389a6..5ae1614ab 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts @@ -1,14 +1,13 @@ -import { ActorModel } from '../../../models/activitypub/actor' -import { VideoModel } from '../../../models/video/video' -import { VideoAbuseModel } from '../../../models/video/video-abuse' import { getVideoAbuseActivityPubUrl } from '../url' import { unicastTo } from './utils' import { logger } from '../../../helpers/logger' import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' import { audiencify, getAudience } from '../audience' import { Transaction } from 'sequelize' +import { MActor, MVideoFullLight } from '../../../typings/models' +import { MVideoAbuseVideo } from '../../../typings/models/video' -async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { +async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { if (!video.VideoChannel.Account.Actor.serverId) return // Local user const url = getVideoAbuseActivityPubUrl(videoAbuse) @@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) } -function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { +function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { if (!audience) audience = getAudience(byActor) const activity = Object.assign( diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts index a59ed50cf..6b17b25da 100644 --- a/server/lib/activitypub/send/send-follow.ts +++ b/server/lib/activitypub/send/send-follow.ts @@ -4,9 +4,9 @@ import { getActorFollowActivityPubUrl } from '../url' import { unicastTo } from './utils' import { logger } from '../../../helpers/logger' import { Transaction } from 'sequelize' -import { ActorModelOnly } from '../../../typings/models' +import { MActor, MActorFollowActors } from '../../../typings/models' -function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { +function sendFollow (actorFollow: MActorFollowActors, t: Transaction) { const me = actorFollow.ActorFollower const following = actorFollow.ActorFollowing @@ -21,7 +21,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) } -function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow { +function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { return { type: 'Follow', id: url, diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index 35227887a..e84a6f98b 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts @@ -1,13 +1,12 @@ import { Transaction } from 'sequelize' import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' -import { ActorModel } from '../../../models/activitypub/actor' -import { VideoModel } from '../../../models/video/video' import { getVideoLikeActivityPubUrl } from '../url' import { sendVideoRelatedActivity } from './utils' import { audiencify, getAudience } from '../audience' import { logger } from '../../../helpers/logger' +import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' -async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) { +async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { logger.info('Creating job to like %s.', video.url) const activityBuilder = (audience: ActivityAudience) => { @@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) } -function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { +function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { if (!audience) audience = getAudience(byActor) return audiencify( diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts index 63110b433..4258a3c36 100644 --- a/server/lib/activitypub/send/send-reject.ts +++ b/server/lib/activitypub/send/send-reject.ts @@ -1,12 +1,11 @@ import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' -import { ActorModel } from '../../../models/activitypub/actor' import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' import { unicastTo } from './utils' import { buildFollowActivity } from './send-follow' import { logger } from '../../../helpers/logger' -import { SignatureActorModel } from '../../../typings/models' +import { MActor } from '../../../typings/models' -async function sendReject (follower: SignatureActorModel, following: ActorModel) { +async function sendReject (follower: MActor, following: MActor) { if (!follower.serverId) { // This should never happen logger.warn('Do not sending reject to local follower.') return @@ -31,7 +30,7 @@ export { // --------------------------------------------------------------------------- -function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { +function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject { return { type: 'Reject', id: url, diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index 8fcbbac5c..e9ab5b3c5 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts @@ -2,13 +2,12 @@ import { Transaction } from 'sequelize' import { ActivityAnnounce, ActivityAudience, - ActivityCreate, ActivityDislike, + ActivityCreate, + ActivityDislike, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub' -import { ActorModel } from '../../../models/activitypub/actor' -import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { VideoModel } from '../../../models/video/video' import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' @@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience' import { buildCreateActivity } from './send-create' import { buildFollowActivity } from './send-follow' import { buildLikeActivity } from './send-like' -import { VideoShareModel } from '../../../models/video/video-share' import { buildAnnounceWithVideoAudience } from './send-announce' import { logger } from '../../../helpers/logger' -import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' import { buildDislikeActivity } from './send-dislike' - -async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { +import { + MActor, MActorAudience, + MActorFollowActors, + MActorLight, + MVideo, + MVideoAccountLight, + MVideoRedundancyVideo, + MVideoShare +} from '../../../typings/models' + +async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { const me = actorFollow.ActorFollower const following = actorFollow.ActorFollowing @@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) } -async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { +async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { logger.info('Creating job to undo announce %s.', videoShare.url) const undoUrl = getUndoActivityPubUrl(videoShare.url) @@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) } -async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { +async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { logger.info('Creating job to undo a like of video %s.', video.url) const likeUrl = getVideoLikeActivityPubUrl(byActor, video) @@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) } -async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { +async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { logger.info('Creating job to undo a dislike of video %s.', video.url) const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) @@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) } -async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { +async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { logger.info('Creating job to undo cache file %s.', redundancyModel.url) const videoId = redundancyModel.getVideo().id @@ -94,7 +100,7 @@ export { function undoActivityData ( url: string, - byActor: ActorModel, + byActor: MActorAudience, object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, audience?: ActivityAudience ): ActivityUndo { @@ -112,8 +118,8 @@ function undoActivityData ( } async function sendUndoVideoRelatedActivity (options: { - byActor: ActorModel, - video: VideoModel, + byActor: MActor, + video: MVideoAccountLight, url: string, activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, transaction: Transaction diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 5bf092894..37517c2be 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -2,21 +2,29 @@ import { Transaction } from 'sequelize' import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' import { VideoPrivacy } from '../../../../shared/models/videos' import { AccountModel } from '../../../models/account/account' -import { ActorModel } from '../../../models/activitypub/actor' import { VideoModel } from '../../../models/video/video' -import { VideoChannelModel } from '../../../models/video/video-channel' import { VideoShareModel } from '../../../models/video/video-share' import { getUpdateActivityPubUrl } from '../url' import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' import { logger } from '../../../helpers/logger' import { VideoCaptionModel } from '../../../models/video/video-caption' -import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' -import { VideoPlaylistModel } from '../../../models/video/video-playlist' import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' import { getServerActor } from '../../../helpers/utils' +import { + 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) @@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) } -async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { +async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { const byActor = accountOrChannel.Actor logger.info('Creating job to update actor %s.', byActor.url) @@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod const audience = getAudience(byActor) const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) - let actorsInvolved: ActorModel[] + let actorsInvolved: MActor[] if (accountOrChannel instanceof AccountModel) { // Actors that shared my videos are involved too actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) @@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) } -async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { +async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { logger.info('Creating job to update cache file %s.', redundancyModel.url) const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) @@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR return sendVideoRelatedActivity(activityBuilder, { byActor, video }) } -async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { +async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined const byActor = videoPlaylist.OwnerAccount.Actor @@ -113,7 +121,7 @@ export { // --------------------------------------------------------------------------- -function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { +function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate { if (!audience) audience = getAudience(byActor) return audiencify( @@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud type: 'Update' as 'Update', id: url, actor: byActor.url, - object: audiencify(object, audience - ) + object: audiencify(object, audience) }, audience ) diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 8ad126be0..8809417f9 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts @@ -1,13 +1,13 @@ import { Transaction } from 'sequelize' import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' import { ActorModel } from '../../../models/activitypub/actor' -import { VideoModel } from '../../../models/video/video' import { getVideoLikeActivityPubUrl } from '../url' import { sendVideoRelatedActivity } from './utils' import { audiencify, getAudience } from '../audience' import { logger } from '../../../helpers/logger' +import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' -async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { +async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { logger.info('Creating job to send view of %s.', video.url) const activityBuilder = (audience: ActivityAudience) => { @@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) } -function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { +function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { if (!audience) audience = getAudience(byActor) return audiencify( diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 4f69afb00..8129ab32a 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts @@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger' import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { JobQueue } from '../../job-queue' -import { VideoModel } from '../../../models/video/video' import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' import { getServerActor } from '../../../helpers/utils' import { afterCommitIfTransaction } from '../../../helpers/database-utils' -import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models' +import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { - byActor: ActorModelOnly, - video: VideoModel, + byActor: MActorLight, + video: MVideoAccountLight, transaction?: Transaction }) { const { byActor, video, transaction } = options @@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud async function forwardVideoRelatedActivity ( activity: Activity, t: Transaction, - followersException: ActorFollowerException[] = [], - video: VideoModel + followersException: MActorFollowerException[] = [], + video: MVideo ) { // Mastodon does not add our announces in audience, so we forward to them manually const additionalActors = await getActorsInvolvedInVideo(video, t) @@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity ( async function forwardActivity ( activity: Activity, t: Transaction, - followersException: ActorFollowerException[] = [], + followersException: MActorFollowerException[] = [], additionalFollowerUrls: string[] = [] ) { logger.info('Forwarding activity %s.', activity.id) @@ -88,10 +87,10 @@ async function forwardActivity ( async function broadcastToFollowers ( data: any, - byActor: ActorModelId, - toFollowersOf: ActorModelId[], + byActor: MActorId, + toFollowersOf: MActorId[], t: Transaction, - actorsException: ActorFollowerException[] = [] + actorsException: MActorFollowerException[] = [] ) { const uris = await computeFollowerUris(toFollowersOf, actorsException, t) @@ -100,16 +99,16 @@ async function broadcastToFollowers ( async function broadcastToActors ( data: any, - byActor: ActorModelId, - toActors: ActorModelOnly[], + byActor: MActorId, + toActors: MActor[], t?: Transaction, - actorsException: ActorFollowerException[] = [] + actorsException: MActorFollowerException[] = [] ) { const uris = await computeUris(toActors, actorsException) return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) } -function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { +function broadcastTo (uris: string[], data: any, byActor: MActorId) { if (uris.length === 0) return undefined logger.debug('Creating broadcast job.', { uris }) @@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) } -function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) { +function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { logger.debug('Creating unicast job.', { uri: toActorUrl }) const payload = { @@ -148,7 +147,7 @@ export { // --------------------------------------------------------------------------- -async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) { +async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) { const toActorFollowerIds = toFollowersOf.map(a => a.id) const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) @@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) } -async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) { +async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) { const serverActor = await getServerActor() const targetUrls = toActors .filter(a => a.id !== serverActor.id) // Don't send to ourselves @@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) } -async function buildSharedInboxesException (actorsException: ActorFollowerException[]) { +async function buildSharedInboxesException (actorsException: MActorFollowerException[]) { const serverActor = await getServerActor() return actorsException diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index 7f38402b6..fdca9bed7 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts @@ -1,19 +1,18 @@ import { Transaction } from 'sequelize' import { VideoPrivacy } from '../../../shared/models/videos' import { getServerActor } from '../../helpers/utils' -import { VideoModel } from '../../models/video/video' import { VideoShareModel } from '../../models/video/video-share' import { sendUndoAnnounce, sendVideoAnnounce } from './send' import { getVideoAnnounceActivityPubUrl } from './url' -import { VideoChannelModel } from '../../models/video/video-channel' import * as Bluebird from 'bluebird' import { doRequest } from '../../helpers/requests' import { getOrCreateActorAndServerAndModel } from './actor' import { logger } from '../../helpers/logger' import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' +import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' -async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { +async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { if (video.privacy === VideoPrivacy.PRIVATE) return undefined return Promise.all([ @@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) ]) } -async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { +async function changeVideoChannelShare ( + video: MVideoAccountLight, + oldVideoChannel: MChannelActorLight, + t: Transaction +) { logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) await undoShareByVideoChannel(video, oldVideoChannel, t) @@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide await shareByVideoChannel(video, t) } -async function addVideoShares (shareUrls: string[], instance: VideoModel) { +async function addVideoShares (shareUrls: string[], video: MVideoId) { await Bluebird.map(shareUrls, async shareUrl => { try { // Fetch url @@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) { const entry = { actorId: actor.id, - videoId: instance.id, + videoId: video.id, url: shareUrl } @@ -69,7 +72,7 @@ export { // --------------------------------------------------------------------------- -async function shareByServer (video: VideoModel, t: Transaction) { +async function shareByServer (video: MVideo, t: Transaction) { const serverActor = await getServerActor() const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) @@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) { return sendVideoAnnounce(serverActor, serverShare, video, t) } -async function shareByVideoChannel (video: VideoModel, t: Transaction) { +async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) { const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ defaults: { @@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) { return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) } -async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { +async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) { // Load old share const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index dfcb3c668..6290af34b 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts @@ -1,36 +1,42 @@ import { WEBSERVER } from '../../initializers/constants' -import { VideoModel } from '../../models/video/video' -import { VideoAbuseModel } from '../../models/video/video-abuse' -import { VideoCommentModel } from '../../models/video/video-comment' -import { VideoFileModel } from '../../models/video/video-file' -import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' -import { VideoPlaylistModel } from '../../models/video/video-playlist' -import { ActorModelOnly, ActorModelUrl } from '../../typings/models' -import { ActorFollowModelLight } from '../../typings/models/actor-follow' - -function getVideoActivityPubUrl (video: VideoModel) { +import { + MActor, + MActorFollowActors, + MActorId, + MActorUrl, + MCommentId, + MVideoAbuseId, + MVideoId, + MVideoUrl, + MVideoUUID +} from '../../typings/models' +import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist' +import { MVideoFileVideoUUID } from '../../typings/models/video/video-file' +import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist' + +function getVideoActivityPubUrl (video: MVideoUUID) { return WEBSERVER.URL + '/videos/watch/' + video.uuid } -function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { +function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) { return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid } -function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { +function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) { return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid } -function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { +function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` } -function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { +function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) { return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` } -function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { +function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) { return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id } @@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) { return WEBSERVER.URL + '/accounts/' + accountName } -function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { +function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id } -function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) { +function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() } -function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { +function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { return byActor.url + '/likes/' + video.id } -function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { +function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { return byActor.url + '/dislikes/' + video.id } -function getVideoSharesActivityPubUrl (video: VideoModel) { +function getVideoSharesActivityPubUrl (video: MVideoUrl) { return video.url + '/announces' } -function getVideoCommentsActivityPubUrl (video: VideoModel) { +function getVideoCommentsActivityPubUrl (video: MVideoUrl) { return video.url + '/comments' } -function getVideoLikesActivityPubUrl (video: VideoModel) { +function getVideoLikesActivityPubUrl (video: MVideoUrl) { return video.url + '/likes' } -function getVideoDislikesActivityPubUrl (video: VideoModel) { +function getVideoDislikesActivityPubUrl (video: MVideoUrl) { return video.url + '/dislikes' } -function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { +function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) { return follower.url + '/follows/' + following.id } -function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) { +function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) { const follower = actorFollow.ActorFollower const me = actorFollow.ActorFollowing return follower.url + '/accepts/follows/' + me.id } -function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { +function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) { return follower.url + '/rejects/follows/' + following.id } -function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) { +function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) { return video.url + '/announces/' + byActor.id } diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 8d2c1ade3..3e8306fa4 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts @@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat import { logger } from '../../helpers/logger' import { doRequest } from '../../helpers/requests' import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' -import { VideoModel } from '../../models/video/video' import { VideoCommentModel } from '../../models/video/video-comment' import { getOrCreateActorAndServerAndModel } from './actor' import { getOrCreateVideoAndAccountAndChannel } from './videos' import * as Bluebird from 'bluebird' import { checkUrlsSameHost } from '../../helpers/activitypub' +import { MCommentOwner, MCommentOwnerVideo, 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 => { @@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) - let resultComment: VideoCommentModel + let resultComment: MCommentOwnerVideo if (comments.length !== 0) { - const firstReply = comments[ comments.length - 1 ] + const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo firstReply.inReplyToCommentId = null firstReply.originCommentId = null firstReply.videoId = video.id @@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { comments[comments.length - 1] = await firstReply.save() for (let i = comments.length - 2; i >= 0; i--) { - const comment = comments[ i ] + const comment = comments[ i ] as MCommentOwnerVideo comment.originCommentId = firstReply.id comment.inReplyToCommentId = comments[ i + 1 ].id comment.videoId = video.id @@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { comments[i] = await comment.save() } - resultComment = comments[0] + resultComment = comments[0] as MCommentOwnerVideo } return { video, comment: resultComment, commentCreated } @@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) { originCommentId: null, createdAt: new Date(body.published), updatedAt: new Date(body.updated) - }) + }) as MCommentOwner comment.Account = actor.Account return resolveThread({ diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index cda5b2981..6bd46bb58 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts @@ -1,6 +1,4 @@ import { Transaction } from 'sequelize' -import { AccountModel } from '../../models/account/account' -import { VideoModel } from '../../models/video/video' import { sendLike, sendUndoDislike, sendUndoLike } from './send' import { VideoRateType } from '../../../shared/models/videos' import * as Bluebird from 'bluebird' @@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger' import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' import { doRequest } from '../../helpers/requests' import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' -import { ActorModel } from '../../models/activitypub/actor' import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' import { sendDislike } from './send/send-dislike' +import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models' -async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { +async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) { let rateCounts = 0 await Bluebird.map(ratesUrl, async rateUrl => { @@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa return } -async function sendVideoRateChange (account: AccountModel, - video: VideoModel, - likes: number, - dislikes: number, - t: Transaction) { +async function sendVideoRateChange ( + account: MAccountActor, + video: MVideoAccountLight, + likes: number, + dislikes: number, + t: Transaction +) { const actor = account.Actor // Keep the order: first we undo and then we create @@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel, if (dislikes > 0) await sendDislike(actor, video, t) } -function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { - return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video) +function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) { + return rateType === 'like' + ? getVideoLikeActivityPubUrl(actor, video) + : getVideoDislikeActivityPubUrl(actor, video) } export { diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 3a8451a32..c318978fd 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -24,7 +24,6 @@ import { REMOTE_SCHEME, STATIC_PATHS } from '../../initializers/constants' -import { ActorModel } from '../../models/activitypub/actor' import { TagModel } from '../../models/video/tag' import { VideoModel } from '../../models/video/video' import { VideoFileModel } from '../../models/video/video-file' @@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue' import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' import { createRates } from './video-rates' import { addVideoShares, shareVideoByServerAndChannel } from './share' -import { AccountModel } from '../../models/account/account' import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' import { Notifier } from '../notifier' @@ -49,15 +47,31 @@ import { VideoShareModel } from '../../models/video/video-share' import { VideoCommentModel } from '../../models/video/video-comment' import { sequelizeTypescript } from '../../initializers/database' import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' -import { ThumbnailModel } from '../../models/video/thumbnail' import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' import { join } from 'path' import { FilteredModelAttributes } from '../../typings/sequelize' import { autoBlacklistVideoIfNeeded } from '../video-blacklist' import { ActorFollowScoreCache } from '../files-cache' -import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models' +import { + 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)) && @@ -102,7 +116,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request. return { response, videoObject: body } } -async function fetchRemoteVideoDescription (video: VideoModel) { +async function fetchRemoteVideoDescription (video: MVideoAccountLight) { const host = video.VideoChannel.Account.Actor.Server.host const path = video.getDescriptionAPIPath() const options = { @@ -114,14 +128,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) { return body.description ? body.description : '' } -function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { +function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) { const url = buildRemoteBaseUrl(video, path) // We need to provide a callback, if no we could have an uncaught exception return doRequestAndSaveToFile({ uri: url }, destPath) } -function buildRemoteBaseUrl (video: VideoModel, path: string) { +function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) { const host = video.VideoChannel.Account.Actor.Server.host return REMOTE_SCHEME.HTTP + '://' + host + path @@ -146,7 +160,7 @@ type SyncParam = { thumbnail: boolean refreshVideo?: boolean } -async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { +async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) const jobPayloads: ActivitypubHttpFetcherPayload[] = [] @@ -194,12 +208,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) } +function getOrCreateVideoAndAccountAndChannel (options: { + videoObject: { id: string } | string, + syncParam?: SyncParam, + fetchType?: 'all', + allowRefresh?: boolean +}): Promise<{ video: 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' @@ -227,8 +253,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: { const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) - const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) - const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail) + const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) + const videoChannel = actor.VideoChannel + const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail) await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) @@ -236,22 +263,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: { } 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) @@ -259,7 +286,7 @@ async function updateVideoFromAP (options: { logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) } - await sequelizeTypescript.transaction(async t => { + const videoUpdated = await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } videoFieldsSave = video.toJSON() @@ -293,21 +320,21 @@ async function updateVideoFromAP (options: { video.channelId = videoData.channelId video.views = videoData.views - await video.save(sequelizeOptions) + const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight - if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) + if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) // FIXME: use icon URL instead - const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) + const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename)) const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) - await video.addAndSaveThumbnail(previewModel, t) + await videoUpdated.addAndSaveThumbnail(previewModel, t) { - const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) + const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject) const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) // Remove video files that do not exist anymore - const destroyTasks = video.VideoFiles + const destroyTasks = videoUpdated.VideoFiles .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) .map(f => f.destroy(sequelizeOptions)) await Promise.all(destroyTasks) @@ -318,15 +345,15 @@ async function updateVideoFromAP (options: { .then(([ file ]) => file) }) - video.VideoFiles = await Promise.all(upsertTasks) + videoUpdated.VideoFiles = await Promise.all(upsertTasks) } { - const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) + const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles) const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) // Remove video files that do not exist anymore - const destroyTasks = video.VideoStreamingPlaylists + const destroyTasks = videoUpdated.VideoStreamingPlaylists .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) .map(f => f.destroy(sequelizeOptions)) await Promise.all(destroyTasks) @@ -337,38 +364,42 @@ async function updateVideoFromAP (options: { .then(([ streamingPlaylist ]) => streamingPlaylist) }) - video.VideoStreamingPlaylists = await Promise.all(upsertTasks) + videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks) } { // Update Tags const tags = videoObject.tag.map(tag => tag.name) const tagInstances = await TagModel.findOrCreateTags(tags, t) - await video.$set('Tags', tagInstances, sequelizeOptions) + await videoUpdated.$set('Tags', tagInstances, sequelizeOptions) } { // Update captions - await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) + await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { - return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) + return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) }) - video.VideoCaptions = await Promise.all(videoCaptionsPromises) + await Promise.all(videoCaptionsPromises) } + + return videoUpdated }) await autoBlacklistVideoIfNeeded({ - video, + video: videoUpdated, user: undefined, isRemote: true, isNew: false, transaction: undefined }) - if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users? + if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users? logger.info('Remote video with uuid %s updated', videoObject.uuid) + + return videoUpdated } catch (err) { if (video !== undefined && videoFieldsSave !== undefined) { resetSequelizeInstance(video, videoFieldsSave) @@ -381,15 +412,15 @@ async function updateVideoFromAP (options: { } async function refreshVideoIfNeeded (options: { - video: VideoModel, + video: MVideoThumbnail, fetchedType: VideoFetchByUrlType, syncParam: SyncParam -}): Promise { +}): Promise { 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 { @@ -410,12 +441,11 @@ async function refreshVideoIfNeeded (options: { } const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) - const account = await AccountModel.load(channelActor.VideoChannel.accountId) const updateOptions = { video, videoObject, - account, + account: channelActor.VideoChannel.Account, channel: channelActor.VideoChannel } await retryTransactionWrapper(updateVideoFromAP, updateOptions) @@ -467,15 +497,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' } -async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { +async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) { logger.debug('Adding remote video %s.', videoObject.id) - const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) - const video = VideoModel.build(videoData) + const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) + const video = VideoModel.build(videoData) as MVideoThumbnail const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) - let thumbnailModel: ThumbnailModel + let thumbnailModel: MThumbnail if (waitThumbnail === true) { thumbnailModel = await promiseThumbnail } @@ -483,8 +513,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { const sequelizeOptions = { transaction: t } - const videoCreated = await video.save(sequelizeOptions) - videoCreated.VideoChannel = channelActor.VideoChannel + const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight + videoCreated.VideoChannel = channel if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) @@ -517,15 +547,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) }) - const captions = await Promise.all(videoCaptionsPromises) + await Promise.all(videoCaptionsPromises) - video.VideoFiles = videoFiles - video.VideoStreamingPlaylists = streamingPlaylists - video.Tags = tagInstances - video.VideoCaptions = captions + videoCreated.VideoFiles = videoFiles + videoCreated.VideoStreamingPlaylists = streamingPlaylists + videoCreated.Tags = tagInstances const autoBlacklisted = await autoBlacklistVideoIfNeeded({ - video, + video: videoCreated, user: undefined, isRemote: true, isNew: true, @@ -548,11 +577,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor return { autoBlacklisted, videoCreated } } -async function videoActivityObjectToDBAttributes ( - videoChannel: VideoChannelModelId, - videoObject: VideoTorrentObject, - to: string[] = [] -) { +async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED const duration = videoObject.duration.replace(/[^\d]+/, '') @@ -603,7 +628,7 @@ async function videoActivityObjectToDBAttributes ( } } -function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { +function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) { const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] if (fileUrls.length === 0) { @@ -641,7 +666,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid return attributes } -function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { +function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) { const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] if (playlistUrls.length === 0) return [] diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index 1b38e6cb5..ad4cdd3ab 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts @@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send' import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' import { updateActorAvatarInstance } from './activitypub' import { processImage } from '../helpers/image-utils' -import { AccountModel } from '../models/account/account' -import { VideoChannelModel } from '../models/video/video-channel' import { extname, join } from 'path' import { retryTransactionWrapper } from '../helpers/database-utils' import * as uuidv4 from 'uuid/v4' @@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database' import * as LRUCache from 'lru-cache' import { queue } from 'async' import { downloadImage } from '../helpers/requests' +import { 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) diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index 1633e500c..28c69b46e 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts @@ -1,6 +1,7 @@ import { sequelizeTypescript } from '../initializers' import { AccountBlocklistModel } from '../models/account/account-blocklist' import { ServerBlocklistModel } from '../models/server/server-blocklist' +import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { return sequelizeTypescript.transaction(async t => { @@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) { }) } -function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) { +function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) { return sequelizeTypescript.transaction(async t => { return accountBlock.destroy({ transaction: t }) }) } -function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { +function removeServerFromBlocklist (serverBlock: MServerBlocklist) { return sequelizeTypescript.transaction(async t => { return serverBlock.destroy({ transaction: t }) }) diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 8841dd2ac..a1f4ae858 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts @@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel' import * as Bluebird from 'bluebird' import { CONFIG } from '../initializers/config' import { logger } from '../helpers/logger' +import { MAccountActor, MChannelActor, MVideo } from '../typings/models' export class ClientHtml { @@ -41,11 +42,11 @@ 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) } @@ -65,7 +66,7 @@ export class ClientHtml { } private static async getAccountOrChannelHTMLPage ( - loader: () => Bluebird, + loader: () => Bluebird, req: express.Request, res: express.Response ) { @@ -157,7 +158,7 @@ export class ClientHtml { return htmlStringPage.replace('', linkTag + '') } - private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { + private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) { const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() @@ -236,7 +237,7 @@ export class ClientHtml { return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) } - private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { + private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) { // SEO, use origin account or channel URL const metaTags = `` diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 10e7d0479..76349ef8f 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts @@ -2,17 +2,13 @@ import { createTransport, Transporter } from 'nodemailer' 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[] @@ -90,7 +86,7 @@ class Emailer { } } - addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { + addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) { const channelName = video.VideoChannel.getDisplayName() const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() @@ -111,7 +107,7 @@ class Emailer { 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() @@ -130,7 +126,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { + addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' const text = `Hi dear admin,\n\n` + @@ -148,7 +144,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - myVideoPublishedNotification (to: string[], video: VideoModel) { + myVideoPublishedNotification (to: string[], video: MVideo) { const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() const text = `Hi dear user,\n\n` + @@ -168,7 +164,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { + myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() const text = `Hi dear user,\n\n` + @@ -188,7 +184,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { + myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { const importUrl = WEBSERVER.URL + '/my-account/video-imports' const text = `Hi dear user,\n\n` + @@ -208,7 +204,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { + addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { const accountName = comment.Account.getDisplayName() const video = comment.Video const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() @@ -230,7 +226,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { + addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { const accountName = comment.Account.getDisplayName() const video = comment.Video const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() @@ -252,7 +248,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { + addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() const text = `Hi,\n\n` + @@ -269,7 +265,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { + addVideoAutoBlacklistModeratorsNotification (to: string[], video: MVideo) { const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() @@ -292,7 +288,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addNewUserRegistrationNotification (to: string[], user: UserModel) { + addNewUserRegistrationNotification (to: string[], user: MUser) { const text = `Hi,\n\n` + `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + `Cheers,\n` + @@ -307,7 +303,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { + addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) { const videoName = videoBlacklist.Video.name const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() @@ -329,7 +325,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addVideoUnblacklistNotification (to: string[], video: VideoModel) { + addVideoUnblacklistNotification (to: string[], video: MVideo) { const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() const text = 'Hi,\n\n' + @@ -381,7 +377,7 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } - addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { + addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { const reasonString = reason ? ` for the following reason: ${reason}` : '' const blockedWord = blocked ? 'blocked' : 'unblocked' const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` diff --git a/server/lib/hls.ts b/server/lib/hls.ts index 98da4dcd8..05136c21c 100644 --- a/server/lib/hls.ts +++ b/server/lib/hls.ts @@ -1,4 +1,3 @@ -import { VideoModel } from '../models/video/video' import { basename, dirname, join } from 'path' import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' @@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash' import { VideoFileModel } from '../models/video/video-file' import { CONFIG } from '../initializers/config' import { sequelizeTypescript } from '../initializers/database' +import { MVideoWithFile } from '@server/typings/models' async function updateStreamingPlaylistsInfohashesIfNeeded () { const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() @@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () { } } -async function updateMasterHLSPlaylist (video: VideoModel) { +async function updateMasterHLSPlaylist (video: MVideoWithFile) { const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) @@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) { await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') } -async function updateSha256Segments (video: VideoModel) { +async function updateSha256Segments (video: MVideoWithFile) { const json: { [filename: string]: { [range: string]: string } } = {} const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 4ae66cd01..5cb55cad6 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts @@ -10,6 +10,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { ActorModel } from '../../../models/activitypub/actor' import { Notifier } from '../../notifier' import { sequelizeTypescript } from '../../../initializers/database' +import { MAccount, MActor, MActorFollowActors, MActorFollowFull, MActorFull } from '../../../typings/models' export type ActivitypubFollowPayload = { followerActorId: number @@ -23,13 +24,13 @@ async function processActivityPubFollow (job: Bull.Job) { logger.info('Processing ActivityPub follow in job %d.', job.id) - let targetActor: ActorModel + let targetActor: MActorFull if (!host || host === WEBSERVER.HOST) { targetActor = await ActorModel.loadLocalByName(payload.name) } else { const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) - targetActor = await getOrCreateActorAndServerAndModel(actorUrl) + targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') } const fromActor = await ActorModel.load(payload.followerActorId) @@ -44,7 +45,7 @@ export { // --------------------------------------------------------------------------- -async function follow (fromActor: ActorModel, targetActor: ActorModel) { +async function follow (fromActor: MActor, targetActor: MActorFull) { if (fromActor.id === targetActor.id) { throw new Error('Follower is the same than target actor.') } @@ -53,7 +54,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' const actorFollow = await sequelizeTypescript.transaction(async t => { - const [ actorFollow ] = await ActorFollowModel.findOrCreate({ + const [ actorFollow ] = await ActorFollowModel.findOrCreate({ where: { actorId: fromActor.id, targetActorId: targetActor.id @@ -74,5 +75,14 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { 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) + } } diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index c3f59dc77..0182c5169 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts @@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account' import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { VideoShareModel } from '../../../models/video/video-share' import { VideoCommentModel } from '../../../models/video/video-comment' +import { MAccountDefault, MVideoFullLight } from '../../../typings/models' type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' @@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) { const payload = job.data as ActivitypubHttpFetcherPayload - let video: VideoModel + let video: MVideoFullLight if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) - let account: AccountModel + let account: MAccountDefault if (payload.accountId) account = await AccountModel.load(payload.accountId) const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise } = { diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index cdee1f6fd..d3bde6e6a 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts @@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils' import { ActorModel } from '../../../../models/activitypub/actor' import { sha256 } from '../../../../helpers/core-utils' import { HTTP_SIGNATURE } from '../../../../initializers/constants' +import { MActor } from '../../../../typings/models' type Payload = { body: any, signatureActorId?: number } @@ -19,7 +20,8 @@ async function computeBody (payload: Payload) { } async function buildSignedRequestOptions (payload: Payload) { - let actor: ActorModel | null + let actor: MActor | null + if (payload.signatureActorId) { actor = await ActorModel.load(payload.signatureActorId) if (!actor) throw new Error('Unknown signature actor id.') diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 8cacb0ef3..5c5b7dccb 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts @@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg import { copy, stat } from 'fs-extra' import { VideoFileModel } from '../../../models/video/video-file' import { extname } from 'path' +import { MVideoFile, MVideoWithFile } from '@server/typings/models' export type VideoFileImportPayload = { videoUUID: string, @@ -37,7 +38,7 @@ export { // --------------------------------------------------------------------------- -async function updateVideoFile (video: VideoModel, inputFilePath: string) { +async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { const { videoFileResolution } = await getVideoFileResolution(inputFilePath) const { size } = await stat(inputFilePath) const fps = await getVideoFileFPS(inputFilePath) @@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { size, fps, videoId: video.id - }) + }) as MVideoFile const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) @@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) // Update the database - currentVideoFile.set('extname', updatedVideoFile.extname) - currentVideoFile.set('size', updatedVideoFile.size) - currentVideoFile.set('fps', updatedVideoFile.fps) + currentVideoFile.extname = updatedVideoFile.extname + currentVideoFile.size = updatedVideoFile.size + currentVideoFile.fps = updatedVideoFile.fps updatedVideoFile = currentVideoFile } diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 13b741180..ff8c93328 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -17,9 +17,10 @@ import { move, remove, stat } from 'fs-extra' import { Notifier } from '../../notifier' import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' -import { ThumbnailModel } from '../../../models/video/thumbnail' import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' +import { MThumbnail } from '../../../typings/models/video/thumbnail' +import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' type VideoImportYoutubeDLPayload = { type: 'youtube-dl' @@ -110,7 +111,7 @@ type ProcessFileOptions = { generateThumbnail: boolean generatePreview: boolean } -async function processFile (downloader: () => Promise, videoImport: VideoImportModel, options: ProcessFileOptions) { +async function processFile (downloader: () => Promise, videoImport: MVideoImportDefault, options: ProcessFileOptions) { let tempVideoPath: string let videoDestFile: string let videoFile: VideoFileModel @@ -139,41 +140,44 @@ async function processFile (downloader: () => Promise, videoImport: Vide 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 @@ -188,25 +192,25 @@ async function processFile (downloader: () => Promise, videoImport: Vide await federateVideoIfNeeded(videoForFederation, true, t) // Update video import object - videoImport.state = VideoImportState.SUCCESS - const videoImportUpdated = await videoImport.save({ transaction: t }) + videoImportToUpdate.state = VideoImportState.SUCCESS + const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo + videoImportUpdated.Video = video logger.info('Video %s imported.', video.uuid) - videoImportUpdated.Video = videoForFederation - return videoImportUpdated + return { videoImportUpdated, video: videoForFederation } }) Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) - if (videoImportUpdated.Video.isBlacklisted()) { - Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) + if (video.isBlacklisted()) { + Notifier.Instance.notifyOnVideoAutoBlacklist(video) } else { - Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video) + Notifier.Instance.notifyOnNewVideoIfNeeded(video) } // Create transcoding jobs? - if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { + if (video.state === VideoState.TO_TRANSCODE) { // Put uuid because we don't have id auto incremented for now const dataInput = { type: 'optimize' as 'optimize', diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 981daf9a1..2ebe15bcb 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' import { Notifier } from '../../notifier' import { CONFIG } from '../../../initializers/config' +import { MVideoUUID, MVideoWithFile } from '@server/typings/models' interface BaseTranscodingPayload { videoUUID: string @@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) { return video } -async function onHlsPlaylistGenerationSuccess (video: VideoModel) { +async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) { if (video === undefined) return undefined await sequelizeTypescript.transaction(async t => { @@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) { }) } -async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { +async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { // Maybe the video changed in database, refresh it let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) @@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes await createHlsJobIfEnabled(payload) } -async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { +async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) { if (videoArg === undefined) return undefined // Outside the transaction (IO on disk) diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index a7dfb0979..23f76a21a 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts @@ -8,13 +8,23 @@ import { UserModel } from '../models/account/user' import { PeerTubeSocket } from './peertube-socket' import { CONFIG } from '../initializers/config' import { VideoPrivacy, VideoState } from '../../shared/models/videos' -import { VideoAbuseModel } from '../models/video/video-abuse' import { VideoBlacklistModel } from '../models/video/video-blacklist' import * as Bluebird from 'bluebird' import { VideoImportModel } from '../models/video/video-import' import { AccountBlocklistModel } from '../models/account/account-blocklist' +import { + MCommentOwnerVideo, + MVideo, + MVideoAbuseVideo, + MVideoAccountLight, + MVideoBlacklistVideo, + MVideoFullLight +} from '../typings/models/video' +import { MUser, MUserAccount, MUserWithNotificationSetting, UserNotificationModelForApi } from '@server/typings/models/user' +import { MActorFollowActors, MActorFollowFull, 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 { @@ -22,7 +32,7 @@ class Notifier { private constructor () {} - notifyOnNewVideoIfNeeded (video: VideoModel): void { + notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { // Only notify on public and published videos which are not blacklisted if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return @@ -30,7 +40,7 @@ class Notifier { .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) } - notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { + notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return @@ -38,7 +48,7 @@ class Notifier { .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) } - notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { + notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void { // don't notify if video is still blacklisted or waiting for transcoding if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return @@ -46,7 +56,7 @@ class Notifier { .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) } - notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { + notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void { // don't notify if video is still waiting for transcoding or scheduled update if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return @@ -54,7 +64,7 @@ class Notifier { .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length } - notifyOnNewComment (comment: VideoCommentModel): void { + notifyOnNewComment (comment: MCommentOwnerVideo): void { this.notifyVideoOwnerOfNewComment(comment) .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) @@ -62,37 +72,37 @@ class Notifier { .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) } - notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { + notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { this.notifyModeratorsOfNewVideoAbuse(videoAbuse) .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) } - notifyOnVideoAutoBlacklist (video: VideoModel): void { + notifyOnVideoAutoBlacklist (video: MVideo): void { this.notifyModeratorsOfVideoAutoBlacklist(video) .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) } - notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { + notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { this.notifyVideoOwnerOfBlacklist(videoBlacklist) .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) } - notifyOnVideoUnblacklist (video: VideoModel): void { + notifyOnVideoUnblacklist (video: MVideo): void { this.notifyVideoOwnerOfUnblacklist(video) .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) } - notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { + notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { this.notifyOwnerVideoImportIsFinished(videoImport, success) .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) } - notifyOnNewUserRegistration (user: UserModel): void { + notifyOnNewUserRegistration (user: MUserAccount): void { this.notifyModeratorsOfNewUserRegistration(user) .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) } - notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { + notifyOfNewUserFollow (actorFollow: MActorFollowFollowingFullFollowerAccount): void { this.notifyUserOfNewActorFollow(actorFollow) .catch(err => { logger.error( @@ -104,14 +114,14 @@ class Notifier { }) } - notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { + notifyOfNewInstanceFollow (actorFollow: MActorFollowActors): void { this.notifyAdminsOfNewInstanceFollow(actorFollow) .catch(err => { logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) }) } - private async notifySubscribersOfNewVideo (video: VideoModel) { + private async notifySubscribersOfNewVideo (video: MVideoAccountLight) { // List all followers that are users const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) @@ -127,7 +137,7 @@ class Notifier { userId: user.id, videoId: video.id }) - notification.Video = video + notification.Video = video as VideoModel return notification } @@ -139,7 +149,7 @@ class Notifier { return this.notify({ users, settingGetter, notificationCreator, emailSender }) } - private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { + private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) { if (comment.Video.isOwned() === false) return const user = await UserModel.loadByVideoId(comment.videoId) @@ -162,7 +172,7 @@ class Notifier { userId: user.id, commentId: comment.id }) - notification.Comment = comment + notification.Comment = comment as VideoCommentModel return notification } @@ -174,7 +184,7 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyOfCommentMention (comment: VideoCommentModel) { + private async notifyOfCommentMention (comment: MCommentOwnerVideo) { const extractedUsernames = comment.extractMentions() logger.debug( 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, @@ -209,7 +219,7 @@ class Notifier { userId: user.id, commentId: comment.id }) - notification.Comment = comment + notification.Comment = comment as VideoCommentModel return notification } @@ -221,7 +231,7 @@ class Notifier { return this.notify({ users, settingGetter, notificationCreator, emailSender }) } - private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) { + private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFollowingFullFollowerAccount) { if (actorFollow.ActorFollowing.isOwned() === false) return // Account follows one of our account? @@ -236,9 +246,6 @@ class Notifier { if (!user) return - if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) { - actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel - } const followerAccount = actorFollow.ActorFollower.Account const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) @@ -256,7 +263,7 @@ class Notifier { userId: user.id, actorFollowId: actorFollow.id }) - notification.ActorFollow = actorFollow + notification.ActorFollow = actorFollow as ActorFollowModel return notification } @@ -268,7 +275,7 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { + private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowActors) { const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) @@ -283,7 +290,7 @@ class Notifier { userId: user.id, actorFollowId: actorFollow.id }) - notification.ActorFollow = actorFollow + notification.ActorFollow = actorFollow as ActorFollowModel return notification } @@ -295,7 +302,7 @@ class Notifier { return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) } - private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { + private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) { const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) if (moderators.length === 0) return @@ -306,7 +313,7 @@ class Notifier { } async function notificationCreator (user: UserModel) { - const notification = await UserNotificationModel.create({ + const notification: UserNotificationModelForApi = await UserNotificationModel.create({ type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, userId: user.id, videoAbuseId: videoAbuse.id @@ -323,7 +330,7 @@ class Notifier { return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) } - private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { + private async notifyModeratorsOfVideoAutoBlacklist (video: MVideo) { const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) if (moderators.length === 0) return @@ -339,7 +346,7 @@ class Notifier { userId: user.id, videoId: video.id }) - notification.Video = video + notification.Video = video as VideoModel return notification } @@ -351,7 +358,7 @@ class Notifier { return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) } - private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { + private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) { const user = await UserModel.loadByVideoId(videoBlacklist.videoId) if (!user) return @@ -367,7 +374,7 @@ class Notifier { userId: user.id, videoBlacklistId: videoBlacklist.id }) - notification.VideoBlacklist = videoBlacklist + notification.VideoBlacklist = videoBlacklist as VideoBlacklistModel return notification } @@ -379,7 +386,7 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { + private async notifyVideoOwnerOfUnblacklist (video: MVideo) { const user = await UserModel.loadByVideoId(video.id) if (!user) return @@ -395,7 +402,7 @@ class Notifier { userId: user.id, videoId: video.id }) - notification.Video = video + notification.Video = video as VideoModel return notification } @@ -407,7 +414,7 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyOwnedVideoHasBeenPublished (video: VideoModel) { + private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) { const user = await UserModel.loadByVideoId(video.id) if (!user) return @@ -423,7 +430,7 @@ class Notifier { userId: user.id, videoId: video.id }) - notification.Video = video + notification.Video = video as VideoModel return notification } @@ -435,7 +442,7 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) { + private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) { const user = await UserModel.loadByVideoImportId(videoImport.id) if (!user) return @@ -451,7 +458,7 @@ class Notifier { userId: user.id, videoImportId: videoImport.id }) - notification.VideoImport = videoImport + notification.VideoImport = videoImport as VideoImportModel return notification } @@ -465,13 +472,13 @@ class Notifier { return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) } - private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) { + private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserAccount) { const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) if (moderators.length === 0) return logger.info( 'Notifying %s moderators of new user registration of %s.', - moderators.length, registeredUser.Account.Actor.preferredUsername + moderators.length, registeredUser.username ) function settingGetter (user: UserModel) { @@ -484,7 +491,7 @@ class Notifier { userId: user.id, accountId: registeredUser.Account.id }) - notification.Account = registeredUser.Account + notification.Account = registeredUser.Account as AccountModel return notification } @@ -497,10 +504,10 @@ class Notifier { } private async notify (options: { - users: UserModel[], - notificationCreator: (user: UserModel) => Promise, + users: MUserWithNotificationSetting[], + notificationCreator: (user: MUserWithNotificationSetting) => Promise, emailSender: (emails: string[]) => Promise | Bluebird, - settingGetter: (user: UserModel) => UserNotificationSettingValue + settingGetter: (user: MUserWithNotificationSetting) => UserNotificationSettingValue }) { const emails: string[] = [] @@ -521,7 +528,7 @@ class Notifier { } } - private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { + private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) { if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false return value & UserNotificationSettingValue.EMAIL diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index a1153e88a..086856f41 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts @@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants' import { Transaction } from 'sequelize' import { CONFIG } from '../initializers/config' import * as LRUCache from 'lru-cache' +import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } -const accessTokenCache = new LRUCache({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) +const accessTokenCache = new LRUCache({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) const userHavingToken = new LRUCache({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) // --------------------------------------------------------------------------- diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts index 17748fd18..26ced351f 100644 --- a/server/lib/peertube-socket.ts +++ b/server/lib/peertube-socket.ts @@ -1,8 +1,8 @@ import * as SocketIO from 'socket.io' import { authenticateSocket } from '../middlewares' -import { UserNotificationModel } from '../models/account/user-notification' import { logger } from '../helpers/logger' import { Server } from 'http' +import { UserNotificationModelForApi } from '@server/typings/models/user' class PeerTubeSocket { @@ -34,13 +34,14 @@ 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) } } diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index 04d3ded8f..1b4ecd7c0 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts @@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' import { sendUndoCacheFile } from './activitypub/send' import { Transaction } from 'sequelize' import { getServerActor } from '../helpers/utils' +import { MVideoRedundancyVideo } from '@server/typings/models' -async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) { +async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { const serverActor = await getServerActor() // Local cache, send undo to remote instances diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index cd70fd851..d326148d0 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts @@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } import { logger } from '../../helpers/logger' import { VideosRedundancy } from '../../../shared/models/redundancy' import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' -import { VideoFileModel } from '../../models/video/video-file' import { downloadWebTorrentVideo } from '../../helpers/webtorrent' import { join } from 'path' import { move } from 'fs-extra' @@ -12,16 +11,31 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' import { removeVideoRedundancy } from '../redundancy' import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' -import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' -import { VideoModel } from '../../models/video/video' import { downloadPlaylistSegments } from '../hls' import { CONFIG } from '../../initializers/config' +import { + MStreamingPlaylist, + MStreamingPlaylistVideo, + MVideoAccountLight, + MVideoFile, + MVideoFileVideo, + MVideoRedundancyFileVideo, + MVideoRedundancyStreamingPlaylistVideo, + MVideoRedundancyVideo, + MVideoWithAllFiles +} from '@server/typings/models' type CandidateToDuplicate = { redundancy: VideosRedundancy, - video: VideoModel, - files: VideoFileModel[], - streamingPlaylists: VideoStreamingPlaylistModel[] + video: MVideoWithAllFiles, + files: MVideoFile[], + streamingPlaylists: MStreamingPlaylist[] +} + +function isMVideoRedundancyFileVideo ( + o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo +): o is MVideoRedundancyFileVideo { + return !!(o as MVideoRedundancyFileVideo).VideoFile } export class VideosRedundancyScheduler extends AbstractScheduler { @@ -102,7 +116,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { } } - private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { + private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) { const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) // Redundancy strategy disabled, remove our redundancy instead of extending expiration if (!redundancy) { @@ -172,7 +186,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler { } } - private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { + private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { + const file = fileArg as MVideoFileVideo file.Video = video const serverActor = await getServerActor() @@ -187,7 +202,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { 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), @@ -203,7 +218,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler { logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) } - private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { + private async createStreamingPlaylistRedundancy ( + redundancy: VideosRedundancy, + video: MVideoAccountLight, + playlistArg: MStreamingPlaylist + ) { + const playlist = playlistArg as MStreamingPlaylistVideo playlist.Video = video const serverActor = await getServerActor() @@ -213,7 +233,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) - const createdModel = await VideoRedundancyModel.create({ + const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ expiresOn: this.buildNewExpiration(redundancy.minLifetime), url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), @@ -229,7 +249,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) } - private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { + private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) { logger.info('Extending expiration of %s.', redundancy.url) const serverActor = await getServerActor() @@ -243,7 +263,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { while (this.isTooHeavy(candidateToDuplicate)) { const redundancy = candidateToDuplicate.redundancy - const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) + const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime) if (!toDelete) return await removeVideoRedundancy(toDelete) @@ -263,19 +283,18 @@ export class VideosRedundancyScheduler extends AbstractScheduler { return new Date(Date.now() + expiresAfterMs) } - private buildEntryLogId (object: VideoRedundancyModel) { - if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` + private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) { + if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` return `${object.VideoStreamingPlaylist.playlistUrl}` } - private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { - const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size + private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) { + const fileReducer = (previous: number, current: MVideoFile) => previous + current.size const totalSize = files.reduce(fileReducer, 0) - if (playlists.length === 0) return totalSize - return totalSize * playlists.length + return totalSize + (totalSize * playlists.length) } private async loadAndRefreshVideo (videoUrl: string) { diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index a59773f5a..84791955e 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts @@ -1,20 +1,20 @@ -import { VideoFileModel } from '../models/video/video-file' import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' import { CONFIG } from '../initializers/config' -import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' -import { VideoModel } from '../models/video/video' +import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' import { ThumbnailModel } from '../models/video/thumbnail' import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' import { processImage } from '../helpers/image-utils' import { join } from 'path' import { downloadImage } from '../helpers/requests' -import { VideoPlaylistModel } from '../models/video/video-playlist' +import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist' +import { MVideoFile, MVideoThumbnail } from '../typings/models' +import { MThumbnail } from '../typings/models/video/thumbnail' type ImageSize = { height: number, width: number } function createPlaylistMiniatureFromExisting ( inputPath: string, - playlist: VideoPlaylistModel, + playlist: MVideoPlaylistThumbnail, automaticallyGenerated: boolean, keepOriginal = false, size?: ImageSize @@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting ( return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) } -function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { +function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) { const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) const type = ThumbnailType.MINIATURE @@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) } -function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { +function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) @@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: function createVideoMiniatureFromExisting ( inputPath: string, - video: VideoModel, + video: MVideoThumbnail, type: ThumbnailType, automaticallyGenerated: boolean, size?: ImageSize @@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting ( return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) } -function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { +function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) { const input = video.getVideoFilePath(videoFile) const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) @@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) } -function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { +function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() @@ -90,7 +90,7 @@ export { createPlaylistMiniatureFromExisting } -function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { +function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { const filename = playlist.generateThumbnailName() const basePath = CONFIG.STORAGE.THUMBNAILS_DIR @@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz } } -function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { +function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { const existingThumbnail = Array.isArray(video.Thumbnails) ? video.Thumbnails.find(t => t.type === type) : undefined @@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: { type: ThumbnailType, automaticallyGenerated?: boolean, fileUrl?: string, - existingThumbnail?: ThumbnailModel + existingThumbnail?: MThumbnail }) { const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters diff --git a/server/lib/user.ts b/server/lib/user.ts index 0e4007770..d84aff464 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts @@ -2,10 +2,8 @@ import * as uuidv4 from 'uuid/v4' 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' @@ -14,14 +12,17 @@ import { sequelizeTypescript } from '../initializers/database' 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 => { @@ -30,7 +31,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { 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({ @@ -43,22 +44,22 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { 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: { @@ -73,7 +74,7 @@ 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, @@ -82,7 +83,7 @@ async function createLocalAccountWithoutKeys (parameters: { actorId: actorInstanceCreated.id }) - const accountInstanceCreated = await accountInstance.save({ transaction: t }) + const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t }) accountInstanceCreated.Actor = actorInstanceCreated return accountInstanceCreated @@ -102,7 +103,7 @@ async function createApplicationActor (applicationId: number) { return accountCreated } -async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) { +async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString @@ -124,7 +125,7 @@ export { // --------------------------------------------------------------------------- -function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) { +function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) { const values: UserNotificationSetting & { userId: number } = { userId: user.id, newVideoFromSubscription: UserNotificationSettingValue.WEB, @@ -143,7 +144,7 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction return UserNotificationSettingModel.create(values, { transaction: t }) } -async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { +async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) { if (channelNames) return channelNames let channelName = user.username + '_channel' diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index bdaecd8e2..a0fc26e84 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts @@ -2,16 +2,15 @@ import { Transaction } from 'sequelize' import { CONFIG } from '../initializers/config' import { UserRight, VideoBlacklistType } from '../../shared/models' import { VideoBlacklistModel } from '../models/video/video-blacklist' -import { UserModel } from '../models/account/user' -import { VideoModel } from '../models/video/video' import { logger } from '../helpers/logger' import { UserAdminFlag } from '../../shared/models/users/user-flag.model' import { Hooks } from './plugins/hooks' import { Notifier } from './notifier' +import { MUser, MVideoBlacklist, MVideoWithBlacklistLight } from '@server/typings/models' async function autoBlacklistVideoIfNeeded (parameters: { - video: VideoModel, - user?: UserModel, + video: MVideoWithBlacklistLight, + user?: MUser, isRemote: boolean, isNew: boolean, notify?: boolean, @@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: { reason: 'Auto-blacklisted. Moderator review required.', type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED } - const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ + const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ where: { videoId: video.id }, @@ -49,10 +48,10 @@ async function autoBlacklistVideoIfNeeded (parameters: { } async function autoBlacklistNeeded (parameters: { - video: VideoModel, + video: MVideoWithBlacklistLight, isRemote: boolean, isNew: boolean, - user?: UserModel + user?: MUser }) { const { user, video, isRemote, isNew } = parameters diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index ee0482c36..41eab456b 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts @@ -1,12 +1,19 @@ import * as Sequelize from 'sequelize' import * as uuidv4 from 'uuid/v4' import { VideoChannelCreate } from '../../shared/models' -import { AccountModel } from '../models/account/account' import { VideoChannelModel } from '../models/video/video-channel' import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' import { VideoModel } from '../models/video/video' +import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' -async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { +type CustomVideoChannelModelAccount = MChannelDefault & + { Account?: T } + +async function createLocalVideoChannel ( + videoChannelInfo: VideoChannelCreate, + account: T, + t: Sequelize.Transaction +): Promise> { const uuid = uuidv4() const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) @@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account actorId: actorInstanceCreated.id } - const videoChannel = VideoChannelModel.build(videoChannelData) + const videoChannel = new VideoChannelModel(videoChannelData) const options = { transaction: t } - const videoChannelCreated = await videoChannel.save(options) + const videoChannelCreated: CustomVideoChannelModelAccount = await videoChannel.save(options) as MChannelDefault // Do not forget to add Account/Actor information to the created video channel videoChannelCreated.Account = account @@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account return videoChannelCreated } -async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { +async function federateAllVideosOfChannel (videoChannel: MChannelId) { const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) for (const videoId of videoIds) { @@ -47,6 +54,6 @@ async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { // --------------------------------------------------------------------------- export { - createVideoChannel, + createLocalVideoChannel, federateAllVideosOfChannel } diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index 449aa74cb..bb811bd2c 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts @@ -1,17 +1,16 @@ import * as Sequelize from 'sequelize' import { ResultList } from '../../shared/models' import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' -import { AccountModel } from '../models/account/account' -import { VideoModel } from '../models/video/video' import { VideoCommentModel } from '../models/video/video-comment' import { getVideoCommentActivityPubUrl } from './activitypub' import { sendCreateVideoComment } from './activitypub/send' +import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' async function createVideoComment (obj: { text: string, - inReplyToComment: VideoCommentModel | null, - video: VideoModel - account: AccountModel + inReplyToComment: MComment | null, + video: MVideoFullLight, + account: MAccountDefault }, t: Sequelize.Transaction) { let originCommentId: number | null = null let inReplyToCommentId: number | null = null @@ -32,7 +31,7 @@ async function createVideoComment (obj: { comment.url = getVideoCommentActivityPubUrl(obj.video, comment) - const savedComment = await comment.save({ transaction: t }) + const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t }) savedComment.InReplyToVideoComment = obj.inReplyToComment savedComment.Video = obj.video savedComment.Account = obj.account diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts index 6e214e60f..29b70cfda 100644 --- a/server/lib/video-playlist.ts +++ b/server/lib/video-playlist.ts @@ -1,12 +1,13 @@ import * as Sequelize from 'sequelize' -import { AccountModel } from '../models/account/account' import { VideoPlaylistModel } from '../models/video/video-playlist' import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' import { getVideoPlaylistActivityPubUrl } from './activitypub' import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' +import { MAccount } from '../typings/models' +import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist' -async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { - const videoPlaylist = new VideoPlaylistModel({ +async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) { + const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({ name: 'Watch later', privacy: VideoPlaylistPrivacy.PRIVATE, type: VideoPlaylistType.WATCH_LATER, diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index ba6b29163..a204c0c63 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts @@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra' import { logger } from '../helpers/logger' import { VideoResolution } from '../../shared/models/videos' import { VideoFileModel } from '../models/video/video-file' -import { VideoModel } from '../models/video/video' import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' import { CONFIG } from '../initializers/config' +import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models' /** * Optimize the original video file and replace it. The resolution is not changed. */ -async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { +async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) { const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const newExtname = '.mp4' @@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi /** * Transcode the original video file to a lower resolution. */ -async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { +async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) { const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const extname = '.mp4' @@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) } -async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { +async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) { const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const newExtname = '.mp4' @@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) } -async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { +async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) { const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) @@ -165,14 +165,14 @@ export { // --------------------------------------------------------------------------- -async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { +async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) { const stats = await stat(transcodingPath) const fps = await getVideoFileFPS(transcodingPath) await move(transcodingPath, outputPath) - videoFile.set('size', stats.size) - videoFile.set('fps', fps) + videoFile.size = stats.size + videoFile.fps = fps await video.createTorrentAndSetInfoHash(videoFile) diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index b1e5b5236..bea213d27 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts @@ -101,6 +101,8 @@ async function checkJsonLDSignature (req: Request, res: Response) { const verified = await isJsonLDSignatureVerified(actor, req.body) if (verified !== true) { + logger.warn('Signature not verified.', req.body) + res.sendStatus(403) return false } diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index c3d772297..788735663 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts @@ -10,6 +10,7 @@ import { areValidationErrors } from './utils' import { ActorModel } from '../../models/activitypub/actor' import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' +import { MActorFollowActorsDefault } from '@server/typings/models' const followValidator = [ body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), @@ -65,7 +66,7 @@ const getFollowerValidator = [ if (areValidationErrors(req, res)) return - let follow: ActorFollowModel + let follow: MActorFollowActorsDefault try { const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) const actor = await ActorModel.loadByUrl(actorUrl) diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index 1fdac0e4e..e65d3b8d3 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts @@ -24,7 +24,7 @@ const videoFileRedundancyGetValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - const video = res.locals.video + const video = res.locals.videoAll const videoFile = video.VideoFiles.find(f => { return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) }) @@ -50,7 +50,7 @@ const videoPlaylistRedundancyGetValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - const video = res.locals.video + const video = res.locals.videoAll const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 8ee2ec1f5..26f43cec7 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts @@ -31,6 +31,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins' import { isThemeRegistered } from '../../lib/plugins/theme-utils' import { doesVideoExist } from '../../helpers/middlewares' import { UserRole } from '../../../shared/models/users' +import { MUserDefault } from '@server/typings/models' const usersAddValidator = [ body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), @@ -462,7 +463,7 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: return true } -async function checkUserExist (finder: () => Bluebird, res: express.Response, abortResponse = true) { +async function checkUserExist (finder: () => Bluebird, res: express.Response, abortResponse = true) { const user = await finder() if (!user) { diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index e27d91bb1..a4aef4024 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts @@ -33,7 +33,7 @@ const videoAbuseGetValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return + if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return return next() } @@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return + if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return return next() } diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 3e8c5b30c..5440e57e7 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts @@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return + if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return return next() } @@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - const video = res.locals.video + const video = res.locals.videoAll if (req.body.unfederate === true && video.remote === true) { return res .status(409) @@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return + if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return return next() } diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index f5610222a..2fb1da5ce 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts @@ -26,7 +26,7 @@ const addVideoCaptionValidator = [ // Check if the user who did the request is able to update the video const user = res.locals.oauth.token.User - if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) + if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) return next() } @@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [ if (areValidationErrors(req, res)) return if (!await doesVideoExist(req.params.videoId, res)) return - if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return + if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return // Check if the user who did the request is able to update the video const user = res.locals.oauth.token.User - if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return + if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return return next() } diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 3ee5064fc..d21274527 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts @@ -7,13 +7,13 @@ import { isVideoChannelSupportValid } from '../../../helpers/custom-validators/video-channels' import { logger } from '../../../helpers/logger' -import { UserModel } from '../../../models/account/user' import { VideoChannelModel } from '../../../models/video/video-channel' import { areValidationErrors } from '../utils' import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' import { ActorModel } from '../../../models/activitypub/actor' import { isBooleanValid } from '../../../helpers/custom-validators/misc' import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' +import { MChannelAccountDefault, MUser } from '@server/typings/models' const videoChannelsAddValidator = [ body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), @@ -131,7 +131,7 @@ export { // --------------------------------------------------------------------------- -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.' }) diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 83a0c24b0..8adbb02ba 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts @@ -4,13 +4,13 @@ import { UserRight } from '../../../../shared' 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'), @@ -34,7 +34,7 @@ const listVideoThreadCommentsValidator = [ 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() } @@ -49,8 +49,8 @@ const addVideoCommentThreadValidator = [ 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() } @@ -66,9 +66,9 @@ const addVideoCommentReplyValidator = [ 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() } @@ -83,7 +83,7 @@ const videoCommentGetValidator = [ 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() } @@ -98,10 +98,10 @@ const removeVideoCommentValidator = [ 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() } @@ -120,7 +120,7 @@ export { // --------------------------------------------------------------------------- -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) { @@ -151,7 +151,7 @@ async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: 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) { @@ -170,11 +170,11 @@ async function doesVideoCommentExist (id: number, video: VideoModel, res: expres 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.' }) @@ -186,7 +186,7 @@ function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { 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) @@ -198,9 +198,9 @@ function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCom 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 } @@ -208,7 +208,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon 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, diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 5823795be..27ee62b1f 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts @@ -2,7 +2,6 @@ import * as express from 'express' 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' @@ -22,13 +21,14 @@ import { 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') @@ -67,9 +67,9 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ 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) } @@ -110,13 +110,13 @@ const videoPlaylistsDeleteValidator = [ 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 } @@ -124,45 +124,47 @@ const videoPlaylistsDeleteValidator = [ } ] -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') @@ -184,8 +186,8 @@ const videoPlaylistsAddVideoValidator = [ 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) { @@ -196,7 +198,7 @@ const videoPlaylistsAddVideoValidator = [ 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 } @@ -223,7 +225,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ 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) { @@ -265,7 +267,7 @@ const videoPlaylistElementAPGetValidator = [ return res.status(403).end() } - res.locals.videoPlaylistElement = videoPlaylistElement + res.locals.videoPlaylistElementAP = videoPlaylistElement return next() } @@ -289,7 +291,7 @@ const videoPlaylistsReorderVideosValidator = [ 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) @@ -388,7 +390,7 @@ function getCommonPlaylistEditAttributes () { ] 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.' }) @@ -410,3 +412,7 @@ function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoP return true } + +function getPlaylist (res: express.Response) { + return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary +} diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts index ace62be5c..20fc96243 100644 --- a/server/middlewares/validators/videos/video-shares.ts +++ b/server/middlewares/validators/videos/video-shares.ts @@ -16,7 +16,7 @@ const videosShareValidator = [ 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) { diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index af06f3c62..1449903b7 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts @@ -37,13 +37,14 @@ import { VideoModel } from '../../../models/video/video' 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') @@ -113,7 +114,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ // 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) @@ -122,7 +123,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ ]) 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() @@ -146,7 +147,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R }) } -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'), @@ -156,10 +157,11 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { 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 @@ -167,7 +169,7 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { // 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.' }) @@ -202,7 +204,7 @@ const videosRemoveValidator = [ 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() } @@ -218,7 +220,7 @@ const videosChangeOwnershipValidator = [ 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) { diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index d7cfe17f0..d50e6527f 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts @@ -18,6 +18,7 @@ const webfingerValidator = [ 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) @@ -25,7 +26,7 @@ const webfingerValidator = [ .end() } - res.locals.actor = actor + res.locals.actorFull = actor return next() } ] diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index d5746ad76..8bcaca828 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts @@ -3,6 +3,8 @@ import { AccountModel } from './account' 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' @@ -103,7 +105,7 @@ export class AccountBlocklistModel extends Model { }) } - static loadByAccountAndTarget (accountId: number, targetAccountId: number) { + static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird { const query = { where: { accountId, @@ -126,13 +128,13 @@ export class AccountBlocklistModel extends Model { return AccountBlocklistModel .scope([ ScopeNames.WITH_ACCOUNTS ]) - .findAndCountAll(query) + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) } - toFormattedJSON (): AccountBlock { + toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { return { byAccount: this.ByAccount.toFormattedJSON(), blockedAccount: this.BlockedAccount.toFormattedJSON(), diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 4bd8114cf..a6edbeee8 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts @@ -10,6 +10,13 @@ import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' 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. @@ -77,7 +84,7 @@ export class AccountVideoRateModel extends Model { }) Account: AccountModel - static load (accountId: number, videoId: number, transaction?: Transaction) { + static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird { const options: FindOptions = { where: { accountId, @@ -89,7 +96,7 @@ export class AccountVideoRateModel extends Model { 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 { const options: FindOptions = { where: { [ Op.or]: [ @@ -103,7 +110,7 @@ export class AccountVideoRateModel extends Model { ] } } - if (transaction) options.transaction = transaction + if (t) options.transaction = t return AccountVideoRateModel.findOne(options) } @@ -140,7 +147,12 @@ export class AccountVideoRateModel extends Model { 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 { const options: FindOptions = { where: { videoId, @@ -152,7 +164,7 @@ export class AccountVideoRateModel extends Model { required: true, include: [ { - attributes: [ 'id', 'url', 'preferredUsername' ], + attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ], model: ActorModel.unscoped(), required: true, where: { @@ -167,7 +179,7 @@ export class AccountVideoRateModel extends Model { } ] } - if (transaction) options.transaction = transaction + if (t) options.transaction = t return AccountVideoRateModel.findOne(options) } @@ -208,7 +220,7 @@ export class AccountVideoRateModel extends Model { ] } - return AccountVideoRateModel.findAndCountAll(query) + return AccountVideoRateModel.findAndCountAll(query) } static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { @@ -241,7 +253,7 @@ export class AccountVideoRateModel extends Model { }) } - toFormattedJSON (): AccountVideoRate { + toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate { return { video: this.Video.toFormattedJSON(), rating: this.type diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 4dc412301..394a55f5e 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -3,7 +3,8 @@ import { BeforeDestroy, BelongsTo, Column, - CreatedAt, DataType, + CreatedAt, + DataType, Default, DefaultScope, ForeignKey, @@ -31,6 +32,8 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ 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' @@ -229,11 +232,11 @@ export class AccountModel extends Model { return undefined } - static load (id: number, transaction?: Transaction) { + static load (id: number, transaction?: Transaction): Bluebird { return AccountModel.findByPk(id, { transaction }) } - static loadByNameWithHost (nameWithHost: string) { + static loadByNameWithHost (nameWithHost: string): Bluebird { const [ accountName, host ] = nameWithHost.split('@') if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) @@ -241,7 +244,7 @@ export class AccountModel extends Model { return AccountModel.loadByNameAndHost(accountName, host) } - static loadLocalByName (name: string) { + static loadLocalByName (name: string): Bluebird { const query = { where: { [ Op.or ]: [ @@ -271,7 +274,7 @@ export class AccountModel extends Model { return AccountModel.findOne(query) } - static loadByNameAndHost (name: string, host: string) { + static loadByNameAndHost (name: string, host: string): Bluebird { const query = { include: [ { @@ -296,7 +299,7 @@ export class AccountModel extends Model { return AccountModel.findOne(query) } - static loadByUrl (url: string, transaction?: Transaction) { + static loadByUrl (url: string, transaction?: Transaction): Bluebird { const query = { include: [ { @@ -329,7 +332,7 @@ export class AccountModel extends Model { }) } - static listLocalsForSitemap (sort: string) { + static listLocalsForSitemap (sort: string): Bluebird { const query = { attributes: [ ], offset: 0, @@ -350,7 +353,7 @@ export class AccountModel extends Model { .findAll(query) } - toFormattedJSON (): Account { + toFormattedJSON (this: MAccountFormattable): Account { const actor = this.Actor.toFormattedJSON() const account = { id: this.id, @@ -364,8 +367,8 @@ export class AccountModel extends Model { 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, @@ -377,7 +380,7 @@ export class AccountModel extends Model { } } - toActivityPubObject () { + toActivityPubObject (this: MAccountAP) { const obj = this.Actor.toActivityPubObject(this.name, 'Account') return Object.assign(obj, { diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts index c2fbc6d23..1506295cf 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/account/user-notification-setting.ts @@ -17,6 +17,7 @@ import { UserModel } from './user' 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', @@ -152,7 +153,7 @@ export class UserNotificationSettingModel extends Model { 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 @@ -436,7 +437,7 @@ export class UserNotificationModel extends Model { } } - private formatVideo (video: VideoModel) { + formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { return { id: video.id, uuid: video.uuid, @@ -444,7 +445,10 @@ export class UserNotificationModel extends Model { } } - private formatActor (accountOrChannel: AccountModel | VideoChannelModel) { + formatActor ( + this: UserNotificationModelForApi, + accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor + ) { const avatar = accountOrChannel.Actor.Avatar ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } : undefined diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts index a862fc45f..3fe4c8db1 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/account/user-video-history.ts @@ -1,7 +1,8 @@ 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', @@ -54,7 +55,7 @@ export class UserVideoHistoryModel extends Model { }) User: UserModel - static listForApi (user: UserModel, start: number, count: number) { + static listForApi (user: MUserAccountId, start: number, count: number) { return VideoModel.listForApi({ start, count, @@ -67,7 +68,7 @@ export class UserVideoHistoryModel extends Model { }) } - static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { + static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) { const query: DestroyOptions = { where: { userId: user.id diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 0041bf577..616dd603c 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -54,6 +54,14 @@ import { VideoImportModel } from '../video/video-import' 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' @@ -303,7 +311,7 @@ export class UserModel extends Model { }) } - static listWithRight (right: UserRight) { + static listWithRight (right: UserRight): Bluebird { const roles = Object.keys(USER_ROLE_LABELS) .map(k => parseInt(k, 10) as UserRole) .filter(role => hasUserRight(role, right)) @@ -319,7 +327,7 @@ export class UserModel extends Model { return UserModel.findAll(query) } - static listUserSubscribersOf (actorId: number) { + static listUserSubscribersOf (actorId: number): Bluebird { const query = { include: [ { @@ -358,7 +366,7 @@ export class UserModel extends Model { return UserModel.unscoped().findAll(query) } - static listByUsernames (usernames: string[]) { + static listByUsernames (usernames: string[]): Bluebird { const query = { where: { username: usernames @@ -368,11 +376,11 @@ export class UserModel extends Model { return UserModel.findAll(query) } - static loadById (id: number) { + static loadById (id: number): Bluebird { return UserModel.findByPk(id) } - static loadByUsername (username: string) { + static loadByUsername (username: string): Bluebird { const query = { where: { username: { [ Op.iLike ]: username } @@ -382,7 +390,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByUsernameAndPopulateChannels (username: string) { + static loadByUsernameAndPopulateChannels (username: string): Bluebird { const query = { where: { username: { [ Op.iLike ]: username } @@ -392,7 +400,7 @@ export class UserModel extends Model { return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) } - static loadByEmail (email: string) { + static loadByEmail (email: string): Bluebird { const query = { where: { email @@ -402,7 +410,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByUsernameOrEmail (username: string, email?: string) { + static loadByUsernameOrEmail (username: string, email?: string): Bluebird { if (!email) email = username const query = { @@ -414,7 +422,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByVideoId (videoId: number) { + static loadByVideoId (videoId: number): Bluebird { const query = { include: [ { @@ -445,7 +453,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByVideoImportId (videoImportId: number) { + static loadByVideoImportId (videoImportId: number): Bluebird { const query = { include: [ { @@ -462,7 +470,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByChannelActorId (videoChannelActorId: number) { + static loadByChannelActorId (videoChannelActorId: number): Bluebird { const query = { include: [ { @@ -486,7 +494,7 @@ export class UserModel extends Model { return UserModel.findOne(query) } - static loadByAccountActorId (accountActorId: number) { + static loadByAccountActorId (accountActorId: number): Bluebird { const query = { include: [ { @@ -503,7 +511,7 @@ export class UserModel extends Model { 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() @@ -511,7 +519,7 @@ export class UserModel extends Model { } // 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\'') @@ -552,7 +560,9 @@ export class UserModel extends Model { 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') diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 51b09e09b..c8b3aae9f 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts @@ -27,7 +27,14 @@ import { createSafeIn, getSort } from '../utils' 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', @@ -143,7 +150,7 @@ export class ActorFollowModel extends Model { 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 { const query = { where: { actorId, @@ -167,7 +174,12 @@ export class ActorFollowModel extends Model { 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 { const actorFollowingPartInclude: IncludeOptions = { model: ActorModel, required: true, @@ -220,7 +232,7 @@ export class ActorFollowModel extends Model { }) } - static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) { + static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird { const whereTab = targets .map(t => { if (t.host) { @@ -314,7 +326,7 @@ export class ActorFollowModel extends Model { ] } - return ActorFollowModel.findAndCountAll(query) + return ActorFollowModel.findAndCountAll(query) .then(({ rows, count }) => { return { data: rows, @@ -357,7 +369,7 @@ export class ActorFollowModel extends Model { ] } - return ActorFollowModel.findAndCountAll(query) + return ActorFollowModel.findAndCountAll(query) .then(({ rows, count }) => { return { data: rows, @@ -414,7 +426,7 @@ export class ActorFollowModel extends Model { ] } - return ActorFollowModel.findAndCountAll(query) + return ActorFollowModel.findAndCountAll(query) .then(({ rows, count }) => { return { data: rows.map(r => r.ActorFollowing.VideoChannel), @@ -569,7 +581,7 @@ export class ActorFollowModel extends Model { return ActorFollowModel.findAll(query) } - toFormattedJSON (): ActorFollow { + toFormattedJSON (this: MActorFollowFormattable): ActorFollow { const follower = this.ActorFollower.toFormattedJSON() const following = this.ActorFollowing.toFormattedJSON() diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 9cc53f78a..67a1b5bc1 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -36,6 +36,18 @@ import { isOutdated, throwIfNotValid } from '../utils' 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' @@ -163,8 +175,8 @@ export class ActorModel extends Model { @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 @@ -173,13 +185,13 @@ export class ActorModel extends Model { @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 @@ -252,11 +264,15 @@ export class ActorModel extends Model { }) VideoChannel: VideoChannelModel - static load (id: number) { + static load (id: number): Bluebird { return ActorModel.unscoped().findByPk(id) } - static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { + static loadFull (id: number): Bluebird { + return ActorModel.scope(ScopeNames.FULL).findByPk(id) + } + + static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird { const query = { include: [ { @@ -300,7 +316,7 @@ export class ActorModel extends Model { .then(a => !!a) } - static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { + static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird { const query = { where: { followersUrl: { @@ -313,7 +329,7 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findAll(query) } - static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { + static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird { const query = { where: { preferredUsername, @@ -325,7 +341,7 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findOne(query) } - static loadByNameAndHost (preferredUsername: string, host: string) { + static loadByNameAndHost (preferredUsername: string, host: string): Bluebird { const query = { where: { preferredUsername @@ -344,7 +360,7 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findOne(query) } - static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird { const query = { where: { url @@ -367,7 +383,7 @@ export class ActorModel extends Model { return ActorModel.unscoped().findOne(query) } - static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) { + static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird { const query = { where: { url @@ -387,27 +403,34 @@ export class ActorModel extends Model { }) } - 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' @@ -494,7 +517,7 @@ export class ActorModel extends Model { return this.serverId === null } - getWebfingerUrl () { + getWebfingerUrl (this: MActorServer) { return 'acct:' + this.preferredUsername + '@' + this.getHost() } @@ -502,7 +525,7 @@ export class ActorModel extends Model { return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername } - getHost () { + getHost (this: MActorHost) { return this.Server ? this.Server.host : WEBSERVER.HOST } diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts index b40144592..950e4b181 100644 --- a/server/models/avatar/avatar.ts +++ b/server/models/avatar/avatar.ts @@ -7,6 +7,7 @@ import { remove } from 'fs-extra' 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', @@ -57,7 +58,7 @@ export class AvatarModel extends Model { return AvatarModel.findOne(query) } - toFormattedJSON (): Avatar { + toFormattedJSON (this: MAvatarFormattable): Avatar { return { path: this.getStaticPath(), createdAt: this.createdAt, diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 903d551df..b680be237 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts @@ -18,6 +18,8 @@ import { Transaction } from 'sequelize' 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 @@ -160,7 +162,7 @@ export class OAuthTokenModel extends Model { }) } - static getByTokenAndPopulateUser (bearerToken: string) { + static getByTokenAndPopulateUser (bearerToken: string): Bluebird { const query = { where: { accessToken: bearerToken @@ -170,13 +172,13 @@ export class OAuthTokenModel extends Model { 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 { const query = { where: { refreshToken: refreshToken @@ -186,12 +188,9 @@ export class OAuthTokenModel extends Model { 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 }) }) } diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 3df1c4f9c..61d9a5612 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts @@ -30,6 +30,7 @@ import * as Bluebird from 'bluebird' 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' @@ -166,7 +167,7 @@ export class VideoRedundancyModel extends Model { return undefined } - static async loadLocalByFileId (videoFileId: number) { + static async loadLocalByFileId (videoFileId: number): Promise { const actor = await getServerActor() const query = { @@ -179,7 +180,7 @@ export class VideoRedundancyModel extends Model { return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) } - static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { + static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise { const actor = await getServerActor() const query = { @@ -192,7 +193,7 @@ export class VideoRedundancyModel extends Model { return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) } - static loadByUrl (url: string, transaction?: Transaction) { + static loadByUrl (url: string, transaction?: Transaction): Bluebird { const query = { where: { url @@ -306,7 +307,7 @@ export class VideoRedundancyModel extends Model { return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) } - static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) { + static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise { const expiredDate = new Date() expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) @@ -487,7 +488,7 @@ export class VideoRedundancyModel extends Model { return !!this.strategy } - toActivityPubObject (): CacheFileObject { + toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject { if (this.VideoStreamingPlaylist) { return { id: this.url, diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index a15f9a7e2..d094da1f5 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts @@ -11,6 +11,8 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type' 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: { @@ -85,7 +87,7 @@ export class PluginModel extends Model { @UpdatedAt updatedAt: Date - static listEnabledPluginsAndThemes () { + static listEnabledPluginsAndThemes (): Bluebird { const query = { where: { enabled: true, @@ -96,7 +98,7 @@ export class PluginModel extends Model { return PluginModel.findAll(query) } - static loadByNpmName (npmName: string) { + static loadByNpmName (npmName: string): Bluebird { const name = this.normalizePluginName(npmName) const type = this.getTypeFromNpmName(npmName) @@ -206,13 +208,13 @@ export class PluginModel extends Model { if (options.pluginType) query.where['type'] = options.pluginType return PluginModel - .findAndCountAll(query) + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) } - static listInstalled () { + static listInstalled (): Bluebird { const query = { where: { uninstalled: false @@ -251,7 +253,7 @@ export class PluginModel extends Model { return result } - toFormattedJSON (): PeerTubePlugin { + toFormattedJSON (this: MPluginFormattable): PeerTubePlugin { return { name: this.name, type: this.type, diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 5138b0f76..3e9687191 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts @@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' 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', @@ -73,7 +75,7 @@ export class ServerBlocklistModel extends Model { }) BlockedServer: ServerModel - static loadByAccountAndHost (accountId: number, host: string) { + static loadByAccountAndHost (accountId: number, host: string): Bluebird { const query = { where: { accountId @@ -104,13 +106,13 @@ export class ServerBlocklistModel extends Model { return ServerBlocklistModel .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) - .findAndCountAll(query) + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) } - toFormattedJSON (): ServerBlock { + toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock { return { byAccount: this.ByAccount.toFormattedJSON(), blockedServer: this.BlockedServer.toFormattedJSON(), diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 1d211f1e0..3b6759b5c 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts @@ -2,8 +2,9 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat 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', @@ -50,7 +51,7 @@ export class ServerModel extends Model { }) BlockedByAccounts: ServerBlocklistModel[] - static loadByHost (host: string) { + static loadByHost (host: string): Bluebird { const query = { where: { host @@ -64,7 +65,7 @@ export class ServerModel extends Model { return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 } - toFormattedJSON () { + toFormattedJSON (this: MServerFormattable) { return { host: this.host } diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index 603d55692..fc2a424aa 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts @@ -2,6 +2,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Ta 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', @@ -96,7 +97,7 @@ export class ScheduleVideoUpdateModel extends Model { return ScheduleVideoUpdateModel.destroy(query) } - toFormattedJSON () { + toFormattedJSON (this: MScheduleVideoUpdateFormattable) { return { updateAt: this.updateAt, privacy: this.privacy || undefined diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 0fc3cfd4c..ed8df8b48 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts @@ -1,11 +1,12 @@ 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', @@ -14,6 +15,10 @@ import { VideoPrivacy, VideoState } from '../../../shared/models/videos' { fields: [ 'name' ], unique: true + }, + { + name: 'tag_lower_name', + fields: [ fn('lower', col('name')) ] as any // FIXME: typings } ] }) @@ -37,10 +42,10 @@ export class TagModel extends Model { }) Videos: VideoModel[] - static findOrCreateTags (tags: string[], transaction: Transaction) { - if (tags === null) return [] + static findOrCreateTags (tags: string[], transaction: Transaction): Promise { + if (tags === null) return Promise.resolve([]) - const tasks: Bluebird[] = [] + const tasks: Bluebird[] = [] tags.forEach(tag => { const query = { where: { @@ -52,7 +57,7 @@ export class TagModel extends Model { transaction } - const promise = TagModel.findOrCreate(query) + const promise = TagModel.findOrCreate(query) .then(([ tagInstance ]) => tagInstance) tasks.push(promise) }) diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 1ac7919b3..3636db18d 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -7,10 +7,13 @@ import { 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', @@ -73,7 +76,7 @@ export class VideoAbuseModel extends Model { }) Video: VideoModel - static loadByIdAndVideoId (id: number, videoId: number) { + static loadByIdAndVideoId (id: number, videoId: number): Bluebird { const query = { where: { id, @@ -83,11 +86,25 @@ export class VideoAbuseModel extends Model { 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, @@ -106,7 +123,7 @@ export class VideoAbuseModel extends Model { }) } - toFormattedJSON (): VideoAbuse { + toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { return { id: this.id, reason: this.reason, @@ -125,7 +142,7 @@ export class VideoAbuseModel extends Model { } } - toActivityPubObject (): VideoAbuseObject { + toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { return { type: 'Flag' as 'Flag', content: this.reason, diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 22d949da0..b4df6cd6a 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts @@ -1,12 +1,14 @@ 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', @@ -99,7 +101,7 @@ export class VideoBlacklistModel extends Model { }) } - static loadByVideoId (id: number) { + static loadByVideoId (id: number): Bluebird { const query = { where: { videoId: id @@ -109,7 +111,7 @@ export class VideoBlacklistModel extends Model { return VideoBlacklistModel.findOne(query) } - toFormattedJSON (): VideoBlacklist { + toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist { return { id: this.id, createdAt: this.createdAt, diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index a01565851..ad5801768 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts @@ -21,6 +21,8 @@ import { join } from 'path' 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' @@ -30,7 +32,7 @@ export enum ScopeNames { [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { include: [ { - attributes: [ 'uuid', 'remote' ], + attributes: [ 'id', 'uuid', 'remote' ], model: VideoModel.unscoped(), required: true } @@ -93,7 +95,7 @@ export class VideoCaptionModel extends Model { return undefined } - static loadByVideoIdAndLanguage (videoId: string | number, language: string) { + static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird { const videoInclude = { model: VideoModel.unscoped(), attributes: [ 'id', 'remote', 'uuid' ], @@ -122,7 +124,7 @@ export class VideoCaptionModel extends Model { .then(([ caption ]) => caption) } - static listVideoCaptions (videoId: number) { + static listVideoCaptions (videoId: number): Bluebird { const query = { order: [ [ 'language', 'ASC' ] ] as OrderItem[], where: { @@ -152,7 +154,7 @@ export class VideoCaptionModel extends Model { return this.Video.remote === false } - toFormattedJSON (): VideoCaption { + toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption { return { language: { id: this.language, @@ -162,15 +164,15 @@ export class VideoCaptionModel extends Model { } } - 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()) } } diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index b545a2f8c..f7a351329 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts @@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' 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', @@ -108,16 +110,16 @@ export class VideoChangeOwnershipModel extends Model 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(query) ]).then(([ count, rows ]) => ({ total: count, data: rows })) } - static load (id: number) { + static load (id: number): Bluebird { return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) .findByPk(id) } - toFormattedJSON (): VideoChangeOwnership { + toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership { return { id: this.id, status: this.status, diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 6241a75a3..7178631b4 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -33,6 +33,15 @@ import { ServerModel } from '../server/server' 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[] = [ @@ -47,7 +56,7 @@ 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', @@ -74,10 +83,10 @@ export type SummaryOptions = { @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: [ @@ -106,7 +115,7 @@ export type SummaryOptions = { 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) @@ -268,7 +277,7 @@ export class VideoChannelModel extends Model { } const scopes = { - method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ] + method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ] } return VideoChannelModel .scope(scopes) @@ -278,7 +287,7 @@ export class VideoChannelModel extends Model { }) } - static listLocalsForSitemap (sort: string) { + static listLocalsForSitemap (sort: string): Bluebird { const query = { attributes: [ ], offset: 0, @@ -331,7 +340,7 @@ export class VideoChannelModel extends Model { } 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) @@ -369,13 +378,13 @@ export class VideoChannelModel extends Model { }) } - static loadByIdAndPopulateAccount (id: number) { + static loadByIdAndPopulateAccount (id: number): Bluebird { 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 { const query = { where: { id, @@ -388,13 +397,13 @@ export class VideoChannelModel extends Model { .findOne(query) } - static loadAndPopulateAccount (id: number) { + static loadAndPopulateAccount (id: number): Bluebird { return VideoChannelModel.unscoped() .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) .findByPk(id) } - static loadByUrlAndPopulateAccount (url: string) { + static loadByUrlAndPopulateAccount (url: string): Bluebird { const query = { include: [ { @@ -420,7 +429,7 @@ export class VideoChannelModel extends Model { return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) } - static loadLocalByNameAndPopulateAccount (name: string) { + static loadLocalByNameAndPopulateAccount (name: string): Bluebird { const query = { include: [ { @@ -439,7 +448,7 @@ export class VideoChannelModel extends Model { .findOne(query) } - static loadByNameAndHostAndPopulateAccount (name: string, host: string) { + static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird { const query = { include: [ { @@ -464,7 +473,7 @@ export class VideoChannelModel extends Model { .findOne(query) } - static loadAndPopulateAccountAndVideos (id: number) { + static loadAndPopulateAccountAndVideos (id: number): Bluebird { const options = { include: [ VideoModel @@ -476,7 +485,20 @@ export class VideoChannelModel extends Model { .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, @@ -494,20 +516,7 @@ export class VideoChannelModel extends Model { 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, { diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 58b75510d..2e4220434 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -1,36 +1,32 @@ -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', @@ -68,22 +64,7 @@ enum ScopeNames { [ScopeNames.WITH_ACCOUNT]: { include: [ { - model: AccountModel, - include: [ - { - model: ActorModel, - include: [ - { - model: ServerModel, - required: false - }, - { - model: AvatarModel, - required: false - } - ] - } - ] + model: AccountModel } ] }, @@ -102,22 +83,12 @@ enum ScopeNames { 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 } ] } @@ -212,7 +183,7 @@ export class VideoCommentModel extends Model { }) Account: AccountModel - static loadById (id: number, t?: Transaction) { + static loadById (id: number, t?: Transaction): Bluebird { const query: FindOptions = { where: { id @@ -224,7 +195,7 @@ export class VideoCommentModel extends Model { return VideoCommentModel.findOne(query) } - static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { + static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird { const query: FindOptions = { where: { id @@ -238,7 +209,7 @@ export class VideoCommentModel extends Model { .findOne(query) } - static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) { + static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird { const query: FindOptions = { where: { url @@ -250,7 +221,7 @@ export class VideoCommentModel extends Model { return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) } - static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) { + static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird { const query: FindOptions = { where: { url @@ -273,7 +244,7 @@ export class VideoCommentModel extends Model { start: number, count: number, sort: string, - user?: UserModel + user?: MUserAccountId }) { const { videoId, start, count, sort, user } = parameters @@ -314,7 +285,7 @@ export class VideoCommentModel extends Model { static async listThreadCommentsForApi (parameters: { videoId: number, threadId: number, - user?: UserModel + user?: MUserAccountId }) { const { videoId, threadId, user } = parameters @@ -353,7 +324,7 @@ export class VideoCommentModel extends Model { }) } - static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { + static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird { const query = { order: [ [ 'createdAt', order ] ] as Order, where: { @@ -389,10 +360,10 @@ export class VideoCommentModel extends Model { transaction: t } - return VideoCommentModel.findAndCountAll(query) + return VideoCommentModel.findAndCountAll(query) } - static listForFeed (start: number, count: number, videoId?: number) { + static listForFeed (start: number, count: number, videoId?: number): Bluebird { const query = { order: [ [ 'createdAt', 'DESC' ] ] as Order, offset: start, @@ -506,7 +477,7 @@ export class VideoCommentModel extends Model { return uniq(result) } - toFormattedJSON () { + toFormattedJSON (this: MCommentFormattable) { return { id: this.id, url: this.url, @@ -521,7 +492,7 @@ export class VideoCommentModel extends Model { } 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) { diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 05c490759..6304f741c 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts @@ -25,6 +25,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy' 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', @@ -166,7 +167,7 @@ export class VideoFileModel extends Model { 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 diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 284539def..2987aa780 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts @@ -1,6 +1,5 @@ import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' import { VideoModel } from './video' -import { VideoFileModel } from './video-file' import { ActivityPlaylistInfohashesObject, ActivityPlaylistSegmentHashesObject, @@ -17,7 +16,9 @@ import { } 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 @@ -28,7 +29,7 @@ export type VideoFormattingJSONOptions = { 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 = { @@ -102,7 +103,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting return videoObject } -function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { +function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails { const formattedJson = video.toFormattedJSON({ additionalAttributes: { scheduledUpdate: true, @@ -114,7 +115,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { 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, @@ -142,7 +143,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { return Object.assign(formattedJson, detailsJson) } -function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { +function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] { if (isArray(playlists) === false) return [] return playlists @@ -161,7 +162,7 @@ function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: V }) } -function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { +function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] { const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() return videoFiles @@ -189,7 +190,7 @@ function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFil }) } -function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { +function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() if (!video.Tags) video.Tags = [] diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 480a671c8..af5314ce9 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts @@ -20,6 +20,8 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help 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: [ @@ -28,7 +30,11 @@ import { UserModel } from '../account/user' 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 } ] @@ -114,7 +120,7 @@ export class VideoImportModel extends Model { return undefined } - static loadAndPopulateVideo (id: number) { + static loadAndPopulateVideo (id: number): Bluebird { return VideoImportModel.findByPk(id) } @@ -135,7 +141,7 @@ export class VideoImportModel extends Model { } } - return VideoImportModel.findAndCountAll(query) + return VideoImportModel.findAndCountAll(query) .then(({ rows, count }) => { return { data: rows, @@ -148,7 +154,7 @@ export class VideoImportModel extends Model { return this.targetUrl || this.magnetUri || this.torrentName } - toFormattedJSON (): VideoImport { + toFormattedJSON (this: MVideoImportFormattable): VideoImport { const videoFormatOptions = { completeDescription: true, additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index dd7653533..a28021313 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts @@ -21,10 +21,18 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 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', @@ -116,7 +124,7 @@ export class VideoPlaylistElementModel extends Model count: number, videoPlaylistId: number, serverAccount: AccountModel, - user?: UserModel + user?: MUserAccountId }) { const accountIds = [ options.serverAccount.id ] const videoScope: (ScopeOptions | string)[] = [ @@ -162,7 +170,7 @@ export class VideoPlaylistElementModel extends Model ]).then(([ total, data ]) => ({ total, data })) } - static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { + static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird { const query = { where: { videoPlaylistId, @@ -173,11 +181,14 @@ export class VideoPlaylistElementModel extends Model return VideoPlaylistElementModel.findOne(query) } - static loadById (playlistElementId: number) { + static loadById (playlistElementId: number): Bluebird { return VideoPlaylistElementModel.findByPk(playlistElementId) } - static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { + static loadByPlaylistAndVideoForAP ( + playlistId: number | string, + videoId: number | string + ): Bluebird { const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } @@ -218,7 +229,7 @@ export class VideoPlaylistElementModel extends Model }) } - static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) { + static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird { const query = { order: getSort('position'), where: { @@ -290,7 +301,7 @@ export class VideoPlaylistElementModel extends Model 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 @@ -306,14 +317,17 @@ export class VideoPlaylistElementModel extends Model 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, @@ -326,7 +340,7 @@ export class VideoPlaylistElementModel extends Model } } - toActivityPubObject (): PlaylistElementObject { + toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject { const base: PlaylistElementObject = { id: this.url, type: 'PlaylistElement', diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index c8e97c491..278d80ac0 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts @@ -43,6 +43,15 @@ import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video- 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', @@ -332,7 +341,7 @@ export class VideoPlaylistModel extends Model { }) } - static listPlaylistIdsOf (accountId: number, videoIds: number[]) { + static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird { const query = { attributes: [ 'id' ], where: { @@ -368,7 +377,7 @@ export class VideoPlaylistModel extends Model { .then(e => !!e) } - static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { + static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird { const where = buildWhereIdOrUUID(id) const query = { @@ -381,7 +390,7 @@ export class VideoPlaylistModel extends Model { .findOne(query) } - static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { + static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird { const where = buildWhereIdOrUUID(id) const query = { @@ -394,7 +403,7 @@ export class VideoPlaylistModel extends Model { .findOne(query) } - static loadByUrlAndPopulateAccount (url: string) { + static loadByUrlAndPopulateAccount (url: string): Bluebird { const query = { where: { url @@ -423,7 +432,7 @@ export class VideoPlaylistModel extends Model { 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 }) @@ -471,7 +480,7 @@ export class VideoPlaylistModel extends Model { return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) } - toFormattedJSON (): VideoPlaylist { + toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist { return { id: this.id, uuid: this.uuid, @@ -501,7 +510,7 @@ export class VideoPlaylistModel extends Model { } } - toActivityPubObject (page: number, t: Transaction): Promise { + toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise { const handler = (start: number, count: number) => { return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) } diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index d8ed64557..9019b401a 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -8,6 +8,8 @@ import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' 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', @@ -88,7 +90,7 @@ export class VideoShareModel extends Model { }) Video: VideoModel - static load (actorId: number, videoId: number, t?: Transaction) { + static load (actorId: number, videoId: number, t?: Transaction): Bluebird { return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ where: { actorId, @@ -98,7 +100,7 @@ export class VideoShareModel extends Model { }) } - static loadByUrl (url: string, t: Transaction) { + static loadByUrl (url: string, t: Transaction): Bluebird { return VideoShareModel.scope(ScopeNames.FULL).findOne({ where: { url @@ -107,7 +109,7 @@ export class VideoShareModel extends Model { }) } - static loadActorsByShare (videoId: number, t: Transaction) { + static loadActorsByShare (videoId: number, t: Transaction): Bluebird { const query = { where: { videoId @@ -122,10 +124,10 @@ export class VideoShareModel extends Model { } 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 { + static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird { const query = { attributes: [], include: [ @@ -163,7 +165,7 @@ export class VideoShareModel extends Model { .then(res => res.map(r => r.Actor)) } - static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird { + static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird { const query = { attributes: [], include: [ diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index 31dc82c54..0ea90d28c 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts @@ -1,16 +1,16 @@ -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', @@ -91,7 +91,7 @@ export class VideoStreamingPlaylistModel extends Model 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 @@ -165,7 +165,7 @@ export class VideoStreamingPlaylistModel extends Model 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) + ')' + ')' ) } @@ -460,14 +484,16 @@ export type AvailableForListIDsOptions = { } 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 + ')' ) } @@ -634,7 +660,7 @@ export type AvailableForListIDsOptions = { [ ScopeNames.WITH_BLACKLISTED ]: { include: [ { - attributes: [ 'id', 'reason' ], + attributes: [ 'id', 'reason', 'unfederated' ], model: VideoBlacklistModel, required: false } @@ -989,18 +1015,16 @@ export class VideoModel extends Model { 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) @@ -1039,7 +1063,7 @@ export class VideoModel extends Model { return undefined } - static listLocal () { + static listLocal (): Bluebird { const query = { where: { remote: false @@ -1159,7 +1183,7 @@ export class VideoModel extends Model { }) } - 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, @@ -1192,16 +1216,9 @@ export class VideoModel extends Model { 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(findQuery) ]).then(([ count, rows ]) => { return { data: rows, @@ -1228,8 +1245,8 @@ export class VideoModel extends Model { 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') @@ -1294,7 +1311,7 @@ export class VideoModel extends Model { tagsAllOf?: string[] durationMin?: number // seconds durationMax?: number // seconds - user?: UserModel, + user?: MUserAccountId, filter?: VideoFilter }) { const whereAnd = [] @@ -1387,7 +1404,7 @@ export class VideoModel extends Model { return VideoModel.getAvailableForApi(query, queryOptions) } - static load (id: number | string, t?: Transaction) { + static load (id: number | string, t?: Transaction): Bluebird { const where = buildWhereIdOrUUID(id) const options = { where, @@ -1397,7 +1414,20 @@ export class VideoModel extends Model { return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) } - static loadWithRights (id: number | string, t?: Transaction) { + static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird { + 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 { const where = buildWhereIdOrUUID(id) const options = { where, @@ -1411,7 +1441,7 @@ export class VideoModel extends Model { ]).findOne(options) } - static loadOnlyId (id: number | string, t?: Transaction) { + static loadOnlyId (id: number | string, t?: Transaction): Bluebird { const where = buildWhereIdOrUUID(id) const options = { @@ -1423,7 +1453,7 @@ export class VideoModel extends Model { 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 { const where = buildWhereIdOrUUID(id) const query = { @@ -1439,7 +1469,7 @@ export class VideoModel extends Model { ]).findOne(query) } - static loadByUUID (uuid: string) { + static loadByUUID (uuid: string): Bluebird { const options = { where: { uuid @@ -1449,7 +1479,7 @@ export class VideoModel extends Model { return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) } - static loadByUrl (url: string, transaction?: Transaction) { + static loadByUrl (url: string, transaction?: Transaction): Bluebird { const query: FindOptions = { where: { url @@ -1460,7 +1490,7 @@ export class VideoModel extends Model { return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) } - static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { + static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird { const query: FindOptions = { where: { url @@ -1472,11 +1502,12 @@ export class VideoModel extends Model { 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 { const where = buildWhereIdOrUUID(id) const options = { @@ -1508,7 +1539,7 @@ export class VideoModel extends Model { id: number | string, t?: Transaction, userId?: number - }) { + }): Bluebird { const { id, t, userId } = parameters const where = buildWhereIdOrUUID(id) @@ -1586,7 +1617,7 @@ export class VideoModel extends Model { .then(results => results.length === 1) } - static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) { + static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) { const options = { where: { channelId: videoChannel.id @@ -1597,7 +1628,7 @@ export class VideoModel extends Model { return VideoModel.update({ support: videoChannel.support }, options) } - static getAllIdsFromChannel (videoChannel: VideoChannelModel) { + static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird { const query = { attributes: [ 'id' ], where: { @@ -1756,20 +1787,20 @@ export class VideoModel extends Model { this.VideoChannel.Account.isBlocked() } - getOriginalFile () { + getOriginalFile (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 (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 }) @@ -1782,7 +1813,7 @@ export class VideoModel extends Model { this.Thumbnails.push(savedThumbnail) } - getVideoFilename (videoFile: VideoFileModel) { + getVideoFilename (videoFile: MVideoFile) { return this.uuid + '-' + videoFile.resolution + videoFile.extname } @@ -1806,7 +1837,7 @@ export class VideoModel extends Model { return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) } - getTorrentFileName (videoFile: VideoFileModel) { + getTorrentFileName (videoFile: MVideoFile) { const extension = '.torrent' return this.uuid + '-' + videoFile.resolution + extension } @@ -1815,15 +1846,15 @@ export class VideoModel extends Model { 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}`, @@ -1869,11 +1900,11 @@ export class VideoModel extends Model { 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) } @@ -1881,7 +1912,7 @@ export class VideoModel extends Model { return videoFilesModelToFormattedJSON(this, this.VideoFiles) } - toActivityPubObject (): VideoTorrentObject { + toActivityPubObject (this: MVideoAP): VideoTorrentObject { return videoModelToActivityPubObject(this) } @@ -1908,7 +1939,7 @@ export class VideoModel extends Model { 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)) @@ -1916,7 +1947,7 @@ export class VideoModel extends Model { .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 })) @@ -1957,7 +1988,7 @@ export class VideoModel extends Model { 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) ] @@ -1980,27 +2011,27 @@ export class VideoModel extends Model { 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) } } diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index 365d0e1ae..0d1f154fe 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts @@ -53,19 +53,6 @@ describe('Test activity pub helpers', function () { 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') diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts index c06200ffe..a3e05156b 100644 --- a/server/tests/api/search/search-videos.ts +++ b/server/tests/api/search/search-videos.ts @@ -206,7 +206,7 @@ describe('Test videos search', function () { 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) @@ -219,15 +219,15 @@ describe('Test videos search', function () { 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) }) diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index a2f3ee161..0cd6f22c7 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts @@ -17,6 +17,12 @@ import { } 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 @@ -163,13 +169,76 @@ describe('Test video abuses', function () { 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 () { diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index 3a3add71b..64ee2355a 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts @@ -191,7 +191,7 @@ describe('Test video change ownership - nominal', 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) diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 8599a270f..58e2445ac 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts @@ -5,6 +5,7 @@ import { root } from '../../shared/extra-utils/miscs/miscs' 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()}` @@ -119,6 +120,7 @@ function buildCommonVideoOptions (command: Command) { .option('-m, --comments-enabled', 'Enable comments') .option('-s, --support ', 'Video support text') .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') + .option('-v, --verbose ', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') } async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { @@ -175,11 +177,42 @@ function getServerCredentials (program: 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, diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index 0ebfa7442..fcb90cca3 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts @@ -8,10 +8,11 @@ import { CONSTRAINTS_FIELDS } from '../initializers/constants' 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 @@ -19,7 +20,6 @@ type UserInfo = { } const processOptions = { - cwd: __dirname, maxBuffer: Infinity } @@ -35,15 +35,23 @@ command .option('--target-url ', 'Video target URL') .option('--since ', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) .option('--until ', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) - .option('-v, --verbose', 'Verbose mode') + .option('--first ', 'Process first n elements of returned playlist') + .option('--last ', 'Process last n elements of returned playlist') + .option('-T, --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) @@ -53,8 +61,7 @@ getServerCredentials(command) run(url, user) .catch(err => { - console.error(err) - process.exit(-1) + exitError(err) }) }) @@ -68,30 +75,32 @@ async function run (url: string, user: UserInfo) { 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) }) } @@ -105,21 +114,21 @@ function processVideo (parameters: { 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(); } @@ -127,27 +136,27 @@ function processVideo (parameters: { 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, @@ -158,7 +167,7 @@ function processVideo (parameters: { return res() }) } catch (err) { - console.log(err.message) + log.error(err.message) return res() } }) @@ -217,7 +226,7 @@ async function uploadVideoOnPeerTube (parameters: { 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) @@ -225,21 +234,20 @@ async function uploadVideoOnPeerTube (parameters: { 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) } /* ---------------------------------------------------------- */ @@ -355,20 +363,17 @@ async function getAccessTokenOrDie (url: string, user: UserInfo) { 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; } @@ -376,3 +381,9 @@ function parseDate (dateAsStr: string): 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) +} diff --git a/server/typings/activitypub-processor.model.ts b/server/typings/activitypub-processor.model.ts index 37b2859de..7ed3a65b1 100644 --- a/server/typings/activitypub-processor.model.ts +++ b/server/typings/activitypub-processor.model.ts @@ -1,10 +1,9 @@ import { Activity } from '../../shared/models/activitypub' -import { ActorModel } from '../models/activitypub/actor' -import { SignatureActorModel } from './models' +import { MActorDefault, MActorSignature } from './models' export type APProcessorOptions = { activity: T - byActor: SignatureActorModel - inboxActor?: ActorModel + byActor: MActorSignature + inboxActor?: MActorDefault fromFetch?: boolean } diff --git a/server/typings/express.ts b/server/typings/express.ts index f7da55ab0..3cc7c7632 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts @@ -1,89 +1,103 @@ -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 } } } diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts new file mode 100644 index 000000000..c9cb55332 --- /dev/null +++ b/server/typings/models/account/account-blocklist.ts @@ -0,0 +1,25 @@ +import { AccountBlocklistModel } from '../../../models/account/account-blocklist' +import { PickWith } from '../../utils' +import { MAccountDefault, MAccountFormattable } from './account' + +type Use = PickWith + +// ############################################################################ + +export type MAccountBlocklist = Omit + +// ############################################################################ + +export type MAccountBlocklistId = Pick + +export type MAccountBlocklistAccounts = MAccountBlocklist & + Use<'ByAccount', MAccountDefault> & + Use<'BlockedAccount', MAccountDefault> + +// ############################################################################ + +// Format for API or AP object + +export type MAccountBlocklistFormattable = Pick & + Use<'ByAccount', MAccountFormattable> & + Use<'BlockedAccount', MAccountFormattable> diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts new file mode 100644 index 000000000..ec78fece8 --- /dev/null +++ b/server/typings/models/account/account.ts @@ -0,0 +1,95 @@ +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 = PickWith + +// ############################################################################ + +export type MAccount = Omit + +// ############################################################################ + +// Only some attributes +export type MAccountId = Pick +export type MAccountUserId = Pick + +// 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 & + Pick & + 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 & + Pick & + Use<'Actor', MActorSummaryFormattable> + +export type MAccountFormattable = FunctionProperties & + Pick & + Use<'Actor', MActorFormattable> + +export type MAccountAP = Pick & + Use<'Actor', MActorAP> diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts new file mode 100644 index 000000000..17a47b8df --- /dev/null +++ b/server/typings/models/account/actor-follow.ts @@ -0,0 +1,67 @@ +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 = PickWith + +// ############################################################################ + +export type MActorFollow = Omit + +// ############################################################################ + +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 + +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 & + Use<'ActorFollower', MActorFormattable> & + Use<'ActorFollowing', MActorFormattable> diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts new file mode 100644 index 000000000..d4bcac4a3 --- /dev/null +++ b/server/typings/models/account/actor.ts @@ -0,0 +1,121 @@ +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 = PickWith + +// ############################################################################ + +export type MActor = Omit + +// ############################################################################ + +export type MActorUrl = Pick +export type MActorId = Pick +export type MActorUsername = Pick + +export type MActorFollowersUrl = Pick +export type MActorAudience = MActorUrl & MActorFollowersUrl +export type MActorFollowerException = Pick +export type MActorSignature = MActorAccountChannelId + +export type MActorLight = Omit + +// ############################################################################ + +// Some association attributes + +export type MActorHost = Use<'Server', MServerHost> +export type MActorRedundancyAllowedOpt = PickWithOpt + +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 & + Pick & + Use<'Server', MServerHost> & + Use<'Avatar', MAvatar> + +export type MActorSummaryBlocks = MActorSummary & + Use<'Server', MServerHostBlocks> + +export type MActorAPI = Omit + +// ############################################################################ + +// Format for API or AP object + +export type MActorSummaryFormattable = FunctionProperties & + Pick & + Use<'Server', MServerHost> & + Use<'Avatar', MAvatarFormattable> + +export type MActorFormattable = MActorSummaryFormattable & + Pick & + Use<'Server', MServerHost & Partial>> + +export type MActorAP = MActor & + Use<'Avatar', MAvatar> diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts new file mode 100644 index 000000000..8af6cc787 --- /dev/null +++ b/server/typings/models/account/avatar.ts @@ -0,0 +1,11 @@ +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 & + Pick diff --git a/server/typings/models/account/index.d.ts b/server/typings/models/account/index.d.ts new file mode 100644 index 000000000..513c09c40 --- /dev/null +++ b/server/typings/models/account/index.d.ts @@ -0,0 +1,5 @@ +export * from './account' +export * from './account-blocklist' +export * from './actor' +export * from './actor-follow' +export * from './avatar' diff --git a/server/typings/models/actor-follow.ts b/server/typings/models/actor-follow.ts deleted file mode 100644 index 952ef877b..000000000 --- a/server/typings/models/actor-follow.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ActorFollowModel } from '../../models/activitypub/actor-follow' -import { ActorModelOnly } from './actor' - -export type ActorFollowModelOnly = Omit -export type ActorFollowModelLight = ActorFollowModelOnly & { - ActorFollower: ActorModelOnly - ActorFollowing: ActorModelOnly -} diff --git a/server/typings/models/actor.ts b/server/typings/models/actor.ts deleted file mode 100644 index 2656c7b66..000000000 --- a/server/typings/models/actor.ts +++ /dev/null @@ -1,22 +0,0 @@ -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 -export type AccountModelId = FunctionProperties | Pick - -export type VideoChannelModelIdActor = VideoChannelModelId & Pick -export type AccountModelIdActor = AccountModelId & Pick - -export type ActorModelUrl = Pick -export type ActorModelOnly = Omit -export type ActorModelId = Pick - -export type SignatureActorModel = ActorModelOnly & { - VideoChannel: VideoChannelModelIdActor - - Account: AccountModelIdActor -} - -export type ActorFollowerException = Pick diff --git a/server/typings/models/index.d.ts b/server/typings/models/index.d.ts index c90656965..78b4948ce 100644 --- a/server/typings/models/index.d.ts +++ b/server/typings/models/index.d.ts @@ -1 +1,5 @@ -export * from './actor' +export * from './account' +export * from './oauth' +export * from './server' +export * from './user' +export * from './video' diff --git a/server/typings/models/oauth/index.d.ts b/server/typings/models/oauth/index.d.ts new file mode 100644 index 000000000..36b7ea8ca --- /dev/null +++ b/server/typings/models/oauth/index.d.ts @@ -0,0 +1,2 @@ +export * from './oauth-client' +export * from './oauth-token' diff --git a/server/typings/models/oauth/oauth-client.ts b/server/typings/models/oauth/oauth-client.ts new file mode 100644 index 000000000..904a07863 --- /dev/null +++ b/server/typings/models/oauth/oauth-client.ts @@ -0,0 +1,3 @@ +import { OAuthClientModel } from '@server/models/oauth/oauth-client' + +export type MOAuthClient = Omit diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts new file mode 100644 index 000000000..af3412925 --- /dev/null +++ b/server/typings/models/oauth/oauth-token.ts @@ -0,0 +1,13 @@ +import { OAuthTokenModel } from '@server/models/oauth/oauth-token' +import { PickWith } from '@server/typings/utils' +import { MUserAccountUrl } from '@server/typings/models' + +type Use = PickWith + +// ############################################################################ + +export type MOAuthToken = Omit + +export type MOAuthTokenUser = MOAuthToken & + Use<'User', MUserAccountUrl> & + { user?: MUserAccountUrl } diff --git a/server/typings/models/server/index.d.ts b/server/typings/models/server/index.d.ts new file mode 100644 index 000000000..c853795ad --- /dev/null +++ b/server/typings/models/server/index.d.ts @@ -0,0 +1,3 @@ +export * from './plugin' +export * from './server' +export * from './server-blocklist' diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts new file mode 100644 index 000000000..94674c318 --- /dev/null +++ b/server/typings/models/server/plugin.ts @@ -0,0 +1,10 @@ +import { PluginModel } from '@server/models/server/plugin' + +export type MPlugin = PluginModel + +// ############################################################################ + +// Format for API or AP object + +export type MPluginFormattable = Pick diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts new file mode 100644 index 000000000..c81f604f5 --- /dev/null +++ b/server/typings/models/server/server-blocklist.ts @@ -0,0 +1,23 @@ +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 = PickWith + +// ############################################################################ + +export type MServerBlocklist = Omit + +// ############################################################################ + +export type MServerBlocklistAccountServer = MServerBlocklist & + Use<'ByAccount', MAccountDefault> & + Use<'BlockedServer', MServer> + +// ############################################################################ + +// Format for API or AP object + +export type MServerBlocklistFormattable = Pick & + Use<'ByAccount', MAccountFormattable> & + Use<'BlockedServer', MServerFormattable> diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts new file mode 100644 index 000000000..190cc0c28 --- /dev/null +++ b/server/typings/models/server/server.ts @@ -0,0 +1,24 @@ +import { ServerModel } from '../../../models/server/server' +import { FunctionProperties, PickWith } from '../../utils' +import { MAccountBlocklistId } from '../account' + +type Use = PickWith + +// ############################################################################ + +export type MServer = Omit + +// ############################################################################ + +export type MServerHost = Pick +export type MServerRedundancyAllowed = Pick + +export type MServerHostBlocks = MServerHost & + Use<'BlockedByAccounts', MAccountBlocklistId[]> + +// ############################################################################ + +// Format for API or AP object + +export type MServerFormattable = FunctionProperties & + Pick diff --git a/server/typings/models/user/index.d.ts b/server/typings/models/user/index.d.ts new file mode 100644 index 000000000..6657b2128 --- /dev/null +++ b/server/typings/models/user/index.d.ts @@ -0,0 +1,4 @@ +export * from './user' +export * from './user-notification' +export * from './user-notification-setting' +export * from './user-video-history' diff --git a/server/typings/models/user/user-notification-setting.ts b/server/typings/models/user/user-notification-setting.ts new file mode 100644 index 000000000..c674add1b --- /dev/null +++ b/server/typings/models/user/user-notification-setting.ts @@ -0,0 +1,9 @@ +import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting' + +export type MNotificationSetting = Omit + +// ############################################################################ + +// Format for API or AP object + +export type MNotificationSettingFormattable = MNotificationSetting diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts new file mode 100644 index 000000000..f9daf5eb2 --- /dev/null +++ b/server/typings/models/user/user-notification.ts @@ -0,0 +1,77 @@ +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 = PickWith + +// ############################################################################ + +export namespace UserNotificationIncludes { + export type VideoInclude = Pick + export type VideoIncludeChannel = VideoInclude & + PickWith + + export type ActorInclude = Pick & + PickWith> & + PickWith> + + export type VideoChannelInclude = Pick + export type VideoChannelIncludeActor = VideoChannelInclude & + PickWith + + export type AccountInclude = Pick + export type AccountIncludeActor = AccountInclude & + PickWith + + export type VideoCommentInclude = Pick & + PickWith & + PickWith + + export type VideoAbuseInclude = Pick & + PickWith + + export type VideoBlacklistInclude = Pick & + PickWith + + export type VideoImportInclude = Pick & + PickWith + + export type ActorFollower = Pick & + PickWith & + PickWith> & + PickWith> + + export type ActorFollowing = Pick & + PickWith & + PickWith + + export type ActorFollowInclude = Pick & + PickWith & + PickWith +} + +// ############################################################################ + +export type MUserNotification = Omit + +// ############################################################################ + +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> diff --git a/server/typings/models/user/user-video-history.ts b/server/typings/models/user/user-video-history.ts new file mode 100644 index 000000000..62673ab1b --- /dev/null +++ b/server/typings/models/user/user-video-history.ts @@ -0,0 +1,5 @@ +import { UserVideoHistoryModel } from '../../../models/account/user-video-history' + +export type MUserVideoHistory = Omit + +export type MUserVideoHistoryTime = Pick diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts new file mode 100644 index 000000000..52d6d4a05 --- /dev/null +++ b/server/typings/models/user/user.ts @@ -0,0 +1,70 @@ +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 = PickWith + +// ############################################################################ + +export type MUser = Omit + +// ############################################################################ + +export type MUserQuotaUsed = MUser & { videoQuotaUsed?: number, videoQuotaUsedDaily?: number } +export type MUserId = Pick + +// ############################################################################ + +// 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> & + PickWithOpt diff --git a/server/typings/models/video-share.ts b/server/typings/models/video-share.ts deleted file mode 100644 index 1406749d2..000000000 --- a/server/typings/models/video-share.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { VideoShareModel } from '../../models/video/video-share' - -export type VideoShareModelOnly = Omit diff --git a/server/typings/models/video/index.d.ts b/server/typings/models/video/index.d.ts new file mode 100644 index 000000000..bd69c8a4b --- /dev/null +++ b/server/typings/models/video/index.d.ts @@ -0,0 +1,18 @@ +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' diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts new file mode 100644 index 000000000..ada9af06e --- /dev/null +++ b/server/typings/models/video/schedule-video-update.ts @@ -0,0 +1,9 @@ +import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' + +export type MScheduleVideoUpdate = Omit + +// ############################################################################ + +// Format for API or AP object + +export type MScheduleVideoUpdateFormattable = Pick diff --git a/server/typings/models/video/tag.ts b/server/typings/models/video/tag.ts new file mode 100644 index 000000000..64a68873e --- /dev/null +++ b/server/typings/models/video/tag.ts @@ -0,0 +1,3 @@ +import { TagModel } from '../../../models/video/tag' + +export type MTag = Omit diff --git a/server/typings/models/video/thumbnail.ts b/server/typings/models/video/thumbnail.ts new file mode 100644 index 000000000..c03ba55ac --- /dev/null +++ b/server/typings/models/video/thumbnail.ts @@ -0,0 +1,3 @@ +import { ThumbnailModel } from '../../../models/video/thumbnail' + +export type MThumbnail = Omit diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts new file mode 100644 index 000000000..e38c3f586 --- /dev/null +++ b/server/typings/models/video/video-abuse.ts @@ -0,0 +1,31 @@ +import { VideoAbuseModel } from '../../../models/video/video-abuse' +import { PickWith } from '../../utils' +import { MVideo } from './video' +import { MAccountDefault, MAccountFormattable } from '../account' + +type Use = PickWith + +// ############################################################################ + +export type MVideoAbuse = Omit + +// ############################################################################ + +export type MVideoAbuseId = Pick + +export type MVideoAbuseVideo = MVideoAbuse & + Pick & + Use<'Video', MVideo> + +export type MVideoAbuseAccountVideo = MVideoAbuse & + Pick & + Use<'Video', MVideo> & + Use<'Account', MAccountDefault> + +// ############################################################################ + +// Format for API or AP object + +export type MVideoAbuseFormattable = MVideoAbuse & + Use<'Account', MAccountFormattable> & + Use<'Video', Pick> diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts new file mode 100644 index 000000000..1dedfa37f --- /dev/null +++ b/server/typings/models/video/video-blacklist.ts @@ -0,0 +1,24 @@ +import { VideoBlacklistModel } from '../../../models/video/video-blacklist' +import { PickWith } from '@server/typings/utils' +import { MVideo, MVideoFormattable } from '@server/typings/models' + +type Use = PickWith + +// ############################################################################ + +export type MVideoBlacklist = Omit + +export type MVideoBlacklistLight = Pick +export type MVideoBlacklistUnfederated = Pick + +// ############################################################################ + +export type MVideoBlacklistVideo = MVideoBlacklist & + Use<'Video', MVideo> + +// ############################################################################ + +// Format for API or AP object + +export type MVideoBlacklistFormattable = MVideoBlacklist & + Use<'Video', MVideoFormattable> diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts new file mode 100644 index 000000000..7cb2a2ad3 --- /dev/null +++ b/server/typings/models/video/video-caption.ts @@ -0,0 +1,24 @@ +import { VideoCaptionModel } from '../../../models/video/video-caption' +import { FunctionProperties, PickWith } from '@server/typings/utils' +import { MVideo, MVideoUUID } from '@server/typings/models' + +type Use = PickWith + +// ############################################################################ + +export type MVideoCaption = Omit + +// ############################################################################ + +export type MVideoCaptionLanguage = Pick + +export type MVideoCaptionVideo = MVideoCaption & + Use<'Video', Pick> + +// ############################################################################ + +// Format for API or AP object + +export type MVideoCaptionFormattable = FunctionProperties & + Pick & + Use<'Video', MVideoUUID> diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts new file mode 100644 index 000000000..72634cdb2 --- /dev/null +++ b/server/typings/models/video/video-change-ownership.ts @@ -0,0 +1,23 @@ +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 = PickWith + +// ############################################################################ + +export type MVideoChangeOwnership = Omit + +export type MVideoChangeOwnershipFull = MVideoChangeOwnership & + Use<'Initiator', MAccountDefault> & + Use<'NextOwner', MAccountDefault> & + Use<'Video', MVideoWithFileThumbnail> + +// ############################################################################ + +// Format for API or AP object + +export type MVideoChangeOwnershipFormattable = Pick & + Use<'Initiator', MAccountFormattable> & + Use<'NextOwner', MAccountFormattable> & + Use<'Video', Pick> diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts new file mode 100644 index 000000000..292d0ac95 --- /dev/null +++ b/server/typings/models/video/video-channels.ts @@ -0,0 +1,126 @@ +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 = PickWith + +// ############################################################################ + +export type MChannel = Omit + +// ############################################################################ + +export type MChannelId = Pick + +// ############################################################################ + +export type MChannelIdActor = MChannelId & + Use<'Actor', MActorAccountChannelId> + +export type MChannelUserId = Pick & + 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 & + Pick & + 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 & + Pick & + Use<'Actor', MActorSummaryFormattable> + +export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & + Use<'Account', MAccountSummaryFormattable> + +export type MChannelFormattable = FunctionProperties & + Pick & + Use<'Actor', MActorFormattable> & + PickWithOpt + +export type MChannelAP = Pick & + Use<'Actor', MActorAP> & + Use<'Account', MAccountUrl> diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts new file mode 100644 index 000000000..4fd1c29e8 --- /dev/null +++ b/server/typings/models/video/video-comment.ts @@ -0,0 +1,57 @@ +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 = PickWith + +// ############################################################################ + +export type MComment = Omit +export type MCommentTotalReplies = MComment & { totalReplies?: number } +export type MCommentId = Pick +export type MCommentUrl = Pick + +// ############################################################################ + +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 & + PickWithOpt diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts new file mode 100644 index 000000000..484351a8d --- /dev/null +++ b/server/typings/models/video/video-file.ts @@ -0,0 +1,19 @@ +import { VideoFileModel } from '../../../models/video/video-file' +import { PickWith, PickWithOpt } from '../../utils' +import { MVideo, MVideoUUID } from './video' +import { MVideoRedundancyFileUrl } from './video-redundancy' + +type Use = PickWith + +// ############################################################################ + +export type MVideoFile = Omit + +export type MVideoFileVideo = MVideoFile & + Use<'Video', MVideo> + +export type MVideoFileVideoUUID = MVideoFile & + Use<'Video', MVideoUUID> + +export type MVideoFileRedundanciesOpt = MVideoFile & + PickWithOpt diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts new file mode 100644 index 000000000..c6a1c5b66 --- /dev/null +++ b/server/typings/models/video/video-import.ts @@ -0,0 +1,31 @@ +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 = PickWith + +// ############################################################################ + +export type MVideoImport = Omit + +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 diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts new file mode 100644 index 000000000..7b1b993ce --- /dev/null +++ b/server/typings/models/video/video-playlist-element.ts @@ -0,0 +1,34 @@ +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 = PickWith + +// ############################################################################ + +export type MVideoPlaylistElement = Omit + +// ############################################################################ + +export type MVideoPlaylistElementId = Pick + +export type MVideoPlaylistElementLight = Pick + +// ############################################################################ + +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> diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts new file mode 100644 index 000000000..a40c7aca9 --- /dev/null +++ b/server/typings/models/video/video-playlist.ts @@ -0,0 +1,92 @@ +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 = PickWith + +// ############################################################################ + +export type MVideoPlaylist = Omit + +// ############################################################################ + +export type MVideoPlaylistId = Pick +export type MVideoPlaylistPrivacy = Pick +export type MVideoPlaylistUUID = Pick +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> diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts new file mode 100644 index 000000000..2ff8a625b --- /dev/null +++ b/server/typings/models/video/video-rate.ts @@ -0,0 +1,23 @@ +import { AccountVideoRateModel } from '@server/models/account/account-video-rate' +import { PickWith } from '@server/typings/utils' +import { MAccountAudience, MAccountUrl, MVideo, MVideoFormattable } from '..' + +type Use = PickWith + +// ############################################################################ + +export type MAccountVideoRate = Omit + +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 & + Use<'Video', MVideoFormattable> diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts new file mode 100644 index 000000000..f3846afd7 --- /dev/null +++ b/server/typings/models/video/video-redundancy.ts @@ -0,0 +1,38 @@ +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 = PickWith + +// ############################################################################ + +export type MVideoRedundancy = Omit + +export type MVideoRedundancyFileUrl = Pick + +// ############################################################################ + +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> & + PickWithOpt> diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts new file mode 100644 index 000000000..a7a90beeb --- /dev/null +++ b/server/typings/models/video/video-share.ts @@ -0,0 +1,17 @@ +import { VideoShareModel } from '../../../models/video/video-share' +import { PickWith } from '../../utils' +import { MActorDefault } from '../account' +import { MVideo } from './video' + +type Use = PickWith + +// ############################################################################ + +export type MVideoShare = Omit + +export type MVideoShareActor = MVideoShare & + Use<'Actor', MActorDefault> + +export type MVideoShareFull = MVideoShare & + Use<'Actor', MActorDefault> & + Use<'Video', MVideo> diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts new file mode 100644 index 000000000..79696bcff --- /dev/null +++ b/server/typings/models/video/video-streaming-playlist.ts @@ -0,0 +1,19 @@ +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 = PickWith + +// ############################################################################ + +export type MStreamingPlaylist = Omit + +export type MStreamingPlaylistVideo = MStreamingPlaylist & + Use<'Video', MVideo> + +export type MStreamingPlaylistRedundancies = MStreamingPlaylist & + Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> + +export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & + PickWithOpt diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts new file mode 100644 index 000000000..9a53bd337 --- /dev/null +++ b/server/typings/models/video/video.ts @@ -0,0 +1,173 @@ +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 = PickWith + +// ############################################################################ + +export type MVideo = Omit + +// ############################################################################ + +export type MVideoId = Pick +export type MVideoUrl = Pick +export type MVideoUUID = Pick + +export type MVideoIdUrl = MVideoId & MVideoUrl +export type MVideoFeed = Pick + +// ############################################################################ + +// 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 + +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 + +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 & + Use<'VideoChannel', MChannelAccountSummaryFormattable> & + PickWithOpt> & + PickWithOpt> + +export type MVideoFormattableDetails = MVideoFormattable & + Use<'VideoChannel', MChannelFormattable> & + Use<'Tags', MTag[]> & + Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & + Use<'VideoFiles', MVideoFileRedundanciesOpt[]> diff --git a/server/typings/utils.ts b/server/typings/utils.ts index a86b05be2..4b5cf4d7e 100644 --- a/server/typings/utils.ts +++ b/server/typings/utils.ts @@ -1,3 +1,13 @@ -export type FunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] +export type FunctionPropertyNames = { + [K in keyof T]: T[K] extends Function ? K : never +}[keyof T] export type FunctionProperties = Pick> + +export type PickWith = { + [P in KT]: T[P] extends V ? V : never +} + +export type PickWithOpt = { + [P in KT]?: T[P] extends V ? V : never +} diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 95801190d..492b672c7 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts @@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity { export interface ActivityFlag extends BaseActivity { type: 'Flag', content: string, - object: APObject + object: APObject | APObject[] } diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts index 40e7abd57..5f1264a76 100644 --- a/shared/models/activitypub/objects/video-abuse-object.ts +++ b/shared/models/activitypub/objects/video-abuse-object.ts @@ -1,5 +1,5 @@ export interface VideoAbuseObject { type: 'Flag', content: string - object: string + object: string | string[] } diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 24ffdbc15..69717525d 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml @@ -130,9 +130,6 @@ paths: 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 @@ -204,6 +201,10 @@ paths: tags: - Accounts summary: Get all accounts + parameters: + - $ref: '#/components/parameters/start' + - $ref: '#/components/parameters/count' + - $ref: '#/components/parameters/sort' responses: '200': description: successful operation @@ -233,6 +234,10 @@ paths: responses: '200': description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ServerConfigAbout' /config/custom: get: summary: Get the runtime configuration of the server @@ -244,6 +249,10 @@ paths: responses: '200': description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ServerConfigCustom' put: summary: Set the runtime configuration of the server tags: @@ -726,8 +735,7 @@ paths: type: string format: binary encoding: - profileImage: - # only accept png/jpeg + avatarfile: contentType: image/png, image/jpeg /videos: get: @@ -829,9 +837,11 @@ paths: thumbnailfile: description: Video thumbnail file type: string + format: binary previewfile: description: Video preview file type: string + format: binary category: description: Video category type: string @@ -874,6 +884,11 @@ paths: 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: @@ -1029,9 +1044,11 @@ paths: thumbnailfile: description: Video thumbnail file type: string + format: binary previewfile: description: Video preview file type: string + format: binary privacy: $ref: '#/components/schemas/VideoPrivacySet' category: @@ -1080,6 +1097,13 @@ paths: - 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: | @@ -1142,9 +1166,11 @@ paths: thumbnailfile: description: Video thumbnail file type: string + format: binary previewfile: description: Video preview file type: string + format: binary privacy: $ref: '#/components/schemas/VideoPrivacySet' category: @@ -1188,6 +1214,13 @@ paths: 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 @@ -1308,6 +1341,9 @@ paths: 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' @@ -1952,7 +1988,7 @@ components: description: 'Video file size in bytes' torrentUrl: type: string - torrentDownaloadUrl: + torrentDownloadUrl: type: string fileUrl: type: string @@ -2227,8 +2263,6 @@ components: properties: id: type: number - uuid: - type: string url: type: string name: @@ -2249,8 +2283,12 @@ components: allOf: - $ref: '#/components/schemas/Actor' - properties: + userId: + type: string displayName: type: string + description: + type: string User: properties: id: @@ -2294,18 +2332,102 @@ components: 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: @@ -2324,6 +2446,18 @@ components: 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: @@ -2331,6 +2465,202 @@ components: 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: diff --git a/support/doc/tools.md b/support/doc/tools.md index cf427ec84..dd2a03db7 100644 --- a/support/doc/tools.md +++ b/support/doc/tools.md @@ -11,6 +11,7 @@ - [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) @@ -19,6 +20,7 @@ - [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) @@ -182,6 +184,22 @@ It provides support for different players: - 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. @@ -262,22 +280,22 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production 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)) diff --git a/tsconfig.json b/tsconfig.json index 4d2bdd6ba..5ed870c5c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,11 +14,15 @@ "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", diff --git a/yarn.lock b/yarn.lock index f26763845..b5a3a3f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -828,24 +828,6 @@ bindings@~1.3.0: 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" @@ -968,16 +950,6 @@ bluebird@^3.0.5, bluebird@^3.5.0: 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" @@ -1043,11 +1015,6 @@ braces@^3.0.1: 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" @@ -1058,11 +1025,6 @@ browserify-package-json@^1.0.0: 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" @@ -1076,16 +1038,6 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: 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" @@ -2096,13 +2048,6 @@ ecc-jsbn@~0.1.1: 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" @@ -2113,16 +2058,6 @@ elegant-spinner@^1.0.1: 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" @@ -3250,14 +3185,6 @@ has@^1.0.1, has@^1.0.3: 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" @@ -3481,11 +3408,6 @@ inherits@2.0.3: 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" @@ -4028,25 +3950,6 @@ jsonify@~0.0.0: 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" @@ -4082,23 +3985,6 @@ junk@^3.1.0: 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" @@ -4335,11 +4221,6 @@ lodash@4.17.4: 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" @@ -4638,11 +4519,6 @@ mimic-response@^1.0.0: 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"