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