From f479685678406a5df864d89615b33d29085ebfc6 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 6 Apr 2021 11:35:56 +0200 Subject: [PATCH] Agnostic actor image storage --- client/src/app/core/users/user.model.ts | 4 +- client/src/app/core/users/user.service.ts | 5 +-- .../shared-main/account/account.model.ts | 4 +- .../shared/shared-main/account/actor.model.ts | 4 +- .../video-channel/video-channel.model.ts | 4 +- .../video-channel/video-channel.service.ts | 4 +- .../shared/shared-main/video/video.model.ts | 6 +-- scripts/prune-storage.ts | 10 ++--- server/controllers/api/users/me.ts | 2 +- server/controllers/api/video-channel.ts | 2 +- server/controllers/lazy-static.ts | 41 +++++++++-------- server/initializers/config.ts | 2 +- server/initializers/constants.ts | 5 ++- server/initializers/database.ts | 8 ++-- server/lib/activitypub/actor.ts | 6 +-- server/lib/{avatar.ts => actor-image.ts} | 34 +++++++------- server/models/account/account.ts | 5 ++- .../avatar.ts => account/actor-image.ts} | 45 ++++++++++--------- server/models/account/user-notification.ts | 8 ++-- server/models/activitypub/actor.ts | 35 +++++++++++---- server/models/video/video-channel.ts | 5 ++- server/models/video/video-query-builder.ts | 5 ++- server/models/video/video.ts | 10 +++-- server/types/models/account/actor-image.ts | 12 +++++ server/types/models/account/actor.ts | 22 ++++----- server/types/models/account/avatar.ts | 12 ----- server/types/models/account/index.ts | 4 +- server/types/models/user/user-notification.ts | 6 +-- shared/models/actors/account.model.ts | 4 +- .../actor-image.model.ts} | 2 +- shared/models/actors/actor-image.type.ts | 4 ++ shared/models/actors/actor.model.ts | 4 +- shared/models/actors/index.ts | 2 + shared/models/avatars/index.ts | 1 - shared/models/index.ts | 1 - .../videos/channel/video-channel.model.ts | 5 +-- 36 files changed, 186 insertions(+), 147 deletions(-) rename server/lib/{avatar.ts => actor-image.ts} (82%) rename server/models/{avatar/avatar.ts => account/actor-image.ts} (55%) create mode 100644 server/types/models/account/actor-image.ts delete mode 100644 server/types/models/account/avatar.ts rename shared/models/{avatars/avatar.model.ts => actors/actor-image.model.ts} (74%) create mode 100644 shared/models/actors/actor-image.type.ts delete mode 100644 shared/models/avatars/index.ts 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 @@ import { Account } from '@app/shared/shared-main/account/account.model' import { hasUserRight } from '@shared/core-utils/users' import { - Avatar, + ActorImage, NSFWPolicyType, User as UserServerModel, UserAdminFlag, @@ -131,7 +131,7 @@ export class User implements UserServerModel { } } - updateAccountAvatar (newAccountAvatar?: Avatar) { + updateAccountAvatar (newAccountAvatar?: ActorImage) { if (newAccountAvatar) this.account.updateAvatar(newAccountAvatar) else this.account.resetAvatar() } 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' import { getBytes } from '@root-helpers/bytes' import { UserLocalStorageKeys } from '@root-helpers/users' import { - Avatar, - NSFWPolicyType, + ActorImage, ResultList, User as UserServerModel, UserCreate, @@ -136,7 +135,7 @@ export class UserService { changeAvatar (avatarForm: FormData) { const url = UserService.BASE_USERS_URL + 'me/avatar/pick' - return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) + return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm) .pipe(catchError(err => this.restExtractor.handleError(err))) } 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 @@ -import { Account as ServerAccount, Avatar } from '@shared/models' +import { Account as ServerAccount, ActorImage } from '@shared/models' import { Actor } from './actor.model' export class Account extends Actor implements ServerAccount { @@ -38,7 +38,7 @@ export class Account extends Actor implements ServerAccount { this.mutedServerByInstance = false } - updateAvatar (newAvatar: Avatar) { + updateAvatar (newAvatar: ActorImage) { this.avatar = newAvatar 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 @@ -import { Actor as ActorServer, Avatar } from '@shared/models' +import { Actor as ActorServer, ActorImage } from '@shared/models' import { getAbsoluteAPIUrl } from '@app/helpers' export abstract class Actor implements ActorServer { @@ -10,7 +10,7 @@ export abstract class Actor implements ActorServer { followersCount: number createdAt: Date | string updatedAt: Date | string - avatar: Avatar + avatar: ActorImage avatarUrl: string 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 @@ -import { Account as ServerAccount, Avatar, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models' +import { Account as ServerAccount, ActorImage, VideoChannel as ServerVideoChannel, ViewsPerDate } from '@shared/models' import { Account } from '../account/account.model' import { Actor } from '../account/actor.model' @@ -51,7 +51,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel { } } - updateAvatar (newAvatar: Avatar) { + updateAvatar (newAvatar: ActorImage) { this.avatar = newAvatar 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' import { HttpClient, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' -import { Avatar, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models' +import { ActorImage, ResultList, VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '@shared/models' import { environment } from '../../../../environments/environment' import { Account } from '../account' import { AccountService } from '../account/account.service' @@ -85,7 +85,7 @@ export class VideoChannelService { changeVideoChannelAvatar (videoChannelName: string, avatarForm: FormData) { const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannelName + '/avatar/pick' - return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) + return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm) .pipe(catchError(err => this.restExtractor.handleError(err))) } 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' import { VideoChannel } from '@app/shared/shared-main/video-channel/video-channel.model' import { peertubeTranslate } from '@shared/core-utils/i18n' import { - Avatar, + ActorImage, ServerConfig, UserRight, Video as VideoServerModel, @@ -72,7 +72,7 @@ export class Video implements VideoServerModel { displayName: string url: string host: string - avatar?: Avatar + avatar?: ActorImage } channel: { @@ -81,7 +81,7 @@ export class Video implements VideoServerModel { displayName: string url: string host: string - avatar?: Avatar + avatar?: ActorImage } 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 import * as Bluebird from 'bluebird' import { getUUIDFromFilename } from '../server/helpers/utils' import { ThumbnailModel } from '../server/models/video/thumbnail' -import { AvatarModel } from '../server/models/avatar/avatar' +import { ActorImageModel } from '../server/models/account/actor-image' import { uniq, values } from 'lodash' import { ThumbnailType } from '@shared/models' @@ -43,7 +43,7 @@ async function run () { await pruneDirectory(CONFIG.STORAGE.PREVIEWS_DIR, doesThumbnailExist(true, ThumbnailType.PREVIEW)), await pruneDirectory(CONFIG.STORAGE.THUMBNAILS_DIR, doesThumbnailExist(false, ThumbnailType.MINIATURE)), - await pruneDirectory(CONFIG.STORAGE.AVATARS_DIR, doesAvatarExist) + await pruneDirectory(CONFIG.STORAGE.ACTOR_IMAGES, doesActorImageExist) ) const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR) @@ -107,10 +107,10 @@ function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) { } } -async function doesAvatarExist (file: string) { - const avatar = await AvatarModel.loadByName(file) +async function doesActorImageExist (file: string) { + const image = await ActorImageModel.loadByName(file) - return !!avatar + return !!image } async 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' import { MIMETYPES } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' import { sendUpdateActor } from '../../../lib/activitypub/send' -import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../../lib/avatar' +import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../../lib/actor-image' import { getOriginalVideoFileTotalDailyFromUser, getOriginalVideoFileTotalFromUser, sendVerifyUserEmail } from '../../../lib/user' import { 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' import { MIMETYPES } from '../../initializers/constants' import { sequelizeTypescript } from '../../initializers/database' import { sendUpdateActor } from '../../lib/activitypub/send' -import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/avatar' +import { deleteLocalActorAvatarFile, updateLocalActorAvatarFile } from '../../lib/actor-image' import { JobQueue } from '../../lib/job-queue' import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' import { 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 import { HttpStatusCode } from '../../shared/core-utils/miscs/http-error-codes' import { logger } from '../helpers/logger' import { LAZY_STATIC_PATHS, STATIC_MAX_AGE } from '../initializers/constants' -import { avatarPathUnsafeCache, pushAvatarProcessInQueue } from '../lib/avatar' +import { actorImagePathUnsafeCache, pushActorImageProcessInQueue } from '../lib/actor-image' import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' import { asyncMiddleware } from '../middlewares' -import { AvatarModel } from '../models/avatar/avatar' +import { ActorImageModel } from '../models/account/actor-image' const lazyStaticRouter = express.Router() @@ -15,7 +15,12 @@ lazyStaticRouter.use(cors()) lazyStaticRouter.use( LAZY_STATIC_PATHS.AVATARS + ':filename', - asyncMiddleware(getAvatar) + asyncMiddleware(getActorImage) +) + +lazyStaticRouter.use( + LAZY_STATIC_PATHS.BANNERS + ':filename', + asyncMiddleware(getActorImage) ) lazyStaticRouter.use( @@ -43,36 +48,36 @@ export { // --------------------------------------------------------------------------- -async function getAvatar (req: express.Request, res: express.Response) { +async function getActorImage (req: express.Request, res: express.Response) { const filename = req.params.filename - if (avatarPathUnsafeCache.has(filename)) { - return res.sendFile(avatarPathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) + if (actorImagePathUnsafeCache.has(filename)) { + return res.sendFile(actorImagePathUnsafeCache.get(filename), { maxAge: STATIC_MAX_AGE.SERVER }) } - const avatar = await AvatarModel.loadByName(filename) - if (!avatar) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + const image = await ActorImageModel.loadByName(filename) + if (!image) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) - if (avatar.onDisk === false) { - if (!avatar.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) + if (image.onDisk === false) { + if (!image.fileUrl) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) - logger.info('Lazy serve remote avatar image %s.', avatar.fileUrl) + logger.info('Lazy serve remote actor image %s.', image.fileUrl) try { - await pushAvatarProcessInQueue({ filename: avatar.filename, fileUrl: avatar.fileUrl }) + await pushActorImageProcessInQueue({ filename: image.filename, fileUrl: image.fileUrl }) } catch (err) { - logger.warn('Cannot process remote avatar %s.', avatar.fileUrl, { err }) + logger.warn('Cannot process remote actor image %s.', image.fileUrl, { err }) return res.sendStatus(HttpStatusCode.NOT_FOUND_404) } - avatar.onDisk = true - avatar.save() - .catch(err => logger.error('Cannot save new avatar disk state.', { err })) + image.onDisk = true + image.save() + .catch(err => logger.error('Cannot save new actor image disk state.', { err })) } - const path = avatar.getPath() + const path = image.getPath() - avatarPathUnsafeCache.set(filename, path) + actorImagePathUnsafeCache.set(filename, path) return res.sendFile(path, { maxAge: STATIC_MAX_AGE.LAZY_SERVER }) } 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 = { }, STORAGE: { TMP_DIR: buildPath(config.get('storage.tmp')), - AVATARS_DIR: buildPath(config.get('storage.avatars')), + ACTOR_IMAGES: buildPath(config.get('storage.avatars')), LOG_DIR: buildPath(config.get('storage.logs')), VIDEOS_DIR: buildPath(config.get('storage.videos')), STREAMING_PLAYLISTS_DIR: buildPath(config.get('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 = { HLS_VIDEOS: '/download/streaming-playlists/hls/videos/' } const LAZY_STATIC_PATHS = { + BANNERS: '/lazy-static/banners/', AVATARS: '/lazy-static/avatars/', PREVIEWS: '/lazy-static/previews/', VIDEO_CAPTIONS: '/lazy-static/video-captions/', @@ -634,7 +635,7 @@ const LRU_CACHE = { USER_TOKENS: { MAX_SIZE: 1000 }, - AVATAR_STATIC: { + ACTOR_IMAGE_STATIC: { MAX_SIZE: 500 } } @@ -671,7 +672,7 @@ const MEMOIZE_LENGTH = { } const QUEUE_CONCURRENCY = { - AVATAR_PROCESS_IMAGE: 3 + ACTOR_PROCESS_IMAGE: 3 } const 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 @@ -import { TrackerModel } from '@server/models/server/tracker' -import { VideoTrackerModel } from '@server/models/server/video-tracker' import { QueryTypes, Transaction } from 'sequelize' import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' +import { TrackerModel } from '@server/models/server/tracker' +import { VideoTrackerModel } from '@server/models/server/video-tracker' import { isTestInstance } from '../helpers/core-utils' import { logger } from '../helpers/logger' import { AbuseModel } from '../models/abuse/abuse' @@ -11,6 +11,7 @@ import { VideoCommentAbuseModel } from '../models/abuse/video-comment-abuse' import { AccountModel } from '../models/account/account' import { AccountBlocklistModel } from '../models/account/account-blocklist' import { AccountVideoRateModel } from '../models/account/account-video-rate' +import { ActorImageModel } from '../models/account/actor-image' import { UserModel } from '../models/account/user' import { UserNotificationModel } from '../models/account/user-notification' import { UserNotificationSettingModel } from '../models/account/user-notification-setting' @@ -18,7 +19,6 @@ import { UserVideoHistoryModel } from '../models/account/user-video-history' import { ActorModel } from '../models/activitypub/actor' import { ActorFollowModel } from '../models/activitypub/actor-follow' import { ApplicationModel } from '../models/application/application' -import { AvatarModel } from '../models/avatar/avatar' import { OAuthClientModel } from '../models/oauth/oauth-client' import { OAuthTokenModel } from '../models/oauth/oauth-token' import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' @@ -95,7 +95,7 @@ async function initDatabaseModels (silent: boolean) { ApplicationModel, ActorModel, ActorFollowModel, - AvatarModel, + ActorImageModel, AccountModel, OAuthClientModel, 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' import { MIMETYPES, WEBSERVER } from '../../initializers/constants' import { sequelizeTypescript } from '../../initializers/database' import { AccountModel } from '../../models/account/account' +import { ActorImageModel } from '../../models/account/actor-image' import { ActorModel } from '../../models/activitypub/actor' -import { AvatarModel } from '../../models/avatar/avatar' import { ServerModel } from '../../models/server/server' import { VideoChannelModel } from '../../models/video/video-channel' import { @@ -183,7 +183,7 @@ async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo } } - const avatar = await AvatarModel.create({ + const avatar = await ActorImageModel.create({ filename: info.name, onDisk: info.onDisk, fileUrl: info.fileUrl @@ -378,7 +378,7 @@ function saveActorAndServerAndModelIfNotExist ( // Avatar? if (result.avatar) { - const avatar = await AvatarModel.create({ + const avatar = await ActorImageModel.create({ filename: result.avatar.name, fileUrl: result.avatar.fileUrl, onDisk: false diff --git a/server/lib/avatar.ts b/server/lib/actor-image.ts similarity index 82% rename from server/lib/avatar.ts rename to server/lib/actor-image.ts index 86f1e7bdb..ca7f9658d 100644 --- a/server/lib/avatar.ts +++ b/server/lib/actor-image.ts @@ -1,17 +1,17 @@ import 'multer' -import { sendUpdateActor } from './activitypub/send' -import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' -import { updateActorAvatarInstance, deleteActorAvatarInstance } from './activitypub/actor' -import { processImage } from '../helpers/image-utils' -import { extname, join } from 'path' -import { retryTransactionWrapper } from '../helpers/database-utils' -import { v4 as uuidv4 } from 'uuid' -import { CONFIG } from '../initializers/config' -import { sequelizeTypescript } from '../initializers/database' -import * as LRUCache from 'lru-cache' import { queue } from 'async' +import * as LRUCache from 'lru-cache' +import { extname, join } from 'path' +import { v4 as uuidv4 } from 'uuid' +import { retryTransactionWrapper } from '../helpers/database-utils' +import { processImage } from '../helpers/image-utils' import { downloadImage } from '../helpers/requests' +import { CONFIG } from '../initializers/config' +import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' +import { sequelizeTypescript } from '../initializers/database' import { MAccountDefault, MChannelDefault } from '../types/models' +import { deleteActorAvatarInstance, updateActorAvatarInstance } from './activitypub/actor' +import { sendUpdateActor } from './activitypub/send' async function updateLocalActorAvatarFile ( accountOrChannel: MAccountDefault | MChannelDefault, @@ -20,7 +20,7 @@ async function updateLocalActorAvatarFile ( const extension = extname(avatarPhysicalFile.filename) const avatarName = uuidv4() + extension - const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) + const destination = join(CONFIG.STORAGE.ACTOR_IMAGES, avatarName) await processImage(avatarPhysicalFile.path, destination, AVATARS_SIZE) return retryTransactionWrapper(() => { @@ -59,12 +59,12 @@ async function deleteLocalActorAvatarFile ( type DownloadImageQueueTask = { fileUrl: string, filename: string } const downloadImageQueue = queue((task, cb) => { - downloadImage(task.fileUrl, CONFIG.STORAGE.AVATARS_DIR, task.filename, AVATARS_SIZE) + downloadImage(task.fileUrl, CONFIG.STORAGE.ACTOR_IMAGES, task.filename, AVATARS_SIZE) .then(() => cb()) .catch(err => cb(err)) -}, QUEUE_CONCURRENCY.AVATAR_PROCESS_IMAGE) +}, QUEUE_CONCURRENCY.ACTOR_PROCESS_IMAGE) -function pushAvatarProcessInQueue (task: DownloadImageQueueTask) { +function pushActorImageProcessInQueue (task: DownloadImageQueueTask) { return new Promise((res, rej) => { downloadImageQueue.push(task, err => { if (err) return rej(err) @@ -75,11 +75,11 @@ function pushAvatarProcessInQueue (task: DownloadImageQueueTask) { } // Unsafe so could returns paths that does not exist anymore -const avatarPathUnsafeCache = new LRUCache({ max: LRU_CACHE.AVATAR_STATIC.MAX_SIZE }) +const actorImagePathUnsafeCache = new LRUCache({ max: LRU_CACHE.ACTOR_IMAGE_STATIC.MAX_SIZE }) export { - avatarPathUnsafeCache, + actorImagePathUnsafeCache, updateLocalActorAvatarFile, deleteLocalActorAvatarFile, - pushAvatarProcessInQueue + pushActorImageProcessInQueue } 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 { import { ActorModel } from '../activitypub/actor' import { ActorFollowModel } from '../activitypub/actor-follow' import { ApplicationModel } from '../application/application' -import { AvatarModel } from '../avatar/avatar' +import { ActorImageModel } from './actor-image' import { ServerModel } from '../server/server' import { ServerBlocklistModel } from '../server/server-blocklist' import { getSort, throwIfNotValid } from '../utils' @@ -82,7 +82,8 @@ export type SummaryOptions = { serverInclude, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] diff --git a/server/models/avatar/avatar.ts b/server/models/account/actor-image.ts similarity index 55% rename from server/models/avatar/avatar.ts rename to 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 @@ +import { remove } from 'fs-extra' import { join } from 'path' import { AfterDestroy, AllowNull, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { Avatar } from '../../../shared/models/avatars/avatar.model' -import { LAZY_STATIC_PATHS } from '../../initializers/constants' -import { logger } from '../../helpers/logger' -import { remove } from 'fs-extra' -import { CONFIG } from '../../initializers/config' -import { throwIfNotValid } from '../utils' +import { MActorImageFormattable } from '@server/types/models' +import { ActorImageType } from '@shared/models' +import { ActorImage } from '../../../shared/models/actors/actor-image.model' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { MAvatarFormattable } from '@server/types/models' +import { logger } from '../../helpers/logger' +import { CONFIG } from '../../initializers/config' +import { LAZY_STATIC_PATHS } from '../../initializers/constants' +import { throwIfNotValid } from '../utils' @Table({ - tableName: 'avatar', + tableName: 'actorImage', indexes: [ { fields: [ 'filename' ], @@ -18,14 +19,14 @@ import { MAvatarFormattable } from '@server/types/models' } ] }) -export class AvatarModel extends Model { +export class ActorImageModel extends Model { @AllowNull(false) @Column filename: string @AllowNull(true) - @Is('AvatarFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true)) + @Is('ActorImageFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true)) @Column fileUrl: string @@ -33,6 +34,10 @@ export class AvatarModel extends Model { @Column onDisk: boolean + @AllowNull(false) + @Column + type: ActorImageType + @CreatedAt createdAt: Date @@ -40,12 +45,12 @@ export class AvatarModel extends Model { updatedAt: Date @AfterDestroy - static removeFilesAndSendDelete (instance: AvatarModel) { - logger.info('Removing avatar file %s.', instance.filename) + static removeFilesAndSendDelete (instance: ActorImageModel) { + logger.info('Removing actor image file %s.', instance.filename) // Don't block the transaction - instance.removeAvatar() - .catch(err => logger.error('Cannot remove avatar file %s.', instance.filename, err)) + instance.removeImage() + .catch(err => logger.error('Cannot remove actor image file %s.', instance.filename, err)) } static loadByName (filename: string) { @@ -55,10 +60,10 @@ export class AvatarModel extends Model { } } - return AvatarModel.findOne(query) + return ActorImageModel.findOne(query) } - toFormattedJSON (this: MAvatarFormattable): Avatar { + toFormattedJSON (this: MActorImageFormattable): ActorImage { return { path: this.getStaticPath(), createdAt: this.createdAt, @@ -71,11 +76,11 @@ export class AvatarModel extends Model { } getPath () { - return join(CONFIG.STORAGE.AVATARS_DIR, this.filename) + return join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename) } - removeAvatar () { - const avatarPath = join(CONFIG.STORAGE.AVATARS_DIR, this.filename) - return remove(avatarPath) + removeImage () { + const imagePath = join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename) + return remove(imagePath) } } 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' import { ActorModel } from '../activitypub/actor' import { ActorFollowModel } from '../activitypub/actor-follow' import { ApplicationModel } from '../application/application' -import { AvatarModel } from '../avatar/avatar' import { PluginModel } from '../server/plugin' import { ServerModel } from '../server/server' import { getSort, throwIfNotValid } from '../utils' @@ -20,6 +19,7 @@ import { VideoChannelModel } from '../video/video-channel' import { VideoCommentModel } from '../video/video-comment' import { VideoImportModel } from '../video/video-import' import { AccountModel } from './account' +import { ActorImageModel } from './actor-image' import { UserModel } from './user' enum ScopeNames { @@ -34,7 +34,8 @@ function buildActorWithAvatarInclude () { include: [ { attributes: [ 'filename' ], - model: AvatarModel.unscoped(), + as: 'Avatar', + model: ActorImageModel.unscoped(), required: false }, { @@ -172,7 +173,8 @@ function buildAccountInclude (required: boolean, withActor = false) { }, { attributes: [ 'filename' ], - model: AvatarModel.unscoped(), + as: 'Avatar', + model: ActorImageModel.unscoped(), required: false }, { 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 { } from 'sequelize-typescript' import { ModelCache } from '@server/models/model-cache' import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' -import { Avatar } from '../../../shared/models/avatars/avatar.model' +import { ActorImage } from '../../../shared/models/actors/actor-image.model' import { activityPubContextify } from '../../helpers/activitypub' import { isActorFollowersCountValid, @@ -43,7 +43,7 @@ import { MActorWithInboxes } from '../../types/models' import { AccountModel } from '../account/account' -import { AvatarModel } from '../avatar/avatar' +import { ActorImageModel } from '../account/actor-image' import { ServerModel } from '../server/server' import { isOutdated, throwIfNotValid } from '../utils' import { VideoModel } from '../video/video' @@ -73,7 +73,8 @@ export const unusedActorAttributesForAPI = [ required: false }, { - model: AvatarModel, + model: ActorImageModel, + as: 'Avatar', required: false } ] @@ -100,7 +101,8 @@ export const unusedActorAttributesForAPI = [ required: false }, { - model: AvatarModel, + model: ActorImageModel, + as: 'Avatar', required: false } ] @@ -213,18 +215,35 @@ export class ActorModel extends Model { @UpdatedAt updatedAt: Date - @ForeignKey(() => AvatarModel) + @ForeignKey(() => ActorImageModel) @Column avatarId: number - @BelongsTo(() => AvatarModel, { + @ForeignKey(() => ActorImageModel) + @Column + bannerId: number + + @BelongsTo(() => ActorImageModel, { foreignKey: { + name: 'avatarId', allowNull: true }, + as: 'Avatar', onDelete: 'set null', hooks: true }) - Avatar: AvatarModel + Avatar: ActorImageModel + + @BelongsTo(() => ActorImageModel, { + foreignKey: { + name: 'bannerId', + allowNull: true + }, + as: 'Banner', + onDelete: 'set null', + hooks: true + }) + Banner: ActorImageModel @HasMany(() => ActorFollowModel, { foreignKey: { @@ -496,7 +515,7 @@ export class ActorModel extends Model { } toFormattedSummaryJSON (this: MActorSummaryFormattable) { - let avatar: Avatar = null + let avatar: ActorImage = null if (this.Avatar) { avatar = this.Avatar.toFormattedJSON() } 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 { MChannelSummaryFormattable } from '../../types/models/video' import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' +import { ActorImageModel } from '../account/actor-image' import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' import { ActorFollowModel } from '../activitypub/actor-follow' -import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' @@ -130,7 +130,8 @@ export type SummaryOptions = { required: false }, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] 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 'INNER JOIN "actor" AS "VideoChannel->Account->Actor" ON "VideoChannel->Account"."actorId" = "VideoChannel->Account->Actor"."id"', 'LEFT OUTER JOIN "server" AS "VideoChannel->Actor->Server" ON "VideoChannel->Actor"."serverId" = "VideoChannel->Actor->Server"."id"', - 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Actor->Avatar" ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"', + 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Actor->Avatar" ' + + 'ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"', 'LEFT OUTER JOIN "server" AS "VideoChannel->Account->Actor->Server" ' + 'ON "VideoChannel->Account->Actor"."serverId" = "VideoChannel->Account->Actor->Server"."id"', - 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Account->Actor->Avatar" ' + + 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Account->Actor->Avatar" ' + 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"', '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 import { VideoAbuseModel } from '../abuse/video-abuse' import { AccountModel } from '../account/account' import { AccountVideoRateModel } from '../account/account-video-rate' +import { ActorImageModel } from '../account/actor-image' import { UserModel } from '../account/user' import { UserVideoHistoryModel } from '../account/user-video-history' import { ActorModel } from '../activitypub/actor' -import { AvatarModel } from '../avatar/avatar' import { VideoRedundancyModel } from '../redundancy/video-redundancy' import { ServerModel } from '../server/server' import { TrackerModel } from '../server/tracker' @@ -286,7 +286,8 @@ export type AvailableForListIDsOptions = { required: false }, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] @@ -308,7 +309,8 @@ export type AvailableForListIDsOptions = { required: false }, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] @@ -1703,7 +1705,7 @@ export class VideoModel extends Model { function buildActor (rowActor: any) { const avatarModel = rowActor.Avatar.id !== null - ? new AvatarModel(pick(rowActor.Avatar, avatarKeys), buildOpts) + ? new ActorImageModel(pick(rowActor.Avatar, avatarKeys), buildOpts) : null 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 @@ +import { ActorImageModel } from '../../../models/account/actor-image' +import { FunctionProperties } from '@shared/core-utils' + +export type MActorImage = ActorImageModel + +// ############################################################################ + +// Format for API or AP object + +export type MActorImageFormattable = + FunctionProperties & + Pick 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 @@ -import { ActorModel } from '../../../models/activitypub/actor' import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' -import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' +import { ActorModel } from '../../../models/activitypub/actor' import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' -import { MAvatar, MAvatarFormattable } from './avatar' import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' +import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' +import { MActorImage, MActorImageFormattable } from './actor-image' type Use = PickWith // ############################################################################ -export type MActor = Omit +export type MActor = Omit // ############################################################################ @@ -34,7 +34,7 @@ export type MActorRedundancyAllowedOpt = PickWithOpt & - Use<'Avatar', MAvatar> + Use<'Avatar', MActorImage> export type MActorAccountId = MActor & @@ -78,7 +78,7 @@ export type MActorServer = export type MActorDefault = MActor & Use<'Server', MServer> & - Use<'Avatar', MAvatar> + Use<'Avatar', MActorImage> // Actor with channel that is associated to an account and its actor // Actor -> VideoChannel -> Account -> Actor @@ -89,7 +89,7 @@ export type MActorChannelAccountActor = export type MActorFull = MActor & Use<'Server', MServer> & - Use<'Avatar', MAvatar> & + Use<'Avatar', MActorImage> & Use<'Account', MAccount> & Use<'VideoChannel', MChannelAccountActor> @@ -97,7 +97,7 @@ export type MActorFull = export type MActorFullActor = MActor & Use<'Server', MServer> & - Use<'Avatar', MAvatar> & + Use<'Avatar', MActorImage> & Use<'Account', MAccountDefault> & Use<'VideoChannel', MChannelAccountDefault> @@ -109,7 +109,7 @@ export type MActorSummary = FunctionProperties & Pick & Use<'Server', MServerHost> & - Use<'Avatar', MAvatar> + Use<'Avatar', MActorImage> export type MActorSummaryBlocks = MActorSummary & @@ -127,7 +127,7 @@ export type MActorSummaryFormattable = FunctionProperties & Pick & Use<'Server', MServerHost> & - Use<'Avatar', MAvatarFormattable> + Use<'Avatar', MActorImageFormattable> export type MActorFormattable = MActorSummaryFormattable & @@ -136,4 +136,4 @@ export type MActorFormattable = export type MActorAP = MActor & - Use<'Avatar', MAvatar> + 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 @@ -import { AvatarModel } from '../../../models/avatar/avatar' -import { FunctionProperties } from '@shared/core-utils' - -export type MAvatar = AvatarModel - -// ############################################################################ - -// Format for API or AP object - -export type MAvatarFormattable = - FunctionProperties & - Pick 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 @@ export * from './account' export * from './account-blocklist' -export * from './actor' export * from './actor-follow' -export * from './avatar' +export * from './actor-image' +export * 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' import { PickWith, PickWithOpt } from '@shared/core-utils' import { AbuseModel } from '../../../models/abuse/abuse' import { AccountModel } from '../../../models/account/account' +import { ActorImageModel } from '../../../models/account/actor-image' import { UserNotificationModel } from '../../../models/account/user-notification' import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' -import { AvatarModel } from '../../../models/avatar/avatar' import { ServerModel } from '../../../models/server/server' import { VideoModel } from '../../../models/video/video' import { VideoBlacklistModel } from '../../../models/video/video-blacklist' @@ -29,7 +29,7 @@ export module UserNotificationIncludes { export type ActorInclude = Pick & - PickWith> & + PickWith> & PickWith> export type VideoChannelInclude = Pick @@ -75,7 +75,7 @@ export module UserNotificationIncludes { Pick & PickWith & PickWith> & - PickWithOpt> + PickWithOpt> export type ActorFollowing = Pick & 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 @@ +import { ActorImage } from './actor-image.model' import { Actor } from './actor.model' -import { Avatar } from '../avatars' export interface Account extends Actor { displayName: string @@ -14,5 +14,5 @@ export interface AccountSummary { displayName: string url: string host: string - avatar?: Avatar + avatar?: ActorImage } diff --git a/shared/models/avatars/avatar.model.ts b/shared/models/actors/actor-image.model.ts similarity index 74% rename from shared/models/avatars/avatar.model.ts rename to 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 @@ -export interface Avatar { +export interface ActorImage { path: string 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 @@ +export const enum ActorImageType { + AVATAR = 1, + BANNER = 2 +} 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 @@ -import { Avatar } from '../avatars/avatar.model' +import { ActorImage } from './actor-image.model' export interface Actor { id: number @@ -9,5 +9,5 @@ export interface Actor { followersCount: number createdAt: Date | string updatedAt: Date | string - avatar?: Avatar + avatar?: ActorImage } 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 @@ export * from './account.model' +export * from './actor-image.model' +export * from './actor-image.type' export * from './actor.model' export * 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 @@ -export * 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 @@ export * from './activitypub' export * from './actors' -export * from './avatars' export * from './moderation' export * from './bulk' export * 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 @@ import { Actor } from '../../actors/actor.model' -import { Account } from '../../actors/index' -import { Avatar } from '../../avatars' +import { Account, ActorImage } from '../../actors' export type ViewsPerDate = { date: Date @@ -24,5 +23,5 @@ export interface VideoChannelSummary { displayName: string url: string host: string - avatar?: Avatar + avatar?: ActorImage }