Skip to content
Snippets Groups Projects
Unverified Commit 265ba139 authored by Chocobozzz's avatar Chocobozzz
Browse files

Send account activitypub update events

parent 9bce8112
No related branches found
No related tags found
No related merge requests found
Showing
with 377 additions and 68 deletions
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 { serverRouter } from './server'
import { usersRouter } from './users'
import { accountsRouter } from './accounts'
import { videosRouter } from './videos'
const apiRouter = express.Router()
......@@ -13,6 +14,7 @@ apiRouter.use('/server', serverRouter)
apiRouter.use('/oauth-clients', oauthClientsRouter)
apiRouter.use('/config', configRouter)
apiRouter.use('/users', usersRouter)
apiRouter.use('/accounts', accountsRouter)
apiRouter.use('/videos', videosRouter)
apiRouter.use('/jobs', jobsRouter)
apiRouter.use('/ping', pong)
......
......@@ -8,6 +8,7 @@ import { retryTransactionWrapper } from '../../helpers/database-utils'
import { logger } from '../../helpers/logger'
import { createReqFiles, getFormattedObjects } from '../../helpers/utils'
import { AVATAR_MIMETYPE_EXT, AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../../initializers'
import { sendUpdateUser } from '../../lib/activitypub/send'
import { createUserAccountAndChannel } from '../../lib/user'
import {
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) {
const body: UserUpdateMe = req.body
// FIXME: user is not already a Sequelize instance?
const user = res.locals.oauth.token.user
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
await user.save()
await sendUpdateUser(user, undefined)
return res.sendStatus(204)
}
async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
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 source = join(avatarDir, avatarPhysicalFile.filename)
......@@ -252,12 +254,19 @@ async function updateMyAvatar (req: express.Request, res: express.Response, next
}, { transaction: t })
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.Avatar = avatar
await actor.save({ transaction: t })
await sendUpdateUser(user, undefined)
return { actor, avatar }
})
......@@ -278,6 +287,8 @@ async function updateUser (req: express.Request, res: express.Response, next: ex
await user.save()
// Don't need to send this update to followers, these attributes are not propagated
return res.sendStatus(204)
}
......
import * as validator from 'validator'
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 { isActivityPubUrlValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
......@@ -64,7 +64,8 @@ function checkCreateActivity (activity: any) {
}
function checkUpdateActivity (activity: any) {
return isVideoTorrentUpdateActivityValid(activity)
return isVideoTorrentUpdateActivityValid(activity) ||
isActorUpdateActivityValid(activity)
}
function checkDeleteActivity (activity: any) {
......
......@@ -45,22 +45,22 @@ function isActorPrivateKeyValid (privateKey: string) {
validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY)
}
function isRemoteActorValid (remoteActor: any) {
return exists(remoteActor) &&
isActivityPubUrlValid(remoteActor.id) &&
isActorTypeValid(remoteActor.type) &&
isActivityPubUrlValid(remoteActor.following) &&
isActivityPubUrlValid(remoteActor.followers) &&
isActivityPubUrlValid(remoteActor.inbox) &&
isActivityPubUrlValid(remoteActor.outbox) &&
isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
isActivityPubUrlValid(remoteActor.url) &&
isActorPublicKeyObjectValid(remoteActor.publicKey) &&
isActorEndpointsObjectValid(remoteActor.endpoints) &&
setValidAttributedTo(remoteActor) &&
function isActorObjectValid (actor: any) {
return exists(actor) &&
isActivityPubUrlValid(actor.id) &&
isActorTypeValid(actor.type) &&
isActivityPubUrlValid(actor.following) &&
isActivityPubUrlValid(actor.followers) &&
isActivityPubUrlValid(actor.inbox) &&
isActivityPubUrlValid(actor.outbox) &&
isActorPreferredUsernameValid(actor.preferredUsername) &&
isActivityPubUrlValid(actor.url) &&
isActorPublicKeyObjectValid(actor.publicKey) &&
isActorEndpointsObjectValid(actor.endpoints) &&
setValidAttributedTo(actor) &&
// If this is not an account, it should be attributed to an account
// 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) {
......@@ -84,6 +84,11 @@ function isActorAcceptActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Accept')
}
function isActorUpdateActivityValid (activity: any) {
return isBaseActivityValid(activity, 'Update') &&
isActorObjectValid(activity.object)
}
// ---------------------------------------------------------------------------
export {
......@@ -93,11 +98,11 @@ export {
isActorPublicKeyValid,
isActorPreferredUsernameValid,
isActorPrivateKeyValid,
isRemoteActorValid,
isActorObjectValid,
isActorFollowingCountValid,
isActorFollowersCountValid,
isActorFollowActivityValid,
isActorAcceptActivityValid,
isActorDeleteActivityValid,
isActorNameValid
isActorUpdateActivityValid
}
......@@ -22,6 +22,7 @@ const PAGINATION_COUNT_DEFAULT = 15
// Sortable columns per schema
const SORTABLE_COLUMNS = {
USERS: [ 'id', 'username', 'createdAt' ],
ACCOUNTS: [ 'createdAt' ],
JOBS: [ 'id', 'createdAt' ],
VIDEO_ABUSES: [ 'id', 'createdAt' ],
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
......
......@@ -5,13 +5,13 @@ import * as url from 'url'
import * as uuidv4 from 'uuid/v4'
import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
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 { retryTransactionWrapper } from '../../helpers/database-utils'
import { logger } from '../../helpers/logger'
import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
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 { ActorModel } from '../../models/activitypub/actor'
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 {
getOrCreateActorAndServerAndModel,
buildActorInstance,
setAsyncActorKeys
setAsyncActorKeys,
fetchActorTotalItems,
fetchAvatarIfExists
}
// ---------------------------------------------------------------------------
......@@ -166,7 +208,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
const requestResult = await doRequest(options)
const actorJSON: ActivityPubActor = requestResult.body
if (isRemoteActorValid(actorJSON) === false) {
if (isActorObjectValid(actorJSON) === false) {
logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
return undefined
}
......@@ -190,22 +232,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<FetchRemoteActorResu
followingUrl: actorJSON.following
})
// Fetch icon?
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 avatarName = await fetchAvatarIfExists(actorJSON)
const name = actorJSON.name || actorJSON.preferredUsername
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) {
const account = new AccountModel({
name: result.name,
......
import * as Bluebird from 'bluebird'
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 { logger } from '../../../helpers/logger'
import { resetSequelizeInstance } from '../../../helpers/utils'
import { sequelizeTypescript } from '../../../initializers'
import { AccountModel } from '../../../models/account/account'
import { ActorModel } from '../../../models/activitypub/actor'
import { AvatarModel } from '../../../models/avatar/avatar'
import { TagModel } from '../../../models/video/tag'
import { VideoModel } from '../../../models/video/video'
import { VideoFileModel } from '../../../models/video/video-file'
import { getOrCreateActorAndServerAndModel } from '../actor'
import { fetchActorTotalItems, fetchAvatarIfExists, getOrCreateActorAndServerAndModel } from '../actor'
import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
async function processUpdateActivity (activity: ActivityUpdate) {
......@@ -16,6 +20,8 @@ async function processUpdateActivity (activity: ActivityUpdate) {
if (activity.object.type === 'Video') {
return processUpdateVideo(actor, activity)
} else if (activity.object.type === 'Person') {
return processUpdateAccount(actor, activity)
}
return
......@@ -39,11 +45,11 @@ function processUpdateVideo (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)
let videoInstance: VideoModel
let videoFieldsSave: object
let videoFieldsSave: any
try {
await sequelizeTypescript.transaction(async t => {
......@@ -54,6 +60,8 @@ async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
videoFieldsSave = videoInstance.toJSON()
const videoChannel = videoInstance.VideoChannel
if (videoChannel.Account.Actor.id !== actor.id) {
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
}
}
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
}
}
import { Transaction } from 'sequelize'
import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub'
import { VideoPrivacy } from '../../../../shared/models/videos'
import { UserModel } from '../../../models/account/user'
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
import { VideoShareModel } from '../../../models/video/video-share'
......@@ -22,9 +23,24 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction) {
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 {
sendUpdateUser,
sendUpdateVideo
}
......
......@@ -2,6 +2,12 @@ import * as express from 'express'
import 'express-validator'
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) {
if (!req.query.sort) req.query.sort = '-createdAt'
......@@ -82,5 +88,6 @@ export {
setFollowersSort,
setFollowingSort,
setJobsSort,
setVideoCommentThreadsSort
setVideoCommentThreadsSort,
setAccountsSort
}
import * as express from 'express'
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 { 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 {
localAccountValidator
localAccountValidator,
accountsGetValidator
}
......@@ -6,6 +6,7 @@ import { areValidationErrors } from './utils'
// Initialize constants here for better performances
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_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
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 usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS)
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
......@@ -33,6 +35,7 @@ export {
videoChannelsSortValidator,
videosSortValidator,
blacklistSortValidator,
accountsSortValidator,
followersSortValidator,
followingSortValidator,
jobsSortValidator,
......
......@@ -18,8 +18,9 @@ import { isUserUsernameValid } from '../../helpers/custom-validators/users'
import { sendDeleteActor } from '../../lib/activitypub/send'
import { ActorModel } from '../activitypub/actor'
import { ApplicationModel } from '../application/application'
import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server'
import { throwIfNotValid } from '../utils'
import { getSort, throwIfNotValid } from '../utils'
import { VideoChannelModel } from '../video/video-channel'
import { UserModel } from './user'
......@@ -32,6 +33,10 @@ import { UserModel } from './user'
{
model: () => ServerModel,
required: false
},
{
model: () => AvatarModel,
required: false
}
]
}
......@@ -166,6 +171,22 @@ export class AccountModel extends Model<AccountModel> {
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 {
const actor = this.Actor.toFormattedJSON()
const account = {
......
......@@ -372,6 +372,6 @@ export class ActorModel extends Model<ActorModel> {
getAvatarUrl () {
if (!this.avatarId) return undefined
return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath
return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath()
}
}
import * as Sequelize from 'sequelize'
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { AccountModel } from '../account/account'
import { ActorModel } from '../activitypub/actor'
import { VideoModel } from './video'
import { VideoChannelModel } from './video-channel'
enum ScopeNames {
FULL = 'FULL',
......@@ -99,4 +101,42 @@ export class VideoShareModel extends Model<VideoShareModel> {
return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
.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))
}
}
/* 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()
}
})
})
// Order of the tests we want to execute
import './accounts'
import './follows'
import './jobs'
import './services'
......
server/tests/api/fixtures/avatar2-resized.png

2.29 KiB

server/tests/api/fixtures/avatar2.png

4.74 KiB

......@@ -5,3 +5,4 @@ import './videos/multiple-servers'
import './server/follows'
import './server/jobs'
import './videos/video-comments'
import './users/users-multiple-servers'
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment