From 50d6de9c286abcb34ff4234d56d9cbb803db7665 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 14 Dec 2017 17:38:41 +0100 Subject: Begin moving video channel to actor --- server/models/account/account-follow.ts | 228 ------------------------- server/models/account/account.ts | 111 ++++-------- server/models/account/user.ts | 23 +-- server/models/activitypub/actor-follow.ts | 260 +++++++++++++++++++++++++++++ server/models/activitypub/actor.ts | 165 +++++++++++++++--- server/models/application/application.ts | 23 ++- server/models/video/video-abuse.ts | 13 +- server/models/video/video-channel-share.ts | 96 ----------- server/models/video/video-channel.ts | 164 ++++++++---------- server/models/video/video-share.ts | 32 ++-- server/models/video/video.ts | 78 +++++---- 11 files changed, 599 insertions(+), 594 deletions(-) delete mode 100644 server/models/account/account-follow.ts create mode 100644 server/models/activitypub/actor-follow.ts delete mode 100644 server/models/video/video-channel-share.ts (limited to 'server/models') diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts deleted file mode 100644 index 975e7ee7d..000000000 --- a/server/models/account/account-follow.ts +++ /dev/null @@ -1,228 +0,0 @@ -import * as Bluebird from 'bluebird' -import { values } from 'lodash' -import * as Sequelize from 'sequelize' -import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { FollowState } from '../../../shared/models/accounts' -import { FOLLOW_STATES } from '../../initializers/constants' -import { ServerModel } from '../server/server' -import { getSort } from '../utils' -import { AccountModel } from './account' - -@Table({ - tableName: 'accountFollow', - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'targetAccountId' ] - }, - { - fields: [ 'accountId', 'targetAccountId' ], - unique: true - } - ] -}) -export class AccountFollowModel extends Model { - - @AllowNull(false) - @Column(DataType.ENUM(values(FOLLOW_STATES))) - state: FollowState - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @ForeignKey(() => AccountModel) - @Column - accountId: number - - @BelongsTo(() => AccountModel, { - foreignKey: { - name: 'accountId', - allowNull: false - }, - as: 'AccountFollower', - onDelete: 'CASCADE' - }) - AccountFollower: AccountModel - - @ForeignKey(() => AccountModel) - @Column - targetAccountId: number - - @BelongsTo(() => AccountModel, { - foreignKey: { - name: 'targetAccountId', - allowNull: false - }, - as: 'AccountFollowing', - onDelete: 'CASCADE' - }) - AccountFollowing: AccountModel - - static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) { - const query = { - where: { - accountId, - targetAccountId - }, - include: [ - { - model: AccountModel, - required: true, - as: 'AccountFollower' - }, - { - model: AccountModel, - required: true, - as: 'AccountFollowing' - } - ], - transaction: t - } - - return AccountFollowModel.findOne(query) - } - - static listFollowingForApi (id: number, start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: AccountModel, - required: true, - as: 'AccountFollower', - where: { - id - } - }, - { - model: AccountModel, - as: 'AccountFollowing', - required: true, - include: [ ServerModel ] - } - ] - } - - return AccountFollowModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) - } - - static listFollowersForApi (id: number, start: number, count: number, sort: string) { - const query = { - distinct: true, - offset: start, - limit: count, - order: [ getSort(sort) ], - include: [ - { - model: AccountModel, - required: true, - as: 'AccountFollower', - include: [ ServerModel ] - }, - { - model: AccountModel, - as: 'AccountFollowing', - required: true, - where: { - id - } - } - ] - } - - return AccountFollowModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) - } - - static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { - return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) - } - - static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) { - return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') - } - - static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { - return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) - } - - private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following', - accountIds: number[], - t: Sequelize.Transaction, - start?: number, - count?: number, - columnUrl = 'url') { - let firstJoin: string - let secondJoin: string - - if (type === 'followers') { - firstJoin = 'targetAccountId' - secondJoin = 'accountId' - } else { - firstJoin = 'accountId' - secondJoin = 'targetAccountId' - } - - const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] - const tasks: Bluebird[] = [] - - for (const selection of selections) { - let query = 'SELECT ' + selection + ' FROM "account" ' + - 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' + - 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' + - 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' ' - - if (count !== undefined) query += 'LIMIT ' + count - if (start !== undefined) query += ' OFFSET ' + start - - const options = { - bind: { accountIds }, - type: Sequelize.QueryTypes.SELECT, - transaction: t - } - tasks.push(AccountFollowModel.sequelize.query(query, options)) - } - - const [ followers, [ { total } ] ] = await - Promise.all(tasks) - const urls: string[] = followers.map(f => f.url) - - return { - data: urls, - total: parseInt(total, 10) - } - } - - toFormattedJSON () { - const follower = this.AccountFollower.toFormattedJSON() - const following = this.AccountFollowing.toFormattedJSON() - - return { - id: this.id, - follower, - following, - state: this.state, - createdAt: this.createdAt, - updatedAt: this.updatedAt - } - } -} diff --git a/server/models/account/account.ts b/server/models/account/account.ts index b26395fd4..1ee232537 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -5,18 +5,16 @@ import { BelongsTo, Column, CreatedAt, - DataType, - Default, + DefaultScope, ForeignKey, HasMany, Is, - IsUUID, Model, Table, UpdatedAt } from 'sequelize-typescript' import { isUserUsernameValid } from '../../helpers/custom-validators/users' -import { sendDeleteAccount } from '../../lib/activitypub/send' +import { sendDeleteActor } from '../../lib/activitypub/send' import { ActorModel } from '../activitypub/actor' import { ApplicationModel } from '../application/application' import { ServerModel } from '../server/server' @@ -24,31 +22,30 @@ import { throwIfNotValid } from '../utils' import { VideoChannelModel } from '../video/video-channel' import { UserModel } from './user' -@Table({ - tableName: 'account', - indexes: [ - { - fields: [ 'name' ] - }, - { - fields: [ 'serverId' ] - }, - { - fields: [ 'userId' ], - unique: true - }, - { - fields: [ 'applicationId' ], - unique: true - }, +@DefaultScope({ + include: [ { - fields: [ 'name', 'serverId', 'applicationId' ], - unique: true + model: () => ActorModel, + required: true, + include: [ + { + model: () => ServerModel, + required: false + } + ] } ] }) +@Table({ + tableName: 'account' +}) export class AccountModel extends Model { + @AllowNull(false) + @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name')) + @Column + name: string + @CreatedAt createdAt: Date @@ -89,7 +86,7 @@ export class AccountModel extends Model { }, onDelete: 'cascade' }) - Application: ApplicationModel + Account: ApplicationModel @HasMany(() => VideoChannelModel, { foreignKey: { @@ -103,32 +100,27 @@ export class AccountModel extends Model { @AfterDestroy static sendDeleteIfOwned (instance: AccountModel) { if (instance.isOwned()) { - return sendDeleteAccount(instance, undefined) + return sendDeleteActor(instance.Actor, undefined) } return undefined } - static loadApplication () { - return AccountModel.findOne({ - include: [ - { - model: ApplicationModel, - required: true - } - ] - }) - } - static load (id: number) { return AccountModel.findById(id) } static loadByUUID (uuid: string) { const query = { - where: { - uuid - } + include: [ + { + model: ActorModel, + required: true, + where: { + uuid + } + } + ] } return AccountModel.findOne(query) @@ -156,25 +148,6 @@ export class AccountModel extends Model { return AccountModel.findOne(query) } - static loadByNameAndHost (name: string, host: string) { - const query = { - where: { - name - }, - include: [ - { - model: ServerModel, - required: true, - where: { - host - } - } - ] - } - - return AccountModel.findOne(query) - } - static loadByUrl (url: string, transaction?: Sequelize.Transaction) { const query = { include: [ @@ -192,29 +165,11 @@ export class AccountModel extends Model { return AccountModel.findOne(query) } - static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { - const query = { - include: [ - { - model: ActorModel, - required: true, - where: { - followersUrl: { - [ Sequelize.Op.in ]: followersUrls - } - } - } - ], - transaction - } - - return AccountModel.findAll(query) - } - toFormattedJSON () { const actor = this.Actor.toFormattedJSON() const account = { id: this.id, + name: this.name, createdAt: this.createdAt, updatedAt: this.updatedAt } @@ -223,7 +178,7 @@ export class AccountModel extends Model { } toActivityPubObject () { - return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account') + return this.Actor.toActivityPubObject(this.name, 'Account') } isOwned () { diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 70ed61e07..1d5759ea3 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -1,26 +1,13 @@ import * as Sequelize from 'sequelize' import { - AllowNull, - BeforeCreate, - BeforeUpdate, - Column, CreatedAt, - DataType, - Default, DefaultScope, - HasMany, - HasOne, - Is, - IsEmail, - Model, Scopes, - Table, UpdatedAt + AllowNull, BeforeCreate, BeforeUpdate, Column, CreatedAt, DataType, Default, DefaultScope, HasMany, HasOne, Is, IsEmail, Model, + Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' +import { comparePassword, cryptPassword } from '../../helpers' import { - comparePassword, - cryptPassword -} from '../../helpers' -import { - isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, - isUserVideoQuotaValid, isUserAutoPlayVideoValid + isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, + isUserVideoQuotaValid } from '../../helpers/custom-validators/users' import { OAuthTokenModel } from '../oauth/oauth-token' import { getSort, throwIfNotValid } from '../utils' diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts new file mode 100644 index 000000000..4cba05e95 --- /dev/null +++ b/server/models/activitypub/actor-follow.ts @@ -0,0 +1,260 @@ +import * as Bluebird from 'bluebird' +import { values } from 'lodash' +import * as Sequelize from 'sequelize' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { FollowState } from '../../../shared/models/actors' +import { FOLLOW_STATES } from '../../initializers/constants' +import { ServerModel } from '../server/server' +import { getSort } from '../utils' +import { ActorModel } from './actor' + +@Table({ + tableName: 'actorFollow', + indexes: [ + { + fields: [ 'actorId' ] + }, + { + fields: [ 'targetActorId' ] + }, + { + fields: [ 'actorId', 'targetActorId' ], + unique: true + } + ] +}) +export class ActorFollowModel extends Model { + + @AllowNull(false) + @Column(DataType.ENUM(values(FOLLOW_STATES))) + state: FollowState + + @CreatedAt + createdAt: Date + + @UpdatedAt + updatedAt: Date + + @ForeignKey(() => ActorModel) + @Column + actorId: number + + @BelongsTo(() => ActorModel, { + foreignKey: { + name: 'actorId', + allowNull: false + }, + as: 'ActorFollower', + onDelete: 'CASCADE' + }) + ActorFollower: ActorModel + + @ForeignKey(() => ActorModel) + @Column + targetActorId: number + + @BelongsTo(() => ActorModel, { + foreignKey: { + name: 'targetActorId', + allowNull: false + }, + as: 'ActorFollowing', + onDelete: 'CASCADE' + }) + ActorFollowing: ActorModel + + static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) { + const query = { + where: { + actorId, + targetActorId: targetActorId + }, + include: [ + { + model: ActorModel, + required: true, + as: 'ActorFollower' + }, + { + model: ActorModel, + required: true, + as: 'ActorFollowing' + } + ], + transaction: t + } + + return ActorFollowModel.findOne(query) + } + + static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) { + const query = { + where: { + actorId + }, + include: [ + { + model: ActorModel, + required: true, + as: 'ActorFollower' + }, + { + model: ActorModel, + required: true, + as: 'ActorFollowing', + include: [ + { + model: ServerModel, + required: true, + where: { + host: targetHost + } + } + ] + } + ], + transaction: t + } + + return ActorFollowModel.findOne(query) + } + + static listFollowingForApi (id: number, start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: ActorModel, + required: true, + as: 'ActorFollower', + where: { + id + } + }, + { + model: ActorModel, + as: 'ActorFollowing', + required: true, + include: [ ServerModel ] + } + ] + } + + return ActorFollowModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) + } + + static listFollowersForApi (id: number, start: number, count: number, sort: string) { + const query = { + distinct: true, + offset: start, + limit: count, + order: [ getSort(sort) ], + include: [ + { + model: ActorModel, + required: true, + as: 'ActorFollower', + include: [ ServerModel ] + }, + { + model: ActorModel, + as: 'ActorFollowing', + required: true, + where: { + id + } + } + ] + } + + return ActorFollowModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count + } + }) + } + + static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { + return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) + } + + static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) { + return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, undefined, undefined, 'sharedInboxUrl') + } + + static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { + return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count) + } + + private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following', + actorIds: number[], + t: Sequelize.Transaction, + start?: number, + count?: number, + columnUrl = 'url') { + let firstJoin: string + let secondJoin: string + + if (type === 'followers') { + firstJoin = 'targetActorId' + secondJoin = 'actorId' + } else { + firstJoin = 'actorId' + secondJoin = 'targetActorId' + } + + const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] + const tasks: Bluebird[] = [] + + for (const selection of selections) { + let query = 'SELECT ' + selection + ' FROM "actor" ' + + 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + + 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + + 'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' ' + + if (count !== undefined) query += 'LIMIT ' + count + if (start !== undefined) query += ' OFFSET ' + start + + const options = { + bind: { actorIds }, + type: Sequelize.QueryTypes.SELECT, + transaction: t + } + tasks.push(ActorFollowModel.sequelize.query(query, options)) + } + + const [ followers, [ { total } ] ] = await + Promise.all(tasks) + const urls: string[] = followers.map(f => f.url) + + return { + data: urls, + total: parseInt(total, 10) + } + } + + toFormattedJSON () { + const follower = this.ActorFollower.toFormattedJSON() + const following = this.ActorFollowing.toFormattedJSON() + + return { + id: this.id, + follower, + following, + state: this.state, + createdAt: this.createdAt, + updatedAt: this.updatedAt + } + } +} diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 4cae6a6ec..ecaa43dcf 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -1,30 +1,75 @@ +import { values } from 'lodash' import { join } from 'path' import * as Sequelize from 'sequelize' import { - AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table, + AllowNull, + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + HasOne, + Is, + IsUUID, + Model, + Scopes, + Table, UpdatedAt } from 'sequelize-typescript' +import { ActivityPubActorType } from '../../../shared/models/activitypub' import { Avatar } from '../../../shared/models/avatars/avatar.model' import { activityPubContextify } from '../../helpers' import { isActivityPubUrlValid, isActorFollowersCountValid, - isActorFollowingCountValid, isActorPreferredUsernameValid, + isActorFollowingCountValid, + isActorNameValid, isActorPrivateKeyValid, isActorPublicKeyValid } from '../../helpers/custom-validators/activitypub' -import { isUserUsernameValid } from '../../helpers/custom-validators/users' -import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' -import { AccountFollowModel } from '../account/account-follow' +import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' +import { AccountModel } from '../account/account' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' import { throwIfNotValid } from '../utils' +import { VideoChannelModel } from '../video/video-channel' +import { ActorFollowModel } from './actor-follow' +enum ScopeNames { + FULL = 'FULL' +} + +@Scopes({ + [ScopeNames.FULL]: { + include: [ + { + model: () => AccountModel, + required: false + }, + { + model: () => VideoChannelModel, + required: false + } + ] + } +}) @Table({ - tableName: 'actor' + tableName: 'actor', + indexes: [ + { + fields: [ 'name', 'serverId' ], + unique: true + } + ] }) export class ActorModel extends Model { + @AllowNull(false) + @Column(DataType.ENUM(values(ACTIVITY_PUB_ACTOR_TYPES))) + type: ActivityPubActorType + @AllowNull(false) @Default(DataType.UUIDV4) @IsUUID(4) @@ -32,7 +77,7 @@ export class ActorModel extends Model { uuid: string @AllowNull(false) - @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name')) + @Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name')) @Column name: string @@ -104,24 +149,24 @@ export class ActorModel extends Model { }) Avatar: AvatarModel - @HasMany(() => AccountFollowModel, { + @HasMany(() => ActorFollowModel, { foreignKey: { - name: 'accountId', + name: 'actorId', allowNull: false }, onDelete: 'cascade' }) - AccountFollowing: AccountFollowModel[] + AccountFollowing: ActorFollowModel[] - @HasMany(() => AccountFollowModel, { + @HasMany(() => ActorFollowModel, { foreignKey: { - name: 'targetAccountId', + name: 'targetActorId', allowNull: false }, as: 'followers', onDelete: 'cascade' }) - AccountFollowers: AccountFollowModel[] + AccountFollowers: ActorFollowModel[] @ForeignKey(() => ServerModel) @Column @@ -135,6 +180,36 @@ export class ActorModel extends Model { }) Server: ServerModel + @HasOne(() => AccountModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + Account: AccountModel + + @HasOne(() => VideoChannelModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + VideoChannel: VideoChannelModel + + static load (id: number) { + return ActorModel.scope(ScopeNames.FULL).findById(id) + } + + static loadByUUID (uuid: string) { + const query = { + where: { + uuid + } + } + + return ActorModel.scope(ScopeNames.FULL).findOne(query) + } + static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { const query = { where: { @@ -145,7 +220,48 @@ export class ActorModel extends Model { transaction } - return ActorModel.findAll(query) + return ActorModel.scope(ScopeNames.FULL).findAll(query) + } + + static loadLocalByName (name: string) { + const query = { + where: { + name, + serverId: null + } + } + + return ActorModel.scope(ScopeNames.FULL).findOne(query) + } + + static loadByNameAndHost (name: string, host: string) { + const query = { + where: { + name + }, + include: [ + { + model: ServerModel, + required: true, + where: { + host + } + } + ] + } + + return ActorModel.scope(ScopeNames.FULL).findOne(query) + } + + static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + const query = { + where: { + url + }, + transaction + } + + return ActorModel.scope(ScopeNames.FULL).findOne(query) } toFormattedJSON () { @@ -167,6 +283,7 @@ export class ActorModel extends Model { return { id: this.id, + uuid: this.uuid, host, score, followingCount: this.followingCount, @@ -175,28 +292,30 @@ export class ActorModel extends Model { } } - toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') { + toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') { let activityPubType if (type === 'Account') { - activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' + activityPubType = 'Person' as 'Person' + } else if (type === 'Application') { + activityPubType = 'Application' as 'Application' } else { // VideoChannel - activityPubType = 'Group' + activityPubType = 'Group' as 'Group' } const json = { - type, + type: activityPubType, id: this.url, following: this.getFollowingUrl(), followers: this.getFollowersUrl(), inbox: this.inboxUrl, outbox: this.outboxUrl, - preferredUsername: name, + preferredUsername, url: this.url, - name, + name: this.name, endpoints: { sharedInbox: this.sharedInboxUrl }, - uuid, + uuid: this.uuid, publicKey: { id: this.getPublicKeyUrl(), owner: this.url, @@ -212,11 +331,11 @@ export class ActorModel extends Model { attributes: [ 'sharedInboxUrl' ], include: [ { - model: AccountFollowModel, + model: ActorFollowModel, required: true, as: 'followers', where: { - targetAccountId: this.id + targetActorId: this.id } } ], diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 9fc07e850..854a5fb36 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts @@ -1,5 +1,14 @@ -import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' +import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' +import { AccountModel } from '../account/account' +@DefaultScope({ + include: [ + { + model: () => AccountModel, + required: true + } + ] +}) @Table({ tableName: 'application' }) @@ -11,7 +20,19 @@ export class ApplicationModel extends Model { @Column migrationVersion: number + @HasOne(() => AccountModel, { + foreignKey: { + allowNull: true + }, + onDelete: 'cascade' + }) + Account: AccountModel + static countTotal () { return ApplicationModel.count() } + + static load () { + return ApplicationModel.findOne() + } } diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index d0ee969fb..182971c4e 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -3,7 +3,6 @@ import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' import { CONFIG } from '../../initializers' import { AccountModel } from '../account/account' -import { ServerModel } from '../server/server' import { getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' @@ -63,13 +62,7 @@ export class VideoAbuseModel extends Model { include: [ { model: AccountModel, - required: true, - include: [ - { - model: ServerModel, - required: false - } - ] + required: true }, { model: VideoModel, @@ -87,8 +80,8 @@ export class VideoAbuseModel extends Model { toFormattedJSON () { let reporterServerHost - if (this.Account.Server) { - reporterServerHost = this.Account.Server.host + if (this.Account.Actor.Server) { + reporterServerHost = this.Account.Actor.Server.host } else { // It means it's our video reporterServerHost = CONFIG.WEBSERVER.HOST diff --git a/server/models/video/video-channel-share.ts b/server/models/video/video-channel-share.ts deleted file mode 100644 index f5b7a7cd5..000000000 --- a/server/models/video/video-channel-share.ts +++ /dev/null @@ -1,96 +0,0 @@ -import * as Sequelize from 'sequelize' -import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' -import { AccountModel } from '../account/account' -import { VideoChannelModel } from './video-channel' - -enum ScopeNames { - FULL = 'FULL', - WITH_ACCOUNT = 'WITH_ACCOUNT' -} - -@Scopes({ - [ScopeNames.FULL]: { - include: [ - { - model: () => AccountModel, - required: true - }, - { - model: () => VideoChannelModel, - required: true - } - ] - }, - [ScopeNames.WITH_ACCOUNT]: { - include: [ - { - model: () => AccountModel, - required: true - } - ] - } -}) -@Table({ - tableName: 'videoChannelShare', - indexes: [ - { - fields: [ 'accountId' ] - }, - { - fields: [ 'videoChannelId' ] - } - ] -}) -export class VideoChannelShareModel extends Model { - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @ForeignKey(() => AccountModel) - @Column - accountId: number - - @BelongsTo(() => AccountModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'cascade' - }) - Account: AccountModel - - @ForeignKey(() => VideoChannelModel) - @Column - videoChannelId: number - - @BelongsTo(() => VideoChannelModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'cascade' - }) - VideoChannel: VideoChannelModel - - static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) { - return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({ - where: { - accountId, - videoChannelId - }, - transaction: t - }) - } - - static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) { - const query = { - where: { - videoChannelId - }, - transaction: t - } - - return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query) - .then(res => res.map(r => r.Account)) - } -} diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index fe44d3d53..acc2486b3 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -1,42 +1,52 @@ -import * as Sequelize from 'sequelize' import { AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, - DataType, - Default, + DefaultScope, ForeignKey, HasMany, Is, - IsUUID, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' -import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' +import { ActivityPubActor } from '../../../shared/models/activitypub' import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' -import { sendDeleteVideoChannel } from '../../lib/activitypub/send' +import { sendDeleteActor } from '../../lib/activitypub/send' import { AccountModel } from '../account/account' import { ActorModel } from '../activitypub/actor' -import { ServerModel } from '../server/server' import { getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' -import { VideoChannelShareModel } from './video-channel-share' enum ScopeNames { WITH_ACCOUNT = 'WITH_ACCOUNT', + WITH_ACTOR = 'WITH_ACTOR', WITH_VIDEOS = 'WITH_VIDEOS' } +@DefaultScope({ + include: [ + { + model: () => ActorModel, + required: true + } + ] +}) @Scopes({ [ScopeNames.WITH_ACCOUNT]: { include: [ { model: () => AccountModel, - include: [ { model: () => ServerModel, required: false } ] + required: true, + include: [ + { + model: () => ActorModel, + required: true + } + ] } ] }, @@ -44,6 +54,11 @@ enum ScopeNames { include: [ () => VideoModel ] + }, + [ScopeNames.WITH_ACTOR]: { + include: [ + () => ActorModel + ] } }) @Table({ @@ -56,12 +71,6 @@ enum ScopeNames { }) export class VideoChannelModel extends Model { - @AllowNull(false) - @Default(DataType.UUIDV4) - @IsUUID(4) - @Column(DataType.UUID) - uuid: string - @AllowNull(false) @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) @Column @@ -72,10 +81,6 @@ export class VideoChannelModel extends Model { @Column description: string - @AllowNull(false) - @Column - remote: boolean - @CreatedAt createdAt: Date @@ -115,19 +120,10 @@ export class VideoChannelModel extends Model { }) Videos: VideoModel[] - @HasMany(() => VideoChannelShareModel, { - foreignKey: { - name: 'channelId', - allowNull: false - }, - onDelete: 'CASCADE' - }) - VideoChannelShares: VideoChannelShareModel[] - @AfterDestroy static sendDeleteIfOwned (instance: VideoChannelModel) { - if (instance.isOwned()) { - return sendDeleteVideoChannel(instance, undefined) + if (instance.Actor.isOwned()) { + return sendDeleteActor(instance.Actor, undefined) } return undefined @@ -150,7 +146,9 @@ export class VideoChannelModel extends Model { order: [ getSort(sort) ] } - return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query) + return VideoChannelModel + .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) @@ -165,51 +163,18 @@ export class VideoChannelModel extends Model { where: { id: accountId }, - required: true, - include: [ { model: ServerModel, required: false } ] + required: true } ] } - return VideoChannelModel.findAndCountAll(query) + return VideoChannelModel + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } }) } - static loadByUrl (url: string, t?: Sequelize.Transaction) { - const query: IFindOptions = { - include: [ - { - model: ActorModel, - required: true, - where: { - url - } - } - ] - } - - if (t !== undefined) query.transaction = t - - return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query) - } - - static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) { - const query: IFindOptions = { - where: { - [ Sequelize.Op.or ]: [ - { uuid }, - { url } - ] - } - } - - if (t !== undefined) query.transaction = t - - return VideoChannelModel.findOne(query) - } - static loadByIdAndAccount (id: number, accountId: number) { const options = { where: { @@ -218,21 +183,33 @@ export class VideoChannelModel extends Model { } } - return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) + return VideoChannelModel + .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) + .findOne(options) } static loadAndPopulateAccount (id: number) { - return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id) + return VideoChannelModel + .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) + .findById(id) } static loadByUUIDAndPopulateAccount (uuid: string) { const options = { - where: { - uuid - } + include: [ + { + model: ActorModel, + required: true, + where: { + uuid + } + } + ] } - return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) + return VideoChannelModel + .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) + .findOne(options) } static loadAndPopulateAccountAndVideos (id: number) { @@ -242,39 +219,36 @@ export class VideoChannelModel extends Model { ] } - return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options) - } - - isOwned () { - return this.remote === false + return VideoChannelModel + .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]) + .findById(id, options) } toFormattedJSON () { - const json = { + const actor = this.Actor.toFormattedJSON() + const account = { id: this.id, - uuid: this.uuid, name: this.name, description: this.description, - isLocal: this.isOwned(), + isLocal: this.Actor.isOwned(), createdAt: this.createdAt, updatedAt: this.updatedAt } - if (this.Account !== undefined) { - json[ 'owner' ] = { - name: this.Account.name, - uuid: this.Account.uuid - } - } - - if (Array.isArray(this.Videos)) { - json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON()) - } - - return json + return Object.assign(actor, account) } - toActivityPubObject () { - return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel') + toActivityPubObject (): ActivityPubActor { + const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') + + return Object.assign(obj, { + summary: this.description, + attributedTo: [ + { + type: 'Person' as 'Person', + id: this.Account.Actor.url + } + ] + }) } } diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index e1733b3a7..c252fd646 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -1,18 +1,18 @@ 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' enum ScopeNames { FULL = 'FULL', - WITH_ACCOUNT = 'WITH_ACCOUNT' + WITH_ACTOR = 'WITH_ACTOR' } @Scopes({ [ScopeNames.FULL]: { include: [ { - model: () => AccountModel, + model: () => ActorModel, required: true }, { @@ -21,10 +21,10 @@ enum ScopeNames { } ] }, - [ScopeNames.WITH_ACCOUNT]: { + [ScopeNames.WITH_ACTOR]: { include: [ { - model: () => AccountModel, + model: () => ActorModel, required: true } ] @@ -34,7 +34,7 @@ enum ScopeNames { tableName: 'videoShare', indexes: [ { - fields: [ 'accountId' ] + fields: [ 'actorId' ] }, { fields: [ 'videoId' ] @@ -48,17 +48,17 @@ export class VideoShareModel extends Model { @UpdatedAt updatedAt: Date - @ForeignKey(() => AccountModel) + @ForeignKey(() => ActorModel) @Column - accountId: number + actorId: number - @BelongsTo(() => AccountModel, { + @BelongsTo(() => ActorModel, { foreignKey: { allowNull: false }, onDelete: 'cascade' }) - Account: AccountModel + Actor: ActorModel @ForeignKey(() => VideoModel) @Column @@ -72,24 +72,24 @@ export class VideoShareModel extends Model { }) Video: VideoModel - static load (accountId: number, videoId: number, t: Sequelize.Transaction) { - return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({ + static load (actorId: number, videoId: number, t: Sequelize.Transaction) { + return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ where: { - accountId, + actorId, videoId }, transaction: t }) } - static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) { + static loadActorsByShare (videoId: number, t: Sequelize.Transaction) { const query = { where: { videoId }, include: [ { - model: AccountModel, + model: ActorModel, required: true } ], @@ -97,6 +97,6 @@ export class VideoShareModel extends Model { } return VideoShareModel.scope(ScopeNames.FULL).findAll(query) - .then(res => res.map(r => r.Account)) + .then(res => res.map(r => r.Actor)) } } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1f940a50d..97fdbc8ef 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -66,9 +66,10 @@ import { VIDEO_PRIVACIES } from '../../initializers' import { getAnnounceActivityPubUrl } from '../../lib/activitypub' -import { sendDeleteVideo } from '../../lib/index' +import { sendDeleteVideo } from '../../lib/activitypub/send' import { AccountModel } from '../account/account' import { AccountVideoRateModel } from '../account/account-video-rate' +import { ActorModel } from '../activitypub/actor' import { ServerModel } from '../server/server' import { getSort, throwIfNotValid } from '../utils' import { TagModel } from './tag' @@ -79,8 +80,7 @@ import { VideoShareModel } from './video-share' import { VideoTagModel } from './video-tag' enum ScopeNames { - NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST', - PUBLIC = 'PUBLIC', + AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', WITH_ACCOUNT = 'WITH_ACCOUNT', WITH_TAGS = 'WITH_TAGS', WITH_FILES = 'WITH_FILES', @@ -89,17 +89,13 @@ enum ScopeNames { } @Scopes({ - [ScopeNames.NOT_IN_BLACKLIST]: { + [ScopeNames.AVAILABLE_FOR_LIST]: { where: { id: { [Sequelize.Op.notIn]: Sequelize.literal( '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' ) - } - } - }, - [ScopeNames.PUBLIC]: { - where: { + }, privacy: VideoPrivacy.PUBLIC } }, @@ -114,8 +110,14 @@ enum ScopeNames { required: true, include: [ { - model: () => ServerModel, - required: false + model: () => ActorModel, + required: true, + include: [ + { + model: () => ServerModel, + required: false + } + ] } ] } @@ -138,7 +140,7 @@ enum ScopeNames { include: [ { model: () => VideoShareModel, - include: [ () => AccountModel ] + include: [ () => ActorModel ] } ] }, @@ -271,7 +273,7 @@ export class VideoModel extends Model { @BelongsTo(() => VideoChannelModel, { foreignKey: { - allowNull: false + allowNull: true }, onDelete: 'cascade' }) @@ -351,14 +353,15 @@ export class VideoModel extends Model { return VideoModel.scope(ScopeNames.WITH_FILES).findAll() } - static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { + static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { function getRawQuery (select: string) { const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + - 'WHERE "VideoChannel"."accountId" = ' + accountId + 'INNER JOIN "account" AS "Account" ON "Account"."id" = "VideoChannel"."accountId" ' + + 'WHERE "Account"."actorId" = ' + actorId const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' + 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + - 'WHERE "VideoShare"."accountId" = ' + accountId + 'WHERE "VideoShare"."actorId" = ' + actorId return `(${queryVideo}) UNION (${queryVideoShare})` } @@ -388,11 +391,16 @@ export class VideoModel extends Model { } }, { - accountId + actorId } ] }, - include: [ AccountModel ] + include: [ + { + model: ActorModel, + required: true + } + ] }, { model: VideoChannelModel, @@ -469,7 +477,7 @@ export class VideoModel extends Model { order: [ getSort(sort) ] } - return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ]) + return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST, ScopeNames.WITH_ACCOUNT ]) .findAndCountAll(query) .then(({ rows, count }) => { return { @@ -541,7 +549,13 @@ export class VideoModel extends Model { const accountInclude: IIncludeOptions = { model: AccountModel, - include: [ serverInclude ] + include: [ + { + model: ActorModel, + required: true, + include: [ serverInclude ] + } + ] } const videoChannelInclude: IIncludeOptions = { @@ -586,7 +600,7 @@ export class VideoModel extends Model { videoChannelInclude, tagInclude ] - return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ]) + return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST ]) .findAndCountAll(query).then(({ rows, count }) => { return { data: rows, @@ -688,8 +702,8 @@ export class VideoModel extends Model { toFormattedJSON () { let serverHost - if (this.VideoChannel.Account.Server) { - serverHost = this.VideoChannel.Account.Server.host + if (this.VideoChannel.Account.Actor.Server) { + serverHost = this.VideoChannel.Account.Actor.Server.host } else { // It means it's our video serverHost = CONFIG.WEBSERVER.HOST @@ -805,9 +819,9 @@ export class VideoModel extends Model { for (const rate of this.AccountVideoRates) { if (rate.type === 'like') { - likes.push(rate.Account.url) + likes.push(rate.Account.Actor.url) } else if (rate.type === 'dislike') { - dislikes.push(rate.Account.url) + dislikes.push(rate.Account.Actor.url) } } @@ -820,7 +834,7 @@ export class VideoModel extends Model { const shares: string[] = [] for (const videoShare of this.VideoShares) { - const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) + const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor) shares.push(shareUrl) } @@ -886,7 +900,13 @@ export class VideoModel extends Model { url, likes: likesObject, dislikes: dislikesObject, - shares: sharesObject + shares: sharesObject, + attributedTo: [ + { + type: 'Group', + id: this.VideoChannel.Actor.url + } + ] } } @@ -1030,8 +1050,8 @@ export class VideoModel extends Model { baseUrlHttp = CONFIG.WEBSERVER.URL baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT } else { - baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host - baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host + baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host + baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host } return { baseUrlHttp, baseUrlWs } -- cgit v1.2.3