]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/actor/actor-image.ts
9d44ef4d1a2fdbad8f7a6b5f5634feb661e188f9
[github/Chocobozzz/PeerTube.git] / server / models / actor / actor-image.ts
1 import { remove } from 'fs-extra'
2 import { join } from 'path'
3 import {
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'
16 import { MActorImage, MActorImageFormattable } from '@server/types/models'
17 import { getLowercaseExtension } from '@shared/core-utils'
18 import { ActivityIconObject, ActorImageType } from '@shared/models'
19 import { AttributesOnly } from '@shared/typescript-utils'
20 import { ActorImage } from '../../../shared/models/actors/actor-image.model'
21 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
22 import { logger } from '../../helpers/logger'
23 import { CONFIG } from '../../initializers/config'
24 import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants'
25 import { throwIfNotValid } from '../utils'
26 import { ActorModel } from './actor'
27
28 @Table({
29 tableName: 'actorImage',
30 indexes: [
31 {
32 fields: [ 'filename' ],
33 unique: true
34 },
35 {
36 fields: [ 'actorId', 'type', 'width' ],
37 unique: true
38 }
39 ]
40 })
41 export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageModel>>> {
42
43 @AllowNull(false)
44 @Column
45 filename: string
46
47 @AllowNull(true)
48 @Default(null)
49 @Column
50 height: number
51
52 @AllowNull(true)
53 @Default(null)
54 @Column
55 width: number
56
57 @AllowNull(true)
58 @Is('ActorImageFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true))
59 @Column
60 fileUrl: string
61
62 @AllowNull(false)
63 @Column
64 onDisk: boolean
65
66 @AllowNull(false)
67 @Column
68 type: ActorImageType
69
70 @CreatedAt
71 createdAt: Date
72
73 @UpdatedAt
74 updatedAt: Date
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
88 @AfterDestroy
89 static removeFilesAndSendDelete (instance: ActorImageModel) {
90 logger.info('Removing actor image file %s.', instance.filename)
91
92 // Don't block the transaction
93 instance.removeImage()
94 .catch(err => logger.error('Cannot remove actor image file %s.', instance.filename, err))
95 }
96
97 static loadByName (filename: string) {
98 const query = {
99 where: {
100 filename
101 }
102 }
103
104 return ActorImageModel.findOne(query)
105 }
106
107 static getImageUrl (image: MActorImage) {
108 if (!image) return undefined
109
110 return WEBSERVER.URL + image.getStaticPath()
111 }
112
113 toFormattedJSON (this: MActorImageFormattable): ActorImage {
114 return {
115 width: this.width,
116 path: this.getStaticPath(),
117 createdAt: this.createdAt,
118 updatedAt: this.updatedAt
119 }
120 }
121
122 toActivityPubObject (): ActivityIconObject {
123 const extension = getLowercaseExtension(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)
131 }
132 }
133
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
142 default:
143 throw new Error('Unknown actor image type: ' + this.type)
144 }
145 }
146
147 getPath () {
148 return join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename)
149 }
150
151 removeImage () {
152 const imagePath = join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename)
153 return remove(imagePath)
154 }
155
156 isOwned () {
157 return !this.fileUrl
158 }
159 }