aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/actor
diff options
context:
space:
mode:
authorkontrollanten <6680299+kontrollanten@users.noreply.github.com>2022-02-28 08:34:43 +0100
committerGitHub <noreply@github.com>2022-02-28 08:34:43 +0100
commitd0800f7661f13fabe7bb6f4aa0ea50764f106405 (patch)
treed43e6b0b6f4a5a32e03487e6464edbcaf288be2a /server/models/actor
parent5cad2ca9db9b9d138f8a33058d10b94a9fd50c69 (diff)
downloadPeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.gz
PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.zst
PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.zip
Implement avatar miniatures (#4639)
* client: remove unused file * refactor(client/my-actor-avatar): size from input Read size from component input instead of scss, to make it possible to use smaller avatar images when implemented. * implement avatar miniatures close #4560 * fix(test): max file size * fix(search-index): normalize res acc to avatarMini * refactor avatars to an array * client/search: resize channel avatar to 120 * refactor(client/videos): remove unused function * client(actor-avatar): set default size * fix tests and avatars full result When findOne is used only an array containting one avatar is returned. * update migration version and version notations * server/search: harmonize normalizing * Cleanup avatar miniature PR Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/models/actor')
-rw-r--r--server/models/actor/actor-follow.ts263
-rw-r--r--server/models/actor/actor-image.ts67
-rw-r--r--server/models/actor/actor.ts129
3 files changed, 259 insertions, 200 deletions
diff --git a/server/models/actor/actor-follow.ts b/server/models/actor/actor-follow.ts
index 006282530..0f4d3c0a6 100644
--- a/server/models/actor/actor-follow.ts
+++ b/server/models/actor/actor-follow.ts
@@ -1,5 +1,5 @@
1import { difference, values } from 'lodash' 1import { difference, values } from 'lodash'
2import { IncludeOptions, Op, QueryTypes, Transaction, WhereOptions } from 'sequelize' 2import { Includeable, IncludeOptions, literal, Op, QueryTypes, Transaction, WhereOptions } from 'sequelize'
3import { 3import {
4 AfterCreate, 4 AfterCreate,
5 AfterDestroy, 5 AfterDestroy,
@@ -30,12 +30,12 @@ import {
30 MActorFollowFormattable, 30 MActorFollowFormattable,
31 MActorFollowSubscriptions 31 MActorFollowSubscriptions
32} from '@server/types/models' 32} from '@server/types/models'
33import { AttributesOnly } from '@shared/typescript-utils'
34import { ActivityPubActorType } from '@shared/models' 33import { ActivityPubActorType } from '@shared/models'
34import { AttributesOnly } from '@shared/typescript-utils'
35import { FollowState } from '../../../shared/models/actors' 35import { FollowState } from '../../../shared/models/actors'
36import { ActorFollow } from '../../../shared/models/actors/follow.model' 36import { ActorFollow } from '../../../shared/models/actors/follow.model'
37import { logger } from '../../helpers/logger' 37import { logger } from '../../helpers/logger'
38import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' 38import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants'
39import { AccountModel } from '../account/account' 39import { AccountModel } from '../account/account'
40import { ServerModel } from '../server/server' 40import { ServerModel } from '../server/server'
41import { doesExist } from '../shared/query' 41import { doesExist } from '../shared/query'
@@ -375,43 +375,46 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
375 Object.assign(followingWhere, { type: actorType }) 375 Object.assign(followingWhere, { type: actorType })
376 } 376 }
377 377
378 const query = { 378 const getQuery = (forCount: boolean) => {
379 distinct: true, 379 const actorModel = forCount
380 offset: start, 380 ? ActorModel.unscoped()
381 limit: count, 381 : ActorModel
382 order: getFollowsSort(sort), 382
383 where: followWhere, 383 return {
384 include: [ 384 distinct: true,
385 { 385 offset: start,
386 model: ActorModel, 386 limit: count,
387 required: true, 387 order: getFollowsSort(sort),
388 as: 'ActorFollower', 388 where: followWhere,
389 where: { 389 include: [
390 id 390 {
391 } 391 model: actorModel,
392 }, 392 required: true,
393 { 393 as: 'ActorFollower',
394 model: ActorModel, 394 where: {
395 as: 'ActorFollowing', 395 id
396 required: true,
397 where: followingWhere,
398 include: [
399 {
400 model: ServerModel,
401 required: true
402 } 396 }
403 ] 397 },
404 } 398 {
405 ] 399 model: actorModel,
400 as: 'ActorFollowing',
401 required: true,
402 where: followingWhere,
403 include: [
404 {
405 model: ServerModel,
406 required: true
407 }
408 ]
409 }
410 ]
411 }
406 } 412 }
407 413
408 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) 414 return Promise.all([
409 .then(({ rows, count }) => { 415 ActorFollowModel.count(getQuery(true)),
410 return { 416 ActorFollowModel.findAll<MActorFollowActorsDefault>(getQuery(false))
411 data: rows, 417 ]).then(([ total, data ]) => ({ total, data }))
412 total: count
413 }
414 })
415 } 418 }
416 419
417 static listFollowersForApi (options: { 420 static listFollowersForApi (options: {
@@ -429,11 +432,17 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
429 const followerWhere: WhereOptions = {} 432 const followerWhere: WhereOptions = {}
430 433
431 if (search) { 434 if (search) {
432 Object.assign(followWhere, { 435 const escapedSearch = ActorFollowModel.sequelize.escape('%' + search + '%')
433 [Op.or]: [ 436
434 searchAttribute(search, '$ActorFollower.preferredUsername$'), 437 Object.assign(followerWhere, {
435 searchAttribute(search, '$ActorFollower.Server.host$') 438 id: {
436 ] 439 [Op.in]: literal(
440 `(` +
441 `SELECT "actor".id FROM actor LEFT JOIN server on server.id = actor."serverId" ` +
442 `WHERE "preferredUsername" ILIKE ${escapedSearch} OR "host" ILIKE ${escapedSearch}` +
443 `)`
444 )
445 }
437 }) 446 })
438 } 447 }
439 448
@@ -441,39 +450,43 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
441 Object.assign(followerWhere, { type: actorType }) 450 Object.assign(followerWhere, { type: actorType })
442 } 451 }
443 452
444 const query = { 453 const getQuery = (forCount: boolean) => {
445 distinct: true, 454 const actorModel = forCount
446 offset: start, 455 ? ActorModel.unscoped()
447 limit: count, 456 : ActorModel
448 order: getFollowsSort(sort), 457
449 where: followWhere, 458 return {
450 include: [ 459 distinct: true,
451 { 460
452 model: ActorModel, 461 offset: start,
453 required: true, 462 limit: count,
454 as: 'ActorFollower', 463 order: getFollowsSort(sort),
455 where: followerWhere 464 where: followWhere,
456 }, 465 include: [
457 { 466 {
458 model: ActorModel, 467 model: actorModel,
459 as: 'ActorFollowing', 468 required: true,
460 required: true, 469 as: 'ActorFollower',
461 where: { 470 where: followerWhere
462 id: { 471 },
463 [Op.in]: actorIds 472 {
473 model: actorModel,
474 as: 'ActorFollowing',
475 required: true,
476 where: {
477 id: {
478 [Op.in]: actorIds
479 }
464 } 480 }
465 } 481 }
466 } 482 ]
467 ] 483 }
468 } 484 }
469 485
470 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) 486 return Promise.all([
471 .then(({ rows, count }) => { 487 ActorFollowModel.count(getQuery(true)),
472 return { 488 ActorFollowModel.findAll<MActorFollowActorsDefault>(getQuery(false))
473 data: rows, 489 ]).then(([ total, data ]) => ({ total, data }))
474 total: count
475 }
476 })
477 } 490 }
478 491
479 static listSubscriptionsForApi (options: { 492 static listSubscriptionsForApi (options: {
@@ -497,58 +510,68 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
497 }) 510 })
498 } 511 }
499 512
500 const query = { 513 const getQuery = (forCount: boolean) => {
501 attributes: [], 514 let channelInclude: Includeable[] = []
502 distinct: true, 515
503 offset: start, 516 if (forCount !== true) {
504 limit: count, 517 channelInclude = [
505 order: getSort(sort), 518 {
506 where, 519 attributes: {
507 include: [ 520 exclude: unusedActorAttributesForAPI
508 { 521 },
509 attributes: [ 'id' ], 522 model: ActorModel,
510 model: ActorModel.unscoped(), 523 required: true
511 as: 'ActorFollowing', 524 },
512 required: true, 525 {
513 include: [ 526 model: AccountModel.unscoped(),
514 { 527 required: true,
515 model: VideoChannelModel.unscoped(), 528 include: [
516 required: true, 529 {
517 include: [ 530 attributes: {
518 { 531 exclude: unusedActorAttributesForAPI
519 attributes: {
520 exclude: unusedActorAttributesForAPI
521 },
522 model: ActorModel,
523 required: true
524 }, 532 },
525 { 533 model: ActorModel,
526 model: AccountModel.unscoped(), 534 required: true
527 required: true, 535 }
528 include: [ 536 ]
529 { 537 }
530 attributes: { 538 ]
531 exclude: unusedActorAttributesForAPI 539 }
532 }, 540
533 model: ActorModel, 541 return {
534 required: true 542 attributes: forCount === true
535 } 543 ? []
536 ] 544 : SORTABLE_COLUMNS.USER_SUBSCRIPTIONS,
537 } 545 distinct: true,
538 ] 546 offset: start,
539 } 547 limit: count,
540 ] 548 order: getSort(sort),
541 } 549 where,
542 ] 550 include: [
551 {
552 attributes: [ 'id' ],
553 model: ActorModel.unscoped(),
554 as: 'ActorFollowing',
555 required: true,
556 include: [
557 {
558 model: VideoChannelModel.unscoped(),
559 required: true,
560 include: channelInclude
561 }
562 ]
563 }
564 ]
565 }
543 } 566 }
544 567
545 return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query) 568 return Promise.all([
546 .then(({ rows, count }) => { 569 ActorFollowModel.count(getQuery(true)),
547 return { 570 ActorFollowModel.findAll<MActorFollowSubscriptions>(getQuery(false))
548 data: rows.map(r => r.ActorFollowing.VideoChannel), 571 ]).then(([ total, rows ]) => ({
549 total: count 572 total,
550 } 573 data: rows.map(r => r.ActorFollowing.VideoChannel)
551 }) 574 }))
552 } 575 }
553 576
554 static async keepUnfollowedInstance (hosts: string[]) { 577 static async keepUnfollowedInstance (hosts: string[]) {
diff --git a/server/models/actor/actor-image.ts b/server/models/actor/actor-image.ts
index 8edff5ab4..f74ab735e 100644
--- a/server/models/actor/actor-image.ts
+++ b/server/models/actor/actor-image.ts
@@ -1,15 +1,29 @@
1import { remove } from 'fs-extra' 1import { remove } from 'fs-extra'
2import { join } from 'path' 2import { join } from 'path'
3import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import {
4import { MActorImageFormattable } from '@server/types/models' 4 AfterDestroy,
5 AllowNull,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 Default,
10 ForeignKey,
11 Is,
12 Model,
13 Table,
14 UpdatedAt
15} from 'sequelize-typescript'
16import { MActorImage, MActorImageFormattable } from '@server/types/models'
17import { getLowercaseExtension } from '@shared/core-utils'
18import { ActivityIconObject, ActorImageType } from '@shared/models'
5import { AttributesOnly } from '@shared/typescript-utils' 19import { AttributesOnly } from '@shared/typescript-utils'
6import { ActorImageType } from '@shared/models'
7import { ActorImage } from '../../../shared/models/actors/actor-image.model' 20import { ActorImage } from '../../../shared/models/actors/actor-image.model'
8import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 21import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
9import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
10import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
11import { LAZY_STATIC_PATHS } from '../../initializers/constants' 24import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants'
12import { throwIfNotValid } from '../utils' 25import { throwIfNotValid } from '../utils'
26import { ActorModel } from './actor'
13 27
14@Table({ 28@Table({
15 tableName: 'actorImage', 29 tableName: 'actorImage',
@@ -17,6 +31,10 @@ import { throwIfNotValid } from '../utils'
17 { 31 {
18 fields: [ 'filename' ], 32 fields: [ 'filename' ],
19 unique: true 33 unique: true
34 },
35 {
36 fields: [ 'actorId', 'type', 'width' ],
37 unique: true
20 } 38 }
21 ] 39 ]
22}) 40})
@@ -55,6 +73,18 @@ export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageMode
55 @UpdatedAt 73 @UpdatedAt
56 updatedAt: Date 74 updatedAt: Date
57 75
76 @ForeignKey(() => ActorModel)
77 @Column
78 actorId: number
79
80 @BelongsTo(() => ActorModel, {
81 foreignKey: {
82 allowNull: false
83 },
84 onDelete: 'CASCADE'
85 })
86 Actor: ActorModel
87
58 @AfterDestroy 88 @AfterDestroy
59 static removeFilesAndSendDelete (instance: ActorImageModel) { 89 static removeFilesAndSendDelete (instance: ActorImageModel) {
60 logger.info('Removing actor image file %s.', instance.filename) 90 logger.info('Removing actor image file %s.', instance.filename)
@@ -74,20 +104,41 @@ export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageMode
74 return ActorImageModel.findOne(query) 104 return ActorImageModel.findOne(query)
75 } 105 }
76 106
107 static getImageUrl (image: MActorImage) {
108 if (!image) return undefined
109
110 return WEBSERVER.URL + image.getStaticPath()
111 }
112
77 toFormattedJSON (this: MActorImageFormattable): ActorImage { 113 toFormattedJSON (this: MActorImageFormattable): ActorImage {
78 return { 114 return {
115 width: this.width,
79 path: this.getStaticPath(), 116 path: this.getStaticPath(),
80 createdAt: this.createdAt, 117 createdAt: this.createdAt,
81 updatedAt: this.updatedAt 118 updatedAt: this.updatedAt
82 } 119 }
83 } 120 }
84 121
85 getStaticPath () { 122 toActivityPubObject (): ActivityIconObject {
86 if (this.type === ActorImageType.AVATAR) { 123 const extension = getLowercaseExtension(this.filename)
87 return join(LAZY_STATIC_PATHS.AVATARS, this.filename) 124
125 return {
126 type: 'Image',
127 mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension],
128 height: this.height,
129 width: this.width,
130 url: ActorImageModel.getImageUrl(this)
88 } 131 }
132 }
89 133
90 return join(LAZY_STATIC_PATHS.BANNERS, this.filename) 134 getStaticPath () {
135 switch (this.type) {
136 case ActorImageType.AVATAR:
137 return join(LAZY_STATIC_PATHS.AVATARS, this.filename)
138
139 case ActorImageType.BANNER:
140 return join(LAZY_STATIC_PATHS.BANNERS, this.filename)
141 }
91 } 142 }
92 143
93 getPath () { 144 getPath () {
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts
index c12dcf634..08cb2fd24 100644
--- a/server/models/actor/actor.ts
+++ b/server/models/actor/actor.ts
@@ -16,11 +16,11 @@ import {
16 Table, 16 Table,
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { getBiggestActorImage } from '@server/lib/actor-image'
19import { ModelCache } from '@server/models/model-cache' 20import { ModelCache } from '@server/models/model-cache'
20import { getLowercaseExtension } from '@shared/core-utils' 21import { getLowercaseExtension } from '@shared/core-utils'
22import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models'
21import { AttributesOnly } from '@shared/typescript-utils' 23import { AttributesOnly } from '@shared/typescript-utils'
22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
23import { ActorImage } from '../../../shared/models/actors/actor-image.model'
24import { activityPubContextify } from '../../helpers/activitypub' 24import { activityPubContextify } from '../../helpers/activitypub'
25import { 25import {
26 isActorFollowersCountValid, 26 isActorFollowersCountValid,
@@ -81,7 +81,7 @@ export const unusedActorAttributesForAPI = [
81 }, 81 },
82 { 82 {
83 model: ActorImageModel, 83 model: ActorImageModel,
84 as: 'Avatar', 84 as: 'Avatars',
85 required: false 85 required: false
86 } 86 }
87 ] 87 ]
@@ -109,12 +109,12 @@ export const unusedActorAttributesForAPI = [
109 }, 109 },
110 { 110 {
111 model: ActorImageModel, 111 model: ActorImageModel,
112 as: 'Avatar', 112 as: 'Avatars',
113 required: false 113 required: false
114 }, 114 },
115 { 115 {
116 model: ActorImageModel, 116 model: ActorImageModel,
117 as: 'Banner', 117 as: 'Banners',
118 required: false 118 required: false
119 } 119 }
120 ] 120 ]
@@ -153,9 +153,6 @@ export const unusedActorAttributesForAPI = [
153 fields: [ 'serverId' ] 153 fields: [ 'serverId' ]
154 }, 154 },
155 { 155 {
156 fields: [ 'avatarId' ]
157 },
158 {
159 fields: [ 'followersUrl' ] 156 fields: [ 'followersUrl' ]
160 } 157 }
161 ] 158 ]
@@ -231,35 +228,31 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
231 @UpdatedAt 228 @UpdatedAt
232 updatedAt: Date 229 updatedAt: Date
233 230
234 @ForeignKey(() => ActorImageModel) 231 @HasMany(() => ActorImageModel, {
235 @Column 232 as: 'Avatars',
236 avatarId: number 233 onDelete: 'cascade',
237 234 hooks: true,
238 @ForeignKey(() => ActorImageModel)
239 @Column
240 bannerId: number
241
242 @BelongsTo(() => ActorImageModel, {
243 foreignKey: { 235 foreignKey: {
244 name: 'avatarId', 236 allowNull: false
245 allowNull: true
246 }, 237 },
247 as: 'Avatar', 238 scope: {
248 onDelete: 'set null', 239 type: ActorImageType.AVATAR
249 hooks: true 240 }
250 }) 241 })
251 Avatar: ActorImageModel 242 Avatars: ActorImageModel[]
252 243
253 @BelongsTo(() => ActorImageModel, { 244 @HasMany(() => ActorImageModel, {
245 as: 'Banners',
246 onDelete: 'cascade',
247 hooks: true,
254 foreignKey: { 248 foreignKey: {
255 name: 'bannerId', 249 allowNull: false
256 allowNull: true
257 }, 250 },
258 as: 'Banner', 251 scope: {
259 onDelete: 'set null', 252 type: ActorImageType.BANNER
260 hooks: true 253 }
261 }) 254 })
262 Banner: ActorImageModel 255 Banners: ActorImageModel[]
263 256
264 @HasMany(() => ActorFollowModel, { 257 @HasMany(() => ActorFollowModel, {
265 foreignKey: { 258 foreignKey: {
@@ -386,8 +379,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
386 transaction 379 transaction
387 } 380 }
388 381
389 return ActorModel.scope(ScopeNames.FULL) 382 return ActorModel.scope(ScopeNames.FULL).findOne(query)
390 .findOne(query)
391 } 383 }
392 384
393 return ModelCache.Instance.doCache({ 385 return ModelCache.Instance.doCache({
@@ -410,8 +402,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
410 transaction 402 transaction
411 } 403 }
412 404
413 return ActorModel.unscoped() 405 return ActorModel.unscoped().findOne(query)
414 .findOne(query)
415 } 406 }
416 407
417 return ModelCache.Instance.doCache({ 408 return ModelCache.Instance.doCache({
@@ -532,55 +523,50 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
532 } 523 }
533 524
534 toFormattedSummaryJSON (this: MActorSummaryFormattable) { 525 toFormattedSummaryJSON (this: MActorSummaryFormattable) {
535 let avatar: ActorImage = null
536 if (this.Avatar) {
537 avatar = this.Avatar.toFormattedJSON()
538 }
539
540 return { 526 return {
541 url: this.url, 527 url: this.url,
542 name: this.preferredUsername, 528 name: this.preferredUsername,
543 host: this.getHost(), 529 host: this.getHost(),
544 avatar 530 avatars: (this.Avatars || []).map(a => a.toFormattedJSON()),
531
532 // TODO: remove, deprecated in 4.2
533 avatar: this.hasImage(ActorImageType.AVATAR)
534 ? this.Avatars[0].toFormattedJSON()
535 : undefined
545 } 536 }
546 } 537 }
547 538
548 toFormattedJSON (this: MActorFormattable) { 539 toFormattedJSON (this: MActorFormattable) {
549 const base = this.toFormattedSummaryJSON() 540 return {
550 541 ...this.toFormattedSummaryJSON(),
551 let banner: ActorImage = null
552 if (this.Banner) {
553 banner = this.Banner.toFormattedJSON()
554 }
555 542
556 return Object.assign(base, {
557 id: this.id, 543 id: this.id,
558 hostRedundancyAllowed: this.getRedundancyAllowed(), 544 hostRedundancyAllowed: this.getRedundancyAllowed(),
559 followingCount: this.followingCount, 545 followingCount: this.followingCount,
560 followersCount: this.followersCount, 546 followersCount: this.followersCount,
561 banner, 547 createdAt: this.getCreatedAt(),
562 createdAt: this.getCreatedAt() 548
563 }) 549 banners: (this.Banners || []).map(b => b.toFormattedJSON()),
550
551 // TODO: remove, deprecated in 4.2
552 banner: this.hasImage(ActorImageType.BANNER)
553 ? this.Banners[0].toFormattedJSON()
554 : undefined
555 }
564 } 556 }
565 557
566 toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) { 558 toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) {
567 let icon: ActivityIconObject 559 let icon: ActivityIconObject
560 let icons: ActivityIconObject[]
568 let image: ActivityIconObject 561 let image: ActivityIconObject
569 562
570 if (this.avatarId) { 563 if (this.hasImage(ActorImageType.AVATAR)) {
571 const extension = getLowercaseExtension(this.Avatar.filename) 564 icon = getBiggestActorImage(this.Avatars).toActivityPubObject()
572 565 icons = this.Avatars.map(a => a.toActivityPubObject())
573 icon = {
574 type: 'Image',
575 mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension],
576 height: this.Avatar.height,
577 width: this.Avatar.width,
578 url: this.getAvatarUrl()
579 }
580 } 566 }
581 567
582 if (this.bannerId) { 568 if (this.hasImage(ActorImageType.BANNER)) {
583 const banner = (this as MActorAPChannel).Banner 569 const banner = getBiggestActorImage((this as MActorAPChannel).Banners)
584 const extension = getLowercaseExtension(banner.filename) 570 const extension = getLowercaseExtension(banner.filename)
585 571
586 image = { 572 image = {
@@ -588,7 +574,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
588 mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], 574 mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension],
589 height: banner.height, 575 height: banner.height,
590 width: banner.width, 576 width: banner.width,
591 url: this.getBannerUrl() 577 url: ActorImageModel.getImageUrl(banner)
592 } 578 }
593 } 579 }
594 580
@@ -612,7 +598,10 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
612 publicKeyPem: this.publicKey 598 publicKeyPem: this.publicKey
613 }, 599 },
614 published: this.getCreatedAt().toISOString(), 600 published: this.getCreatedAt().toISOString(),
601
615 icon, 602 icon,
603 icons,
604
616 image 605 image
617 } 606 }
618 607
@@ -677,16 +666,12 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
677 return this.Server ? this.Server.redundancyAllowed : false 666 return this.Server ? this.Server.redundancyAllowed : false
678 } 667 }
679 668
680 getAvatarUrl () { 669 hasImage (type: ActorImageType) {
681 if (!this.avatarId) return undefined 670 const images = type === ActorImageType.AVATAR
682 671 ? this.Avatars
683 return WEBSERVER.URL + this.Avatar.getStaticPath() 672 : this.Banners
684 }
685
686 getBannerUrl () {
687 if (!this.bannerId) return undefined
688 673
689 return WEBSERVER.URL + this.Banner.getStaticPath() 674 return Array.isArray(images) && images.length !== 0
690 } 675 }
691 676
692 isOutdated () { 677 isOutdated () {