aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-04-06 11:35:56 +0200
committerChocobozzz <chocobozzz@cpy.re>2021-04-08 10:07:53 +0200
commitf479685678406a5df864d89615b33d29085ebfc6 (patch)
tree8de15e90cd8d97d8810715df8585c61f48d5282a
parent968aaed2066873fc1c39f95168284122d9d15e21 (diff)
downloadPeerTube-f479685678406a5df864d89615b33d29085ebfc6.tar.gz
PeerTube-f479685678406a5df864d89615b33d29085ebfc6.tar.zst
PeerTube-f479685678406a5df864d89615b33d29085ebfc6.zip
Agnostic actor image storage
-rw-r--r--client/src/app/core/users/user.model.ts4
-rw-r--r--client/src/app/core/users/user.service.ts5
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts4
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts4
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.model.ts4
-rw-r--r--client/src/app/shared/shared-main/video-channel/video-channel.service.ts4
-rw-r--r--client/src/app/shared/shared-main/video/video.model.ts6
-rwxr-xr-xscripts/prune-storage.ts10
-rw-r--r--server/controllers/api/users/me.ts2
-rw-r--r--server/controllers/api/video-channel.ts2
-rw-r--r--server/controllers/lazy-static.ts41
-rw-r--r--server/initializers/config.ts2
-rw-r--r--server/initializers/constants.ts5
-rw-r--r--server/initializers/database.ts8
-rw-r--r--server/lib/activitypub/actor.ts6
-rw-r--r--server/lib/actor-image.ts (renamed from server/lib/avatar.ts)30
-rw-r--r--server/models/account/account.ts5
-rw-r--r--server/models/account/actor-image.ts (renamed from server/models/avatar/avatar.ts)41
-rw-r--r--server/models/account/user-notification.ts8
-rw-r--r--server/models/activitypub/actor.ts35
-rw-r--r--server/models/video/video-channel.ts5
-rw-r--r--server/models/video/video-query-builder.ts5
-rw-r--r--server/models/video/video.ts10
-rw-r--r--server/types/models/account/actor-image.ts12
-rw-r--r--server/types/models/account/actor.ts22
-rw-r--r--server/types/models/account/avatar.ts12
-rw-r--r--server/types/models/account/index.ts4
-rw-r--r--server/types/models/user/user-notification.ts6
-rw-r--r--shared/models/actors/account.model.ts4
-rw-r--r--shared/models/actors/actor-image.model.ts (renamed from shared/models/avatars/avatar.model.ts)2
-rw-r--r--shared/models/actors/actor-image.type.ts4
-rw-r--r--shared/models/actors/actor.model.ts4
-rw-r--r--shared/models/actors/index.ts2
-rw-r--r--shared/models/avatars/index.ts1
-rw-r--r--shared/models/index.ts1
-rw-r--r--shared/models/videos/channel/video-channel.model.ts5
36 files changed, 182 insertions, 143 deletions
diff --git a/client/src/app/core/users/user.model.ts b/client/src/app/core/users/user.model.ts
index 15a4f7f82..8aaaa238d 100644
--- a/client/src/app/core/users/user.model.ts
+++ b/client/src/app/core/users/user.model.ts
@@ -1,7 +1,7 @@
1import { Account } from '@app/shared/shared-main/account/account.model' 1import { Account } from '@app/shared/shared-main/account/account.model'
2import { hasUserRight } from '@shared/core-utils/users' 2import { hasUserRight } from '@shared/core-utils/users'
3import { 3import {
4 Avatar, 4 ActorImage,
5 NSFWPolicyType, 5 NSFWPolicyType,
6 User as UserServerModel, 6 User as UserServerModel,
7 UserAdminFlag, 7 UserAdminFlag,
@@ -131,7 +131,7 @@ export class User implements UserServerModel {
131 } 131 }
132 } 132 }
133 133
134 updateAccountAvatar (newAccountAvatar?: Avatar) { 134 updateAccountAvatar (newAccountAvatar?: ActorImage) {
135 if (newAccountAvatar) this.account.updateAvatar(newAccountAvatar) 135 if (newAccountAvatar) this.account.updateAvatar(newAccountAvatar)
136 else this.account.resetAvatar() 136 else this.account.resetAvatar()
137 } 137 }
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index 33cc1f668..3de83152c 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -7,8 +7,7 @@ import { AuthService } from '@app/core/auth'
7import { getBytes } from '@root-helpers/bytes' 7import { getBytes } from '@root-helpers/bytes'
8import { UserLocalStorageKeys } from '@root-helpers/users' 8import { UserLocalStorageKeys } from '@root-helpers/users'
9import { 9import {
10 Avatar, 10 ActorImage,
11 NSFWPolicyType,
12 ResultList, 11 ResultList,
13 User as UserServerModel, 12 User as UserServerModel,
14 UserCreate, 13 UserCreate,
@@ -136,7 +135,7 @@ export class UserService {
136 changeAvatar (avatarForm: FormData) { 135 changeAvatar (avatarForm: FormData) {
137 const url = UserService.BASE_USERS_URL + 'me/avatar/pick' 136 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
138 137
139 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) 138 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm)
140 .pipe(catchError(err => this.restExtractor.handleError(err))) 139 .pipe(catchError(err => this.restExtractor.handleError(err)))
141 } 140 }
142 141
diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts
index b71a893d1..17fddff09 100644
--- a/client/src/app/shared/shared-main/account/account.model.ts
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -1,4 +1,4 @@
1import { Account as ServerAccount, Avatar } from '@shared/models' 1import { Account as ServerAccount, ActorImage } from '@shared/models'
2import { Actor } from './actor.model' 2import { Actor } from './actor.model'
3 3
4export class Account extends Actor implements ServerAccount { 4export class Account extends Actor implements ServerAccount {
@@ -38,7 +38,7 @@ export class Account extends Actor implements ServerAccount {
38 this.mutedServerByInstance = false 38 this.mutedServerByInstance = false
39 } 39 }
40 40
41 updateAvatar (newAvatar: Avatar) { 41 updateAvatar (newAvatar: ActorImage) {
42 this.avatar = newAvatar 42 this.avatar = newAvatar
43 43
44 this.updateComputedAttributes() 44 this.updateComputedAttributes()
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
index 8222c9769..c105a88ac 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -1,4 +1,4 @@
1import { Actor as ActorServer, Avatar } from '@shared/models' 1import { Actor as ActorServer, ActorImage } from '@shared/models'
2import { getAbsoluteAPIUrl } from '@app/helpers' 2import { getAbsoluteAPIUrl } from '@app/helpers'
3 3
4export abstract class Actor implements ActorServer { 4export abstract class Actor implements ActorServer {
@@ -10,7 +10,7 @@ export abstract class Actor implements ActorServer {
10 followersCount: number 10 followersCount: number
11 createdAt: Date | string 11 createdAt: Date | string
12 updatedAt: Date | string 12 updatedAt: Date | string
13 avatar: Avatar 13 avatar: ActorImage
14 14
15 avatarUrl: string 15 avatarUrl: string
16 16
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
index 126e43cea..b4c3365a9 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.model.ts
@@ -1,4 +1,4 @@
1import { Account as ServerAccount, Avatar, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models' 1import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models'
2import { Account } from '../account/account.model' 2import { Account } from '../account/account.model'
3import { Actor } from '../account/actor.model' 3import { Actor } from '../account/actor.model'
4 4
@@ -51,7 +51,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
51 } 51 }
52 } 52 }
53 53
54 updateAvatar (newAvatar: Avatar) { 54 updateAvatar (newAvatar: ActorImage) {
55 this.avatar = newAvatar 55 this.avatar = newAvatar
56 56
57 this.updateComputedAttributes() 57 this.updateComputedAttributes()
diff --git a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
index eff3fad4d..3f9ef74fa 100644
--- a/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
+++ b/client/src/app/shared/shared-main/video-channel/video-channel.service.ts
@@ -3,7 +3,7 @@ import { catchError, map, tap } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' 5import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
6import { Avatar, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models' 6import { ActorImage, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models'
7import { environment } from '../../../../environments/environment' 7import { environment } from '../../../../environments/environment'
8import { Account } from '../account' 8import { Account } from '../account'
9import { AccountService } from '../account/account.service' 9import { AccountService } from '../account/account.service'
@@ -85,7 +85,7 @@ export class VideoChannelService {
85 changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) { 85 changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) {
86 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick' 86 const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick'
87 87
88 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) 88 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm)
89 .pipe(catchError(err => this.restExtractor.handleError(err))) 89 .pipe(catchError(err => this.restExtractor.handleError(err)))
90 } 90 }
91 91
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts
index adb6e884f..1c2c4a575 100644
--- a/client/src/app/shared/shared-main/video/video.model.ts
+++ b/client/src/app/shared/shared-main/video/video.model.ts
@@ -6,7 +6,7 @@ import { Actor } from '@app/shared/shared-main/account/actor.model'
6import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' 6import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model'
7import { peertubeTranslate } from '@shared/core-utils/i18n' 7import { peertubeTranslate } from '@shared/core-utils/i18n'
8import { 8import {
9 Avatar, 9 ActorImage,
10 ServerConfig, 10 ServerConfig,
11 UserRight, 11 UserRight,
12 Video as VideoServerModel, 12 Video as VideoServerModel,
@@ -72,7 +72,7 @@ export class Video implements VideoServerModel {
72 displayName: string 72 displayName: string
73 url: string 73 url: string
74 host: string 74 host: string
75 avatar?: Avatar 75 avatar?: ActorImage
76 } 76 }
77 77
78 channel: { 78 channel: {
@@ -81,7 +81,7 @@ export class Video implements VideoServerModel {
81 displayName: string 81 displayName: string
82 url: string 82 url: string
83 host: string 83 host: string
84 avatar?: Avatar 84 avatar?: ActorImage
85 } 85 }
86 86
87 userHistory?: { 87 userHistory?: {
diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts
index dcb1fcf90..bdfb335c6 100755
--- a/scripts/prune-storage.ts
+++ b/scripts/prune-storage.ts
@@ -11,7 +11,7 @@ import { VideoRedundancyModel } from '../server/models/redundancy/video-redundan
11import * as Bluebird from 'bluebird' 11import * as Bluebird from 'bluebird'
12import { getUUIDFromFilename } from '../server/helpers/utils' 12import { getUUIDFromFilename } from '../server/helpers/utils'
13import { ThumbnailModel } from '../server/models/video/thumbnail' 13import { ThumbnailModel } from '../server/models/video/thumbnail'
14import { AvatarModel } from '../server/models/avatar/avatar' 14import { ActorImageModel } from '../server/models/account/actor-image'
15import { uniq, values } from 'lodash' 15import { uniq, values } from 'lodash'
16import { ThumbnailType } from '@shared/models' 16import { ThumbnailType } from '@shared/models'
17 17
@@ -43,7 +43,7 @@ async function run () {
43 await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true, ThumbnailType.PREVIEW)), 43 await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true, ThumbnailType.PREVIEW)),
44 await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false, ThumbnailType.MINIATURE)), 44 await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false, ThumbnailType.MINIATURE)),
45 45
46 await pruneDirectory(CONFIG.STORAGE.AVATARS_DIR, doesAvatarExist) 46 await pruneDirectory(CONFIG.STORAGE.ACTOR_IMAGES, doesActorImageExist)
47 ) 47 )
48 48
49 const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR) 49 const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR)
@@ -107,10 +107,10 @@ function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) {
107 } 107 }
108} 108}
109 109
110async function doesAvatarExist (file: string) { 110async function doesActorImageExist (file: string) {
111 const avatar = await AvatarModel.loadByName(file) 111 const image = await ActorImageModel.loadByName(file)
112 112
113 return !!avatar 113 return !!image
114} 114}
115 115
116async function doesRedundancyExist (file: string) { 116async function doesRedundancyExist (file: string) {
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 5a3e9e51a..4671ec5ac 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -11,7 +11,7 @@ import { CONFIG } from '../../../initializers/config'
11import { MIMETYPES } from '../../../initializers/constants' 11import { MIMETYPES } from '../../../initializers/constants'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { sequelizeTypescript } from '../../../initializers/database'
13import { sendUpdateActor } from '../../../lib/activitypub/send' 13import { sendUpdateActor } from '../../../lib/activitypub/send'
14import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../../lib/avatar' 14import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../../lib/actor-image'
15import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user' 15import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user'
16import { 16import {
17 asyncMiddleware, 17 asyncMiddleware,
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 03617dc8d..c9d8e1120 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -13,7 +13,7 @@ import { CONFIG } from '../../initializers/config'
13import { MIMETYPES } from '../../initializers/constants' 13import { MIMETYPES } from '../../initializers/constants'
14import { sequelizeTypescript } from '../../initializers/database' 14import { sequelizeTypescript } from '../../initializers/database'
15import { sendUpdateActor } from '../../lib/activitypub/send' 15import { sendUpdateActor } from '../../lib/activitypub/send'
16import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' 16import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/actor-image'
17import { JobQueue } from '../../lib/job-queue' 17import { JobQueue } from '../../lib/job-queue'
18import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' 18import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
19import { 19import {
diff --git a/server/controllers/lazy-static.ts b/server/controllers/lazy-static.ts
index 4e553479b..68b5c9eec 100644
--- a/server/controllers/lazy-static.ts
+++ b/server/controllers/lazy-static.ts
@@ -4,10 +4,10 @@ import { VideosTorrentCache } from '@server/lib/files-cache/videos-torrent-cache
4import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' 4import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' 6import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants'
7import { avatarPathUnsafeCache, pushAvatarProcessInQueue } from '../lib/avatar' 7import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image'
8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' 8import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache'
9import { asyncMiddleware } from '../middlewares' 9import { asyncMiddleware } from '../middlewares'
10import { AvatarModel } from '../models/avatar/avatar' 10import { ActorImageModel } from '../models/account/actor-image'
11 11
12const lazyStaticRouter = express.Router() 12const lazyStaticRouter = express.Router()
13 13
@@ -15,7 +15,12 @@ lazyStaticRouter.use(cors())
15 15
16lazyStaticRouter.use( 16lazyStaticRouter.use(
17 LAZY_STATIC_PATHS.AVATARS + ':filename', 17 LAZY_STATIC_PATHS.AVATARS + ':filename',
18 asyncMiddleware(getAvatar) 18 asyncMiddleware(getActorImage)
19)
20
21lazyStaticRouter.use(
22 LAZY_STATIC_PATHS.BANNERS + ':filename',
23 asyncMiddleware(getActorImage)
19) 24)
20 25
21lazyStaticRouter.use( 26lazyStaticRouter.use(
@@ -43,36 +48,36 @@ export {
43 48
44// --------------------------------------------------------------------------- 49// ---------------------------------------------------------------------------
45 50
46async function getAvatar (req: express.Request, res: express.Response) { 51async function getActorImage (req: express.Request, res: express.Response) {
47 const filename = req.params.filename 52 const filename = req.params.filename
48 53
49 if (avatarPathUnsafeCache.has(filename)) { 54 if (actorImagePathUnsafeCache.has(filename)) {
50 return res.sendFile(avatarPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) 55 return res.sendFile(actorImagePathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER })
51 } 56 }
52 57
53 const avatar = await AvatarModel.loadByName(filename) 58 const image = await ActorImageModel.loadByName(filename)
54 if (!avatar) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 59 if (!image) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
55 60
56 if (avatar.onDisk === false) { 61 if (image.onDisk === false) {
57 if (!avatar.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 62 if (!image.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
58 63
59 logger.info('Lazy serve remote avatar image %s.', avatar.fileUrl) 64 logger.info('Lazy serve remote actor image %s.', image.fileUrl)
60 65
61 try { 66 try {
62 await pushAvatarProcessInQueue({ filename: avatar.filename, fileUrl: avatar.fileUrl }) 67 await pushActorImageProcessInQueue({ filename: image.filename, fileUrl: image.fileUrl })
63 } catch (err) { 68 } catch (err) {
64 logger.warn('Cannot process remote avatar %s.', avatar.fileUrl, { err }) 69 logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err })
65 return res.sendStatus(HttpStatusCode.NOT_FOUND_404) 70 return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
66 } 71 }
67 72
68 avatar.onDisk = true 73 image.onDisk = true
69 avatar.save() 74 image.save()
70 .catch(err => logger.error('Cannot save new avatar disk state.', { err })) 75 .catch(err => logger.error('Cannot save new actor image disk state.', { err }))
71 } 76 }
72 77
73 const path = avatar.getPath() 78 const path = image.getPath()
74 79
75 avatarPathUnsafeCache.set(filename, path) 80 actorImagePathUnsafeCache.set(filename, path)
76 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) 81 return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER })
77} 82}
78 83
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 48e7f7397..93dd5ac04 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -59,7 +59,7 @@ const CONFIG = {
59 }, 59 },
60 STORAGE: { 60 STORAGE: {
61 TMP_DIR: buildPath(config.get<string>('storage.tmp')), 61 TMP_DIR: buildPath(config.get<string>('storage.tmp')),
62 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), 62 ACTOR_IMAGES: buildPath(config.get<string>('storage.avatars')),
63 LOG_DIR: buildPath(config.get<string>('storage.logs')), 63 LOG_DIR: buildPath(config.get<string>('storage.logs')),
64 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')), 64 VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
65 STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')), 65 STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')),
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 25e9aad9c..3f934688b 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -580,6 +580,7 @@ const STATIC_DOWNLOAD_PATHS = {
580 HLS_VIDEOS: '/download/streaming-playlists/hls/videos/' 580 HLS_VIDEOS: '/download/streaming-playlists/hls/videos/'
581} 581}
582const LAZY_STATIC_PATHS = { 582const LAZY_STATIC_PATHS = {
583 BANNERS: '/lazy-static/banners/',
583 AVATARS: '/lazy-static/avatars/', 584 AVATARS: '/lazy-static/avatars/',
584 PREVIEWS: '/lazy-static/previews/', 585 PREVIEWS: '/lazy-static/previews/',
585 VIDEO_CAPTIONS: '/lazy-static/video-captions/', 586 VIDEO_CAPTIONS: '/lazy-static/video-captions/',
@@ -634,7 +635,7 @@ const LRU_CACHE = {
634 USER_TOKENS: { 635 USER_TOKENS: {
635 MAX_SIZE: 1000 636 MAX_SIZE: 1000
636 }, 637 },
637 AVATAR_STATIC: { 638 ACTOR_IMAGE_STATIC: {
638 MAX_SIZE: 500 639 MAX_SIZE: 500
639 } 640 }
640} 641}
@@ -671,7 +672,7 @@ const MEMOIZE_LENGTH = {
671} 672}
672 673
673const QUEUE_CONCURRENCY = { 674const QUEUE_CONCURRENCY = {
674 AVATAR_PROCESS_IMAGE: 3 675 ACTOR_PROCESS_IMAGE: 3
675} 676}
676 677
677const REDUNDANCY = { 678const REDUNDANCY = {
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 8378fa982..4c9d7c610 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -1,7 +1,7 @@
1import { TrackerModel } from '@server/models/server/tracker'
2import { VideoTrackerModel } from '@server/models/server/video-tracker'
3import { QueryTypes, Transaction } from 'sequelize' 1import { QueryTypes, Transaction } from 'sequelize'
4import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' 2import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
3import { TrackerModel } from '@server/models/server/tracker'
4import { VideoTrackerModel } from '@server/models/server/video-tracker'
5import { isTestInstance } from '../helpers/core-utils' 5import { isTestInstance } from '../helpers/core-utils'
6import { logger } from '../helpers/logger' 6import { logger } from '../helpers/logger'
7import { AbuseModel } from '../models/abuse/abuse' 7import { AbuseModel } from '../models/abuse/abuse'
@@ -11,6 +11,7 @@ import { VideoCommentAbuseModel } from '../models/abuse/video-comment-abuse'
11import { AccountModel } from '../models/account/account' 11import { AccountModel } from '../models/account/account'
12import { AccountBlocklistModel } from '../models/account/account-blocklist' 12import { AccountBlocklistModel } from '../models/account/account-blocklist'
13import { AccountVideoRateModel } from '../models/account/account-video-rate' 13import { AccountVideoRateModel } from '../models/account/account-video-rate'
14import { ActorImageModel } from '../models/account/actor-image'
14import { UserModel } from '../models/account/user' 15import { UserModel } from '../models/account/user'
15import { UserNotificationModel } from '../models/account/user-notification' 16import { UserNotificationModel } from '../models/account/user-notification'
16import { UserNotificationSettingModel } from '../models/account/user-notification-setting' 17import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
@@ -18,7 +19,6 @@ import { UserVideoHistoryModel } from '../models/account/user-video-history'
18import { ActorModel } from '../models/activitypub/actor' 19import { ActorModel } from '../models/activitypub/actor'
19import { ActorFollowModel } from '../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../models/activitypub/actor-follow'
20import { ApplicationModel } from '../models/application/application' 21import { ApplicationModel } from '../models/application/application'
21import { AvatarModel } from '../models/avatar/avatar'
22import { OAuthClientModel } from '../models/oauth/oauth-client' 22import { OAuthClientModel } from '../models/oauth/oauth-client'
23import { OAuthTokenModel } from '../models/oauth/oauth-token' 23import { OAuthTokenModel } from '../models/oauth/oauth-token'
24import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' 24import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
@@ -95,7 +95,7 @@ async function initDatabaseModels (silent: boolean) {
95 ApplicationModel, 95 ApplicationModel,
96 ActorModel, 96 ActorModel,
97 ActorFollowModel, 97 ActorFollowModel,
98 AvatarModel, 98 ActorImageModel,
99 AccountModel, 99 AccountModel,
100 OAuthClientModel, 100 OAuthClientModel,
101 OAuthTokenModel, 101 OAuthTokenModel,
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 3c9a7ba02..da831dcfd 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -19,8 +19,8 @@ import { getUrlFromWebfinger } from '../../helpers/webfinger'
19import { MIMETYPES, WEBSERVER } from '../../initializers/constants' 19import { MIMETYPES, WEBSERVER } from '../../initializers/constants'
20import { sequelizeTypescript } from '../../initializers/database' 20import { sequelizeTypescript } from '../../initializers/database'
21import { AccountModel } from '../../models/account/account' 21import { AccountModel } from '../../models/account/account'
22import { ActorImageModel } from '../../models/account/actor-image'
22import { ActorModel } from '../../models/activitypub/actor' 23import { ActorModel } from '../../models/activitypub/actor'
23import { AvatarModel } from '../../models/avatar/avatar'
24import { ServerModel } from '../../models/server/server' 24import { ServerModel } from '../../models/server/server'
25import { VideoChannelModel } from '../../models/video/video-channel' 25import { VideoChannelModel } from '../../models/video/video-channel'
26import { 26import {
@@ -183,7 +183,7 @@ async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo
183 } 183 }
184 } 184 }
185 185
186 const avatar = await AvatarModel.create({ 186 const avatar = await ActorImageModel.create({
187 filename: info.name, 187 filename: info.name,
188 onDisk: info.onDisk, 188 onDisk: info.onDisk,
189 fileUrl: info.fileUrl 189 fileUrl: info.fileUrl
@@ -378,7 +378,7 @@ function saveActorAndServerAndModelIfNotExist (
378 378
379 // Avatar? 379 // Avatar?
380 if (result.avatar) { 380 if (result.avatar) {
381 const avatar = await AvatarModel.create({ 381 const avatar = await ActorImageModel.create({
382 filename: result.avatar.name, 382 filename: result.avatar.name,
383 fileUrl: result.avatar.fileUrl, 383 fileUrl: result.avatar.fileUrl,
384 onDisk: false 384 onDisk: false
diff --git a/server/lib/avatar.ts b/server/lib/actor-image.ts
index 86f1e7bdb..ca7f9658d 100644
--- a/server/lib/avatar.ts
+++ b/server/lib/actor-image.ts
@@ -1,17 +1,17 @@
1import 'multer' 1import 'multer'
2import { sendUpdateActor } from './activitypub/send' 2import { queue } from 'async'
3import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' 3import * as LRUCache from 'lru-cache'
4import { updateActorAvatarInstance, deleteActorAvatarInstance } from './activitypub/actor'
5import { processImage } from '../helpers/image-utils'
6import { extname, join } from 'path' 4import { extname, join } from 'path'
7import { retryTransactionWrapper } from '../helpers/database-utils'
8import { v4 as uuidv4 } from 'uuid' 5import { v4 as uuidv4 } from 'uuid'
6import { retryTransactionWrapper } from '../helpers/database-utils'
7import { processImage } from '../helpers/image-utils'
8import { downloadImage } from '../helpers/requests'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants'
10import { sequelizeTypescript } from '../initializers/database' 11import { sequelizeTypescript } from '../initializers/database'
11import * as LRUCache from 'lru-cache'
12import { queue } from 'async'
13import { downloadImage } from '../helpers/requests'
14import { MAccountDefault, MChannelDefault } from '../types/models' 12import { MAccountDefault, MChannelDefault } from '../types/models'
13import { deleteActorAvatarInstance, updateActorAvatarInstance } from './activitypub/actor'
14import { sendUpdateActor } from './activitypub/send'
15 15
16async function updateLocalActorAvatarFile ( 16async function updateLocalActorAvatarFile (
17 accountOrChannel: MAccountDefault | MChannelDefault, 17 accountOrChannel: MAccountDefault | MChannelDefault,
@@ -20,7 +20,7 @@ async function updateLocalActorAvatarFile (
20 const extension = extname(avatarPhysicalFile.filename) 20 const extension = extname(avatarPhysicalFile.filename)
21 21
22 const avatarName = uuidv4() + extension 22 const avatarName = uuidv4() + extension
23 const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) 23 const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, avatarName)
24 await processImage(avatarPhysicalFile.path, destination, AVATARS_SIZE) 24 await processImage(avatarPhysicalFile.path, destination, AVATARS_SIZE)
25 25
26 return retryTransactionWrapper(() => { 26 return retryTransactionWrapper(() => {
@@ -59,12 +59,12 @@ async function deleteLocalActorAvatarFile (
59type DownloadImageQueueTask = { fileUrl: string, filename: string } 59type DownloadImageQueueTask = { fileUrl: string, filename: string }
60 60
61const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => { 61const downloadImageQueue = queue<DownloadImageQueueTask, Error>((task, cb) => {
62 downloadImage(task.fileUrl, CONFIG.STORAGE.AVATARS_DIR, task.filename, AVATARS_SIZE) 62 downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, AVATARS_SIZE)
63 .then(() => cb()) 63 .then(() => cb())
64 .catch(err => cb(err)) 64 .catch(err => cb(err))
65}, QUEUE_CONCURRENCY.AVATAR_PROCESS_IMAGE) 65}, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE)
66 66
67function pushAvatarProcessInQueue (task: DownloadImageQueueTask) { 67function pushActorImageProcessInQueue (task: DownloadImageQueueTask) {
68 return new Promise<void>((res, rej) => { 68 return new Promise<void>((res, rej) => {
69 downloadImageQueue.push(task, err => { 69 downloadImageQueue.push(task, err => {
70 if (err) return rej(err) 70 if (err) return rej(err)
@@ -75,11 +75,11 @@ function pushAvatarProcessInQueue (task: DownloadImageQueueTask) {
75} 75}
76 76
77// Unsafe so could returns paths that does not exist anymore 77// Unsafe so could returns paths that does not exist anymore
78const avatarPathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.AVATAR_STATIC.MAX_SIZE }) 78const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.ACTOR_IMAGE_STATIC.MAX_SIZE })
79 79
80export { 80export {
81 avatarPathUnsafeCache, 81 actorImagePathUnsafeCache,
82 updateLocalActorAvatarFile, 82 updateLocalActorAvatarFile,
83 deleteLocalActorAvatarFile, 83 deleteLocalActorAvatarFile,
84 pushAvatarProcessInQueue 84 pushActorImageProcessInQueue
85} 85}
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index c72f9c63d..312451abe 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -33,7 +33,7 @@ import {
33import { ActorModel } from '../activitypub/actor' 33import { ActorModel } from '../activitypub/actor'
34import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { ApplicationModel } from '../application/application' 35import { ApplicationModel } from '../application/application'
36import { AvatarModel } from '../avatar/avatar' 36import { ActorImageModel } from './actor-image'
37import { ServerModel } from '../server/server' 37import { ServerModel } from '../server/server'
38import { ServerBlocklistModel } from '../server/server-blocklist' 38import { ServerBlocklistModel } from '../server/server-blocklist'
39import { getSort, throwIfNotValid } from '../utils' 39import { getSort, throwIfNotValid } from '../utils'
@@ -82,7 +82,8 @@ export type SummaryOptions = {
82 serverInclude, 82 serverInclude,
83 83
84 { 84 {
85 model: AvatarModel.unscoped(), 85 model: ActorImageModel.unscoped(),
86 as: 'Avatar',
86 required: false 87 required: false
87 } 88 }
88 ] 89 ]
diff --git a/server/models/avatar/avatar.ts b/server/models/account/actor-image.ts
index 0d246a144..c532bd08d 100644
--- a/server/models/avatar/avatar.ts
+++ b/server/models/account/actor-image.ts
@@ -1,16 +1,17 @@
1import { remove } from 'fs-extra'
1import { join } from 'path' 2import { join } from 'path'
2import { AfterDestroy, AllowNull, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AfterDestroy, AllowNull, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { Avatar } from '../../../shared/models/avatars/avatar.model' 4import { MActorImageFormattable } from '@server/types/models'
4import { LAZY_STATIC_PATHS } from '../../initializers/constants' 5import { ActorImageType } from '@shared/models'
6import { ActorImage } from '../../../shared/models/actors/actor-image.model'
7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
5import { logger } from '../../helpers/logger' 8import { logger } from '../../helpers/logger'
6import { remove } from 'fs-extra'
7import { CONFIG } from '../../initializers/config' 9import { CONFIG } from '../../initializers/config'
10import { LAZY_STATIC_PATHS } from '../../initializers/constants'
8import { throwIfNotValid } from '../utils' 11import { throwIfNotValid } from '../utils'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { MAvatarFormattable } from '@server/types/models'
11 12
12@Table({ 13@Table({
13 tableName: 'avatar', 14 tableName: 'actorImage',
14 indexes: [ 15 indexes: [
15 { 16 {
16 fields: [ 'filename' ], 17 fields: [ 'filename' ],
@@ -18,14 +19,14 @@ import { MAvatarFormattable } from '@server/types/models'
18 } 19 }
19 ] 20 ]
20}) 21})
21export class AvatarModel extends Model { 22export class ActorImageModel extends Model {
22 23
23 @AllowNull(false) 24 @AllowNull(false)
24 @Column 25 @Column
25 filename: string 26 filename: string
26 27
27 @AllowNull(true) 28 @AllowNull(true)
28 @Is('AvatarFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true)) 29 @Is('ActorImageFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true))
29 @Column 30 @Column
30 fileUrl: string 31 fileUrl: string
31 32
@@ -33,6 +34,10 @@ export class AvatarModel extends Model {
33 @Column 34 @Column
34 onDisk: boolean 35 onDisk: boolean
35 36
37 @AllowNull(false)
38 @Column
39 type: ActorImageType
40
36 @CreatedAt 41 @CreatedAt
37 createdAt: Date 42 createdAt: Date
38 43
@@ -40,12 +45,12 @@ export class AvatarModel extends Model {
40 updatedAt: Date 45 updatedAt: Date
41 46
42 @AfterDestroy 47 @AfterDestroy
43 static removeFilesAndSendDelete (instance: AvatarModel) { 48 static removeFilesAndSendDelete (instance: ActorImageModel) {
44 logger.info('Removing avatar file %s.', instance.filename) 49 logger.info('Removing actor image file %s.', instance.filename)
45 50
46 // Don't block the transaction 51 // Don't block the transaction
47 instance.removeAvatar() 52 instance.removeImage()
48 .catch(err => logger.error('Cannot remove avatar file %s.', instance.filename, err)) 53 .catch(err => logger.error('Cannot remove actor image file %s.', instance.filename, err))
49 } 54 }
50 55
51 static loadByName (filename: string) { 56 static loadByName (filename: string) {
@@ -55,10 +60,10 @@ export class AvatarModel extends Model {
55 } 60 }
56 } 61 }
57 62
58 return AvatarModel.findOne(query) 63 return ActorImageModel.findOne(query)
59 } 64 }
60 65
61 toFormattedJSON (this: MAvatarFormattable): Avatar { 66 toFormattedJSON (this: MActorImageFormattable): ActorImage {
62 return { 67 return {
63 path: this.getStaticPath(), 68 path: this.getStaticPath(),
64 createdAt: this.createdAt, 69 createdAt: this.createdAt,
@@ -71,11 +76,11 @@ export class AvatarModel extends Model {
71 } 76 }
72 77
73 getPath () { 78 getPath () {
74 return join(CONFIG.STORAGE.AVATARS_DIR, this.filename) 79 return join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename)
75 } 80 }
76 81
77 removeAvatar () { 82 removeImage () {
78 const avatarPath = join(CONFIG.STORAGE.AVATARS_DIR, this.filename) 83 const imagePath = join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename)
79 return remove(avatarPath) 84 return remove(imagePath)
80 } 85 }
81} 86}
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index 25c523203..805095002 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -10,7 +10,6 @@ import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
10import { ActorModel } from '../activitypub/actor' 10import { ActorModel } from '../activitypub/actor'
11import { ActorFollowModel } from '../activitypub/actor-follow' 11import { ActorFollowModel } from '../activitypub/actor-follow'
12import { ApplicationModel } from '../application/application' 12import { ApplicationModel } from '../application/application'
13import { AvatarModel } from '../avatar/avatar'
14import { PluginModel } from '../server/plugin' 13import { PluginModel } from '../server/plugin'
15import { ServerModel } from '../server/server' 14import { ServerModel } from '../server/server'
16import { getSort, throwIfNotValid } from '../utils' 15import { getSort, throwIfNotValid } from '../utils'
@@ -20,6 +19,7 @@ import { VideoChannelModel } from '../video/video-channel'
20import { VideoCommentModel } from '../video/video-comment' 19import { VideoCommentModel } from '../video/video-comment'
21import { VideoImportModel } from '../video/video-import' 20import { VideoImportModel } from '../video/video-import'
22import { AccountModel } from './account' 21import { AccountModel } from './account'
22import { ActorImageModel } from './actor-image'
23import { UserModel } from './user' 23import { UserModel } from './user'
24 24
25enum ScopeNames { 25enum ScopeNames {
@@ -34,7 +34,8 @@ function buildActorWithAvatarInclude () {
34 include: [ 34 include: [
35 { 35 {
36 attributes: [ 'filename' ], 36 attributes: [ 'filename' ],
37 model: AvatarModel.unscoped(), 37 as: 'Avatar',
38 model: ActorImageModel.unscoped(),
38 required: false 39 required: false
39 }, 40 },
40 { 41 {
@@ -172,7 +173,8 @@ function buildAccountInclude (required: boolean, withActor = false) {
172 }, 173 },
173 { 174 {
174 attributes: [ 'filename' ], 175 attributes: [ 'filename' ],
175 model: AvatarModel.unscoped(), 176 as: 'Avatar',
177 model: ActorImageModel.unscoped(),
176 required: false 178 required: false
177 }, 179 },
178 { 180 {
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 3b98e8841..09d96b24d 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -19,7 +19,7 @@ import {
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { ModelCache } from '@server/models/model-cache' 20import { ModelCache } from '@server/models/model-cache'
21import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' 21import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
22import { Avatar } from '../../../shared/models/avatars/avatar.model' 22import { ActorImage } from '../../../shared/models/actors/actor-image.model'
23import { activityPubContextify } from '../../helpers/activitypub' 23import { activityPubContextify } from '../../helpers/activitypub'
24import { 24import {
25 isActorFollowersCountValid, 25 isActorFollowersCountValid,
@@ -43,7 +43,7 @@ import {
43 MActorWithInboxes 43 MActorWithInboxes
44} from '../../types/models' 44} from '../../types/models'
45import { AccountModel } from '../account/account' 45import { AccountModel } from '../account/account'
46import { AvatarModel } from '../avatar/avatar' 46import { ActorImageModel } from '../account/actor-image'
47import { ServerModel } from '../server/server' 47import { ServerModel } from '../server/server'
48import { isOutdated, throwIfNotValid } from '../utils' 48import { isOutdated, throwIfNotValid } from '../utils'
49import { VideoModel } from '../video/video' 49import { VideoModel } from '../video/video'
@@ -73,7 +73,8 @@ export const unusedActorAttributesForAPI = [
73 required: false 73 required: false
74 }, 74 },
75 { 75 {
76 model: AvatarModel, 76 model: ActorImageModel,
77 as: 'Avatar',
77 required: false 78 required: false
78 } 79 }
79 ] 80 ]
@@ -100,7 +101,8 @@ export const unusedActorAttributesForAPI = [
100 required: false 101 required: false
101 }, 102 },
102 { 103 {
103 model: AvatarModel, 104 model: ActorImageModel,
105 as: 'Avatar',
104 required: false 106 required: false
105 } 107 }
106 ] 108 ]
@@ -213,18 +215,35 @@ export class ActorModel extends Model {
213 @UpdatedAt 215 @UpdatedAt
214 updatedAt: Date 216 updatedAt: Date
215 217
216 @ForeignKey(() => AvatarModel) 218 @ForeignKey(() => ActorImageModel)
217 @Column 219 @Column
218 avatarId: number 220 avatarId: number
219 221
220 @BelongsTo(() => AvatarModel, { 222 @ForeignKey(() => ActorImageModel)
223 @Column
224 bannerId: number
225
226 @BelongsTo(() => ActorImageModel, {
227 foreignKey: {
228 name: 'avatarId',
229 allowNull: true
230 },
231 as: 'Avatar',
232 onDelete: 'set null',
233 hooks: true
234 })
235 Avatar: ActorImageModel
236
237 @BelongsTo(() => ActorImageModel, {
221 foreignKey: { 238 foreignKey: {
239 name: 'bannerId',
222 allowNull: true 240 allowNull: true
223 }, 241 },
242 as: 'Banner',
224 onDelete: 'set null', 243 onDelete: 'set null',
225 hooks: true 244 hooks: true
226 }) 245 })
227 Avatar: AvatarModel 246 Banner: ActorImageModel
228 247
229 @HasMany(() => ActorFollowModel, { 248 @HasMany(() => ActorFollowModel, {
230 foreignKey: { 249 foreignKey: {
@@ -496,7 +515,7 @@ export class ActorModel extends Model {
496 } 515 }
497 516
498 toFormattedSummaryJSON (this: MActorSummaryFormattable) { 517 toFormattedSummaryJSON (this: MActorSummaryFormattable) {
499 let avatar: Avatar = null 518 let avatar: ActorImage = null
500 if (this.Avatar) { 519 if (this.Avatar) {
501 avatar = this.Avatar.toFormattedJSON() 520 avatar = this.Avatar.toFormattedJSON()
502 } 521 }
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 178878c55..815fb16c0 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -36,9 +36,9 @@ import {
36 MChannelSummaryFormattable 36 MChannelSummaryFormattable
37} from '../../types/models/video' 37} from '../../types/models/video'
38import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 38import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
39import { ActorImageModel } from '../account/actor-image'
39import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 40import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
40import { ActorFollowModel } from '../activitypub/actor-follow' 41import { ActorFollowModel } from '../activitypub/actor-follow'
41import { AvatarModel } from '../avatar/avatar'
42import { ServerModel } from '../server/server' 42import { ServerModel } from '../server/server'
43import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' 43import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
44import { VideoModel } from './video' 44import { VideoModel } from './video'
@@ -130,7 +130,8 @@ export type SummaryOptions = {
130 required: false 130 required: false
131 }, 131 },
132 { 132 {
133 model: AvatarModel.unscoped(), 133 model: ActorImageModel.unscoped(),
134 as: 'Avatar',
134 required: false 135 required: false
135 } 136 }
136 ] 137 ]
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts
index 96df0a7f8..4d95ddee2 100644
--- a/server/models/video/video-query-builder.ts
+++ b/server/models/video/video-query-builder.ts
@@ -490,12 +490,13 @@ function wrapForAPIResults (baseQuery: string, replacements: any, options: Build
490 'INNER JOIN "actor" AS "VideoChannel->Account->Actor" ON "VideoChannel->Account"."actorId" = "VideoChannel->Account->Actor"."id"', 490 'INNER JOIN "actor" AS "VideoChannel->Account->Actor" ON "VideoChannel->Account"."actorId" = "VideoChannel->Account->Actor"."id"',
491 491
492 'LEFT OUTER JOIN "server" AS "VideoChannel->Actor->Server" ON "VideoChannel->Actor"."serverId" = "VideoChannel->Actor->Server"."id"', 492 'LEFT OUTER JOIN "server" AS "VideoChannel->Actor->Server" ON "VideoChannel->Actor"."serverId" = "VideoChannel->Actor->Server"."id"',
493 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Actor->Avatar" ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"', 493 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Actor->Avatar" ' +
494 'ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"',
494 495
495 'LEFT OUTER JOIN "server" AS "VideoChannel->Account->Actor->Server" ' + 496 'LEFT OUTER JOIN "server" AS "VideoChannel->Account->Actor->Server" ' +
496 'ON "VideoChannel->Account->Actor"."serverId" = "VideoChannel->Account->Actor->Server"."id"', 497 'ON "VideoChannel->Account->Actor"."serverId" = "VideoChannel->Account->Actor->Server"."id"',
497 498
498 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Account->Actor->Avatar" ' + 499 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Account->Actor->Avatar" ' +
499 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"', 500 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"',
500 501
501 'LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"' 502 'LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"'
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 9d89efa5b..086269921 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -100,10 +100,10 @@ import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models
100import { VideoAbuseModel } from '../abuse/video-abuse' 100import { VideoAbuseModel } from '../abuse/video-abuse'
101import { AccountModel } from '../account/account' 101import { AccountModel } from '../account/account'
102import { AccountVideoRateModel } from '../account/account-video-rate' 102import { AccountVideoRateModel } from '../account/account-video-rate'
103import { ActorImageModel } from '../account/actor-image'
103import { UserModel } from '../account/user' 104import { UserModel } from '../account/user'
104import { UserVideoHistoryModel } from '../account/user-video-history' 105import { UserVideoHistoryModel } from '../account/user-video-history'
105import { ActorModel } from '../activitypub/actor' 106import { ActorModel } from '../activitypub/actor'
106import { AvatarModel } from '../avatar/avatar'
107import { VideoRedundancyModel } from '../redundancy/video-redundancy' 107import { VideoRedundancyModel } from '../redundancy/video-redundancy'
108import { ServerModel } from '../server/server' 108import { ServerModel } from '../server/server'
109import { TrackerModel } from '../server/tracker' 109import { TrackerModel } from '../server/tracker'
@@ -286,7 +286,8 @@ export type AvailableForListIDsOptions = {
286 required: false 286 required: false
287 }, 287 },
288 { 288 {
289 model: AvatarModel.unscoped(), 289 model: ActorImageModel.unscoped(),
290 as: 'Avatar',
290 required: false 291 required: false
291 } 292 }
292 ] 293 ]
@@ -308,7 +309,8 @@ export type AvailableForListIDsOptions = {
308 required: false 309 required: false
309 }, 310 },
310 { 311 {
311 model: AvatarModel.unscoped(), 312 model: ActorImageModel.unscoped(),
313 as: 'Avatar',
312 required: false 314 required: false
313 } 315 }
314 ] 316 ]
@@ -1703,7 +1705,7 @@ export class VideoModel extends Model {
1703 1705
1704 function buildActor (rowActor: any) { 1706 function buildActor (rowActor: any) {
1705 const avatarModel = rowActor.Avatar.id !== null 1707 const avatarModel = rowActor.Avatar.id !== null
1706 ? new AvatarModel(pick(rowActor.Avatar, avatarKeys), buildOpts) 1708 ? new ActorImageModel(pick(rowActor.Avatar, avatarKeys), buildOpts)
1707 : null 1709 : null
1708 1710
1709 const serverModel = rowActor.Server.id !== null 1711 const serverModel = rowActor.Server.id !== null
diff --git a/server/types/models/account/actor-image.ts b/server/types/models/account/actor-image.ts
new file mode 100644
index 000000000..e59f8b141
--- /dev/null
+++ b/server/types/models/account/actor-image.ts
@@ -0,0 +1,12 @@
1import { ActorImageModel } from '../../../models/account/actor-image'
2import { FunctionProperties } from '@shared/core-utils'
3
4export type MActorImage = ActorImageModel
5
6// ############################################################################
7
8// Format for API or AP object
9
10export type MActorImageFormattable =
11 FunctionProperties<MActorImage> &
12 Pick<MActorImage, 'filename' | 'createdAt' | 'updatedAt'>
diff --git a/server/types/models/account/actor.ts b/server/types/models/account/actor.ts
index ee0d05f4e..8af19c4da 100644
--- a/server/types/models/account/actor.ts
+++ b/server/types/models/account/actor.ts
@@ -1,15 +1,15 @@
1import { ActorModel } from '../../../models/activitypub/actor'
2import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' 1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils'
3import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' 2import { ActorModel } from '../../../models/activitypub/actor'
4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' 3import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server'
5import { MAvatar, MAvatarFormattable } from './avatar'
6import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' 4import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video'
5import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account'
6import { MActorImage, MActorImageFormattable } from './actor-image'
7 7
8type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> 8type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M>
9 9
10// ############################################################################ 10// ############################################################################
11 11
12export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'> 12export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server' | 'Banner'>
13 13
14// ############################################################################ 14// ############################################################################
15 15
@@ -34,7 +34,7 @@ export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServ
34export type MActorDefaultLight = 34export type MActorDefaultLight =
35 MActorLight & 35 MActorLight &
36 Use<'Server', MServerHost> & 36 Use<'Server', MServerHost> &
37 Use<'Avatar', MAvatar> 37 Use<'Avatar', MActorImage>
38 38
39export type MActorAccountId = 39export type MActorAccountId =
40 MActor & 40 MActor &
@@ -78,7 +78,7 @@ export type MActorServer =
78export type MActorDefault = 78export type MActorDefault =
79 MActor & 79 MActor &
80 Use<'Server', MServer> & 80 Use<'Server', MServer> &
81 Use<'Avatar', MAvatar> 81 Use<'Avatar', MActorImage>
82 82
83// Actor with channel that is associated to an account and its actor 83// Actor with channel that is associated to an account and its actor
84// Actor -> VideoChannel -> Account -> Actor 84// Actor -> VideoChannel -> Account -> Actor
@@ -89,7 +89,7 @@ export type MActorChannelAccountActor =
89export type MActorFull = 89export type MActorFull =
90 MActor & 90 MActor &
91 Use<'Server', MServer> & 91 Use<'Server', MServer> &
92 Use<'Avatar', MAvatar> & 92 Use<'Avatar', MActorImage> &
93 Use<'Account', MAccount> & 93 Use<'Account', MAccount> &
94 Use<'VideoChannel', MChannelAccountActor> 94 Use<'VideoChannel', MChannelAccountActor>
95 95
@@ -97,7 +97,7 @@ export type MActorFull =
97export type MActorFullActor = 97export type MActorFullActor =
98 MActor & 98 MActor &
99 Use<'Server', MServer> & 99 Use<'Server', MServer> &
100 Use<'Avatar', MAvatar> & 100 Use<'Avatar', MActorImage> &
101 Use<'Account', MAccountDefault> & 101 Use<'Account', MAccountDefault> &
102 Use<'VideoChannel', MChannelAccountDefault> 102 Use<'VideoChannel', MChannelAccountDefault>
103 103
@@ -109,7 +109,7 @@ export type MActorSummary =
109 FunctionProperties<MActor> & 109 FunctionProperties<MActor> &
110 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & 110 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
111 Use<'Server', MServerHost> & 111 Use<'Server', MServerHost> &
112 Use<'Avatar', MAvatar> 112 Use<'Avatar', MActorImage>
113 113
114export type MActorSummaryBlocks = 114export type MActorSummaryBlocks =
115 MActorSummary & 115 MActorSummary &
@@ -127,7 +127,7 @@ export type MActorSummaryFormattable =
127 FunctionProperties<MActor> & 127 FunctionProperties<MActor> &
128 Pick<MActor, 'url' | 'preferredUsername'> & 128 Pick<MActor, 'url' | 'preferredUsername'> &
129 Use<'Server', MServerHost> & 129 Use<'Server', MServerHost> &
130 Use<'Avatar', MAvatarFormattable> 130 Use<'Avatar', MActorImageFormattable>
131 131
132export type MActorFormattable = 132export type MActorFormattable =
133 MActorSummaryFormattable & 133 MActorSummaryFormattable &
@@ -136,4 +136,4 @@ export type MActorFormattable =
136 136
137export type MActorAP = 137export type MActorAP =
138 MActor & 138 MActor &
139 Use<'Avatar', MAvatar> 139 Use<'Avatar', MActorImage>
diff --git a/server/types/models/account/avatar.ts b/server/types/models/account/avatar.ts
deleted file mode 100644
index 0489a8599..000000000
--- a/server/types/models/account/avatar.ts
+++ /dev/null
@@ -1,12 +0,0 @@
1import { AvatarModel } from '../../../models/avatar/avatar'
2import { FunctionProperties } from '@shared/core-utils'
3
4export type MAvatar = AvatarModel
5
6// ############################################################################
7
8// Format for API or AP object
9
10export type MAvatarFormattable =
11 FunctionProperties<MAvatar> &
12 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'>
diff --git a/server/types/models/account/index.ts b/server/types/models/account/index.ts
index 513c09c40..e3fc00f94 100644
--- a/server/types/models/account/index.ts
+++ b/server/types/models/account/index.ts
@@ -1,5 +1,5 @@
1export * from './account' 1export * from './account'
2export * from './account-blocklist' 2export * from './account-blocklist'
3export * from './actor'
4export * from './actor-follow' 3export * from './actor-follow'
5export * from './avatar' 4export * from './actor-image'
5export * from './actor'
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts
index 6988086f1..7ebb0485d 100644
--- a/server/types/models/user/user-notification.ts
+++ b/server/types/models/user/user-notification.ts
@@ -5,10 +5,10 @@ import { PluginModel } from '@server/models/server/plugin'
5import { PickWith, PickWithOpt } from '@shared/core-utils' 5import { PickWith, PickWithOpt } from '@shared/core-utils'
6import { AbuseModel } from '../../../models/abuse/abuse' 6import { AbuseModel } from '../../../models/abuse/abuse'
7import { AccountModel } from '../../../models/account/account' 7import { AccountModel } from '../../../models/account/account'
8import { ActorImageModel } from '../../../models/account/actor-image'
8import { UserNotificationModel } from '../../../models/account/user-notification' 9import { UserNotificationModel } from '../../../models/account/user-notification'
9import { ActorModel } from '../../../models/activitypub/actor' 10import { ActorModel } from '../../../models/activitypub/actor'
10import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 11import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
11import { AvatarModel } from '../../../models/avatar/avatar'
12import { ServerModel } from '../../../models/server/server' 12import { ServerModel } from '../../../models/server/server'
13import { VideoModel } from '../../../models/video/video' 13import { VideoModel } from '../../../models/video/video'
14import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 14import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
@@ -29,7 +29,7 @@ export module UserNotificationIncludes {
29 29
30 export type ActorInclude = 30 export type ActorInclude =
31 Pick<ActorModel, 'preferredUsername' | 'getHost'> & 31 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
32 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & 32 PickWith<ActorModel, 'Avatar', Pick<ActorImageModel, 'filename' | 'getStaticPath'>> &
33 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> 33 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
34 34
35 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> 35 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
@@ -75,7 +75,7 @@ export module UserNotificationIncludes {
75 Pick<ActorModel, 'preferredUsername' | 'getHost'> & 75 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
76 PickWith<ActorModel, 'Account', AccountInclude> & 76 PickWith<ActorModel, 'Account', AccountInclude> &
77 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & 77 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> &
78 PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> 78 PickWithOpt<ActorModel, 'Avatar', Pick<ActorImageModel, 'filename' | 'getStaticPath'>>
79 79
80 export type ActorFollowing = 80 export type ActorFollowing =
81 Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & 81 Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> &
diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts
index 2ff4b9f5e..120dec271 100644
--- a/shared/models/actors/account.model.ts
+++ b/shared/models/actors/account.model.ts
@@ -1,5 +1,5 @@
1import { ActorImage } from './actor-image.model'
1import { Actor } from './actor.model' 2import { Actor } from './actor.model'
2import { Avatar } from '../avatars'
3 3
4export interface Account extends Actor { 4export interface Account extends Actor {
5 displayName: string 5 displayName: string
@@ -14,5 +14,5 @@ export interface AccountSummary {
14 displayName: string 14 displayName: string
15 url: string 15 url: string
16 host: string 16 host: string
17 avatar?: Avatar 17 avatar?: ActorImage
18} 18}
diff --git a/shared/models/avatars/avatar.model.ts b/shared/models/actors/actor-image.model.ts
index f7fa16f49..ad5eab627 100644
--- a/shared/models/avatars/avatar.model.ts
+++ b/shared/models/actors/actor-image.model.ts
@@ -1,4 +1,4 @@
1export interface Avatar { 1export interface ActorImage {
2 path: string 2 path: string
3 3
4 url?: string 4 url?: string
diff --git a/shared/models/actors/actor-image.type.ts b/shared/models/actors/actor-image.type.ts
new file mode 100644
index 000000000..ac8eb6bf2
--- /dev/null
+++ b/shared/models/actors/actor-image.type.ts
@@ -0,0 +1,4 @@
1export const enum ActorImageType {
2 AVATAR = 1,
3 BANNER = 2
4}
diff --git a/shared/models/actors/actor.model.ts b/shared/models/actors/actor.model.ts
index 1dbf5f638..7d9f35b10 100644
--- a/shared/models/actors/actor.model.ts
+++ b/shared/models/actors/actor.model.ts
@@ -1,4 +1,4 @@
1import { Avatar } from '../avatars/avatar.model' 1import { ActorImage } from './actor-image.model'
2 2
3export interface Actor { 3export interface Actor {
4 id: number 4 id: number
@@ -9,5 +9,5 @@ export interface Actor {
9 followersCount: number 9 followersCount: number
10 createdAt: Date | string 10 createdAt: Date | string
11 updatedAt: Date | string 11 updatedAt: Date | string
12 avatar?: Avatar 12 avatar?: ActorImage
13} 13}
diff --git a/shared/models/actors/index.ts b/shared/models/actors/index.ts
index c7a92399d..156f83248 100644
--- a/shared/models/actors/index.ts
+++ b/shared/models/actors/index.ts
@@ -1,3 +1,5 @@
1export * from './account.model' 1export * from './account.model'
2export * from './actor-image.model'
3export * from './actor-image.type'
2export * from './actor.model' 4export * from './actor.model'
3export * from './follow.model' 5export * from './follow.model'
diff --git a/shared/models/avatars/index.ts b/shared/models/avatars/index.ts
deleted file mode 100644
index 65e8e0882..000000000
--- a/shared/models/avatars/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './avatar.model'
diff --git a/shared/models/index.ts b/shared/models/index.ts
index f105303f4..dff5fdf0e 100644
--- a/shared/models/index.ts
+++ b/shared/models/index.ts
@@ -1,6 +1,5 @@
1export * from './activitypub' 1export * from './activitypub'
2export * from './actors' 2export * from './actors'
3export * from './avatars'
4export * from './moderation' 3export * from './moderation'
5export * from './bulk' 4export * from './bulk'
6export * from './redundancy' 5export * from './redundancy'
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index 32829e92a..ae6dea42d 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -1,6 +1,5 @@
1import { Actor } from '../../actors/actor.model' 1import { Actor } from '../../actors/actor.model'
2import { Account } from '../../actors/index' 2import { Account, ActorImage } from '../../actors'
3import { Avatar } from '../../avatars'
4 3
5export type ViewsPerDate = { 4export type ViewsPerDate = {
6 date: Date 5 date: Date
@@ -24,5 +23,5 @@ export interface VideoChannelSummary {
24 displayName: string 23 displayName: string
25 url: string 24 url: string
26 host: string 25 host: string
27 avatar?: Avatar 26 avatar?: ActorImage
28} 27}