From 7a7724e66e4533523083e7336cd0d0c747c4a33b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 13 Nov 2017 17:39:41 +0100 Subject: Handle follow/accept --- server/models/account/account-follow-interface.ts | 8 +- server/models/account/account-follow.ts | 30 +++-- server/models/account/account-interface.ts | 24 ++-- server/models/account/account.ts | 138 ++++++++++++++++++---- server/models/video/video-channel.ts | 7 +- server/models/video/video-interface.ts | 17 +-- server/models/video/video.ts | 9 +- 7 files changed, 172 insertions(+), 61 deletions(-) (limited to 'server/models') diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts index 3be383649..efdff915e 100644 --- a/server/models/account/account-follow-interface.ts +++ b/server/models/account/account-follow-interface.ts @@ -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 } export interface AccountFollowClass { + loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget } export interface AccountFollowAttributes { accountId: number targetAccountId: number + state: FollowState } export interface AccountFollowInstance extends AccountFollowClass, AccountFollowAttributes, Sequelize.Instance { diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts index 9bf03b253..e6abc893a 100644 --- a/server/models/account/account-follow.ts +++ b/server/models/account/account-follow.ts @@ -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 +let loadByAccountAndTarget: AccountFollowMethods.LoadByAccountAndTarget export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { AccountFollow = sequelize.define('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) +} diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts index a662eb992..d49dfbe17 100644 --- a/server/models/account/account-interface.ts +++ b/server/models/account/account-interface.ts @@ -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 + export type Load = (id: number) => Bluebird export type LoadByUUID = (uuid: string) => Bluebird export type LoadByUrl = (url: string) => Bluebird export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird - export type LoadLocalAccountByName = (name: string) => Bluebird + export type LoadLocalAccountByNameAndPod = (name: string, host: string) => Bluebird export type ListOwned = () => Bluebird - export type ListFollowerUrlsForApi = (name: string, start: number, count?: number) => Promise< ResultList > - export type ListFollowingUrlsForApi = (name: string, start: number, count?: number) => Promise< ResultList > + export type ListFollowerUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList > + export type ListFollowingUrlsForApi = (id: number, start: number, count?: number) => Promise< ResultList > + export type ListFollowingForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList > + export type ListFollowersForApi = (id: number, start: number, count: number, sort: string) => Bluebird< ResultList > export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor + export type ToFormattedJSON = (this: AccountInstance) => FormattedAccount export type IsOwned = (this: AccountInstance) => boolean export type GetFollowerSharedInboxUrls = (this: AccountInstance) => Bluebird 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 { isOwned: AccountMethods.IsOwned toActivityPubObject: AccountMethods.ToActivityPubObject + toFormattedJSON: AccountMethods.ToFormattedJSON getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls getFollowingUrl: AccountMethods.GetFollowingUrl getFollowersUrl: AccountMethods.GetFollowersUrl diff --git a/server/models/account/account.ts b/server/models/account/account.ts index a79e13880..daf8f4703 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -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 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 (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 + } + }) } -listFollowingUrlsForApi = function (name: string, start: number, count?: number) { - return createListFollowForApiQuery('following', name, start, 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 = { 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)) diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 183ff3436..919ec916d 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -9,6 +9,7 @@ import { VideoChannelMethods } from './video-channel-interface' +import { sendDeleteVideoChannel } from '../../lib/activitypub/send-request' let VideoChannel: Sequelize.Model 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 diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index a0ac43e1e..7243756d2 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts @@ -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 diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 10ae5097c..ca71da375 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -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 -- cgit v1.2.3