]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/models/account/account.ts
Put activity pub sends inside transactions
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
index daf8f47035c009a581496577bbe9a48183bcaf32..61a88524c2d39662557d9ba524450f01b0566981 100644 (file)
@@ -1,42 +1,26 @@
 import * as Sequelize from 'sequelize'
-
 import {
-  isUserUsernameValid,
-  isAccountPublicKeyValid,
-  isAccountUrlValid,
-  isAccountPrivateKeyValid,
+  activityPubContextify,
   isAccountFollowersCountValid,
   isAccountFollowingCountValid,
-  isAccountInboxValid,
-  isAccountOutboxValid,
-  isAccountSharedInboxValid,
-  isAccountFollowersValid,
-  isAccountFollowingValid,
-  activityPubContextify
+  isAccountPrivateKeyValid,
+  isAccountPublicKeyValid,
+  isUserUsernameValid
 } from '../../helpers'
-
-import { addMethodsToModel, getSort } from '../utils'
-import {
-  AccountInstance,
-  AccountAttributes,
-
-  AccountMethods
-} from './account-interface'
-import LoadApplication = AccountMethods.LoadApplication
-import { sendDeleteAccount } from '../../lib/activitypub/send-request'
+import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
+import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
+import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
+import { addMethodsToModel } from '../utils'
+import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface'
 
 let Account: Sequelize.Model<AccountInstance, AccountAttributes>
-let loadAccountByPodAndUUID: AccountMethods.LoadAccountByPodAndUUID
 let load: AccountMethods.Load
 let loadApplication: AccountMethods.LoadApplication
 let loadByUUID: AccountMethods.LoadByUUID
 let loadByUrl: AccountMethods.LoadByUrl
-let loadLocalAccountByNameAndPod: AccountMethods.LoadLocalAccountByNameAndPod
-let listOwned: AccountMethods.ListOwned
-let listFollowerUrlsForApi: AccountMethods.ListFollowerUrlsForApi
-let listFollowingUrlsForApi: AccountMethods.ListFollowingUrlsForApi
-let listFollowingForApi: AccountMethods.ListFollowingForApi
-let listFollowersForApi: AccountMethods.ListFollowersForApi
+let loadLocalByName: AccountMethods.LoadLocalByName
+let loadByNameAndHost: AccountMethods.LoadByNameAndHost
+let listByFollowersUrls: AccountMethods.ListByFollowersUrls
 let isOwned: AccountMethods.IsOwned
 let toActivityPubObject: AccountMethods.ToActivityPubObject
 let toFormattedJSON: AccountMethods.ToFormattedJSON
@@ -60,25 +44,25 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         type: DataTypes.STRING,
         allowNull: false,
         validate: {
-          usernameValid: value => {
+          nameValid: value => {
             const res = isUserUsernameValid(value)
-            if (res === false) throw new Error('Username is not valid.')
+            if (res === false) throw new Error('Name is not valid.')
           }
         }
       },
       url: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           urlValid: value => {
-            const res = isAccountUrlValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('URL is not valid.')
           }
         }
       },
       publicKey: {
-        type: DataTypes.STRING,
-        allowNull: false,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max),
+        allowNull: true,
         validate: {
           publicKeyValid: value => {
             const res = isAccountPublicKeyValid(value)
@@ -87,8 +71,8 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         }
       },
       privateKey: {
-        type: DataTypes.STRING,
-        allowNull: false,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max),
+        allowNull: true,
         validate: {
           privateKeyValid: value => {
             const res = isAccountPrivateKeyValid(value)
@@ -110,58 +94,58 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
         type: DataTypes.INTEGER,
         allowNull: false,
         validate: {
-          followersCountValid: value => {
+          followingCountValid: value => {
             const res = isAccountFollowingCountValid(value)
             if (res === false) throw new Error('Following count is not valid.')
           }
         }
       },
       inboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           inboxUrlValid: value => {
-            const res = isAccountInboxValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('Inbox URL is not valid.')
           }
         }
       },
       outboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           outboxUrlValid: value => {
-            const res = isAccountOutboxValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('Outbox URL is not valid.')
           }
         }
       },
       sharedInboxUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           sharedInboxUrlValid: value => {
-            const res = isAccountSharedInboxValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('Shared inbox URL is not valid.')
           }
         }
       },
       followersUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           followersUrlValid: value => {
-            const res = isAccountFollowersValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('Followers URL is not valid.')
           }
         }
       },
       followingUrl: {
-        type: DataTypes.STRING,
+        type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max),
         allowNull: false,
         validate: {
           followingUrlValid: value => {
-            const res = isAccountFollowingValid(value)
+            const res = isActivityPubUrlValid(value)
             if (res === false) throw new Error('Following URL is not valid.')
           }
         }
@@ -173,7 +157,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
           fields: [ 'name' ]
         },
         {
-          fields: [ 'podId' ]
+          fields: [ 'serverId' ]
         },
         {
           fields: [ 'userId' ],
@@ -184,7 +168,7 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
           unique: true
         },
         {
-          fields: [ 'name', 'podId' ],
+          fields: [ 'name', 'serverId', 'applicationId' ],
           unique: true
         }
       ],
@@ -194,16 +178,13 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
 
   const classMethods = [
     associate,
-    loadAccountByPodAndUUID,
     loadApplication,
     load,
     loadByUUID,
-    loadLocalAccountByNameAndPod,
-    listOwned,
-    listFollowerUrlsForApi,
-    listFollowingUrlsForApi,
-    listFollowingForApi,
-    listFollowersForApi
+    loadByUrl,
+    loadLocalByName,
+    loadByNameAndHost,
+    listByFollowersUrls
   ]
   const instanceMethods = [
     isOwned,
@@ -222,9 +203,9 @@ export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes
 // ---------------------------------------------------------------------------
 
 function associate (models) {
-  Account.belongsTo(models.Pod, {
+  Account.belongsTo(models.Server, {
     foreignKey: {
-      name: 'podId',
+      name: 'serverId',
       allowNull: true
     },
     onDelete: 'cascade'
@@ -240,7 +221,7 @@ function associate (models) {
 
   Account.belongsTo(models.Application, {
     foreignKey: {
-      name: 'userId',
+      name: 'applicationId',
       allowNull: true
     },
     onDelete: 'cascade'
@@ -255,16 +236,15 @@ function associate (models) {
     hooks: true
   })
 
-  Account.hasMany(models.AccountFollower, {
+  Account.hasMany(models.AccountFollow, {
     foreignKey: {
       name: 'accountId',
       allowNull: false
     },
-    as: 'following',
     onDelete: 'cascade'
   })
 
-  Account.hasMany(models.AccountFollower, {
+  Account.hasMany(models.AccountFollow, {
     foreignKey: {
       name: 'targetAccountId',
       allowNull: false
@@ -283,17 +263,28 @@ function afterDestroy (account: AccountInstance) {
 }
 
 toFormattedJSON = function (this: AccountInstance) {
+  let host = CONFIG.WEBSERVER.HOST
+  let score: number
+
+  if (this.Server) {
+    host = this.Server.host
+    score = this.Server.score as number
+  }
+
   const json = {
     id: this.id,
-    host: this.Pod.host,
-    name: this.name
+    host,
+    score,
+    name: this.name,
+    createdAt: this.createdAt,
+    updatedAt: this.updatedAt
   }
 
   return json
 }
 
 toActivityPubObject = function (this: AccountInstance) {
-  const type = this.podId ? 'Application' as 'Application' : 'Person' as 'Person'
+  const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
 
   const json = {
     type,
@@ -320,20 +311,23 @@ toActivityPubObject = function (this: AccountInstance) {
 }
 
 isOwned = function (this: AccountInstance) {
-  return this.podId === null
+  return this.serverId === null
 }
 
-getFollowerSharedInboxUrls = function (this: AccountInstance) {
+getFollowerSharedInboxUrls = function (this: AccountInstance, t: Sequelize.Transaction) {
   const query: Sequelize.FindOptions<AccountAttributes> = {
     attributes: [ 'sharedInboxUrl' ],
     include: [
       {
-        model: Account['sequelize'].models.AccountFollower,
+        model: Account['sequelize'].models.AccountFollow,
+        required: true,
+        as: 'followers',
         where: {
           targetAccountId: this.id
         }
       }
-    ]
+    ],
+    transaction: t
   }
 
   return Account.findAll(query)
@@ -341,7 +335,7 @@ getFollowerSharedInboxUrls = function (this: AccountInstance) {
 }
 
 getFollowingUrl = function (this: AccountInstance) {
-  return this.url + '/followers'
+  return this.url + '/following'
 }
 
 getFollowersUrl = function (this: AccountInstance) {
@@ -354,91 +348,11 @@ getPublicKeyUrl = function (this: AccountInstance) {
 
 // ------------------------------ STATICS ------------------------------
 
-listOwned = function () {
-  const query: Sequelize.FindOptions<AccountAttributes> = {
-    where: {
-      podId: null
-    }
-  }
-
-  return Account.findAll(query)
-}
-
-listFollowerUrlsForApi = function (id: number, start: number, count?: number) {
-  return createListFollowForApiQuery('followers', id, start, count)
-}
-
-listFollowingUrlsForApi = function (id: number, start: number, count?: number) {
-  return createListFollowForApiQuery('following', id, start, count)
-}
-
-listFollowingForApi = function (id: number, start: number, count: number, sort: string) {
-  const query = {
-    distinct: true,
-    offset: start,
-    limit: count,
-    order: [ getSort(sort) ],
-    include: [
-      {
-        model: Account['sequelize'].models.AccountFollow,
-        required: true,
-        as: 'following',
-        include: [
-          {
-            model: Account['sequelize'].models.Account,
-            as: 'following',
-            required: true,
-            include: [ Account['sequelize'].models.Pod ]
-          }
-        ]
-      }
-    ]
-  }
-
-  return Account.findAndCountAll(query).then(({ rows, count }) => {
-    return {
-      data: rows,
-      total: count
-    }
-  })
-}
-
-listFollowersForApi = function (id: number, start: number, count: number, sort: string) {
-  const query = {
-    distinct: true,
-    offset: start,
-    limit: count,
-    order: [ getSort(sort) ],
-    include: [
-      {
-        model: Account['sequelize'].models.AccountFollow,
-        required: true,
-        as: 'followers',
-        include: [
-          {
-            model: Account['sequelize'].models.Account,
-            as: 'followers',
-            required: true,
-            include: [ Account['sequelize'].models.Pod ]
-          }
-        ]
-      }
-    ]
-  }
-
-  return Account.findAndCountAll(query).then(({ rows, count }) => {
-    return {
-      data: rows,
-      total: count
-    }
-  })
-}
-
 loadApplication = function () {
   return Account.findOne({
     include: [
       {
-        model: Account['sequelize'].model.Application,
+        model: Account['sequelize'].models.Application,
         required: true
       }
     ]
@@ -459,17 +373,37 @@ loadByUUID = function (uuid: string) {
   return Account.findOne(query)
 }
 
-loadLocalAccountByNameAndPod = function (name: string, host: string) {
+loadLocalByName = function (name: string) {
   const query: Sequelize.FindOptions<AccountAttributes> = {
     where: {
       name,
-      userId: {
-        [Sequelize.Op.ne]: null
-      }
+      [Sequelize.Op.or]: [
+        {
+          userId: {
+            [Sequelize.Op.ne]: null
+          }
+        },
+        {
+          applicationId: {
+            [Sequelize.Op.ne]: null
+          }
+        }
+      ]
+    }
+  }
+
+  return Account.findOne(query)
+}
+
+loadByNameAndHost = function (name: string, host: string) {
+  const query: Sequelize.FindOptions<AccountAttributes> = {
+    where: {
+      name
     },
     include: [
       {
-        model: Account['sequelize'].models.Pod,
+        model: Account['sequelize'].models.Server,
+        required: true,
         where: {
           host
         }
@@ -480,66 +414,26 @@ loadLocalAccountByNameAndPod = function (name: string, host: string) {
   return Account.findOne(query)
 }
 
-loadByUrl = function (url: string) {
+loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
   const query: Sequelize.FindOptions<AccountAttributes> = {
     where: {
       url
-    }
+    },
+    transaction
   }
 
   return Account.findOne(query)
 }
 
-loadAccountByPodAndUUID = function (uuid: string, podId: number, transaction: Sequelize.Transaction) {
+listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) {
   const query: Sequelize.FindOptions<AccountAttributes> = {
     where: {
-      podId,
-      uuid
+      followersUrl: {
+        [Sequelize.Op.in]: followersUrls
+      }
     },
     transaction
   }
 
-  return Account.find(query)
-}
-
-// ------------------------------ UTILS ------------------------------
-
-async function createListFollowForApiQuery (type: 'followers' | 'following', id: number, start: number, count?: number) {
-  let firstJoin: string
-  let secondJoin: string
-
-  if (type === 'followers') {
-    firstJoin = 'targetAccountId'
-    secondJoin = 'accountId'
-  } else {
-    firstJoin = 'accountId'
-    secondJoin = 'targetAccountId'
-  }
-
-  const selections = [ '"Followers"."url" AS "url"', 'COUNT(*) AS "total"' ]
-  const tasks: Promise<any>[] = []
-
-  for (const selection of selections) {
-    let query = 'SELECT ' + selection + ' FROM "Account" ' +
-      'INNER JOIN "AccountFollower" ON "AccountFollower"."' + firstJoin + '" = "Account"."id" ' +
-      'INNER JOIN "Account" AS "Follows" ON "Followers"."id" = "Follows"."' + secondJoin + '" ' +
-      'WHERE "Account"."id" = $id ' +
-      'LIMIT ' + start
-
-    if (count !== undefined) query += ', ' + count
-
-    const options = {
-      bind: { id },
-      type: Sequelize.QueryTypes.SELECT
-    }
-    tasks.push(Account['sequelize'].query(query, options))
-  }
-
-  const [ followers, [ { total } ]] = await Promise.all(tasks)
-  const urls: string[] = followers.map(f => f.url)
-
-  return {
-    data: urls,
-    total: parseInt(total, 10)
-  }
+  return Account.findAll(query)
 }