Handle follow/accept
This commit is contained in:
parent
571389d43b
commit
7a7724e66e
29 changed files with 493 additions and 208 deletions
|
@ -15,4 +15,4 @@
|
|||
</video>
|
||||
|
||||
</body>
|
||||
</html
|
||||
</html>
|
||||
|
|
|
@ -16,12 +16,12 @@ activityPubClientRouter.get('/account/:name',
|
|||
executeIfActivityPub(asyncMiddleware(accountController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/account/:name/followers',
|
||||
activityPubClientRouter.get('/account/:nameWithHost/followers',
|
||||
executeIfActivityPub(localAccountValidator),
|
||||
executeIfActivityPub(asyncMiddleware(accountFollowersController))
|
||||
)
|
||||
|
||||
activityPubClientRouter.get('/account/:name/following',
|
||||
activityPubClientRouter.get('/account/:nameWithHost/following',
|
||||
executeIfActivityPub(localAccountValidator),
|
||||
executeIfActivityPub(asyncMiddleware(accountFollowingController))
|
||||
)
|
||||
|
@ -46,7 +46,7 @@ async function accountFollowersController (req: express.Request, res: express.Re
|
|||
const page = req.params.page || 1
|
||||
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
|
||||
|
||||
const result = await db.Account.listFollowerUrlsForApi(account.name, start, count)
|
||||
const result = await db.Account.listFollowerUrlsForApi(account.id, start, count)
|
||||
const activityPubResult = activityPubCollectionPagination(req.url, page, result)
|
||||
|
||||
return res.json(activityPubResult)
|
||||
|
@ -58,7 +58,7 @@ async function accountFollowingController (req: express.Request, res: express.Re
|
|||
const page = req.params.page || 1
|
||||
const { start, count } = pageToStartAndCount(page, ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE)
|
||||
|
||||
const result = await db.Account.listFollowingUrlsForApi(account.name, start, count)
|
||||
const result = await db.Account.listFollowingUrlsForApi(account.id, start, count)
|
||||
const activityPubResult = activityPubCollectionPagination(req.url, page, result)
|
||||
|
||||
return res.json(activityPubResult)
|
||||
|
|
|
@ -3,26 +3,41 @@ import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, Activity
|
|||
import { logger } from '../../helpers'
|
||||
import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
|
||||
import { processCreateActivity, processFlagActivity, processUpdateActivity } from '../../lib'
|
||||
import { processAcceptActivity } from '../../lib/activitypub/process-accept'
|
||||
import { processAddActivity } from '../../lib/activitypub/process-add'
|
||||
import { asyncMiddleware, checkSignature, signatureValidator } from '../../middlewares'
|
||||
import { processDeleteActivity } from '../../lib/activitypub/process-delete'
|
||||
import { processFollowActivity } from '../../lib/activitypub/process-follow'
|
||||
import { asyncMiddleware, checkSignature, localAccountValidator, signatureValidator } from '../../middlewares'
|
||||
import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
|
||||
const processActivity: { [ P in ActivityType ]: (activity: Activity) => Promise<any> } = {
|
||||
const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountInstance) => Promise<any> } = {
|
||||
Create: processCreateActivity,
|
||||
Add: processAddActivity,
|
||||
Update: processUpdateActivity,
|
||||
Flag: processFlagActivity
|
||||
Flag: processFlagActivity,
|
||||
Delete: processDeleteActivity,
|
||||
Follow: processFollowActivity,
|
||||
Accept: processAcceptActivity
|
||||
}
|
||||
|
||||
const inboxRouter = express.Router()
|
||||
|
||||
inboxRouter.post('/',
|
||||
inboxRouter.post('/inbox',
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
activityPubValidator,
|
||||
asyncMiddleware(inboxController)
|
||||
)
|
||||
|
||||
inboxRouter.post('/:nameWithHost/inbox',
|
||||
signatureValidator,
|
||||
asyncMiddleware(checkSignature),
|
||||
localAccountValidator,
|
||||
activityPubValidator,
|
||||
asyncMiddleware(inboxController)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -46,12 +61,12 @@ async function inboxController (req: express.Request, res: express.Response, nex
|
|||
// Only keep activities we are able to process
|
||||
activities = activities.filter(a => isActivityValid(a))
|
||||
|
||||
await processActivities(activities)
|
||||
await processActivities(activities, res.locals.account)
|
||||
|
||||
res.status(204).end()
|
||||
}
|
||||
|
||||
async function processActivities (activities: Activity[]) {
|
||||
async function processActivities (activities: Activity[], inboxAccount?: AccountInstance) {
|
||||
for (const activity of activities) {
|
||||
const activityProcessor = processActivity[activity.type]
|
||||
if (activityProcessor === undefined) {
|
||||
|
@ -59,6 +74,6 @@ async function processActivities (activities: Activity[]) {
|
|||
continue
|
||||
}
|
||||
|
||||
await activityProcessor(activity)
|
||||
await activityProcessor(activity, inboxAccount)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { activityPubClientRouter } from './client'
|
|||
|
||||
const remoteRouter = express.Router()
|
||||
|
||||
remoteRouter.use('/inbox', inboxRouter)
|
||||
remoteRouter.use('/', inboxRouter)
|
||||
remoteRouter.use('/', activityPubClientRouter)
|
||||
remoteRouter.use('/*', badRequest)
|
||||
|
||||
|
|
|
@ -224,67 +224,6 @@
|
|||
// logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
|
||||
// const options = {
|
||||
// arguments: [ videoToRemoveData, fromPod ],
|
||||
// errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
// }
|
||||
//
|
||||
// await retryTransactionWrapper(removeRemoteVideo, options)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) {
|
||||
// logger.debug('Removing remote video "%s".', videoToRemoveData.uuid)
|
||||
//
|
||||
// await db.sequelize.transaction(async t => {
|
||||
// // We need the instance because we have to remove some other stuffs (thumbnail etc)
|
||||
// const videoInstance = await fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t)
|
||||
// await videoInstance.destroy({ transaction: t })
|
||||
// })
|
||||
//
|
||||
// logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideoAccountRetryWrapper (accountAttributesToRemove: RemoteVideoAccountRemoveData, fromPod: PodInstance) {
|
||||
// const options = {
|
||||
// arguments: [ accountAttributesToRemove, fromPod ],
|
||||
// errorMessage: 'Cannot remove the remote video account with many retries.'
|
||||
// }
|
||||
//
|
||||
// await retryTransactionWrapper(removeRemoteVideoAccount, options)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideoAccount (accountAttributesToRemove: RemoteVideoAccountRemoveData, fromPod: PodInstance) {
|
||||
// logger.debug('Removing remote video account "%s".', accountAttributesToRemove.uuid)
|
||||
//
|
||||
// await db.sequelize.transaction(async t => {
|
||||
// const videoAccount = await db.Account.loadAccountByPodAndUUID(accountAttributesToRemove.uuid, fromPod.id, t)
|
||||
// await videoAccount.destroy({ transaction: t })
|
||||
// })
|
||||
//
|
||||
// logger.info('Remote video account with uuid %s removed.', accountAttributesToRemove.uuid)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
||||
// const options = {
|
||||
// arguments: [ videoChannelAttributesToRemove, fromPod ],
|
||||
// errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
// }
|
||||
//
|
||||
// await retryTransactionWrapper(removeRemoteVideoChannel, options)
|
||||
// }
|
||||
//
|
||||
// async function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) {
|
||||
// logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid)
|
||||
//
|
||||
// await db.sequelize.transaction(async t => {
|
||||
// const videoChannel = await fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t)
|
||||
// await videoChannel.destroy({ transaction: t })
|
||||
// })
|
||||
//
|
||||
// logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)
|
||||
// }
|
||||
//
|
||||
// async function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) {
|
||||
// const options = {
|
||||
// arguments: [ reportData, fromPod ],
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
import * as express from 'express'
|
||||
import { getFormattedObjects } from '../../helpers'
|
||||
import { getApplicationAccount } from '../../helpers/utils'
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { asyncMiddleware, paginationValidator, podsSortValidator, setPagination, setPodsSort } from '../../middlewares'
|
||||
import { asyncMiddleware, paginationValidator, setFollowersSort, setPagination } from '../../middlewares'
|
||||
import { setFollowingSort } from '../../middlewares/sort'
|
||||
import { followersSortValidator, followingSortValidator } from '../../middlewares/validators/sort'
|
||||
|
||||
const podsRouter = express.Router()
|
||||
|
||||
podsRouter.get('/',
|
||||
podsRouter.get('/following',
|
||||
paginationValidator,
|
||||
podsSortValidator,
|
||||
setPodsSort,
|
||||
followingSortValidator,
|
||||
setFollowingSort,
|
||||
setPagination,
|
||||
asyncMiddleware(listPods)
|
||||
asyncMiddleware(listFollowing)
|
||||
)
|
||||
|
||||
podsRouter.get('/followers',
|
||||
paginationValidator,
|
||||
followersSortValidator,
|
||||
setFollowersSort,
|
||||
setPagination,
|
||||
asyncMiddleware(listFollowers)
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -21,8 +32,16 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function listPods (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const resultList = await db.Pod.listForApi(req.query.start, req.query.count, req.query.sort)
|
||||
async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const applicationAccount = await getApplicationAccount()
|
||||
const resultList = await db.Account.listFollowingForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
const applicationAccount = await getApplicationAccount()
|
||||
const resultList = await db.Account.listFollowersForApi(applicationAccount.id, req.query.start, req.query.count, req.query.sort)
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
|
|
@ -8,11 +8,18 @@ import { AccountInstance } from '../../models'
|
|||
import { logger } from '../logger'
|
||||
|
||||
import { isUserUsernameValid } from './users'
|
||||
import { isHostValid } from './pods'
|
||||
|
||||
function isVideoAccountNameValid (value: string) {
|
||||
return isUserUsernameValid(value)
|
||||
}
|
||||
|
||||
function isAccountNameWithHostValid (value: string) {
|
||||
const [ name, host ] = value.split('@')
|
||||
|
||||
return isVideoAccountNameValid(name) && isHostValid(host)
|
||||
}
|
||||
|
||||
function checkVideoAccountExists (id: string, res: express.Response, callback: () => void) {
|
||||
let promise: Promise<AccountInstance>
|
||||
if (validator.isInt(id)) {
|
||||
|
@ -41,5 +48,6 @@ function checkVideoAccountExists (id: string, res: express.Response, callback: (
|
|||
|
||||
export {
|
||||
checkVideoAccountExists,
|
||||
isAccountNameWithHostValid,
|
||||
isVideoAccountNameValid
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { pseudoRandomBytesPromise } from './core-utils'
|
|||
import { CONFIG, database as db } from '../initializers'
|
||||
import { ResultList } from '../../shared'
|
||||
import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
|
||||
import { AccountInstance } from '../models/account/account-interface'
|
||||
|
||||
function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
return res.type('json').status(400).end()
|
||||
|
@ -78,6 +79,15 @@ function resetSequelizeInstance (instance: Sequelize.Instance<any>, savedFields:
|
|||
})
|
||||
}
|
||||
|
||||
let applicationAccount: AccountInstance
|
||||
async function getApplicationAccount () {
|
||||
if (applicationAccount === undefined) {
|
||||
applicationAccount = await db.Account.loadApplication()
|
||||
}
|
||||
|
||||
return Promise.resolve(applicationAccount)
|
||||
}
|
||||
|
||||
type SortType = { sortModel: any, sortValue: string }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -89,5 +99,6 @@ export {
|
|||
isSignupAllowed,
|
||||
computeResolutionsToTranscode,
|
||||
resetSequelizeInstance,
|
||||
getApplicationAccount,
|
||||
SortType
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
JobCategory
|
||||
} from '../../shared/models'
|
||||
import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum'
|
||||
import { FollowState } from '../../shared/models/accounts/follow.model'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -34,12 +35,13 @@ const SEARCHABLE_COLUMNS = {
|
|||
|
||||
// Sortable columns per schema
|
||||
const SORTABLE_COLUMNS = {
|
||||
PODS: [ 'id', 'host', 'score', 'createdAt' ],
|
||||
USERS: [ 'id', 'username', 'createdAt' ],
|
||||
VIDEO_ABUSES: [ 'id', 'createdAt' ],
|
||||
VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ],
|
||||
VIDEOS: [ 'name', 'duration', 'createdAt', 'views', 'likes' ],
|
||||
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ]
|
||||
BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ],
|
||||
FOLLOWERS: [ 'createdAt' ],
|
||||
FOLLOWING: [ 'createdAt' ]
|
||||
}
|
||||
|
||||
const OAUTH_LIFETIME = {
|
||||
|
@ -236,27 +238,10 @@ const PODS_SCORE = {
|
|||
BONUS: 10
|
||||
}
|
||||
|
||||
// Time to wait between requests to the friends (10 min)
|
||||
let REQUESTS_INTERVAL = 600000
|
||||
|
||||
// Number of requests in parallel we can make
|
||||
const REQUESTS_IN_PARALLEL = 10
|
||||
|
||||
// To how many pods we send requests
|
||||
const REQUESTS_LIMIT_PODS = 10
|
||||
// How many requests we send to a pod per interval
|
||||
const REQUESTS_LIMIT_PER_POD = 5
|
||||
|
||||
const REQUESTS_VIDEO_QADU_LIMIT_PODS = 10
|
||||
// The QADU requests are not big
|
||||
const REQUESTS_VIDEO_QADU_LIMIT_PER_POD = 50
|
||||
|
||||
const REQUESTS_VIDEO_EVENT_LIMIT_PODS = 10
|
||||
// The EVENTS requests are not big
|
||||
const REQUESTS_VIDEO_EVENT_LIMIT_PER_POD = 50
|
||||
|
||||
// Number of requests to retry for replay requests module
|
||||
const RETRY_REQUESTS = 5
|
||||
const FOLLOW_STATES: { [ id: string ]: FollowState } = {
|
||||
PENDING: 'pending',
|
||||
ACCEPTED: 'accepted'
|
||||
}
|
||||
|
||||
const REMOTE_SCHEME = {
|
||||
HTTP: 'https',
|
||||
|
@ -333,7 +318,6 @@ const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
|
|||
if (isTestInstance() === true) {
|
||||
CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14
|
||||
FRIEND_SCORE.BASE = 20
|
||||
REQUESTS_INTERVAL = 10000
|
||||
JOBS_FETCHING_INTERVAL = 10000
|
||||
REMOTE_SCHEME.HTTP = 'http'
|
||||
REMOTE_SCHEME.WS = 'ws'
|
||||
|
@ -361,15 +345,7 @@ export {
|
|||
PODS_SCORE,
|
||||
PREVIEWS_SIZE,
|
||||
REMOTE_SCHEME,
|
||||
REQUESTS_IN_PARALLEL,
|
||||
REQUESTS_INTERVAL,
|
||||
REQUESTS_LIMIT_PER_POD,
|
||||
REQUESTS_LIMIT_PODS,
|
||||
REQUESTS_VIDEO_EVENT_LIMIT_PER_POD,
|
||||
REQUESTS_VIDEO_EVENT_LIMIT_PODS,
|
||||
REQUESTS_VIDEO_QADU_LIMIT_PER_POD,
|
||||
REQUESTS_VIDEO_QADU_LIMIT_PODS,
|
||||
RETRY_REQUESTS,
|
||||
FOLLOW_STATES,
|
||||
SEARCHABLE_COLUMNS,
|
||||
PRIVATE_RSA_KEY_SIZE,
|
||||
SORTABLE_COLUMNS,
|
||||
|
|
27
server/lib/activitypub/process-accept.ts
Normal file
27
server/lib/activitypub/process-accept.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { ActivityAccept } from '../../../shared/models/activitypub/activity'
|
||||
import { database as db } from '../../initializers'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
|
||||
async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountInstance) {
|
||||
if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.')
|
||||
|
||||
const targetAccount = await db.Account.loadByUrl(activity.actor)
|
||||
|
||||
return processFollow(inboxAccount, targetAccount)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
processAcceptActivity
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processFollow (account: AccountInstance, targetAccount: AccountInstance) {
|
||||
const follow = await db.AccountFollow.loadByAccountAndTarget(account.id, targetAccount.id)
|
||||
if (!follow) throw new Error('Cannot find associated follow.')
|
||||
|
||||
follow.set('state', 'accepted')
|
||||
return follow.save()
|
||||
}
|
105
server/lib/activitypub/process-delete.ts
Normal file
105
server/lib/activitypub/process-delete.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { ActivityDelete } from '../../../shared/models/activitypub/activity'
|
||||
import { getOrCreateAccount } from '../../helpers/activitypub'
|
||||
import { retryTransactionWrapper } from '../../helpers/database-utils'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { database as db } from '../../initializers'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
import { VideoChannelInstance } from '../../models/video/video-channel-interface'
|
||||
import { VideoInstance } from '../../models/video/video-interface'
|
||||
|
||||
async function processDeleteActivity (activity: ActivityDelete) {
|
||||
const account = await getOrCreateAccount(activity.actor)
|
||||
|
||||
if (account.url === activity.id) {
|
||||
return processDeleteAccount(account)
|
||||
}
|
||||
|
||||
{
|
||||
let videoObject = await db.Video.loadByUrl(activity.id)
|
||||
if (videoObject !== undefined) {
|
||||
return processDeleteVideo(account, videoObject)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let videoChannelObject = await db.VideoChannel.loadByUrl(activity.id)
|
||||
if (videoChannelObject !== undefined) {
|
||||
return processDeleteVideoChannel(account, videoChannelObject)
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
processDeleteActivity
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processDeleteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
|
||||
const options = {
|
||||
arguments: [ account, videoToDelete ],
|
||||
errorMessage: 'Cannot remove the remote video with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteVideo, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteVideo (account: AccountInstance, videoToDelete: VideoInstance) {
|
||||
logger.debug('Removing remote video "%s".', videoToDelete.uuid)
|
||||
|
||||
await db.sequelize.transaction(async t => {
|
||||
if (videoToDelete.VideoChannel.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url)
|
||||
}
|
||||
|
||||
await videoToDelete.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
|
||||
const options = {
|
||||
arguments: [ account, videoChannelToRemove ],
|
||||
errorMessage: 'Cannot remove the remote video channel with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteVideoChannel, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteVideoChannel (account: AccountInstance, videoChannelToRemove: VideoChannelInstance) {
|
||||
logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid)
|
||||
|
||||
await db.sequelize.transaction(async t => {
|
||||
if (videoChannelToRemove.Account.id !== account.id) {
|
||||
throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
|
||||
}
|
||||
|
||||
await videoChannelToRemove.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid)
|
||||
}
|
||||
|
||||
async function processDeleteAccount (accountToRemove: AccountInstance) {
|
||||
const options = {
|
||||
arguments: [ accountToRemove ],
|
||||
errorMessage: 'Cannot remove the remote account with many retries.'
|
||||
}
|
||||
|
||||
await retryTransactionWrapper(deleteRemoteAccount, options)
|
||||
}
|
||||
|
||||
async function deleteRemoteAccount (accountToRemove: AccountInstance) {
|
||||
logger.debug('Removing remote account "%s".', accountToRemove.uuid)
|
||||
|
||||
await db.sequelize.transaction(async t => {
|
||||
await accountToRemove.destroy({ transaction: t })
|
||||
})
|
||||
|
||||
logger.info('Remote account with uuid %s removed.', accountToRemove.uuid)
|
||||
}
|
32
server/lib/activitypub/process-follow.ts
Normal file
32
server/lib/activitypub/process-follow.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { ActivityFollow } from '../../../shared/models/activitypub/activity'
|
||||
import { getOrCreateAccount } from '../../helpers'
|
||||
import { database as db } from '../../initializers'
|
||||
import { AccountInstance } from '../../models/account/account-interface'
|
||||
|
||||
async function processFollowActivity (activity: ActivityFollow) {
|
||||
const activityObject = activity.object
|
||||
const account = await getOrCreateAccount(activity.actor)
|
||||
|
||||
return processFollow(account, activityObject)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
processFollowActivity
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function processFollow (account: AccountInstance, targetAccountURL: string) {
|
||||
const targetAccount = await db.Account.loadByUrl(targetAccountURL)
|
||||
|
||||
if (targetAccount === undefined) throw new Error('Unknown account')
|
||||
if (targetAccount.isOwned() === false) throw new Error('This is not a local account.')
|
||||
|
||||
return db.AccountFollow.create({
|
||||
accountId: account.id,
|
||||
targetAccountId: targetAccount.id,
|
||||
state: 'accepted'
|
||||
})
|
||||
}
|
|
@ -25,8 +25,7 @@ function sendUpdateVideoChannel (videoChannel: VideoChannelInstance, t: Sequeliz
|
|||
}
|
||||
|
||||
function sendDeleteVideoChannel (videoChannel: VideoChannelInstance, t: Sequelize.Transaction) {
|
||||
const videoChannelObject = videoChannel.toActivityPubObject()
|
||||
const data = deleteActivityData(videoChannel.url, videoChannel.Account, videoChannelObject)
|
||||
const data = deleteActivityData(videoChannel.url, videoChannel.Account)
|
||||
|
||||
return broadcastToFollowers(data, videoChannel.Account, t)
|
||||
}
|
||||
|
@ -46,12 +45,17 @@ function sendUpdateVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
|||
}
|
||||
|
||||
function sendDeleteVideo (video: VideoInstance, t: Sequelize.Transaction) {
|
||||
const videoObject = video.toActivityPubObject()
|
||||
const data = deleteActivityData(video.url, video.VideoChannel.Account, videoObject)
|
||||
const data = deleteActivityData(video.url, video.VideoChannel.Account)
|
||||
|
||||
return broadcastToFollowers(data, video.VideoChannel.Account, t)
|
||||
}
|
||||
|
||||
function sendDeleteAccount (account: AccountInstance, t: Sequelize.Transaction) {
|
||||
const data = deleteActivityData(account.url, account)
|
||||
|
||||
return broadcastToFollowers(data, account, t)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -60,13 +64,14 @@ export {
|
|||
sendDeleteVideoChannel,
|
||||
sendAddVideo,
|
||||
sendUpdateVideo,
|
||||
sendDeleteVideo
|
||||
sendDeleteVideo,
|
||||
sendDeleteAccount
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function broadcastToFollowers (data: any, fromAccount: AccountInstance, t: Sequelize.Transaction) {
|
||||
const result = await db.Account.listFollowerUrlsForApi(fromAccount.name, 0)
|
||||
const result = await db.Account.listFollowerUrlsForApi(fromAccount.id, 0)
|
||||
|
||||
const jobPayload = {
|
||||
uris: result.data,
|
||||
|
@ -114,14 +119,11 @@ async function updateActivityData (url: string, byAccount: AccountInstance, obje
|
|||
return buildSignedActivity(byAccount, base)
|
||||
}
|
||||
|
||||
async function deleteActivityData (url: string, byAccount: AccountInstance, object: any) {
|
||||
const to = await getPublicActivityTo(byAccount)
|
||||
async function deleteActivityData (url: string, byAccount: AccountInstance) {
|
||||
const base = {
|
||||
type: 'Update',
|
||||
type: 'Delete',
|
||||
id: url,
|
||||
actor: byAccount.url,
|
||||
to,
|
||||
object
|
||||
actor: byAccount.url
|
||||
}
|
||||
|
||||
return buildSignedActivity(byAccount, base)
|
||||
|
|
|
@ -34,6 +34,18 @@ function setVideosSort (req: express.Request, res: express.Response, next: expre
|
|||
return next()
|
||||
}
|
||||
|
||||
function setFollowersSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function setFollowingSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
let newSort: SortType = { sortModel: undefined, sortValue: undefined }
|
||||
|
||||
|
@ -63,5 +75,7 @@ export {
|
|||
setVideoAbusesSort,
|
||||
setVideoChannelsSort,
|
||||
setVideosSort,
|
||||
setBlacklistSort
|
||||
setBlacklistSort,
|
||||
setFollowersSort,
|
||||
setFollowingSort
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import { param } from 'express-validator/check'
|
||||
import * as express from 'express'
|
||||
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { checkErrors } from './utils'
|
||||
import { param } from 'express-validator/check'
|
||||
import {
|
||||
logger,
|
||||
isUserUsernameValid,
|
||||
isUserPasswordValid,
|
||||
isUserVideoQuotaValid,
|
||||
isUserDisplayNSFWValid,
|
||||
isUserPasswordValid,
|
||||
isUserRoleValid,
|
||||
isAccountNameValid
|
||||
isUserUsernameValid,
|
||||
isUserVideoQuotaValid,
|
||||
logger
|
||||
} from '../../helpers'
|
||||
import { isAccountNameWithHostValid } from '../../helpers/custom-validators/video-accounts'
|
||||
import { database as db } from '../../initializers/database'
|
||||
import { AccountInstance } from '../../models'
|
||||
import { checkErrors } from './utils'
|
||||
|
||||
const localAccountValidator = [
|
||||
param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
|
||||
param('nameWithHost').custom(isAccountNameWithHostValid).withMessage('Should have a valid account with domain name (myuser@domain.tld)'),
|
||||
|
||||
(req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking localAccountValidator parameters', { parameters: req.params })
|
||||
|
@ -34,8 +33,10 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function checkLocalAccountExists (name: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
|
||||
db.Account.loadLocalAccountByName(name)
|
||||
function checkLocalAccountExists (nameWithHost: string, res: express.Response, callback: (err: Error, account: AccountInstance) => void) {
|
||||
const [ name, host ] = nameWithHost.split('@')
|
||||
|
||||
db.Account.loadLocalAccountByNameAndPod(name, host)
|
||||
.then(account => {
|
||||
if (!account) {
|
||||
return res.status(404)
|
||||
|
|
|
@ -6,29 +6,32 @@ import { logger } from '../../helpers'
|
|||
import { SORTABLE_COLUMNS } from '../../initializers'
|
||||
|
||||
// Initialize constants here for better performances
|
||||
const SORTABLE_PODS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PODS)
|
||||
const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS)
|
||||
const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES)
|
||||
const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS)
|
||||
const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS)
|
||||
const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS)
|
||||
const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS)
|
||||
const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWING)
|
||||
|
||||
const podsSortValidator = checkSort(SORTABLE_PODS_COLUMNS)
|
||||
const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
|
||||
const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS)
|
||||
const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS)
|
||||
const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS)
|
||||
const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS)
|
||||
const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS)
|
||||
const followingSortValidator = checkSort(SORTABLE_FOLLOWING_COLUMNS)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
podsSortValidator,
|
||||
usersSortValidator,
|
||||
videoAbusesSortValidator,
|
||||
videoChannelsSortValidator,
|
||||
videosSortValidator,
|
||||
blacklistSortValidator
|
||||
blacklistSortValidator,
|
||||
followersSortValidator,
|
||||
followingSortValidator
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Promise from 'bluebird'
|
||||
|
||||
import { VideoRateType } from '../../../shared/models/videos/video-rate.type'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import { FollowState } from '../../../shared/models/accounts/follow.model'
|
||||
|
||||
export namespace AccountFollowMethods {
|
||||
export type LoadByAccountAndTarget = (accountId: number, targetAccountId: number) => Bluebird<AccountFollowInstance>
|
||||
}
|
||||
|
||||
export interface AccountFollowClass {
|
||||
loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
|
||||
}
|
||||
|
||||
export interface AccountFollowAttributes {
|
||||
accountId: number
|
||||
targetAccountId: number
|
||||
state: FollowState
|
||||
}
|
||||
|
||||
export interface AccountFollowInstance extends AccountFollowClass, AccountFollowAttributes, Sequelize.Instance<AccountFollowAttributes> {
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import { values } from 'lodash'
|
||||
import * as Sequelize from 'sequelize'
|
||||
|
||||
import { addMethodsToModel } from '../utils'
|
||||
import {
|
||||
AccountFollowInstance,
|
||||
AccountFollowAttributes,
|
||||
|
||||
AccountFollowMethods
|
||||
} from './account-follow-interface'
|
||||
import { AccountFollowAttributes, AccountFollowInstance, AccountFollowMethods } from './account-follow-interface'
|
||||
import { FOLLOW_STATES } from '../../initializers/constants'
|
||||
|
||||
let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes>
|
||||
let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget
|
||||
|
||||
export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
|
||||
AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow',
|
||||
{ },
|
||||
{
|
||||
state: {
|
||||
type: DataTypes.ENUM(values(FOLLOW_STATES)),
|
||||
allowNull: false
|
||||
}
|
||||
},
|
||||
{
|
||||
indexes: [
|
||||
{
|
||||
|
@ -43,6 +46,7 @@ function associate (models) {
|
|||
name: 'accountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'followers',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
|
||||
|
@ -51,6 +55,18 @@ function associate (models) {
|
|||
name: 'targetAccountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'following',
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
}
|
||||
|
||||
loadByAccountAndTarget = function (accountId: number, targetAccountId: number) {
|
||||
const query = {
|
||||
where: {
|
||||
accountId,
|
||||
targetAccountId
|
||||
}
|
||||
}
|
||||
|
||||
return AccountFollow.findOne(query)
|
||||
}
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Bluebird from 'bluebird'
|
||||
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { Account as FormattedAccount, ActivityPubActor } from '../../../shared'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
import { PodInstance } from '../pod/pod-interface'
|
||||
import { VideoChannelInstance } from '../video/video-channel-interface'
|
||||
import { ActivityPubActor } from '../../../shared'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
|
||||
export namespace AccountMethods {
|
||||
export type LoadApplication = () => Bluebird<AccountInstance>
|
||||
|
||||
export type Load = (id: number) => Bluebird<AccountInstance>
|
||||
export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance>
|
||||
export type LoadByUrl = (url: string) => Bluebird<AccountInstance>
|
||||
export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance>
|
||||
export type LoadLocalAccountByName = (name: string) => Bluebird<AccountInstance>
|
||||
export type LoadLocalAccountByNameAndPod = (name: string, host: string) => Bluebird<AccountInstance>
|
||||
export type ListOwned = () => Bluebird<AccountInstance[]>
|
||||
export type ListFollowerUrlsForApi = (name: string, start: number, count?: number) => Promise< ResultList<string> >
|
||||
export type ListFollowingUrlsForApi = (name: string, start: number, count?: number) => Promise< ResultList<string> >
|
||||
export type ListFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
|
||||
export type ListFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList<string> >
|
||||
export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
|
||||
export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList<AccountInstance> >
|
||||
|
||||
export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor
|
||||
export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount
|
||||
export type IsOwned = (this: AccountInstance) => boolean
|
||||
export type GetFollowerSharedInboxUrls = (this: AccountInstance) => Bluebird<string[]>
|
||||
export type GetFollowingUrl = (this: AccountInstance) => string
|
||||
|
@ -25,14 +29,17 @@ export namespace AccountMethods {
|
|||
}
|
||||
|
||||
export interface AccountClass {
|
||||
loadApplication: AccountMethods.LoadApplication
|
||||
loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID
|
||||
load: AccountMethods.Load
|
||||
loadByUUID: AccountMethods.LoadByUUID
|
||||
loadByUrl: AccountMethods.LoadByUrl
|
||||
loadLocalAccountByName: AccountMethods.LoadLocalAccountByName
|
||||
loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod
|
||||
listOwned: AccountMethods.ListOwned
|
||||
listFollowerUrlsForApi: AccountMethods.ListFollowerUrlsForApi
|
||||
listFollowingUrlsForApi: AccountMethods.ListFollowingUrlsForApi
|
||||
listFollowingForApi: AccountMethods.ListFollowingForApi
|
||||
listFollowersForApi: AccountMethods.ListFollowersForApi
|
||||
}
|
||||
|
||||
export interface AccountAttributes {
|
||||
|
@ -58,6 +65,7 @@ export interface AccountAttributes {
|
|||
export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance<AccountAttributes> {
|
||||
isOwned: AccountMethods.IsOwned
|
||||
toActivityPubObject: AccountMethods.ToActivityPubObject
|
||||
toFormattedJSON: AccountMethods.ToFormattedJSON
|
||||
getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
|
||||
getFollowingUrl: AccountMethods.GetFollowingUrl
|
||||
getFollowersUrl: AccountMethods.GetFollowersUrl
|
||||
|
|
|
@ -15,25 +15,31 @@ import {
|
|||
activityPubContextify
|
||||
} from '../../helpers'
|
||||
|
||||
import { addMethodsToModel } from '../utils'
|
||||
import { addMethodsToModel, getSort } from '../utils'
|
||||
import {
|
||||
AccountInstance,
|
||||
AccountAttributes,
|
||||
|
||||
AccountMethods
|
||||
} from './account-interface'
|
||||
import LoadApplication = AccountMethods.LoadApplication
|
||||
import { sendDeleteAccount } from '../../lib/activitypub/send-request'
|
||||
|
||||
let Account: Sequelize.Model<AccountInstance, AccountAttributes>
|
||||
let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID
|
||||
let load: AccountMethods.Load
|
||||
let loadApplication: AccountMethods.LoadApplication
|
||||
let loadByUUID: AccountMethods.LoadByUUID
|
||||
let loadByUrl: AccountMethods.LoadByUrl
|
||||
let loadLocalAccountByName: AccountMethods.LoadLocalAccountByName
|
||||
let loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod
|
||||
let listOwned: AccountMethods.ListOwned
|
||||
let listFollowerUrlsForApi: AccountMethods.ListFollowerUrlsForApi
|
||||
let listFollowingUrlsForApi: AccountMethods.ListFollowingUrlsForApi
|
||||
let listFollowingForApi: AccountMethods.ListFollowingForApi
|
||||
let listFollowersForApi: AccountMethods.ListFollowersForApi
|
||||
let isOwned: AccountMethods.IsOwned
|
||||
let toActivityPubObject: AccountMethods.ToActivityPubObject
|
||||
let toFormattedJSON: AccountMethods.ToFormattedJSON
|
||||
let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls
|
||||
let getFollowingUrl: AccountMethods.GetFollowingUrl
|
||||
let getFollowersUrl: AccountMethods.GetFollowersUrl
|
||||
|
@ -189,16 +195,20 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
|
|||
const classMethods = [
|
||||
associate,
|
||||
loadAccountByPodAndUUID,
|
||||
loadApplication,
|
||||
load,
|
||||
loadByUUID,
|
||||
loadLocalAccountByName,
|
||||
loadLocalAccountByNameAndPod,
|
||||
listOwned,
|
||||
listFollowerUrlsForApi,
|
||||
listFollowingUrlsForApi
|
||||
listFollowingUrlsForApi,
|
||||
listFollowingForApi,
|
||||
listFollowersForApi
|
||||
]
|
||||
const instanceMethods = [
|
||||
isOwned,
|
||||
toActivityPubObject,
|
||||
toFormattedJSON,
|
||||
getFollowerSharedInboxUrls,
|
||||
getFollowingUrl,
|
||||
getFollowersUrl,
|
||||
|
@ -250,6 +260,7 @@ function associate (models) {
|
|||
name: 'accountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'following',
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
|
||||
|
@ -258,23 +269,29 @@ function associate (models) {
|
|||
name: 'targetAccountId',
|
||||
allowNull: false
|
||||
},
|
||||
as: 'followers',
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
}
|
||||
|
||||
function afterDestroy (account: AccountInstance) {
|
||||
if (account.isOwned()) {
|
||||
const removeVideoAccountToFriendsParams = {
|
||||
uuid: account.uuid
|
||||
}
|
||||
|
||||
// FIXME: remove account in followers
|
||||
// return removeVideoAccountToFriends(removeVideoAccountToFriendsParams)
|
||||
return sendDeleteAccount(account, undefined)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
toFormattedJSON = function (this: AccountInstance) {
|
||||
const json = {
|
||||
id: this.id,
|
||||
host: this.Pod.host,
|
||||
name: this.name
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
toActivityPubObject = function (this: AccountInstance) {
|
||||
const type = this.podId ? 'Application' as 'Application' : 'Person' as 'Person'
|
||||
|
||||
|
@ -347,12 +364,85 @@ listOwned = function () {
|
|||
return Account.findAll(query)
|
||||
}
|
||||
|
||||
listFollowerUrlsForApi = function (name: string, start: number, count?: number) {
|
||||
return createListFollowForApiQuery('followers', name, start, count)
|
||||
listFollowerUrlsForApi = function (id: number, start: number, count?: number) {
|
||||
return createListFollowForApiQuery('followers', id, start, count)
|
||||
}
|
||||
|
||||
listFollowingUrlsForApi = function (name: string, start: number, count?: number) {
|
||||
return createListFollowForApiQuery('following', name, start, count)
|
||||
listFollowingUrlsForApi = function (id: number, start: number, count?: number) {
|
||||
return createListFollowForApiQuery('following', id, start, count)
|
||||
}
|
||||
|
||||
listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.AccountFollow,
|
||||
required: true,
|
||||
as: 'following',
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Account,
|
||||
as: 'following',
|
||||
required: true,
|
||||
include: [ Account['sequelize'].models.Pod ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return Account.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
|
||||
const query = {
|
||||
distinct: true,
|
||||
offset: start,
|
||||
limit: count,
|
||||
order: [ getSort(sort) ],
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.AccountFollow,
|
||||
required: true,
|
||||
as: 'followers',
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Account,
|
||||
as: 'followers',
|
||||
required: true,
|
||||
include: [ Account['sequelize'].models.Pod ]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return Account.findAndCountAll(query).then(({ rows, count }) => {
|
||||
return {
|
||||
data: rows,
|
||||
total: count
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadApplication = function () {
|
||||
return Account.findOne({
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].model.Application,
|
||||
required: true
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
load = function (id: number) {
|
||||
|
@ -369,14 +459,22 @@ loadByUUID = function (uuid: string) {
|
|||
return Account.findOne(query)
|
||||
}
|
||||
|
||||
loadLocalAccountByName = function (name: string) {
|
||||
loadLocalAccountByNameAndPod = function (name: string, host: string) {
|
||||
const query: Sequelize.FindOptions<AccountAttributes> = {
|
||||
where: {
|
||||
name,
|
||||
userId: {
|
||||
[Sequelize.Op.ne]: null
|
||||
}
|
||||
}
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Account['sequelize'].models.Pod,
|
||||
where: {
|
||||
host
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return Account.findOne(query)
|
||||
|
@ -406,7 +504,7 @@ loadAccountByPodAndUUID = function (uuid: string, podId: number, transaction: Se
|
|||
|
||||
// ------------------------------ UTILS ------------------------------
|
||||
|
||||
async function createListFollowForApiQuery (type: 'followers' | 'following', name: string, start: number, count?: number) {
|
||||
async function createListFollowForApiQuery (type: 'followers' | 'following', id: number, start: number, count?: number) {
|
||||
let firstJoin: string
|
||||
let secondJoin: string
|
||||
|
||||
|
@ -424,14 +522,14 @@ async function createListFollowForApiQuery (type: 'followers' | 'following', nam
|
|||
for (const selection of selections) {
|
||||
let query = 'SELECT ' + selection + ' FROM "Account" ' +
|
||||
'INNER JOIN "AccountFollower" ON "AccountFollower"."' + firstJoin + '" = "Account"."id" ' +
|
||||
'INNER JOIN "Account" AS "Followers" ON "Followers"."id" = "AccountFollower"."' + secondJoin + '" ' +
|
||||
'WHERE "Account"."name" = \'$name\' ' +
|
||||
'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' +
|
||||
'WHERE "Account"."id" = $id ' +
|
||||
'LIMIT ' + start
|
||||
|
||||
if (count !== undefined) query += ', ' + count
|
||||
|
||||
const options = {
|
||||
bind: { name },
|
||||
bind: { id },
|
||||
type: Sequelize.QueryTypes.SELECT
|
||||
}
|
||||
tasks.push(Account['sequelize'].query(query, options))
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
VideoChannelMethods
|
||||
} from './video-channel-interface'
|
||||
import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request'
|
||||
|
||||
let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes>
|
||||
let toFormattedJSON: VideoChannelMethods.ToFormattedJSON
|
||||
|
@ -176,11 +177,7 @@ function associate (models) {
|
|||
|
||||
function afterDestroy (videoChannel: VideoChannelInstance) {
|
||||
if (videoChannel.isOwned()) {
|
||||
const removeVideoChannelToFriendsParams = {
|
||||
uuid: videoChannel.uuid
|
||||
}
|
||||
|
||||
// FIXME: send remove event to followers
|
||||
return sendDeleteVideoChannel(videoChannel, undefined)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
import * as Bluebird from 'bluebird'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
import { Video as FormattedVideo, VideoDetails as FormattedDetailsVideo } from '../../../shared/models/videos/video.model'
|
||||
|
||||
import { TagAttributes, TagInstance } from './tag-interface'
|
||||
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||
|
||||
// Don't use barrel, import just what we need
|
||||
import {
|
||||
Video as FormattedVideo,
|
||||
VideoDetails as FormattedDetailsVideo
|
||||
} from '../../../shared/models/videos/video.model'
|
||||
import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/remote-video-update-request.model'
|
||||
import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model'
|
||||
import { ResultList } from '../../../shared/models/result-list.model'
|
||||
import { VideoChannelInstance } from './video-channel-interface'
|
||||
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object'
|
||||
import { VideoFileAttributes, VideoFileInstance } from './video-file-interface'
|
||||
|
||||
export namespace VideoMethods {
|
||||
export type GetThumbnailName = (this: VideoInstance) => string
|
||||
|
|
|
@ -45,6 +45,7 @@ import { addMethodsToModel, getSort } from '../utils'
|
|||
import { TagInstance } from './tag-interface'
|
||||
import { VideoFileInstance, VideoFileModel } from './video-file-interface'
|
||||
import { VideoAttributes, VideoInstance, VideoMethods } from './video-interface'
|
||||
import { sendDeleteVideo } from '../../lib/activitypub/send-request'
|
||||
|
||||
const Buffer = safeBuffer.Buffer
|
||||
|
||||
|
@ -363,13 +364,9 @@ function afterDestroy (video: VideoInstance) {
|
|||
)
|
||||
|
||||
if (video.isOwned()) {
|
||||
const removeVideoToFriendsParams = {
|
||||
uuid: video.uuid
|
||||
}
|
||||
|
||||
tasks.push(
|
||||
video.removePreview()
|
||||
// FIXME: remove video for followers
|
||||
video.removePreview(),
|
||||
sendDeleteVideo(video, undefined)
|
||||
)
|
||||
|
||||
// Remove physical files and torrents
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as program from 'commander'
|
|||
// /!\ Before imports /!\
|
||||
process.env.NODE_ENV = 'test'
|
||||
|
||||
import { REQUESTS_INTERVAL } from '../../initializers/constants'
|
||||
import { Video, VideoRateType, VideoFile } from '../../../shared'
|
||||
import {
|
||||
ServerInfo as DefaultServerInfo,
|
||||
|
@ -137,7 +136,7 @@ async function start () {
|
|||
initializeRequestsPerServer(servers)
|
||||
checking = false
|
||||
clearInterval(waitingInterval)
|
||||
}, REQUESTS_INTERVAL)
|
||||
}, 10000)
|
||||
}, integrityInterval)
|
||||
}
|
||||
|
||||
|
|
5
shared/models/accounts/account.model.ts
Normal file
5
shared/models/accounts/account.model.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export interface Account {
|
||||
id: number
|
||||
name: string
|
||||
host: string
|
||||
}
|
1
shared/models/accounts/follow.model.ts
Normal file
1
shared/models/accounts/follow.model.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export type FollowState = 'pending' | 'accepted'
|
2
shared/models/accounts/index.ts
Normal file
2
shared/models/accounts/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from './account.model'
|
||||
export * from './follow.model'
|
|
@ -4,10 +4,11 @@ import {
|
|||
} from './objects'
|
||||
import { ActivityPubSignature } from './activitypub-signature'
|
||||
|
||||
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag
|
||||
export type Activity = ActivityCreate | ActivityAdd | ActivityUpdate | ActivityFlag |
|
||||
ActivityDelete | ActivityFollow | ActivityAccept
|
||||
|
||||
// Flag -> report abuse
|
||||
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag'
|
||||
export type ActivityType = 'Create' | 'Add' | 'Update' | 'Flag' | 'Delete' | 'Follow' | 'Accept'
|
||||
|
||||
export interface BaseActivity {
|
||||
'@context'?: any[]
|
||||
|
@ -37,3 +38,16 @@ export interface ActivityFlag extends BaseActivity {
|
|||
type: 'Flag'
|
||||
object: string
|
||||
}
|
||||
|
||||
export interface ActivityDelete extends BaseActivity {
|
||||
type: 'Delete'
|
||||
}
|
||||
|
||||
export interface ActivityFollow extends BaseActivity {
|
||||
type: 'Follow'
|
||||
object: string
|
||||
}
|
||||
|
||||
export interface ActivityAccept extends BaseActivity {
|
||||
type: 'Accept'
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './accounts'
|
||||
export * from './activitypub'
|
||||
export * from './pods'
|
||||
export * from './users'
|
||||
|
|
Loading…
Reference in a new issue