diff options
author | Chocobozzz <me@florianbigard.com> | 2017-12-14 17:38:41 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2017-12-19 10:53:16 +0100 |
commit | 50d6de9c286abcb34ff4234d56d9cbb803db7665 (patch) | |
tree | f1732b27edcd05c7877a8358b8312f1e38c287ed /server/models | |
parent | fadf619ad61a016c1c7fc53de5a8f398a4f77519 (diff) | |
download | PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.tar.gz PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.tar.zst PeerTube-50d6de9c286abcb34ff4234d56d9cbb803db7665.zip |
Begin moving video channel to actor
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/account/account-follow.ts | 228 | ||||
-rw-r--r-- | server/models/account/account.ts | 111 | ||||
-rw-r--r-- | server/models/account/user.ts | 23 | ||||
-rw-r--r-- | server/models/activitypub/actor-follow.ts | 260 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 165 | ||||
-rw-r--r-- | server/models/application/application.ts | 23 | ||||
-rw-r--r-- | server/models/video/video-abuse.ts | 13 | ||||
-rw-r--r-- | server/models/video/video-channel-share.ts | 96 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 164 | ||||
-rw-r--r-- | server/models/video/video-share.ts | 32 | ||||
-rw-r--r-- | server/models/video/video.ts | 78 |
11 files changed, 599 insertions, 594 deletions
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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { values } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
5 | import { FollowState } from '../../../shared/models/accounts' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | ||
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { AccountModel } from './account' | ||
10 | |||
11 | @Table({ | ||
12 | tableName: 'accountFollow', | ||
13 | indexes: [ | ||
14 | { | ||
15 | fields: [ 'accountId' ] | ||
16 | }, | ||
17 | { | ||
18 | fields: [ 'targetAccountId' ] | ||
19 | }, | ||
20 | { | ||
21 | fields: [ 'accountId', 'targetAccountId' ], | ||
22 | unique: true | ||
23 | } | ||
24 | ] | ||
25 | }) | ||
26 | export class AccountFollowModel extends Model<AccountFollowModel> { | ||
27 | |||
28 | @AllowNull(false) | ||
29 | @Column(DataType.ENUM(values(FOLLOW_STATES))) | ||
30 | state: FollowState | ||
31 | |||
32 | @CreatedAt | ||
33 | createdAt: Date | ||
34 | |||
35 | @UpdatedAt | ||
36 | updatedAt: Date | ||
37 | |||
38 | @ForeignKey(() => AccountModel) | ||
39 | @Column | ||
40 | accountId: number | ||
41 | |||
42 | @BelongsTo(() => AccountModel, { | ||
43 | foreignKey: { | ||
44 | name: 'accountId', | ||
45 | allowNull: false | ||
46 | }, | ||
47 | as: 'AccountFollower', | ||
48 | onDelete: 'CASCADE' | ||
49 | }) | ||
50 | AccountFollower: AccountModel | ||
51 | |||
52 | @ForeignKey(() => AccountModel) | ||
53 | @Column | ||
54 | targetAccountId: number | ||
55 | |||
56 | @BelongsTo(() => AccountModel, { | ||
57 | foreignKey: { | ||
58 | name: 'targetAccountId', | ||
59 | allowNull: false | ||
60 | }, | ||
61 | as: 'AccountFollowing', | ||
62 | onDelete: 'CASCADE' | ||
63 | }) | ||
64 | AccountFollowing: AccountModel | ||
65 | |||
66 | static loadByAccountAndTarget (accountId: number, targetAccountId: number, t?: Sequelize.Transaction) { | ||
67 | const query = { | ||
68 | where: { | ||
69 | accountId, | ||
70 | targetAccountId | ||
71 | }, | ||
72 | include: [ | ||
73 | { | ||
74 | model: AccountModel, | ||
75 | required: true, | ||
76 | as: 'AccountFollower' | ||
77 | }, | ||
78 | { | ||
79 | model: AccountModel, | ||
80 | required: true, | ||
81 | as: 'AccountFollowing' | ||
82 | } | ||
83 | ], | ||
84 | transaction: t | ||
85 | } | ||
86 | |||
87 | return AccountFollowModel.findOne(query) | ||
88 | } | ||
89 | |||
90 | static listFollowingForApi (id: number, start: number, count: number, sort: string) { | ||
91 | const query = { | ||
92 | distinct: true, | ||
93 | offset: start, | ||
94 | limit: count, | ||
95 | order: [ getSort(sort) ], | ||
96 | include: [ | ||
97 | { | ||
98 | model: AccountModel, | ||
99 | required: true, | ||
100 | as: 'AccountFollower', | ||
101 | where: { | ||
102 | id | ||
103 | } | ||
104 | }, | ||
105 | { | ||
106 | model: AccountModel, | ||
107 | as: 'AccountFollowing', | ||
108 | required: true, | ||
109 | include: [ ServerModel ] | ||
110 | } | ||
111 | ] | ||
112 | } | ||
113 | |||
114 | return AccountFollowModel.findAndCountAll(query) | ||
115 | .then(({ rows, count }) => { | ||
116 | return { | ||
117 | data: rows, | ||
118 | total: count | ||
119 | } | ||
120 | }) | ||
121 | } | ||
122 | |||
123 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { | ||
124 | const query = { | ||
125 | distinct: true, | ||
126 | offset: start, | ||
127 | limit: count, | ||
128 | order: [ getSort(sort) ], | ||
129 | include: [ | ||
130 | { | ||
131 | model: AccountModel, | ||
132 | required: true, | ||
133 | as: 'AccountFollower', | ||
134 | include: [ ServerModel ] | ||
135 | }, | ||
136 | { | ||
137 | model: AccountModel, | ||
138 | as: 'AccountFollowing', | ||
139 | required: true, | ||
140 | where: { | ||
141 | id | ||
142 | } | ||
143 | } | ||
144 | ] | ||
145 | } | ||
146 | |||
147 | return AccountFollowModel.findAndCountAll(query) | ||
148 | .then(({ rows, count }) => { | ||
149 | return { | ||
150 | data: rows, | ||
151 | total: count | ||
152 | } | ||
153 | }) | ||
154 | } | ||
155 | |||
156 | static listAcceptedFollowerUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | ||
157 | return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, start, count) | ||
158 | } | ||
159 | |||
160 | static listAcceptedFollowerSharedInboxUrls (accountIds: number[], t: Sequelize.Transaction) { | ||
161 | return AccountFollowModel.createListAcceptedFollowForApiQuery('followers', accountIds, t, undefined, undefined, 'sharedInboxUrl') | ||
162 | } | ||
163 | |||
164 | static listAcceptedFollowingUrlsForApi (accountIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | ||
165 | return AccountFollowModel.createListAcceptedFollowForApiQuery('following', accountIds, t, start, count) | ||
166 | } | ||
167 | |||
168 | private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following', | ||
169 | accountIds: number[], | ||
170 | t: Sequelize.Transaction, | ||
171 | start?: number, | ||
172 | count?: number, | ||
173 | columnUrl = 'url') { | ||
174 | let firstJoin: string | ||
175 | let secondJoin: string | ||
176 | |||
177 | if (type === 'followers') { | ||
178 | firstJoin = 'targetAccountId' | ||
179 | secondJoin = 'accountId' | ||
180 | } else { | ||
181 | firstJoin = 'accountId' | ||
182 | secondJoin = 'targetAccountId' | ||
183 | } | ||
184 | |||
185 | const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] | ||
186 | const tasks: Bluebird<any>[] = [] | ||
187 | |||
188 | for (const selection of selections) { | ||
189 | let query = 'SELECT ' + selection + ' FROM "account" ' + | ||
190 | 'INNER JOIN "accountFollow" ON "accountFollow"."' + firstJoin + '" = "account"."id" ' + | ||
191 | 'INNER JOIN "account" AS "Follows" ON "accountFollow"."' + secondJoin + '" = "Follows"."id" ' + | ||
192 | 'WHERE "account"."id" = ANY ($accountIds) AND "accountFollow"."state" = \'accepted\' ' | ||
193 | |||
194 | if (count !== undefined) query += 'LIMIT ' + count | ||
195 | if (start !== undefined) query += ' OFFSET ' + start | ||
196 | |||
197 | const options = { | ||
198 | bind: { accountIds }, | ||
199 | type: Sequelize.QueryTypes.SELECT, | ||
200 | transaction: t | ||
201 | } | ||
202 | tasks.push(AccountFollowModel.sequelize.query(query, options)) | ||
203 | } | ||
204 | |||
205 | const [ followers, [ { total } ] ] = await | ||
206 | Promise.all(tasks) | ||
207 | const urls: string[] = followers.map(f => f.url) | ||
208 | |||
209 | return { | ||
210 | data: urls, | ||
211 | total: parseInt(total, 10) | ||
212 | } | ||
213 | } | ||
214 | |||
215 | toFormattedJSON () { | ||
216 | const follower = this.AccountFollower.toFormattedJSON() | ||
217 | const following = this.AccountFollowing.toFormattedJSON() | ||
218 | |||
219 | return { | ||
220 | id: this.id, | ||
221 | follower, | ||
222 | following, | ||
223 | state: this.state, | ||
224 | createdAt: this.createdAt, | ||
225 | updatedAt: this.updatedAt | ||
226 | } | ||
227 | } | ||
228 | } | ||
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 { | |||
5 | BelongsTo, | 5 | BelongsTo, |
6 | Column, | 6 | Column, |
7 | CreatedAt, | 7 | CreatedAt, |
8 | DataType, | 8 | DefaultScope, |
9 | Default, | ||
10 | ForeignKey, | 9 | ForeignKey, |
11 | HasMany, | 10 | HasMany, |
12 | Is, | 11 | Is, |
13 | IsUUID, | ||
14 | Model, | 12 | Model, |
15 | Table, | 13 | Table, |
16 | UpdatedAt | 14 | UpdatedAt |
17 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
18 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | 16 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' |
19 | import { sendDeleteAccount } from '../../lib/activitypub/send' | 17 | import { sendDeleteActor } from '../../lib/activitypub/send' |
20 | import { ActorModel } from '../activitypub/actor' | 18 | import { ActorModel } from '../activitypub/actor' |
21 | import { ApplicationModel } from '../application/application' | 19 | import { ApplicationModel } from '../application/application' |
22 | import { ServerModel } from '../server/server' | 20 | import { ServerModel } from '../server/server' |
@@ -24,31 +22,30 @@ import { throwIfNotValid } from '../utils' | |||
24 | import { VideoChannelModel } from '../video/video-channel' | 22 | import { VideoChannelModel } from '../video/video-channel' |
25 | import { UserModel } from './user' | 23 | import { UserModel } from './user' |
26 | 24 | ||
27 | @Table({ | 25 | @DefaultScope({ |
28 | tableName: 'account', | 26 | include: [ |
29 | indexes: [ | ||
30 | { | ||
31 | fields: [ 'name' ] | ||
32 | }, | ||
33 | { | ||
34 | fields: [ 'serverId' ] | ||
35 | }, | ||
36 | { | ||
37 | fields: [ 'userId' ], | ||
38 | unique: true | ||
39 | }, | ||
40 | { | ||
41 | fields: [ 'applicationId' ], | ||
42 | unique: true | ||
43 | }, | ||
44 | { | 27 | { |
45 | fields: [ 'name', 'serverId', 'applicationId' ], | 28 | model: () => ActorModel, |
46 | unique: true | 29 | required: true, |
30 | include: [ | ||
31 | { | ||
32 | model: () => ServerModel, | ||
33 | required: false | ||
34 | } | ||
35 | ] | ||
47 | } | 36 | } |
48 | ] | 37 | ] |
49 | }) | 38 | }) |
39 | @Table({ | ||
40 | tableName: 'account' | ||
41 | }) | ||
50 | export class AccountModel extends Model<AccountModel> { | 42 | export class AccountModel extends Model<AccountModel> { |
51 | 43 | ||
44 | @AllowNull(false) | ||
45 | @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name')) | ||
46 | @Column | ||
47 | name: string | ||
48 | |||
52 | @CreatedAt | 49 | @CreatedAt |
53 | createdAt: Date | 50 | createdAt: Date |
54 | 51 | ||
@@ -89,7 +86,7 @@ export class AccountModel extends Model<AccountModel> { | |||
89 | }, | 86 | }, |
90 | onDelete: 'cascade' | 87 | onDelete: 'cascade' |
91 | }) | 88 | }) |
92 | Application: ApplicationModel | 89 | Account: ApplicationModel |
93 | 90 | ||
94 | @HasMany(() => VideoChannelModel, { | 91 | @HasMany(() => VideoChannelModel, { |
95 | foreignKey: { | 92 | foreignKey: { |
@@ -103,32 +100,27 @@ export class AccountModel extends Model<AccountModel> { | |||
103 | @AfterDestroy | 100 | @AfterDestroy |
104 | static sendDeleteIfOwned (instance: AccountModel) { | 101 | static sendDeleteIfOwned (instance: AccountModel) { |
105 | if (instance.isOwned()) { | 102 | if (instance.isOwned()) { |
106 | return sendDeleteAccount(instance, undefined) | 103 | return sendDeleteActor(instance.Actor, undefined) |
107 | } | 104 | } |
108 | 105 | ||
109 | return undefined | 106 | return undefined |
110 | } | 107 | } |
111 | 108 | ||
112 | static loadApplication () { | ||
113 | return AccountModel.findOne({ | ||
114 | include: [ | ||
115 | { | ||
116 | model: ApplicationModel, | ||
117 | required: true | ||
118 | } | ||
119 | ] | ||
120 | }) | ||
121 | } | ||
122 | |||
123 | static load (id: number) { | 109 | static load (id: number) { |
124 | return AccountModel.findById(id) | 110 | return AccountModel.findById(id) |
125 | } | 111 | } |
126 | 112 | ||
127 | static loadByUUID (uuid: string) { | 113 | static loadByUUID (uuid: string) { |
128 | const query = { | 114 | const query = { |
129 | where: { | 115 | include: [ |
130 | uuid | 116 | { |
131 | } | 117 | model: ActorModel, |
118 | required: true, | ||
119 | where: { | ||
120 | uuid | ||
121 | } | ||
122 | } | ||
123 | ] | ||
132 | } | 124 | } |
133 | 125 | ||
134 | return AccountModel.findOne(query) | 126 | return AccountModel.findOne(query) |
@@ -156,25 +148,6 @@ export class AccountModel extends Model<AccountModel> { | |||
156 | return AccountModel.findOne(query) | 148 | return AccountModel.findOne(query) |
157 | } | 149 | } |
158 | 150 | ||
159 | static loadByNameAndHost (name: string, host: string) { | ||
160 | const query = { | ||
161 | where: { | ||
162 | name | ||
163 | }, | ||
164 | include: [ | ||
165 | { | ||
166 | model: ServerModel, | ||
167 | required: true, | ||
168 | where: { | ||
169 | host | ||
170 | } | ||
171 | } | ||
172 | ] | ||
173 | } | ||
174 | |||
175 | return AccountModel.findOne(query) | ||
176 | } | ||
177 | |||
178 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 151 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { |
179 | const query = { | 152 | const query = { |
180 | include: [ | 153 | include: [ |
@@ -192,29 +165,11 @@ export class AccountModel extends Model<AccountModel> { | |||
192 | return AccountModel.findOne(query) | 165 | return AccountModel.findOne(query) |
193 | } | 166 | } |
194 | 167 | ||
195 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | ||
196 | const query = { | ||
197 | include: [ | ||
198 | { | ||
199 | model: ActorModel, | ||
200 | required: true, | ||
201 | where: { | ||
202 | followersUrl: { | ||
203 | [ Sequelize.Op.in ]: followersUrls | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | ], | ||
208 | transaction | ||
209 | } | ||
210 | |||
211 | return AccountModel.findAll(query) | ||
212 | } | ||
213 | |||
214 | toFormattedJSON () { | 168 | toFormattedJSON () { |
215 | const actor = this.Actor.toFormattedJSON() | 169 | const actor = this.Actor.toFormattedJSON() |
216 | const account = { | 170 | const account = { |
217 | id: this.id, | 171 | id: this.id, |
172 | name: this.name, | ||
218 | createdAt: this.createdAt, | 173 | createdAt: this.createdAt, |
219 | updatedAt: this.updatedAt | 174 | updatedAt: this.updatedAt |
220 | } | 175 | } |
@@ -223,7 +178,7 @@ export class AccountModel extends Model<AccountModel> { | |||
223 | } | 178 | } |
224 | 179 | ||
225 | toActivityPubObject () { | 180 | toActivityPubObject () { |
226 | return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account') | 181 | return this.Actor.toActivityPubObject(this.name, 'Account') |
227 | } | 182 | } |
228 | 183 | ||
229 | isOwned () { | 184 | 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { | 2 | import { |
3 | AllowNull, | 3 | AllowNull, BeforeCreate, BeforeUpdate, Column, CreatedAt, DataType, Default, DefaultScope, HasMany, HasOne, Is, IsEmail, Model, |
4 | BeforeCreate, | 4 | Scopes, Table, UpdatedAt |
5 | BeforeUpdate, | ||
6 | Column, CreatedAt, | ||
7 | DataType, | ||
8 | Default, DefaultScope, | ||
9 | HasMany, | ||
10 | HasOne, | ||
11 | Is, | ||
12 | IsEmail, | ||
13 | Model, Scopes, | ||
14 | Table, UpdatedAt | ||
15 | } from 'sequelize-typescript' | 5 | } from 'sequelize-typescript' |
16 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 6 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
7 | import { comparePassword, cryptPassword } from '../../helpers' | ||
17 | import { | 8 | import { |
18 | comparePassword, | 9 | isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, |
19 | cryptPassword | 10 | isUserVideoQuotaValid |
20 | } from '../../helpers' | ||
21 | import { | ||
22 | isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, | ||
23 | isUserVideoQuotaValid, isUserAutoPlayVideoValid | ||
24 | } from '../../helpers/custom-validators/users' | 11 | } from '../../helpers/custom-validators/users' |
25 | import { OAuthTokenModel } from '../oauth/oauth-token' | 12 | import { OAuthTokenModel } from '../oauth/oauth-token' |
26 | import { getSort, throwIfNotValid } from '../utils' | 13 | 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { values } from 'lodash' | ||
3 | import * as Sequelize from 'sequelize' | ||
4 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
5 | import { FollowState } from '../../../shared/models/actors' | ||
6 | import { FOLLOW_STATES } from '../../initializers/constants' | ||
7 | import { ServerModel } from '../server/server' | ||
8 | import { getSort } from '../utils' | ||
9 | import { ActorModel } from './actor' | ||
10 | |||
11 | @Table({ | ||
12 | tableName: 'actorFollow', | ||
13 | indexes: [ | ||
14 | { | ||
15 | fields: [ 'actorId' ] | ||
16 | }, | ||
17 | { | ||
18 | fields: [ 'targetActorId' ] | ||
19 | }, | ||
20 | { | ||
21 | fields: [ 'actorId', 'targetActorId' ], | ||
22 | unique: true | ||
23 | } | ||
24 | ] | ||
25 | }) | ||
26 | export class ActorFollowModel extends Model<ActorFollowModel> { | ||
27 | |||
28 | @AllowNull(false) | ||
29 | @Column(DataType.ENUM(values(FOLLOW_STATES))) | ||
30 | state: FollowState | ||
31 | |||
32 | @CreatedAt | ||
33 | createdAt: Date | ||
34 | |||
35 | @UpdatedAt | ||
36 | updatedAt: Date | ||
37 | |||
38 | @ForeignKey(() => ActorModel) | ||
39 | @Column | ||
40 | actorId: number | ||
41 | |||
42 | @BelongsTo(() => ActorModel, { | ||
43 | foreignKey: { | ||
44 | name: 'actorId', | ||
45 | allowNull: false | ||
46 | }, | ||
47 | as: 'ActorFollower', | ||
48 | onDelete: 'CASCADE' | ||
49 | }) | ||
50 | ActorFollower: ActorModel | ||
51 | |||
52 | @ForeignKey(() => ActorModel) | ||
53 | @Column | ||
54 | targetActorId: number | ||
55 | |||
56 | @BelongsTo(() => ActorModel, { | ||
57 | foreignKey: { | ||
58 | name: 'targetActorId', | ||
59 | allowNull: false | ||
60 | }, | ||
61 | as: 'ActorFollowing', | ||
62 | onDelete: 'CASCADE' | ||
63 | }) | ||
64 | ActorFollowing: ActorModel | ||
65 | |||
66 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) { | ||
67 | const query = { | ||
68 | where: { | ||
69 | actorId, | ||
70 | targetActorId: targetActorId | ||
71 | }, | ||
72 | include: [ | ||
73 | { | ||
74 | model: ActorModel, | ||
75 | required: true, | ||
76 | as: 'ActorFollower' | ||
77 | }, | ||
78 | { | ||
79 | model: ActorModel, | ||
80 | required: true, | ||
81 | as: 'ActorFollowing' | ||
82 | } | ||
83 | ], | ||
84 | transaction: t | ||
85 | } | ||
86 | |||
87 | return ActorFollowModel.findOne(query) | ||
88 | } | ||
89 | |||
90 | static loadByActorAndTargetHost (actorId: number, targetHost: string, t?: Sequelize.Transaction) { | ||
91 | const query = { | ||
92 | where: { | ||
93 | actorId | ||
94 | }, | ||
95 | include: [ | ||
96 | { | ||
97 | model: ActorModel, | ||
98 | required: true, | ||
99 | as: 'ActorFollower' | ||
100 | }, | ||
101 | { | ||
102 | model: ActorModel, | ||
103 | required: true, | ||
104 | as: 'ActorFollowing', | ||
105 | include: [ | ||
106 | { | ||
107 | model: ServerModel, | ||
108 | required: true, | ||
109 | where: { | ||
110 | host: targetHost | ||
111 | } | ||
112 | } | ||
113 | ] | ||
114 | } | ||
115 | ], | ||
116 | transaction: t | ||
117 | } | ||
118 | |||
119 | return ActorFollowModel.findOne(query) | ||
120 | } | ||
121 | |||
122 | static listFollowingForApi (id: number, start: number, count: number, sort: string) { | ||
123 | const query = { | ||
124 | distinct: true, | ||
125 | offset: start, | ||
126 | limit: count, | ||
127 | order: [ getSort(sort) ], | ||
128 | include: [ | ||
129 | { | ||
130 | model: ActorModel, | ||
131 | required: true, | ||
132 | as: 'ActorFollower', | ||
133 | where: { | ||
134 | id | ||
135 | } | ||
136 | }, | ||
137 | { | ||
138 | model: ActorModel, | ||
139 | as: 'ActorFollowing', | ||
140 | required: true, | ||
141 | include: [ ServerModel ] | ||
142 | } | ||
143 | ] | ||
144 | } | ||
145 | |||
146 | return ActorFollowModel.findAndCountAll(query) | ||
147 | .then(({ rows, count }) => { | ||
148 | return { | ||
149 | data: rows, | ||
150 | total: count | ||
151 | } | ||
152 | }) | ||
153 | } | ||
154 | |||
155 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { | ||
156 | const query = { | ||
157 | distinct: true, | ||
158 | offset: start, | ||
159 | limit: count, | ||
160 | order: [ getSort(sort) ], | ||
161 | include: [ | ||
162 | { | ||
163 | model: ActorModel, | ||
164 | required: true, | ||
165 | as: 'ActorFollower', | ||
166 | include: [ ServerModel ] | ||
167 | }, | ||
168 | { | ||
169 | model: ActorModel, | ||
170 | as: 'ActorFollowing', | ||
171 | required: true, | ||
172 | where: { | ||
173 | id | ||
174 | } | ||
175 | } | ||
176 | ] | ||
177 | } | ||
178 | |||
179 | return ActorFollowModel.findAndCountAll(query) | ||
180 | .then(({ rows, count }) => { | ||
181 | return { | ||
182 | data: rows, | ||
183 | total: count | ||
184 | } | ||
185 | }) | ||
186 | } | ||
187 | |||
188 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | ||
189 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | ||
190 | } | ||
191 | |||
192 | static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) { | ||
193 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, undefined, undefined, 'sharedInboxUrl') | ||
194 | } | ||
195 | |||
196 | static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | ||
197 | return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count) | ||
198 | } | ||
199 | |||
200 | private static async createListAcceptedFollowForApiQuery (type: 'followers' | 'following', | ||
201 | actorIds: number[], | ||
202 | t: Sequelize.Transaction, | ||
203 | start?: number, | ||
204 | count?: number, | ||
205 | columnUrl = 'url') { | ||
206 | let firstJoin: string | ||
207 | let secondJoin: string | ||
208 | |||
209 | if (type === 'followers') { | ||
210 | firstJoin = 'targetActorId' | ||
211 | secondJoin = 'actorId' | ||
212 | } else { | ||
213 | firstJoin = 'actorId' | ||
214 | secondJoin = 'targetActorId' | ||
215 | } | ||
216 | |||
217 | const selections = [ '"Follows"."' + columnUrl + '" AS "url"', 'COUNT(*) AS "total"' ] | ||
218 | const tasks: Bluebird<any>[] = [] | ||
219 | |||
220 | for (const selection of selections) { | ||
221 | let query = 'SELECT ' + selection + ' FROM "actor" ' + | ||
222 | 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + | ||
223 | 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + | ||
224 | 'WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = \'accepted\' ' | ||
225 | |||
226 | if (count !== undefined) query += 'LIMIT ' + count | ||
227 | if (start !== undefined) query += ' OFFSET ' + start | ||
228 | |||
229 | const options = { | ||
230 | bind: { actorIds }, | ||
231 | type: Sequelize.QueryTypes.SELECT, | ||
232 | transaction: t | ||
233 | } | ||
234 | tasks.push(ActorFollowModel.sequelize.query(query, options)) | ||
235 | } | ||
236 | |||
237 | const [ followers, [ { total } ] ] = await | ||
238 | Promise.all(tasks) | ||
239 | const urls: string[] = followers.map(f => f.url) | ||
240 | |||
241 | return { | ||
242 | data: urls, | ||
243 | total: parseInt(total, 10) | ||
244 | } | ||
245 | } | ||
246 | |||
247 | toFormattedJSON () { | ||
248 | const follower = this.ActorFollower.toFormattedJSON() | ||
249 | const following = this.ActorFollowing.toFormattedJSON() | ||
250 | |||
251 | return { | ||
252 | id: this.id, | ||
253 | follower, | ||
254 | following, | ||
255 | state: this.state, | ||
256 | createdAt: this.createdAt, | ||
257 | updatedAt: this.updatedAt | ||
258 | } | ||
259 | } | ||
260 | } | ||
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,38 +1,83 @@ | |||
1 | import { values } from 'lodash' | ||
1 | import { join } from 'path' | 2 | import { join } from 'path' |
2 | import * as Sequelize from 'sequelize' | 3 | import * as Sequelize from 'sequelize' |
3 | import { | 4 | import { |
4 | AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table, | 5 | AllowNull, |
6 | BelongsTo, | ||
7 | Column, | ||
8 | CreatedAt, | ||
9 | DataType, | ||
10 | Default, | ||
11 | ForeignKey, | ||
12 | HasMany, | ||
13 | HasOne, | ||
14 | Is, | ||
15 | IsUUID, | ||
16 | Model, | ||
17 | Scopes, | ||
18 | Table, | ||
5 | UpdatedAt | 19 | UpdatedAt |
6 | } from 'sequelize-typescript' | 20 | } from 'sequelize-typescript' |
21 | import { ActivityPubActorType } from '../../../shared/models/activitypub' | ||
7 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | 22 | import { Avatar } from '../../../shared/models/avatars/avatar.model' |
8 | import { activityPubContextify } from '../../helpers' | 23 | import { activityPubContextify } from '../../helpers' |
9 | import { | 24 | import { |
10 | isActivityPubUrlValid, | 25 | isActivityPubUrlValid, |
11 | isActorFollowersCountValid, | 26 | isActorFollowersCountValid, |
12 | isActorFollowingCountValid, isActorPreferredUsernameValid, | 27 | isActorFollowingCountValid, |
28 | isActorNameValid, | ||
13 | isActorPrivateKeyValid, | 29 | isActorPrivateKeyValid, |
14 | isActorPublicKeyValid | 30 | isActorPublicKeyValid |
15 | } from '../../helpers/custom-validators/activitypub' | 31 | } from '../../helpers/custom-validators/activitypub' |
16 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | 32 | import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' |
17 | import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 33 | import { AccountModel } from '../account/account' |
18 | import { AccountFollowModel } from '../account/account-follow' | ||
19 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
20 | import { ServerModel } from '../server/server' | 35 | import { ServerModel } from '../server/server' |
21 | import { throwIfNotValid } from '../utils' | 36 | import { throwIfNotValid } from '../utils' |
37 | import { VideoChannelModel } from '../video/video-channel' | ||
38 | import { ActorFollowModel } from './actor-follow' | ||
22 | 39 | ||
40 | enum ScopeNames { | ||
41 | FULL = 'FULL' | ||
42 | } | ||
43 | |||
44 | @Scopes({ | ||
45 | [ScopeNames.FULL]: { | ||
46 | include: [ | ||
47 | { | ||
48 | model: () => AccountModel, | ||
49 | required: false | ||
50 | }, | ||
51 | { | ||
52 | model: () => VideoChannelModel, | ||
53 | required: false | ||
54 | } | ||
55 | ] | ||
56 | } | ||
57 | }) | ||
23 | @Table({ | 58 | @Table({ |
24 | tableName: 'actor' | 59 | tableName: 'actor', |
60 | indexes: [ | ||
61 | { | ||
62 | fields: [ 'name', 'serverId' ], | ||
63 | unique: true | ||
64 | } | ||
65 | ] | ||
25 | }) | 66 | }) |
26 | export class ActorModel extends Model<ActorModel> { | 67 | export class ActorModel extends Model<ActorModel> { |
27 | 68 | ||
28 | @AllowNull(false) | 69 | @AllowNull(false) |
70 | @Column(DataType.ENUM(values(ACTIVITY_PUB_ACTOR_TYPES))) | ||
71 | type: ActivityPubActorType | ||
72 | |||
73 | @AllowNull(false) | ||
29 | @Default(DataType.UUIDV4) | 74 | @Default(DataType.UUIDV4) |
30 | @IsUUID(4) | 75 | @IsUUID(4) |
31 | @Column(DataType.UUID) | 76 | @Column(DataType.UUID) |
32 | uuid: string | 77 | uuid: string |
33 | 78 | ||
34 | @AllowNull(false) | 79 | @AllowNull(false) |
35 | @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name')) | 80 | @Is('ActorName', value => throwIfNotValid(value, isActorNameValid, 'actor name')) |
36 | @Column | 81 | @Column |
37 | name: string | 82 | name: string |
38 | 83 | ||
@@ -104,24 +149,24 @@ export class ActorModel extends Model<ActorModel> { | |||
104 | }) | 149 | }) |
105 | Avatar: AvatarModel | 150 | Avatar: AvatarModel |
106 | 151 | ||
107 | @HasMany(() => AccountFollowModel, { | 152 | @HasMany(() => ActorFollowModel, { |
108 | foreignKey: { | 153 | foreignKey: { |
109 | name: 'accountId', | 154 | name: 'actorId', |
110 | allowNull: false | 155 | allowNull: false |
111 | }, | 156 | }, |
112 | onDelete: 'cascade' | 157 | onDelete: 'cascade' |
113 | }) | 158 | }) |
114 | AccountFollowing: AccountFollowModel[] | 159 | AccountFollowing: ActorFollowModel[] |
115 | 160 | ||
116 | @HasMany(() => AccountFollowModel, { | 161 | @HasMany(() => ActorFollowModel, { |
117 | foreignKey: { | 162 | foreignKey: { |
118 | name: 'targetAccountId', | 163 | name: 'targetActorId', |
119 | allowNull: false | 164 | allowNull: false |
120 | }, | 165 | }, |
121 | as: 'followers', | 166 | as: 'followers', |
122 | onDelete: 'cascade' | 167 | onDelete: 'cascade' |
123 | }) | 168 | }) |
124 | AccountFollowers: AccountFollowModel[] | 169 | AccountFollowers: ActorFollowModel[] |
125 | 170 | ||
126 | @ForeignKey(() => ServerModel) | 171 | @ForeignKey(() => ServerModel) |
127 | @Column | 172 | @Column |
@@ -135,6 +180,36 @@ export class ActorModel extends Model<ActorModel> { | |||
135 | }) | 180 | }) |
136 | Server: ServerModel | 181 | Server: ServerModel |
137 | 182 | ||
183 | @HasOne(() => AccountModel, { | ||
184 | foreignKey: { | ||
185 | allowNull: true | ||
186 | }, | ||
187 | onDelete: 'cascade' | ||
188 | }) | ||
189 | Account: AccountModel | ||
190 | |||
191 | @HasOne(() => VideoChannelModel, { | ||
192 | foreignKey: { | ||
193 | allowNull: true | ||
194 | }, | ||
195 | onDelete: 'cascade' | ||
196 | }) | ||
197 | VideoChannel: VideoChannelModel | ||
198 | |||
199 | static load (id: number) { | ||
200 | return ActorModel.scope(ScopeNames.FULL).findById(id) | ||
201 | } | ||
202 | |||
203 | static loadByUUID (uuid: string) { | ||
204 | const query = { | ||
205 | where: { | ||
206 | uuid | ||
207 | } | ||
208 | } | ||
209 | |||
210 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
211 | } | ||
212 | |||
138 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | 213 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { |
139 | const query = { | 214 | const query = { |
140 | where: { | 215 | where: { |
@@ -145,7 +220,48 @@ export class ActorModel extends Model<ActorModel> { | |||
145 | transaction | 220 | transaction |
146 | } | 221 | } |
147 | 222 | ||
148 | return ActorModel.findAll(query) | 223 | return ActorModel.scope(ScopeNames.FULL).findAll(query) |
224 | } | ||
225 | |||
226 | static loadLocalByName (name: string) { | ||
227 | const query = { | ||
228 | where: { | ||
229 | name, | ||
230 | serverId: null | ||
231 | } | ||
232 | } | ||
233 | |||
234 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
235 | } | ||
236 | |||
237 | static loadByNameAndHost (name: string, host: string) { | ||
238 | const query = { | ||
239 | where: { | ||
240 | name | ||
241 | }, | ||
242 | include: [ | ||
243 | { | ||
244 | model: ServerModel, | ||
245 | required: true, | ||
246 | where: { | ||
247 | host | ||
248 | } | ||
249 | } | ||
250 | ] | ||
251 | } | ||
252 | |||
253 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
254 | } | ||
255 | |||
256 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | ||
257 | const query = { | ||
258 | where: { | ||
259 | url | ||
260 | }, | ||
261 | transaction | ||
262 | } | ||
263 | |||
264 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | ||
149 | } | 265 | } |
150 | 266 | ||
151 | toFormattedJSON () { | 267 | toFormattedJSON () { |
@@ -167,6 +283,7 @@ export class ActorModel extends Model<ActorModel> { | |||
167 | 283 | ||
168 | return { | 284 | return { |
169 | id: this.id, | 285 | id: this.id, |
286 | uuid: this.uuid, | ||
170 | host, | 287 | host, |
171 | score, | 288 | score, |
172 | followingCount: this.followingCount, | 289 | followingCount: this.followingCount, |
@@ -175,28 +292,30 @@ export class ActorModel extends Model<ActorModel> { | |||
175 | } | 292 | } |
176 | } | 293 | } |
177 | 294 | ||
178 | toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') { | 295 | toActivityPubObject (preferredUsername: string, type: 'Account' | 'Application' | 'VideoChannel') { |
179 | let activityPubType | 296 | let activityPubType |
180 | if (type === 'Account') { | 297 | if (type === 'Account') { |
181 | activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' | 298 | activityPubType = 'Person' as 'Person' |
299 | } else if (type === 'Application') { | ||
300 | activityPubType = 'Application' as 'Application' | ||
182 | } else { // VideoChannel | 301 | } else { // VideoChannel |
183 | activityPubType = 'Group' | 302 | activityPubType = 'Group' as 'Group' |
184 | } | 303 | } |
185 | 304 | ||
186 | const json = { | 305 | const json = { |
187 | type, | 306 | type: activityPubType, |
188 | id: this.url, | 307 | id: this.url, |
189 | following: this.getFollowingUrl(), | 308 | following: this.getFollowingUrl(), |
190 | followers: this.getFollowersUrl(), | 309 | followers: this.getFollowersUrl(), |
191 | inbox: this.inboxUrl, | 310 | inbox: this.inboxUrl, |
192 | outbox: this.outboxUrl, | 311 | outbox: this.outboxUrl, |
193 | preferredUsername: name, | 312 | preferredUsername, |
194 | url: this.url, | 313 | url: this.url, |
195 | name, | 314 | name: this.name, |
196 | endpoints: { | 315 | endpoints: { |
197 | sharedInbox: this.sharedInboxUrl | 316 | sharedInbox: this.sharedInboxUrl |
198 | }, | 317 | }, |
199 | uuid, | 318 | uuid: this.uuid, |
200 | publicKey: { | 319 | publicKey: { |
201 | id: this.getPublicKeyUrl(), | 320 | id: this.getPublicKeyUrl(), |
202 | owner: this.url, | 321 | owner: this.url, |
@@ -212,11 +331,11 @@ export class ActorModel extends Model<ActorModel> { | |||
212 | attributes: [ 'sharedInboxUrl' ], | 331 | attributes: [ 'sharedInboxUrl' ], |
213 | include: [ | 332 | include: [ |
214 | { | 333 | { |
215 | model: AccountFollowModel, | 334 | model: ActorFollowModel, |
216 | required: true, | 335 | required: true, |
217 | as: 'followers', | 336 | as: 'followers', |
218 | where: { | 337 | where: { |
219 | targetAccountId: this.id | 338 | targetActorId: this.id |
220 | } | 339 | } |
221 | } | 340 | } |
222 | ], | 341 | ], |
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 @@ | |||
1 | import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
2 | import { AccountModel } from '../account/account' | ||
2 | 3 | ||
4 | @DefaultScope({ | ||
5 | include: [ | ||
6 | { | ||
7 | model: () => AccountModel, | ||
8 | required: true | ||
9 | } | ||
10 | ] | ||
11 | }) | ||
3 | @Table({ | 12 | @Table({ |
4 | tableName: 'application' | 13 | tableName: 'application' |
5 | }) | 14 | }) |
@@ -11,7 +20,19 @@ export class ApplicationModel extends Model<ApplicationModel> { | |||
11 | @Column | 20 | @Column |
12 | migrationVersion: number | 21 | migrationVersion: number |
13 | 22 | ||
23 | @HasOne(() => AccountModel, { | ||
24 | foreignKey: { | ||
25 | allowNull: true | ||
26 | }, | ||
27 | onDelete: 'cascade' | ||
28 | }) | ||
29 | Account: AccountModel | ||
30 | |||
14 | static countTotal () { | 31 | static countTotal () { |
15 | return ApplicationModel.count() | 32 | return ApplicationModel.count() |
16 | } | 33 | } |
34 | |||
35 | static load () { | ||
36 | return ApplicationModel.findOne() | ||
37 | } | ||
17 | } | 38 | } |
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' | |||
3 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' | 3 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' |
4 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers' |
5 | import { AccountModel } from '../account/account' | 5 | import { AccountModel } from '../account/account' |
6 | import { ServerModel } from '../server/server' | ||
7 | import { getSort, throwIfNotValid } from '../utils' | 6 | import { getSort, throwIfNotValid } from '../utils' |
8 | import { VideoModel } from './video' | 7 | import { VideoModel } from './video' |
9 | 8 | ||
@@ -63,13 +62,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
63 | include: [ | 62 | include: [ |
64 | { | 63 | { |
65 | model: AccountModel, | 64 | model: AccountModel, |
66 | required: true, | 65 | required: true |
67 | include: [ | ||
68 | { | ||
69 | model: ServerModel, | ||
70 | required: false | ||
71 | } | ||
72 | ] | ||
73 | }, | 66 | }, |
74 | { | 67 | { |
75 | model: VideoModel, | 68 | model: VideoModel, |
@@ -87,8 +80,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
87 | toFormattedJSON () { | 80 | toFormattedJSON () { |
88 | let reporterServerHost | 81 | let reporterServerHost |
89 | 82 | ||
90 | if (this.Account.Server) { | 83 | if (this.Account.Actor.Server) { |
91 | reporterServerHost = this.Account.Server.host | 84 | reporterServerHost = this.Account.Actor.Server.host |
92 | } else { | 85 | } else { |
93 | // It means it's our video | 86 | // It means it's our video |
94 | reporterServerHost = CONFIG.WEBSERVER.HOST | 87 | 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { AccountModel } from '../account/account' | ||
4 | import { VideoChannelModel } from './video-channel' | ||
5 | |||
6 | enum ScopeNames { | ||
7 | FULL = 'FULL', | ||
8 | WITH_ACCOUNT = 'WITH_ACCOUNT' | ||
9 | } | ||
10 | |||
11 | @Scopes({ | ||
12 | [ScopeNames.FULL]: { | ||
13 | include: [ | ||
14 | { | ||
15 | model: () => AccountModel, | ||
16 | required: true | ||
17 | }, | ||
18 | { | ||
19 | model: () => VideoChannelModel, | ||
20 | required: true | ||
21 | } | ||
22 | ] | ||
23 | }, | ||
24 | [ScopeNames.WITH_ACCOUNT]: { | ||
25 | include: [ | ||
26 | { | ||
27 | model: () => AccountModel, | ||
28 | required: true | ||
29 | } | ||
30 | ] | ||
31 | } | ||
32 | }) | ||
33 | @Table({ | ||
34 | tableName: 'videoChannelShare', | ||
35 | indexes: [ | ||
36 | { | ||
37 | fields: [ 'accountId' ] | ||
38 | }, | ||
39 | { | ||
40 | fields: [ 'videoChannelId' ] | ||
41 | } | ||
42 | ] | ||
43 | }) | ||
44 | export class VideoChannelShareModel extends Model<VideoChannelShareModel> { | ||
45 | @CreatedAt | ||
46 | createdAt: Date | ||
47 | |||
48 | @UpdatedAt | ||
49 | updatedAt: Date | ||
50 | |||
51 | @ForeignKey(() => AccountModel) | ||
52 | @Column | ||
53 | accountId: number | ||
54 | |||
55 | @BelongsTo(() => AccountModel, { | ||
56 | foreignKey: { | ||
57 | allowNull: false | ||
58 | }, | ||
59 | onDelete: 'cascade' | ||
60 | }) | ||
61 | Account: AccountModel | ||
62 | |||
63 | @ForeignKey(() => VideoChannelModel) | ||
64 | @Column | ||
65 | videoChannelId: number | ||
66 | |||
67 | @BelongsTo(() => VideoChannelModel, { | ||
68 | foreignKey: { | ||
69 | allowNull: false | ||
70 | }, | ||
71 | onDelete: 'cascade' | ||
72 | }) | ||
73 | VideoChannel: VideoChannelModel | ||
74 | |||
75 | static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) { | ||
76 | return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({ | ||
77 | where: { | ||
78 | accountId, | ||
79 | videoChannelId | ||
80 | }, | ||
81 | transaction: t | ||
82 | }) | ||
83 | } | ||
84 | |||
85 | static loadAccountsByShare (videoChannelId: number, t: Sequelize.Transaction) { | ||
86 | const query = { | ||
87 | where: { | ||
88 | videoChannelId | ||
89 | }, | ||
90 | transaction: t | ||
91 | } | ||
92 | |||
93 | return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query) | ||
94 | .then(res => res.map(r => r.Account)) | ||
95 | } | ||
96 | } | ||
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { | 1 | import { |
3 | AfterDestroy, | 2 | AfterDestroy, |
4 | AllowNull, | 3 | AllowNull, |
5 | BelongsTo, | 4 | BelongsTo, |
6 | Column, | 5 | Column, |
7 | CreatedAt, | 6 | CreatedAt, |
8 | DataType, | 7 | DefaultScope, |
9 | Default, | ||
10 | ForeignKey, | 8 | ForeignKey, |
11 | HasMany, | 9 | HasMany, |
12 | Is, | 10 | Is, |
13 | IsUUID, | ||
14 | Model, | 11 | Model, |
15 | Scopes, | 12 | Scopes, |
16 | Table, | 13 | Table, |
17 | UpdatedAt | 14 | UpdatedAt |
18 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
19 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' | 16 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
20 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 17 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
21 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send' | 18 | import { sendDeleteActor } from '../../lib/activitypub/send' |
22 | import { AccountModel } from '../account/account' | 19 | import { AccountModel } from '../account/account' |
23 | import { ActorModel } from '../activitypub/actor' | 20 | import { ActorModel } from '../activitypub/actor' |
24 | import { ServerModel } from '../server/server' | ||
25 | import { getSort, throwIfNotValid } from '../utils' | 21 | import { getSort, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 22 | import { VideoModel } from './video' |
27 | import { VideoChannelShareModel } from './video-channel-share' | ||
28 | 23 | ||
29 | enum ScopeNames { | 24 | enum ScopeNames { |
30 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 25 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
26 | WITH_ACTOR = 'WITH_ACTOR', | ||
31 | WITH_VIDEOS = 'WITH_VIDEOS' | 27 | WITH_VIDEOS = 'WITH_VIDEOS' |
32 | } | 28 | } |
33 | 29 | ||
30 | @DefaultScope({ | ||
31 | include: [ | ||
32 | { | ||
33 | model: () => ActorModel, | ||
34 | required: true | ||
35 | } | ||
36 | ] | ||
37 | }) | ||
34 | @Scopes({ | 38 | @Scopes({ |
35 | [ScopeNames.WITH_ACCOUNT]: { | 39 | [ScopeNames.WITH_ACCOUNT]: { |
36 | include: [ | 40 | include: [ |
37 | { | 41 | { |
38 | model: () => AccountModel, | 42 | model: () => AccountModel, |
39 | include: [ { model: () => ServerModel, required: false } ] | 43 | required: true, |
44 | include: [ | ||
45 | { | ||
46 | model: () => ActorModel, | ||
47 | required: true | ||
48 | } | ||
49 | ] | ||
40 | } | 50 | } |
41 | ] | 51 | ] |
42 | }, | 52 | }, |
@@ -44,6 +54,11 @@ enum ScopeNames { | |||
44 | include: [ | 54 | include: [ |
45 | () => VideoModel | 55 | () => VideoModel |
46 | ] | 56 | ] |
57 | }, | ||
58 | [ScopeNames.WITH_ACTOR]: { | ||
59 | include: [ | ||
60 | () => ActorModel | ||
61 | ] | ||
47 | } | 62 | } |
48 | }) | 63 | }) |
49 | @Table({ | 64 | @Table({ |
@@ -57,12 +72,6 @@ enum ScopeNames { | |||
57 | export class VideoChannelModel extends Model<VideoChannelModel> { | 72 | export class VideoChannelModel extends Model<VideoChannelModel> { |
58 | 73 | ||
59 | @AllowNull(false) | 74 | @AllowNull(false) |
60 | @Default(DataType.UUIDV4) | ||
61 | @IsUUID(4) | ||
62 | @Column(DataType.UUID) | ||
63 | uuid: string | ||
64 | |||
65 | @AllowNull(false) | ||
66 | @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) | 75 | @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelNameValid, 'name')) |
67 | @Column | 76 | @Column |
68 | name: string | 77 | name: string |
@@ -72,10 +81,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
72 | @Column | 81 | @Column |
73 | description: string | 82 | description: string |
74 | 83 | ||
75 | @AllowNull(false) | ||
76 | @Column | ||
77 | remote: boolean | ||
78 | |||
79 | @CreatedAt | 84 | @CreatedAt |
80 | createdAt: Date | 85 | createdAt: Date |
81 | 86 | ||
@@ -115,19 +120,10 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
115 | }) | 120 | }) |
116 | Videos: VideoModel[] | 121 | Videos: VideoModel[] |
117 | 122 | ||
118 | @HasMany(() => VideoChannelShareModel, { | ||
119 | foreignKey: { | ||
120 | name: 'channelId', | ||
121 | allowNull: false | ||
122 | }, | ||
123 | onDelete: 'CASCADE' | ||
124 | }) | ||
125 | VideoChannelShares: VideoChannelShareModel[] | ||
126 | |||
127 | @AfterDestroy | 123 | @AfterDestroy |
128 | static sendDeleteIfOwned (instance: VideoChannelModel) { | 124 | static sendDeleteIfOwned (instance: VideoChannelModel) { |
129 | if (instance.isOwned()) { | 125 | if (instance.Actor.isOwned()) { |
130 | return sendDeleteVideoChannel(instance, undefined) | 126 | return sendDeleteActor(instance.Actor, undefined) |
131 | } | 127 | } |
132 | 128 | ||
133 | return undefined | 129 | return undefined |
@@ -150,7 +146,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
150 | order: [ getSort(sort) ] | 146 | order: [ getSort(sort) ] |
151 | } | 147 | } |
152 | 148 | ||
153 | return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query) | 149 | return VideoChannelModel |
150 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | ||
151 | .findAndCountAll(query) | ||
154 | .then(({ rows, count }) => { | 152 | .then(({ rows, count }) => { |
155 | return { total: count, data: rows } | 153 | return { total: count, data: rows } |
156 | }) | 154 | }) |
@@ -165,51 +163,18 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
165 | where: { | 163 | where: { |
166 | id: accountId | 164 | id: accountId |
167 | }, | 165 | }, |
168 | required: true, | 166 | required: true |
169 | include: [ { model: ServerModel, required: false } ] | ||
170 | } | 167 | } |
171 | ] | 168 | ] |
172 | } | 169 | } |
173 | 170 | ||
174 | return VideoChannelModel.findAndCountAll(query) | 171 | return VideoChannelModel |
172 | .findAndCountAll(query) | ||
175 | .then(({ rows, count }) => { | 173 | .then(({ rows, count }) => { |
176 | return { total: count, data: rows } | 174 | return { total: count, data: rows } |
177 | }) | 175 | }) |
178 | } | 176 | } |
179 | 177 | ||
180 | static loadByUrl (url: string, t?: Sequelize.Transaction) { | ||
181 | const query: IFindOptions<VideoChannelModel> = { | ||
182 | include: [ | ||
183 | { | ||
184 | model: ActorModel, | ||
185 | required: true, | ||
186 | where: { | ||
187 | url | ||
188 | } | ||
189 | } | ||
190 | ] | ||
191 | } | ||
192 | |||
193 | if (t !== undefined) query.transaction = t | ||
194 | |||
195 | return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query) | ||
196 | } | ||
197 | |||
198 | static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) { | ||
199 | const query: IFindOptions<VideoChannelModel> = { | ||
200 | where: { | ||
201 | [ Sequelize.Op.or ]: [ | ||
202 | { uuid }, | ||
203 | { url } | ||
204 | ] | ||
205 | } | ||
206 | } | ||
207 | |||
208 | if (t !== undefined) query.transaction = t | ||
209 | |||
210 | return VideoChannelModel.findOne(query) | ||
211 | } | ||
212 | |||
213 | static loadByIdAndAccount (id: number, accountId: number) { | 178 | static loadByIdAndAccount (id: number, accountId: number) { |
214 | const options = { | 179 | const options = { |
215 | where: { | 180 | where: { |
@@ -218,21 +183,33 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
218 | } | 183 | } |
219 | } | 184 | } |
220 | 185 | ||
221 | return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) | 186 | return VideoChannelModel |
187 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | ||
188 | .findOne(options) | ||
222 | } | 189 | } |
223 | 190 | ||
224 | static loadAndPopulateAccount (id: number) { | 191 | static loadAndPopulateAccount (id: number) { |
225 | return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id) | 192 | return VideoChannelModel |
193 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | ||
194 | .findById(id) | ||
226 | } | 195 | } |
227 | 196 | ||
228 | static loadByUUIDAndPopulateAccount (uuid: string) { | 197 | static loadByUUIDAndPopulateAccount (uuid: string) { |
229 | const options = { | 198 | const options = { |
230 | where: { | 199 | include: [ |
231 | uuid | 200 | { |
232 | } | 201 | model: ActorModel, |
202 | required: true, | ||
203 | where: { | ||
204 | uuid | ||
205 | } | ||
206 | } | ||
207 | ] | ||
233 | } | 208 | } |
234 | 209 | ||
235 | return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options) | 210 | return VideoChannelModel |
211 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | ||
212 | .findOne(options) | ||
236 | } | 213 | } |
237 | 214 | ||
238 | static loadAndPopulateAccountAndVideos (id: number) { | 215 | static loadAndPopulateAccountAndVideos (id: number) { |
@@ -242,39 +219,36 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
242 | ] | 219 | ] |
243 | } | 220 | } |
244 | 221 | ||
245 | return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options) | 222 | return VideoChannelModel |
246 | } | 223 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]) |
247 | 224 | .findById(id, options) | |
248 | isOwned () { | ||
249 | return this.remote === false | ||
250 | } | 225 | } |
251 | 226 | ||
252 | toFormattedJSON () { | 227 | toFormattedJSON () { |
253 | const json = { | 228 | const actor = this.Actor.toFormattedJSON() |
229 | const account = { | ||
254 | id: this.id, | 230 | id: this.id, |
255 | uuid: this.uuid, | ||
256 | name: this.name, | 231 | name: this.name, |
257 | description: this.description, | 232 | description: this.description, |
258 | isLocal: this.isOwned(), | 233 | isLocal: this.Actor.isOwned(), |
259 | createdAt: this.createdAt, | 234 | createdAt: this.createdAt, |
260 | updatedAt: this.updatedAt | 235 | updatedAt: this.updatedAt |
261 | } | 236 | } |
262 | 237 | ||
263 | if (this.Account !== undefined) { | 238 | return Object.assign(actor, account) |
264 | json[ 'owner' ] = { | ||
265 | name: this.Account.name, | ||
266 | uuid: this.Account.uuid | ||
267 | } | ||
268 | } | ||
269 | |||
270 | if (Array.isArray(this.Videos)) { | ||
271 | json[ 'videos' ] = this.Videos.map(v => v.toFormattedJSON()) | ||
272 | } | ||
273 | |||
274 | return json | ||
275 | } | 239 | } |
276 | 240 | ||
277 | toActivityPubObject () { | 241 | toActivityPubObject (): ActivityPubActor { |
278 | return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel') | 242 | const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') |
243 | |||
244 | return Object.assign(obj, { | ||
245 | summary: this.description, | ||
246 | attributedTo: [ | ||
247 | { | ||
248 | type: 'Person' as 'Person', | ||
249 | id: this.Account.Actor.url | ||
250 | } | ||
251 | ] | ||
252 | }) | ||
279 | } | 253 | } |
280 | } | 254 | } |
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 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { AccountModel } from '../account/account' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
5 | 5 | ||
6 | enum ScopeNames { | 6 | enum ScopeNames { |
7 | FULL = 'FULL', | 7 | FULL = 'FULL', |
8 | WITH_ACCOUNT = 'WITH_ACCOUNT' | 8 | WITH_ACTOR = 'WITH_ACTOR' |
9 | } | 9 | } |
10 | 10 | ||
11 | @Scopes({ | 11 | @Scopes({ |
12 | [ScopeNames.FULL]: { | 12 | [ScopeNames.FULL]: { |
13 | include: [ | 13 | include: [ |
14 | { | 14 | { |
15 | model: () => AccountModel, | 15 | model: () => ActorModel, |
16 | required: true | 16 | required: true |
17 | }, | 17 | }, |
18 | { | 18 | { |
@@ -21,10 +21,10 @@ enum ScopeNames { | |||
21 | } | 21 | } |
22 | ] | 22 | ] |
23 | }, | 23 | }, |
24 | [ScopeNames.WITH_ACCOUNT]: { | 24 | [ScopeNames.WITH_ACTOR]: { |
25 | include: [ | 25 | include: [ |
26 | { | 26 | { |
27 | model: () => AccountModel, | 27 | model: () => ActorModel, |
28 | required: true | 28 | required: true |
29 | } | 29 | } |
30 | ] | 30 | ] |
@@ -34,7 +34,7 @@ enum ScopeNames { | |||
34 | tableName: 'videoShare', | 34 | tableName: 'videoShare', |
35 | indexes: [ | 35 | indexes: [ |
36 | { | 36 | { |
37 | fields: [ 'accountId' ] | 37 | fields: [ 'actorId' ] |
38 | }, | 38 | }, |
39 | { | 39 | { |
40 | fields: [ 'videoId' ] | 40 | fields: [ 'videoId' ] |
@@ -48,17 +48,17 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
48 | @UpdatedAt | 48 | @UpdatedAt |
49 | updatedAt: Date | 49 | updatedAt: Date |
50 | 50 | ||
51 | @ForeignKey(() => AccountModel) | 51 | @ForeignKey(() => ActorModel) |
52 | @Column | 52 | @Column |
53 | accountId: number | 53 | actorId: number |
54 | 54 | ||
55 | @BelongsTo(() => AccountModel, { | 55 | @BelongsTo(() => ActorModel, { |
56 | foreignKey: { | 56 | foreignKey: { |
57 | allowNull: false | 57 | allowNull: false |
58 | }, | 58 | }, |
59 | onDelete: 'cascade' | 59 | onDelete: 'cascade' |
60 | }) | 60 | }) |
61 | Account: AccountModel | 61 | Actor: ActorModel |
62 | 62 | ||
63 | @ForeignKey(() => VideoModel) | 63 | @ForeignKey(() => VideoModel) |
64 | @Column | 64 | @Column |
@@ -72,24 +72,24 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
72 | }) | 72 | }) |
73 | Video: VideoModel | 73 | Video: VideoModel |
74 | 74 | ||
75 | static load (accountId: number, videoId: number, t: Sequelize.Transaction) { | 75 | static load (actorId: number, videoId: number, t: Sequelize.Transaction) { |
76 | return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({ | 76 | return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ |
77 | where: { | 77 | where: { |
78 | accountId, | 78 | actorId, |
79 | videoId | 79 | videoId |
80 | }, | 80 | }, |
81 | transaction: t | 81 | transaction: t |
82 | }) | 82 | }) |
83 | } | 83 | } |
84 | 84 | ||
85 | static loadAccountsByShare (videoId: number, t: Sequelize.Transaction) { | 85 | static loadActorsByShare (videoId: number, t: Sequelize.Transaction) { |
86 | const query = { | 86 | const query = { |
87 | where: { | 87 | where: { |
88 | videoId | 88 | videoId |
89 | }, | 89 | }, |
90 | include: [ | 90 | include: [ |
91 | { | 91 | { |
92 | model: AccountModel, | 92 | model: ActorModel, |
93 | required: true | 93 | required: true |
94 | } | 94 | } |
95 | ], | 95 | ], |
@@ -97,6 +97,6 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
97 | } | 97 | } |
98 | 98 | ||
99 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) | 99 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) |
100 | .then(res => res.map(r => r.Account)) | 100 | .then(res => res.map(r => r.Actor)) |
101 | } | 101 | } |
102 | } | 102 | } |
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 { | |||
66 | VIDEO_PRIVACIES | 66 | VIDEO_PRIVACIES |
67 | } from '../../initializers' | 67 | } from '../../initializers' |
68 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' | 68 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' |
69 | import { sendDeleteVideo } from '../../lib/index' | 69 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
70 | import { AccountModel } from '../account/account' | 70 | import { AccountModel } from '../account/account' |
71 | import { AccountVideoRateModel } from '../account/account-video-rate' | 71 | import { AccountVideoRateModel } from '../account/account-video-rate' |
72 | import { ActorModel } from '../activitypub/actor' | ||
72 | import { ServerModel } from '../server/server' | 73 | import { ServerModel } from '../server/server' |
73 | import { getSort, throwIfNotValid } from '../utils' | 74 | import { getSort, throwIfNotValid } from '../utils' |
74 | import { TagModel } from './tag' | 75 | import { TagModel } from './tag' |
@@ -79,8 +80,7 @@ import { VideoShareModel } from './video-share' | |||
79 | import { VideoTagModel } from './video-tag' | 80 | import { VideoTagModel } from './video-tag' |
80 | 81 | ||
81 | enum ScopeNames { | 82 | enum ScopeNames { |
82 | NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST', | 83 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
83 | PUBLIC = 'PUBLIC', | ||
84 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 84 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
85 | WITH_TAGS = 'WITH_TAGS', | 85 | WITH_TAGS = 'WITH_TAGS', |
86 | WITH_FILES = 'WITH_FILES', | 86 | WITH_FILES = 'WITH_FILES', |
@@ -89,17 +89,13 @@ enum ScopeNames { | |||
89 | } | 89 | } |
90 | 90 | ||
91 | @Scopes({ | 91 | @Scopes({ |
92 | [ScopeNames.NOT_IN_BLACKLIST]: { | 92 | [ScopeNames.AVAILABLE_FOR_LIST]: { |
93 | where: { | 93 | where: { |
94 | id: { | 94 | id: { |
95 | [Sequelize.Op.notIn]: Sequelize.literal( | 95 | [Sequelize.Op.notIn]: Sequelize.literal( |
96 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | 96 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' |
97 | ) | 97 | ) |
98 | } | 98 | }, |
99 | } | ||
100 | }, | ||
101 | [ScopeNames.PUBLIC]: { | ||
102 | where: { | ||
103 | privacy: VideoPrivacy.PUBLIC | 99 | privacy: VideoPrivacy.PUBLIC |
104 | } | 100 | } |
105 | }, | 101 | }, |
@@ -114,8 +110,14 @@ enum ScopeNames { | |||
114 | required: true, | 110 | required: true, |
115 | include: [ | 111 | include: [ |
116 | { | 112 | { |
117 | model: () => ServerModel, | 113 | model: () => ActorModel, |
118 | required: false | 114 | required: true, |
115 | include: [ | ||
116 | { | ||
117 | model: () => ServerModel, | ||
118 | required: false | ||
119 | } | ||
120 | ] | ||
119 | } | 121 | } |
120 | ] | 122 | ] |
121 | } | 123 | } |
@@ -138,7 +140,7 @@ enum ScopeNames { | |||
138 | include: [ | 140 | include: [ |
139 | { | 141 | { |
140 | model: () => VideoShareModel, | 142 | model: () => VideoShareModel, |
141 | include: [ () => AccountModel ] | 143 | include: [ () => ActorModel ] |
142 | } | 144 | } |
143 | ] | 145 | ] |
144 | }, | 146 | }, |
@@ -271,7 +273,7 @@ export class VideoModel extends Model<VideoModel> { | |||
271 | 273 | ||
272 | @BelongsTo(() => VideoChannelModel, { | 274 | @BelongsTo(() => VideoChannelModel, { |
273 | foreignKey: { | 275 | foreignKey: { |
274 | allowNull: false | 276 | allowNull: true |
275 | }, | 277 | }, |
276 | onDelete: 'cascade' | 278 | onDelete: 'cascade' |
277 | }) | 279 | }) |
@@ -351,14 +353,15 @@ export class VideoModel extends Model<VideoModel> { | |||
351 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() | 353 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() |
352 | } | 354 | } |
353 | 355 | ||
354 | static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { | 356 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { |
355 | function getRawQuery (select: string) { | 357 | function getRawQuery (select: string) { |
356 | const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + | 358 | const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + |
357 | 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + | 359 | 'INNER JOIN "videoChannel" AS "VideoChannel" ON "VideoChannel"."id" = "Video"."channelId" ' + |
358 | 'WHERE "VideoChannel"."accountId" = ' + accountId | 360 | 'INNER JOIN "account" AS "Account" ON "Account"."id" = "VideoChannel"."accountId" ' + |
361 | 'WHERE "Account"."actorId" = ' + actorId | ||
359 | const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' + | 362 | const queryVideoShare = 'SELECT ' + select + ' FROM "videoShare" AS "VideoShare" ' + |
360 | 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + | 363 | 'INNER JOIN "video" AS "Video" ON "Video"."id" = "VideoShare"."videoId" ' + |
361 | 'WHERE "VideoShare"."accountId" = ' + accountId | 364 | 'WHERE "VideoShare"."actorId" = ' + actorId |
362 | 365 | ||
363 | return `(${queryVideo}) UNION (${queryVideoShare})` | 366 | return `(${queryVideo}) UNION (${queryVideoShare})` |
364 | } | 367 | } |
@@ -388,11 +391,16 @@ export class VideoModel extends Model<VideoModel> { | |||
388 | } | 391 | } |
389 | }, | 392 | }, |
390 | { | 393 | { |
391 | accountId | 394 | actorId |
392 | } | 395 | } |
393 | ] | 396 | ] |
394 | }, | 397 | }, |
395 | include: [ AccountModel ] | 398 | include: [ |
399 | { | ||
400 | model: ActorModel, | ||
401 | required: true | ||
402 | } | ||
403 | ] | ||
396 | }, | 404 | }, |
397 | { | 405 | { |
398 | model: VideoChannelModel, | 406 | model: VideoChannelModel, |
@@ -469,7 +477,7 @@ export class VideoModel extends Model<VideoModel> { | |||
469 | order: [ getSort(sort) ] | 477 | order: [ getSort(sort) ] |
470 | } | 478 | } |
471 | 479 | ||
472 | return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ]) | 480 | return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST, ScopeNames.WITH_ACCOUNT ]) |
473 | .findAndCountAll(query) | 481 | .findAndCountAll(query) |
474 | .then(({ rows, count }) => { | 482 | .then(({ rows, count }) => { |
475 | return { | 483 | return { |
@@ -541,7 +549,13 @@ export class VideoModel extends Model<VideoModel> { | |||
541 | 549 | ||
542 | const accountInclude: IIncludeOptions = { | 550 | const accountInclude: IIncludeOptions = { |
543 | model: AccountModel, | 551 | model: AccountModel, |
544 | include: [ serverInclude ] | 552 | include: [ |
553 | { | ||
554 | model: ActorModel, | ||
555 | required: true, | ||
556 | include: [ serverInclude ] | ||
557 | } | ||
558 | ] | ||
545 | } | 559 | } |
546 | 560 | ||
547 | const videoChannelInclude: IIncludeOptions = { | 561 | const videoChannelInclude: IIncludeOptions = { |
@@ -586,7 +600,7 @@ export class VideoModel extends Model<VideoModel> { | |||
586 | videoChannelInclude, tagInclude | 600 | videoChannelInclude, tagInclude |
587 | ] | 601 | ] |
588 | 602 | ||
589 | return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ]) | 603 | return VideoModel.scope([ ScopeNames.AVAILABLE_FOR_LIST ]) |
590 | .findAndCountAll(query).then(({ rows, count }) => { | 604 | .findAndCountAll(query).then(({ rows, count }) => { |
591 | return { | 605 | return { |
592 | data: rows, | 606 | data: rows, |
@@ -688,8 +702,8 @@ export class VideoModel extends Model<VideoModel> { | |||
688 | toFormattedJSON () { | 702 | toFormattedJSON () { |
689 | let serverHost | 703 | let serverHost |
690 | 704 | ||
691 | if (this.VideoChannel.Account.Server) { | 705 | if (this.VideoChannel.Account.Actor.Server) { |
692 | serverHost = this.VideoChannel.Account.Server.host | 706 | serverHost = this.VideoChannel.Account.Actor.Server.host |
693 | } else { | 707 | } else { |
694 | // It means it's our video | 708 | // It means it's our video |
695 | serverHost = CONFIG.WEBSERVER.HOST | 709 | serverHost = CONFIG.WEBSERVER.HOST |
@@ -805,9 +819,9 @@ export class VideoModel extends Model<VideoModel> { | |||
805 | 819 | ||
806 | for (const rate of this.AccountVideoRates) { | 820 | for (const rate of this.AccountVideoRates) { |
807 | if (rate.type === 'like') { | 821 | if (rate.type === 'like') { |
808 | likes.push(rate.Account.url) | 822 | likes.push(rate.Account.Actor.url) |
809 | } else if (rate.type === 'dislike') { | 823 | } else if (rate.type === 'dislike') { |
810 | dislikes.push(rate.Account.url) | 824 | dislikes.push(rate.Account.Actor.url) |
811 | } | 825 | } |
812 | } | 826 | } |
813 | 827 | ||
@@ -820,7 +834,7 @@ export class VideoModel extends Model<VideoModel> { | |||
820 | const shares: string[] = [] | 834 | const shares: string[] = [] |
821 | 835 | ||
822 | for (const videoShare of this.VideoShares) { | 836 | for (const videoShare of this.VideoShares) { |
823 | const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Account) | 837 | const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor) |
824 | shares.push(shareUrl) | 838 | shares.push(shareUrl) |
825 | } | 839 | } |
826 | 840 | ||
@@ -886,7 +900,13 @@ export class VideoModel extends Model<VideoModel> { | |||
886 | url, | 900 | url, |
887 | likes: likesObject, | 901 | likes: likesObject, |
888 | dislikes: dislikesObject, | 902 | dislikes: dislikesObject, |
889 | shares: sharesObject | 903 | shares: sharesObject, |
904 | attributedTo: [ | ||
905 | { | ||
906 | type: 'Group', | ||
907 | id: this.VideoChannel.Actor.url | ||
908 | } | ||
909 | ] | ||
890 | } | 910 | } |
891 | } | 911 | } |
892 | 912 | ||
@@ -1030,8 +1050,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1030 | baseUrlHttp = CONFIG.WEBSERVER.URL | 1050 | baseUrlHttp = CONFIG.WEBSERVER.URL |
1031 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 1051 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT |
1032 | } else { | 1052 | } else { |
1033 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Server.host | 1053 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host |
1034 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Server.host | 1054 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host |
1035 | } | 1055 | } |
1036 | 1056 | ||
1037 | return { baseUrlHttp, baseUrlWs } | 1057 | return { baseUrlHttp, baseUrlWs } |