diff options
-rw-r--r-- | server/controllers/activitypub/outbox.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/server/follows.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/videos/channel.ts | 11 | ||||
-rw-r--r-- | server/controllers/webfinger.ts | 4 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/account.ts | 92 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/activity.ts | 2 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/actor.ts | 91 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/index.ts | 2 | ||||
-rw-r--r-- | server/helpers/custom-validators/activitypub/undo.ts | 2 | ||||
-rw-r--r-- | server/initializers/constants.ts | 2 | ||||
-rw-r--r-- | server/lib/user.ts | 24 | ||||
-rw-r--r-- | server/models/account/account.ts | 234 | ||||
-rw-r--r-- | server/models/activitypub/actor.ts | 245 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 60 | ||||
-rw-r--r-- | shared/models/activitypub/activitypub-actor.ts | 2 |
15 files changed, 426 insertions, 351 deletions
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index dc6b72a6e..6ed8a3454 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -40,14 +40,14 @@ async function outboxController (req: express.Request, res: express.Response, ne | |||
40 | // This is a shared video | 40 | // This is a shared video |
41 | const videoChannel = video.VideoChannel | 41 | const videoChannel = video.VideoChannel |
42 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { | 42 | if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { |
43 | const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.url, videoObject, undefined) | 43 | const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.Actor.url, videoObject, undefined) |
44 | 44 | ||
45 | const url = getAnnounceActivityPubUrl(video.url, account) | 45 | const url = getAnnounceActivityPubUrl(video.url, account) |
46 | const announceActivity = await announceActivityData(url, account, addActivity, undefined) | 46 | const announceActivity = await announceActivityData(url, account, addActivity, undefined) |
47 | 47 | ||
48 | activities.push(announceActivity) | 48 | activities.push(announceActivity) |
49 | } else { | 49 | } else { |
50 | const addActivity = await addActivityData(video.url, account, video, videoChannel.url, videoObject, undefined) | 50 | const addActivity = await addActivityData(video.url, account, video, videoChannel.Actor.url, videoObject, undefined) |
51 | 51 | ||
52 | activities.push(addActivity) | 52 | activities.push(addActivity) |
53 | } | 53 | } |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 913998e3a..497edb8eb 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -157,7 +157,7 @@ async function removeFollow (req: express.Request, res: express.Response, next: | |||
157 | // This could be long so don't wait this task | 157 | // This could be long so don't wait this task |
158 | const following = follow.AccountFollowing | 158 | const following = follow.AccountFollowing |
159 | following.destroy() | 159 | following.destroy() |
160 | .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.url, err)) | 160 | .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.Actor.url, err)) |
161 | 161 | ||
162 | return res.status(204).end() | 162 | return res.status(204).end() |
163 | } | 163 | } |
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts index 683b0448d..315469115 100644 --- a/server/controllers/api/videos/channel.ts +++ b/server/controllers/api/videos/channel.ts | |||
@@ -92,16 +92,15 @@ async function addVideoChannelRetryWrapper (req: express.Request, res: express.R | |||
92 | return res.type('json').status(204).end() | 92 | return res.type('json').status(204).end() |
93 | } | 93 | } |
94 | 94 | ||
95 | async function addVideoChannel (req: express.Request, res: express.Response) { | 95 | function addVideoChannel (req: express.Request, res: express.Response) { |
96 | const videoChannelInfo: VideoChannelCreate = req.body | 96 | const videoChannelInfo: VideoChannelCreate = req.body |
97 | const account: AccountModel = res.locals.oauth.token.User.Account | 97 | const account: AccountModel = res.locals.oauth.token.User.Account |
98 | let videoChannelCreated: VideoChannelModel | ||
99 | 98 | ||
100 | await sequelizeTypescript.transaction(async t => { | 99 | return sequelizeTypescript.transaction(async t => { |
101 | videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t) | 100 | const videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t) |
102 | }) | ||
103 | 101 | ||
104 | logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid) | 102 | logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid) |
103 | }) | ||
105 | } | 104 | } |
106 | 105 | ||
107 | async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 106 | async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts index bb2ea40fa..8829500bc 100644 --- a/server/controllers/webfinger.ts +++ b/server/controllers/webfinger.ts | |||
@@ -23,12 +23,12 @@ function webfingerController (req: express.Request, res: express.Response, next: | |||
23 | 23 | ||
24 | const json = { | 24 | const json = { |
25 | subject: req.query.resource, | 25 | subject: req.query.resource, |
26 | aliases: [ account.url ], | 26 | aliases: [ account.Actor.url ], |
27 | links: [ | 27 | links: [ |
28 | { | 28 | { |
29 | rel: 'self', | 29 | rel: 'self', |
30 | type: 'application/activity+json', | 30 | type: 'application/activity+json', |
31 | href: account.url | 31 | href: account.Actor.url |
32 | } | 32 | } |
33 | ] | 33 | ] |
34 | } | 34 | } |
diff --git a/server/helpers/custom-validators/activitypub/account.ts b/server/helpers/custom-validators/activitypub/account.ts deleted file mode 100644 index 10bf81e8a..000000000 --- a/server/helpers/custom-validators/activitypub/account.ts +++ /dev/null | |||
@@ -1,92 +0,0 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | ||
3 | import { isAccountNameValid } from '../accounts' | ||
4 | import { exists, isUUIDValid } from '../misc' | ||
5 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | ||
6 | |||
7 | function isAccountEndpointsObjectValid (endpointObject: any) { | ||
8 | return isActivityPubUrlValid(endpointObject.sharedInbox) | ||
9 | } | ||
10 | |||
11 | function isAccountPublicKeyObjectValid (publicKeyObject: any) { | ||
12 | return isActivityPubUrlValid(publicKeyObject.id) && | ||
13 | isActivityPubUrlValid(publicKeyObject.owner) && | ||
14 | isAccountPublicKeyValid(publicKeyObject.publicKeyPem) | ||
15 | } | ||
16 | |||
17 | function isAccountTypeValid (type: string) { | ||
18 | return type === 'Person' || type === 'Application' | ||
19 | } | ||
20 | |||
21 | function isAccountPublicKeyValid (publicKey: string) { | ||
22 | return exists(publicKey) && | ||
23 | typeof publicKey === 'string' && | ||
24 | publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && | ||
25 | publicKey.endsWith('-----END PUBLIC KEY-----') && | ||
26 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY) | ||
27 | } | ||
28 | |||
29 | function isAccountPreferredUsernameValid (preferredUsername: string) { | ||
30 | return isAccountNameValid(preferredUsername) | ||
31 | } | ||
32 | |||
33 | function isAccountPrivateKeyValid (privateKey: string) { | ||
34 | return exists(privateKey) && | ||
35 | typeof privateKey === 'string' && | ||
36 | privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') && | ||
37 | privateKey.endsWith('-----END RSA PRIVATE KEY-----') && | ||
38 | validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY) | ||
39 | } | ||
40 | |||
41 | function isRemoteAccountValid (remoteAccount: any) { | ||
42 | return isActivityPubUrlValid(remoteAccount.id) && | ||
43 | isUUIDValid(remoteAccount.uuid) && | ||
44 | isAccountTypeValid(remoteAccount.type) && | ||
45 | isActivityPubUrlValid(remoteAccount.following) && | ||
46 | isActivityPubUrlValid(remoteAccount.followers) && | ||
47 | isActivityPubUrlValid(remoteAccount.inbox) && | ||
48 | isActivityPubUrlValid(remoteAccount.outbox) && | ||
49 | isAccountPreferredUsernameValid(remoteAccount.preferredUsername) && | ||
50 | isActivityPubUrlValid(remoteAccount.url) && | ||
51 | isAccountPublicKeyObjectValid(remoteAccount.publicKey) && | ||
52 | isAccountEndpointsObjectValid(remoteAccount.endpoints) | ||
53 | } | ||
54 | |||
55 | function isAccountFollowingCountValid (value: string) { | ||
56 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
57 | } | ||
58 | |||
59 | function isAccountFollowersCountValid (value: string) { | ||
60 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
61 | } | ||
62 | |||
63 | function isAccountDeleteActivityValid (activity: any) { | ||
64 | return isBaseActivityValid(activity, 'Delete') | ||
65 | } | ||
66 | |||
67 | function isAccountFollowActivityValid (activity: any) { | ||
68 | return isBaseActivityValid(activity, 'Follow') && | ||
69 | isActivityPubUrlValid(activity.object) | ||
70 | } | ||
71 | |||
72 | function isAccountAcceptActivityValid (activity: any) { | ||
73 | return isBaseActivityValid(activity, 'Accept') | ||
74 | } | ||
75 | |||
76 | // --------------------------------------------------------------------------- | ||
77 | |||
78 | export { | ||
79 | isAccountEndpointsObjectValid, | ||
80 | isAccountPublicKeyObjectValid, | ||
81 | isAccountTypeValid, | ||
82 | isAccountPublicKeyValid, | ||
83 | isAccountPreferredUsernameValid, | ||
84 | isAccountPrivateKeyValid, | ||
85 | isRemoteAccountValid, | ||
86 | isAccountFollowingCountValid, | ||
87 | isAccountFollowersCountValid, | ||
88 | isAccountNameValid, | ||
89 | isAccountFollowActivityValid, | ||
90 | isAccountAcceptActivityValid, | ||
91 | isAccountDeleteActivityValid | ||
92 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index 043e3c55e..ae7732194 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | 2 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
3 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account' | 3 | import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './actor' |
4 | import { isAnnounceActivityValid } from './announce' | 4 | import { isAnnounceActivityValid } from './announce' |
5 | import { isActivityPubUrlValid } from './misc' | 5 | import { isActivityPubUrlValid } from './misc' |
6 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | 6 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' |
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts new file mode 100644 index 000000000..28551c96c --- /dev/null +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -0,0 +1,91 @@ | |||
1 | import * as validator from 'validator' | ||
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | ||
3 | import { isAccountNameValid } from '../accounts' | ||
4 | import { exists, isUUIDValid } from '../misc' | ||
5 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | ||
6 | |||
7 | function isActorEndpointsObjectValid (endpointObject: any) { | ||
8 | return isActivityPubUrlValid(endpointObject.sharedInbox) | ||
9 | } | ||
10 | |||
11 | function isActorPublicKeyObjectValid (publicKeyObject: any) { | ||
12 | return isActivityPubUrlValid(publicKeyObject.id) && | ||
13 | isActivityPubUrlValid(publicKeyObject.owner) && | ||
14 | isActorPublicKeyValid(publicKeyObject.publicKeyPem) | ||
15 | } | ||
16 | |||
17 | function isActorTypeValid (type: string) { | ||
18 | return type === 'Person' || type === 'Application' || type === 'Group' | ||
19 | } | ||
20 | |||
21 | function isActorPublicKeyValid (publicKey: string) { | ||
22 | return exists(publicKey) && | ||
23 | typeof publicKey === 'string' && | ||
24 | publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && | ||
25 | publicKey.endsWith('-----END PUBLIC KEY-----') && | ||
26 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY) | ||
27 | } | ||
28 | |||
29 | function isActorPreferredUsernameValid (preferredUsername: string) { | ||
30 | return isAccountNameValid(preferredUsername) | ||
31 | } | ||
32 | |||
33 | function isActorPrivateKeyValid (privateKey: string) { | ||
34 | return exists(privateKey) && | ||
35 | typeof privateKey === 'string' && | ||
36 | privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') && | ||
37 | privateKey.endsWith('-----END RSA PRIVATE KEY-----') && | ||
38 | validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY) | ||
39 | } | ||
40 | |||
41 | function isRemoteActorValid (remoteActor: any) { | ||
42 | return isActivityPubUrlValid(remoteActor.id) && | ||
43 | isUUIDValid(remoteActor.uuid) && | ||
44 | isActorTypeValid(remoteActor.type) && | ||
45 | isActivityPubUrlValid(remoteActor.following) && | ||
46 | isActivityPubUrlValid(remoteActor.followers) && | ||
47 | isActivityPubUrlValid(remoteActor.inbox) && | ||
48 | isActivityPubUrlValid(remoteActor.outbox) && | ||
49 | isActorPreferredUsernameValid(remoteActor.preferredUsername) && | ||
50 | isActivityPubUrlValid(remoteActor.url) && | ||
51 | isActorPublicKeyObjectValid(remoteActor.publicKey) && | ||
52 | isActorEndpointsObjectValid(remoteActor.endpoints) | ||
53 | } | ||
54 | |||
55 | function isActorFollowingCountValid (value: string) { | ||
56 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
57 | } | ||
58 | |||
59 | function isActorFollowersCountValid (value: string) { | ||
60 | return exists(value) && validator.isInt('' + value, { min: 0 }) | ||
61 | } | ||
62 | |||
63 | function isActorDeleteActivityValid (activity: any) { | ||
64 | return isBaseActivityValid(activity, 'Delete') | ||
65 | } | ||
66 | |||
67 | function isActorFollowActivityValid (activity: any) { | ||
68 | return isBaseActivityValid(activity, 'Follow') && | ||
69 | isActivityPubUrlValid(activity.object) | ||
70 | } | ||
71 | |||
72 | function isActorAcceptActivityValid (activity: any) { | ||
73 | return isBaseActivityValid(activity, 'Accept') | ||
74 | } | ||
75 | |||
76 | // --------------------------------------------------------------------------- | ||
77 | |||
78 | export { | ||
79 | isActorEndpointsObjectValid, | ||
80 | isActorPublicKeyObjectValid, | ||
81 | isActorTypeValid, | ||
82 | isActorPublicKeyValid, | ||
83 | isActorPreferredUsernameValid, | ||
84 | isActorPrivateKeyValid, | ||
85 | isRemoteActorValid, | ||
86 | isActorFollowingCountValid, | ||
87 | isActorFollowersCountValid, | ||
88 | isActorFollowActivityValid, | ||
89 | isActorAcceptActivityValid, | ||
90 | isActorDeleteActivityValid | ||
91 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts index f8dfae4ff..ba411f1c6 100644 --- a/server/helpers/custom-validators/activitypub/index.ts +++ b/server/helpers/custom-validators/activitypub/index.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export * from './account' | 1 | export * from './actor' |
2 | export * from './activity' | 2 | export * from './activity' |
3 | export * from './misc' | 3 | export * from './misc' |
4 | export * from './signature' | 4 | export * from './signature' |
diff --git a/server/helpers/custom-validators/activitypub/undo.ts b/server/helpers/custom-validators/activitypub/undo.ts index 58043f8a1..d07bbf6b7 100644 --- a/server/helpers/custom-validators/activitypub/undo.ts +++ b/server/helpers/custom-validators/activitypub/undo.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { isAccountFollowActivityValid } from './account' | 1 | import { isAccountFollowActivityValid } from './actor' |
2 | import { isBaseActivityValid } from './misc' | 2 | import { isBaseActivityValid } from './misc' |
3 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' | 3 | import { isDislikeActivityValid, isLikeActivityValid } from './rate' |
4 | 4 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index ff322730f..f209bef90 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -131,7 +131,7 @@ const CONSTRAINTS_FIELDS = { | |||
131 | FILE_SIZE: { min: 10 }, | 131 | FILE_SIZE: { min: 10 }, |
132 | URL: { min: 3, max: 2000 } // Length | 132 | URL: { min: 3, max: 2000 } // Length |
133 | }, | 133 | }, |
134 | ACCOUNTS: { | 134 | ACTOR: { |
135 | PUBLIC_KEY: { min: 10, max: 5000 }, // Length | 135 | PUBLIC_KEY: { min: 10, max: 5000 }, // Length |
136 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length | 136 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length |
137 | URL: { min: 3, max: 2000 } // Length | 137 | URL: { min: 3, max: 2000 } // Length |
diff --git a/server/lib/user.ts b/server/lib/user.ts index c4722fae2..6aeb198b9 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -3,6 +3,7 @@ import { createPrivateAndPublicKeys, logger } from '../helpers' | |||
3 | import { CONFIG, sequelizeTypescript } from '../initializers' | 3 | import { CONFIG, sequelizeTypescript } from '../initializers' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { ActorModel } from '../models/activitypub/actor' | ||
6 | import { getAccountActivityPubUrl } from './activitypub' | 7 | import { getAccountActivityPubUrl } from './activitypub' |
7 | import { createVideoChannel } from './video-channel' | 8 | import { createVideoChannel } from './video-channel' |
8 | 9 | ||
@@ -27,9 +28,10 @@ async function createUserAccountAndChannel (user: UserModel, validateUser = true | |||
27 | 28 | ||
28 | // Set account keys, this could be long so process after the account creation and do not block the client | 29 | // Set account keys, this could be long so process after the account creation and do not block the client |
29 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() | 30 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() |
30 | account.set('publicKey', publicKey) | 31 | const actor = account.Actor |
31 | account.set('privateKey', privateKey) | 32 | actor.set('publicKey', publicKey) |
32 | account.save().catch(err => logger.error('Cannot set public/private keys of local account %d.', account.id, err)) | 33 | actor.set('privateKey', privateKey) |
34 | actor.save().catch(err => logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err)) | ||
33 | 35 | ||
34 | return { account, videoChannel } | 36 | return { account, videoChannel } |
35 | } | 37 | } |
@@ -37,8 +39,7 @@ async function createUserAccountAndChannel (user: UserModel, validateUser = true | |||
37 | async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { | 39 | async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) { |
38 | const url = getAccountActivityPubUrl(name) | 40 | const url = getAccountActivityPubUrl(name) |
39 | 41 | ||
40 | const accountInstance = new AccountModel({ | 42 | const actorInstance = new ActorModel({ |
41 | name, | ||
42 | url, | 43 | url, |
43 | publicKey: null, | 44 | publicKey: null, |
44 | privateKey: null, | 45 | privateKey: null, |
@@ -48,13 +49,22 @@ async function createLocalAccountWithoutKeys (name: string, userId: number, appl | |||
48 | outboxUrl: url + '/outbox', | 49 | outboxUrl: url + '/outbox', |
49 | sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox', | 50 | sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox', |
50 | followersUrl: url + '/followers', | 51 | followersUrl: url + '/followers', |
51 | followingUrl: url + '/following', | 52 | followingUrl: url + '/following' |
53 | }) | ||
54 | const actorInstanceCreated = await actorInstance.save({ transaction: t }) | ||
55 | |||
56 | const accountInstance = new AccountModel({ | ||
57 | name, | ||
52 | userId, | 58 | userId, |
53 | applicationId, | 59 | applicationId, |
60 | actorId: actorInstanceCreated.id, | ||
54 | serverId: null // It is our server | 61 | serverId: null // It is our server |
55 | }) | 62 | }) |
56 | 63 | ||
57 | return accountInstance.save({ transaction: t }) | 64 | const accountInstanceCreated = await accountInstance.save({ transaction: t }) |
65 | accountInstanceCreated.Actor = actorInstanceCreated | ||
66 | |||
67 | return accountInstanceCreated | ||
58 | } | 68 | } |
59 | 69 | ||
60 | // --------------------------------------------------------------------------- | 70 | // --------------------------------------------------------------------------- |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index d6758fa10..b26395fd4 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | import { join } from 'path' | ||
2 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
3 | import { | 2 | import { |
4 | AfterDestroy, | 3 | AfterDestroy, |
@@ -16,24 +15,13 @@ import { | |||
16 | Table, | 15 | Table, |
17 | UpdatedAt | 16 | UpdatedAt |
18 | } from 'sequelize-typescript' | 17 | } from 'sequelize-typescript' |
19 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | ||
20 | import { activityPubContextify } from '../../helpers' | ||
21 | import { | ||
22 | isAccountFollowersCountValid, | ||
23 | isAccountFollowingCountValid, | ||
24 | isAccountPrivateKeyValid, | ||
25 | isAccountPublicKeyValid, | ||
26 | isActivityPubUrlValid | ||
27 | } from '../../helpers/custom-validators/activitypub' | ||
28 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | 18 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' |
29 | import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | ||
30 | import { sendDeleteAccount } from '../../lib/activitypub/send' | 19 | import { sendDeleteAccount } from '../../lib/activitypub/send' |
20 | import { ActorModel } from '../activitypub/actor' | ||
31 | import { ApplicationModel } from '../application/application' | 21 | import { ApplicationModel } from '../application/application' |
32 | import { AvatarModel } from '../avatar/avatar' | ||
33 | import { ServerModel } from '../server/server' | 22 | import { ServerModel } from '../server/server' |
34 | import { throwIfNotValid } from '../utils' | 23 | import { throwIfNotValid } from '../utils' |
35 | import { VideoChannelModel } from '../video/video-channel' | 24 | import { VideoChannelModel } from '../video/video-channel' |
36 | import { AccountFollowModel } from './account-follow' | ||
37 | import { UserModel } from './user' | 25 | import { UserModel } from './user' |
38 | 26 | ||
39 | @Table({ | 27 | @Table({ |
@@ -59,68 +47,7 @@ import { UserModel } from './user' | |||
59 | } | 47 | } |
60 | ] | 48 | ] |
61 | }) | 49 | }) |
62 | export class AccountModel extends Model<Account> { | 50 | export class AccountModel extends Model<AccountModel> { |
63 | |||
64 | @AllowNull(false) | ||
65 | @Default(DataType.UUIDV4) | ||
66 | @IsUUID(4) | ||
67 | @Column(DataType.UUID) | ||
68 | uuid: string | ||
69 | |||
70 | @AllowNull(false) | ||
71 | @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name')) | ||
72 | @Column | ||
73 | name: string | ||
74 | |||
75 | @AllowNull(false) | ||
76 | @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
77 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
78 | url: string | ||
79 | |||
80 | @AllowNull(true) | ||
81 | @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key')) | ||
82 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max)) | ||
83 | publicKey: string | ||
84 | |||
85 | @AllowNull(true) | ||
86 | @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key')) | ||
87 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max)) | ||
88 | privateKey: string | ||
89 | |||
90 | @AllowNull(false) | ||
91 | @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count')) | ||
92 | @Column | ||
93 | followersCount: number | ||
94 | |||
95 | @AllowNull(false) | ||
96 | @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count')) | ||
97 | @Column | ||
98 | followingCount: number | ||
99 | |||
100 | @AllowNull(false) | ||
101 | @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) | ||
102 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
103 | inboxUrl: string | ||
104 | |||
105 | @AllowNull(false) | ||
106 | @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) | ||
107 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
108 | outboxUrl: string | ||
109 | |||
110 | @AllowNull(false) | ||
111 | @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url')) | ||
112 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
113 | sharedInboxUrl: string | ||
114 | |||
115 | @AllowNull(false) | ||
116 | @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) | ||
117 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
118 | followersUrl: string | ||
119 | |||
120 | @AllowNull(false) | ||
121 | @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) | ||
122 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max)) | ||
123 | followingUrl: string | ||
124 | 51 | ||
125 | @CreatedAt | 52 | @CreatedAt |
126 | createdAt: Date | 53 | createdAt: Date |
@@ -128,29 +55,17 @@ export class AccountModel extends Model<Account> { | |||
128 | @UpdatedAt | 55 | @UpdatedAt |
129 | updatedAt: Date | 56 | updatedAt: Date |
130 | 57 | ||
131 | @ForeignKey(() => AvatarModel) | 58 | @ForeignKey(() => ActorModel) |
132 | @Column | ||
133 | avatarId: number | ||
134 | |||
135 | @BelongsTo(() => AvatarModel, { | ||
136 | foreignKey: { | ||
137 | allowNull: true | ||
138 | }, | ||
139 | onDelete: 'cascade' | ||
140 | }) | ||
141 | Avatar: AvatarModel | ||
142 | |||
143 | @ForeignKey(() => ServerModel) | ||
144 | @Column | 59 | @Column |
145 | serverId: number | 60 | actorId: number |
146 | 61 | ||
147 | @BelongsTo(() => ServerModel, { | 62 | @BelongsTo(() => ActorModel, { |
148 | foreignKey: { | 63 | foreignKey: { |
149 | allowNull: true | 64 | allowNull: false |
150 | }, | 65 | }, |
151 | onDelete: 'cascade' | 66 | onDelete: 'cascade' |
152 | }) | 67 | }) |
153 | Server: ServerModel | 68 | Actor: ActorModel |
154 | 69 | ||
155 | @ForeignKey(() => UserModel) | 70 | @ForeignKey(() => UserModel) |
156 | @Column | 71 | @Column |
@@ -185,25 +100,6 @@ export class AccountModel extends Model<Account> { | |||
185 | }) | 100 | }) |
186 | VideoChannels: VideoChannelModel[] | 101 | VideoChannels: VideoChannelModel[] |
187 | 102 | ||
188 | @HasMany(() => AccountFollowModel, { | ||
189 | foreignKey: { | ||
190 | name: 'accountId', | ||
191 | allowNull: false | ||
192 | }, | ||
193 | onDelete: 'cascade' | ||
194 | }) | ||
195 | AccountFollowing: AccountFollowModel[] | ||
196 | |||
197 | @HasMany(() => AccountFollowModel, { | ||
198 | foreignKey: { | ||
199 | name: 'targetAccountId', | ||
200 | allowNull: false | ||
201 | }, | ||
202 | as: 'followers', | ||
203 | onDelete: 'cascade' | ||
204 | }) | ||
205 | AccountFollowers: AccountFollowModel[] | ||
206 | |||
207 | @AfterDestroy | 103 | @AfterDestroy |
208 | static sendDeleteIfOwned (instance: AccountModel) { | 104 | static sendDeleteIfOwned (instance: AccountModel) { |
209 | if (instance.isOwned()) { | 105 | if (instance.isOwned()) { |
@@ -281,9 +177,15 @@ export class AccountModel extends Model<Account> { | |||
281 | 177 | ||
282 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 178 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { |
283 | const query = { | 179 | const query = { |
284 | where: { | 180 | include: [ |
285 | url | 181 | { |
286 | }, | 182 | model: ActorModel, |
183 | required: true, | ||
184 | where: { | ||
185 | url | ||
186 | } | ||
187 | } | ||
188 | ], | ||
287 | transaction | 189 | transaction |
288 | } | 190 | } |
289 | 191 | ||
@@ -292,11 +194,17 @@ export class AccountModel extends Model<Account> { | |||
292 | 194 | ||
293 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | 195 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { |
294 | const query = { | 196 | const query = { |
295 | where: { | 197 | include: [ |
296 | followersUrl: { | 198 | { |
297 | [ Sequelize.Op.in ]: followersUrls | 199 | model: ActorModel, |
200 | required: true, | ||
201 | where: { | ||
202 | followersUrl: { | ||
203 | [ Sequelize.Op.in ]: followersUrls | ||
204 | } | ||
205 | } | ||
298 | } | 206 | } |
299 | }, | 207 | ], |
300 | transaction | 208 | transaction |
301 | } | 209 | } |
302 | 210 | ||
@@ -304,97 +212,21 @@ export class AccountModel extends Model<Account> { | |||
304 | } | 212 | } |
305 | 213 | ||
306 | toFormattedJSON () { | 214 | toFormattedJSON () { |
307 | let host = CONFIG.WEBSERVER.HOST | 215 | const actor = this.Actor.toFormattedJSON() |
308 | let score: number | 216 | const account = { |
309 | let avatar: Avatar = null | ||
310 | |||
311 | if (this.Avatar) { | ||
312 | avatar = { | ||
313 | path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), | ||
314 | createdAt: this.Avatar.createdAt, | ||
315 | updatedAt: this.Avatar.updatedAt | ||
316 | } | ||
317 | } | ||
318 | |||
319 | if (this.Server) { | ||
320 | host = this.Server.host | ||
321 | score = this.Server.score | ||
322 | } | ||
323 | |||
324 | return { | ||
325 | id: this.id, | 217 | id: this.id, |
326 | uuid: this.uuid, | ||
327 | host, | ||
328 | score, | ||
329 | name: this.name, | ||
330 | followingCount: this.followingCount, | ||
331 | followersCount: this.followersCount, | ||
332 | createdAt: this.createdAt, | 218 | createdAt: this.createdAt, |
333 | updatedAt: this.updatedAt, | 219 | updatedAt: this.updatedAt |
334 | avatar | ||
335 | } | 220 | } |
221 | |||
222 | return Object.assign(actor, account) | ||
336 | } | 223 | } |
337 | 224 | ||
338 | toActivityPubObject () { | 225 | toActivityPubObject () { |
339 | const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' | 226 | return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account') |
340 | |||
341 | const json = { | ||
342 | type, | ||
343 | id: this.url, | ||
344 | following: this.getFollowingUrl(), | ||
345 | followers: this.getFollowersUrl(), | ||
346 | inbox: this.inboxUrl, | ||
347 | outbox: this.outboxUrl, | ||
348 | preferredUsername: this.name, | ||
349 | url: this.url, | ||
350 | name: this.name, | ||
351 | endpoints: { | ||
352 | sharedInbox: this.sharedInboxUrl | ||
353 | }, | ||
354 | uuid: this.uuid, | ||
355 | publicKey: { | ||
356 | id: this.getPublicKeyUrl(), | ||
357 | owner: this.url, | ||
358 | publicKeyPem: this.publicKey | ||
359 | } | ||
360 | } | ||
361 | |||
362 | return activityPubContextify(json) | ||
363 | } | 227 | } |
364 | 228 | ||
365 | isOwned () { | 229 | isOwned () { |
366 | return this.serverId === null | 230 | return this.Actor.isOwned() |
367 | } | ||
368 | |||
369 | getFollowerSharedInboxUrls (t: Sequelize.Transaction) { | ||
370 | const query = { | ||
371 | attributes: [ 'sharedInboxUrl' ], | ||
372 | include: [ | ||
373 | { | ||
374 | model: AccountFollowModel, | ||
375 | required: true, | ||
376 | as: 'followers', | ||
377 | where: { | ||
378 | targetAccountId: this.id | ||
379 | } | ||
380 | } | ||
381 | ], | ||
382 | transaction: t | ||
383 | } | ||
384 | |||
385 | return AccountModel.findAll(query) | ||
386 | .then(accounts => accounts.map(a => a.sharedInboxUrl)) | ||
387 | } | ||
388 | |||
389 | getFollowingUrl () { | ||
390 | return this.url + '/following' | ||
391 | } | ||
392 | |||
393 | getFollowersUrl () { | ||
394 | return this.url + '/followers' | ||
395 | } | ||
396 | |||
397 | getPublicKeyUrl () { | ||
398 | return this.url + '#main-key' | ||
399 | } | 231 | } |
400 | } | 232 | } |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts new file mode 100644 index 000000000..4cae6a6ec --- /dev/null +++ b/server/models/activitypub/actor.ts | |||
@@ -0,0 +1,245 @@ | |||
1 | import { join } from 'path' | ||
2 | import * as Sequelize from 'sequelize' | ||
3 | import { | ||
4 | AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table, | ||
5 | UpdatedAt | ||
6 | } from 'sequelize-typescript' | ||
7 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | ||
8 | import { activityPubContextify } from '../../helpers' | ||
9 | import { | ||
10 | isActivityPubUrlValid, | ||
11 | isActorFollowersCountValid, | ||
12 | isActorFollowingCountValid, isActorPreferredUsernameValid, | ||
13 | isActorPrivateKeyValid, | ||
14 | isActorPublicKeyValid | ||
15 | } from '../../helpers/custom-validators/activitypub' | ||
16 | import { isUserUsernameValid } from '../../helpers/custom-validators/users' | ||
17 | import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | ||
18 | import { AccountFollowModel } from '../account/account-follow' | ||
19 | import { AvatarModel } from '../avatar/avatar' | ||
20 | import { ServerModel } from '../server/server' | ||
21 | import { throwIfNotValid } from '../utils' | ||
22 | |||
23 | @Table({ | ||
24 | tableName: 'actor' | ||
25 | }) | ||
26 | export class ActorModel extends Model<ActorModel> { | ||
27 | |||
28 | @AllowNull(false) | ||
29 | @Default(DataType.UUIDV4) | ||
30 | @IsUUID(4) | ||
31 | @Column(DataType.UUID) | ||
32 | uuid: string | ||
33 | |||
34 | @AllowNull(false) | ||
35 | @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name')) | ||
36 | @Column | ||
37 | name: string | ||
38 | |||
39 | @AllowNull(false) | ||
40 | @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
41 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
42 | url: string | ||
43 | |||
44 | @AllowNull(true) | ||
45 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key')) | ||
46 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY.max)) | ||
47 | publicKey: string | ||
48 | |||
49 | @AllowNull(true) | ||
50 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key')) | ||
51 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY.max)) | ||
52 | privateKey: string | ||
53 | |||
54 | @AllowNull(false) | ||
55 | @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowersCountValid, 'followers count')) | ||
56 | @Column | ||
57 | followersCount: number | ||
58 | |||
59 | @AllowNull(false) | ||
60 | @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowingCountValid, 'following count')) | ||
61 | @Column | ||
62 | followingCount: number | ||
63 | |||
64 | @AllowNull(false) | ||
65 | @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) | ||
66 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
67 | inboxUrl: string | ||
68 | |||
69 | @AllowNull(false) | ||
70 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) | ||
71 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
72 | outboxUrl: string | ||
73 | |||
74 | @AllowNull(false) | ||
75 | @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url')) | ||
76 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
77 | sharedInboxUrl: string | ||
78 | |||
79 | @AllowNull(false) | ||
80 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) | ||
81 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
82 | followersUrl: string | ||
83 | |||
84 | @AllowNull(false) | ||
85 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) | ||
86 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) | ||
87 | followingUrl: string | ||
88 | |||
89 | @CreatedAt | ||
90 | createdAt: Date | ||
91 | |||
92 | @UpdatedAt | ||
93 | updatedAt: Date | ||
94 | |||
95 | @ForeignKey(() => AvatarModel) | ||
96 | @Column | ||
97 | avatarId: number | ||
98 | |||
99 | @BelongsTo(() => AvatarModel, { | ||
100 | foreignKey: { | ||
101 | allowNull: true | ||
102 | }, | ||
103 | onDelete: 'cascade' | ||
104 | }) | ||
105 | Avatar: AvatarModel | ||
106 | |||
107 | @HasMany(() => AccountFollowModel, { | ||
108 | foreignKey: { | ||
109 | name: 'accountId', | ||
110 | allowNull: false | ||
111 | }, | ||
112 | onDelete: 'cascade' | ||
113 | }) | ||
114 | AccountFollowing: AccountFollowModel[] | ||
115 | |||
116 | @HasMany(() => AccountFollowModel, { | ||
117 | foreignKey: { | ||
118 | name: 'targetAccountId', | ||
119 | allowNull: false | ||
120 | }, | ||
121 | as: 'followers', | ||
122 | onDelete: 'cascade' | ||
123 | }) | ||
124 | AccountFollowers: AccountFollowModel[] | ||
125 | |||
126 | @ForeignKey(() => ServerModel) | ||
127 | @Column | ||
128 | serverId: number | ||
129 | |||
130 | @BelongsTo(() => ServerModel, { | ||
131 | foreignKey: { | ||
132 | allowNull: true | ||
133 | }, | ||
134 | onDelete: 'cascade' | ||
135 | }) | ||
136 | Server: ServerModel | ||
137 | |||
138 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | ||
139 | const query = { | ||
140 | where: { | ||
141 | followersUrl: { | ||
142 | [ Sequelize.Op.in ]: followersUrls | ||
143 | } | ||
144 | }, | ||
145 | transaction | ||
146 | } | ||
147 | |||
148 | return ActorModel.findAll(query) | ||
149 | } | ||
150 | |||
151 | toFormattedJSON () { | ||
152 | let avatar: Avatar = null | ||
153 | if (this.Avatar) { | ||
154 | avatar = { | ||
155 | path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), | ||
156 | createdAt: this.Avatar.createdAt, | ||
157 | updatedAt: this.Avatar.updatedAt | ||
158 | } | ||
159 | } | ||
160 | |||
161 | let host = CONFIG.WEBSERVER.HOST | ||
162 | let score: number | ||
163 | if (this.Server) { | ||
164 | host = this.Server.host | ||
165 | score = this.Server.score | ||
166 | } | ||
167 | |||
168 | return { | ||
169 | id: this.id, | ||
170 | host, | ||
171 | score, | ||
172 | followingCount: this.followingCount, | ||
173 | followersCount: this.followersCount, | ||
174 | avatar | ||
175 | } | ||
176 | } | ||
177 | |||
178 | toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') { | ||
179 | let activityPubType | ||
180 | if (type === 'Account') { | ||
181 | activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' | ||
182 | } else { // VideoChannel | ||
183 | activityPubType = 'Group' | ||
184 | } | ||
185 | |||
186 | const json = { | ||
187 | type, | ||
188 | id: this.url, | ||
189 | following: this.getFollowingUrl(), | ||
190 | followers: this.getFollowersUrl(), | ||
191 | inbox: this.inboxUrl, | ||
192 | outbox: this.outboxUrl, | ||
193 | preferredUsername: name, | ||
194 | url: this.url, | ||
195 | name, | ||
196 | endpoints: { | ||
197 | sharedInbox: this.sharedInboxUrl | ||
198 | }, | ||
199 | uuid, | ||
200 | publicKey: { | ||
201 | id: this.getPublicKeyUrl(), | ||
202 | owner: this.url, | ||
203 | publicKeyPem: this.publicKey | ||
204 | } | ||
205 | } | ||
206 | |||
207 | return activityPubContextify(json) | ||
208 | } | ||
209 | |||
210 | getFollowerSharedInboxUrls (t: Sequelize.Transaction) { | ||
211 | const query = { | ||
212 | attributes: [ 'sharedInboxUrl' ], | ||
213 | include: [ | ||
214 | { | ||
215 | model: AccountFollowModel, | ||
216 | required: true, | ||
217 | as: 'followers', | ||
218 | where: { | ||
219 | targetAccountId: this.id | ||
220 | } | ||
221 | } | ||
222 | ], | ||
223 | transaction: t | ||
224 | } | ||
225 | |||
226 | return ActorModel.findAll(query) | ||
227 | .then(accounts => accounts.map(a => a.sharedInboxUrl)) | ||
228 | } | ||
229 | |||
230 | getFollowingUrl () { | ||
231 | return this.url + '/following' | ||
232 | } | ||
233 | |||
234 | getFollowersUrl () { | ||
235 | return this.url + '/followers' | ||
236 | } | ||
237 | |||
238 | getPublicKeyUrl () { | ||
239 | return this.url + '#main-key' | ||
240 | } | ||
241 | |||
242 | isOwned () { | ||
243 | return this.serverId === null | ||
244 | } | ||
245 | } | ||
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 068c8029d..fe44d3d53 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -11,18 +11,16 @@ import { | |||
11 | HasMany, | 11 | HasMany, |
12 | Is, | 12 | Is, |
13 | IsUUID, | 13 | IsUUID, |
14 | Model, Scopes, | 14 | Model, |
15 | Scopes, | ||
15 | Table, | 16 | Table, |
16 | UpdatedAt | 17 | UpdatedAt |
17 | } from 'sequelize-typescript' | 18 | } from 'sequelize-typescript' |
18 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' | 19 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' |
19 | import { activityPubCollection } from '../../helpers' | ||
20 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' | ||
21 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' | 20 | import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels' |
22 | import { CONSTRAINTS_FIELDS } from '../../initializers' | ||
23 | import { getAnnounceActivityPubUrl } from '../../lib/activitypub' | ||
24 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send' | 21 | import { sendDeleteVideoChannel } from '../../lib/activitypub/send' |
25 | import { AccountModel } from '../account/account' | 22 | import { AccountModel } from '../account/account' |
23 | import { ActorModel } from '../activitypub/actor' | ||
26 | import { ServerModel } from '../server/server' | 24 | import { ServerModel } from '../server/server' |
27 | import { getSort, throwIfNotValid } from '../utils' | 25 | import { getSort, throwIfNotValid } from '../utils' |
28 | import { VideoModel } from './video' | 26 | import { VideoModel } from './video' |
@@ -78,17 +76,24 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
78 | @Column | 76 | @Column |
79 | remote: boolean | 77 | remote: boolean |
80 | 78 | ||
81 | @AllowNull(false) | ||
82 | @Is('VideoChannelUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
83 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max)) | ||
84 | url: string | ||
85 | |||
86 | @CreatedAt | 79 | @CreatedAt |
87 | createdAt: Date | 80 | createdAt: Date |
88 | 81 | ||
89 | @UpdatedAt | 82 | @UpdatedAt |
90 | updatedAt: Date | 83 | updatedAt: Date |
91 | 84 | ||
85 | @ForeignKey(() => ActorModel) | ||
86 | @Column | ||
87 | actorId: number | ||
88 | |||
89 | @BelongsTo(() => ActorModel, { | ||
90 | foreignKey: { | ||
91 | allowNull: false | ||
92 | }, | ||
93 | onDelete: 'cascade' | ||
94 | }) | ||
95 | Actor: ActorModel | ||
96 | |||
92 | @ForeignKey(() => AccountModel) | 97 | @ForeignKey(() => AccountModel) |
93 | @Column | 98 | @Column |
94 | accountId: number | 99 | accountId: number |
@@ -174,9 +179,15 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
174 | 179 | ||
175 | static loadByUrl (url: string, t?: Sequelize.Transaction) { | 180 | static loadByUrl (url: string, t?: Sequelize.Transaction) { |
176 | const query: IFindOptions<VideoChannelModel> = { | 181 | const query: IFindOptions<VideoChannelModel> = { |
177 | where: { | 182 | include: [ |
178 | url | 183 | { |
179 | } | 184 | model: ActorModel, |
185 | required: true, | ||
186 | where: { | ||
187 | url | ||
188 | } | ||
189 | } | ||
190 | ] | ||
180 | } | 191 | } |
181 | 192 | ||
182 | if (t !== undefined) query.transaction = t | 193 | if (t !== undefined) query.transaction = t |
@@ -264,27 +275,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
264 | } | 275 | } |
265 | 276 | ||
266 | toActivityPubObject () { | 277 | toActivityPubObject () { |
267 | let sharesObject | 278 | return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel') |
268 | if (Array.isArray(this.VideoChannelShares)) { | ||
269 | const shares: string[] = [] | ||
270 | |||
271 | for (const videoChannelShare of this.VideoChannelShares) { | ||
272 | const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account) | ||
273 | shares.push(shareUrl) | ||
274 | } | ||
275 | |||
276 | sharesObject = activityPubCollection(shares) | ||
277 | } | ||
278 | |||
279 | return { | ||
280 | type: 'VideoChannel' as 'VideoChannel', | ||
281 | id: this.url, | ||
282 | uuid: this.uuid, | ||
283 | content: this.description, | ||
284 | name: this.name, | ||
285 | published: this.createdAt.toISOString(), | ||
286 | updated: this.updatedAt.toISOString(), | ||
287 | shares: sharesObject | ||
288 | } | ||
289 | } | 279 | } |
290 | } | 280 | } |
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts index 77489135c..05b911d81 100644 --- a/shared/models/activitypub/activitypub-actor.ts +++ b/shared/models/activitypub/activitypub-actor.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | export interface ActivityPubActor { | 1 | export interface ActivityPubActor { |
2 | '@context': any[] | 2 | '@context': any[] |
3 | type: 'Person' | 'Application' | 3 | type: 'Person' | 'Application' | 'Group' |
4 | id: string | 4 | id: string |
5 | following: string | 5 | following: string |
6 | followers: string | 6 | followers: string |