diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-11-09 17:51:58 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-11-27 19:40:51 +0100 |
commit | e4f97babf701481b55cc10fb3448feab5f97c867 (patch) | |
tree | af37402a594dc5ff09f71ecb0687e8cfe4cdb471 /server/models | |
parent | 343ad675f2a26c15b86150a9a3552e619d5d44f4 (diff) | |
download | PeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.tar.gz PeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.tar.zst PeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.zip |
Begin activitypub
Diffstat (limited to 'server/models')
23 files changed, 981 insertions, 585 deletions
diff --git a/server/models/account/account-follow-interface.ts b/server/models/account/account-follow-interface.ts new file mode 100644 index 000000000..3be383649 --- /dev/null +++ b/server/models/account/account-follow-interface.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { VideoRateType } from '../../../shared/models/videos/video-rate.type' | ||
5 | |||
6 | export namespace AccountFollowMethods { | ||
7 | } | ||
8 | |||
9 | export interface AccountFollowClass { | ||
10 | } | ||
11 | |||
12 | export interface AccountFollowAttributes { | ||
13 | accountId: number | ||
14 | targetAccountId: number | ||
15 | } | ||
16 | |||
17 | export interface AccountFollowInstance extends AccountFollowClass, AccountFollowAttributes, Sequelize.Instance<AccountFollowAttributes> { | ||
18 | id: number | ||
19 | createdAt: Date | ||
20 | updatedAt: Date | ||
21 | } | ||
22 | |||
23 | export interface AccountFollowModel extends AccountFollowClass, Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> {} | ||
diff --git a/server/models/account/account-follow.ts b/server/models/account/account-follow.ts new file mode 100644 index 000000000..9bf03b253 --- /dev/null +++ b/server/models/account/account-follow.ts | |||
@@ -0,0 +1,56 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | import { addMethodsToModel } from '../utils' | ||
4 | import { | ||
5 | AccountFollowInstance, | ||
6 | AccountFollowAttributes, | ||
7 | |||
8 | AccountFollowMethods | ||
9 | } from './account-follow-interface' | ||
10 | |||
11 | let AccountFollow: Sequelize.Model<AccountFollowInstance, AccountFollowAttributes> | ||
12 | |||
13 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | ||
14 | AccountFollow = sequelize.define<AccountFollowInstance, AccountFollowAttributes>('AccountFollow', | ||
15 | { }, | ||
16 | { | ||
17 | indexes: [ | ||
18 | { | ||
19 | fields: [ 'accountId' ], | ||
20 | unique: true | ||
21 | }, | ||
22 | { | ||
23 | fields: [ 'targetAccountId' ], | ||
24 | unique: true | ||
25 | } | ||
26 | ] | ||
27 | } | ||
28 | ) | ||
29 | |||
30 | const classMethods = [ | ||
31 | associate | ||
32 | ] | ||
33 | addMethodsToModel(AccountFollow, classMethods) | ||
34 | |||
35 | return AccountFollow | ||
36 | } | ||
37 | |||
38 | // ------------------------------ STATICS ------------------------------ | ||
39 | |||
40 | function associate (models) { | ||
41 | AccountFollow.belongsTo(models.Account, { | ||
42 | foreignKey: { | ||
43 | name: 'accountId', | ||
44 | allowNull: false | ||
45 | }, | ||
46 | onDelete: 'CASCADE' | ||
47 | }) | ||
48 | |||
49 | AccountFollow.belongsTo(models.Account, { | ||
50 | foreignKey: { | ||
51 | name: 'targetAccountId', | ||
52 | allowNull: false | ||
53 | }, | ||
54 | onDelete: 'CASCADE' | ||
55 | }) | ||
56 | } | ||
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts new file mode 100644 index 000000000..2ef3e2246 --- /dev/null +++ b/server/models/account/account-interface.ts | |||
@@ -0,0 +1,74 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Bluebird from 'bluebird' | ||
3 | |||
4 | import { PodInstance } from '../pod/pod-interface' | ||
5 | import { VideoChannelInstance } from '../video/video-channel-interface' | ||
6 | import { ActivityPubActor } from '../../../shared' | ||
7 | import { ResultList } from '../../../shared/models/result-list.model' | ||
8 | |||
9 | export namespace AccountMethods { | ||
10 | export type Load = (id: number) => Bluebird<AccountInstance> | ||
11 | export type LoadByUUID = (uuid: string) => Bluebird<AccountInstance> | ||
12 | export type LoadByUrl = (url: string) => Bluebird<AccountInstance> | ||
13 | export type LoadAccountByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Bluebird<AccountInstance> | ||
14 | export type LoadLocalAccountByName = (name: string) => Bluebird<AccountInstance> | ||
15 | export type ListOwned = () => Bluebird<AccountInstance[]> | ||
16 | export type ListFollowerUrlsForApi = (name: string, start: number, count: number) => Promise< ResultList<string> > | ||
17 | export type ListFollowingUrlsForApi = (name: string, start: number, count: number) => Promise< ResultList<string> > | ||
18 | |||
19 | export type ToActivityPubObject = (this: AccountInstance) => ActivityPubActor | ||
20 | export type IsOwned = (this: AccountInstance) => boolean | ||
21 | export type GetFollowerSharedInboxUrls = (this: AccountInstance) => Bluebird<string[]> | ||
22 | export type GetFollowingUrl = (this: AccountInstance) => string | ||
23 | export type GetFollowersUrl = (this: AccountInstance) => string | ||
24 | export type GetPublicKeyUrl = (this: AccountInstance) => string | ||
25 | } | ||
26 | |||
27 | export interface AccountClass { | ||
28 | loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID | ||
29 | load: AccountMethods.Load | ||
30 | loadByUUID: AccountMethods.LoadByUUID | ||
31 | loadByUrl: AccountMethods.LoadByUrl | ||
32 | loadLocalAccountByName: AccountMethods.LoadLocalAccountByName | ||
33 | listOwned: AccountMethods.ListOwned | ||
34 | listFollowerUrlsForApi: AccountMethods.ListFollowerUrlsForApi | ||
35 | listFollowingUrlsForApi: AccountMethods.ListFollowingUrlsForApi | ||
36 | } | ||
37 | |||
38 | export interface AccountAttributes { | ||
39 | name: string | ||
40 | url: string | ||
41 | publicKey: string | ||
42 | privateKey: string | ||
43 | followersCount: number | ||
44 | followingCount: number | ||
45 | inboxUrl: string | ||
46 | outboxUrl: string | ||
47 | sharedInboxUrl: string | ||
48 | followersUrl: string | ||
49 | followingUrl: string | ||
50 | |||
51 | uuid?: string | ||
52 | |||
53 | podId?: number | ||
54 | userId?: number | ||
55 | applicationId?: number | ||
56 | } | ||
57 | |||
58 | export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance<AccountAttributes> { | ||
59 | isOwned: AccountMethods.IsOwned | ||
60 | toActivityPubObject: AccountMethods.ToActivityPubObject | ||
61 | getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls | ||
62 | getFollowingUrl: AccountMethods.GetFollowingUrl | ||
63 | getFollowersUrl: AccountMethods.GetFollowersUrl | ||
64 | getPublicKeyUrl: AccountMethods.GetPublicKeyUrl | ||
65 | |||
66 | id: number | ||
67 | createdAt: Date | ||
68 | updatedAt: Date | ||
69 | |||
70 | Pod: PodInstance | ||
71 | VideoChannels: VideoChannelInstance[] | ||
72 | } | ||
73 | |||
74 | export interface AccountModel extends AccountClass, Sequelize.Model<AccountInstance, AccountAttributes> {} | ||
diff --git a/server/models/account/account-video-rate-interface.ts b/server/models/account/account-video-rate-interface.ts new file mode 100644 index 000000000..82cbe38cc --- /dev/null +++ b/server/models/account/account-video-rate-interface.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { VideoRateType } from '../../../shared/models/videos/video-rate.type' | ||
5 | |||
6 | export namespace AccountVideoRateMethods { | ||
7 | export type Load = (accountId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<AccountVideoRateInstance> | ||
8 | } | ||
9 | |||
10 | export interface AccountVideoRateClass { | ||
11 | load: AccountVideoRateMethods.Load | ||
12 | } | ||
13 | |||
14 | export interface AccountVideoRateAttributes { | ||
15 | type: VideoRateType | ||
16 | accountId: number | ||
17 | videoId: number | ||
18 | } | ||
19 | |||
20 | export interface AccountVideoRateInstance extends AccountVideoRateClass, AccountVideoRateAttributes, Sequelize.Instance<AccountVideoRateAttributes> { | ||
21 | id: number | ||
22 | createdAt: Date | ||
23 | updatedAt: Date | ||
24 | } | ||
25 | |||
26 | export interface AccountVideoRateModel extends AccountVideoRateClass, Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes> {} | ||
diff --git a/server/models/user/user-video-rate.ts b/server/models/account/account-video-rate.ts index 7d6dd7281..7f7c97606 100644 --- a/server/models/user/user-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | User rates per video. | 2 | Account rates per video. |
3 | */ | 3 | */ |
4 | import { values } from 'lodash' | 4 | import { values } from 'lodash' |
5 | import * as Sequelize from 'sequelize' | 5 | import * as Sequelize from 'sequelize' |
@@ -8,17 +8,17 @@ import { VIDEO_RATE_TYPES } from '../../initializers' | |||
8 | 8 | ||
9 | import { addMethodsToModel } from '../utils' | 9 | import { addMethodsToModel } from '../utils' |
10 | import { | 10 | import { |
11 | UserVideoRateInstance, | 11 | AccountVideoRateInstance, |
12 | UserVideoRateAttributes, | 12 | AccountVideoRateAttributes, |
13 | 13 | ||
14 | UserVideoRateMethods | 14 | AccountVideoRateMethods |
15 | } from './user-video-rate-interface' | 15 | } from './account-video-rate-interface' |
16 | 16 | ||
17 | let UserVideoRate: Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes> | 17 | let AccountVideoRate: Sequelize.Model<AccountVideoRateInstance, AccountVideoRateAttributes> |
18 | let load: UserVideoRateMethods.Load | 18 | let load: AccountVideoRateMethods.Load |
19 | 19 | ||
20 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 20 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
21 | UserVideoRate = sequelize.define<UserVideoRateInstance, UserVideoRateAttributes>('UserVideoRate', | 21 | AccountVideoRate = sequelize.define<AccountVideoRateInstance, AccountVideoRateAttributes>('AccountVideoRate', |
22 | { | 22 | { |
23 | type: { | 23 | type: { |
24 | type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)), | 24 | type: DataTypes.ENUM(values(VIDEO_RATE_TYPES)), |
@@ -28,7 +28,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
28 | { | 28 | { |
29 | indexes: [ | 29 | indexes: [ |
30 | { | 30 | { |
31 | fields: [ 'videoId', 'userId', 'type' ], | 31 | fields: [ 'videoId', 'accountId', 'type' ], |
32 | unique: true | 32 | unique: true |
33 | } | 33 | } |
34 | ] | 34 | ] |
@@ -40,15 +40,15 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
40 | 40 | ||
41 | load | 41 | load |
42 | ] | 42 | ] |
43 | addMethodsToModel(UserVideoRate, classMethods) | 43 | addMethodsToModel(AccountVideoRate, classMethods) |
44 | 44 | ||
45 | return UserVideoRate | 45 | return AccountVideoRate |
46 | } | 46 | } |
47 | 47 | ||
48 | // ------------------------------ STATICS ------------------------------ | 48 | // ------------------------------ STATICS ------------------------------ |
49 | 49 | ||
50 | function associate (models) { | 50 | function associate (models) { |
51 | UserVideoRate.belongsTo(models.Video, { | 51 | AccountVideoRate.belongsTo(models.Video, { |
52 | foreignKey: { | 52 | foreignKey: { |
53 | name: 'videoId', | 53 | name: 'videoId', |
54 | allowNull: false | 54 | allowNull: false |
@@ -56,23 +56,23 @@ function associate (models) { | |||
56 | onDelete: 'CASCADE' | 56 | onDelete: 'CASCADE' |
57 | }) | 57 | }) |
58 | 58 | ||
59 | UserVideoRate.belongsTo(models.User, { | 59 | AccountVideoRate.belongsTo(models.Account, { |
60 | foreignKey: { | 60 | foreignKey: { |
61 | name: 'userId', | 61 | name: 'accountId', |
62 | allowNull: false | 62 | allowNull: false |
63 | }, | 63 | }, |
64 | onDelete: 'CASCADE' | 64 | onDelete: 'CASCADE' |
65 | }) | 65 | }) |
66 | } | 66 | } |
67 | 67 | ||
68 | load = function (userId: number, videoId: number, transaction: Sequelize.Transaction) { | 68 | load = function (accountId: number, videoId: number, transaction: Sequelize.Transaction) { |
69 | const options: Sequelize.FindOptions<UserVideoRateAttributes> = { | 69 | const options: Sequelize.FindOptions<AccountVideoRateAttributes> = { |
70 | where: { | 70 | where: { |
71 | userId, | 71 | accountId, |
72 | videoId | 72 | videoId |
73 | } | 73 | } |
74 | } | 74 | } |
75 | if (transaction) options.transaction = transaction | 75 | if (transaction) options.transaction = transaction |
76 | 76 | ||
77 | return UserVideoRate.findOne(options) | 77 | return AccountVideoRate.findOne(options) |
78 | } | 78 | } |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts new file mode 100644 index 000000000..00c0aefd4 --- /dev/null +++ b/server/models/account/account.ts | |||
@@ -0,0 +1,444 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | import { | ||
4 | isUserUsernameValid, | ||
5 | isAccountPublicKeyValid, | ||
6 | isAccountUrlValid, | ||
7 | isAccountPrivateKeyValid, | ||
8 | isAccountFollowersCountValid, | ||
9 | isAccountFollowingCountValid, | ||
10 | isAccountInboxValid, | ||
11 | isAccountOutboxValid, | ||
12 | isAccountSharedInboxValid, | ||
13 | isAccountFollowersValid, | ||
14 | isAccountFollowingValid, | ||
15 | activityPubContextify | ||
16 | } from '../../helpers' | ||
17 | |||
18 | import { addMethodsToModel } from '../utils' | ||
19 | import { | ||
20 | AccountInstance, | ||
21 | AccountAttributes, | ||
22 | |||
23 | AccountMethods | ||
24 | } from './account-interface' | ||
25 | |||
26 | let Account: Sequelize.Model<AccountInstance, AccountAttributes> | ||
27 | let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID | ||
28 | let load: AccountMethods.Load | ||
29 | let loadByUUID: AccountMethods.LoadByUUID | ||
30 | let loadByUrl: AccountMethods.LoadByUrl | ||
31 | let loadLocalAccountByName: AccountMethods.LoadLocalAccountByName | ||
32 | let listOwned: AccountMethods.ListOwned | ||
33 | let listFollowerUrlsForApi: AccountMethods.ListFollowerUrlsForApi | ||
34 | let listFollowingUrlsForApi: AccountMethods.ListFollowingUrlsForApi | ||
35 | let isOwned: AccountMethods.IsOwned | ||
36 | let toActivityPubObject: AccountMethods.ToActivityPubObject | ||
37 | let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls | ||
38 | let getFollowingUrl: AccountMethods.GetFollowingUrl | ||
39 | let getFollowersUrl: AccountMethods.GetFollowersUrl | ||
40 | let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl | ||
41 | |||
42 | export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | ||
43 | Account = sequelize.define<AccountInstance, AccountAttributes>('Account', | ||
44 | { | ||
45 | uuid: { | ||
46 | type: DataTypes.UUID, | ||
47 | defaultValue: DataTypes.UUIDV4, | ||
48 | allowNull: false, | ||
49 | validate: { | ||
50 | isUUID: 4 | ||
51 | } | ||
52 | }, | ||
53 | name: { | ||
54 | type: DataTypes.STRING, | ||
55 | allowNull: false, | ||
56 | validate: { | ||
57 | usernameValid: value => { | ||
58 | const res = isUserUsernameValid(value) | ||
59 | if (res === false) throw new Error('Username is not valid.') | ||
60 | } | ||
61 | } | ||
62 | }, | ||
63 | url: { | ||
64 | type: DataTypes.STRING, | ||
65 | allowNull: false, | ||
66 | validate: { | ||
67 | urlValid: value => { | ||
68 | const res = isAccountUrlValid(value) | ||
69 | if (res === false) throw new Error('URL is not valid.') | ||
70 | } | ||
71 | } | ||
72 | }, | ||
73 | publicKey: { | ||
74 | type: DataTypes.STRING, | ||
75 | allowNull: false, | ||
76 | validate: { | ||
77 | publicKeyValid: value => { | ||
78 | const res = isAccountPublicKeyValid(value) | ||
79 | if (res === false) throw new Error('Public key is not valid.') | ||
80 | } | ||
81 | } | ||
82 | }, | ||
83 | privateKey: { | ||
84 | type: DataTypes.STRING, | ||
85 | allowNull: false, | ||
86 | validate: { | ||
87 | privateKeyValid: value => { | ||
88 | const res = isAccountPrivateKeyValid(value) | ||
89 | if (res === false) throw new Error('Private key is not valid.') | ||
90 | } | ||
91 | } | ||
92 | }, | ||
93 | followersCount: { | ||
94 | type: DataTypes.INTEGER, | ||
95 | allowNull: false, | ||
96 | validate: { | ||
97 | followersCountValid: value => { | ||
98 | const res = isAccountFollowersCountValid(value) | ||
99 | if (res === false) throw new Error('Followers count is not valid.') | ||
100 | } | ||
101 | } | ||
102 | }, | ||
103 | followingCount: { | ||
104 | type: DataTypes.INTEGER, | ||
105 | allowNull: false, | ||
106 | validate: { | ||
107 | followersCountValid: value => { | ||
108 | const res = isAccountFollowingCountValid(value) | ||
109 | if (res === false) throw new Error('Following count is not valid.') | ||
110 | } | ||
111 | } | ||
112 | }, | ||
113 | inboxUrl: { | ||
114 | type: DataTypes.STRING, | ||
115 | allowNull: false, | ||
116 | validate: { | ||
117 | inboxUrlValid: value => { | ||
118 | const res = isAccountInboxValid(value) | ||
119 | if (res === false) throw new Error('Inbox URL is not valid.') | ||
120 | } | ||
121 | } | ||
122 | }, | ||
123 | outboxUrl: { | ||
124 | type: DataTypes.STRING, | ||
125 | allowNull: false, | ||
126 | validate: { | ||
127 | outboxUrlValid: value => { | ||
128 | const res = isAccountOutboxValid(value) | ||
129 | if (res === false) throw new Error('Outbox URL is not valid.') | ||
130 | } | ||
131 | } | ||
132 | }, | ||
133 | sharedInboxUrl: { | ||
134 | type: DataTypes.STRING, | ||
135 | allowNull: false, | ||
136 | validate: { | ||
137 | sharedInboxUrlValid: value => { | ||
138 | const res = isAccountSharedInboxValid(value) | ||
139 | if (res === false) throw new Error('Shared inbox URL is not valid.') | ||
140 | } | ||
141 | } | ||
142 | }, | ||
143 | followersUrl: { | ||
144 | type: DataTypes.STRING, | ||
145 | allowNull: false, | ||
146 | validate: { | ||
147 | followersUrlValid: value => { | ||
148 | const res = isAccountFollowersValid(value) | ||
149 | if (res === false) throw new Error('Followers URL is not valid.') | ||
150 | } | ||
151 | } | ||
152 | }, | ||
153 | followingUrl: { | ||
154 | type: DataTypes.STRING, | ||
155 | allowNull: false, | ||
156 | validate: { | ||
157 | followingUrlValid: value => { | ||
158 | const res = isAccountFollowingValid(value) | ||
159 | if (res === false) throw new Error('Following URL is not valid.') | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | }, | ||
164 | { | ||
165 | indexes: [ | ||
166 | { | ||
167 | fields: [ 'name' ] | ||
168 | }, | ||
169 | { | ||
170 | fields: [ 'podId' ] | ||
171 | }, | ||
172 | { | ||
173 | fields: [ 'userId' ], | ||
174 | unique: true | ||
175 | }, | ||
176 | { | ||
177 | fields: [ 'applicationId' ], | ||
178 | unique: true | ||
179 | }, | ||
180 | { | ||
181 | fields: [ 'name', 'podId' ], | ||
182 | unique: true | ||
183 | } | ||
184 | ], | ||
185 | hooks: { afterDestroy } | ||
186 | } | ||
187 | ) | ||
188 | |||
189 | const classMethods = [ | ||
190 | associate, | ||
191 | loadAccountByPodAndUUID, | ||
192 | load, | ||
193 | loadByUUID, | ||
194 | loadLocalAccountByName, | ||
195 | listOwned, | ||
196 | listFollowerUrlsForApi, | ||
197 | listFollowingUrlsForApi | ||
198 | ] | ||
199 | const instanceMethods = [ | ||
200 | isOwned, | ||
201 | toActivityPubObject, | ||
202 | getFollowerSharedInboxUrls, | ||
203 | getFollowingUrl, | ||
204 | getFollowersUrl, | ||
205 | getPublicKeyUrl | ||
206 | ] | ||
207 | addMethodsToModel(Account, classMethods, instanceMethods) | ||
208 | |||
209 | return Account | ||
210 | } | ||
211 | |||
212 | // --------------------------------------------------------------------------- | ||
213 | |||
214 | function associate (models) { | ||
215 | Account.belongsTo(models.Pod, { | ||
216 | foreignKey: { | ||
217 | name: 'podId', | ||
218 | allowNull: true | ||
219 | }, | ||
220 | onDelete: 'cascade' | ||
221 | }) | ||
222 | |||
223 | Account.belongsTo(models.User, { | ||
224 | foreignKey: { | ||
225 | name: 'userId', | ||
226 | allowNull: true | ||
227 | }, | ||
228 | onDelete: 'cascade' | ||
229 | }) | ||
230 | |||
231 | Account.belongsTo(models.Application, { | ||
232 | foreignKey: { | ||
233 | name: 'userId', | ||
234 | allowNull: true | ||
235 | }, | ||
236 | onDelete: 'cascade' | ||
237 | }) | ||
238 | |||
239 | Account.hasMany(models.VideoChannel, { | ||
240 | foreignKey: { | ||
241 | name: 'accountId', | ||
242 | allowNull: false | ||
243 | }, | ||
244 | onDelete: 'cascade', | ||
245 | hooks: true | ||
246 | }) | ||
247 | |||
248 | Account.hasMany(models.AccountFollower, { | ||
249 | foreignKey: { | ||
250 | name: 'accountId', | ||
251 | allowNull: false | ||
252 | }, | ||
253 | onDelete: 'cascade' | ||
254 | }) | ||
255 | |||
256 | Account.hasMany(models.AccountFollower, { | ||
257 | foreignKey: { | ||
258 | name: 'targetAccountId', | ||
259 | allowNull: false | ||
260 | }, | ||
261 | onDelete: 'cascade' | ||
262 | }) | ||
263 | } | ||
264 | |||
265 | function afterDestroy (account: AccountInstance) { | ||
266 | if (account.isOwned()) { | ||
267 | const removeVideoAccountToFriendsParams = { | ||
268 | uuid: account.uuid | ||
269 | } | ||
270 | |||
271 | return removeVideoAccountToFriends(removeVideoAccountToFriendsParams) | ||
272 | } | ||
273 | |||
274 | return undefined | ||
275 | } | ||
276 | |||
277 | toActivityPubObject = function (this: AccountInstance) { | ||
278 | const type = this.podId ? 'Application' : 'Person' | ||
279 | |||
280 | const json = { | ||
281 | type, | ||
282 | id: this.url, | ||
283 | following: this.getFollowingUrl(), | ||
284 | followers: this.getFollowersUrl(), | ||
285 | inbox: this.inboxUrl, | ||
286 | outbox: this.outboxUrl, | ||
287 | preferredUsername: this.name, | ||
288 | url: this.url, | ||
289 | name: this.name, | ||
290 | endpoints: { | ||
291 | sharedInbox: this.sharedInboxUrl | ||
292 | }, | ||
293 | uuid: this.uuid, | ||
294 | publicKey: { | ||
295 | id: this.getPublicKeyUrl(), | ||
296 | owner: this.url, | ||
297 | publicKeyPem: this.publicKey | ||
298 | } | ||
299 | } | ||
300 | |||
301 | return activityPubContextify(json) | ||
302 | } | ||
303 | |||
304 | isOwned = function (this: AccountInstance) { | ||
305 | return this.podId === null | ||
306 | } | ||
307 | |||
308 | getFollowerSharedInboxUrls = function (this: AccountInstance) { | ||
309 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
310 | attributes: [ 'sharedInboxUrl' ], | ||
311 | include: [ | ||
312 | { | ||
313 | model: Account['sequelize'].models.AccountFollower, | ||
314 | where: { | ||
315 | targetAccountId: this.id | ||
316 | } | ||
317 | } | ||
318 | ] | ||
319 | } | ||
320 | |||
321 | return Account.findAll(query) | ||
322 | .then(accounts => accounts.map(a => a.sharedInboxUrl)) | ||
323 | } | ||
324 | |||
325 | getFollowingUrl = function (this: AccountInstance) { | ||
326 | return this.url + '/followers' | ||
327 | } | ||
328 | |||
329 | getFollowersUrl = function (this: AccountInstance) { | ||
330 | return this.url + '/followers' | ||
331 | } | ||
332 | |||
333 | getPublicKeyUrl = function (this: AccountInstance) { | ||
334 | return this.url + '#main-key' | ||
335 | } | ||
336 | |||
337 | // ------------------------------ STATICS ------------------------------ | ||
338 | |||
339 | listOwned = function () { | ||
340 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
341 | where: { | ||
342 | podId: null | ||
343 | } | ||
344 | } | ||
345 | |||
346 | return Account.findAll(query) | ||
347 | } | ||
348 | |||
349 | listFollowerUrlsForApi = function (name: string, start: number, count: number) { | ||
350 | return createListFollowForApiQuery('followers', name, start, count) | ||
351 | } | ||
352 | |||
353 | listFollowingUrlsForApi = function (name: string, start: number, count: number) { | ||
354 | return createListFollowForApiQuery('following', name, start, count) | ||
355 | } | ||
356 | |||
357 | load = function (id: number) { | ||
358 | return Account.findById(id) | ||
359 | } | ||
360 | |||
361 | loadByUUID = function (uuid: string) { | ||
362 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
363 | where: { | ||
364 | uuid | ||
365 | } | ||
366 | } | ||
367 | |||
368 | return Account.findOne(query) | ||
369 | } | ||
370 | |||
371 | loadLocalAccountByName = function (name: string) { | ||
372 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
373 | where: { | ||
374 | name, | ||
375 | userId: { | ||
376 | [Sequelize.Op.ne]: null | ||
377 | } | ||
378 | } | ||
379 | } | ||
380 | |||
381 | return Account.findOne(query) | ||
382 | } | ||
383 | |||
384 | loadByUrl = function (url: string) { | ||
385 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
386 | where: { | ||
387 | url | ||
388 | } | ||
389 | } | ||
390 | |||
391 | return Account.findOne(query) | ||
392 | } | ||
393 | |||
394 | loadAccountByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) { | ||
395 | const query: Sequelize.FindOptions<AccountAttributes> = { | ||
396 | where: { | ||
397 | podId, | ||
398 | uuid | ||
399 | }, | ||
400 | transaction | ||
401 | } | ||
402 | |||
403 | return Account.find(query) | ||
404 | } | ||
405 | |||
406 | // ------------------------------ UTILS ------------------------------ | ||
407 | |||
408 | async function createListFollowForApiQuery (type: 'followers' | 'following', name: string, start: number, count: number) { | ||
409 | let firstJoin: string | ||
410 | let secondJoin: string | ||
411 | |||
412 | if (type === 'followers') { | ||
413 | firstJoin = 'targetAccountId' | ||
414 | secondJoin = 'accountId' | ||
415 | } else { | ||
416 | firstJoin = 'accountId' | ||
417 | secondJoin = 'targetAccountId' | ||
418 | } | ||
419 | |||
420 | const selections = [ '"Followers"."url" AS "url"', 'COUNT(*) AS "total"' ] | ||
421 | const tasks: Promise<any>[] = [] | ||
422 | |||
423 | for (const selection of selections) { | ||
424 | const query = 'SELECT ' + selection + ' FROM "Account" ' + | ||
425 | 'INNER JOIN "AccountFollower" ON "AccountFollower"."' + firstJoin + '" = "Account"."id" ' + | ||
426 | 'INNER JOIN "Account" AS "Followers" ON "Followers"."id" = "AccountFollower"."' + secondJoin + '" ' + | ||
427 | 'WHERE "Account"."name" = \'$name\' ' + | ||
428 | 'LIMIT ' + start + ', ' + count | ||
429 | |||
430 | const options = { | ||
431 | bind: { name }, | ||
432 | type: Sequelize.QueryTypes.SELECT | ||
433 | } | ||
434 | tasks.push(Account['sequelize'].query(query, options)) | ||
435 | } | ||
436 | |||
437 | const [ followers, [ { total } ]] = await Promise.all(tasks) | ||
438 | const urls: string[] = followers.map(f => f.url) | ||
439 | |||
440 | return { | ||
441 | data: urls, | ||
442 | total: parseInt(total, 10) | ||
443 | } | ||
444 | } | ||
diff --git a/server/models/account/index.ts b/server/models/account/index.ts new file mode 100644 index 000000000..179f66974 --- /dev/null +++ b/server/models/account/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './account-interface' | ||
2 | export * from './account-follow-interface' | ||
3 | export * from './account-video-rate-interface' | ||
4 | export * from './user-interface' | ||
diff --git a/server/models/user/user-interface.ts b/server/models/account/user-interface.ts index 49c75aa3b..1a04fb750 100644 --- a/server/models/user/user-interface.ts +++ b/server/models/account/user-interface.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | 3 | ||
4 | // Don't use barrel, import just what we need | 4 | // Don't use barrel, import just what we need |
5 | import { AccountInstance } from './account-interface' | ||
5 | import { User as FormattedUser } from '../../../shared/models/users/user.model' | 6 | import { User as FormattedUser } from '../../../shared/models/users/user.model' |
6 | import { ResultList } from '../../../shared/models/result-list.model' | 7 | import { ResultList } from '../../../shared/models/result-list.model' |
7 | import { AuthorInstance } from '../video/author-interface' | ||
8 | import { UserRight } from '../../../shared/models/users/user-right.enum' | 8 | import { UserRight } from '../../../shared/models/users/user-right.enum' |
9 | import { UserRole } from '../../../shared/models/users/user-role' | 9 | import { UserRole } from '../../../shared/models/users/user-role' |
10 | 10 | ||
@@ -15,18 +15,18 @@ export namespace UserMethods { | |||
15 | export type ToFormattedJSON = (this: UserInstance) => FormattedUser | 15 | export type ToFormattedJSON = (this: UserInstance) => FormattedUser |
16 | export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean> | 16 | export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean> |
17 | 17 | ||
18 | export type CountTotal = () => Promise<number> | 18 | export type CountTotal = () => Bluebird<number> |
19 | 19 | ||
20 | export type GetByUsername = (username: string) => Promise<UserInstance> | 20 | export type GetByUsername = (username: string) => Bluebird<UserInstance> |
21 | 21 | ||
22 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<UserInstance> > | 22 | export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<UserInstance> > |
23 | 23 | ||
24 | export type LoadById = (id: number) => Promise<UserInstance> | 24 | export type LoadById = (id: number) => Bluebird<UserInstance> |
25 | 25 | ||
26 | export type LoadByUsername = (username: string) => Promise<UserInstance> | 26 | export type LoadByUsername = (username: string) => Bluebird<UserInstance> |
27 | export type LoadByUsernameAndPopulateChannels = (username: string) => Promise<UserInstance> | 27 | export type LoadByUsernameAndPopulateChannels = (username: string) => Bluebird<UserInstance> |
28 | 28 | ||
29 | export type LoadByUsernameOrEmail = (username: string, email: string) => Promise<UserInstance> | 29 | export type LoadByUsernameOrEmail = (username: string, email: string) => Bluebird<UserInstance> |
30 | } | 30 | } |
31 | 31 | ||
32 | export interface UserClass { | 32 | export interface UserClass { |
@@ -53,7 +53,7 @@ export interface UserAttributes { | |||
53 | role: UserRole | 53 | role: UserRole |
54 | videoQuota: number | 54 | videoQuota: number |
55 | 55 | ||
56 | Author?: AuthorInstance | 56 | Account?: AccountInstance |
57 | } | 57 | } |
58 | 58 | ||
59 | export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { | 59 | export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { |
diff --git a/server/models/user/user.ts b/server/models/account/user.ts index b974418d4..1401762c5 100644 --- a/server/models/user/user.ts +++ b/server/models/account/user.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | ||
3 | 2 | ||
4 | import { getSort, addMethodsToModel } from '../utils' | 3 | import { getSort, addMethodsToModel } from '../utils' |
5 | import { | 4 | import { |
@@ -166,13 +165,13 @@ toFormattedJSON = function (this: UserInstance) { | |||
166 | videoQuota: this.videoQuota, | 165 | videoQuota: this.videoQuota, |
167 | createdAt: this.createdAt, | 166 | createdAt: this.createdAt, |
168 | author: { | 167 | author: { |
169 | id: this.Author.id, | 168 | id: this.Account.id, |
170 | uuid: this.Author.uuid | 169 | uuid: this.Account.uuid |
171 | } | 170 | } |
172 | } | 171 | } |
173 | 172 | ||
174 | if (Array.isArray(this.Author.VideoChannels) === true) { | 173 | if (Array.isArray(this.Account.VideoChannels) === true) { |
175 | const videoChannels = this.Author.VideoChannels | 174 | const videoChannels = this.Account.VideoChannels |
176 | .map(c => c.toFormattedJSON()) | 175 | .map(c => c.toFormattedJSON()) |
177 | .sort((v1, v2) => { | 176 | .sort((v1, v2) => { |
178 | if (v1.createdAt < v2.createdAt) return -1 | 177 | if (v1.createdAt < v2.createdAt) return -1 |
@@ -198,7 +197,7 @@ isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.Fi | |||
198 | // ------------------------------ STATICS ------------------------------ | 197 | // ------------------------------ STATICS ------------------------------ |
199 | 198 | ||
200 | function associate (models) { | 199 | function associate (models) { |
201 | User.hasOne(models.Author, { | 200 | User.hasOne(models.Account, { |
202 | foreignKey: 'userId', | 201 | foreignKey: 'userId', |
203 | onDelete: 'cascade' | 202 | onDelete: 'cascade' |
204 | }) | 203 | }) |
@@ -218,7 +217,7 @@ getByUsername = function (username: string) { | |||
218 | where: { | 217 | where: { |
219 | username: username | 218 | username: username |
220 | }, | 219 | }, |
221 | include: [ { model: User['sequelize'].models.Author, required: true } ] | 220 | include: [ { model: User['sequelize'].models.Account, required: true } ] |
222 | } | 221 | } |
223 | 222 | ||
224 | return User.findOne(query) | 223 | return User.findOne(query) |
@@ -229,7 +228,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
229 | offset: start, | 228 | offset: start, |
230 | limit: count, | 229 | limit: count, |
231 | order: [ getSort(sort) ], | 230 | order: [ getSort(sort) ], |
232 | include: [ { model: User['sequelize'].models.Author, required: true } ] | 231 | include: [ { model: User['sequelize'].models.Account, required: true } ] |
233 | } | 232 | } |
234 | 233 | ||
235 | return User.findAndCountAll(query).then(({ rows, count }) => { | 234 | return User.findAndCountAll(query).then(({ rows, count }) => { |
@@ -242,7 +241,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
242 | 241 | ||
243 | loadById = function (id: number) { | 242 | loadById = function (id: number) { |
244 | const options = { | 243 | const options = { |
245 | include: [ { model: User['sequelize'].models.Author, required: true } ] | 244 | include: [ { model: User['sequelize'].models.Account, required: true } ] |
246 | } | 245 | } |
247 | 246 | ||
248 | return User.findById(id, options) | 247 | return User.findById(id, options) |
@@ -253,7 +252,7 @@ loadByUsername = function (username: string) { | |||
253 | where: { | 252 | where: { |
254 | username | 253 | username |
255 | }, | 254 | }, |
256 | include: [ { model: User['sequelize'].models.Author, required: true } ] | 255 | include: [ { model: User['sequelize'].models.Account, required: true } ] |
257 | } | 256 | } |
258 | 257 | ||
259 | return User.findOne(query) | 258 | return User.findOne(query) |
@@ -266,7 +265,7 @@ loadByUsernameAndPopulateChannels = function (username: string) { | |||
266 | }, | 265 | }, |
267 | include: [ | 266 | include: [ |
268 | { | 267 | { |
269 | model: User['sequelize'].models.Author, | 268 | model: User['sequelize'].models.Account, |
270 | required: true, | 269 | required: true, |
271 | include: [ User['sequelize'].models.VideoChannel ] | 270 | include: [ User['sequelize'].models.VideoChannel ] |
272 | } | 271 | } |
@@ -278,7 +277,7 @@ loadByUsernameAndPopulateChannels = function (username: string) { | |||
278 | 277 | ||
279 | loadByUsernameOrEmail = function (username: string, email: string) { | 278 | loadByUsernameOrEmail = function (username: string, email: string) { |
280 | const query = { | 279 | const query = { |
281 | include: [ { model: User['sequelize'].models.Author, required: true } ], | 280 | include: [ { model: User['sequelize'].models.Account, required: true } ], |
282 | where: { | 281 | where: { |
283 | [Sequelize.Op.or]: [ { username }, { email } ] | 282 | [Sequelize.Op.or]: [ { username }, { email } ] |
284 | } | 283 | } |
@@ -296,8 +295,8 @@ function getOriginalVideoFileTotalFromUser (user: UserInstance) { | |||
296 | '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' + | 295 | '(SELECT MAX("VideoFiles"."size") AS "size" FROM "VideoFiles" ' + |
297 | 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' + | 296 | 'INNER JOIN "Videos" ON "VideoFiles"."videoId" = "Videos"."id" ' + |
298 | 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' + | 297 | 'INNER JOIN "VideoChannels" ON "VideoChannels"."id" = "Videos"."channelId" ' + |
299 | 'INNER JOIN "Authors" ON "VideoChannels"."authorId" = "Authors"."id" ' + | 298 | 'INNER JOIN "Accounts" ON "VideoChannels"."authorId" = "Accounts"."id" ' + |
300 | 'INNER JOIN "Users" ON "Authors"."userId" = "Users"."id" ' + | 299 | 'INNER JOIN "Users" ON "Accounts"."userId" = "Users"."id" ' + |
301 | 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t' | 300 | 'WHERE "Users"."id" = $userId GROUP BY "Videos"."id") t' |
302 | 301 | ||
303 | const options = { | 302 | const options = { |
diff --git a/server/models/index.ts b/server/models/index.ts index b392a8a77..29479e067 100644 --- a/server/models/index.ts +++ b/server/models/index.ts | |||
@@ -3,5 +3,5 @@ export * from './job' | |||
3 | export * from './oauth' | 3 | export * from './oauth' |
4 | export * from './pod' | 4 | export * from './pod' |
5 | export * from './request' | 5 | export * from './request' |
6 | export * from './user' | 6 | export * from './account' |
7 | export * from './video' | 7 | export * from './video' |
diff --git a/server/models/job/job-interface.ts b/server/models/job/job-interface.ts index ba5622977..163930a4f 100644 --- a/server/models/job/job-interface.ts +++ b/server/models/job/job-interface.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { JobState } from '../../../shared/models/job.model' | 4 | import { JobCategory, JobState } from '../../../shared/models/job.model' |
5 | 5 | ||
6 | export namespace JobMethods { | 6 | export namespace JobMethods { |
7 | export type ListWithLimit = (limit: number, state: JobState) => Promise<JobInstance[]> | 7 | export type ListWithLimitByCategory = (limit: number, state: JobState, category: JobCategory) => Promise<JobInstance[]> |
8 | } | 8 | } |
9 | 9 | ||
10 | export interface JobClass { | 10 | export interface JobClass { |
11 | listWithLimit: JobMethods.ListWithLimit | 11 | listWithLimitByCategory: JobMethods.ListWithLimitByCategory |
12 | } | 12 | } |
13 | 13 | ||
14 | export interface JobAttributes { | 14 | export interface JobAttributes { |
diff --git a/server/models/job/job.ts b/server/models/job/job.ts index 968f9d71d..ce1203e5a 100644 --- a/server/models/job/job.ts +++ b/server/models/job/job.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | 3 | ||
4 | import { JOB_STATES } from '../../initializers' | 4 | import { JOB_STATES, JOB_CATEGORIES } from '../../initializers' |
5 | 5 | ||
6 | import { addMethodsToModel } from '../utils' | 6 | import { addMethodsToModel } from '../utils' |
7 | import { | 7 | import { |
@@ -13,7 +13,7 @@ import { | |||
13 | import { JobState } from '../../../shared/models/job.model' | 13 | import { JobState } from '../../../shared/models/job.model' |
14 | 14 | ||
15 | let Job: Sequelize.Model<JobInstance, JobAttributes> | 15 | let Job: Sequelize.Model<JobInstance, JobAttributes> |
16 | let listWithLimit: JobMethods.ListWithLimit | 16 | let listWithLimitByCategory: JobMethods.ListWithLimitByCategory |
17 | 17 | ||
18 | export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 18 | export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
19 | Job = sequelize.define<JobInstance, JobAttributes>('Job', | 19 | Job = sequelize.define<JobInstance, JobAttributes>('Job', |
@@ -22,6 +22,10 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
22 | type: DataTypes.ENUM(values(JOB_STATES)), | 22 | type: DataTypes.ENUM(values(JOB_STATES)), |
23 | allowNull: false | 23 | allowNull: false |
24 | }, | 24 | }, |
25 | category: { | ||
26 | type: DataTypes.ENUM(values(JOB_CATEGORIES)), | ||
27 | allowNull: false | ||
28 | }, | ||
25 | handlerName: { | 29 | handlerName: { |
26 | type: DataTypes.STRING, | 30 | type: DataTypes.STRING, |
27 | allowNull: false | 31 | allowNull: false |
@@ -40,7 +44,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
40 | } | 44 | } |
41 | ) | 45 | ) |
42 | 46 | ||
43 | const classMethods = [ listWithLimit ] | 47 | const classMethods = [ listWithLimitByCategory ] |
44 | addMethodsToModel(Job, classMethods) | 48 | addMethodsToModel(Job, classMethods) |
45 | 49 | ||
46 | return Job | 50 | return Job |
@@ -48,7 +52,7 @@ export default function defineJob (sequelize: Sequelize.Sequelize, DataTypes: Se | |||
48 | 52 | ||
49 | // --------------------------------------------------------------------------- | 53 | // --------------------------------------------------------------------------- |
50 | 54 | ||
51 | listWithLimit = function (limit: number, state: JobState) { | 55 | listWithLimitByCategory = function (limit: number, state: JobState) { |
52 | const query = { | 56 | const query = { |
53 | order: [ | 57 | order: [ |
54 | [ 'id', 'ASC' ] | 58 | [ 'id', 'ASC' ] |
diff --git a/server/models/oauth/oauth-token-interface.ts b/server/models/oauth/oauth-token-interface.ts index 0c947bde8..ef97893c4 100644 --- a/server/models/oauth/oauth-token-interface.ts +++ b/server/models/oauth/oauth-token-interface.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { UserModel } from '../user/user-interface' | 4 | import { UserModel } from '../account/user-interface' |
5 | 5 | ||
6 | export type OAuthTokenInfo = { | 6 | export type OAuthTokenInfo = { |
7 | refreshToken: string | 7 | refreshToken: string |
diff --git a/server/models/pod/pod-interface.ts b/server/models/pod/pod-interface.ts index 7e095d424..6c5aab3fa 100644 --- a/server/models/pod/pod-interface.ts +++ b/server/models/pod/pod-interface.ts | |||
@@ -48,9 +48,7 @@ export interface PodClass { | |||
48 | export interface PodAttributes { | 48 | export interface PodAttributes { |
49 | id?: number | 49 | id?: number |
50 | host?: string | 50 | host?: string |
51 | publicKey?: string | ||
52 | score?: number | Sequelize.literal // Sequelize literal for 'score +' + value | 51 | score?: number | Sequelize.literal // Sequelize literal for 'score +' + value |
53 | email?: string | ||
54 | } | 52 | } |
55 | 53 | ||
56 | export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance<PodAttributes> { | 54 | export interface PodInstance extends PodClass, PodAttributes, Sequelize.Instance<PodAttributes> { |
diff --git a/server/models/pod/pod.ts b/server/models/pod/pod.ts index 6b33336b8..7c8b49bf8 100644 --- a/server/models/pod/pod.ts +++ b/server/models/pod/pod.ts | |||
@@ -39,10 +39,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
39 | } | 39 | } |
40 | } | 40 | } |
41 | }, | 41 | }, |
42 | publicKey: { | ||
43 | type: DataTypes.STRING(5000), | ||
44 | allowNull: false | ||
45 | }, | ||
46 | score: { | 42 | score: { |
47 | type: DataTypes.INTEGER, | 43 | type: DataTypes.INTEGER, |
48 | defaultValue: FRIEND_SCORE.BASE, | 44 | defaultValue: FRIEND_SCORE.BASE, |
@@ -51,13 +47,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
51 | isInt: true, | 47 | isInt: true, |
52 | max: FRIEND_SCORE.MAX | 48 | max: FRIEND_SCORE.MAX |
53 | } | 49 | } |
54 | }, | ||
55 | email: { | ||
56 | type: DataTypes.STRING(400), | ||
57 | allowNull: false, | ||
58 | validate: { | ||
59 | isEmail: true | ||
60 | } | ||
61 | } | 50 | } |
62 | }, | 51 | }, |
63 | { | 52 | { |
@@ -100,7 +89,6 @@ toFormattedJSON = function (this: PodInstance) { | |||
100 | const json = { | 89 | const json = { |
101 | id: this.id, | 90 | id: this.id, |
102 | host: this.host, | 91 | host: this.host, |
103 | email: this.email, | ||
104 | score: this.score as number, | 92 | score: this.score as number, |
105 | createdAt: this.createdAt | 93 | createdAt: this.createdAt |
106 | } | 94 | } |
diff --git a/server/models/user/index.ts b/server/models/user/index.ts deleted file mode 100644 index ed3689518..000000000 --- a/server/models/user/index.ts +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | export * from './user-video-rate-interface' | ||
2 | export * from './user-interface' | ||
diff --git a/server/models/user/user-video-rate-interface.ts b/server/models/user/user-video-rate-interface.ts deleted file mode 100644 index ea0fdc4d9..000000000 --- a/server/models/user/user-video-rate-interface.ts +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { VideoRateType } from '../../../shared/models/videos/video-rate.type' | ||
5 | |||
6 | export namespace UserVideoRateMethods { | ||
7 | export type Load = (userId: number, videoId: number, transaction: Sequelize.Transaction) => Promise<UserVideoRateInstance> | ||
8 | } | ||
9 | |||
10 | export interface UserVideoRateClass { | ||
11 | load: UserVideoRateMethods.Load | ||
12 | } | ||
13 | |||
14 | export interface UserVideoRateAttributes { | ||
15 | type: VideoRateType | ||
16 | userId: number | ||
17 | videoId: number | ||
18 | } | ||
19 | |||
20 | export interface UserVideoRateInstance extends UserVideoRateClass, UserVideoRateAttributes, Sequelize.Instance<UserVideoRateAttributes> { | ||
21 | id: number | ||
22 | createdAt: Date | ||
23 | updatedAt: Date | ||
24 | } | ||
25 | |||
26 | export interface UserVideoRateModel extends UserVideoRateClass, Sequelize.Model<UserVideoRateInstance, UserVideoRateAttributes> {} | ||
diff --git a/server/models/video/author-interface.ts b/server/models/video/author-interface.ts deleted file mode 100644 index fc69ff3c2..000000000 --- a/server/models/video/author-interface.ts +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | import { PodInstance } from '../pod/pod-interface' | ||
5 | import { RemoteVideoAuthorCreateData } from '../../../shared/models/pods/remote-video/remote-video-author-create-request.model' | ||
6 | import { VideoChannelInstance } from './video-channel-interface' | ||
7 | |||
8 | export namespace AuthorMethods { | ||
9 | export type Load = (id: number) => Promise<AuthorInstance> | ||
10 | export type LoadByUUID = (uuid: string) => Promise<AuthorInstance> | ||
11 | export type LoadAuthorByPodAndUUID = (uuid: string, podId: number, transaction: Sequelize.Transaction) => Promise<AuthorInstance> | ||
12 | export type ListOwned = () => Promise<AuthorInstance[]> | ||
13 | |||
14 | export type ToAddRemoteJSON = (this: AuthorInstance) => RemoteVideoAuthorCreateData | ||
15 | export type IsOwned = (this: AuthorInstance) => boolean | ||
16 | } | ||
17 | |||
18 | export interface AuthorClass { | ||
19 | loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID | ||
20 | load: AuthorMethods.Load | ||
21 | loadByUUID: AuthorMethods.LoadByUUID | ||
22 | listOwned: AuthorMethods.ListOwned | ||
23 | } | ||
24 | |||
25 | export interface AuthorAttributes { | ||
26 | name: string | ||
27 | uuid?: string | ||
28 | |||
29 | podId?: number | ||
30 | userId?: number | ||
31 | } | ||
32 | |||
33 | export interface AuthorInstance extends AuthorClass, AuthorAttributes, Sequelize.Instance<AuthorAttributes> { | ||
34 | isOwned: AuthorMethods.IsOwned | ||
35 | toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON | ||
36 | |||
37 | id: number | ||
38 | createdAt: Date | ||
39 | updatedAt: Date | ||
40 | |||
41 | Pod: PodInstance | ||
42 | VideoChannels: VideoChannelInstance[] | ||
43 | } | ||
44 | |||
45 | export interface AuthorModel extends AuthorClass, Sequelize.Model<AuthorInstance, AuthorAttributes> {} | ||
diff --git a/server/models/video/author.ts b/server/models/video/author.ts deleted file mode 100644 index 43f84c3ea..000000000 --- a/server/models/video/author.ts +++ /dev/null | |||
@@ -1,171 +0,0 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | import { isUserUsernameValid } from '../../helpers' | ||
4 | import { removeVideoAuthorToFriends } from '../../lib' | ||
5 | |||
6 | import { addMethodsToModel } from '../utils' | ||
7 | import { | ||
8 | AuthorInstance, | ||
9 | AuthorAttributes, | ||
10 | |||
11 | AuthorMethods | ||
12 | } from './author-interface' | ||
13 | |||
14 | let Author: Sequelize.Model<AuthorInstance, AuthorAttributes> | ||
15 | let loadAuthorByPodAndUUID: AuthorMethods.LoadAuthorByPodAndUUID | ||
16 | let load: AuthorMethods.Load | ||
17 | let loadByUUID: AuthorMethods.LoadByUUID | ||
18 | let listOwned: AuthorMethods.ListOwned | ||
19 | let isOwned: AuthorMethods.IsOwned | ||
20 | let toAddRemoteJSON: AuthorMethods.ToAddRemoteJSON | ||
21 | |||
22 | export default function defineAuthor (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | ||
23 | Author = sequelize.define<AuthorInstance, AuthorAttributes>('Author', | ||
24 | { | ||
25 | uuid: { | ||
26 | type: DataTypes.UUID, | ||
27 | defaultValue: DataTypes.UUIDV4, | ||
28 | allowNull: false, | ||
29 | validate: { | ||
30 | isUUID: 4 | ||
31 | } | ||
32 | }, | ||
33 | name: { | ||
34 | type: DataTypes.STRING, | ||
35 | allowNull: false, | ||
36 | validate: { | ||
37 | usernameValid: value => { | ||
38 | const res = isUserUsernameValid(value) | ||
39 | if (res === false) throw new Error('Username is not valid.') | ||
40 | } | ||
41 | } | ||
42 | } | ||
43 | }, | ||
44 | { | ||
45 | indexes: [ | ||
46 | { | ||
47 | fields: [ 'name' ] | ||
48 | }, | ||
49 | { | ||
50 | fields: [ 'podId' ] | ||
51 | }, | ||
52 | { | ||
53 | fields: [ 'userId' ], | ||
54 | unique: true | ||
55 | }, | ||
56 | { | ||
57 | fields: [ 'name', 'podId' ], | ||
58 | unique: true | ||
59 | } | ||
60 | ], | ||
61 | hooks: { afterDestroy } | ||
62 | } | ||
63 | ) | ||
64 | |||
65 | const classMethods = [ | ||
66 | associate, | ||
67 | loadAuthorByPodAndUUID, | ||
68 | load, | ||
69 | loadByUUID, | ||
70 | listOwned | ||
71 | ] | ||
72 | const instanceMethods = [ | ||
73 | isOwned, | ||
74 | toAddRemoteJSON | ||
75 | ] | ||
76 | addMethodsToModel(Author, classMethods, instanceMethods) | ||
77 | |||
78 | return Author | ||
79 | } | ||
80 | |||
81 | // --------------------------------------------------------------------------- | ||
82 | |||
83 | function associate (models) { | ||
84 | Author.belongsTo(models.Pod, { | ||
85 | foreignKey: { | ||
86 | name: 'podId', | ||
87 | allowNull: true | ||
88 | }, | ||
89 | onDelete: 'cascade' | ||
90 | }) | ||
91 | |||
92 | Author.belongsTo(models.User, { | ||
93 | foreignKey: { | ||
94 | name: 'userId', | ||
95 | allowNull: true | ||
96 | }, | ||
97 | onDelete: 'cascade' | ||
98 | }) | ||
99 | |||
100 | Author.hasMany(models.VideoChannel, { | ||
101 | foreignKey: { | ||
102 | name: 'authorId', | ||
103 | allowNull: false | ||
104 | }, | ||
105 | onDelete: 'cascade', | ||
106 | hooks: true | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | function afterDestroy (author: AuthorInstance) { | ||
111 | if (author.isOwned()) { | ||
112 | const removeVideoAuthorToFriendsParams = { | ||
113 | uuid: author.uuid | ||
114 | } | ||
115 | |||
116 | return removeVideoAuthorToFriends(removeVideoAuthorToFriendsParams) | ||
117 | } | ||
118 | |||
119 | return undefined | ||
120 | } | ||
121 | |||
122 | toAddRemoteJSON = function (this: AuthorInstance) { | ||
123 | const json = { | ||
124 | uuid: this.uuid, | ||
125 | name: this.name | ||
126 | } | ||
127 | |||
128 | return json | ||
129 | } | ||
130 | |||
131 | isOwned = function (this: AuthorInstance) { | ||
132 | return this.podId === null | ||
133 | } | ||
134 | |||
135 | // ------------------------------ STATICS ------------------------------ | ||
136 | |||
137 | listOwned = function () { | ||
138 | const query: Sequelize.FindOptions<AuthorAttributes> = { | ||
139 | where: { | ||
140 | podId: null | ||
141 | } | ||
142 | } | ||
143 | |||
144 | return Author.findAll(query) | ||
145 | } | ||
146 | |||
147 | load = function (id: number) { | ||
148 | return Author.findById(id) | ||
149 | } | ||
150 | |||
151 | loadByUUID = function (uuid: string) { | ||
152 | const query: Sequelize.FindOptions<AuthorAttributes> = { | ||
153 | where: { | ||
154 | uuid | ||
155 | } | ||
156 | } | ||
157 | |||
158 | return Author.findOne(query) | ||
159 | } | ||
160 | |||
161 | loadAuthorByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) { | ||
162 | const query: Sequelize.FindOptions<AuthorAttributes> = { | ||
163 | where: { | ||
164 | podId, | ||
165 | uuid | ||
166 | }, | ||
167 | transaction | ||
168 | } | ||
169 | |||
170 | return Author.find(query) | ||
171 | } | ||
diff --git a/server/models/video/video-channel-interface.ts b/server/models/video/video-channel-interface.ts index b8d3e0f42..477f97cd4 100644 --- a/server/models/video/video-channel-interface.ts +++ b/server/models/video/video-channel-interface.ts | |||
@@ -1,42 +1,42 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | import { ResultList, RemoteVideoChannelCreateData, RemoteVideoChannelUpdateData } from '../../../shared' | 4 | import { ResultList } from '../../../shared' |
5 | 5 | ||
6 | // Don't use barrel, import just what we need | 6 | // Don't use barrel, import just what we need |
7 | import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' | 7 | import { VideoChannel as FormattedVideoChannel } from '../../../shared/models/videos/video-channel.model' |
8 | import { AuthorInstance } from './author-interface' | ||
9 | import { VideoInstance } from './video-interface' | 8 | import { VideoInstance } from './video-interface' |
9 | import { AccountInstance } from '../account/account-interface' | ||
10 | import { VideoChannelObject } from '../../../shared/models/activitypub/objects/video-channel-object' | ||
10 | 11 | ||
11 | export namespace VideoChannelMethods { | 12 | export namespace VideoChannelMethods { |
12 | export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel | 13 | export type ToFormattedJSON = (this: VideoChannelInstance) => FormattedVideoChannel |
13 | export type ToAddRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelCreateData | 14 | export type ToActivityPubObject = (this: VideoChannelInstance) => VideoChannelObject |
14 | export type ToUpdateRemoteJSON = (this: VideoChannelInstance) => RemoteVideoChannelUpdateData | ||
15 | export type IsOwned = (this: VideoChannelInstance) => boolean | 15 | export type IsOwned = (this: VideoChannelInstance) => boolean |
16 | 16 | ||
17 | export type CountByAuthor = (authorId: number) => Promise<number> | 17 | export type CountByAccount = (accountId: number) => Promise<number> |
18 | export type ListOwned = () => Promise<VideoChannelInstance[]> | 18 | export type ListOwned = () => Promise<VideoChannelInstance[]> |
19 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> > | 19 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoChannelInstance> > |
20 | export type LoadByIdAndAuthor = (id: number, authorId: number) => Promise<VideoChannelInstance> | 20 | export type LoadByIdAndAccount = (id: number, accountId: number) => Promise<VideoChannelInstance> |
21 | export type ListByAuthor = (authorId: number) => Promise< ResultList<VideoChannelInstance> > | 21 | export type ListByAccount = (accountId: number) => Promise< ResultList<VideoChannelInstance> > |
22 | export type LoadAndPopulateAuthor = (id: number) => Promise<VideoChannelInstance> | 22 | export type LoadAndPopulateAccount = (id: number) => Promise<VideoChannelInstance> |
23 | export type LoadByUUIDAndPopulateAuthor = (uuid: string) => Promise<VideoChannelInstance> | 23 | export type LoadByUUIDAndPopulateAccount = (uuid: string) => Promise<VideoChannelInstance> |
24 | export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance> | 24 | export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance> |
25 | export type LoadByHostAndUUID = (uuid: string, podHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance> | 25 | export type LoadByHostAndUUID = (uuid: string, podHost: string, t?: Sequelize.Transaction) => Promise<VideoChannelInstance> |
26 | export type LoadAndPopulateAuthorAndVideos = (id: number) => Promise<VideoChannelInstance> | 26 | export type LoadAndPopulateAccountAndVideos = (id: number) => Promise<VideoChannelInstance> |
27 | } | 27 | } |
28 | 28 | ||
29 | export interface VideoChannelClass { | 29 | export interface VideoChannelClass { |
30 | countByAuthor: VideoChannelMethods.CountByAuthor | 30 | countByAccount: VideoChannelMethods.CountByAccount |
31 | listForApi: VideoChannelMethods.ListForApi | 31 | listForApi: VideoChannelMethods.ListForApi |
32 | listByAuthor: VideoChannelMethods.ListByAuthor | 32 | listByAccount: VideoChannelMethods.ListByAccount |
33 | listOwned: VideoChannelMethods.ListOwned | 33 | listOwned: VideoChannelMethods.ListOwned |
34 | loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor | 34 | loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount |
35 | loadByUUID: VideoChannelMethods.LoadByUUID | 35 | loadByUUID: VideoChannelMethods.LoadByUUID |
36 | loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID | 36 | loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID |
37 | loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor | 37 | loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount |
38 | loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor | 38 | loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount |
39 | loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos | 39 | loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos |
40 | } | 40 | } |
41 | 41 | ||
42 | export interface VideoChannelAttributes { | 42 | export interface VideoChannelAttributes { |
@@ -45,8 +45,9 @@ export interface VideoChannelAttributes { | |||
45 | name: string | 45 | name: string |
46 | description: string | 46 | description: string |
47 | remote: boolean | 47 | remote: boolean |
48 | url: string | ||
48 | 49 | ||
49 | Author?: AuthorInstance | 50 | Account?: AccountInstance |
50 | Videos?: VideoInstance[] | 51 | Videos?: VideoInstance[] |
51 | } | 52 | } |
52 | 53 | ||
@@ -57,8 +58,7 @@ export interface VideoChannelInstance extends VideoChannelClass, VideoChannelAtt | |||
57 | 58 | ||
58 | isOwned: VideoChannelMethods.IsOwned | 59 | isOwned: VideoChannelMethods.IsOwned |
59 | toFormattedJSON: VideoChannelMethods.ToFormattedJSON | 60 | toFormattedJSON: VideoChannelMethods.ToFormattedJSON |
60 | toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON | 61 | toActivityPubObject: VideoChannelMethods.ToActivityPubObject |
61 | toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON | ||
62 | } | 62 | } |
63 | 63 | ||
64 | export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {} | 64 | export interface VideoChannelModel extends VideoChannelClass, Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> {} |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 46c2db63f..c17828f3e 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -13,19 +13,18 @@ import { | |||
13 | 13 | ||
14 | let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> | 14 | let VideoChannel: Sequelize.Model<VideoChannelInstance, VideoChannelAttributes> |
15 | let toFormattedJSON: VideoChannelMethods.ToFormattedJSON | 15 | let toFormattedJSON: VideoChannelMethods.ToFormattedJSON |
16 | let toAddRemoteJSON: VideoChannelMethods.ToAddRemoteJSON | 16 | let toActivityPubObject: VideoChannelMethods.ToActivityPubObject |
17 | let toUpdateRemoteJSON: VideoChannelMethods.ToUpdateRemoteJSON | ||
18 | let isOwned: VideoChannelMethods.IsOwned | 17 | let isOwned: VideoChannelMethods.IsOwned |
19 | let countByAuthor: VideoChannelMethods.CountByAuthor | 18 | let countByAccount: VideoChannelMethods.CountByAccount |
20 | let listOwned: VideoChannelMethods.ListOwned | 19 | let listOwned: VideoChannelMethods.ListOwned |
21 | let listForApi: VideoChannelMethods.ListForApi | 20 | let listForApi: VideoChannelMethods.ListForApi |
22 | let listByAuthor: VideoChannelMethods.ListByAuthor | 21 | let listByAccount: VideoChannelMethods.ListByAccount |
23 | let loadByIdAndAuthor: VideoChannelMethods.LoadByIdAndAuthor | 22 | let loadByIdAndAccount: VideoChannelMethods.LoadByIdAndAccount |
24 | let loadByUUID: VideoChannelMethods.LoadByUUID | 23 | let loadByUUID: VideoChannelMethods.LoadByUUID |
25 | let loadAndPopulateAuthor: VideoChannelMethods.LoadAndPopulateAuthor | 24 | let loadAndPopulateAccount: VideoChannelMethods.LoadAndPopulateAccount |
26 | let loadByUUIDAndPopulateAuthor: VideoChannelMethods.LoadByUUIDAndPopulateAuthor | 25 | let loadByUUIDAndPopulateAccount: VideoChannelMethods.LoadByUUIDAndPopulateAccount |
27 | let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID | 26 | let loadByHostAndUUID: VideoChannelMethods.LoadByHostAndUUID |
28 | let loadAndPopulateAuthorAndVideos: VideoChannelMethods.LoadAndPopulateAuthorAndVideos | 27 | let loadAndPopulateAccountAndVideos: VideoChannelMethods.LoadAndPopulateAccountAndVideos |
29 | 28 | ||
30 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 29 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
31 | VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel', | 30 | VideoChannel = sequelize.define<VideoChannelInstance, VideoChannelAttributes>('VideoChannel', |
@@ -62,12 +61,19 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
62 | type: DataTypes.BOOLEAN, | 61 | type: DataTypes.BOOLEAN, |
63 | allowNull: false, | 62 | allowNull: false, |
64 | defaultValue: false | 63 | defaultValue: false |
64 | }, | ||
65 | url: { | ||
66 | type: DataTypes.STRING, | ||
67 | allowNull: false, | ||
68 | validate: { | ||
69 | isUrl: true | ||
70 | } | ||
65 | } | 71 | } |
66 | }, | 72 | }, |
67 | { | 73 | { |
68 | indexes: [ | 74 | indexes: [ |
69 | { | 75 | { |
70 | fields: [ 'authorId' ] | 76 | fields: [ 'accountId' ] |
71 | } | 77 | } |
72 | ], | 78 | ], |
73 | hooks: { | 79 | hooks: { |
@@ -80,21 +86,20 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
80 | associate, | 86 | associate, |
81 | 87 | ||
82 | listForApi, | 88 | listForApi, |
83 | listByAuthor, | 89 | listByAccount, |
84 | listOwned, | 90 | listOwned, |
85 | loadByIdAndAuthor, | 91 | loadByIdAndAccount, |
86 | loadAndPopulateAuthor, | 92 | loadAndPopulateAccount, |
87 | loadByUUIDAndPopulateAuthor, | 93 | loadByUUIDAndPopulateAccount, |
88 | loadByUUID, | 94 | loadByUUID, |
89 | loadByHostAndUUID, | 95 | loadByHostAndUUID, |
90 | loadAndPopulateAuthorAndVideos, | 96 | loadAndPopulateAccountAndVideos, |
91 | countByAuthor | 97 | countByAccount |
92 | ] | 98 | ] |
93 | const instanceMethods = [ | 99 | const instanceMethods = [ |
94 | isOwned, | 100 | isOwned, |
95 | toFormattedJSON, | 101 | toFormattedJSON, |
96 | toAddRemoteJSON, | 102 | toActivityPubObject, |
97 | toUpdateRemoteJSON | ||
98 | ] | 103 | ] |
99 | addMethodsToModel(VideoChannel, classMethods, instanceMethods) | 104 | addMethodsToModel(VideoChannel, classMethods, instanceMethods) |
100 | 105 | ||
@@ -118,10 +123,10 @@ toFormattedJSON = function (this: VideoChannelInstance) { | |||
118 | updatedAt: this.updatedAt | 123 | updatedAt: this.updatedAt |
119 | } | 124 | } |
120 | 125 | ||
121 | if (this.Author !== undefined) { | 126 | if (this.Account !== undefined) { |
122 | json['owner'] = { | 127 | json['owner'] = { |
123 | name: this.Author.name, | 128 | name: this.Account.name, |
124 | uuid: this.Author.uuid | 129 | uuid: this.Account.uuid |
125 | } | 130 | } |
126 | } | 131 | } |
127 | 132 | ||
@@ -132,27 +137,14 @@ toFormattedJSON = function (this: VideoChannelInstance) { | |||
132 | return json | 137 | return json |
133 | } | 138 | } |
134 | 139 | ||
135 | toAddRemoteJSON = function (this: VideoChannelInstance) { | 140 | toActivityPubObject = function (this: VideoChannelInstance) { |
136 | const json = { | ||
137 | uuid: this.uuid, | ||
138 | name: this.name, | ||
139 | description: this.description, | ||
140 | createdAt: this.createdAt, | ||
141 | updatedAt: this.updatedAt, | ||
142 | ownerUUID: this.Author.uuid | ||
143 | } | ||
144 | |||
145 | return json | ||
146 | } | ||
147 | |||
148 | toUpdateRemoteJSON = function (this: VideoChannelInstance) { | ||
149 | const json = { | 141 | const json = { |
150 | uuid: this.uuid, | 142 | uuid: this.uuid, |
151 | name: this.name, | 143 | name: this.name, |
152 | description: this.description, | 144 | description: this.description, |
153 | createdAt: this.createdAt, | 145 | createdAt: this.createdAt, |
154 | updatedAt: this.updatedAt, | 146 | updatedAt: this.updatedAt, |
155 | ownerUUID: this.Author.uuid | 147 | ownerUUID: this.Account.uuid |
156 | } | 148 | } |
157 | 149 | ||
158 | return json | 150 | return json |
@@ -161,9 +153,9 @@ toUpdateRemoteJSON = function (this: VideoChannelInstance) { | |||
161 | // ------------------------------ STATICS ------------------------------ | 153 | // ------------------------------ STATICS ------------------------------ |
162 | 154 | ||
163 | function associate (models) { | 155 | function associate (models) { |
164 | VideoChannel.belongsTo(models.Author, { | 156 | VideoChannel.belongsTo(models.Account, { |
165 | foreignKey: { | 157 | foreignKey: { |
166 | name: 'authorId', | 158 | name: 'accountId', |
167 | allowNull: false | 159 | allowNull: false |
168 | }, | 160 | }, |
169 | onDelete: 'CASCADE' | 161 | onDelete: 'CASCADE' |
@@ -190,10 +182,10 @@ function afterDestroy (videoChannel: VideoChannelInstance) { | |||
190 | return undefined | 182 | return undefined |
191 | } | 183 | } |
192 | 184 | ||
193 | countByAuthor = function (authorId: number) { | 185 | countByAccount = function (accountId: number) { |
194 | const query = { | 186 | const query = { |
195 | where: { | 187 | where: { |
196 | authorId | 188 | accountId |
197 | } | 189 | } |
198 | } | 190 | } |
199 | 191 | ||
@@ -205,7 +197,7 @@ listOwned = function () { | |||
205 | where: { | 197 | where: { |
206 | remote: false | 198 | remote: false |
207 | }, | 199 | }, |
208 | include: [ VideoChannel['sequelize'].models.Author ] | 200 | include: [ VideoChannel['sequelize'].models.Account ] |
209 | } | 201 | } |
210 | 202 | ||
211 | return VideoChannel.findAll(query) | 203 | return VideoChannel.findAll(query) |
@@ -218,7 +210,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
218 | order: [ getSort(sort) ], | 210 | order: [ getSort(sort) ], |
219 | include: [ | 211 | include: [ |
220 | { | 212 | { |
221 | model: VideoChannel['sequelize'].models.Author, | 213 | model: VideoChannel['sequelize'].models.Account, |
222 | required: true, | 214 | required: true, |
223 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 215 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
224 | } | 216 | } |
@@ -230,14 +222,14 @@ listForApi = function (start: number, count: number, sort: string) { | |||
230 | }) | 222 | }) |
231 | } | 223 | } |
232 | 224 | ||
233 | listByAuthor = function (authorId: number) { | 225 | listByAccount = function (accountId: number) { |
234 | const query = { | 226 | const query = { |
235 | order: [ getSort('createdAt') ], | 227 | order: [ getSort('createdAt') ], |
236 | include: [ | 228 | include: [ |
237 | { | 229 | { |
238 | model: VideoChannel['sequelize'].models.Author, | 230 | model: VideoChannel['sequelize'].models.Account, |
239 | where: { | 231 | where: { |
240 | id: authorId | 232 | id: accountId |
241 | }, | 233 | }, |
242 | required: true, | 234 | required: true, |
243 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 235 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
@@ -269,7 +261,7 @@ loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Tran | |||
269 | }, | 261 | }, |
270 | include: [ | 262 | include: [ |
271 | { | 263 | { |
272 | model: VideoChannel['sequelize'].models.Author, | 264 | model: VideoChannel['sequelize'].models.Account, |
273 | include: [ | 265 | include: [ |
274 | { | 266 | { |
275 | model: VideoChannel['sequelize'].models.Pod, | 267 | model: VideoChannel['sequelize'].models.Pod, |
@@ -288,15 +280,15 @@ loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Tran | |||
288 | return VideoChannel.findOne(query) | 280 | return VideoChannel.findOne(query) |
289 | } | 281 | } |
290 | 282 | ||
291 | loadByIdAndAuthor = function (id: number, authorId: number) { | 283 | loadByIdAndAccount = function (id: number, accountId: number) { |
292 | const options = { | 284 | const options = { |
293 | where: { | 285 | where: { |
294 | id, | 286 | id, |
295 | authorId | 287 | accountId |
296 | }, | 288 | }, |
297 | include: [ | 289 | include: [ |
298 | { | 290 | { |
299 | model: VideoChannel['sequelize'].models.Author, | 291 | model: VideoChannel['sequelize'].models.Account, |
300 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 292 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
301 | } | 293 | } |
302 | ] | 294 | ] |
@@ -305,11 +297,11 @@ loadByIdAndAuthor = function (id: number, authorId: number) { | |||
305 | return VideoChannel.findOne(options) | 297 | return VideoChannel.findOne(options) |
306 | } | 298 | } |
307 | 299 | ||
308 | loadAndPopulateAuthor = function (id: number) { | 300 | loadAndPopulateAccount = function (id: number) { |
309 | const options = { | 301 | const options = { |
310 | include: [ | 302 | include: [ |
311 | { | 303 | { |
312 | model: VideoChannel['sequelize'].models.Author, | 304 | model: VideoChannel['sequelize'].models.Account, |
313 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 305 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
314 | } | 306 | } |
315 | ] | 307 | ] |
@@ -318,14 +310,14 @@ loadAndPopulateAuthor = function (id: number) { | |||
318 | return VideoChannel.findById(id, options) | 310 | return VideoChannel.findById(id, options) |
319 | } | 311 | } |
320 | 312 | ||
321 | loadByUUIDAndPopulateAuthor = function (uuid: string) { | 313 | loadByUUIDAndPopulateAccount = function (uuid: string) { |
322 | const options = { | 314 | const options = { |
323 | where: { | 315 | where: { |
324 | uuid | 316 | uuid |
325 | }, | 317 | }, |
326 | include: [ | 318 | include: [ |
327 | { | 319 | { |
328 | model: VideoChannel['sequelize'].models.Author, | 320 | model: VideoChannel['sequelize'].models.Account, |
329 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 321 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
330 | } | 322 | } |
331 | ] | 323 | ] |
@@ -334,11 +326,11 @@ loadByUUIDAndPopulateAuthor = function (uuid: string) { | |||
334 | return VideoChannel.findOne(options) | 326 | return VideoChannel.findOne(options) |
335 | } | 327 | } |
336 | 328 | ||
337 | loadAndPopulateAuthorAndVideos = function (id: number) { | 329 | loadAndPopulateAccountAndVideos = function (id: number) { |
338 | const options = { | 330 | const options = { |
339 | include: [ | 331 | include: [ |
340 | { | 332 | { |
341 | model: VideoChannel['sequelize'].models.Author, | 333 | model: VideoChannel['sequelize'].models.Account, |
342 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] | 334 | include: [ { model: VideoChannel['sequelize'].models.Pod, required: false } ] |
343 | }, | 335 | }, |
344 | VideoChannel['sequelize'].models.Video | 336 | VideoChannel['sequelize'].models.Video |
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index cfe65f9aa..e62e25a82 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as Promise from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | 3 | ||
4 | import { TagAttributes, TagInstance } from './tag-interface' | 4 | import { TagAttributes, TagInstance } from './tag-interface' |
5 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' | 5 | import { VideoFileAttributes, VideoFileInstance } from './video-file-interface' |
@@ -13,6 +13,7 @@ import { RemoteVideoUpdateData } from '../../../shared/models/pods/remote-video/ | |||
13 | import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' | 13 | import { RemoteVideoCreateData } from '../../../shared/models/pods/remote-video/remote-video-create-request.model' |
14 | import { ResultList } from '../../../shared/models/result-list.model' | 14 | import { ResultList } from '../../../shared/models/result-list.model' |
15 | import { VideoChannelInstance } from './video-channel-interface' | 15 | import { VideoChannelInstance } from './video-channel-interface' |
16 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' | ||
16 | 17 | ||
17 | export namespace VideoMethods { | 18 | export namespace VideoMethods { |
18 | export type GetThumbnailName = (this: VideoInstance) => string | 19 | export type GetThumbnailName = (this: VideoInstance) => string |
@@ -29,8 +30,7 @@ export namespace VideoMethods { | |||
29 | export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string | 30 | export type GetVideoFilePath = (this: VideoInstance, videoFile: VideoFileInstance) => string |
30 | export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> | 31 | export type CreateTorrentAndSetInfoHash = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<void> |
31 | 32 | ||
32 | export type ToAddRemoteJSON = (this: VideoInstance) => Promise<RemoteVideoCreateData> | 33 | export type ToActivityPubObject = (this: VideoInstance) => VideoTorrentObject |
33 | export type ToUpdateRemoteJSON = (this: VideoInstance) => RemoteVideoUpdateData | ||
34 | 34 | ||
35 | export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void> | 35 | export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void> |
36 | export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void> | 36 | export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void> |
@@ -40,31 +40,35 @@ export namespace VideoMethods { | |||
40 | export type GetPreviewPath = (this: VideoInstance) => string | 40 | export type GetPreviewPath = (this: VideoInstance) => string |
41 | export type GetDescriptionPath = (this: VideoInstance) => string | 41 | export type GetDescriptionPath = (this: VideoInstance) => string |
42 | export type GetTruncatedDescription = (this: VideoInstance) => string | 42 | export type GetTruncatedDescription = (this: VideoInstance) => string |
43 | export type GetCategoryLabel = (this: VideoInstance) => string | ||
44 | export type GetLicenceLabel = (this: VideoInstance) => string | ||
45 | export type GetLanguageLabel = (this: VideoInstance) => string | ||
43 | 46 | ||
44 | // Return thumbnail name | 47 | // Return thumbnail name |
45 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> | 48 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> |
46 | 49 | ||
47 | export type List = () => Promise<VideoInstance[]> | 50 | export type List = () => Bluebird<VideoInstance[]> |
48 | export type ListOwnedAndPopulateAuthorAndTags = () => Promise<VideoInstance[]> | 51 | export type ListOwnedAndPopulateAccountAndTags = () => Bluebird<VideoInstance[]> |
49 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> | 52 | export type ListOwnedByAccount = (account: string) => Bluebird<VideoInstance[]> |
50 | 53 | ||
51 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | 54 | export type ListForApi = (start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > |
52 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | 55 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > |
53 | export type SearchAndPopulateAuthorAndPodAndTags = ( | 56 | export type SearchAndPopulateAccountAndPodAndTags = ( |
54 | value: string, | 57 | value: string, |
55 | field: string, | 58 | field: string, |
56 | start: number, | 59 | start: number, |
57 | count: number, | 60 | count: number, |
58 | sort: string | 61 | sort: string |
59 | ) => Promise< ResultList<VideoInstance> > | 62 | ) => Bluebird< ResultList<VideoInstance> > |
60 | 63 | ||
61 | export type Load = (id: number) => Promise<VideoInstance> | 64 | export type Load = (id: number) => Bluebird<VideoInstance> |
62 | export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance> | 65 | export type LoadByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance> |
63 | export type LoadLocalVideoByUUID = (uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance> | 66 | export type LoadByUrl = (url: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance> |
64 | export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Promise<VideoInstance> | 67 | export type LoadLocalVideoByUUID = (uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance> |
65 | export type LoadAndPopulateAuthor = (id: number) => Promise<VideoInstance> | 68 | export type LoadByHostAndUUID = (fromHost: string, uuid: string, t?: Sequelize.Transaction) => Bluebird<VideoInstance> |
66 | export type LoadAndPopulateAuthorAndPodAndTags = (id: number) => Promise<VideoInstance> | 69 | export type LoadAndPopulateAccount = (id: number) => Bluebird<VideoInstance> |
67 | export type LoadByUUIDAndPopulateAuthorAndPodAndTags = (uuid: string) => Promise<VideoInstance> | 70 | export type LoadAndPopulateAccountAndPodAndTags = (id: number) => Bluebird<VideoInstance> |
71 | export type LoadByUUIDAndPopulateAccountAndPodAndTags = (uuid: string) => Bluebird<VideoInstance> | ||
68 | 72 | ||
69 | export type RemoveThumbnail = (this: VideoInstance) => Promise<void> | 73 | export type RemoveThumbnail = (this: VideoInstance) => Promise<void> |
70 | export type RemovePreview = (this: VideoInstance) => Promise<void> | 74 | export type RemovePreview = (this: VideoInstance) => Promise<void> |
@@ -77,16 +81,17 @@ export interface VideoClass { | |||
77 | list: VideoMethods.List | 81 | list: VideoMethods.List |
78 | listForApi: VideoMethods.ListForApi | 82 | listForApi: VideoMethods.ListForApi |
79 | listUserVideosForApi: VideoMethods.ListUserVideosForApi | 83 | listUserVideosForApi: VideoMethods.ListUserVideosForApi |
80 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 84 | listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags |
81 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 85 | listOwnedByAccount: VideoMethods.ListOwnedByAccount |
82 | load: VideoMethods.Load | 86 | load: VideoMethods.Load |
83 | loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor | 87 | loadAndPopulateAccount: VideoMethods.LoadAndPopulateAccount |
84 | loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags | 88 | loadAndPopulateAccountAndPodAndTags: VideoMethods.LoadAndPopulateAccountAndPodAndTags |
85 | loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | 89 | loadByHostAndUUID: VideoMethods.LoadByHostAndUUID |
86 | loadByUUID: VideoMethods.LoadByUUID | 90 | loadByUUID: VideoMethods.LoadByUUID |
91 | loadByUrl: VideoMethods.LoadByUrl | ||
87 | loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID | 92 | loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID |
88 | loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags | 93 | loadByUUIDAndPopulateAccountAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndPodAndTags |
89 | searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags | 94 | searchAndPopulateAccountAndPodAndTags: VideoMethods.SearchAndPopulateAccountAndPodAndTags |
90 | } | 95 | } |
91 | 96 | ||
92 | export interface VideoAttributes { | 97 | export interface VideoAttributes { |
@@ -104,7 +109,9 @@ export interface VideoAttributes { | |||
104 | likes?: number | 109 | likes?: number |
105 | dislikes?: number | 110 | dislikes?: number |
106 | remote: boolean | 111 | remote: boolean |
112 | url: string | ||
107 | 113 | ||
114 | parentId?: number | ||
108 | channelId?: number | 115 | channelId?: number |
109 | 116 | ||
110 | VideoChannel?: VideoChannelInstance | 117 | VideoChannel?: VideoChannelInstance |
@@ -132,16 +139,18 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
132 | removePreview: VideoMethods.RemovePreview | 139 | removePreview: VideoMethods.RemovePreview |
133 | removeThumbnail: VideoMethods.RemoveThumbnail | 140 | removeThumbnail: VideoMethods.RemoveThumbnail |
134 | removeTorrent: VideoMethods.RemoveTorrent | 141 | removeTorrent: VideoMethods.RemoveTorrent |
135 | toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 142 | toActivityPubObject: VideoMethods.ToActivityPubObject |
136 | toFormattedJSON: VideoMethods.ToFormattedJSON | 143 | toFormattedJSON: VideoMethods.ToFormattedJSON |
137 | toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON | 144 | toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON |
138 | toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | ||
139 | optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile | 145 | optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile |
140 | transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | 146 | transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile |
141 | getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | 147 | getOriginalFileHeight: VideoMethods.GetOriginalFileHeight |
142 | getEmbedPath: VideoMethods.GetEmbedPath | 148 | getEmbedPath: VideoMethods.GetEmbedPath |
143 | getDescriptionPath: VideoMethods.GetDescriptionPath | 149 | getDescriptionPath: VideoMethods.GetDescriptionPath |
144 | getTruncatedDescription: VideoMethods.GetTruncatedDescription | 150 | getTruncatedDescription: VideoMethods.GetTruncatedDescription |
151 | getCategoryLabel: VideoMethods.GetCategoryLabel | ||
152 | getLicenceLabel: VideoMethods.GetLicenceLabel | ||
153 | getLanguageLabel: VideoMethods.GetLanguageLabel | ||
145 | 154 | ||
146 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> | 155 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> |
147 | addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> | 156 | addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> |
@@ -149,3 +158,4 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
149 | } | 158 | } |
150 | 159 | ||
151 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} | 160 | export interface VideoModel extends VideoClass, Sequelize.Model<VideoInstance, VideoAttributes> {} |
161 | |||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 02dde1726..94af1ece5 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -5,7 +5,6 @@ import { map, maxBy, truncate } from 'lodash' | |||
5 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import * as Sequelize from 'sequelize' | 7 | import * as Sequelize from 'sequelize' |
8 | import * as Promise from 'bluebird' | ||
9 | 8 | ||
10 | import { TagInstance } from './tag-interface' | 9 | import { TagInstance } from './tag-interface' |
11 | import { | 10 | import { |
@@ -52,6 +51,7 @@ import { | |||
52 | 51 | ||
53 | VideoMethods | 52 | VideoMethods |
54 | } from './video-interface' | 53 | } from './video-interface' |
54 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects/video-torrent-object' | ||
55 | 55 | ||
56 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> | 56 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> |
57 | let getOriginalFile: VideoMethods.GetOriginalFile | 57 | let getOriginalFile: VideoMethods.GetOriginalFile |
@@ -64,8 +64,7 @@ let getTorrentFileName: VideoMethods.GetTorrentFileName | |||
64 | let isOwned: VideoMethods.IsOwned | 64 | let isOwned: VideoMethods.IsOwned |
65 | let toFormattedJSON: VideoMethods.ToFormattedJSON | 65 | let toFormattedJSON: VideoMethods.ToFormattedJSON |
66 | let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON | 66 | let toFormattedDetailsJSON: VideoMethods.ToFormattedDetailsJSON |
67 | let toAddRemoteJSON: VideoMethods.ToAddRemoteJSON | 67 | let toActivityPubObject: VideoMethods.ToActivityPubObject |
68 | let toUpdateRemoteJSON: VideoMethods.ToUpdateRemoteJSON | ||
69 | let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile | 68 | let optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile |
70 | let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | 69 | let transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile |
71 | let createPreview: VideoMethods.CreatePreview | 70 | let createPreview: VideoMethods.CreatePreview |
@@ -76,21 +75,25 @@ let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | |||
76 | let getEmbedPath: VideoMethods.GetEmbedPath | 75 | let getEmbedPath: VideoMethods.GetEmbedPath |
77 | let getDescriptionPath: VideoMethods.GetDescriptionPath | 76 | let getDescriptionPath: VideoMethods.GetDescriptionPath |
78 | let getTruncatedDescription: VideoMethods.GetTruncatedDescription | 77 | let getTruncatedDescription: VideoMethods.GetTruncatedDescription |
78 | let getCategoryLabel: VideoMethods.GetCategoryLabel | ||
79 | let getLicenceLabel: VideoMethods.GetLicenceLabel | ||
80 | let getLanguageLabel: VideoMethods.GetLanguageLabel | ||
79 | 81 | ||
80 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 82 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
81 | let list: VideoMethods.List | 83 | let list: VideoMethods.List |
82 | let listForApi: VideoMethods.ListForApi | 84 | let listForApi: VideoMethods.ListForApi |
83 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi | 85 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi |
84 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | 86 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID |
85 | let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 87 | let listOwnedAndPopulateAccountAndTags: VideoMethods.ListOwnedAndPopulateAccountAndTags |
86 | let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 88 | let listOwnedByAccount: VideoMethods.ListOwnedByAccount |
87 | let load: VideoMethods.Load | 89 | let load: VideoMethods.Load |
88 | let loadByUUID: VideoMethods.LoadByUUID | 90 | let loadByUUID: VideoMethods.LoadByUUID |
91 | let loadByUrl: VideoMethods.LoadByUrl | ||
89 | let loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID | 92 | let loadLocalVideoByUUID: VideoMethods.LoadLocalVideoByUUID |
90 | let loadAndPopulateAuthor: VideoMethods.LoadAndPopulateAuthor | 93 | let loadAndPopulateAccount: VideoMethods.LoadAndPopulateAccount |
91 | let loadAndPopulateAuthorAndPodAndTags: VideoMethods.LoadAndPopulateAuthorAndPodAndTags | 94 | let loadAndPopulateAccountAndPodAndTags: VideoMethods.LoadAndPopulateAccountAndPodAndTags |
92 | let loadByUUIDAndPopulateAuthorAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAuthorAndPodAndTags | 95 | let loadByUUIDAndPopulateAccountAndPodAndTags: VideoMethods.LoadByUUIDAndPopulateAccountAndPodAndTags |
93 | let searchAndPopulateAuthorAndPodAndTags: VideoMethods.SearchAndPopulateAuthorAndPodAndTags | 96 | let searchAndPopulateAccountAndPodAndTags: VideoMethods.SearchAndPopulateAccountAndPodAndTags |
94 | let removeThumbnail: VideoMethods.RemoveThumbnail | 97 | let removeThumbnail: VideoMethods.RemoveThumbnail |
95 | let removePreview: VideoMethods.RemovePreview | 98 | let removePreview: VideoMethods.RemovePreview |
96 | let removeFile: VideoMethods.RemoveFile | 99 | let removeFile: VideoMethods.RemoveFile |
@@ -219,6 +222,13 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
219 | type: DataTypes.BOOLEAN, | 222 | type: DataTypes.BOOLEAN, |
220 | allowNull: false, | 223 | allowNull: false, |
221 | defaultValue: false | 224 | defaultValue: false |
225 | }, | ||
226 | url: { | ||
227 | type: DataTypes.STRING, | ||
228 | allowNull: false, | ||
229 | validate: { | ||
230 | isUrl: true | ||
231 | } | ||
222 | } | 232 | } |
223 | }, | 233 | }, |
224 | { | 234 | { |
@@ -243,6 +253,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
243 | }, | 253 | }, |
244 | { | 254 | { |
245 | fields: [ 'channelId' ] | 255 | fields: [ 'channelId' ] |
256 | }, | ||
257 | { | ||
258 | fields: [ 'parentId' ] | ||
246 | } | 259 | } |
247 | ], | 260 | ], |
248 | hooks: { | 261 | hooks: { |
@@ -258,16 +271,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
258 | list, | 271 | list, |
259 | listForApi, | 272 | listForApi, |
260 | listUserVideosForApi, | 273 | listUserVideosForApi, |
261 | listOwnedAndPopulateAuthorAndTags, | 274 | listOwnedAndPopulateAccountAndTags, |
262 | listOwnedByAuthor, | 275 | listOwnedByAccount, |
263 | load, | 276 | load, |
264 | loadAndPopulateAuthor, | 277 | loadAndPopulateAccount, |
265 | loadAndPopulateAuthorAndPodAndTags, | 278 | loadAndPopulateAccountAndPodAndTags, |
266 | loadByHostAndUUID, | 279 | loadByHostAndUUID, |
267 | loadByUUID, | 280 | loadByUUID, |
268 | loadLocalVideoByUUID, | 281 | loadLocalVideoByUUID, |
269 | loadByUUIDAndPopulateAuthorAndPodAndTags, | 282 | loadByUUIDAndPopulateAccountAndPodAndTags, |
270 | searchAndPopulateAuthorAndPodAndTags | 283 | searchAndPopulateAccountAndPodAndTags |
271 | ] | 284 | ] |
272 | const instanceMethods = [ | 285 | const instanceMethods = [ |
273 | createPreview, | 286 | createPreview, |
@@ -286,16 +299,18 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
286 | removePreview, | 299 | removePreview, |
287 | removeThumbnail, | 300 | removeThumbnail, |
288 | removeTorrent, | 301 | removeTorrent, |
289 | toAddRemoteJSON, | 302 | toActivityPubObject, |
290 | toFormattedJSON, | 303 | toFormattedJSON, |
291 | toFormattedDetailsJSON, | 304 | toFormattedDetailsJSON, |
292 | toUpdateRemoteJSON, | ||
293 | optimizeOriginalVideofile, | 305 | optimizeOriginalVideofile, |
294 | transcodeOriginalVideofile, | 306 | transcodeOriginalVideofile, |
295 | getOriginalFileHeight, | 307 | getOriginalFileHeight, |
296 | getEmbedPath, | 308 | getEmbedPath, |
297 | getTruncatedDescription, | 309 | getTruncatedDescription, |
298 | getDescriptionPath | 310 | getDescriptionPath, |
311 | getCategoryLabel, | ||
312 | getLicenceLabel, | ||
313 | getLanguageLabel | ||
299 | ] | 314 | ] |
300 | addMethodsToModel(Video, classMethods, instanceMethods) | 315 | addMethodsToModel(Video, classMethods, instanceMethods) |
301 | 316 | ||
@@ -313,6 +328,14 @@ function associate (models) { | |||
313 | onDelete: 'cascade' | 328 | onDelete: 'cascade' |
314 | }) | 329 | }) |
315 | 330 | ||
331 | Video.belongsTo(models.VideoChannel, { | ||
332 | foreignKey: { | ||
333 | name: 'parentId', | ||
334 | allowNull: true | ||
335 | }, | ||
336 | onDelete: 'cascade' | ||
337 | }) | ||
338 | |||
316 | Video.belongsToMany(models.Tag, { | 339 | Video.belongsToMany(models.Tag, { |
317 | foreignKey: 'videoId', | 340 | foreignKey: 'videoId', |
318 | through: models.VideoTag, | 341 | through: models.VideoTag, |
@@ -423,7 +446,7 @@ getVideoFilePath = function (this: VideoInstance, videoFile: VideoFileInstance) | |||
423 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | 446 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) |
424 | } | 447 | } |
425 | 448 | ||
426 | createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFileInstance) { | 449 | createTorrentAndSetInfoHash = async function (this: VideoInstance, videoFile: VideoFileInstance) { |
427 | const options = { | 450 | const options = { |
428 | announceList: [ | 451 | announceList: [ |
429 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] | 452 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ] |
@@ -433,18 +456,15 @@ createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFil | |||
433 | ] | 456 | ] |
434 | } | 457 | } |
435 | 458 | ||
436 | return createTorrentPromise(this.getVideoFilePath(videoFile), options) | 459 | const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) |
437 | .then(torrent => { | ||
438 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | ||
439 | logger.info('Creating torrent %s.', filePath) | ||
440 | 460 | ||
441 | return writeFilePromise(filePath, torrent).then(() => torrent) | 461 | const filePath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
442 | }) | 462 | logger.info('Creating torrent %s.', filePath) |
443 | .then(torrent => { | ||
444 | const parsedTorrent = parseTorrent(torrent) | ||
445 | 463 | ||
446 | videoFile.infoHash = parsedTorrent.infoHash | 464 | await writeFilePromise(filePath, torrent) |
447 | }) | 465 | |
466 | const parsedTorrent = parseTorrent(torrent) | ||
467 | videoFile.infoHash = parsedTorrent.infoHash | ||
448 | } | 468 | } |
449 | 469 | ||
450 | getEmbedPath = function (this: VideoInstance) { | 470 | getEmbedPath = function (this: VideoInstance) { |
@@ -462,40 +482,28 @@ getPreviewPath = function (this: VideoInstance) { | |||
462 | toFormattedJSON = function (this: VideoInstance) { | 482 | toFormattedJSON = function (this: VideoInstance) { |
463 | let podHost | 483 | let podHost |
464 | 484 | ||
465 | if (this.VideoChannel.Author.Pod) { | 485 | if (this.VideoChannel.Account.Pod) { |
466 | podHost = this.VideoChannel.Author.Pod.host | 486 | podHost = this.VideoChannel.Account.Pod.host |
467 | } else { | 487 | } else { |
468 | // It means it's our video | 488 | // It means it's our video |
469 | podHost = CONFIG.WEBSERVER.HOST | 489 | podHost = CONFIG.WEBSERVER.HOST |
470 | } | 490 | } |
471 | 491 | ||
472 | // Maybe our pod is not up to date and there are new categories since our version | ||
473 | let categoryLabel = VIDEO_CATEGORIES[this.category] | ||
474 | if (!categoryLabel) categoryLabel = 'Misc' | ||
475 | |||
476 | // Maybe our pod is not up to date and there are new licences since our version | ||
477 | let licenceLabel = VIDEO_LICENCES[this.licence] | ||
478 | if (!licenceLabel) licenceLabel = 'Unknown' | ||
479 | |||
480 | // Language is an optional attribute | ||
481 | let languageLabel = VIDEO_LANGUAGES[this.language] | ||
482 | if (!languageLabel) languageLabel = 'Unknown' | ||
483 | |||
484 | const json = { | 492 | const json = { |
485 | id: this.id, | 493 | id: this.id, |
486 | uuid: this.uuid, | 494 | uuid: this.uuid, |
487 | name: this.name, | 495 | name: this.name, |
488 | category: this.category, | 496 | category: this.category, |
489 | categoryLabel, | 497 | categoryLabel: this.getCategoryLabel(), |
490 | licence: this.licence, | 498 | licence: this.licence, |
491 | licenceLabel, | 499 | licenceLabel: this.getLicenceLabel(), |
492 | language: this.language, | 500 | language: this.language, |
493 | languageLabel, | 501 | languageLabel: this.getLanguageLabel(), |
494 | nsfw: this.nsfw, | 502 | nsfw: this.nsfw, |
495 | description: this.getTruncatedDescription(), | 503 | description: this.getTruncatedDescription(), |
496 | podHost, | 504 | podHost, |
497 | isLocal: this.isOwned(), | 505 | isLocal: this.isOwned(), |
498 | author: this.VideoChannel.Author.name, | 506 | account: this.VideoChannel.Account.name, |
499 | duration: this.duration, | 507 | duration: this.duration, |
500 | views: this.views, | 508 | views: this.views, |
501 | likes: this.likes, | 509 | likes: this.likes, |
@@ -552,75 +560,75 @@ toFormattedDetailsJSON = function (this: VideoInstance) { | |||
552 | return Object.assign(formattedJson, detailsJson) | 560 | return Object.assign(formattedJson, detailsJson) |
553 | } | 561 | } |
554 | 562 | ||
555 | toAddRemoteJSON = function (this: VideoInstance) { | 563 | toActivityPubObject = function (this: VideoInstance) { |
556 | // Get thumbnail data to send to the other pod | 564 | const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) |
557 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | ||
558 | 565 | ||
559 | return readFileBufferPromise(thumbnailPath).then(thumbnailData => { | 566 | const tag = this.Tags.map(t => ({ |
560 | const remoteVideo = { | 567 | type: 'Hashtag', |
561 | uuid: this.uuid, | 568 | name: t.name |
562 | name: this.name, | 569 | })) |
563 | category: this.category, | 570 | |
564 | licence: this.licence, | 571 | const url = [] |
565 | language: this.language, | 572 | for (const file of this.VideoFiles) { |
566 | nsfw: this.nsfw, | 573 | url.push({ |
567 | truncatedDescription: this.getTruncatedDescription(), | 574 | type: 'Link', |
568 | channelUUID: this.VideoChannel.uuid, | 575 | mimeType: 'video/' + file.extname, |
569 | duration: this.duration, | 576 | url: getVideoFileUrl(this, file, baseUrlHttp), |
570 | thumbnailData: thumbnailData.toString('binary'), | 577 | width: file.resolution, |
571 | tags: map<TagInstance, string>(this.Tags, 'name'), | 578 | size: file.size |
572 | createdAt: this.createdAt, | 579 | }) |
573 | updatedAt: this.updatedAt, | ||
574 | views: this.views, | ||
575 | likes: this.likes, | ||
576 | dislikes: this.dislikes, | ||
577 | privacy: this.privacy, | ||
578 | files: [] | ||
579 | } | ||
580 | 580 | ||
581 | this.VideoFiles.forEach(videoFile => { | 581 | url.push({ |
582 | remoteVideo.files.push({ | 582 | type: 'Link', |
583 | infoHash: videoFile.infoHash, | 583 | mimeType: 'application/x-bittorrent', |
584 | resolution: videoFile.resolution, | 584 | url: getTorrentUrl(this, file, baseUrlHttp), |
585 | extname: videoFile.extname, | 585 | width: file.resolution |
586 | size: videoFile.size | ||
587 | }) | ||
588 | }) | 586 | }) |
589 | 587 | ||
590 | return remoteVideo | 588 | url.push({ |
591 | }) | 589 | type: 'Link', |
592 | } | 590 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet', |
591 | url: generateMagnetUri(this, file, baseUrlHttp, baseUrlWs), | ||
592 | width: file.resolution | ||
593 | }) | ||
594 | } | ||
593 | 595 | ||
594 | toUpdateRemoteJSON = function (this: VideoInstance) { | 596 | const videoObject: VideoTorrentObject = { |
595 | const json = { | 597 | type: 'Video', |
596 | uuid: this.uuid, | ||
597 | name: this.name, | 598 | name: this.name, |
598 | category: this.category, | 599 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration |
599 | licence: this.licence, | 600 | duration: 'PT' + this.duration + 'S', |
600 | language: this.language, | 601 | uuid: this.uuid, |
601 | nsfw: this.nsfw, | 602 | tag, |
602 | truncatedDescription: this.getTruncatedDescription(), | 603 | category: { |
603 | duration: this.duration, | 604 | id: this.category, |
604 | tags: map<TagInstance, string>(this.Tags, 'name'), | 605 | label: this.getCategoryLabel() |
605 | createdAt: this.createdAt, | 606 | }, |
606 | updatedAt: this.updatedAt, | 607 | licence: { |
608 | id: this.licence, | ||
609 | name: this.getLicenceLabel() | ||
610 | }, | ||
611 | language: { | ||
612 | id: this.language, | ||
613 | name: this.getLanguageLabel() | ||
614 | }, | ||
607 | views: this.views, | 615 | views: this.views, |
608 | likes: this.likes, | 616 | nsfw: this.nsfw, |
609 | dislikes: this.dislikes, | 617 | published: this.createdAt, |
610 | privacy: this.privacy, | 618 | updated: this.updatedAt, |
611 | files: [] | 619 | mediaType: 'text/markdown', |
620 | content: this.getTruncatedDescription(), | ||
621 | icon: { | ||
622 | type: 'Image', | ||
623 | url: getThumbnailUrl(this, baseUrlHttp), | ||
624 | mediaType: 'image/jpeg', | ||
625 | width: THUMBNAILS_SIZE.width, | ||
626 | height: THUMBNAILS_SIZE.height | ||
627 | }, | ||
628 | url | ||
612 | } | 629 | } |
613 | 630 | ||
614 | this.VideoFiles.forEach(videoFile => { | 631 | return videoObject |
615 | json.files.push({ | ||
616 | infoHash: videoFile.infoHash, | ||
617 | resolution: videoFile.resolution, | ||
618 | extname: videoFile.extname, | ||
619 | size: videoFile.size | ||
620 | }) | ||
621 | }) | ||
622 | |||
623 | return json | ||
624 | } | 632 | } |
625 | 633 | ||
626 | getTruncatedDescription = function (this: VideoInstance) { | 634 | getTruncatedDescription = function (this: VideoInstance) { |
@@ -631,7 +639,7 @@ getTruncatedDescription = function (this: VideoInstance) { | |||
631 | return truncate(this.description, options) | 639 | return truncate(this.description, options) |
632 | } | 640 | } |
633 | 641 | ||
634 | optimizeOriginalVideofile = function (this: VideoInstance) { | 642 | optimizeOriginalVideofile = async function (this: VideoInstance) { |
635 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 643 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
636 | const newExtname = '.mp4' | 644 | const newExtname = '.mp4' |
637 | const inputVideoFile = this.getOriginalFile() | 645 | const inputVideoFile = this.getOriginalFile() |
@@ -643,40 +651,32 @@ optimizeOriginalVideofile = function (this: VideoInstance) { | |||
643 | outputPath: videoOutputPath | 651 | outputPath: videoOutputPath |
644 | } | 652 | } |
645 | 653 | ||
646 | return transcode(transcodeOptions) | 654 | try { |
647 | .then(() => { | 655 | // Could be very long! |
648 | return unlinkPromise(videoInputPath) | 656 | await transcode(transcodeOptions) |
649 | }) | ||
650 | .then(() => { | ||
651 | // Important to do this before getVideoFilename() to take in account the new file extension | ||
652 | inputVideoFile.set('extname', newExtname) | ||
653 | 657 | ||
654 | return renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) | 658 | await unlinkPromise(videoInputPath) |
655 | }) | ||
656 | .then(() => { | ||
657 | return statPromise(this.getVideoFilePath(inputVideoFile)) | ||
658 | }) | ||
659 | .then(stats => { | ||
660 | return inputVideoFile.set('size', stats.size) | ||
661 | }) | ||
662 | .then(() => { | ||
663 | return this.createTorrentAndSetInfoHash(inputVideoFile) | ||
664 | }) | ||
665 | .then(() => { | ||
666 | return inputVideoFile.save() | ||
667 | }) | ||
668 | .then(() => { | ||
669 | return undefined | ||
670 | }) | ||
671 | .catch(err => { | ||
672 | // Auto destruction... | ||
673 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) | ||
674 | 659 | ||
675 | throw err | 660 | // Important to do this before getVideoFilename() to take in account the new file extension |
676 | }) | 661 | inputVideoFile.set('extname', newExtname) |
662 | |||
663 | await renamePromise(videoOutputPath, this.getVideoFilePath(inputVideoFile)) | ||
664 | const stats = await statPromise(this.getVideoFilePath(inputVideoFile)) | ||
665 | |||
666 | inputVideoFile.set('size', stats.size) | ||
667 | |||
668 | await this.createTorrentAndSetInfoHash(inputVideoFile) | ||
669 | await inputVideoFile.save() | ||
670 | |||
671 | } catch (err) { | ||
672 | // Auto destruction... | ||
673 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) | ||
674 | |||
675 | throw err | ||
676 | } | ||
677 | } | 677 | } |
678 | 678 | ||
679 | transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoResolution) { | 679 | transcodeOriginalVideofile = async function (this: VideoInstance, resolution: VideoResolution) { |
680 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 680 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
681 | const extname = '.mp4' | 681 | const extname = '.mp4' |
682 | 682 | ||
@@ -696,25 +696,18 @@ transcodeOriginalVideofile = function (this: VideoInstance, resolution: VideoRes | |||
696 | outputPath: videoOutputPath, | 696 | outputPath: videoOutputPath, |
697 | resolution | 697 | resolution |
698 | } | 698 | } |
699 | return transcode(transcodeOptions) | ||
700 | .then(() => { | ||
701 | return statPromise(videoOutputPath) | ||
702 | }) | ||
703 | .then(stats => { | ||
704 | newVideoFile.set('size', stats.size) | ||
705 | 699 | ||
706 | return undefined | 700 | await transcode(transcodeOptions) |
707 | }) | 701 | |
708 | .then(() => { | 702 | const stats = await statPromise(videoOutputPath) |
709 | return this.createTorrentAndSetInfoHash(newVideoFile) | 703 | |
710 | }) | 704 | newVideoFile.set('size', stats.size) |
711 | .then(() => { | 705 | |
712 | return newVideoFile.save() | 706 | await this.createTorrentAndSetInfoHash(newVideoFile) |
713 | }) | 707 | |
714 | .then(() => { | 708 | await newVideoFile.save() |
715 | return this.VideoFiles.push(newVideoFile) | 709 | |
716 | }) | 710 | this.VideoFiles.push(newVideoFile) |
717 | .then(() => undefined) | ||
718 | } | 711 | } |
719 | 712 | ||
720 | getOriginalFileHeight = function (this: VideoInstance) { | 713 | getOriginalFileHeight = function (this: VideoInstance) { |
@@ -727,6 +720,31 @@ getDescriptionPath = function (this: VideoInstance) { | |||
727 | return `/api/${API_VERSION}/videos/${this.uuid}/description` | 720 | return `/api/${API_VERSION}/videos/${this.uuid}/description` |
728 | } | 721 | } |
729 | 722 | ||
723 | getCategoryLabel = function (this: VideoInstance) { | ||
724 | let categoryLabel = VIDEO_CATEGORIES[this.category] | ||
725 | |||
726 | // Maybe our pod is not up to date and there are new categories since our version | ||
727 | if (!categoryLabel) categoryLabel = 'Misc' | ||
728 | |||
729 | return categoryLabel | ||
730 | } | ||
731 | |||
732 | getLicenceLabel = function (this: VideoInstance) { | ||
733 | let licenceLabel = VIDEO_LICENCES[this.licence] | ||
734 | // Maybe our pod is not up to date and there are new licences since our version | ||
735 | if (!licenceLabel) licenceLabel = 'Unknown' | ||
736 | |||
737 | return licenceLabel | ||
738 | } | ||
739 | |||
740 | getLanguageLabel = function (this: VideoInstance) { | ||
741 | // Language is an optional attribute | ||
742 | let languageLabel = VIDEO_LANGUAGES[this.language] | ||
743 | if (!languageLabel) languageLabel = 'Unknown' | ||
744 | |||
745 | return languageLabel | ||
746 | } | ||
747 | |||
730 | removeThumbnail = function (this: VideoInstance) { | 748 | removeThumbnail = function (this: VideoInstance) { |
731 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | 749 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) |
732 | return unlinkPromise(thumbnailPath) | 750 | return unlinkPromise(thumbnailPath) |
@@ -779,7 +797,7 @@ listUserVideosForApi = function (userId: number, start: number, count: number, s | |||
779 | required: true, | 797 | required: true, |
780 | include: [ | 798 | include: [ |
781 | { | 799 | { |
782 | model: Video['sequelize'].models.Author, | 800 | model: Video['sequelize'].models.Account, |
783 | where: { | 801 | where: { |
784 | userId | 802 | userId |
785 | }, | 803 | }, |
@@ -810,7 +828,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
810 | model: Video['sequelize'].models.VideoChannel, | 828 | model: Video['sequelize'].models.VideoChannel, |
811 | include: [ | 829 | include: [ |
812 | { | 830 | { |
813 | model: Video['sequelize'].models.Author, | 831 | model: Video['sequelize'].models.Account, |
814 | include: [ | 832 | include: [ |
815 | { | 833 | { |
816 | model: Video['sequelize'].models.Pod, | 834 | model: Video['sequelize'].models.Pod, |
@@ -846,7 +864,7 @@ loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Tran | |||
846 | model: Video['sequelize'].models.VideoChannel, | 864 | model: Video['sequelize'].models.VideoChannel, |
847 | include: [ | 865 | include: [ |
848 | { | 866 | { |
849 | model: Video['sequelize'].models.Author, | 867 | model: Video['sequelize'].models.Account, |
850 | include: [ | 868 | include: [ |
851 | { | 869 | { |
852 | model: Video['sequelize'].models.Pod, | 870 | model: Video['sequelize'].models.Pod, |
@@ -867,7 +885,7 @@ loadByHostAndUUID = function (fromHost: string, uuid: string, t?: Sequelize.Tran | |||
867 | return Video.findOne(query) | 885 | return Video.findOne(query) |
868 | } | 886 | } |
869 | 887 | ||
870 | listOwnedAndPopulateAuthorAndTags = function () { | 888 | listOwnedAndPopulateAccountAndTags = function () { |
871 | const query = { | 889 | const query = { |
872 | where: { | 890 | where: { |
873 | remote: false | 891 | remote: false |
@@ -876,7 +894,7 @@ listOwnedAndPopulateAuthorAndTags = function () { | |||
876 | Video['sequelize'].models.VideoFile, | 894 | Video['sequelize'].models.VideoFile, |
877 | { | 895 | { |
878 | model: Video['sequelize'].models.VideoChannel, | 896 | model: Video['sequelize'].models.VideoChannel, |
879 | include: [ Video['sequelize'].models.Author ] | 897 | include: [ Video['sequelize'].models.Account ] |
880 | }, | 898 | }, |
881 | Video['sequelize'].models.Tag | 899 | Video['sequelize'].models.Tag |
882 | ] | 900 | ] |
@@ -885,7 +903,7 @@ listOwnedAndPopulateAuthorAndTags = function () { | |||
885 | return Video.findAll(query) | 903 | return Video.findAll(query) |
886 | } | 904 | } |
887 | 905 | ||
888 | listOwnedByAuthor = function (author: string) { | 906 | listOwnedByAccount = function (account: string) { |
889 | const query = { | 907 | const query = { |
890 | where: { | 908 | where: { |
891 | remote: false | 909 | remote: false |
@@ -898,9 +916,9 @@ listOwnedByAuthor = function (author: string) { | |||
898 | model: Video['sequelize'].models.VideoChannel, | 916 | model: Video['sequelize'].models.VideoChannel, |
899 | include: [ | 917 | include: [ |
900 | { | 918 | { |
901 | model: Video['sequelize'].models.Author, | 919 | model: Video['sequelize'].models.Account, |
902 | where: { | 920 | where: { |
903 | name: author | 921 | name: account |
904 | } | 922 | } |
905 | } | 923 | } |
906 | ] | 924 | ] |
@@ -942,13 +960,13 @@ loadLocalVideoByUUID = function (uuid: string, t?: Sequelize.Transaction) { | |||
942 | return Video.findOne(query) | 960 | return Video.findOne(query) |
943 | } | 961 | } |
944 | 962 | ||
945 | loadAndPopulateAuthor = function (id: number) { | 963 | loadAndPopulateAccount = function (id: number) { |
946 | const options = { | 964 | const options = { |
947 | include: [ | 965 | include: [ |
948 | Video['sequelize'].models.VideoFile, | 966 | Video['sequelize'].models.VideoFile, |
949 | { | 967 | { |
950 | model: Video['sequelize'].models.VideoChannel, | 968 | model: Video['sequelize'].models.VideoChannel, |
951 | include: [ Video['sequelize'].models.Author ] | 969 | include: [ Video['sequelize'].models.Account ] |
952 | } | 970 | } |
953 | ] | 971 | ] |
954 | } | 972 | } |
@@ -956,14 +974,14 @@ loadAndPopulateAuthor = function (id: number) { | |||
956 | return Video.findById(id, options) | 974 | return Video.findById(id, options) |
957 | } | 975 | } |
958 | 976 | ||
959 | loadAndPopulateAuthorAndPodAndTags = function (id: number) { | 977 | loadAndPopulateAccountAndPodAndTags = function (id: number) { |
960 | const options = { | 978 | const options = { |
961 | include: [ | 979 | include: [ |
962 | { | 980 | { |
963 | model: Video['sequelize'].models.VideoChannel, | 981 | model: Video['sequelize'].models.VideoChannel, |
964 | include: [ | 982 | include: [ |
965 | { | 983 | { |
966 | model: Video['sequelize'].models.Author, | 984 | model: Video['sequelize'].models.Account, |
967 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] | 985 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] |
968 | } | 986 | } |
969 | ] | 987 | ] |
@@ -976,7 +994,7 @@ loadAndPopulateAuthorAndPodAndTags = function (id: number) { | |||
976 | return Video.findById(id, options) | 994 | return Video.findById(id, options) |
977 | } | 995 | } |
978 | 996 | ||
979 | loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) { | 997 | loadByUUIDAndPopulateAccountAndPodAndTags = function (uuid: string) { |
980 | const options = { | 998 | const options = { |
981 | where: { | 999 | where: { |
982 | uuid | 1000 | uuid |
@@ -986,7 +1004,7 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) { | |||
986 | model: Video['sequelize'].models.VideoChannel, | 1004 | model: Video['sequelize'].models.VideoChannel, |
987 | include: [ | 1005 | include: [ |
988 | { | 1006 | { |
989 | model: Video['sequelize'].models.Author, | 1007 | model: Video['sequelize'].models.Account, |
990 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] | 1008 | include: [ { model: Video['sequelize'].models.Pod, required: false } ] |
991 | } | 1009 | } |
992 | ] | 1010 | ] |
@@ -999,20 +1017,20 @@ loadByUUIDAndPopulateAuthorAndPodAndTags = function (uuid: string) { | |||
999 | return Video.findOne(options) | 1017 | return Video.findOne(options) |
1000 | } | 1018 | } |
1001 | 1019 | ||
1002 | searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) { | 1020 | searchAndPopulateAccountAndPodAndTags = function (value: string, field: string, start: number, count: number, sort: string) { |
1003 | const podInclude: Sequelize.IncludeOptions = { | 1021 | const podInclude: Sequelize.IncludeOptions = { |
1004 | model: Video['sequelize'].models.Pod, | 1022 | model: Video['sequelize'].models.Pod, |
1005 | required: false | 1023 | required: false |
1006 | } | 1024 | } |
1007 | 1025 | ||
1008 | const authorInclude: Sequelize.IncludeOptions = { | 1026 | const accountInclude: Sequelize.IncludeOptions = { |
1009 | model: Video['sequelize'].models.Author, | 1027 | model: Video['sequelize'].models.Account, |
1010 | include: [ podInclude ] | 1028 | include: [ podInclude ] |
1011 | } | 1029 | } |
1012 | 1030 | ||
1013 | const videoChannelInclude: Sequelize.IncludeOptions = { | 1031 | const videoChannelInclude: Sequelize.IncludeOptions = { |
1014 | model: Video['sequelize'].models.VideoChannel, | 1032 | model: Video['sequelize'].models.VideoChannel, |
1015 | include: [ authorInclude ], | 1033 | include: [ accountInclude ], |
1016 | required: true | 1034 | required: true |
1017 | } | 1035 | } |
1018 | 1036 | ||
@@ -1045,8 +1063,8 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
1045 | } | 1063 | } |
1046 | } | 1064 | } |
1047 | podInclude.required = true | 1065 | podInclude.required = true |
1048 | } else if (field === 'author') { | 1066 | } else if (field === 'account') { |
1049 | authorInclude.where = { | 1067 | accountInclude.where = { |
1050 | name: { | 1068 | name: { |
1051 | [Sequelize.Op.iLike]: '%' + value + '%' | 1069 | [Sequelize.Op.iLike]: '%' + value + '%' |
1052 | } | 1070 | } |
@@ -1090,13 +1108,17 @@ function getBaseUrls (video: VideoInstance) { | |||
1090 | baseUrlHttp = CONFIG.WEBSERVER.URL | 1108 | baseUrlHttp = CONFIG.WEBSERVER.URL |
1091 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 1109 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT |
1092 | } else { | 1110 | } else { |
1093 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Author.Pod.host | 1111 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.VideoChannel.Account.Pod.host |
1094 | baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Author.Pod.host | 1112 | baseUrlWs = REMOTE_SCHEME.WS + '://' + video.VideoChannel.Account.Pod.host |
1095 | } | 1113 | } |
1096 | 1114 | ||
1097 | return { baseUrlHttp, baseUrlWs } | 1115 | return { baseUrlHttp, baseUrlWs } |
1098 | } | 1116 | } |
1099 | 1117 | ||
1118 | function getThumbnailUrl (video: VideoInstance, baseUrlHttp: string) { | ||
1119 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + video.getThumbnailName() | ||
1120 | } | ||
1121 | |||
1100 | function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { | 1122 | function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) { |
1101 | return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile) | 1123 | return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile) |
1102 | } | 1124 | } |