aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub/actors/image.ts
blob: e1d29af5bc71bd748c61d50f9fa5356430e4e775 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import { Transaction } from 'sequelize/types'
import { logger } from '@server/helpers/logger'
import { ActorImageModel } from '@server/models/actor/actor-image'
import { MActorImage, MActorImages } from '@server/types/models'
import { ActorImageType } from '@shared/models'

type ImageInfo = {
  name: string
  fileUrl: string
  height: number
  width: number
  onDisk?: boolean
}

async function updateActorImages (actor: MActorImages, type: ActorImageType, imagesInfo: ImageInfo[], t: Transaction) {
  const getAvatarsOrBanners = () => {
    const result = type === ActorImageType.AVATAR
      ? actor.Avatars
      : actor.Banners

    return result || []
  }

  if (imagesInfo.length === 0) {
    await deleteActorImages(actor, type, t)
  }

  // Cleanup old images that did not have a width
  for (const oldImageModel of getAvatarsOrBanners()) {
    if (oldImageModel.width) continue

    await safeDeleteActorImage(actor, oldImageModel, type, t)
  }

  for (const imageInfo of imagesInfo) {
    const oldImageModel = getAvatarsOrBanners().find(i => imageInfo.width && i.width === imageInfo.width)

    if (oldImageModel) {
      // Don't update the avatar if the file URL did not change
      if (imageInfo.fileUrl && oldImageModel.fileUrl === imageInfo.fileUrl) {
        continue
      }

      await safeDeleteActorImage(actor, oldImageModel, type, t)
    }

    const imageModel = await ActorImageModel.create({
      filename: imageInfo.name,
      onDisk: imageInfo.onDisk ?? false,
      fileUrl: imageInfo.fileUrl,
      height: imageInfo.height,
      width: imageInfo.width,
      type,
      actorId: actor.id
    }, { transaction: t })

    addActorImage(actor, type, imageModel)
  }

  return actor
}

async function deleteActorImages (actor: MActorImages, type: ActorImageType, t: Transaction) {
  try {
    const association = buildAssociationName(type)

    for (const image of actor[association]) {
      await image.destroy({ transaction: t })
    }

    actor[association] = []
  } catch (err) {
    logger.error('Cannot remove old image of actor %s.', actor.url, { err })
  }

  return actor
}

async function safeDeleteActorImage (actor: MActorImages, toDelete: MActorImage, type: ActorImageType, t: Transaction) {
  try {
    await toDelete.destroy({ transaction: t })

    const association = buildAssociationName(type)
    actor[association] = actor[association].filter(image => image.id !== toDelete.id)
  } catch (err) {
    logger.error('Cannot remove old actor image of actor %s.', actor.url, { err })
  }
}

// ---------------------------------------------------------------------------

export {
  ImageInfo,

  updateActorImages,
  deleteActorImages
}

// ---------------------------------------------------------------------------

function addActorImage (actor: MActorImages, type: ActorImageType, imageModel: MActorImage) {
  const association = buildAssociationName(type)
  if (!actor[association]) actor[association] = []

  actor[association].push(imageModel)
}

function buildAssociationName (type: ActorImageType) {
  return type === ActorImageType.AVATAR
    ? 'Avatars'
    : 'Banners'
}