import * as express from 'express'
import { getFormattedObjects } from '../../helpers/utils'
import {
- asyncMiddleware, commonVideosFiltersValidator,
+ asyncMiddleware,
+ commonVideosFiltersValidator,
listVideoAccountChannelsValidator,
optionalAuthenticate,
paginationValidator,
nsfw: buildNSFWFilter(res, req.query.nsfw),
withFiles: false,
accountId: account.id,
- userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined
+ user: res.locals.oauth ? res.locals.oauth.token.User : undefined
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
includeLocalVideos: true,
nsfw: buildNSFWFilter(res, query.nsfw),
filter: query.filter,
- userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined
+ user: res.locals.oauth ? res.locals.oauth.token.User : undefined
})
const resultList = await VideoModel.searchAndPopulateAccountAndServer(options)
import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
import { meRouter } from './me'
import { deleteUserToken } from '../../../lib/oauth-model'
+import { myBlocklistRouter } from './my-blocklist'
const auditLogger = auditLoggerFactory('users')
})
const usersRouter = express.Router()
+usersRouter.use('/', myBlocklistRouter)
usersRouter.use('/', meRouter)
usersRouter.get('/autocomplete',
nsfw: buildNSFWFilter(res, req.query.nsfw),
filter: req.query.filter as VideoFilter,
withFiles: false,
- actorId: user.Account.Actor.id
+ actorId: user.Account.Actor.id,
+ user
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
--- /dev/null
+import * as express from 'express'
+import 'multer'
+import { getFormattedObjects } from '../../../helpers/utils'
+import {
+ asyncMiddleware,
+ asyncRetryTransactionMiddleware,
+ authenticate,
+ paginationValidator,
+ serverGetValidator,
+ setDefaultPagination,
+ setDefaultSort,
+ unblockAccountByAccountValidator
+} from '../../../middlewares'
+import {
+ accountsBlocklistSortValidator,
+ blockAccountByAccountValidator,
+ serversBlocklistSortValidator,
+ unblockServerByAccountValidator
+} from '../../../middlewares/validators'
+import { UserModel } from '../../../models/account/user'
+import { AccountModel } from '../../../models/account/account'
+import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
+import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
+import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
+import { ServerModel } from '../../../models/server/server'
+
+const myBlocklistRouter = express.Router()
+
+myBlocklistRouter.get('/me/blocklist/accounts',
+ authenticate,
+ paginationValidator,
+ accountsBlocklistSortValidator,
+ setDefaultSort,
+ setDefaultPagination,
+ asyncMiddleware(listBlockedAccounts)
+)
+
+myBlocklistRouter.post('/me/blocklist/accounts',
+ authenticate,
+ asyncMiddleware(blockAccountByAccountValidator),
+ asyncRetryTransactionMiddleware(blockAccount)
+)
+
+myBlocklistRouter.delete('/me/blocklist/accounts/:accountName',
+ authenticate,
+ asyncMiddleware(unblockAccountByAccountValidator),
+ asyncRetryTransactionMiddleware(unblockAccount)
+)
+
+myBlocklistRouter.get('/me/blocklist/servers',
+ authenticate,
+ paginationValidator,
+ serversBlocklistSortValidator,
+ setDefaultSort,
+ setDefaultPagination,
+ asyncMiddleware(listBlockedServers)
+)
+
+myBlocklistRouter.post('/me/blocklist/servers',
+ authenticate,
+ asyncMiddleware(serverGetValidator),
+ asyncRetryTransactionMiddleware(blockServer)
+)
+
+myBlocklistRouter.delete('/me/blocklist/servers/:host',
+ authenticate,
+ asyncMiddleware(unblockServerByAccountValidator),
+ asyncRetryTransactionMiddleware(unblockServer)
+)
+
+export {
+ myBlocklistRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function listBlockedAccounts (req: express.Request, res: express.Response) {
+ const user: UserModel = res.locals.oauth.token.User
+
+ const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
+
+ return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function blockAccount (req: express.Request, res: express.Response) {
+ const user: UserModel = res.locals.oauth.token.User
+ const accountToBlock: AccountModel = res.locals.account
+
+ await addAccountInBlocklist(user.Account.id, accountToBlock.id)
+
+ return res.status(204).end()
+}
+
+async function unblockAccount (req: express.Request, res: express.Response) {
+ const accountBlock: AccountBlocklistModel = res.locals.accountBlock
+
+ await removeAccountFromBlocklist(accountBlock)
+
+ return res.status(204).end()
+}
+
+async function listBlockedServers (req: express.Request, res: express.Response) {
+ const user: UserModel = res.locals.oauth.token.User
+
+ const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
+
+ return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function blockServer (req: express.Request, res: express.Response) {
+ const user: UserModel = res.locals.oauth.token.User
+ const serverToBlock: ServerModel = res.locals.server
+
+ await addServerInBlocklist(user.Account.id, serverToBlock.id)
+
+ return res.status(204).end()
+}
+
+async function unblockServer (req: express.Request, res: express.Response) {
+ const serverBlock: ServerBlocklistModel = res.locals.serverBlock
+
+ await removeServerFromBlocklist(serverBlock)
+
+ return res.status(204).end()
+}
nsfw: buildNSFWFilter(res, req.query.nsfw),
withFiles: false,
videoChannelId: videoChannelInstance.id,
- userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined
+ user: res.locals.oauth ? res.locals.oauth.token.User : undefined
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
import {
asyncMiddleware,
asyncRetryTransactionMiddleware,
- authenticate,
+ authenticate, optionalAuthenticate,
paginationValidator,
setDefaultPagination,
setDefaultSort
setDefaultSort,
setDefaultPagination,
asyncMiddleware(listVideoCommentThreadsValidator),
+ optionalAuthenticate,
asyncMiddleware(listVideoThreads)
)
videoCommentRouter.get('/:videoId/comment-threads/:threadId',
asyncMiddleware(listVideoThreadCommentsValidator),
+ optionalAuthenticate,
asyncMiddleware(listVideoThreadComments)
)
async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) {
const video = res.locals.video as VideoModel
+ const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined
+
let resultList: ResultList<VideoCommentModel>
if (video.commentsEnabled === true) {
- resultList = await VideoCommentModel.listThreadsForApi(video.id, req.query.start, req.query.count, req.query.sort)
+ resultList = await VideoCommentModel.listThreadsForApi(video.id, req.query.start, req.query.count, req.query.sort, user)
} else {
resultList = {
total: 0,
async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) {
const video = res.locals.video as VideoModel
+ const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined
+
let resultList: ResultList<VideoCommentModel>
if (video.commentsEnabled === true) {
- resultList = await VideoCommentModel.listThreadCommentsForApi(video.id, res.locals.videoCommentThread.id)
+ resultList = await VideoCommentModel.listThreadCommentsForApi(video.id, res.locals.videoCommentThread.id, user)
} else {
resultList = {
total: 0,
nsfw: buildNSFWFilter(res, req.query.nsfw),
filter: req.query.filter as VideoFilter,
withFiles: false,
- userId: res.locals.oauth ? res.locals.oauth.token.User.id : undefined
+ user: res.locals.oauth ? res.locals.oauth.token.User : undefined
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
const application = await ApplicationModel.load()
if (!application) throw Error('Could not load Application from database.')
- return application.Account.Actor
+ const actor = application.Account.Actor
+ actor.Account = application.Account
+
+ return actor
})
function generateVideoTmpPath (target: string | ParseTorrent) {
VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'trending' ],
VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'match' ],
- VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ]
+ VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ],
+
+ ACCOUNTS_BLOCKLIST: [ 'createdAt' ],
+ SERVERS_BLOCKLIST: [ 'createdAt' ]
}
const OAUTH_LIFETIME = {
import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership'
import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
import { UserVideoHistoryModel } from '../models/account/user-video-history'
+import { AccountBlocklistModel } from '../models/account/account-blocklist'
+import { ServerBlocklistModel } from '../models/server/server-blocklist'
require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
VideoImportModel,
VideoViewModel,
VideoRedundancyModel,
- UserVideoHistoryModel
+ UserVideoHistoryModel,
+ AccountBlocklistModel,
+ ServerBlocklistModel
])
// Check extensions exist in the database
--- /dev/null
+import { sequelizeTypescript } from '../initializers'
+import { AccountBlocklistModel } from '../models/account/account-blocklist'
+import { ServerBlocklistModel } from '../models/server/server-blocklist'
+
+function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
+ return sequelizeTypescript.transaction(async t => {
+ return AccountBlocklistModel.create({
+ accountId: byAccountId,
+ targetAccountId: targetAccountId
+ }, { transaction: t })
+ })
+}
+
+function addServerInBlocklist (byAccountId: number, targetServerId: number) {
+ return sequelizeTypescript.transaction(async t => {
+ return ServerBlocklistModel.create({
+ accountId: byAccountId,
+ targetServerId
+ }, { transaction: t })
+ })
+}
+
+function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) {
+ return sequelizeTypescript.transaction(async t => {
+ return accountBlock.destroy({ transaction: t })
+ })
+}
+
+function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) {
+ return sequelizeTypescript.transaction(async t => {
+ return serverBlock.destroy({ transaction: t })
+ })
+}
+
+export {
+ addAccountInBlocklist,
+ addServerInBlocklist,
+ removeAccountFromBlocklist,
+ removeServerFromBlocklist
+}
}
const parentCommentThread = idx[childComment.inReplyToCommentId]
- if (!parentCommentThread) {
- const msg = `Cannot format video thread tree, parent ${childComment.inReplyToCommentId} not found for child ${childComment.id}`
- throw new Error(msg)
- }
+ // Maybe the parent comment was blocked by the admin/user
+ if (!parentCommentThread) continue
parentCommentThread.children.push(childCommentThread)
idx[childComment.id] = childCommentThread
--- /dev/null
+import { param, body } from 'express-validator/check'
+import * as express from 'express'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import { isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts'
+import { UserModel } from '../../models/account/user'
+import { AccountBlocklistModel } from '../../models/account/account-blocklist'
+import { isHostValid } from '../../helpers/custom-validators/servers'
+import { ServerBlocklistModel } from '../../models/server/server-blocklist'
+
+const blockAccountByAccountValidator = [
+ body('accountName').exists().withMessage('Should have an account name with host'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking blockAccountByAccountValidator parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+ if (!await isAccountNameWithHostExist(req.body.accountName, res)) return
+
+ return next()
+ }
+]
+
+const unblockAccountByAccountValidator = [
+ param('accountName').exists().withMessage('Should have an account name with host'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking unblockAccountByAccountValidator parameters', { parameters: req.params })
+
+ if (areValidationErrors(req, res)) return
+ if (!await isAccountNameWithHostExist(req.params.accountName, res)) return
+
+ const user = res.locals.oauth.token.User as UserModel
+ const targetAccount = res.locals.account
+ if (!await isUnblockAccountExists(user.Account.id, targetAccount.id, res)) return
+
+ return next()
+ }
+]
+
+const unblockServerByAccountValidator = [
+ param('host').custom(isHostValid).withMessage('Should have an account name with host'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking unblockServerByAccountValidator parameters', { parameters: req.params })
+
+ if (areValidationErrors(req, res)) return
+
+ const user = res.locals.oauth.token.User as UserModel
+ if (!await isUnblockServerExists(user.Account.id, req.params.host, res)) return
+
+ return next()
+ }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+ blockAccountByAccountValidator,
+ unblockAccountByAccountValidator,
+ unblockServerByAccountValidator
+}
+
+// ---------------------------------------------------------------------------
+
+async function isUnblockAccountExists (accountId: number, targetAccountId: number, res: express.Response) {
+ const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId)
+ if (!accountBlock) {
+ res.status(404)
+ .send({ error: 'Account block entry not found.' })
+ .end()
+
+ return false
+ }
+
+ res.locals.accountBlock = accountBlock
+
+ return true
+}
+
+async function isUnblockServerExists (accountId: number, host: string, res: express.Response) {
+ const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host)
+ if (!serverBlock) {
+ res.status(404)
+ .send({ error: 'Server block entry not found.' })
+ .end()
+
+ return false
+ }
+
+ res.locals.serverBlock = serverBlock
+
+ return true
+}
export * from './account'
+export * from './blocklist'
export * from './oembed'
export * from './activitypub'
export * from './pagination'
export * from './videos'
export * from './webfinger'
export * from './search'
+export * from './server'
--- /dev/null
+import * as express from 'express'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import { isHostValid } from '../../helpers/custom-validators/servers'
+import { ServerModel } from '../../models/server/server'
+import { body } from 'express-validator/check'
+
+const serverGetValidator = [
+ body('host').custom(isHostValid).withMessage('Should have a valid host'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking serverGetValidator parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+
+ const server = await ServerModel.loadByHost(req.body.host)
+ if (!server) {
+ return res.status(404)
+ .send({ error: 'Server host not found.' })
+ .end()
+ }
+
+ res.locals.server = server
+
+ return next()
+ }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+ serverGetValidator
+}
const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS)
const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING)
const SORTABLE_USER_SUBSCRIPTIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS)
+const SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST)
+const SORTABLE_SERVERS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.SERVERS_BLOCKLIST)
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS)
const followingSortValidator = checkSort(SORTABLE_FOLLOWING_COLUMNS)
const userSubscriptionsSortValidator = checkSort(SORTABLE_USER_SUBSCRIPTIONS_COLUMNS)
+const accountsBlocklistSortValidator = checkSort(SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS)
+const serversBlocklistSortValidator = checkSort(SORTABLE_SERVERS_BLOCKLIST_COLUMNS)
// ---------------------------------------------------------------------------
jobsSortValidator,
videoCommentThreadsSortValidator,
userSubscriptionsSortValidator,
- videoChannelsSearchSortValidator
+ videoChannelsSearchSortValidator,
+ accountsBlocklistSortValidator,
+ serversBlocklistSortValidator
}
--- /dev/null
+import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { AccountModel } from './account'
+import { getSort } from '../utils'
+import { AccountBlock } from '../../../shared/models/blocklist'
+
+enum ScopeNames {
+ WITH_ACCOUNTS = 'WITH_ACCOUNTS'
+}
+
+@Scopes({
+ [ScopeNames.WITH_ACCOUNTS]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true,
+ as: 'ByAccount'
+ },
+ {
+ model: () => AccountModel,
+ required: true,
+ as: 'AccountBlocked'
+ }
+ ]
+ }
+})
+
+@Table({
+ tableName: 'accountBlocklist',
+ indexes: [
+ {
+ fields: [ 'accountId', 'targetAccountId' ],
+ unique: true
+ },
+ {
+ fields: [ 'targetAccountId' ]
+ }
+ ]
+})
+export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
+
+ @CreatedAt
+ createdAt: Date
+
+ @UpdatedAt
+ updatedAt: Date
+
+ @ForeignKey(() => AccountModel)
+ @Column
+ accountId: number
+
+ @BelongsTo(() => AccountModel, {
+ foreignKey: {
+ name: 'accountId',
+ allowNull: false
+ },
+ as: 'ByAccount',
+ onDelete: 'CASCADE'
+ })
+ ByAccount: AccountModel
+
+ @ForeignKey(() => AccountModel)
+ @Column
+ targetAccountId: number
+
+ @BelongsTo(() => AccountModel, {
+ foreignKey: {
+ name: 'targetAccountId',
+ allowNull: false
+ },
+ as: 'AccountBlocked',
+ onDelete: 'CASCADE'
+ })
+ AccountBlocked: AccountModel
+
+ static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
+ const query = {
+ where: {
+ accountId,
+ targetAccountId
+ }
+ }
+
+ return AccountBlocklistModel.findOne(query)
+ }
+
+ static listForApi (accountId: number, start: number, count: number, sort: string) {
+ const query = {
+ offset: start,
+ limit: count,
+ order: getSort(sort),
+ where: {
+ accountId
+ }
+ }
+
+ return AccountBlocklistModel
+ .scope([ ScopeNames.WITH_ACCOUNTS ])
+ .findAndCountAll(query)
+ .then(({ rows, count }) => {
+ return { total: count, data: rows }
+ })
+ }
+
+ toFormattedJSON (): AccountBlock {
+ return {
+ byAccount: this.ByAccount.toFormattedJSON(),
+ accountBlocked: this.AccountBlocked.toFormattedJSON(),
+ createdAt: this.createdAt
+ }
+ }
+}
--- /dev/null
+import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { AccountModel } from '../account/account'
+import { ServerModel } from './server'
+import { ServerBlock } from '../../../shared/models/blocklist'
+import { getSort } from '../utils'
+
+enum ScopeNames {
+ WITH_ACCOUNT = 'WITH_ACCOUNT',
+ WITH_SERVER = 'WITH_SERVER'
+}
+
+@Scopes({
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ }
+ ]
+ },
+ [ScopeNames.WITH_SERVER]: {
+ include: [
+ {
+ model: () => ServerModel,
+ required: true
+ }
+ ]
+ }
+})
+
+@Table({
+ tableName: 'serverBlocklist',
+ indexes: [
+ {
+ fields: [ 'accountId', 'targetServerId' ],
+ unique: true
+ },
+ {
+ fields: [ 'targetServerId' ]
+ }
+ ]
+})
+export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
+
+ @CreatedAt
+ createdAt: Date
+
+ @UpdatedAt
+ updatedAt: Date
+
+ @ForeignKey(() => AccountModel)
+ @Column
+ accountId: number
+
+ @BelongsTo(() => AccountModel, {
+ foreignKey: {
+ name: 'accountId',
+ allowNull: false
+ },
+ onDelete: 'CASCADE'
+ })
+ ByAccount: AccountModel
+
+ @ForeignKey(() => ServerModel)
+ @Column
+ targetServerId: number
+
+ @BelongsTo(() => ServerModel, {
+ foreignKey: {
+ name: 'targetServerId',
+ allowNull: false
+ },
+ onDelete: 'CASCADE'
+ })
+ ServerBlocked: ServerModel
+
+ static loadByAccountAndHost (accountId: number, host: string) {
+ const query = {
+ where: {
+ accountId
+ },
+ include: [
+ {
+ model: ServerModel,
+ where: {
+ host
+ },
+ required: true
+ }
+ ]
+ }
+
+ return ServerBlocklistModel.findOne(query)
+ }
+
+ static listForApi (accountId: number, start: number, count: number, sort: string) {
+ const query = {
+ offset: start,
+ limit: count,
+ order: getSort(sort),
+ where: {
+ accountId
+ }
+ }
+
+ return ServerBlocklistModel
+ .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ])
+ .findAndCountAll(query)
+ .then(({ rows, count }) => {
+ return { total: count, data: rows }
+ })
+ }
+
+ toFormattedJSON (): ServerBlock {
+ return {
+ byAccount: this.ByAccount.toFormattedJSON(),
+ serverBlocked: this.ServerBlocked.toFormattedJSON(),
+ createdAt: this.createdAt
+ }
+ }
+}
return ServerModel.findOne(query)
}
+
+ toFormattedJSON () {
+ return {
+ host: this.host
+ }
+ }
}
)
}
+function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number) {
+ const blockerIds = [ serverAccountId ]
+ if (userAccountId) blockerIds.push(userAccountId)
+
+ const blockerIdsString = blockerIds.join(', ')
+
+ const query = 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
+ ' UNION ALL ' +
+ // 'SELECT "accountId" FROM "accountBlocklist" WHERE "targetAccountId" = user.account.id
+ // UNION ALL
+ 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
+ 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
+ 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
+
+ return query
+}
+
// ---------------------------------------------------------------------------
export {
+ buildBlockedAccountSQL,
SortType,
getSort,
getVideoSort,
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'
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',
}
@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]: {
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: {
[ 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 }
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'
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'
} 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[] = [
}
type AvailableForListIDsOptions = {
+ serverAccountId: number
actorId: number
includeLocalVideos: boolean
filter?: VideoFilter
accountId?: number
videoChannelId?: number
trendingDays?: number
+ user?: UserModel
}
@Scopes({
)
}
]
+ },
+ channelId: {
+ [ Sequelize.Op.notIn ]: Sequelize.literal(
+ '(' +
+ 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
+ buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
+ ')' +
+ ')'
+ )
}
},
include: []
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<VideoModel> = {
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,
accountId: options.accountId,
videoChannelId: options.videoChannelId,
includeLocalVideos: options.includeLocalVideos,
- userId: options.userId,
+ user: options.user,
trendingDays
}
tagsAllOf?: string[]
durationMin?: number // seconds
durationMax?: number // seconds
- userId?: number,
+ user?: UserModel,
filter?: VideoFilter
}) {
const whereAnd = []
const serverActor = await getServerActor()
const queryOptions = {
actorId: serverActor.id,
+ serverAccountId: serverActor.Account.id,
includeLocalVideos: options.includeLocalVideos,
nsfw: options.nsfw,
categoryOneOf: options.categoryOneOf,
languageOneOf: options.languageOneOf,
tagsOneOf: options.tagsOneOf,
tagsAllOf: options.tagsAllOf,
- userId: options.userId,
+ user: options.user,
filter: options.filter
}
private static async getAvailableForApi (
query: IFindOptions<VideoModel>,
- options: AvailableForListIDsOptions & { userId?: number},
+ options: AvailableForListIDsOptions,
countVideos = true
) {
const idsScope = {
}
]
- 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 = {
--- /dev/null
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+
+import {
+ createUser,
+ doubleFollow,
+ flushAndRunMultipleServers,
+ flushTests,
+ killallServers,
+ makeDeleteRequest,
+ makeGetRequest,
+ makePostBodyRequest,
+ ServerInfo,
+ setAccessTokensToServers
+} from '../../utils'
+import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+
+describe('Test blocklist API validators', function () {
+ let servers: ServerInfo[]
+ let server: ServerInfo
+
+ before(async function () {
+ this.timeout(60000)
+
+ await flushTests()
+
+ servers = await flushAndRunMultipleServers(2)
+ await setAccessTokensToServers(servers)
+
+ server = servers[0]
+
+ const user = { username: 'user1', password: 'password' }
+ await createUser(server.url, server.accessToken, user.username, user.password)
+
+ await doubleFollow(servers[0], servers[1])
+ })
+
+ // ---------------------------------------------------------------
+
+ describe('When managing user blocklist', function () {
+ const path = '/api/v1/users/me/blocklist/accounts'
+
+ describe('When managing user accounts blocklist', function () {
+
+ describe('When listing blocked accounts', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with a bad start pagination', async function () {
+ await checkBadStartPagination(server.url, path, server.accessToken)
+ })
+
+ it('Should fail with a bad count pagination', async function () {
+ await checkBadCountPagination(server.url, path, server.accessToken)
+ })
+
+ it('Should fail with an incorrect sort', async function () {
+ await checkBadSortPagination(server.url, path, server.accessToken)
+ })
+ })
+
+ describe('When blocking an account', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ path,
+ fields: { accountName: 'user1' },
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with an unknown account', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { accountName: 'user2' },
+ statusCodeExpected: 404
+ })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { accountName: 'user1' },
+ statusCodeExpected: 204
+ })
+ })
+ })
+
+ describe('When unblocking an account', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/user1',
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with an unknown account block', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/user2',
+ token: server.accessToken,
+ statusCodeExpected: 404
+ })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/user1',
+ token: server.accessToken,
+ statusCodeExpected: 204
+ })
+ })
+ })
+ })
+
+ describe('When managing user servers blocklist', function () {
+ const path = '/api/v1/users/me/blocklist/servers'
+
+ describe('When listing blocked servers', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with a bad start pagination', async function () {
+ await checkBadStartPagination(server.url, path, server.accessToken)
+ })
+
+ it('Should fail with a bad count pagination', async function () {
+ await checkBadCountPagination(server.url, path, server.accessToken)
+ })
+
+ it('Should fail with an incorrect sort', async function () {
+ await checkBadSortPagination(server.url, path, server.accessToken)
+ })
+ })
+
+ describe('When blocking a server', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ path,
+ fields: { host: 'localhost:9002' },
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with an unknown server', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { host: 'localhost:9003' },
+ statusCodeExpected: 404
+ })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { host: 'localhost:9002' },
+ statusCodeExpected: 204
+ })
+ })
+ })
+
+ describe('When unblocking a server', function () {
+ it('Should fail with an unauthenticated user', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/localhost:9002',
+ statusCodeExpected: 401
+ })
+ })
+
+ it('Should fail with an unknown server block', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/localhost:9003',
+ token: server.accessToken,
+ statusCodeExpected: 404
+ })
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await makeDeleteRequest({
+ url: server.url,
+ path: path + '/localhost:9002',
+ token: server.accessToken,
+ statusCodeExpected: 204
+ })
+ })
+ })
+ })
+ })
+
+ after(async function () {
+ killallServers(servers)
+
+ // Keep the logs if the test failed
+ if (this['ok']) {
+ await flushTests()
+ }
+ })
+})
// Order of the tests we want to execute
import './accounts'
+import './blocklist'
import './config'
import './follows'
import './jobs'
--- /dev/null
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { AccountBlock, ServerBlock, Video } from '../../../../shared/index'
+import {
+ createUser,
+ doubleFollow,
+ flushAndRunMultipleServers,
+ flushTests,
+ killallServers,
+ ServerInfo,
+ uploadVideo,
+ userLogin
+} from '../../utils/index'
+import { setAccessTokensToServers } from '../../utils/users/login'
+import { getVideosListWithToken } from '../../utils/videos/videos'
+import {
+ addVideoCommentReply,
+ addVideoCommentThread,
+ getVideoCommentThreads,
+ getVideoThreadComments
+} from '../../utils/videos/video-comments'
+import { waitJobs } from '../../utils/server/jobs'
+import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
+import {
+ addAccountToAccountBlocklist,
+ addServerToAccountBlocklist,
+ getAccountBlocklistByAccount, getServerBlocklistByAccount,
+ removeAccountFromAccountBlocklist,
+ removeServerFromAccountBlocklist
+} from '../../utils/users/blocklist'
+
+const expect = chai.expect
+
+async function checkAllVideos (url: string, token: string) {
+ const res = await getVideosListWithToken(url, token)
+
+ expect(res.body.data).to.have.lengthOf(4)
+}
+
+async function checkAllComments (url: string, token: string, videoUUID: string) {
+ const resThreads = await getVideoCommentThreads(url, videoUUID, 0, 5, '-createdAt', token)
+
+ const threads: VideoComment[] = resThreads.body.data
+ expect(threads).to.have.lengthOf(2)
+
+ for (const thread of threads) {
+ const res = await getVideoThreadComments(url, videoUUID, thread.id, token)
+
+ const tree: VideoCommentThreadTree = res.body
+ expect(tree.children).to.have.lengthOf(1)
+ }
+}
+
+describe('Test accounts blocklist', function () {
+ let servers: ServerInfo[]
+ let videoUUID1: string
+ let videoUUID2: string
+ let userToken1: string
+ let userToken2: string
+
+ before(async function () {
+ this.timeout(60000)
+
+ await flushTests()
+
+ servers = await flushAndRunMultipleServers(2)
+ await setAccessTokensToServers(servers)
+
+ {
+ const user = { username: 'user1', password: 'password' }
+ await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
+
+ userToken1 = await userLogin(servers[0], user)
+ await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' })
+ }
+
+ {
+ const user = { username: 'user2', password: 'password' }
+ await createUser(servers[1].url, servers[1].accessToken, user.username, user.password)
+
+ userToken2 = await userLogin(servers[1], user)
+ await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' })
+ }
+
+ {
+ const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' })
+ videoUUID1 = res.body.video.uuid
+ }
+
+ {
+ const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' })
+ videoUUID2 = res.body.video.uuid
+ }
+
+ await doubleFollow(servers[0], servers[1])
+
+ {
+ const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1')
+ const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1')
+ await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1')
+ }
+
+ {
+ const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1')
+ await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1')
+ }
+
+ await waitJobs(servers)
+ })
+
+ describe('When managing account blocklist', function () {
+ it('Should list all videos', function () {
+ return checkAllVideos(servers[0].url, servers[0].accessToken)
+ })
+
+ it('Should list the comments', function () {
+ return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
+ })
+
+ it('Should block a remote account', async function () {
+ await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:9002')
+ })
+
+ it('Should hide its videos', async function () {
+ const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
+
+ const videos: Video[] = res.body.data
+ expect(videos).to.have.lengthOf(3)
+
+ const v = videos.find(v => v.name === 'video user 2')
+ expect(v).to.be.undefined
+ })
+
+ it('Should block a local account', async function () {
+ await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
+ })
+
+ it('Should hide its videos', async function () {
+ const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
+
+ const videos: Video[] = res.body.data
+ expect(videos).to.have.lengthOf(2)
+
+ const v = videos.find(v => v.name === 'video user 1')
+ expect(v).to.be.undefined
+ })
+
+ it('Should hide its comments', async function () {
+ const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', servers[0].accessToken)
+
+ const threads: VideoComment[] = resThreads.body.data
+ expect(threads).to.have.lengthOf(1)
+ expect(threads[0].totalReplies).to.equal(0)
+
+ const t = threads.find(t => t.text === 'comment user 1')
+ expect(t).to.be.undefined
+
+ for (const thread of threads) {
+ const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, servers[0].accessToken)
+
+ const tree: VideoCommentThreadTree = res.body
+ expect(tree.children).to.have.lengthOf(0)
+ }
+ })
+
+ it('Should list all the videos with another user', async function () {
+ return checkAllVideos(servers[0].url, userToken1)
+ })
+
+ it('Should list all the comments with another user', async function () {
+ return checkAllComments(servers[0].url, userToken1, videoUUID1)
+ })
+
+ it('Should list blocked accounts', async function () {
+ {
+ const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt')
+ const blocks: AccountBlock[] = res.body.data
+
+ expect(res.body.total).to.equal(2)
+
+ const block = blocks[0]
+ expect(block.byAccount.displayName).to.equal('root')
+ expect(block.byAccount.name).to.equal('root')
+ expect(block.accountBlocked.displayName).to.equal('user2')
+ expect(block.accountBlocked.name).to.equal('user2')
+ expect(block.accountBlocked.host).to.equal('localhost:9002')
+ }
+
+ {
+ const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt')
+ const blocks: AccountBlock[] = res.body.data
+
+ expect(res.body.total).to.equal(2)
+
+ const block = blocks[0]
+ expect(block.byAccount.displayName).to.equal('root')
+ expect(block.byAccount.name).to.equal('root')
+ expect(block.accountBlocked.displayName).to.equal('user1')
+ expect(block.accountBlocked.name).to.equal('user1')
+ expect(block.accountBlocked.host).to.equal('localhost:9001')
+ }
+ })
+
+ it('Should unblock the remote account', async function () {
+ await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:9002')
+ })
+
+ it('Should display its videos', async function () {
+ const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
+
+ const videos: Video[] = res.body.data
+ expect(videos).to.have.lengthOf(3)
+
+ const v = videos.find(v => v.name === 'video user 2')
+ expect(v).not.to.be.undefined
+ })
+
+ it('Should unblock the local account', async function () {
+ await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
+ })
+
+ it('Should display its comments', function () {
+ return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
+ })
+ })
+
+ describe('When managing server blocklist', function () {
+ it('Should list all videos', function () {
+ return checkAllVideos(servers[0].url, servers[0].accessToken)
+ })
+
+ it('Should list the comments', function () {
+ return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
+ })
+
+ it('Should block a remote server', async function () {
+ await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:9002')
+ })
+
+ it('Should hide its videos', async function () {
+ const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
+
+ const videos: Video[] = res.body.data
+ expect(videos).to.have.lengthOf(2)
+
+ const v1 = videos.find(v => v.name === 'video user 2')
+ const v2 = videos.find(v => v.name === 'video server 2')
+
+ expect(v1).to.be.undefined
+ expect(v2).to.be.undefined
+ })
+
+ it('Should list all the videos with another user', async function () {
+ return checkAllVideos(servers[0].url, userToken1)
+ })
+
+ it('Should hide its comments')
+
+ it('Should list blocked servers', async function () {
+ const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt')
+ const blocks: ServerBlock[] = res.body.data
+
+ expect(res.body.total).to.equal(1)
+
+ const block = blocks[0]
+ expect(block.byAccount.displayName).to.equal('root')
+ expect(block.byAccount.name).to.equal('root')
+ expect(block.serverBlocked.host).to.equal('localhost:9002')
+ })
+
+ it('Should unblock the remote server', async function () {
+ await removeServerFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:9002')
+ })
+
+ it('Should display its videos', function () {
+ return checkAllVideos(servers[0].url, servers[0].accessToken)
+ })
+
+ it('Should display its comments', function () {
+ return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
+ })
+ })
+
+ after(async function () {
+ killallServers(servers)
+
+ // Keep the logs if the test failed
+ if (this[ 'ok' ]) {
+ await flushTests()
+ }
+ })
+})
if (options.token) req.set('Authorization', 'Bearer ' + options.token)
- return req
- .expect('Content-Type', /json/)
- .expect(options.statusCodeExpected)
+ return req.expect(options.statusCodeExpected)
}
function makeUploadRequest (options: {
--- /dev/null
+/* tslint:disable:no-unused-expression */
+
+import { makeDeleteRequest, makePostBodyRequest } from '../index'
+import { makeGetRequest } from '../requests/requests'
+
+function getAccountBlocklistByAccount (
+ url: string,
+ token: string,
+ start: number,
+ count: number,
+ sort = '-createdAt',
+ statusCodeExpected = 200
+) {
+ const path = '/api/v1/users/me/blocklist/accounts'
+
+ return makeGetRequest({
+ url,
+ token,
+ query: { start, count, sort },
+ path,
+ statusCodeExpected
+ })
+}
+
+function addAccountToAccountBlocklist (url: string, token: string, accountToBlock: string, statusCodeExpected = 204) {
+ const path = '/api/v1/users/me/blocklist/accounts'
+
+ return makePostBodyRequest({
+ url,
+ path,
+ token,
+ fields: {
+ accountName: accountToBlock
+ },
+ statusCodeExpected
+ })
+}
+
+function removeAccountFromAccountBlocklist (url: string, token: string, accountToUnblock: string, statusCodeExpected = 204) {
+ const path = '/api/v1/users/me/blocklist/accounts/' + accountToUnblock
+
+ return makeDeleteRequest({
+ url,
+ path,
+ token,
+ statusCodeExpected
+ })
+}
+
+function getServerBlocklistByAccount (
+ url: string,
+ token: string,
+ start: number,
+ count: number,
+ sort = '-createdAt',
+ statusCodeExpected = 200
+) {
+ const path = '/api/v1/users/me/blocklist/servers'
+
+ return makeGetRequest({
+ url,
+ token,
+ query: { start, count, sort },
+ path,
+ statusCodeExpected
+ })
+}
+
+function addServerToAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
+ const path = '/api/v1/users/me/blocklist/servers'
+
+ return makePostBodyRequest({
+ url,
+ path,
+ token,
+ fields: {
+ host: serverToBlock
+ },
+ statusCodeExpected
+ })
+}
+
+function removeServerFromAccountBlocklist (url: string, token: string, serverToBlock: string, statusCodeExpected = 204) {
+ const path = '/api/v1/users/me/blocklist/servers/' + serverToBlock
+
+ return makeDeleteRequest({
+ url,
+ path,
+ token,
+ statusCodeExpected
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ getAccountBlocklistByAccount,
+ addAccountToAccountBlocklist,
+ removeAccountFromAccountBlocklist,
+ getServerBlocklistByAccount,
+ addServerToAccountBlocklist,
+ removeServerFromAccountBlocklist
+}
import * as request from 'supertest'
import { makeDeleteRequest } from '../'
-function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string) {
+function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) {
const path = '/api/v1/videos/' + videoId + '/comment-threads'
const req = request(url)
.query({ count: count })
if (sort) req.query({ sort })
+ if (token) req.set('Authorization', 'Bearer ' + token)
return req.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
}
-function getVideoThreadComments (url: string, videoId: number | string, threadId: number) {
+function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) {
const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId
- return request(url)
+ const req = request(url)
.get(path)
.set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
+
+ if (token) req.set('Authorization', 'Bearer ' + token)
+
+ return req.expect(200)
+ .expect('Content-Type', /json/)
}
function addVideoCommentThread (url: string, token: string, videoId: number | string, text: string, expectedStatus = 200) {
--- /dev/null
+import { Account } from '../actors'
+
+export interface AccountBlock {
+ byAccount: Account
+ accountBlocked: Account
+ createdAt: Date | string
+}
--- /dev/null
+export * from './account-block.model'
+export * from './server-block.model'
--- /dev/null
+import { Account } from '../actors'
+
+export interface ServerBlock {
+ byAccount: Account
+ serverBlocked: {
+ host: string
+ }
+ createdAt: Date | string
+}
export * from './activitypub'
export * from './actors'
export * from './avatars'
+export * from './blocklist'
export * from './redundancy'
export * from './users'
export * from './videos'