diff --git a/packages/core-utils/src/renderer/rss.ts b/packages/core-utils/src/renderer/rss.ts index ec00f810a..d00230620 100644 --- a/packages/core-utils/src/renderer/rss.ts +++ b/packages/core-utils/src/renderer/rss.ts @@ -10,10 +10,14 @@ export function getDefaultRSSFeeds (url: string, instanceName: string) { ] } +export function getChannelPodcastFeed (url: string, channel: { id: number }) { + return `${url}/feeds/podcast/videos.xml?videoChannelId=${channel.id}` +} + export function getChannelRSSFeeds (url: string, instanceName: string, channel: { name: string, id: number }) { return [ { - url: `${url}/feeds/podcast/videos.xml?videoChannelId=${channel.id}`, + url: getChannelPodcastFeed(url, channel), // TODO: translate title: `${channel.name} podcast feed` }, diff --git a/packages/server-commands/src/feeds/feeds-command.ts b/packages/server-commands/src/feeds/feeds-command.ts index 51bc45b7f..4e242b8c9 100644 --- a/packages/server-commands/src/feeds/feeds-command.ts +++ b/packages/server-commands/src/feeds/feeds-command.ts @@ -5,35 +5,39 @@ import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js' type FeedType = 'videos' | 'video-comments' | 'subscriptions' export class FeedCommand extends AbstractCommand { - - getXML (options: OverrideCommandOptions & { - feed: FeedType - ignoreCache: boolean - format?: string - }) { - const { feed, format, ignoreCache } = options + getXML ( + options: OverrideCommandOptions & { + feed: FeedType + ignoreCache: boolean + format?: string + query?: { [id: string]: any } + } + ) { + const { feed, format, ignoreCache, query = {} } = options const path = '/feeds/' + feed + '.xml' - const query: { [id: string]: string } = {} + const internalQuery: { [id: string]: string } = {} - if (ignoreCache) query.v = buildUUID() - if (format) query.format = format + if (ignoreCache) internalQuery.v = buildUUID() + if (format) internalQuery.format = format return this.getRequestText({ ...options, path, - query, + query: { ...internalQuery, ...query }, accept: 'application/xml', implicitToken: false, defaultExpectedStatus: HttpStatusCode.OK_200 }) } - getPodcastXML (options: OverrideCommandOptions & { - ignoreCache: boolean - channelId: number - }) { + getPodcastXML ( + options: OverrideCommandOptions & { + ignoreCache: boolean + channelId: number + } + ) { const { ignoreCache, channelId } = options const path = `/feeds/podcast/videos.xml` @@ -53,11 +57,13 @@ export class FeedCommand extends AbstractCommand { }) } - getJSON (options: OverrideCommandOptions & { - feed: FeedType - ignoreCache: boolean - query?: { [ id: string ]: any } - }) { + getJSON ( + options: OverrideCommandOptions & { + feed: FeedType + ignoreCache: boolean + query?: { [id: string]: any } + } + ) { const { feed, query = {}, ignoreCache } = options const path = '/feeds/' + feed + '.json' diff --git a/packages/server-commands/src/videos/channels-command.ts b/packages/server-commands/src/videos/channels-command.ts index 772677d39..14b6d8d16 100644 --- a/packages/server-commands/src/videos/channels-command.ts +++ b/packages/server-commands/src/videos/channels-command.ts @@ -13,7 +13,6 @@ import { unwrapBody } from '../requests/index.js' import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js' export class ChannelsCommand extends AbstractCommand { - list (options: OverrideCommandOptions & { start?: number count?: number @@ -32,14 +31,16 @@ export class ChannelsCommand extends AbstractCommand { }) } - listByAccount (options: OverrideCommandOptions & { - accountName: string - start?: number - count?: number - sort?: string - withStats?: boolean - search?: string - }) { + listByAccount ( + options: OverrideCommandOptions & { + accountName: string + start?: number + count?: number + sort?: string + withStats?: boolean + search?: string + } + ) { const { accountName, sort = 'createdAt' } = options const path = '/api/v1/accounts/' + accountName + '/video-channels' @@ -53,9 +54,11 @@ export class ChannelsCommand extends AbstractCommand { }) } - async create (options: OverrideCommandOptions & { - attributes: Partial - }) { + async create ( + options: OverrideCommandOptions & { + attributes: Partial + } + ) { const path = '/api/v1/video-channels/' // Default attributes @@ -78,10 +81,12 @@ export class ChannelsCommand extends AbstractCommand { return body.videoChannel } - update (options: OverrideCommandOptions & { - channelName: string - attributes: VideoChannelUpdate - }) { + update ( + options: OverrideCommandOptions & { + channelName: string + attributes: VideoChannelUpdate + } + ) { const { channelName, attributes } = options const path = '/api/v1/video-channels/' + channelName @@ -95,9 +100,11 @@ export class ChannelsCommand extends AbstractCommand { }) } - delete (options: OverrideCommandOptions & { - channelName: string - }) { + delete ( + options: OverrideCommandOptions & { + channelName: string + } + ) { const path = '/api/v1/video-channels/' + options.channelName return this.deleteRequest({ @@ -109,9 +116,13 @@ export class ChannelsCommand extends AbstractCommand { }) } - get (options: OverrideCommandOptions & { - channelName: string - }) { + // --------------------------------------------------------------------------- + + get ( + options: OverrideCommandOptions & { + channelName: string + } + ) { const path = '/api/v1/video-channels/' + options.channelName return this.getRequestBody({ @@ -123,11 +134,25 @@ export class ChannelsCommand extends AbstractCommand { }) } - updateImage (options: OverrideCommandOptions & { - fixture: string - channelName: string | number - type: 'avatar' | 'banner' - }) { + async getIdOf ( + options: OverrideCommandOptions & { + channelName: string + } + ) { + const { id } = await this.get(options) + + return id + } + + // --------------------------------------------------------------------------- + + updateImage ( + options: OverrideCommandOptions & { + fixture: string + channelName: string | number + type: 'avatar' | 'banner' + } + ) { const { channelName, fixture, type } = options const path = `/api/v1/video-channels/${channelName}/${type}/pick` @@ -144,10 +169,12 @@ export class ChannelsCommand extends AbstractCommand { }) } - deleteImage (options: OverrideCommandOptions & { - channelName: string | number - type: 'avatar' | 'banner' - }) { + deleteImage ( + options: OverrideCommandOptions & { + channelName: string | number + type: 'avatar' | 'banner' + } + ) { const { channelName, type } = options const path = `/api/v1/video-channels/${channelName}/${type}` @@ -161,13 +188,15 @@ export class ChannelsCommand extends AbstractCommand { }) } - listFollowers (options: OverrideCommandOptions & { - channelName: string - start?: number - count?: number - sort?: string - search?: string - }) { + listFollowers ( + options: OverrideCommandOptions & { + channelName: string + start?: number + count?: number + sort?: string + search?: string + } + ) { const { channelName, start, count, sort, search } = options const path = '/api/v1/video-channels/' + channelName + '/followers' @@ -183,9 +212,11 @@ export class ChannelsCommand extends AbstractCommand { }) } - importVideos (options: OverrideCommandOptions & VideosImportInChannelCreate & { - channelName: string - }) { + importVideos ( + options: OverrideCommandOptions & VideosImportInChannelCreate & { + channelName: string + } + ) { const { channelName, externalChannelUrl, videoChannelSyncId } = options const path = `/api/v1/video-channels/${channelName}/import-videos` diff --git a/packages/tests/src/feeds/feeds.ts b/packages/tests/src/feeds/feeds.ts index b623cc4dd..830beef9e 100644 --- a/packages/tests/src/feeds/feeds.ts +++ b/packages/tests/src/feeds/feeds.ts @@ -34,7 +34,7 @@ describe('Test syndication feeds', () => { let userAccessToken: string let rootAccountId: number - let rootChannelId: number + let rootChannelIdServer1: number let userAccountId: number let userChannelId: number @@ -53,7 +53,7 @@ describe('Test syndication feeds', () => { await setAccessTokensToServers([ ...servers, serverHLSOnly ]) await setDefaultChannelAvatar([ servers[0], serverHLSOnly ]) - await setDefaultVideoChannel(servers) + await setDefaultVideoChannel([ ...servers, serverHLSOnly ]) await doubleFollow(servers[0], servers[1]) await servers[0].config.enableLive({ allowReplay: false, transcoding: false }) @@ -62,7 +62,7 @@ describe('Test syndication feeds', () => { { const user = await servers[0].users.getMyInfo() rootAccountId = user.account.id - rootChannelId = user.videoChannels[0].id + rootChannelIdServer1 = user.videoChannels[0].id } { @@ -116,7 +116,6 @@ describe('Test syndication feeds', () => { }) describe('All feed', function () { - it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { const rss = await servers[0].feed.getXML({ feed, ignoreCache: true }) @@ -128,7 +127,7 @@ describe('Test syndication feeds', () => { }) it('Should be well formed XML (covers Podcast endpoint)', async function () { - const podcast = await servers[0].feed.getPodcastXML({ ignoreCache: true, channelId: rootChannelId }) + const podcast = await servers[0].feed.getPodcastXML({ ignoreCache: true, channelId: rootChannelIdServer1 }) expect(podcast).xml.to.be.valid() }) @@ -154,13 +153,11 @@ describe('Test syndication feeds', () => { }) describe('Videos feed', function () { - describe('Podcast feed', function () { - it('Should contain a valid podcast enclosures', async function () { // Since podcast feeds should only work on the server they originate on, // only test the first server where the videos reside - const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId }) + const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: servers[0].store.channel.id }) expect(XMLValidator.validate(rss)).to.be.true const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) @@ -192,7 +189,7 @@ describe('Test syndication feeds', () => { }) it('Should contain a valid podcast enclosures with HLS only', async function () { - const rss = await serverHLSOnly.feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId }) + const rss = await serverHLSOnly.feed.getPodcastXML({ ignoreCache: false, channelId: serverHLSOnly.store.channel.id }) expect(XMLValidator.validate(rss)).to.be.true const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) @@ -230,7 +227,7 @@ describe('Test syndication feeds', () => { }) it('Should contain a valid podcast:socialInteract', async function () { - const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId }) + const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: servers[0].store.channel.id }) expect(XMLValidator.validate(rss)).to.be.true const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) @@ -284,7 +281,7 @@ describe('Test syndication feeds', () => { fields: { name: 'live-0', privacy: VideoPrivacy.PUBLIC, - channelId: rootChannelId, + channelId: rootChannelIdServer1, permanentLive: false } }) @@ -293,7 +290,7 @@ describe('Test syndication feeds', () => { const ffmpeg = await servers[0].live.sendRTMPStreamInVideo({ videoId: liveId, copyCodecs: true, fixtureName: 'video_short.mp4' }) await servers[0].live.waitUntilPublished({ videoId: liveId }) - const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId }) + const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: servers[0].store.channel.id }) expect(XMLValidator.validate(rss)).to.be.true const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) @@ -321,7 +318,7 @@ describe('Test syndication feeds', () => { }) it('Should have valid itunes metadata', async function () { - const rss = await serverHLSOnly.feed.getPodcastXML({ ignoreCache: false, channelId: rootChannelId }) + const rss = await serverHLSOnly.feed.getPodcastXML({ ignoreCache: false, channelId: serverHLSOnly.store.channel.id }) expect(XMLValidator.validate(rss)).to.be.true const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) @@ -345,10 +342,49 @@ describe('Test syndication feeds', () => { expect(item['itunes:duration']).to.equal(5) }) + + it('Should have p20url podcast txt attribute with local podcast feed', async function () { + const rss = await servers[0].feed.getPodcastXML({ ignoreCache: false, channelId: servers[0].store.channel.id }) + const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) + const xmlDoc = parser.parse(rss) + + const podcastUrlEl = xmlDoc.rss.channel['podcast:txt'] + expect(podcastUrlEl).to.exist + expect(podcastUrlEl['@_purpose']).to.equal('p20url') + expect(podcastUrlEl['#text']).to.equal( + servers[0].url + '/feeds/podcast/videos.xml?videoChannelId=' + servers[0].store.channel.id + ) + }) + + it('Should have p20url podcast txt attribute with remote classic RSS feed with channel', async function () { + const videoChannelId = await servers[1].channels.getIdOf({ channelName: 'root_channel@' + servers[0].host }) + + const rss = await servers[1].feed.getXML({ + feed: 'videos', + ignoreCache: true, + query: { videoChannelId } + }) + + const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) + const xmlDoc = parser.parse(rss) + + const podcastUrlEl = xmlDoc.rss.channel['podcast:txt'] + expect(podcastUrlEl).to.exist + expect(podcastUrlEl['@_purpose']).to.equal('p20url') + expect(podcastUrlEl['#text']).to.equal(servers[0].url + '/feeds/podcast/videos.xml?videoChannelId=' + videoChannelId) + }) + + it('Should not have p20url podcast txt attribute with classic RSS feed without channel', async function () { + const rss = await serverHLSOnly.feed.getXML({ feed: 'videos', ignoreCache: true }) + const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false }) + const xmlDoc = parser.parse(rss) + + const podcastUrlEl = xmlDoc.rss.channel['podcast:txt'] + expect(podcastUrlEl).to.not.exist + }) }) describe('JSON feed', function () { - it('Should contain a valid \'attachments\' object', async function () { for (const server of servers) { const json = await server.feed.getJSON({ feed: 'videos', ignoreCache: true }) @@ -398,7 +434,7 @@ describe('Test syndication feeds', () => { it('Should filter by video channel', async function () { { - const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true }) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelIdServer1 }, ignoreCache: true }) const jsonObj = JSON.parse(json) expect(jsonObj.items.length).to.be.equal(1) expect(jsonObj.items[0].title).to.equal('my super name for server 1') @@ -453,7 +489,7 @@ describe('Test syndication feeds', () => { fields: { name: 'live', privacy: VideoPrivacy.PUBLIC, - channelId: rootChannelId + channelId: rootChannelIdServer1 } }) liveId = uuid @@ -484,7 +520,7 @@ describe('Test syndication feeds', () => { }) it('Should have the channel avatar as feed icon', async function () { - const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelId }, ignoreCache: true }) + const json = await servers[0].feed.getJSON({ feed: 'videos', query: { videoChannelId: rootChannelIdServer1 }, ignoreCache: true }) const jsonObj = JSON.parse(json) const imageUrl = jsonObj.icon @@ -494,7 +530,6 @@ describe('Test syndication feeds', () => { }) describe('XML feed', function () { - it('Should correctly have video mime types feed with HLS only', async function () { this.timeout(120000) @@ -514,7 +549,6 @@ describe('Test syndication feeds', () => { }) describe('Video comments feed', function () { - it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted/password protected videos', async function () { for (const server of servers) { const json = await server.feed.getJSON({ feed: 'video-comments', ignoreCache: true }) @@ -544,7 +578,11 @@ describe('Test syndication feeds', () => { it('Should filter by videoChannelId/videoChannelName', async function () { { - const json = await servers[0].feed.getJSON({ feed: 'video-comments', query: { videoChannelId: rootChannelId }, ignoreCache: true }) + const json = await servers[0].feed.getJSON({ + feed: 'video-comments', + query: { videoChannelId: rootChannelIdServer1 }, + ignoreCache: true + }) expect(JSON.parse(json).items.length).to.be.equal(2) } @@ -744,7 +782,6 @@ describe('Test syndication feeds', () => { const query = { accountId: userAccountId, token: userFeedToken } await servers[0].feed.getJSON({ feed: 'subscriptions', query, ignoreCache: true }) }) - }) describe('Cache', function () { @@ -830,7 +867,6 @@ describe('Test syndication feeds', () => { const res = await doPodcastRequest() expect(res.headers['x-api-cache-cached']).to.not.exist }) - }) after(async function () { diff --git a/server/core/controllers/feeds/shared/video-feed-utils.ts b/server/core/controllers/feeds/shared/video-feed-utils.ts index 01a047563..32b99bfd9 100644 --- a/server/core/controllers/feeds/shared/video-feed-utils.ts +++ b/server/core/controllers/feeds/shared/video-feed-utils.ts @@ -1,12 +1,13 @@ +import { getChannelPodcastFeed } from '@peertube/peertube-core-utils' import { VideoIncludeType } from '@peertube/peertube-models' import { mdToPlainText, toSafeHtml } from '@server/helpers/markdown.js' import { CONFIG } from '@server/initializers/config.js' -import { WEBSERVER } from '@server/initializers/constants.js' +import { REMOTE_SCHEME, WEBSERVER } from '@server/initializers/constants.js' import { getServerActor } from '@server/models/application/application.js' import { getCategoryLabel } from '@server/models/video/formatter/index.js' import { DisplayOnlyForFollowerOptions } from '@server/models/video/sql/video/index.js' import { VideoModel } from '@server/models/video/video.js' -import { MThumbnail, MUserDefault } from '@server/types/models/index.js' +import { MChannelHostOnly, MThumbnail, MUserDefault } from '@server/types/models/index.js' export async function getVideosForFeeds (options: { sort: string @@ -64,3 +65,16 @@ export function getCommonVideoFeedAttributes (video: VideoModel) { })) } } + +export function getPodcastFeedUrlCustomTag (videoChannel: MChannelHostOnly) { + const rootHost = videoChannel.Actor.getHost() + const originUrl = `${REMOTE_SCHEME.HTTP}://${rootHost}` + + return { + name: 'podcast:txt', + attributes: { + purpose: 'p20url' + }, + value: getChannelPodcastFeed(originUrl, videoChannel) + } +} diff --git a/server/core/controllers/feeds/video-feeds.ts b/server/core/controllers/feeds/video-feeds.ts index 29efecf85..57813d503 100644 --- a/server/core/controllers/feeds/video-feeds.ts +++ b/server/core/controllers/feeds/video-feeds.ts @@ -18,7 +18,14 @@ import { videosSortValidator, videoSubscriptionFeedsValidator } from '../../middlewares/index.js' -import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed, sendFeed } from './shared/index.js' +import { + buildFeedMetadata, + getCommonVideoFeedAttributes, + getPodcastFeedUrlCustomTag, + getVideosForFeeds, + initFeed, + sendFeed +} from './shared/index.js' const videoFeedsRouter = express.Router() @@ -28,7 +35,8 @@ const { middleware: cacheRouteMiddleware } = cacheRouteFactory({ // --------------------------------------------------------------------------- -videoFeedsRouter.get('/videos.:format', +videoFeedsRouter.get( + '/videos.:format', videosSortValidator, setDefaultVideosSort, feedsFormatValidator, @@ -39,7 +47,8 @@ videoFeedsRouter.get('/videos.:format', asyncMiddleware(generateVideoFeed) ) -videoFeedsRouter.get('/subscriptions.:format', +videoFeedsRouter.get( + '/subscriptions.:format', videosSortValidator, setDefaultVideosSort, feedsFormatValidator, @@ -72,7 +81,10 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { imageUrl: ownerImageUrl || imageUrl, author: { name, link: ownerLink }, resourceType: 'videos', - queryString: new URL(WEBSERVER.URL + req.url).search + queryString: new URL(WEBSERVER.URL + req.url).search, + customTags: videoChannel + ? [ getPodcastFeedUrlCustomTag(videoChannel) ] + : [] }) const data = await getVideosForFeeds({ diff --git a/server/core/controllers/feeds/video-podcast-feeds.ts b/server/core/controllers/feeds/video-podcast-feeds.ts index 28525a73e..6babddd57 100644 --- a/server/core/controllers/feeds/video-podcast-feeds.ts +++ b/server/core/controllers/feeds/video-podcast-feeds.ts @@ -16,7 +16,7 @@ import { MIMETYPES, ROUTE_CACHE_LIFETIME, VIDEO_CATEGORIES, WEBSERVER } from '.. import { asyncMiddleware, setFeedPodcastContentType, videoFeedsPodcastValidator } from '../../middlewares/index.js' import { VideoCaptionModel } from '../../models/video/video-caption.js' import { VideoModel } from '../../models/video/video.js' -import { buildFeedMetadata, getCommonVideoFeedAttributes, getVideosForFeeds, initFeed } from './shared/index.js' +import { buildFeedMetadata, getCommonVideoFeedAttributes, getPodcastFeedUrlCustomTag, getVideosForFeeds, initFeed } from './shared/index.js' const videoPodcastFeedsRouter = express.Router() @@ -42,7 +42,8 @@ for (const event of ([ 'channel-updated', 'channel-deleted' ] as const)) { // --------------------------------------------------------------------------- -videoPodcastFeedsRouter.get('/podcast/videos.xml', +videoPodcastFeedsRouter.get( + '/podcast/videos.xml', setFeedPodcastContentType, videoFeedsPodcastSetCacheKey, podcastCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.FEEDS), @@ -85,7 +86,7 @@ async function generateVideoPodcastFeed (req: express.Request, res: express.Resp : false const customTags: CustomTag[] = await Hooks.wrapObject( - [], + [ getPodcastFeedUrlCustomTag(videoChannel) ], 'filter:feed.podcast.channel.create-custom-tags.result', { videoChannel } ) @@ -128,15 +129,15 @@ async function generateVideoPodcastFeed (req: express.Request, res: express.Resp } type PodcastMedia = - { + | { type: string length: number bitrate: number sources: { uri: string, contentType?: string }[] title: string language?: string - } | - { + } + | { sources: { uri: string }[] type: string title: string @@ -209,7 +210,7 @@ async function addVideosToPodcastFeed (feed: Feed, videos: VideoModel[]) { async function addVODPodcastItem (options: { feed: Feed video: VideoModel - captionsGroup: { [ id: number ]: MVideoCaptionVideo[] } + captionsGroup: { [id: number]: MVideoCaptionVideo[] } }) { const { feed, video, captionsGroup } = options @@ -350,7 +351,7 @@ function buildVODCaptions (video: MVideo, videoCaptions: MVideoCaptionVideo[]) { } function categoryToItunes (category: number) { - const itunesMap: { [ id in keyof typeof VIDEO_CATEGORIES ]: string } = { + const itunesMap: { [id in keyof typeof VIDEO_CATEGORIES]: string } = { 1: 'Music', 2: 'TV & Film', 3: 'Leisure', diff --git a/server/core/models/actor/actor.ts b/server/core/models/actor/actor.ts index 49d0f4c31..be4000e7e 100644 --- a/server/core/models/actor/actor.ts +++ b/server/core/models/actor/actor.ts @@ -664,6 +664,8 @@ export class ActorModel extends SequelizeModel { } getHost (this: MActorHostOnly) { + if (this.serverId && !this.Server) throw new Error('Server is not loaded in the object') + return this.Server ? this.Server.host : WEBSERVER.HOST } diff --git a/server/core/types/models/actor/actor.ts b/server/core/types/models/actor/actor.ts index 7fe9c418f..395e876ca 100644 --- a/server/core/types/models/actor/actor.ts +++ b/server/core/types/models/actor/actor.ts @@ -29,7 +29,10 @@ export type MActorLight = Omit // Some association attributes -export type MActorHostOnly = Use<'Server', MServerHost> +export type MActorHostOnly = + & Pick + & Use<'Server', MServerHost> + export type MActorHost = & MActorLight & Use<'Server', MServerHost> @@ -157,7 +160,7 @@ export type MActorAPI = Omit< export type MActorSummaryFormattable = & FunctionProperties - & Pick + & Pick & Use<'Server', MServerHost> & Use<'Avatars', MActorImageFormattable[]> diff --git a/server/core/types/models/user/user-notification.ts b/server/core/types/models/user/user-notification.ts index dcadb51a2..69e49e30d 100644 --- a/server/core/types/models/user/user-notification.ts +++ b/server/core/types/models/user/user-notification.ts @@ -31,7 +31,7 @@ export module UserNotificationIncludes { & PickWith export type ActorInclude = - & Pick + & Pick & PickWith & PickWith> @@ -78,13 +78,13 @@ export module UserNotificationIncludes { & PickWith export type ActorFollower = - & Pick + & Pick & PickWith & PickWith> & PickWithOpt export type ActorFollowing = - & Pick + & Pick & PickWith & PickWith & PickWith>