Add scores to follows and remove bad ones
authorChocobozzz <me@florianbigard.com>
Thu, 11 Jan 2018 08:35:50 +0000 (09:35 +0100)
committerChocobozzz <me@florianbigard.com>
Thu, 11 Jan 2018 08:35:50 +0000 (09:35 +0100)
21 files changed:
client/src/app/+admin/follows/followers-list/followers-list.component.html
client/src/app/shared/misc/utils.ts
client/src/app/videos/+video-edit/video-add.component.html
client/src/app/videos/+video-watch/video-watch.component.html
server.ts
server/initializers/constants.ts
server/initializers/migrations/0170-actor-follow-score.ts [new file with mode: 0644]
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
server/lib/schedulers/abstract-scheduler.ts [new file with mode: 0644]
server/lib/schedulers/bad-actor-follow-scheduler.ts [new file with mode: 0644]
server/models/account/account.ts
server/models/activitypub/actor-follow.ts
server/models/activitypub/actor.ts
server/models/server/server.ts
server/models/video/video-channel.ts
shared/models/actors/account.model.ts
shared/models/actors/actor.model.ts [new file with mode: 0644]
shared/models/actors/follow.model.ts
shared/models/videos/video-channel.model.ts

index 9499a04334ae36acc9d40c77f290aa6ef3e8335d..d5b1b789de748fc3bc331e69dbf65e1be3cfb73e 100644 (file)
@@ -3,8 +3,8 @@
     sortField="createdAt" (onLazyLoad)="loadLazy($event)"
 >
   <p-column field="id" header="ID" [style]="{ width: '60px' }"></p-column>
+  <p-column field="score" header="Score"></p-column>
   <p-column field="follower.host" header="Host"></p-column>
-  <p-column field="follower.score" header="Score"></p-column>
   <p-column field="state" header="State"></p-column>
   <p-column field="createdAt" header="Created date" [sortable]="true"></p-column>
 </p-dataTable>
index 2739ff81a037ec0141812ec3bc0f3e8cbb1af7af..23b46812b5a330e77abe7f83db087d7a2f10d83d 100644 (file)
@@ -31,7 +31,7 @@ function populateAsyncUserVideoChannels (authService: AuthService, channel: any[
           const videoChannels = user.videoChannels
           if (Array.isArray(videoChannels) === false) return
 
-          videoChannels.forEach(c => channel.push({ id: c.id, label: c.name }))
+          videoChannels.forEach(c => channel.push({ id: c.id, label: c.displayName }))
 
           return res()
         }
index 2040ff9d428764027b454d9165b3561b6d387be4..34291c6c66587304049f774a1cadd0640ee1eda3 100644 (file)
@@ -44,7 +44,6 @@
       [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
     ></my-video-edit>
 
-
     <div class="submit-container">
       <div *ngIf="videoUploaded === false" class="message-submit">Publish will be available when upload is finished</div>
 
index a5c387638906af7d24d3174490d166a30401abd6..5921b4b7202cfb42db9f09a9737306204b6aaefc 100644 (file)
@@ -85,7 +85,7 @@
       </div>
 
       <div class="video-info-channel">
-        {{ video.channel.name }}
+        {{ video.channel.displayName }}
         <!-- Here will be the subscribe button -->
       </div>
 
index a52c47083ed2a4e090eff74b4226c4da62c51fae..99077a1738c498c2f7076ccf87c23aaa94272b26 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -56,6 +56,7 @@ import { installApplication } from './server/initializers'
 import { activitypubHttpJobScheduler, transcodingJobScheduler } from './server/lib/jobs'
 import { VideosPreviewCache } from './server/lib/cache'
 import { apiRouter, clientsRouter, staticRouter, servicesRouter, webfingerRouter, activityPubRouter } from './server/controllers'
+import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler'
 
 // ----------- Command line -----------
 
@@ -168,6 +169,8 @@ function onDatabaseInitDone () {
       // ----------- Make the server listening -----------
       server.listen(port, () => {
         VideosPreviewCache.Instance.init(CONFIG.CACHE.PREVIEWS.SIZE)
+        BadActorFollowScheduler.Instance.enable()
+
         activitypubHttpJobScheduler.activate()
         transcodingJobScheduler.activate()
 
index c735e6dafa8cca0783b512b98c65865b1fc63802..0c139912c5eead720c2211a82a23107d13adf660 100644 (file)
@@ -9,7 +9,7 @@ import { isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 165
+const LAST_MIGRATION_VERSION = 170
 
 // ---------------------------------------------------------------------------
 
@@ -40,12 +40,12 @@ const OAUTH_LIFETIME = {
 
 // ---------------------------------------------------------------------------
 
-// Number of points we add/remove from a friend after a successful/bad request
-const SERVERS_SCORE = {
+// Number of points we add/remove after a successful/bad request
+const ACTOR_FOLLOW_SCORE = {
   PENALTY: -10,
   BONUS: 10,
-  BASE: 100,
-  MAX: 1000
+  BASE: 1000,
+  MAX: 10000
 }
 
 const FOLLOW_STATES: { [ id: string ]: FollowState } = {
@@ -76,6 +76,9 @@ const JOBS_FETCH_LIMIT_PER_CYCLE = {
 // 1 minutes
 let JOBS_FETCHING_INTERVAL = 60000
 
+// 1 hour
+let SCHEDULER_INTERVAL = 60000 * 60
+
 // ---------------------------------------------------------------------------
 
 const CONFIG = {
@@ -346,7 +349,7 @@ const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->'
 
 // Special constants for a test instance
 if (isTestInstance() === true) {
-  SERVERS_SCORE.BASE = 20
+  ACTOR_FOLLOW_SCORE.BASE = 20
   JOBS_FETCHING_INTERVAL = 1000
   REMOTE_SCHEME.HTTP = 'http'
   REMOTE_SCHEME.WS = 'ws'
@@ -354,6 +357,7 @@ if (isTestInstance() === true) {
   ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2
   ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 60 // 1 minute
   CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
+  SCHEDULER_INTERVAL = 10000
 }
 
 CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
@@ -378,7 +382,7 @@ export {
   OAUTH_LIFETIME,
   OPENGRAPH_AND_OEMBED_COMMENT,
   PAGINATION_COUNT_DEFAULT,
-  SERVERS_SCORE,
+  ACTOR_FOLLOW_SCORE,
   PREVIEWS_SIZE,
   REMOTE_SCHEME,
   FOLLOW_STATES,
@@ -396,5 +400,6 @@ export {
   VIDEO_LICENCES,
   VIDEO_RATE_TYPES,
   VIDEO_MIMETYPE_EXT,
-  AVATAR_MIMETYPE_EXT
+  AVATAR_MIMETYPE_EXT,
+  SCHEDULER_INTERVAL
 }
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts
new file mode 100644 (file)
index 0000000..2deabaf
--- /dev/null
@@ -0,0 +1,28 @@
+import * as Sequelize from 'sequelize'
+import { ACTOR_FOLLOW_SCORE } from '../index'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+  await utils.queryInterface.removeColumn('server', 'score')
+
+  const data = {
+    type: Sequelize.INTEGER,
+    allowNull: false,
+    defaultValue: ACTOR_FOLLOW_SCORE.BASE
+  }
+
+  await utils.queryInterface.addColumn('actorFollow', 'score', data)
+
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index c20a48a4efd55e4037c00da0e4bdfffbc0cba739..3f780e3193b54612b28a3694a609934fa07e0adc 100644 (file)
@@ -1,5 +1,6 @@
 import { logger } from '../../../helpers/logger'
 import { doRequest } from '../../../helpers/requests'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
 
 async function process (payload: ActivityPubHttpPayload, jobId: number) {
@@ -15,15 +16,22 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
     httpSignature: httpSignatureOptions
   }
 
+  const badUrls: string[] = []
+  const goodUrls: string[] = []
+
   for (const uri of payload.uris) {
     options.uri = uri
 
     try {
       await doRequest(options)
+      goodUrls.push(uri)
     } catch (err) {
-      await maybeRetryRequestLater(err, payload, uri)
+      const isRetryingLater = await maybeRetryRequestLater(err, payload, uri)
+      if (isRetryingLater === false) badUrls.push(uri)
     }
   }
+
+  return ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes(goodUrls, badUrls, undefined)
 }
 
 function onError (err: Error, jobId: number) {
index d576cd42ecaf6471f92d80dd54dae36c9f9ca795..884ede5a37e1d07fb94218647cdba530898f7d3f 100644 (file)
@@ -4,6 +4,7 @@ import { logger } from '../../../helpers/logger'
 import { getServerActor } from '../../../helpers/utils'
 import { ACTIVITY_PUB } from '../../../initializers'
 import { ActorModel } from '../../../models/activitypub/actor'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { JobHandler, JobScheduler } from '../job-scheduler'
 
 import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
@@ -26,7 +27,7 @@ const jobCategory: JobCategory = 'activitypub-http'
 
 const activitypubHttpJobScheduler = new JobScheduler(jobCategory, jobHandlers)
 
-function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, uri: string) {
+async function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, uri: string) {
   logger.warn('Cannot make request to %s.', uri, err)
 
   let attemptNumber = payload.attemptNumber || 1
@@ -39,8 +40,12 @@ function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, ur
       uris: [ uri ],
       attemptNumber
     })
-    return activitypubHttpJobScheduler.createJob(undefined, 'activitypubHttpUnicastHandler', newPayload)
+    await activitypubHttpJobScheduler.createJob(undefined, 'activitypubHttpUnicastHandler', newPayload)
+
+    return true
   }
+
+  return false
 }
 
 async function computeBody (payload: ActivityPubHttpPayload) {
index 175ec6642c995db6931375154a4ff6babbd2d82a..e02bd698e53bbcddcfb035db5aa80986e28a67fc 100644 (file)
@@ -1,5 +1,6 @@
 import { logger } from '../../../helpers/logger'
 import { doRequest } from '../../../helpers/requests'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { ActivityPubHttpPayload, buildSignedRequestOptions, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
 
 async function process (payload: ActivityPubHttpPayload, jobId: number) {
@@ -18,8 +19,13 @@ async function process (payload: ActivityPubHttpPayload, jobId: number) {
 
   try {
     await doRequest(options)
+    await ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([ uri ], [], undefined)
   } catch (err) {
-    await maybeRetryRequestLater(err, payload, uri)
+    const isRetryingLater = await maybeRetryRequestLater(err, payload, uri)
+    if (isRetryingLater === false) {
+      await ActorFollowModel.updateActorFollowsScoreAndRemoveBadOnes([], [ uri ], undefined)
+    }
+
     throw err
   }
 }
diff --git a/server/lib/schedulers/abstract-scheduler.ts b/server/lib/schedulers/abstract-scheduler.ts
new file mode 100644 (file)
index 0000000..473544d
--- /dev/null
@@ -0,0 +1,16 @@
+import { SCHEDULER_INTERVAL } from '../../initializers'
+
+export abstract class AbstractScheduler {
+
+  private interval: NodeJS.Timer
+
+  enable () {
+    this.interval = setInterval(() => this.execute(), SCHEDULER_INTERVAL)
+  }
+
+  disable () {
+    clearInterval(this.interval)
+  }
+
+  protected abstract execute ()
+}
diff --git a/server/lib/schedulers/bad-actor-follow-scheduler.ts b/server/lib/schedulers/bad-actor-follow-scheduler.ts
new file mode 100644 (file)
index 0000000..c6c285e
--- /dev/null
@@ -0,0 +1,24 @@
+import { logger } from '../../helpers/logger'
+import { ActorFollowModel } from '../../models/activitypub/actor-follow'
+import { AbstractScheduler } from './abstract-scheduler'
+
+export class BadActorFollowScheduler extends AbstractScheduler {
+
+  private static instance: AbstractScheduler
+
+  private constructor () {
+    super()
+  }
+
+  async execute () {
+    try {
+      await ActorFollowModel.removeBadActorFollows()
+    } catch (err) {
+      logger.error('Error in bad actor follows scheduler.', err)
+    }
+  }
+
+  static get Instance () {
+    return this.instance || (this.instance = new this())
+  }
+}
index 47336d1e017af9c1cbd82233872fb1ec74b543a5..f81c5018083d4783a6a323cd591fd5bf5ffcc3f1 100644 (file)
@@ -179,7 +179,6 @@ export class AccountModel extends Model<AccountModel> {
     const actor = this.Actor.toFormattedJSON()
     const account = {
       id: this.id,
-      name: this.Actor.preferredUsername,
       displayName: this.name,
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
index 5fcc3449dbb301c76ca1ec88c8a8db0ca9ce4935..78a65a0ff58cdaf8f937e08993cfd1c6510ad935 100644 (file)
@@ -1,8 +1,14 @@
 import * as Bluebird from 'bluebird'
 import { values } from 'lodash'
 import * as Sequelize from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import {
+  AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, IsInt, Max, Model, Table,
+  UpdatedAt
+} from 'sequelize-typescript'
 import { FollowState } from '../../../shared/models/actors'
+import { AccountFollow } from '../../../shared/models/actors/follow.model'
+import { logger } from '../../helpers/logger'
+import { ACTOR_FOLLOW_SCORE } from '../../initializers'
 import { FOLLOW_STATES } from '../../initializers/constants'
 import { ServerModel } from '../server/server'
 import { getSort } from '../utils'
@@ -20,6 +26,9 @@ import { ActorModel } from './actor'
     {
       fields: [ 'actorId', 'targetActorId' ],
       unique: true
+    },
+    {
+      fields: [ 'score' ]
     }
   ]
 })
@@ -29,6 +38,13 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
   @Column(DataType.ENUM(values(FOLLOW_STATES)))
   state: FollowState
 
+  @AllowNull(false)
+  @Default(ACTOR_FOLLOW_SCORE.BASE)
+  @IsInt
+  @Max(ACTOR_FOLLOW_SCORE.MAX)
+  @Column
+  score: number
+
   @CreatedAt
   createdAt: Date
 
@@ -63,6 +79,34 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
   })
   ActorFollowing: ActorModel
 
+  // Remove actor follows with a score of 0 (too many requests where they were unreachable)
+  static async removeBadActorFollows () {
+    const actorFollows = await ActorFollowModel.listBadActorFollows()
+
+    const actorFollowsRemovePromises = actorFollows.map(actorFollow => actorFollow.destroy())
+    await Promise.all(actorFollowsRemovePromises)
+
+    const numberOfActorFollowsRemoved = actorFollows.length
+
+    if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
+  }
+
+  static updateActorFollowsScoreAndRemoveBadOnes (goodInboxes: string[], badInboxes: string[], t: Sequelize.Transaction) {
+    if (goodInboxes.length === 0 && badInboxes.length === 0) return
+
+    logger.info('Updating %d good actor follows and %d bad actor follows scores.', goodInboxes.length, badInboxes.length)
+
+    if (goodInboxes.length !== 0) {
+      ActorFollowModel.incrementScores(goodInboxes, ACTOR_FOLLOW_SCORE.BONUS, t)
+        .catch(err => logger.error('Cannot increment scores of good actor follows.', err))
+    }
+
+    if (badInboxes.length !== 0) {
+      ActorFollowModel.incrementScores(badInboxes, ACTOR_FOLLOW_SCORE.PENALTY, t)
+        .catch(err => logger.error('Cannot decrement scores of bad actor follows.', err))
+    }
+  }
+
   static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) {
     const query = {
       where: {
@@ -260,7 +304,37 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     }
   }
 
-  toFormattedJSON () {
+  private static incrementScores (inboxUrls: string[], value: number, t: Sequelize.Transaction) {
+    const inboxUrlsString = inboxUrls.map(url => `'${url}'`).join(',')
+
+    const query = 'UPDATE "actorFollow" SET "score" = "score" +' + value + ' ' +
+      'WHERE id IN (' +
+        'SELECT "actorFollow"."id" FROM "actorFollow" ' +
+        'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."actorId" ' +
+        'WHERE "actor"."inboxUrl" IN (' + inboxUrlsString + ') OR "actor"."sharedInboxUrl" IN (' + inboxUrlsString + ')' +
+      ')'
+
+    const options = {
+      type: Sequelize.QueryTypes.BULKUPDATE,
+      transaction: t
+    }
+
+    return ActorFollowModel.sequelize.query(query, options)
+  }
+
+  private static listBadActorFollows () {
+    const query = {
+      where: {
+        score: {
+          [Sequelize.Op.lte]: 0
+        }
+      }
+    }
+
+    return ActorFollowModel.findAll(query)
+  }
+
+  toFormattedJSON (): AccountFollow {
     const follower = this.ActorFollower.toFormattedJSON()
     const following = this.ActorFollowing.toFormattedJSON()
 
@@ -268,6 +342,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
       id: this.id,
       follower,
       following,
+      score: this.score,
       state: this.state,
       createdAt: this.createdAt,
       updatedAt: this.updatedAt
index b88e06b41a6eb2db3702cca9767db42df54132de..912d8d748b97b1319ce85530efb2aec7723da7d4 100644 (file)
@@ -204,7 +204,7 @@ export class ActorModel extends Model<ActorModel> {
   VideoChannel: VideoChannelModel
 
   static load (id: number) {
-    return ActorModel.scope(ScopeNames.FULL).findById(id)
+    return ActorModel.unscoped().findById(id)
   }
 
   static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
@@ -267,20 +267,17 @@ export class ActorModel extends Model<ActorModel> {
       avatar = this.Avatar.toFormattedJSON()
     }
 
-    let score: number
-    if (this.Server) {
-      score = this.Server.score
-    }
-
     return {
       id: this.id,
       url: this.url,
       uuid: this.uuid,
+      name: this.preferredUsername,
       host: this.getHost(),
-      score,
       followingCount: this.followingCount,
       followersCount: this.followersCount,
-      avatar
+      avatar,
+      createdAt: this.createdAt,
+      updatedAt: this.updatedAt
     }
   }
 
index d35aa0ca402222f3ec6a2c54bdf371739867846f..c43146156efad09d366c76df309695ac01ac8f0d 100644 (file)
@@ -1,8 +1,5 @@
-import * as Sequelize from 'sequelize'
-import { AllowNull, Column, CreatedAt, Default, Is, IsInt, Max, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
 import { isHostValid } from '../../helpers/custom-validators/servers'
-import { logger } from '../../helpers/logger'
-import { SERVERS_SCORE } from '../../initializers'
 import { throwIfNotValid } from '../utils'
 
 @Table({
@@ -11,9 +8,6 @@ import { throwIfNotValid } from '../utils'
     {
       fields: [ 'host' ],
       unique: true
-    },
-    {
-      fields: [ 'score' ]
     }
   ]
 })
@@ -24,86 +18,9 @@ export class ServerModel extends Model<ServerModel> {
   @Column
   host: string
 
-  @AllowNull(false)
-  @Default(SERVERS_SCORE.BASE)
-  @IsInt
-  @Max(SERVERS_SCORE.MAX)
-  @Column
-  score: number
-
   @CreatedAt
   createdAt: Date
 
   @UpdatedAt
   updatedAt: Date
-
-  static updateServersScoreAndRemoveBadOnes (goodServers: number[], badServers: number[]) {
-    logger.info('Updating %d good servers and %d bad servers scores.', goodServers.length, badServers.length)
-
-    if (goodServers.length !== 0) {
-      ServerModel.incrementScores(goodServers, SERVERS_SCORE.BONUS)
-        .catch(err => {
-          logger.error('Cannot increment scores of good servers.', err)
-        })
-    }
-
-    if (badServers.length !== 0) {
-      ServerModel.incrementScores(badServers, SERVERS_SCORE.PENALTY)
-        .then(() => ServerModel.removeBadServers())
-        .catch(err => {
-          if (err) logger.error('Cannot decrement scores of bad servers.', err)
-        })
-
-    }
-  }
-
-  // Remove servers with a score of 0 (too many requests where they were unreachable)
-  private static async removeBadServers () {
-    try {
-      const servers = await ServerModel.listBadServers()
-
-      const serversRemovePromises = servers.map(server => server.destroy())
-      await Promise.all(serversRemovePromises)
-
-      const numberOfServersRemoved = servers.length
-
-      if (numberOfServersRemoved) {
-        logger.info('Removed %d servers.', numberOfServersRemoved)
-      } else {
-        logger.info('No need to remove bad servers.')
-      }
-    } catch (err) {
-      logger.error('Cannot remove bad servers.', err)
-    }
-  }
-
-  private static incrementScores (ids: number[], value: number) {
-    const update = {
-      score: Sequelize.literal('score +' + value)
-    }
-
-    const options = {
-      where: {
-        id: {
-          [Sequelize.Op.in]: ids
-        }
-      },
-      // In this case score is a literal and not an integer so we do not validate it
-      validate: false
-    }
-
-    return ServerModel.update(update, options)
-  }
-
-  private static listBadServers () {
-    const query = {
-      where: {
-        score: {
-          [Sequelize.Op.lte]: 0
-        }
-      }
-    }
-
-    return ServerModel.findAll(query)
-  }
 }
index acc2486b398b18dba072f9a2dec5359d9545703d..e2cbf0422cd62bb2d721e8948d97df9b4c17962b 100644 (file)
@@ -228,7 +228,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
     const actor = this.Actor.toFormattedJSON()
     const account = {
       id: this.id,
-      name: this.name,
+      displayName: this.name,
       description: this.description,
       isLocal: this.Actor.isOwned(),
       createdAt: this.createdAt,
index e4dbc81e5a8d9385d3a91f4b8531ca67c24c389c..5cc12c18fc789ddab91ae29e738b7d50b115de48 100644 (file)
@@ -1,15 +1,5 @@
-import { Avatar } from '../avatars/avatar.model'
+import { Actor } from './actor.model'
 
-export interface Account {
-  id: number
-  uuid: string
-  url: string
-  name: string
+export interface Account extends Actor {
   displayName: string
-  host: string
-  followingCount: number
-  followersCount: number
-  createdAt: Date
-  updatedAt: Date
-  avatar: Avatar
 }
diff --git a/shared/models/actors/actor.model.ts b/shared/models/actors/actor.model.ts
new file mode 100644 (file)
index 0000000..f916165
--- /dev/null
@@ -0,0 +1,14 @@
+import { Avatar } from '../avatars/avatar.model'
+
+export interface Actor {
+  id: number
+  uuid: string
+  url: string
+  name: string
+  host: string
+  followingCount: number
+  followersCount: number
+  createdAt: Date
+  updatedAt: Date
+  avatar: Avatar
+}
index cdc3da5602ffc81eeee963ab03aa6ccf0c971e03..70562bfc7d3278ae9e06f88e245f23bae27afd64 100644 (file)
@@ -1,11 +1,12 @@
-import { Account } from './account.model'
+import { Actor } from './actor.model'
 
 export type FollowState = 'pending' | 'accepted'
 
 export interface AccountFollow {
   id: number
-  follower: Account
-  following: Account
+  follower: Actor
+  following: Actor
+  score: number
   state: FollowState
   createdAt: Date
   updatedAt: Date
index d1a952826f34a3b18833be875000f493c96daa24..b164fb555593a7824e9ee46cc6fb72395a99f960 100644 (file)
@@ -1,13 +1,10 @@
+import { Actor } from '../actors/actor.model'
 import { Video } from './video.model'
 
-export interface VideoChannel {
-  id: number
-  name: string
-  url: string
+export interface VideoChannel extends Actor {
+  displayName: string
   description: string
   isLocal: boolean
-  createdAt: Date | string
-  updatedAt: Date | string
   owner?: {
     name: string
     uuid: string