Send account activitypub update events
This commit is contained in:
parent
9bce811268
commit
265ba139eb
|
@ -0,0 +1,38 @@
|
||||||
|
import * as express from 'express'
|
||||||
|
import { getFormattedObjects } from '../../helpers/utils'
|
||||||
|
import { asyncMiddleware, paginationValidator, setAccountsSort, setPagination } from '../../middlewares'
|
||||||
|
import { accountsGetValidator, accountsSortValidator } from '../../middlewares/validators'
|
||||||
|
import { AccountModel } from '../../models/account/account'
|
||||||
|
|
||||||
|
const accountsRouter = express.Router()
|
||||||
|
|
||||||
|
accountsRouter.get('/',
|
||||||
|
paginationValidator,
|
||||||
|
accountsSortValidator,
|
||||||
|
setAccountsSort,
|
||||||
|
setPagination,
|
||||||
|
asyncMiddleware(listAccounts)
|
||||||
|
)
|
||||||
|
|
||||||
|
accountsRouter.get('/:id',
|
||||||
|
asyncMiddleware(accountsGetValidator),
|
||||||
|
getAccount
|
||||||
|
)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
accountsRouter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
return res.json(res.locals.account.toFormattedJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort)
|
||||||
|
|
||||||
|
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import { jobsRouter } from './jobs'
|
||||||
import { oauthClientsRouter } from './oauth-clients'
|
import { oauthClientsRouter } from './oauth-clients'
|
||||||
import { serverRouter } from './server'
|
import { serverRouter } from './server'
|
||||||
import { usersRouter } from './users'
|
import { usersRouter } from './users'
|
||||||
|
import { accountsRouter } from './accounts'
|
||||||
import { videosRouter } from './videos'
|
import { videosRouter } from './videos'
|
||||||
|
|
||||||
const apiRouter = express.Router()
|
const apiRouter = express.Router()
|
||||||
|
@ -13,6 +14,7 @@ apiRouter.use('/server', serverRouter)
|
||||||
apiRouter.use('/oauth-clients', oauthClientsRouter)
|
apiRouter.use('/oauth-clients', oauthClientsRouter)
|
||||||
apiRouter.use('/config', configRouter)
|
apiRouter.use('/config', configRouter)
|
||||||
apiRouter.use('/users', usersRouter)
|
apiRouter.use('/users', usersRouter)
|
||||||
|
apiRouter.use('/accounts', accountsRouter)
|
||||||
apiRouter.use('/videos', videosRouter)
|
apiRouter.use('/videos', videosRouter)
|
||||||
apiRouter.use('/jobs', jobsRouter)
|
apiRouter.use('/jobs', jobsRouter)
|
||||||
apiRouter.use('/ping', pong)
|
apiRouter.use('/ping', pong)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { retryTransactionWrapper } from '../../helpers/database-utils'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
|
import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
|
||||||
import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
|
import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
|
||||||
|
import { sendUpdateUser } from '../../lib/activitypub/send'
|
||||||
import { createUserAccountAndChannel } from '../../lib/user'
|
import { createUserAccountAndChannel } from '../../lib/user'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware, authenticate, ensureUserHasRight, ensureUserRegistrationAllowed, paginationValidator, setPagination, setUsersSort,
|
asyncMiddleware, authenticate, ensureUserHasRight, ensureUserRegistrationAllowed, paginationValidator, setPagination, setUsersSort,
|
||||||
|
@ -217,7 +218,6 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
|
||||||
async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const body: UserUpdateMe = req.body
|
const body: UserUpdateMe = req.body
|
||||||
|
|
||||||
// FIXME: user is not already a Sequelize instance?
|
|
||||||
const user = res.locals.oauth.token.user
|
const user = res.locals.oauth.token.user
|
||||||
|
|
||||||
if (body.password !== undefined) user.password = body.password
|
if (body.password !== undefined) user.password = body.password
|
||||||
|
@ -226,13 +226,15 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
|
||||||
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
|
if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
|
||||||
|
|
||||||
await user.save()
|
await user.save()
|
||||||
|
await sendUpdateUser(user, undefined)
|
||||||
|
|
||||||
return res.sendStatus(204)
|
return res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
|
async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
const avatarPhysicalFile = req.files['avatarfile'][0]
|
const avatarPhysicalFile = req.files['avatarfile'][0]
|
||||||
const actor = res.locals.oauth.token.user.Account.Actor
|
const user = res.locals.oauth.token.user
|
||||||
|
const actor = user.Account.Actor
|
||||||
|
|
||||||
const avatarDir = CONFIG.STORAGE.AVATARS_DIR
|
const avatarDir = CONFIG.STORAGE.AVATARS_DIR
|
||||||
const source = join(avatarDir, avatarPhysicalFile.filename)
|
const source = join(avatarDir, avatarPhysicalFile.filename)
|
||||||
|
@ -252,12 +254,19 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
|
||||||
}, { transaction: t })
|
}, { transaction: t })
|
||||||
|
|
||||||
if (actor.Avatar) {
|
if (actor.Avatar) {
|
||||||
await actor.Avatar.destroy({ transaction: t })
|
try {
|
||||||
|
await actor.Avatar.destroy({ transaction: t })
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Cannot remove old avatar of user %s.', user.username, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actor.set('avatarId', avatar.id)
|
actor.set('avatarId', avatar.id)
|
||||||
|
actor.Avatar = avatar
|
||||||
await actor.save({ transaction: t })
|
await actor.save({ transaction: t })
|
||||||
|
|
||||||
|
await sendUpdateUser(user, undefined)
|
||||||
|
|
||||||
return { actor, avatar }
|
return { actor, avatar }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -278,6 +287,8 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
|
||||||
|
|
||||||
await user.save()
|
await user.save()
|
||||||
|
|
||||||
|
// Don't need to send this update to followers, these attributes are not propagated
|
||||||
|
|
||||||
return res.sendStatus(204)
|
return res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as validator from 'validator'
|
import * as validator from 'validator'
|
||||||
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
|
||||||
import { isActorAcceptActivityValid, isActorDeleteActivityValid, isActorFollowActivityValid } from './actor'
|
import { isActorAcceptActivityValid, isActorDeleteActivityValid, isActorFollowActivityValid, isActorUpdateActivityValid } from './actor'
|
||||||
import { isAnnounceActivityValid } from './announce'
|
import { isAnnounceActivityValid } from './announce'
|
||||||
import { isActivityPubUrlValid } from './misc'
|
import { isActivityPubUrlValid } from './misc'
|
||||||
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
|
||||||
|
@ -64,7 +64,8 @@ function checkCreateActivity (activity: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdateActivity (activity: any) {
|
function checkUpdateActivity (activity: any) {
|
||||||
return isVideoTorrentUpdateActivityValid(activity)
|
return isVideoTorrentUpdateActivityValid(activity) ||
|
||||||
|
isActorUpdateActivityValid(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDeleteActivity (activity: any) {
|
function checkDeleteActivity (activity: any) {
|
||||||
|
|
|
@ -45,22 +45,22 @@ function isActorPrivateKeyValid (privateKey: string) {
|
||||||
validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY)
|
validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRemoteActorValid (remoteActor: any) {
|
function isActorObjectValid (actor: any) {
|
||||||
return exists(remoteActor) &&
|
return exists(actor) &&
|
||||||
isActivityPubUrlValid(remoteActor.id) &&
|
isActivityPubUrlValid(actor.id) &&
|
||||||
isActorTypeValid(remoteActor.type) &&
|
isActorTypeValid(actor.type) &&
|
||||||
isActivityPubUrlValid(remoteActor.following) &&
|
isActivityPubUrlValid(actor.following) &&
|
||||||
isActivityPubUrlValid(remoteActor.followers) &&
|
isActivityPubUrlValid(actor.followers) &&
|
||||||
isActivityPubUrlValid(remoteActor.inbox) &&
|
isActivityPubUrlValid(actor.inbox) &&
|
||||||
isActivityPubUrlValid(remoteActor.outbox) &&
|
isActivityPubUrlValid(actor.outbox) &&
|
||||||
isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
|
isActorPreferredUsernameValid(actor.preferredUsername) &&
|
||||||
isActivityPubUrlValid(remoteActor.url) &&
|
isActivityPubUrlValid(actor.url) &&
|
||||||
isActorPublicKeyObjectValid(remoteActor.publicKey) &&
|
isActorPublicKeyObjectValid(actor.publicKey) &&
|
||||||
isActorEndpointsObjectValid(remoteActor.endpoints) &&
|
isActorEndpointsObjectValid(actor.endpoints) &&
|
||||||
setValidAttributedTo(remoteActor) &&
|
setValidAttributedTo(actor) &&
|
||||||
// If this is not an account, it should be attributed to an account
|
// If this is not an account, it should be attributed to an account
|
||||||
// In PeerTube we use this to attach a video channel to a specific account
|
// In PeerTube we use this to attach a video channel to a specific account
|
||||||
(remoteActor.type === 'Person' || remoteActor.attributedTo.length !== 0)
|
(actor.type === 'Person' || actor.attributedTo.length !== 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActorFollowingCountValid (value: string) {
|
function isActorFollowingCountValid (value: string) {
|
||||||
|
@ -84,6 +84,11 @@ function isActorAcceptActivityValid (activity: any) {
|
||||||
return isBaseActivityValid(activity, 'Accept')
|
return isBaseActivityValid(activity, 'Accept')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isActorUpdateActivityValid (activity: any) {
|
||||||
|
return isBaseActivityValid(activity, 'Update') &&
|
||||||
|
isActorObjectValid(activity.object)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -93,11 +98,11 @@ export {
|
||||||
isActorPublicKeyValid,
|
isActorPublicKeyValid,
|
||||||
isActorPreferredUsernameValid,
|
isActorPreferredUsernameValid,
|
||||||
isActorPrivateKeyValid,
|
isActorPrivateKeyValid,
|
||||||
isRemoteActorValid,
|
isActorObjectValid,
|
||||||
isActorFollowingCountValid,
|
isActorFollowingCountValid,
|
||||||
isActorFollowersCountValid,
|
isActorFollowersCountValid,
|
||||||
isActorFollowActivityValid,
|
isActorFollowActivityValid,
|
||||||
isActorAcceptActivityValid,
|
isActorAcceptActivityValid,
|
||||||
isActorDeleteActivityValid,
|
isActorDeleteActivityValid,
|
||||||
isActorNameValid
|
isActorUpdateActivityValid
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ const PAGINATION_COUNT_DEFAULT = 15
|
||||||
// Sortable columns per schema
|
// Sortable columns per schema
|
||||||
const SORTABLE_COLUMNS = {
|
const SORTABLE_COLUMNS = {
|
||||||
USERS: [ 'id', 'username', 'createdAt' ],
|
USERS: [ 'id', 'username', 'createdAt' ],
|
||||||
|
ACCOUNTS: [ 'createdAt' ],
|
||||||
JOBS: [ 'id', 'createdAt' ],
|
JOBS: [ 'id', 'createdAt' ],
|
||||||
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
||||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||||
|
|
|
@ -5,13 +5,13 @@ import * as url from 'url'
|
||||||
import * as uuidv4 from 'uuid/v4'
|
import * as uuidv4 from 'uuid/v4'
|
||||||
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
|
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
|
||||||
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
|
import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
|
||||||
import { isRemoteActorValid } from '../../helpers/custom-validators/activitypub/actor'
|
import { isActorObjectValid } from '../../helpers/custom-validators/activitypub/actor'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { retryTransactionWrapper } from '../../helpers/database-utils'
|
import { retryTransactionWrapper } from '../../helpers/database-utils'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
|
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
|
||||||
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
|
||||||
import { CONFIG, sequelizeTypescript } from '../../initializers'
|
import { AVATAR_MIMETYPE_EXT, CONFIG, sequelizeTypescript } from '../../initializers'
|
||||||
import { AccountModel } from '../../models/account/account'
|
import { AccountModel } from '../../models/account/account'
|
||||||
import { ActorModel } from '../../models/activitypub/actor'
|
import { ActorModel } from '../../models/activitypub/actor'
|
||||||
import { AvatarModel } from '../../models/avatar/avatar'
|
import { AvatarModel } from '../../models/avatar/avatar'
|
||||||
|
@ -84,10 +84,52 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchActorTotalItems (url: string) {
|
||||||
|
const options = {
|
||||||
|
uri: url,
|
||||||
|
method: 'GET',
|
||||||
|
json: true,
|
||||||
|
activityPub: true
|
||||||
|
}
|
||||||
|
|
||||||
|
let requestResult
|
||||||
|
try {
|
||||||
|
requestResult = await doRequest(options)
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn('Cannot fetch remote actor count %s.', url, err)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestResult.totalItems ? requestResult.totalItems : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
|
||||||
|
if (
|
||||||
|
actorJSON.icon && actorJSON.icon.type === 'Image' && AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
|
||||||
|
isActivityPubUrlValid(actorJSON.icon.url)
|
||||||
|
) {
|
||||||
|
const extension = AVATAR_MIMETYPE_EXT[actorJSON.icon.mediaType]
|
||||||
|
|
||||||
|
const avatarName = uuidv4() + extension
|
||||||
|
const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
|
||||||
|
|
||||||
|
await doRequestAndSaveToFile({
|
||||||
|
method: 'GET',
|
||||||
|
uri: actorJSON.icon.url
|
||||||
|
}, destPath)
|
||||||
|
|
||||||
|
return avatarName
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getOrCreateActorAndServerAndModel,
|
getOrCreateActorAndServerAndModel,
|
||||||
buildActorInstance,
|
buildActorInstance,
|
||||||
setAsyncActorKeys
|
setAsyncActorKeys,
|
||||||
|
fetchActorTotalItems,
|
||||||
|
fetchAvatarIfExists
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -166,7 +208,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
|
||||||
const requestResult = await doRequest(options)
|
const requestResult = await doRequest(options)
|
||||||
const actorJSON: ActivityPubActor = requestResult.body
|
const actorJSON: ActivityPubActor = requestResult.body
|
||||||
|
|
||||||
if (isRemoteActorValid(actorJSON) === false) {
|
if (isActorObjectValid(actorJSON) === false) {
|
||||||
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
|
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -190,22 +232,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
|
||||||
followingUrl: actorJSON.following
|
followingUrl: actorJSON.following
|
||||||
})
|
})
|
||||||
|
|
||||||
// Fetch icon?
|
const avatarName = await fetchAvatarIfExists(actorJSON)
|
||||||
let avatarName: string = undefined
|
|
||||||
if (
|
|
||||||
actorJSON.icon && actorJSON.icon.type === 'Image' && actorJSON.icon.mediaType === 'image/png' &&
|
|
||||||
isActivityPubUrlValid(actorJSON.icon.url)
|
|
||||||
) {
|
|
||||||
const extension = actorJSON.icon.mediaType === 'image/png' ? '.png' : '.jpg'
|
|
||||||
|
|
||||||
avatarName = uuidv4() + extension
|
|
||||||
const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
|
|
||||||
|
|
||||||
await doRequestAndSaveToFile({
|
|
||||||
method: 'GET',
|
|
||||||
uri: actorJSON.icon.url
|
|
||||||
}, destPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = actorJSON.name || actorJSON.preferredUsername
|
const name = actorJSON.name || actorJSON.preferredUsername
|
||||||
return {
|
return {
|
||||||
|
@ -217,25 +244,6 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchActorTotalItems (url: string) {
|
|
||||||
const options = {
|
|
||||||
uri: url,
|
|
||||||
method: 'GET',
|
|
||||||
json: true,
|
|
||||||
activityPub: true
|
|
||||||
}
|
|
||||||
|
|
||||||
let requestResult
|
|
||||||
try {
|
|
||||||
requestResult = await doRequest(options)
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn('Cannot fetch remote actor count %s.', url, err)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestResult.totalItems ? requestResult.totalItems : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
|
function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) {
|
||||||
const account = new AccountModel({
|
const account = new AccountModel({
|
||||||
name: result.name,
|
name: result.name,
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import { ActivityUpdate } from '../../../../shared/models/activitypub'
|
import { ActivityUpdate } from '../../../../shared/models/activitypub'
|
||||||
|
import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
|
||||||
|
import { VideoTorrentObject } from '../../../../shared/models/activitypub/objects'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
import { logger } from '../../../helpers/logger'
|
import { logger } from '../../../helpers/logger'
|
||||||
import { resetSequelizeInstance } from '../../../helpers/utils'
|
import { resetSequelizeInstance } from '../../../helpers/utils'
|
||||||
import { sequelizeTypescript } from '../../../initializers'
|
import { sequelizeTypescript } from '../../../initializers'
|
||||||
|
import { AccountModel } from '../../../models/account/account'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
|
import { AvatarModel } from '../../../models/avatar/avatar'
|
||||||
import { TagModel } from '../../../models/video/tag'
|
import { TagModel } from '../../../models/video/tag'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoFileModel } from '../../../models/video/video-file'
|
import { VideoFileModel } from '../../../models/video/video-file'
|
||||||
import { getOrCreateActorAndServerAndModel } from '../actor'
|
import { fetchActorTotalItems, fetchAvatarIfExists, getOrCreateActorAndServerAndModel } from '../actor'
|
||||||
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
|
||||||
|
|
||||||
async function processUpdateActivity (activity: ActivityUpdate) {
|
async function processUpdateActivity (activity: ActivityUpdate) {
|
||||||
|
@ -16,6 +20,8 @@ async function processUpdateActivity (activity: ActivityUpdate) {
|
||||||
|
|
||||||
if (activity.object.type === 'Video') {
|
if (activity.object.type === 'Video') {
|
||||||
return processUpdateVideo(actor, activity)
|
return processUpdateVideo(actor, activity)
|
||||||
|
} else if (activity.object.type === 'Person') {
|
||||||
|
return processUpdateAccount(actor, activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -39,11 +45,11 @@ function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
const videoAttributesToUpdate = activity.object
|
const videoAttributesToUpdate = activity.object as VideoTorrentObject
|
||||||
|
|
||||||
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
|
logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
|
||||||
let videoInstance: VideoModel
|
let videoInstance: VideoModel
|
||||||
let videoFieldsSave: object
|
let videoFieldsSave: any
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
|
@ -54,6 +60,8 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
|
const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
|
||||||
if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
|
if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
|
||||||
|
|
||||||
|
videoFieldsSave = videoInstance.toJSON()
|
||||||
|
|
||||||
const videoChannel = videoInstance.VideoChannel
|
const videoChannel = videoInstance.VideoChannel
|
||||||
if (videoChannel.Account.Actor.id !== actor.id) {
|
if (videoChannel.Account.Actor.id !== actor.id) {
|
||||||
throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
|
throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
|
||||||
|
@ -102,3 +110,83 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processUpdateAccount (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
|
const options = {
|
||||||
|
arguments: [ actor, activity ],
|
||||||
|
errorMessage: 'Cannot update the remote account with many retries'
|
||||||
|
}
|
||||||
|
|
||||||
|
return retryTransactionWrapper(updateRemoteAccount, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateRemoteAccount (actor: ActorModel, activity: ActivityUpdate) {
|
||||||
|
const accountAttributesToUpdate = activity.object as ActivityPubActor
|
||||||
|
|
||||||
|
logger.debug('Updating remote account "%s".', accountAttributesToUpdate.uuid)
|
||||||
|
let actorInstance: ActorModel
|
||||||
|
let accountInstance: AccountModel
|
||||||
|
let actorFieldsSave: object
|
||||||
|
let accountFieldsSave: object
|
||||||
|
|
||||||
|
// Fetch icon?
|
||||||
|
const avatarName = await fetchAvatarIfExists(accountAttributesToUpdate)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sequelizeTypescript.transaction(async t => {
|
||||||
|
actorInstance = await ActorModel.loadByUrl(accountAttributesToUpdate.id, t)
|
||||||
|
if (!actorInstance) throw new Error('Actor ' + accountAttributesToUpdate.id + ' not found.')
|
||||||
|
|
||||||
|
actorFieldsSave = actorInstance.toJSON()
|
||||||
|
accountInstance = actorInstance.Account
|
||||||
|
accountFieldsSave = actorInstance.Account.toJSON()
|
||||||
|
|
||||||
|
const followersCount = await fetchActorTotalItems(accountAttributesToUpdate.followers)
|
||||||
|
const followingCount = await fetchActorTotalItems(accountAttributesToUpdate.following)
|
||||||
|
|
||||||
|
actorInstance.set('type', accountAttributesToUpdate.type)
|
||||||
|
actorInstance.set('uuid', accountAttributesToUpdate.uuid)
|
||||||
|
actorInstance.set('preferredUsername', accountAttributesToUpdate.preferredUsername)
|
||||||
|
actorInstance.set('url', accountAttributesToUpdate.id)
|
||||||
|
actorInstance.set('publicKey', accountAttributesToUpdate.publicKey.publicKeyPem)
|
||||||
|
actorInstance.set('followersCount', followersCount)
|
||||||
|
actorInstance.set('followingCount', followingCount)
|
||||||
|
actorInstance.set('inboxUrl', accountAttributesToUpdate.inbox)
|
||||||
|
actorInstance.set('outboxUrl', accountAttributesToUpdate.outbox)
|
||||||
|
actorInstance.set('sharedInboxUrl', accountAttributesToUpdate.endpoints.sharedInbox)
|
||||||
|
actorInstance.set('followersUrl', accountAttributesToUpdate.followers)
|
||||||
|
actorInstance.set('followingUrl', accountAttributesToUpdate.following)
|
||||||
|
|
||||||
|
if (avatarName !== undefined) {
|
||||||
|
if (actorInstance.avatarId) {
|
||||||
|
await actorInstance.Avatar.destroy({ transaction: t })
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatar = await AvatarModel.create({
|
||||||
|
filename: avatarName
|
||||||
|
}, { transaction: t })
|
||||||
|
|
||||||
|
actor.set('avatarId', avatar.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
await actor.save({ transaction: t })
|
||||||
|
|
||||||
|
actor.Account.set('name', accountAttributesToUpdate.name || accountAttributesToUpdate.preferredUsername)
|
||||||
|
await actor.Account.save({ transaction: t })
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Remote account with uuid %s updated', accountAttributesToUpdate.uuid)
|
||||||
|
} catch (err) {
|
||||||
|
if (actorInstance !== undefined && actorFieldsSave !== undefined) {
|
||||||
|
resetSequelizeInstance(actorInstance, actorFieldsSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accountInstance !== undefined && accountFieldsSave !== undefined) {
|
||||||
|
resetSequelizeInstance(accountInstance, accountFieldsSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is just a debug because we will retry the insert
|
||||||
|
logger.debug('Cannot update the remote account.', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
|
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
|
||||||
import { VideoPrivacy } from '../../../../shared/models/videos'
|
import { VideoPrivacy } from '../../../../shared/models/videos'
|
||||||
|
import { UserModel } from '../../../models/account/user'
|
||||||
import { ActorModel } from '../../../models/activitypub/actor'
|
import { ActorModel } from '../../../models/activitypub/actor'
|
||||||
import { VideoModel } from '../../../models/video/video'
|
import { VideoModel } from '../../../models/video/video'
|
||||||
import { VideoShareModel } from '../../../models/video/video-share'
|
import { VideoShareModel } from '../../../models/video/video-share'
|
||||||
|
@ -22,9 +23,24 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) {
|
||||||
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendUpdateUser (user: UserModel, t: Transaction) {
|
||||||
|
const byActor = user.Account.Actor
|
||||||
|
|
||||||
|
const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString())
|
||||||
|
const accountObject = user.Account.toActivityPubObject()
|
||||||
|
const audience = await getAudience(byActor, t)
|
||||||
|
const data = await updateActivityData(url, byActor, accountObject, t, audience)
|
||||||
|
|
||||||
|
const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t)
|
||||||
|
actorsInvolved.push(byActor)
|
||||||
|
|
||||||
|
return broadcastToFollowers(data, byActor, actorsInvolved, t)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
sendUpdateUser,
|
||||||
sendUpdateVideo
|
sendUpdateVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,12 @@ import * as express from 'express'
|
||||||
import 'express-validator'
|
import 'express-validator'
|
||||||
import { SortType } from '../helpers/utils'
|
import { SortType } from '../helpers/utils'
|
||||||
|
|
||||||
|
function setAccountsSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
|
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
function setUsersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||||
|
|
||||||
|
@ -82,5 +88,6 @@ export {
|
||||||
setFollowersSort,
|
setFollowersSort,
|
||||||
setFollowingSort,
|
setFollowingSort,
|
||||||
setJobsSort,
|
setJobsSort,
|
||||||
setVideoCommentThreadsSort
|
setVideoCommentThreadsSort,
|
||||||
|
setAccountsSort
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { param } from 'express-validator/check'
|
import { param } from 'express-validator/check'
|
||||||
import { isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts'
|
import { isAccountIdExist, isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts'
|
||||||
|
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { areValidationErrors } from './utils'
|
import { areValidationErrors } from './utils'
|
||||||
|
|
||||||
|
@ -17,8 +18,22 @@ const localAccountValidator = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const accountsGetValidator = [
|
||||||
|
param('id').custom(isIdOrUUIDValid).withMessage('Should have a valid id'),
|
||||||
|
|
||||||
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
|
logger.debug('Checking accountsGetValidator parameters', { parameters: req.params })
|
||||||
|
|
||||||
|
if (areValidationErrors(req, res)) return
|
||||||
|
if (!await isAccountIdExist(req.params.id, res)) return
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export {
|
export {
|
||||||
localAccountValidator
|
localAccountValidator,
|
||||||
|
accountsGetValidator
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { areValidationErrors } from './utils'
|
||||||
|
|
||||||
// Initialize constants here for better performances
|
// Initialize constants here for better performances
|
||||||
const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
|
const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
|
||||||
|
const SORTABLE_ACCOUNTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS)
|
||||||
const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
|
const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS)
|
||||||
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
|
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
|
||||||
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
||||||
|
@ -16,6 +17,7 @@ const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOW
|
||||||
const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING)
|
const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING)
|
||||||
|
|
||||||
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
|
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
|
||||||
|
const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
|
||||||
const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
|
const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
|
||||||
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
|
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
|
||||||
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
||||||
|
@ -33,6 +35,7 @@ export {
|
||||||
videoChannelsSortValidator,
|
videoChannelsSortValidator,
|
||||||
videosSortValidator,
|
videosSortValidator,
|
||||||
blacklistSortValidator,
|
blacklistSortValidator,
|
||||||
|
accountsSortValidator,
|
||||||
followersSortValidator,
|
followersSortValidator,
|
||||||
followingSortValidator,
|
followingSortValidator,
|
||||||
jobsSortValidator,
|
jobsSortValidator,
|
||||||
|
|
|
@ -18,8 +18,9 @@ import { isUserUsernameValid } from '../../helpers/custom-validators/users'
|
||||||
import { sendDeleteActor } from '../../lib/activitypub/send'
|
import { sendDeleteActor } from '../../lib/activitypub/send'
|
||||||
import { ActorModel } from '../activitypub/actor'
|
import { ActorModel } from '../activitypub/actor'
|
||||||
import { ApplicationModel } from '../application/application'
|
import { ApplicationModel } from '../application/application'
|
||||||
|
import { AvatarModel } from '../avatar/avatar'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../utils'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { UserModel } from './user'
|
import { UserModel } from './user'
|
||||||
|
|
||||||
|
@ -32,6 +33,10 @@ import { UserModel } from './user'
|
||||||
{
|
{
|
||||||
model: () => ServerModel,
|
model: () => ServerModel,
|
||||||
required: false
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: () => AvatarModel,
|
||||||
|
required: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -166,6 +171,22 @@ export class AccountModel extends Model<AccountModel> {
|
||||||
return AccountModel.findOne(query)
|
return AccountModel.findOne(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static listForApi (start: number, count: number, sort: string) {
|
||||||
|
const query = {
|
||||||
|
offset: start,
|
||||||
|
limit: count,
|
||||||
|
order: [ getSort(sort) ]
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountModel.findAndCountAll(query)
|
||||||
|
.then(({ rows, count }) => {
|
||||||
|
return {
|
||||||
|
data: rows,
|
||||||
|
total: count
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
toFormattedJSON (): Account {
|
toFormattedJSON (): Account {
|
||||||
const actor = this.Actor.toFormattedJSON()
|
const actor = this.Actor.toFormattedJSON()
|
||||||
const account = {
|
const account = {
|
||||||
|
|
|
@ -372,6 +372,6 @@ export class ActorModel extends Model<ActorModel> {
|
||||||
getAvatarUrl () {
|
getAvatarUrl () {
|
||||||
if (!this.avatarId) return undefined
|
if (!this.avatarId) return undefined
|
||||||
|
|
||||||
return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath
|
return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import * as Sequelize from 'sequelize'
|
import * as Sequelize from 'sequelize'
|
||||||
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
||||||
|
import { AccountModel } from '../account/account'
|
||||||
import { ActorModel } from '../activitypub/actor'
|
import { ActorModel } from '../activitypub/actor'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
import { VideoChannelModel } from './video-channel'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
FULL = 'FULL',
|
FULL = 'FULL',
|
||||||
|
@ -99,4 +101,42 @@ export class VideoShareModel extends Model<VideoShareModel> {
|
||||||
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
|
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
|
||||||
.then(res => res.map(r => r.Actor))
|
.then(res => res.map(r => r.Actor))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction) {
|
||||||
|
const query = {
|
||||||
|
attributes: [],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: ActorModel,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: [],
|
||||||
|
model: VideoModel,
|
||||||
|
required: true,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
attributes: [],
|
||||||
|
model: VideoChannelModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
attributes: [],
|
||||||
|
model: AccountModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
where: {
|
||||||
|
actorId: actorOwnerId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
transaction: t
|
||||||
|
}
|
||||||
|
|
||||||
|
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
|
||||||
|
.then(res => res.map(r => r.Actor))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
|
import 'mocha'
|
||||||
|
|
||||||
|
import { flushTests, killallServers, runServer, ServerInfo } from '../../utils'
|
||||||
|
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
|
||||||
|
import { getAccount } from '../../utils/users/accounts'
|
||||||
|
|
||||||
|
describe('Test users API validators', function () {
|
||||||
|
const path = '/api/v1/accounts/'
|
||||||
|
let server: ServerInfo
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(20000)
|
||||||
|
|
||||||
|
await flushTests()
|
||||||
|
|
||||||
|
server = await runServer(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('When listing accounts', function () {
|
||||||
|
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 getting an account', function () {
|
||||||
|
it('Should return 404 with a non existing id', async function () {
|
||||||
|
await getAccount(server.url, 4545454, 404)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
killallServers([ server ])
|
||||||
|
|
||||||
|
// Keep the logs if the test failed
|
||||||
|
if (this['ok']) {
|
||||||
|
await flushTests()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,4 +1,5 @@
|
||||||
// Order of the tests we want to execute
|
// Order of the tests we want to execute
|
||||||
|
import './accounts'
|
||||||
import './follows'
|
import './follows'
|
||||||
import './jobs'
|
import './jobs'
|
||||||
import './services'
|
import './services'
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
|
@ -5,3 +5,4 @@ import './videos/multiple-servers'
|
||||||
import './server/follows'
|
import './server/follows'
|
||||||
import './server/jobs'
|
import './server/jobs'
|
||||||
import './videos/video-comments'
|
import './videos/video-comments'
|
||||||
|
import './users/users-multiple-servers'
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/* tslint:disable:no-unused-expression */
|
||||||
|
|
||||||
|
import * as chai from 'chai'
|
||||||
|
import 'mocha'
|
||||||
|
import { Account } from '../../../../shared/models/actors'
|
||||||
|
import { doubleFollow, flushAndRunMultipleServers, wait } from '../../utils'
|
||||||
|
import {
|
||||||
|
flushTests, getMyUserInformation, killallServers, ServerInfo, testVideoImage, updateMyAvatar,
|
||||||
|
uploadVideo
|
||||||
|
} from '../../utils/index'
|
||||||
|
import { getAccount, getAccountsList } from '../../utils/users/accounts'
|
||||||
|
import { setAccessTokensToServers } from '../../utils/users/login'
|
||||||
|
|
||||||
|
const expect = chai.expect
|
||||||
|
|
||||||
|
describe('Test users with multiple servers', function () {
|
||||||
|
let servers: ServerInfo[] = []
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
servers = await flushAndRunMultipleServers(3)
|
||||||
|
|
||||||
|
// Get the access tokens
|
||||||
|
await setAccessTokensToServers(servers)
|
||||||
|
|
||||||
|
// Server 1 and server 2 follow each other
|
||||||
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
// Server 1 and server 3 follow each other
|
||||||
|
await doubleFollow(servers[0], servers[2])
|
||||||
|
// Server 2 and server 3 follow each other
|
||||||
|
await doubleFollow(servers[1], servers[2])
|
||||||
|
|
||||||
|
// The root user of server 1 is propagated to servers 2 and 3
|
||||||
|
await uploadVideo(servers[0].url, servers[0].accessToken, {})
|
||||||
|
|
||||||
|
await wait(5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should be able to update my avatar', async function () {
|
||||||
|
this.timeout(10000)
|
||||||
|
|
||||||
|
const fixture = 'avatar2.png'
|
||||||
|
|
||||||
|
await updateMyAvatar({
|
||||||
|
url: servers[0].url,
|
||||||
|
accessToken: servers[0].accessToken,
|
||||||
|
fixture
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
|
||||||
|
const user = res.body
|
||||||
|
|
||||||
|
const test = await testVideoImage(servers[0].url, 'avatar2-resized', user.account.avatar.path, '.png')
|
||||||
|
expect(test).to.equal(true)
|
||||||
|
|
||||||
|
await wait(5000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should have updated my avatar on other servers too', async function () {
|
||||||
|
for (const server of servers) {
|
||||||
|
const resAccounts = await getAccountsList(server.url, '-createdAt')
|
||||||
|
|
||||||
|
const rootServer1List = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:9001') as Account
|
||||||
|
expect(rootServer1List).not.to.be.undefined
|
||||||
|
|
||||||
|
const resAccount = await getAccount(server.url, rootServer1List.id)
|
||||||
|
const rootServer1Get = resAccount.body as Account
|
||||||
|
expect(rootServer1Get.name).to.equal('root')
|
||||||
|
expect(rootServer1Get.host).to.equal('localhost:9001')
|
||||||
|
|
||||||
|
const test = await testVideoImage(server.url, 'avatar2-resized', rootServer1Get.avatar.path, '.png')
|
||||||
|
expect(test).to.equal(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
killallServers(servers)
|
||||||
|
|
||||||
|
// Keep the logs if the test failed
|
||||||
|
if (this[ 'ok' ]) {
|
||||||
|
await flushTests()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { makeGetRequest } from '../requests/requests'
|
||||||
|
|
||||||
|
function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) {
|
||||||
|
const path = '/api/v1/accounts'
|
||||||
|
|
||||||
|
return makeGetRequest({
|
||||||
|
url,
|
||||||
|
query: { sort },
|
||||||
|
path,
|
||||||
|
statusCodeExpected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAccount (url: string, accountId: number | string, statusCodeExpected = 200) {
|
||||||
|
const path = '/api/v1/accounts/' + accountId
|
||||||
|
|
||||||
|
return makeGetRequest({
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
statusCodeExpected
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
getAccount,
|
||||||
|
getAccountsList
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ActivityPubActor } from './activitypub-actor'
|
||||||
import { ActivityPubSignature } from './activitypub-signature'
|
import { ActivityPubSignature } from './activitypub-signature'
|
||||||
import { VideoTorrentObject } from './objects'
|
import { VideoTorrentObject } from './objects'
|
||||||
import { DislikeObject } from './objects/dislike-object'
|
import { DislikeObject } from './objects/dislike-object'
|
||||||
|
@ -33,7 +34,7 @@ export interface ActivityCreate extends BaseActivity {
|
||||||
|
|
||||||
export interface ActivityUpdate extends BaseActivity {
|
export interface ActivityUpdate extends BaseActivity {
|
||||||
type: 'Update'
|
type: 'Update'
|
||||||
object: VideoTorrentObject
|
object: VideoTorrentObject | ActivityPubActor
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActivityDelete extends BaseActivity {
|
export interface ActivityDelete extends BaseActivity {
|
||||||
|
|
Loading…
Reference in New Issue