From 7ad9b9846c44d198a736183fb186c2039f5236b5 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 12 Oct 2018 15:26:04 +0200 Subject: Add ability for users to block an account/instance on server side --- server/models/video/video-comment.ts | 95 ++++++++++++++++++++++++++++-------- server/models/video/video.ts | 40 ++++++++++----- 2 files changed, 104 insertions(+), 31 deletions(-) (limited to 'server/models/video') diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index f84c1880c..08c6b3ff0 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -1,6 +1,17 @@ import * as Sequelize from 'sequelize' import { - AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, + AllowNull, + BeforeDestroy, + BelongsTo, + Column, + CreatedAt, + DataType, + ForeignKey, + IFindOptions, + Is, + Model, + Scopes, + Table, UpdatedAt } from 'sequelize-typescript' import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' @@ -13,9 +24,11 @@ import { AccountModel } from '../account/account' import { ActorModel } from '../activitypub/actor' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' -import { getSort, throwIfNotValid } from '../utils' +import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' import { VideoChannelModel } from './video-channel' +import { getServerActor } from '../../helpers/utils' +import { UserModel } from '../account/user' enum ScopeNames { WITH_ACCOUNT = 'WITH_ACCOUNT', @@ -25,18 +38,29 @@ enum ScopeNames { } @Scopes({ - [ScopeNames.ATTRIBUTES_FOR_API]: { - attributes: { - include: [ - [ - Sequelize.literal( - '(SELECT COUNT("replies"."id") ' + - 'FROM "videoComment" AS "replies" ' + - 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id")' - ), - 'totalReplies' + [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { + return { + attributes: { + include: [ + [ + Sequelize.literal( + '(' + + 'WITH "blocklist" AS (' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + + 'SELECT COUNT("replies"."id") - (' + + 'SELECT COUNT("replies"."id") ' + + 'FROM "videoComment" AS "replies" ' + + 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' + + 'AND "accountId" IN (SELECT "id" FROM "blocklist")' + + ')' + + 'FROM "videoComment" AS "replies" ' + + 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' + + 'AND "accountId" NOT IN (SELECT "id" FROM "blocklist")' + + ')' + ), + 'totalReplies' + ] ] - ] + } } }, [ScopeNames.WITH_ACCOUNT]: { @@ -267,26 +291,47 @@ export class VideoCommentModel extends Model { return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) } - static listThreadsForApi (videoId: number, start: number, count: number, sort: string) { + static async listThreadsForApi (videoId: number, start: number, count: number, sort: string, user?: UserModel) { + const serverActor = await getServerActor() + const serverAccountId = serverActor.Account.id + const userAccountId = user.Account.id + const query = { offset: start, limit: count, order: getSort(sort), where: { videoId, - inReplyToCommentId: null + inReplyToCommentId: null, + accountId: { + [Sequelize.Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + ) + } } } + // FIXME: typings + const scopes: any[] = [ + ScopeNames.WITH_ACCOUNT, + { + method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] + } + ] + return VideoCommentModel - .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) + .scope(scopes) .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) } - static listThreadCommentsForApi (videoId: number, threadId: number) { + static async listThreadCommentsForApi (videoId: number, threadId: number, user?: UserModel) { + const serverActor = await getServerActor() + const serverAccountId = serverActor.Account.id + const userAccountId = user.Account.id + const query = { order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], where: { @@ -294,12 +339,24 @@ export class VideoCommentModel extends Model { [ Sequelize.Op.or ]: [ { id: threadId }, { originCommentId: threadId } - ] + ], + accountId: { + [Sequelize.Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + ) + } } } + const scopes: any[] = [ + ScopeNames.WITH_ACCOUNT, + { + method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] + } + ] + return VideoCommentModel - .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) + .scope(scopes) .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 4f3f75613..eab99cba7 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -27,7 +27,7 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { VideoPrivacy, VideoState } from '../../../shared' +import { UserRight, VideoPrivacy, VideoState } from '../../../shared' import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' import { VideoFilter } from '../../../shared/models/videos/video-query.type' @@ -70,7 +70,7 @@ import { AccountVideoRateModel } from '../account/account-video-rate' import { ActorModel } from '../activitypub/actor' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' -import { buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' +import { buildBlockedAccountSQL, buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' import { TagModel } from './tag' import { VideoAbuseModel } from './video-abuse' import { VideoChannelModel } from './video-channel' @@ -93,6 +93,7 @@ import { } from './video-format-utils' import * as validator from 'validator' import { UserVideoHistoryModel } from '../account/user-video-history' +import { UserModel } from '../account/user' // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation const indexes: Sequelize.DefineIndexesOptions[] = [ @@ -138,6 +139,7 @@ type ForAPIOptions = { } type AvailableForListIDsOptions = { + serverAccountId: number actorId: number includeLocalVideos: boolean filter?: VideoFilter @@ -151,6 +153,7 @@ type AvailableForListIDsOptions = { accountId?: number videoChannelId?: number trendingDays?: number + user?: UserModel } @Scopes({ @@ -235,6 +238,15 @@ type AvailableForListIDsOptions = { ) } ] + }, + channelId: { + [ Sequelize.Op.notIn ]: Sequelize.literal( + '(' + + 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + + buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + + ')' + + ')' + ) } }, include: [] @@ -975,10 +987,10 @@ export class VideoModel extends Model { videoChannelId?: number, actorId?: number trendingDays?: number, - userId?: number + user?: UserModel }, countVideos = true) { - if (options.filter && options.filter === 'all-local' && !options.userId) { - throw new Error('Try to filter all-local but no userId is provided') + 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') } const query: IFindOptions = { @@ -994,11 +1006,14 @@ export class VideoModel extends Model { query.group = 'VideoModel.id' } + const serverActor = await getServerActor() + // actorId === null has a meaning, so just check undefined - const actorId = options.actorId !== undefined ? options.actorId : (await getServerActor()).id + const actorId = options.actorId !== undefined ? options.actorId : serverActor.id const queryOptions = { actorId, + serverAccountId: serverActor.Account.id, nsfw: options.nsfw, categoryOneOf: options.categoryOneOf, licenceOneOf: options.licenceOneOf, @@ -1010,7 +1025,7 @@ export class VideoModel extends Model { accountId: options.accountId, videoChannelId: options.videoChannelId, includeLocalVideos: options.includeLocalVideos, - userId: options.userId, + user: options.user, trendingDays } @@ -1033,7 +1048,7 @@ export class VideoModel extends Model { tagsAllOf?: string[] durationMin?: number // seconds durationMax?: number // seconds - userId?: number, + user?: UserModel, filter?: VideoFilter }) { const whereAnd = [] @@ -1104,6 +1119,7 @@ export class VideoModel extends Model { const serverActor = await getServerActor() const queryOptions = { actorId: serverActor.id, + serverAccountId: serverActor.Account.id, includeLocalVideos: options.includeLocalVideos, nsfw: options.nsfw, categoryOneOf: options.categoryOneOf, @@ -1111,7 +1127,7 @@ export class VideoModel extends Model { languageOneOf: options.languageOneOf, tagsOneOf: options.tagsOneOf, tagsAllOf: options.tagsAllOf, - userId: options.userId, + user: options.user, filter: options.filter } @@ -1287,7 +1303,7 @@ export class VideoModel extends Model { private static async getAvailableForApi ( query: IFindOptions, - options: AvailableForListIDsOptions & { userId?: number}, + options: AvailableForListIDsOptions, countVideos = true ) { const idsScope = { @@ -1320,8 +1336,8 @@ export class VideoModel extends Model { } ] - if (options.userId) { - apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.userId ] }) + if (options.user) { + apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) } const secondQuery = { -- cgit v1.2.3