mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-24 09:40:28 -06:00
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 { 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()
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)))
|
||||
}
|
||||
|
||||
|
@ -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?: {
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ const CONFIG = {
|
||||
},
|
||||
STORAGE: {
|
||||
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')),
|
||||
VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
|
||||
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/'
|
||||
}
|
||||
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 = {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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<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())
|
||||
.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) => {
|
||||
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<string, string>({ max: LRU_CACHE.AVATAR_STATIC.MAX_SIZE })
|
||||
const actorImagePathUnsafeCache = new LRUCache<string, string>({ max: LRU_CACHE.ACTOR_IMAGE_STATIC.MAX_SIZE })
|
||||
|
||||
export {
|
||||
avatarPathUnsafeCache,
|
||||
actorImagePathUnsafeCache,
|
||||
updateLocalActorAvatarFile,
|
||||
deleteLocalActorAvatarFile,
|
||||
pushAvatarProcessInQueue
|
||||
pushActorImageProcessInQueue
|
||||
}
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
},
|
||||
{
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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"'
|
||||
|
@ -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
|
||||
|
12
server/types/models/account/actor-image.ts
Normal file
12
server/types/models/account/actor-image.ts
Normal file
@ -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 { 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<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 =
|
||||
MActorLight &
|
||||
Use<'Server', MServerHost> &
|
||||
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<MActor> &
|
||||
Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
|
||||
Use<'Server', MServerHost> &
|
||||
Use<'Avatar', MAvatar>
|
||||
Use<'Avatar', MActorImage>
|
||||
|
||||
export type MActorSummaryBlocks =
|
||||
MActorSummary &
|
||||
@ -127,7 +127,7 @@ export type MActorSummaryFormattable =
|
||||
FunctionProperties<MActor> &
|
||||
Pick<MActor, 'url' | 'preferredUsername'> &
|
||||
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>
|
||||
|
@ -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-blocklist'
|
||||
export * from './actor'
|
||||
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 { 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<ActorModel, 'preferredUsername' | 'getHost'> &
|
||||
PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
|
||||
PickWith<ActorModel, 'Avatar', Pick<ActorImageModel, 'filename' | 'getStaticPath'>> &
|
||||
PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
|
||||
|
||||
export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
|
||||
@ -75,7 +75,7 @@ export module UserNotificationIncludes {
|
||||
Pick<ActorModel, 'preferredUsername' | 'getHost'> &
|
||||
PickWith<ActorModel, 'Account', AccountInclude> &
|
||||
PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> &
|
||||
PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>>
|
||||
PickWithOpt<ActorModel, 'Avatar', Pick<ActorImageModel, 'filename' | 'getStaticPath'>>
|
||||
|
||||
export type ActorFollowing =
|
||||
Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> &
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export interface Avatar {
|
||||
export interface ActorImage {
|
||||
path: string
|
||||
|
||||
url?: string
|
4
shared/models/actors/actor-image.type.ts
Normal file
4
shared/models/actors/actor-image.type.ts
Normal file
@ -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 {
|
||||
id: number
|
||||
@ -9,5 +9,5 @@ export interface Actor {
|
||||
followersCount: number
|
||||
createdAt: Date | string
|
||||
updatedAt: Date | string
|
||||
avatar?: Avatar
|
||||
avatar?: ActorImage
|
||||
}
|
||||
|
@ -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'
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './avatar.model'
|
@ -1,6 +1,5 @@
|
||||
export * from './activitypub'
|
||||
export * from './actors'
|
||||
export * from './avatars'
|
||||
export * from './moderation'
|
||||
export * from './bulk'
|
||||
export * from './redundancy'
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user