From 3a4992633ee62d5edfbb484d9c6bcb3cf158489d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 31 Jul 2023 14:34:36 +0200 Subject: Migrate server to ESM Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports) --- shared/server-commands/bulk/bulk-command.ts | 20 - shared/server-commands/bulk/index.ts | 1 - shared/server-commands/cli/cli-command.ts | 27 - shared/server-commands/cli/index.ts | 1 - .../custom-pages/custom-pages-command.ts | 33 - shared/server-commands/custom-pages/index.ts | 1 - shared/server-commands/feeds/feeds-command.ts | 78 -- shared/server-commands/feeds/index.ts | 1 - shared/server-commands/index.ts | 14 - shared/server-commands/logs/index.ts | 1 - shared/server-commands/logs/logs-command.ts | 56 -- .../server-commands/moderation/abuses-command.ts | 228 ------ shared/server-commands/moderation/index.ts | 1 - shared/server-commands/overviews/index.ts | 1 - .../server-commands/overviews/overviews-command.ts | 23 - shared/server-commands/requests/index.ts | 1 - shared/server-commands/requests/requests.ts | 259 ------- shared/server-commands/runners/index.ts | 3 - .../server-commands/runners/runner-jobs-command.ts | 294 -------- .../runners/runner-registration-tokens-command.ts | 55 -- shared/server-commands/runners/runners-command.ts | 78 -- shared/server-commands/search/index.ts | 1 - shared/server-commands/search/search-command.ts | 98 --- shared/server-commands/server/config-command.ts | 576 -------------- .../server-commands/server/contact-form-command.ts | 31 - shared/server-commands/server/debug-command.ts | 33 - shared/server-commands/server/follows-command.ts | 139 ---- shared/server-commands/server/follows.ts | 20 - shared/server-commands/server/index.ts | 15 - shared/server-commands/server/jobs-command.ts | 84 --- shared/server-commands/server/jobs.ts | 118 --- shared/server-commands/server/metrics-command.ts | 18 - .../server/object-storage-command.ts | 165 ---- shared/server-commands/server/plugins-command.ts | 257 ------- .../server-commands/server/redundancy-command.ts | 80 -- shared/server-commands/server/server.ts | 450 ----------- shared/server-commands/server/servers-command.ts | 103 --- shared/server-commands/server/servers.ts | 68 -- shared/server-commands/server/stats-command.ts | 25 - shared/server-commands/shared/abstract-command.ts | 223 ------ shared/server-commands/shared/index.ts | 1 - shared/server-commands/socket/index.ts | 1 - shared/server-commands/socket/socket-io-command.ts | 24 - shared/server-commands/users/accounts-command.ts | 76 -- shared/server-commands/users/accounts.ts | 15 - shared/server-commands/users/blocklist-command.ts | 165 ---- shared/server-commands/users/index.ts | 10 - shared/server-commands/users/login-command.ts | 159 ---- shared/server-commands/users/login.ts | 19 - .../server-commands/users/notifications-command.ts | 85 --- .../server-commands/users/registrations-command.ts | 151 ---- .../server-commands/users/subscriptions-command.ts | 83 --- shared/server-commands/users/two-factor-command.ts | 92 --- shared/server-commands/users/users-command.ts | 388 ---------- shared/server-commands/videos/blacklist-command.ts | 75 -- shared/server-commands/videos/captions-command.ts | 67 -- .../videos/change-ownership-command.ts | 68 -- .../videos/channel-syncs-command.ts | 55 -- shared/server-commands/videos/channels-command.ts | 202 ----- shared/server-commands/videos/channels.ts | 29 - shared/server-commands/videos/comments-command.ts | 159 ---- shared/server-commands/videos/history-command.ts | 54 -- shared/server-commands/videos/imports-command.ts | 77 -- shared/server-commands/videos/index.ts | 21 - shared/server-commands/videos/live-command.ts | 337 --------- shared/server-commands/videos/live.ts | 128 ---- shared/server-commands/videos/playlists-command.ts | 281 ------- shared/server-commands/videos/services-command.ts | 29 - .../server-commands/videos/storyboard-command.ts | 19 - .../videos/streaming-playlists-command.ts | 119 --- .../videos/video-passwords-command.ts | 55 -- .../server-commands/videos/video-stats-command.ts | 56 -- .../server-commands/videos/video-studio-command.ts | 67 -- .../server-commands/videos/video-token-command.ts | 34 - shared/server-commands/videos/videos-command.ts | 829 --------------------- shared/server-commands/videos/views-command.ts | 51 -- 76 files changed, 7761 deletions(-) delete mode 100644 shared/server-commands/bulk/bulk-command.ts delete mode 100644 shared/server-commands/bulk/index.ts delete mode 100644 shared/server-commands/cli/cli-command.ts delete mode 100644 shared/server-commands/cli/index.ts delete mode 100644 shared/server-commands/custom-pages/custom-pages-command.ts delete mode 100644 shared/server-commands/custom-pages/index.ts delete mode 100644 shared/server-commands/feeds/feeds-command.ts delete mode 100644 shared/server-commands/feeds/index.ts delete mode 100644 shared/server-commands/index.ts delete mode 100644 shared/server-commands/logs/index.ts delete mode 100644 shared/server-commands/logs/logs-command.ts delete mode 100644 shared/server-commands/moderation/abuses-command.ts delete mode 100644 shared/server-commands/moderation/index.ts delete mode 100644 shared/server-commands/overviews/index.ts delete mode 100644 shared/server-commands/overviews/overviews-command.ts delete mode 100644 shared/server-commands/requests/index.ts delete mode 100644 shared/server-commands/requests/requests.ts delete mode 100644 shared/server-commands/runners/index.ts delete mode 100644 shared/server-commands/runners/runner-jobs-command.ts delete mode 100644 shared/server-commands/runners/runner-registration-tokens-command.ts delete mode 100644 shared/server-commands/runners/runners-command.ts delete mode 100644 shared/server-commands/search/index.ts delete mode 100644 shared/server-commands/search/search-command.ts delete mode 100644 shared/server-commands/server/config-command.ts delete mode 100644 shared/server-commands/server/contact-form-command.ts delete mode 100644 shared/server-commands/server/debug-command.ts delete mode 100644 shared/server-commands/server/follows-command.ts delete mode 100644 shared/server-commands/server/follows.ts delete mode 100644 shared/server-commands/server/index.ts delete mode 100644 shared/server-commands/server/jobs-command.ts delete mode 100644 shared/server-commands/server/jobs.ts delete mode 100644 shared/server-commands/server/metrics-command.ts delete mode 100644 shared/server-commands/server/object-storage-command.ts delete mode 100644 shared/server-commands/server/plugins-command.ts delete mode 100644 shared/server-commands/server/redundancy-command.ts delete mode 100644 shared/server-commands/server/server.ts delete mode 100644 shared/server-commands/server/servers-command.ts delete mode 100644 shared/server-commands/server/servers.ts delete mode 100644 shared/server-commands/server/stats-command.ts delete mode 100644 shared/server-commands/shared/abstract-command.ts delete mode 100644 shared/server-commands/shared/index.ts delete mode 100644 shared/server-commands/socket/index.ts delete mode 100644 shared/server-commands/socket/socket-io-command.ts delete mode 100644 shared/server-commands/users/accounts-command.ts delete mode 100644 shared/server-commands/users/accounts.ts delete mode 100644 shared/server-commands/users/blocklist-command.ts delete mode 100644 shared/server-commands/users/index.ts delete mode 100644 shared/server-commands/users/login-command.ts delete mode 100644 shared/server-commands/users/login.ts delete mode 100644 shared/server-commands/users/notifications-command.ts delete mode 100644 shared/server-commands/users/registrations-command.ts delete mode 100644 shared/server-commands/users/subscriptions-command.ts delete mode 100644 shared/server-commands/users/two-factor-command.ts delete mode 100644 shared/server-commands/users/users-command.ts delete mode 100644 shared/server-commands/videos/blacklist-command.ts delete mode 100644 shared/server-commands/videos/captions-command.ts delete mode 100644 shared/server-commands/videos/change-ownership-command.ts delete mode 100644 shared/server-commands/videos/channel-syncs-command.ts delete mode 100644 shared/server-commands/videos/channels-command.ts delete mode 100644 shared/server-commands/videos/channels.ts delete mode 100644 shared/server-commands/videos/comments-command.ts delete mode 100644 shared/server-commands/videos/history-command.ts delete mode 100644 shared/server-commands/videos/imports-command.ts delete mode 100644 shared/server-commands/videos/index.ts delete mode 100644 shared/server-commands/videos/live-command.ts delete mode 100644 shared/server-commands/videos/live.ts delete mode 100644 shared/server-commands/videos/playlists-command.ts delete mode 100644 shared/server-commands/videos/services-command.ts delete mode 100644 shared/server-commands/videos/storyboard-command.ts delete mode 100644 shared/server-commands/videos/streaming-playlists-command.ts delete mode 100644 shared/server-commands/videos/video-passwords-command.ts delete mode 100644 shared/server-commands/videos/video-stats-command.ts delete mode 100644 shared/server-commands/videos/video-studio-command.ts delete mode 100644 shared/server-commands/videos/video-token-command.ts delete mode 100644 shared/server-commands/videos/videos-command.ts delete mode 100644 shared/server-commands/videos/views-command.ts (limited to 'shared/server-commands') diff --git a/shared/server-commands/bulk/bulk-command.ts b/shared/server-commands/bulk/bulk-command.ts deleted file mode 100644 index b5c5673ce..000000000 --- a/shared/server-commands/bulk/bulk-command.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { BulkRemoveCommentsOfBody, HttpStatusCode } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class BulkCommand extends AbstractCommand { - - removeCommentsOf (options: OverrideCommandOptions & { - attributes: BulkRemoveCommentsOfBody - }) { - const { attributes } = options - - return this.postBodyRequest({ - ...options, - - path: '/api/v1/bulk/remove-comments-of', - fields: attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/bulk/index.ts b/shared/server-commands/bulk/index.ts deleted file mode 100644 index 391597243..000000000 --- a/shared/server-commands/bulk/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './bulk-command' diff --git a/shared/server-commands/cli/cli-command.ts b/shared/server-commands/cli/cli-command.ts deleted file mode 100644 index ab9738174..000000000 --- a/shared/server-commands/cli/cli-command.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { exec } from 'child_process' -import { AbstractCommand } from '../shared' - -export class CLICommand extends AbstractCommand { - - static exec (command: string) { - return new Promise((res, rej) => { - exec(command, (err, stdout, _stderr) => { - if (err) return rej(err) - - return res(stdout) - }) - }) - } - - getEnv () { - return `NODE_ENV=test NODE_APP_INSTANCE=${this.server.internalServerNumber}` - } - - async execWithEnv (command: string, configOverride?: any) { - const prefix = configOverride - ? `NODE_CONFIG='${JSON.stringify(configOverride)}'` - : '' - - return CLICommand.exec(`${prefix} ${this.getEnv()} ${command}`) - } -} diff --git a/shared/server-commands/cli/index.ts b/shared/server-commands/cli/index.ts deleted file mode 100644 index 91b5abfbe..000000000 --- a/shared/server-commands/cli/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './cli-command' diff --git a/shared/server-commands/custom-pages/custom-pages-command.ts b/shared/server-commands/custom-pages/custom-pages-command.ts deleted file mode 100644 index cd869a8de..000000000 --- a/shared/server-commands/custom-pages/custom-pages-command.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CustomPage, HttpStatusCode } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class CustomPagesCommand extends AbstractCommand { - - getInstanceHomepage (options: OverrideCommandOptions = {}) { - const path = '/api/v1/custom-pages/homepage/instance' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - updateInstanceHomepage (options: OverrideCommandOptions & { - content: string - }) { - const { content } = options - const path = '/api/v1/custom-pages/homepage/instance' - - return this.putBodyRequest({ - ...options, - - path, - fields: { content }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/custom-pages/index.ts b/shared/server-commands/custom-pages/index.ts deleted file mode 100644 index 58aed04f2..000000000 --- a/shared/server-commands/custom-pages/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './custom-pages-command' diff --git a/shared/server-commands/feeds/feeds-command.ts b/shared/server-commands/feeds/feeds-command.ts deleted file mode 100644 index 26763b43e..000000000 --- a/shared/server-commands/feeds/feeds-command.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { buildUUID } from '@shared/extra-utils' -import { HttpStatusCode } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -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 - const path = '/feeds/' + feed + '.xml' - - const query: { [id: string]: string } = {} - - if (ignoreCache) query.v = buildUUID() - if (format) query.format = format - - return this.getRequestText({ - ...options, - - path, - query, - accept: 'application/xml', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getPodcastXML (options: OverrideCommandOptions & { - ignoreCache: boolean - channelId: number - }) { - const { ignoreCache, channelId } = options - const path = `/feeds/podcast/videos.xml` - - const query: { [id: string]: string } = {} - - if (ignoreCache) query.v = buildUUID() - if (channelId) query.videoChannelId = channelId + '' - - return this.getRequestText({ - ...options, - - path, - query, - accept: 'application/xml', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getJSON (options: OverrideCommandOptions & { - feed: FeedType - ignoreCache: boolean - query?: { [ id: string ]: any } - }) { - const { feed, query = {}, ignoreCache } = options - const path = '/feeds/' + feed + '.json' - - const cacheQuery = ignoreCache - ? { v: buildUUID() } - : {} - - return this.getRequestText({ - ...options, - - path, - query: { ...query, ...cacheQuery }, - accept: 'application/json', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/feeds/index.ts b/shared/server-commands/feeds/index.ts deleted file mode 100644 index 662a22b6f..000000000 --- a/shared/server-commands/feeds/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './feeds-command' diff --git a/shared/server-commands/index.ts b/shared/server-commands/index.ts deleted file mode 100644 index a4581dbc0..000000000 --- a/shared/server-commands/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './bulk' -export * from './cli' -export * from './custom-pages' -export * from './feeds' -export * from './logs' -export * from './moderation' -export * from './overviews' -export * from './requests' -export * from './runners' -export * from './search' -export * from './server' -export * from './socket' -export * from './users' -export * from './videos' diff --git a/shared/server-commands/logs/index.ts b/shared/server-commands/logs/index.ts deleted file mode 100644 index 69452d7f0..000000000 --- a/shared/server-commands/logs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './logs-command' diff --git a/shared/server-commands/logs/logs-command.ts b/shared/server-commands/logs/logs-command.ts deleted file mode 100644 index 1c5de7f59..000000000 --- a/shared/server-commands/logs/logs-command.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ClientLogCreate, HttpStatusCode, ServerLogLevel } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class LogsCommand extends AbstractCommand { - - createLogClient (options: OverrideCommandOptions & { payload: ClientLogCreate }) { - const path = '/api/v1/server/logs/client' - - return this.postBodyRequest({ - ...options, - - path, - fields: options.payload, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getLogs (options: OverrideCommandOptions & { - startDate: Date - endDate?: Date - level?: ServerLogLevel - tagsOneOf?: string[] - }) { - const { startDate, endDate, tagsOneOf, level } = options - const path = '/api/v1/server/logs' - - return this.getRequestBody({ - ...options, - - path, - query: { startDate, endDate, level, tagsOneOf }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getAuditLogs (options: OverrideCommandOptions & { - startDate: Date - endDate?: Date - }) { - const { startDate, endDate } = options - - const path = '/api/v1/server/audit-logs' - - return this.getRequestBody({ - ...options, - - path, - query: { startDate, endDate }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - -} diff --git a/shared/server-commands/moderation/abuses-command.ts b/shared/server-commands/moderation/abuses-command.ts deleted file mode 100644 index 0db32ba46..000000000 --- a/shared/server-commands/moderation/abuses-command.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { pick } from '@shared/core-utils' -import { - AbuseFilter, - AbuseMessage, - AbusePredefinedReasonsString, - AbuseState, - AbuseUpdate, - AbuseVideoIs, - AdminAbuse, - HttpStatusCode, - ResultList, - UserAbuse -} from '@shared/models' -import { unwrapBody } from '../requests/requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class AbusesCommand extends AbstractCommand { - - report (options: OverrideCommandOptions & { - reason: string - - accountId?: number - videoId?: number - commentId?: number - - predefinedReasons?: AbusePredefinedReasonsString[] - - startAt?: number - endAt?: number - }) { - const path = '/api/v1/abuses' - - const video = options.videoId - ? { - id: options.videoId, - startAt: options.startAt, - endAt: options.endAt - } - : undefined - - const comment = options.commentId - ? { id: options.commentId } - : undefined - - const account = options.accountId - ? { id: options.accountId } - : undefined - - const body = { - account, - video, - comment, - - reason: options.reason, - predefinedReasons: options.predefinedReasons - } - - return unwrapBody<{ abuse: { id: number } }>(this.postBodyRequest({ - ...options, - - path, - fields: body, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - getAdminList (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - - id?: number - predefinedReason?: AbusePredefinedReasonsString - search?: string - filter?: AbuseFilter - state?: AbuseState - videoIs?: AbuseVideoIs - searchReporter?: string - searchReportee?: string - searchVideo?: string - searchVideoChannel?: string - } = {}) { - const toPick: (keyof typeof options)[] = [ - 'count', - 'filter', - 'id', - 'predefinedReason', - 'search', - 'searchReportee', - 'searchReporter', - 'searchVideo', - 'searchVideoChannel', - 'sort', - 'start', - 'state', - 'videoIs' - ] - - const path = '/api/v1/abuses' - - const defaultQuery = { sort: 'createdAt' } - const query = { ...defaultQuery, ...pick(options, toPick) } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getUserList (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - - id?: number - search?: string - state?: AbuseState - }) { - const toPick: (keyof typeof options)[] = [ - 'id', - 'search', - 'state', - 'start', - 'count', - 'sort' - ] - - const path = '/api/v1/users/me/abuses' - - const defaultQuery = { sort: 'createdAt' } - const query = { ...defaultQuery, ...pick(options, toPick) } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - update (options: OverrideCommandOptions & { - abuseId: number - body: AbuseUpdate - }) { - const { abuseId, body } = options - const path = '/api/v1/abuses/' + abuseId - - return this.putBodyRequest({ - ...options, - - path, - fields: body, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - delete (options: OverrideCommandOptions & { - abuseId: number - }) { - const { abuseId } = options - const path = '/api/v1/abuses/' + abuseId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - listMessages (options: OverrideCommandOptions & { - abuseId: number - }) { - const { abuseId } = options - const path = '/api/v1/abuses/' + abuseId + '/messages' - - return this.getRequestBody>({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - deleteMessage (options: OverrideCommandOptions & { - abuseId: number - messageId: number - }) { - const { abuseId, messageId } = options - const path = '/api/v1/abuses/' + abuseId + '/messages/' + messageId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - addMessage (options: OverrideCommandOptions & { - abuseId: number - message: string - }) { - const { abuseId, message } = options - const path = '/api/v1/abuses/' + abuseId + '/messages' - - return this.postBodyRequest({ - ...options, - - path, - fields: { message }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - -} diff --git a/shared/server-commands/moderation/index.ts b/shared/server-commands/moderation/index.ts deleted file mode 100644 index b37643956..000000000 --- a/shared/server-commands/moderation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './abuses-command' diff --git a/shared/server-commands/overviews/index.ts b/shared/server-commands/overviews/index.ts deleted file mode 100644 index e19551907..000000000 --- a/shared/server-commands/overviews/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './overviews-command' diff --git a/shared/server-commands/overviews/overviews-command.ts b/shared/server-commands/overviews/overviews-command.ts deleted file mode 100644 index 06b4892d2..000000000 --- a/shared/server-commands/overviews/overviews-command.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { HttpStatusCode, VideosOverview } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class OverviewsCommand extends AbstractCommand { - - getVideos (options: OverrideCommandOptions & { - page: number - }) { - const { page } = options - const path = '/api/v1/overviews/videos' - - const query = { page } - - return this.getRequestBody({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/requests/index.ts b/shared/server-commands/requests/index.ts deleted file mode 100644 index 802982301..000000000 --- a/shared/server-commands/requests/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './requests' diff --git a/shared/server-commands/requests/requests.ts b/shared/server-commands/requests/requests.ts deleted file mode 100644 index 8227017eb..000000000 --- a/shared/server-commands/requests/requests.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ - -import { decode } from 'querystring' -import request from 'supertest' -import { URL } from 'url' -import { buildAbsoluteFixturePath, pick } from '@shared/core-utils' -import { HttpStatusCode } from '@shared/models' - -export type CommonRequestParams = { - url: string - path?: string - contentType?: string - responseType?: string - range?: string - redirects?: number - accept?: string - host?: string - token?: string - headers?: { [ name: string ]: string } - type?: string - xForwardedFor?: string - expectedStatus?: HttpStatusCode -} - -function makeRawRequest (options: { - url: string - token?: string - expectedStatus?: HttpStatusCode - range?: string - query?: { [ id: string ]: string } - method?: 'GET' | 'POST' - headers?: { [ name: string ]: string } -}) { - const { host, protocol, pathname } = new URL(options.url) - - const reqOptions = { - url: `${protocol}//${host}`, - path: pathname, - contentType: undefined, - - ...pick(options, [ 'expectedStatus', 'range', 'token', 'query', 'headers' ]) - } - - if (options.method === 'POST') { - return makePostBodyRequest(reqOptions) - } - - return makeGetRequest(reqOptions) -} - -function makeGetRequest (options: CommonRequestParams & { - query?: any - rawQuery?: string -}) { - const req = request(options.url).get(options.path) - - if (options.query) req.query(options.query) - if (options.rawQuery) req.query(options.rawQuery) - - return buildRequest(req, { contentType: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) -} - -function makeHTMLRequest (url: string, path: string) { - return makeGetRequest({ - url, - path, - accept: 'text/html', - expectedStatus: HttpStatusCode.OK_200 - }) -} - -function makeActivityPubGetRequest (url: string, path: string, expectedStatus = HttpStatusCode.OK_200) { - return makeGetRequest({ - url, - path, - expectedStatus, - accept: 'application/activity+json,text/html;q=0.9,\\*/\\*;q=0.8' - }) -} - -function makeDeleteRequest (options: CommonRequestParams & { - query?: any - rawQuery?: string -}) { - const req = request(options.url).delete(options.path) - - if (options.query) req.query(options.query) - if (options.rawQuery) req.query(options.rawQuery) - - return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) -} - -function makeUploadRequest (options: CommonRequestParams & { - method?: 'POST' | 'PUT' - - fields: { [ fieldName: string ]: any } - attaches?: { [ attachName: string ]: any | any[] } -}) { - let req = options.method === 'PUT' - ? request(options.url).put(options.path) - : request(options.url).post(options.path) - - req = buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) - - buildFields(req, options.fields) - - Object.keys(options.attaches || {}).forEach(attach => { - const value = options.attaches[attach] - if (!value) return - - if (Array.isArray(value)) { - req.attach(attach, buildAbsoluteFixturePath(value[0]), value[1]) - } else { - req.attach(attach, buildAbsoluteFixturePath(value)) - } - }) - - return req -} - -function makePostBodyRequest (options: CommonRequestParams & { - fields?: { [ fieldName: string ]: any } -}) { - const req = request(options.url).post(options.path) - .send(options.fields) - - return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) -} - -function makePutBodyRequest (options: { - url: string - path: string - token?: string - fields: { [ fieldName: string ]: any } - expectedStatus?: HttpStatusCode - headers?: { [name: string]: string } -}) { - const req = request(options.url).put(options.path) - .send(options.fields) - - return buildRequest(req, { accept: 'application/json', expectedStatus: HttpStatusCode.BAD_REQUEST_400, ...options }) -} - -function decodeQueryString (path: string) { - return decode(path.split('?')[1]) -} - -// --------------------------------------------------------------------------- - -function unwrapBody (test: request.Test): Promise { - return test.then(res => res.body) -} - -function unwrapText (test: request.Test): Promise { - return test.then(res => res.text) -} - -function unwrapBodyOrDecodeToJSON (test: request.Test): Promise { - return test.then(res => { - if (res.body instanceof Buffer) { - try { - return JSON.parse(new TextDecoder().decode(res.body)) - } catch (err) { - console.error('Cannot decode JSON.', { res, body: res.body instanceof Buffer ? res.body.toString() : res.body }) - throw err - } - } - - if (res.text) { - try { - return JSON.parse(res.text) - } catch (err) { - console.error('Cannot decode json', { res, text: res.text }) - throw err - } - } - - return res.body - }) -} - -function unwrapTextOrDecode (test: request.Test): Promise { - return test.then(res => res.text || new TextDecoder().decode(res.body)) -} - -// --------------------------------------------------------------------------- - -export { - makeHTMLRequest, - makeGetRequest, - decodeQueryString, - makeUploadRequest, - makePostBodyRequest, - makePutBodyRequest, - makeDeleteRequest, - makeRawRequest, - makeActivityPubGetRequest, - unwrapBody, - unwrapTextOrDecode, - unwrapBodyOrDecodeToJSON, - unwrapText -} - -// --------------------------------------------------------------------------- - -function buildRequest (req: request.Test, options: CommonRequestParams) { - if (options.contentType) req.set('Accept', options.contentType) - if (options.responseType) req.responseType(options.responseType) - if (options.token) req.set('Authorization', 'Bearer ' + options.token) - if (options.range) req.set('Range', options.range) - if (options.accept) req.set('Accept', options.accept) - if (options.host) req.set('Host', options.host) - if (options.redirects) req.redirects(options.redirects) - if (options.xForwardedFor) req.set('X-Forwarded-For', options.xForwardedFor) - if (options.type) req.type(options.type) - - Object.keys(options.headers || {}).forEach(name => { - req.set(name, options.headers[name]) - }) - - return req.expect(res => { - if (options.expectedStatus && res.status !== options.expectedStatus) { - const err = new Error(`Expected status ${options.expectedStatus}, got ${res.status}. ` + - `\nThe server responded: "${res.body?.error ?? res.text}".\n` + - 'You may take a closer look at the logs. To see how to do so, check out this page: ' + - 'https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/development/tests.md#debug-server-logs'); - - (err as any).res = res - - throw err - } - - return res - }) -} - -function buildFields (req: request.Test, fields: { [ fieldName: string ]: any }, namespace?: string) { - if (!fields) return - - let formKey: string - - for (const key of Object.keys(fields)) { - if (namespace) formKey = `${namespace}[${key}]` - else formKey = key - - if (fields[key] === undefined) continue - - if (Array.isArray(fields[key]) && fields[key].length === 0) { - req.field(key, []) - continue - } - - if (fields[key] !== null && typeof fields[key] === 'object') { - buildFields(req, fields[key], formKey) - } else { - req.field(formKey, fields[key]) - } - } -} diff --git a/shared/server-commands/runners/index.ts b/shared/server-commands/runners/index.ts deleted file mode 100644 index 9e8e1baf2..000000000 --- a/shared/server-commands/runners/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './runner-jobs-command' -export * from './runner-registration-tokens-command' -export * from './runners-command' diff --git a/shared/server-commands/runners/runner-jobs-command.ts b/shared/server-commands/runners/runner-jobs-command.ts deleted file mode 100644 index 0a0ffb5d3..000000000 --- a/shared/server-commands/runners/runner-jobs-command.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { omit, pick, wait } from '@shared/core-utils' -import { - AbortRunnerJobBody, - AcceptRunnerJobBody, - AcceptRunnerJobResult, - ErrorRunnerJobBody, - HttpStatusCode, - isHLSTranscodingPayloadSuccess, - isLiveRTMPHLSTranscodingUpdatePayload, - isWebVideoOrAudioMergeTranscodingPayloadSuccess, - ListRunnerJobsQuery, - RequestRunnerJobBody, - RequestRunnerJobResult, - ResultList, - RunnerJobAdmin, - RunnerJobLiveRTMPHLSTranscodingPayload, - RunnerJobPayload, - RunnerJobState, - RunnerJobSuccessBody, - RunnerJobSuccessPayload, - RunnerJobType, - RunnerJobUpdateBody, - RunnerJobVODPayload -} from '@shared/models' -import { unwrapBody } from '../requests' -import { waitJobs } from '../server' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class RunnerJobsCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & ListRunnerJobsQuery = {}) { - const path = '/api/v1/runners/jobs' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort', 'search', 'stateOneOf' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - cancelByAdmin (options: OverrideCommandOptions & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/cancel' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - deleteByAdmin (options: OverrideCommandOptions & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - request (options: OverrideCommandOptions & RequestRunnerJobBody) { - const path = '/api/v1/runners/jobs/request' - - return unwrapBody(this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - async requestVOD (options: OverrideCommandOptions & RequestRunnerJobBody) { - const vodTypes = new Set([ 'vod-audio-merge-transcoding', 'vod-hls-transcoding', 'vod-web-video-transcoding' ]) - - const { availableJobs } = await this.request(options) - - return { - availableJobs: availableJobs.filter(j => vodTypes.has(j.type)) - } as RequestRunnerJobResult - } - - async requestLive (options: OverrideCommandOptions & RequestRunnerJobBody) { - const vodTypes = new Set([ 'live-rtmp-hls-transcoding' ]) - - const { availableJobs } = await this.request(options) - - return { - availableJobs: availableJobs.filter(j => vodTypes.has(j.type)) - } as RequestRunnerJobResult - } - - // --------------------------------------------------------------------------- - - accept (options: OverrideCommandOptions & AcceptRunnerJobBody & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/accept' - - return unwrapBody>(this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - abort (options: OverrideCommandOptions & AbortRunnerJobBody & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/abort' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'reason', 'jobToken', 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - update (options: OverrideCommandOptions & RunnerJobUpdateBody & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/update' - - const { payload } = options - const attaches: { [id: string]: any } = {} - let payloadWithoutFiles = payload - - if (isLiveRTMPHLSTranscodingUpdatePayload(payload)) { - if (payload.masterPlaylistFile) { - attaches[`payload[masterPlaylistFile]`] = payload.masterPlaylistFile - } - - attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile - attaches[`payload[videoChunkFile]`] = payload.videoChunkFile - - payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'masterPlaylistFile', 'resolutionPlaylistFile', 'videoChunkFile' ]) - } - - return this.postUploadRequest({ - ...options, - - path, - fields: { - ...pick(options, [ 'progress', 'jobToken', 'runnerToken' ]), - - payload: payloadWithoutFiles - }, - attaches, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - error (options: OverrideCommandOptions & ErrorRunnerJobBody & { jobUUID: string }) { - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/error' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'message', 'jobToken', 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - success (options: OverrideCommandOptions & RunnerJobSuccessBody & { jobUUID: string }) { - const { payload } = options - - const path = '/api/v1/runners/jobs/' + options.jobUUID + '/success' - const attaches: { [id: string]: any } = {} - let payloadWithoutFiles = payload - - if ((isWebVideoOrAudioMergeTranscodingPayloadSuccess(payload) || isHLSTranscodingPayloadSuccess(payload)) && payload.videoFile) { - attaches[`payload[videoFile]`] = payload.videoFile - - payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'videoFile' ]) - } - - if (isHLSTranscodingPayloadSuccess(payload) && payload.resolutionPlaylistFile) { - attaches[`payload[resolutionPlaylistFile]`] = payload.resolutionPlaylistFile - - payloadWithoutFiles = omit(payloadWithoutFiles as any, [ 'resolutionPlaylistFile' ]) - } - - return this.postUploadRequest({ - ...options, - - path, - attaches, - fields: { - ...pick(options, [ 'jobToken', 'runnerToken' ]), - - payload: payloadWithoutFiles - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getJobFile (options: OverrideCommandOptions & { url: string, jobToken: string, runnerToken: string }) { - const { host, protocol, pathname } = new URL(options.url) - - return this.postBodyRequest({ - url: `${protocol}//${host}`, - path: pathname, - - fields: pick(options, [ 'jobToken', 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - async autoAccept (options: OverrideCommandOptions & RequestRunnerJobBody & { type?: RunnerJobType }) { - const { availableJobs } = await this.request(options) - - const job = options.type - ? availableJobs.find(j => j.type === options.type) - : availableJobs[0] - - return this.accept({ ...options, jobUUID: job.uuid }) - } - - async autoProcessWebVideoJob (runnerToken: string, jobUUIDToProcess?: string) { - let jobUUID = jobUUIDToProcess - - if (!jobUUID) { - const { availableJobs } = await this.request({ runnerToken }) - jobUUID = availableJobs[0].uuid - } - - const { job } = await this.accept({ runnerToken, jobUUID }) - const jobToken = job.jobToken - - const payload: RunnerJobSuccessPayload = { videoFile: 'video_short.mp4' } - await this.success({ runnerToken, jobUUID, jobToken, payload }) - - await waitJobs([ this.server ]) - - return job - } - - async cancelAllJobs (options: { state?: RunnerJobState } = {}) { - const { state } = options - - const { data } = await this.list({ count: 100 }) - - const allowedStates = new Set([ - RunnerJobState.PENDING, - RunnerJobState.PROCESSING, - RunnerJobState.WAITING_FOR_PARENT_JOB - ]) - - for (const job of data) { - if (state && job.state.id !== state) continue - else if (allowedStates.has(job.state.id) !== true) continue - - await this.cancelByAdmin({ jobUUID: job.uuid }) - } - } - - async getJob (options: OverrideCommandOptions & { uuid: string }) { - const { data } = await this.list({ ...options, count: 100, sort: '-updatedAt' }) - - return data.find(j => j.uuid === options.uuid) - } - - async requestLiveJob (runnerToken: string) { - let availableJobs: RequestRunnerJobResult['availableJobs'] = [] - - while (availableJobs.length === 0) { - const result = await this.requestLive({ runnerToken }) - availableJobs = result.availableJobs - - if (availableJobs.length === 1) break - - await wait(150) - } - - return availableJobs[0] - } -} diff --git a/shared/server-commands/runners/runner-registration-tokens-command.ts b/shared/server-commands/runners/runner-registration-tokens-command.ts deleted file mode 100644 index e4f2e3d95..000000000 --- a/shared/server-commands/runners/runner-registration-tokens-command.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { pick } from '@shared/core-utils' -import { HttpStatusCode, ResultList, RunnerRegistrationToken } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class RunnerRegistrationTokensCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - } = {}) { - const path = '/api/v1/runners/registration-tokens' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - generate (options: OverrideCommandOptions = {}) { - const path = '/api/v1/runners/registration-tokens/generate' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - delete (options: OverrideCommandOptions & { - id: number - }) { - const path = '/api/v1/runners/registration-tokens/' + options.id - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async getFirstRegistrationToken (options: OverrideCommandOptions = {}) { - const { data } = await this.list(options) - - return data[0].registrationToken - } -} diff --git a/shared/server-commands/runners/runners-command.ts b/shared/server-commands/runners/runners-command.ts deleted file mode 100644 index b0083e841..000000000 --- a/shared/server-commands/runners/runners-command.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { pick } from '@shared/core-utils' -import { buildUUID } from '@shared/extra-utils' -import { HttpStatusCode, RegisterRunnerBody, RegisterRunnerResult, ResultList, Runner, UnregisterRunnerBody } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class RunnersCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - } = {}) { - const path = '/api/v1/runners' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - register (options: OverrideCommandOptions & RegisterRunnerBody) { - const path = '/api/v1/runners/register' - - return unwrapBody(this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'name', 'registrationToken', 'description' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - unregister (options: OverrideCommandOptions & UnregisterRunnerBody) { - const path = '/api/v1/runners/unregister' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'runnerToken' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - delete (options: OverrideCommandOptions & { - id: number - }) { - const path = '/api/v1/runners/' + options.id - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - async autoRegisterRunner () { - const { data } = await this.server.runnerRegistrationTokens.list({ sort: 'createdAt' }) - - const { runnerToken } = await this.register({ - name: 'runner ' + buildUUID(), - registrationToken: data[0].registrationToken - }) - - return runnerToken - } -} diff --git a/shared/server-commands/search/index.ts b/shared/server-commands/search/index.ts deleted file mode 100644 index 48dbe8ae9..000000000 --- a/shared/server-commands/search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './search-command' diff --git a/shared/server-commands/search/search-command.ts b/shared/server-commands/search/search-command.ts deleted file mode 100644 index a5b498b66..000000000 --- a/shared/server-commands/search/search-command.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - HttpStatusCode, - ResultList, - Video, - VideoChannel, - VideoChannelsSearchQuery, - VideoPlaylist, - VideoPlaylistsSearchQuery, - VideosSearchQuery -} from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class SearchCommand extends AbstractCommand { - - searchChannels (options: OverrideCommandOptions & { - search: string - }) { - return this.advancedChannelSearch({ - ...options, - - search: { search: options.search } - }) - } - - advancedChannelSearch (options: OverrideCommandOptions & { - search: VideoChannelsSearchQuery - }) { - const { search } = options - const path = '/api/v1/search/video-channels' - - return this.getRequestBody>({ - ...options, - - path, - query: search, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - searchPlaylists (options: OverrideCommandOptions & { - search: string - }) { - return this.advancedPlaylistSearch({ - ...options, - - search: { search: options.search } - }) - } - - advancedPlaylistSearch (options: OverrideCommandOptions & { - search: VideoPlaylistsSearchQuery - }) { - const { search } = options - const path = '/api/v1/search/video-playlists' - - return this.getRequestBody>({ - ...options, - - path, - query: search, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - searchVideos (options: OverrideCommandOptions & { - search: string - sort?: string - }) { - const { search, sort } = options - - return this.advancedVideoSearch({ - ...options, - - search: { - search, - sort: sort ?? '-publishedAt' - } - }) - } - - advancedVideoSearch (options: OverrideCommandOptions & { - search: VideosSearchQuery - }) { - const { search } = options - const path = '/api/v1/search/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: search, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/server/config-command.ts b/shared/server-commands/server/config-command.ts deleted file mode 100644 index 5ee2fe021..000000000 --- a/shared/server-commands/server/config-command.ts +++ /dev/null @@ -1,576 +0,0 @@ -import { merge } from 'lodash' -import { About, CustomConfig, HttpStatusCode, ServerConfig } from '@shared/models' -import { DeepPartial } from '@shared/typescript-utils' -import { AbstractCommand, OverrideCommandOptions } from '../shared/abstract-command' - -export class ConfigCommand extends AbstractCommand { - - static getCustomConfigResolutions (enabled: boolean, with0p = false) { - return { - '0p': enabled && with0p, - '144p': enabled, - '240p': enabled, - '360p': enabled, - '480p': enabled, - '720p': enabled, - '1080p': enabled, - '1440p': enabled, - '2160p': enabled - } - } - - // --------------------------------------------------------------------------- - - static getEmailOverrideConfig (emailPort: number) { - return { - smtp: { - hostname: '127.0.0.1', - port: emailPort - } - } - } - - // --------------------------------------------------------------------------- - - enableSignup (requiresApproval: boolean, limit = -1) { - return this.updateExistingSubConfig({ - newConfig: { - signup: { - enabled: true, - requiresApproval, - limit - } - } - }) - } - - // --------------------------------------------------------------------------- - - disableImports () { - return this.setImportsEnabled(false) - } - - enableImports () { - return this.setImportsEnabled(true) - } - - private setImportsEnabled (enabled: boolean) { - return this.updateExistingSubConfig({ - newConfig: { - import: { - videos: { - http: { - enabled - }, - - torrent: { - enabled - } - } - } - } - }) - } - - // --------------------------------------------------------------------------- - - disableFileUpdate () { - return this.setFileUpdateEnabled(false) - } - - enableFileUpdate () { - return this.setFileUpdateEnabled(true) - } - - private setFileUpdateEnabled (enabled: boolean) { - return this.updateExistingSubConfig({ - newConfig: { - videoFile: { - update: { - enabled - } - } - } - }) - } - - // --------------------------------------------------------------------------- - - enableChannelSync () { - return this.setChannelSyncEnabled(true) - } - - disableChannelSync () { - return this.setChannelSyncEnabled(false) - } - - private setChannelSyncEnabled (enabled: boolean) { - return this.updateExistingSubConfig({ - newConfig: { - import: { - videoChannelSynchronization: { - enabled - } - } - } - }) - } - - // --------------------------------------------------------------------------- - - enableLive (options: { - allowReplay?: boolean - transcoding?: boolean - resolutions?: 'min' | 'max' // Default max - } = {}) { - const { allowReplay, transcoding, resolutions = 'max' } = options - - return this.updateExistingSubConfig({ - newConfig: { - live: { - enabled: true, - allowReplay: allowReplay ?? true, - transcoding: { - enabled: transcoding ?? true, - resolutions: ConfigCommand.getCustomConfigResolutions(resolutions === 'max') - } - } - } - }) - } - - disableTranscoding () { - return this.updateExistingSubConfig({ - newConfig: { - transcoding: { - enabled: false - }, - videoStudio: { - enabled: false - } - } - }) - } - - enableTranscoding (options: { - webVideo?: boolean // default true - hls?: boolean // default true - with0p?: boolean // default false - } = {}) { - const { webVideo = true, hls = true, with0p = false } = options - - return this.updateExistingSubConfig({ - newConfig: { - transcoding: { - enabled: true, - - allowAudioFiles: true, - allowAdditionalExtensions: true, - - resolutions: ConfigCommand.getCustomConfigResolutions(true, with0p), - - webVideos: { - enabled: webVideo - }, - hls: { - enabled: hls - } - } - } - }) - } - - enableMinimumTranscoding (options: { - webVideo?: boolean // default true - hls?: boolean // default true - } = {}) { - const { webVideo = true, hls = true } = options - - return this.updateExistingSubConfig({ - newConfig: { - transcoding: { - enabled: true, - - allowAudioFiles: true, - allowAdditionalExtensions: true, - - resolutions: { - ...ConfigCommand.getCustomConfigResolutions(false), - - '240p': true - }, - - webVideos: { - enabled: webVideo - }, - hls: { - enabled: hls - } - } - } - }) - } - - enableRemoteTranscoding () { - return this.updateExistingSubConfig({ - newConfig: { - transcoding: { - remoteRunners: { - enabled: true - } - }, - live: { - transcoding: { - remoteRunners: { - enabled: true - } - } - } - } - }) - } - - enableRemoteStudio () { - return this.updateExistingSubConfig({ - newConfig: { - videoStudio: { - remoteRunners: { - enabled: true - } - } - } - }) - } - - // --------------------------------------------------------------------------- - - enableStudio () { - return this.updateExistingSubConfig({ - newConfig: { - videoStudio: { - enabled: true - } - } - }) - } - - // --------------------------------------------------------------------------- - - getConfig (options: OverrideCommandOptions = {}) { - const path = '/api/v1/config' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async getIndexHTMLConfig (options: OverrideCommandOptions = {}) { - const text = await this.getRequestText({ - ...options, - - path: '/', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - - const match = text.match('') - - // We parse the string twice, first to extract the string and then to extract the JSON - return JSON.parse(JSON.parse(match[1])) as ServerConfig - } - - getAbout (options: OverrideCommandOptions = {}) { - const path = '/api/v1/config/about' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getCustomConfig (options: OverrideCommandOptions = {}) { - const path = '/api/v1/config/custom' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - updateCustomConfig (options: OverrideCommandOptions & { - newCustomConfig: CustomConfig - }) { - const path = '/api/v1/config/custom' - - return this.putBodyRequest({ - ...options, - - path, - fields: options.newCustomConfig, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - deleteCustomConfig (options: OverrideCommandOptions = {}) { - const path = '/api/v1/config/custom' - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async updateExistingSubConfig (options: OverrideCommandOptions & { - newConfig: DeepPartial - }) { - const existing = await this.getCustomConfig({ ...options, expectedStatus: HttpStatusCode.OK_200 }) - - return this.updateCustomConfig({ ...options, newCustomConfig: merge({}, existing, options.newConfig) }) - } - - updateCustomSubConfig (options: OverrideCommandOptions & { - newConfig: DeepPartial - }) { - const newCustomConfig: CustomConfig = { - instance: { - name: 'PeerTube updated', - shortDescription: 'my short description', - description: 'my super description', - terms: 'my super terms', - codeOfConduct: 'my super coc', - - creationReason: 'my super creation reason', - moderationInformation: 'my super moderation information', - administrator: 'Kuja', - maintenanceLifetime: 'forever', - businessModel: 'my super business model', - hardwareInformation: '2vCore 3GB RAM', - - languages: [ 'en', 'es' ], - categories: [ 1, 2 ], - - isNSFW: true, - defaultNSFWPolicy: 'blur', - - defaultClientRoute: '/videos/recently-added', - - customizations: { - javascript: 'alert("coucou")', - css: 'body { background-color: red; }' - } - }, - theme: { - default: 'default' - }, - services: { - twitter: { - username: '@MySuperUsername', - whitelisted: true - } - }, - client: { - videos: { - miniature: { - preferAuthorDisplayName: false - } - }, - menu: { - login: { - redirectOnSingleExternalAuth: false - } - } - }, - cache: { - previews: { - size: 2 - }, - captions: { - size: 3 - }, - torrents: { - size: 4 - }, - storyboards: { - size: 5 - } - }, - signup: { - enabled: false, - limit: 5, - requiresApproval: true, - requiresEmailVerification: false, - minimumAge: 16 - }, - admin: { - email: 'superadmin1@example.com' - }, - contactForm: { - enabled: true - }, - user: { - history: { - videos: { - enabled: true - } - }, - videoQuota: 5242881, - videoQuotaDaily: 318742 - }, - videoChannels: { - maxPerUser: 20 - }, - transcoding: { - enabled: true, - remoteRunners: { - enabled: false - }, - allowAdditionalExtensions: true, - allowAudioFiles: true, - threads: 1, - concurrency: 3, - profile: 'default', - resolutions: { - '0p': false, - '144p': false, - '240p': false, - '360p': true, - '480p': true, - '720p': false, - '1080p': false, - '1440p': false, - '2160p': false - }, - alwaysTranscodeOriginalResolution: true, - webVideos: { - enabled: true - }, - hls: { - enabled: false - } - }, - live: { - enabled: true, - allowReplay: false, - latencySetting: { - enabled: false - }, - maxDuration: -1, - maxInstanceLives: -1, - maxUserLives: 50, - transcoding: { - enabled: true, - remoteRunners: { - enabled: false - }, - threads: 4, - profile: 'default', - resolutions: { - '144p': true, - '240p': true, - '360p': true, - '480p': true, - '720p': true, - '1080p': true, - '1440p': true, - '2160p': true - }, - alwaysTranscodeOriginalResolution: true - } - }, - videoStudio: { - enabled: false, - remoteRunners: { - enabled: false - } - }, - videoFile: { - update: { - enabled: false - } - }, - import: { - videos: { - concurrency: 3, - http: { - enabled: false - }, - torrent: { - enabled: false - } - }, - videoChannelSynchronization: { - enabled: false, - maxPerUser: 10 - } - }, - trending: { - videos: { - algorithms: { - enabled: [ 'hot', 'most-viewed', 'most-liked' ], - default: 'hot' - } - } - }, - autoBlacklist: { - videos: { - ofUsers: { - enabled: false - } - } - }, - followers: { - instance: { - enabled: true, - manualApproval: false - } - }, - followings: { - instance: { - autoFollowBack: { - enabled: false - }, - autoFollowIndex: { - indexUrl: 'https://instances.joinpeertube.org/api/v1/instances/hosts', - enabled: false - } - } - }, - broadcastMessage: { - enabled: true, - level: 'warning', - message: 'hello', - dismissable: true - }, - search: { - remoteUri: { - users: true, - anonymous: true - }, - searchIndex: { - enabled: true, - url: 'https://search.joinpeertube.org', - disableLocalSearch: true, - isDefaultSearch: true - } - } - } - - merge(newCustomConfig, options.newConfig) - - return this.updateCustomConfig({ ...options, newCustomConfig }) - } -} diff --git a/shared/server-commands/server/contact-form-command.ts b/shared/server-commands/server/contact-form-command.ts deleted file mode 100644 index 0e8fd6d84..000000000 --- a/shared/server-commands/server/contact-form-command.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HttpStatusCode } from '@shared/models' -import { ContactForm } from '../../models/server' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ContactFormCommand extends AbstractCommand { - - send (options: OverrideCommandOptions & { - fromEmail: string - fromName: string - subject: string - body: string - }) { - const path = '/api/v1/server/contact' - - const body: ContactForm = { - fromEmail: options.fromEmail, - fromName: options.fromName, - subject: options.subject, - body: options.body - } - - return this.postBodyRequest({ - ...options, - - path, - fields: body, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/server/debug-command.ts b/shared/server-commands/server/debug-command.ts deleted file mode 100644 index 3c5a785bb..000000000 --- a/shared/server-commands/server/debug-command.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Debug, HttpStatusCode, SendDebugCommand } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class DebugCommand extends AbstractCommand { - - getDebug (options: OverrideCommandOptions = {}) { - const path = '/api/v1/server/debug' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - sendCommand (options: OverrideCommandOptions & { - body: SendDebugCommand - }) { - const { body } = options - const path = '/api/v1/server/debug/run-command' - - return this.postBodyRequest({ - ...options, - - path, - fields: body, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/server/follows-command.ts b/shared/server-commands/server/follows-command.ts deleted file mode 100644 index 496e11df1..000000000 --- a/shared/server-commands/server/follows-command.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { pick } from '@shared/core-utils' -import { ActivityPubActorType, ActorFollow, FollowState, HttpStatusCode, ResultList, ServerFollowCreate } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' -import { PeerTubeServer } from './server' - -export class FollowsCommand extends AbstractCommand { - - getFollowers (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - search?: string - actorType?: ActivityPubActorType - state?: FollowState - } = {}) { - const path = '/api/v1/server/followers' - - const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getFollowings (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - search?: string - actorType?: ActivityPubActorType - state?: FollowState - } = {}) { - const path = '/api/v1/server/following' - - const query = pick(options, [ 'start', 'count', 'sort', 'search', 'state', 'actorType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - follow (options: OverrideCommandOptions & { - hosts?: string[] - handles?: string[] - }) { - const path = '/api/v1/server/following' - - const fields: ServerFollowCreate = {} - - if (options.hosts) { - fields.hosts = options.hosts.map(f => f.replace(/^http:\/\//, '')) - } - - if (options.handles) { - fields.handles = options.handles - } - - return this.postBodyRequest({ - ...options, - - path, - fields, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async unfollow (options: OverrideCommandOptions & { - target: PeerTubeServer | string - }) { - const { target } = options - - const handle = typeof target === 'string' - ? target - : target.host - - const path = '/api/v1/server/following/' + handle - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - acceptFollower (options: OverrideCommandOptions & { - follower: string - }) { - const path = '/api/v1/server/followers/' + options.follower + '/accept' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - rejectFollower (options: OverrideCommandOptions & { - follower: string - }) { - const path = '/api/v1/server/followers/' + options.follower + '/reject' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeFollower (options: OverrideCommandOptions & { - follower: PeerTubeServer - }) { - const path = '/api/v1/server/followers/peertube@' + options.follower.host - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/server/follows.ts b/shared/server-commands/server/follows.ts deleted file mode 100644 index 698238f29..000000000 --- a/shared/server-commands/server/follows.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { waitJobs } from './jobs' -import { PeerTubeServer } from './server' - -async function doubleFollow (server1: PeerTubeServer, server2: PeerTubeServer) { - await Promise.all([ - server1.follows.follow({ hosts: [ server2.url ] }), - server2.follows.follow({ hosts: [ server1.url ] }) - ]) - - // Wait request propagation - await waitJobs([ server1, server2 ]) - - return true -} - -// --------------------------------------------------------------------------- - -export { - doubleFollow -} diff --git a/shared/server-commands/server/index.ts b/shared/server-commands/server/index.ts deleted file mode 100644 index 9a2fbf8d3..000000000 --- a/shared/server-commands/server/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export * from './config-command' -export * from './contact-form-command' -export * from './debug-command' -export * from './follows-command' -export * from './follows' -export * from './jobs' -export * from './jobs-command' -export * from './metrics-command' -export * from './object-storage-command' -export * from './plugins-command' -export * from './redundancy-command' -export * from './server' -export * from './servers-command' -export * from './servers' -export * from './stats-command' diff --git a/shared/server-commands/server/jobs-command.ts b/shared/server-commands/server/jobs-command.ts deleted file mode 100644 index b8790ea00..000000000 --- a/shared/server-commands/server/jobs-command.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { pick } from '@shared/core-utils' -import { HttpStatusCode, Job, JobState, JobType, ResultList } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class JobsCommand extends AbstractCommand { - - async getLatest (options: OverrideCommandOptions & { - jobType: JobType - }) { - const { data } = await this.list({ ...options, start: 0, count: 1, sort: '-createdAt' }) - - if (data.length === 0) return undefined - - return data[0] - } - - pauseJobQueue (options: OverrideCommandOptions = {}) { - const path = '/api/v1/jobs/pause' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - resumeJobQueue (options: OverrideCommandOptions = {}) { - const path = '/api/v1/jobs/resume' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions & { - state?: JobState - jobType?: JobType - start?: number - count?: number - sort?: string - } = {}) { - const path = this.buildJobsUrl(options.state) - - const query = pick(options, [ 'start', 'count', 'sort', 'jobType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listFailed (options: OverrideCommandOptions & { - jobType?: JobType - }) { - const path = this.buildJobsUrl('failed') - - return this.getRequestBody>({ - ...options, - - path, - query: { start: 0, count: 50 }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - private buildJobsUrl (state?: JobState) { - let path = '/api/v1/jobs' - - if (state) path += '/' + state - - return path - } -} diff --git a/shared/server-commands/server/jobs.ts b/shared/server-commands/server/jobs.ts deleted file mode 100644 index 8f131fba4..000000000 --- a/shared/server-commands/server/jobs.ts +++ /dev/null @@ -1,118 +0,0 @@ - -import { expect } from 'chai' -import { wait } from '@shared/core-utils' -import { JobState, JobType, RunnerJobState } from '../../models' -import { PeerTubeServer } from './server' - -async function waitJobs ( - serversArg: PeerTubeServer[] | PeerTubeServer, - options: { - skipDelayed?: boolean // default false - runnerJobs?: boolean // default false - } = {} -) { - const { skipDelayed = false, runnerJobs = false } = options - - const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT - ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) - : 250 - - let servers: PeerTubeServer[] - - if (Array.isArray(serversArg) === false) servers = [ serversArg as PeerTubeServer ] - else servers = serversArg as PeerTubeServer[] - - const states: JobState[] = [ 'waiting', 'active' ] - if (!skipDelayed) states.push('delayed') - - const repeatableJobs: JobType[] = [ 'videos-views-stats', 'activitypub-cleaner' ] - let pendingRequests: boolean - - function tasksBuilder () { - const tasks: Promise[] = [] - - // Check if each server has pending request - for (const server of servers) { - if (process.env.DEBUG) console.log('Checking ' + server.url) - - for (const state of states) { - - const jobPromise = server.jobs.list({ - state, - start: 0, - count: 10, - sort: '-createdAt' - }).then(body => body.data) - .then(jobs => jobs.filter(j => !repeatableJobs.includes(j.type))) - .then(jobs => { - if (jobs.length !== 0) { - pendingRequests = true - - if (process.env.DEBUG) { - console.log(jobs) - } - } - }) - - tasks.push(jobPromise) - } - - const debugPromise = server.debug.getDebug() - .then(obj => { - if (obj.activityPubMessagesWaiting !== 0) { - pendingRequests = true - - if (process.env.DEBUG) { - console.log('AP messages waiting: ' + obj.activityPubMessagesWaiting) - } - } - }) - tasks.push(debugPromise) - - if (runnerJobs) { - const runnerJobsPromise = server.runnerJobs.list({ count: 100 }) - .then(({ data }) => { - for (const job of data) { - if (job.state.id !== RunnerJobState.COMPLETED) { - pendingRequests = true - - if (process.env.DEBUG) { - console.log(job) - } - } - } - }) - tasks.push(runnerJobsPromise) - } - } - - return tasks - } - - do { - pendingRequests = false - await Promise.all(tasksBuilder()) - - // Retry, in case of new jobs were created - if (pendingRequests === false) { - await wait(pendingJobWait) - await Promise.all(tasksBuilder()) - } - - if (pendingRequests) { - await wait(pendingJobWait) - } - } while (pendingRequests) -} - -async function expectNoFailedTranscodingJob (server: PeerTubeServer) { - const { data } = await server.jobs.listFailed({ jobType: 'video-transcoding' }) - expect(data).to.have.lengthOf(0) -} - -// --------------------------------------------------------------------------- - -export { - waitJobs, - expectNoFailedTranscodingJob -} diff --git a/shared/server-commands/server/metrics-command.ts b/shared/server-commands/server/metrics-command.ts deleted file mode 100644 index d22b4833d..000000000 --- a/shared/server-commands/server/metrics-command.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpStatusCode, PlaybackMetricCreate } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class MetricsCommand extends AbstractCommand { - - addPlaybackMetric (options: OverrideCommandOptions & { metrics: PlaybackMetricCreate }) { - const path = '/api/v1/metrics/playback' - - return this.postBodyRequest({ - ...options, - - path, - fields: options.metrics, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/server/object-storage-command.ts b/shared/server-commands/server/object-storage-command.ts deleted file mode 100644 index 6bb232c36..000000000 --- a/shared/server-commands/server/object-storage-command.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { randomInt } from 'crypto' -import { HttpStatusCode } from '@shared/models' -import { makePostBodyRequest } from '../requests' - -export class ObjectStorageCommand { - static readonly DEFAULT_SCALEWAY_BUCKET = 'peertube-ci-test' - - private readonly bucketsCreated: string[] = [] - private readonly seed: number - - // --------------------------------------------------------------------------- - - constructor () { - this.seed = randomInt(0, 10000) - } - - static getMockCredentialsConfig () { - return { - access_key_id: 'AKIAIOSFODNN7EXAMPLE', - secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' - } - } - - static getMockEndpointHost () { - return 'localhost:9444' - } - - static getMockRegion () { - return 'us-east-1' - } - - getDefaultMockConfig () { - return { - object_storage: { - enabled: true, - endpoint: 'http://' + ObjectStorageCommand.getMockEndpointHost(), - region: ObjectStorageCommand.getMockRegion(), - - credentials: ObjectStorageCommand.getMockCredentialsConfig(), - - streaming_playlists: { - bucket_name: this.getMockStreamingPlaylistsBucketName() - }, - - web_videos: { - bucket_name: this.getMockWebVideosBucketName() - } - } - } - } - - getMockWebVideosBaseUrl () { - return `http://${this.getMockWebVideosBucketName()}.${ObjectStorageCommand.getMockEndpointHost()}/` - } - - getMockPlaylistBaseUrl () { - return `http://${this.getMockStreamingPlaylistsBucketName()}.${ObjectStorageCommand.getMockEndpointHost()}/` - } - - async prepareDefaultMockBuckets () { - await this.createMockBucket(this.getMockStreamingPlaylistsBucketName()) - await this.createMockBucket(this.getMockWebVideosBucketName()) - } - - async createMockBucket (name: string) { - this.bucketsCreated.push(name) - - await this.deleteMockBucket(name) - - await makePostBodyRequest({ - url: ObjectStorageCommand.getMockEndpointHost(), - path: '/ui/' + name + '?create', - expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307 - }) - - await makePostBodyRequest({ - url: ObjectStorageCommand.getMockEndpointHost(), - path: '/ui/' + name + '?make-public', - expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307 - }) - } - - async cleanupMock () { - for (const name of this.bucketsCreated) { - await this.deleteMockBucket(name) - } - } - - getMockStreamingPlaylistsBucketName (name = 'streaming-playlists') { - return this.getMockBucketName(name) - } - - getMockWebVideosBucketName (name = 'web-videos') { - return this.getMockBucketName(name) - } - - getMockBucketName (name: string) { - return `${this.seed}-${name}` - } - - private async deleteMockBucket (name: string) { - await makePostBodyRequest({ - url: ObjectStorageCommand.getMockEndpointHost(), - path: '/ui/' + name + '?delete', - expectedStatus: HttpStatusCode.TEMPORARY_REDIRECT_307 - }) - } - - // --------------------------------------------------------------------------- - - static getDefaultScalewayConfig (options: { - serverNumber: number - enablePrivateProxy?: boolean // default true - privateACL?: 'private' | 'public-read' // default 'private' - }) { - const { serverNumber, enablePrivateProxy = true, privateACL = 'private' } = options - - return { - object_storage: { - enabled: true, - endpoint: this.getScalewayEndpointHost(), - region: this.getScalewayRegion(), - - credentials: this.getScalewayCredentialsConfig(), - - upload_acl: { - private: privateACL - }, - - proxy: { - proxify_private_files: enablePrivateProxy - }, - - streaming_playlists: { - bucket_name: this.DEFAULT_SCALEWAY_BUCKET, - prefix: `test:server-${serverNumber}-streaming-playlists:` - }, - - web_videos: { - bucket_name: this.DEFAULT_SCALEWAY_BUCKET, - prefix: `test:server-${serverNumber}-web-videos:` - } - } - } - } - - static getScalewayCredentialsConfig () { - return { - access_key_id: process.env.OBJECT_STORAGE_SCALEWAY_KEY_ID, - secret_access_key: process.env.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY - } - } - - static getScalewayEndpointHost () { - return 's3.fr-par.scw.cloud' - } - - static getScalewayRegion () { - return 'fr-par' - } - - static getScalewayBaseUrl () { - return `https://${this.DEFAULT_SCALEWAY_BUCKET}.${this.getScalewayEndpointHost()}/` - } -} diff --git a/shared/server-commands/server/plugins-command.ts b/shared/server-commands/server/plugins-command.ts deleted file mode 100644 index bb1277a7c..000000000 --- a/shared/server-commands/server/plugins-command.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import { readJSON, writeJSON } from 'fs-extra' -import { join } from 'path' -import { root } from '@shared/core-utils' -import { - HttpStatusCode, - PeerTubePlugin, - PeerTubePluginIndex, - PeertubePluginIndexList, - PluginPackageJSON, - PluginTranslation, - PluginType, - PublicServerSetting, - RegisteredServerSettings, - ResultList -} from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class PluginsCommand extends AbstractCommand { - - static getPluginTestPath (suffix = '') { - return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix) - } - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - pluginType?: PluginType - uninstalled?: boolean - }) { - const { start, count, sort, pluginType, uninstalled } = options - const path = '/api/v1/plugins' - - return this.getRequestBody>({ - ...options, - - path, - query: { - start, - count, - sort, - pluginType, - uninstalled - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listAvailable (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - pluginType?: PluginType - currentPeerTubeEngine?: string - search?: string - expectedStatus?: HttpStatusCode - }) { - const { start, count, sort, pluginType, search, currentPeerTubeEngine } = options - const path = '/api/v1/plugins/available' - - const query: PeertubePluginIndexList = { - start, - count, - sort, - pluginType, - currentPeerTubeEngine, - search - } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - get (options: OverrideCommandOptions & { - npmName: string - }) { - const path = '/api/v1/plugins/' + options.npmName - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - updateSettings (options: OverrideCommandOptions & { - npmName: string - settings: any - }) { - const { npmName, settings } = options - const path = '/api/v1/plugins/' + npmName + '/settings' - - return this.putBodyRequest({ - ...options, - - path, - fields: { settings }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getRegisteredSettings (options: OverrideCommandOptions & { - npmName: string - }) { - const path = '/api/v1/plugins/' + options.npmName + '/registered-settings' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getPublicSettings (options: OverrideCommandOptions & { - npmName: string - }) { - const { npmName } = options - const path = '/api/v1/plugins/' + npmName + '/public-settings' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getTranslations (options: OverrideCommandOptions & { - locale: string - }) { - const { locale } = options - const path = '/plugins/translations/' + locale + '.json' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - install (options: OverrideCommandOptions & { - path?: string - npmName?: string - pluginVersion?: string - }) { - const { npmName, path, pluginVersion } = options - const apiPath = '/api/v1/plugins/install' - - return this.postBodyRequest({ - ...options, - - path: apiPath, - fields: { npmName, path, pluginVersion }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - update (options: OverrideCommandOptions & { - path?: string - npmName?: string - }) { - const { npmName, path } = options - const apiPath = '/api/v1/plugins/update' - - return this.postBodyRequest({ - ...options, - - path: apiPath, - fields: { npmName, path }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - uninstall (options: OverrideCommandOptions & { - npmName: string - }) { - const { npmName } = options - const apiPath = '/api/v1/plugins/uninstall' - - return this.postBodyRequest({ - ...options, - - path: apiPath, - fields: { npmName }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getCSS (options: OverrideCommandOptions = {}) { - const path = '/plugins/global.css' - - return this.getRequestText({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getExternalAuth (options: OverrideCommandOptions & { - npmName: string - npmVersion: string - authName: string - query?: any - }) { - const { npmName, npmVersion, authName, query } = options - - const path = '/plugins/' + npmName + '/' + npmVersion + '/auth/' + authName - - return this.getRequest({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200, - redirects: 0 - }) - } - - updatePackageJSON (npmName: string, json: any) { - const path = this.getPackageJSONPath(npmName) - - return writeJSON(path, json) - } - - getPackageJSON (npmName: string): Promise { - const path = this.getPackageJSONPath(npmName) - - return readJSON(path) - } - - private getPackageJSONPath (npmName: string) { - return this.server.servers.buildDirectory(join('plugins', 'node_modules', npmName, 'package.json')) - } -} diff --git a/shared/server-commands/server/redundancy-command.ts b/shared/server-commands/server/redundancy-command.ts deleted file mode 100644 index e7a8b3c29..000000000 --- a/shared/server-commands/server/redundancy-command.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { HttpStatusCode, ResultList, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class RedundancyCommand extends AbstractCommand { - - updateRedundancy (options: OverrideCommandOptions & { - host: string - redundancyAllowed: boolean - }) { - const { host, redundancyAllowed } = options - const path = '/api/v1/server/redundancy/' + host - - return this.putBodyRequest({ - ...options, - - path, - fields: { redundancyAllowed }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - listVideos (options: OverrideCommandOptions & { - target: VideoRedundanciesTarget - start?: number - count?: number - sort?: string - }) { - const path = '/api/v1/server/redundancy/videos' - - const { target, start, count, sort } = options - - return this.getRequestBody>({ - ...options, - - path, - - query: { - start: start ?? 0, - count: count ?? 5, - sort: sort ?? 'name', - target - }, - - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - addVideo (options: OverrideCommandOptions & { - videoId: number - }) { - const path = '/api/v1/server/redundancy/videos' - const { videoId } = options - - return this.postBodyRequest({ - ...options, - - path, - fields: { videoId }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeVideo (options: OverrideCommandOptions & { - redundancyId: number - }) { - const { redundancyId } = options - const path = '/api/v1/server/redundancy/videos/' + redundancyId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts deleted file mode 100644 index 38568a890..000000000 --- a/shared/server-commands/server/server.ts +++ /dev/null @@ -1,450 +0,0 @@ -import { ChildProcess, fork } from 'child_process' -import { copy } from 'fs-extra' -import { join } from 'path' -import { parallelTests, randomInt, root } from '@shared/core-utils' -import { Video, VideoChannel, VideoChannelSync, VideoCreateResult, VideoDetails } from '@shared/models' -import { BulkCommand } from '../bulk' -import { CLICommand } from '../cli' -import { CustomPagesCommand } from '../custom-pages' -import { FeedCommand } from '../feeds' -import { LogsCommand } from '../logs' -import { AbusesCommand } from '../moderation' -import { OverviewsCommand } from '../overviews' -import { RunnerJobsCommand, RunnerRegistrationTokensCommand, RunnersCommand } from '../runners' -import { SearchCommand } from '../search' -import { SocketIOCommand } from '../socket' -import { - AccountsCommand, - BlocklistCommand, - LoginCommand, - NotificationsCommand, - RegistrationsCommand, - SubscriptionsCommand, - TwoFactorCommand, - UsersCommand -} from '../users' -import { - BlacklistCommand, - CaptionsCommand, - ChangeOwnershipCommand, - ChannelsCommand, - ChannelSyncsCommand, - HistoryCommand, - ImportsCommand, - LiveCommand, - VideoPasswordsCommand, - PlaylistsCommand, - ServicesCommand, - StoryboardCommand, - StreamingPlaylistsCommand, - VideosCommand, - VideoStudioCommand, - VideoTokenCommand, - ViewsCommand -} from '../videos' -import { CommentsCommand } from '../videos/comments-command' -import { VideoStatsCommand } from '../videos/video-stats-command' -import { ConfigCommand } from './config-command' -import { ContactFormCommand } from './contact-form-command' -import { DebugCommand } from './debug-command' -import { FollowsCommand } from './follows-command' -import { JobsCommand } from './jobs-command' -import { MetricsCommand } from './metrics-command' -import { PluginsCommand } from './plugins-command' -import { RedundancyCommand } from './redundancy-command' -import { ServersCommand } from './servers-command' -import { StatsCommand } from './stats-command' - -export type RunServerOptions = { - hideLogs?: boolean - nodeArgs?: string[] - peertubeArgs?: string[] - env?: { [ id: string ]: string } -} - -export class PeerTubeServer { - app?: ChildProcess - - url: string - host?: string - hostname?: string - port?: number - - rtmpPort?: number - rtmpsPort?: number - - parallel?: boolean - internalServerNumber: number - - serverNumber?: number - customConfigFile?: string - - store?: { - client?: { - id?: string - secret?: string - } - - user?: { - username: string - password: string - email?: string - } - - channel?: VideoChannel - videoChannelSync?: Partial - - video?: Video - videoCreated?: VideoCreateResult - videoDetails?: VideoDetails - - videos?: { id: number, uuid: string }[] - } - - accessToken?: string - refreshToken?: string - - bulk?: BulkCommand - cli?: CLICommand - customPage?: CustomPagesCommand - feed?: FeedCommand - logs?: LogsCommand - abuses?: AbusesCommand - overviews?: OverviewsCommand - search?: SearchCommand - contactForm?: ContactFormCommand - debug?: DebugCommand - follows?: FollowsCommand - jobs?: JobsCommand - metrics?: MetricsCommand - plugins?: PluginsCommand - redundancy?: RedundancyCommand - stats?: StatsCommand - config?: ConfigCommand - socketIO?: SocketIOCommand - accounts?: AccountsCommand - blocklist?: BlocklistCommand - subscriptions?: SubscriptionsCommand - live?: LiveCommand - services?: ServicesCommand - blacklist?: BlacklistCommand - captions?: CaptionsCommand - changeOwnership?: ChangeOwnershipCommand - playlists?: PlaylistsCommand - history?: HistoryCommand - imports?: ImportsCommand - channelSyncs?: ChannelSyncsCommand - streamingPlaylists?: StreamingPlaylistsCommand - channels?: ChannelsCommand - comments?: CommentsCommand - notifications?: NotificationsCommand - servers?: ServersCommand - login?: LoginCommand - users?: UsersCommand - videoStudio?: VideoStudioCommand - videos?: VideosCommand - videoStats?: VideoStatsCommand - views?: ViewsCommand - twoFactor?: TwoFactorCommand - videoToken?: VideoTokenCommand - registrations?: RegistrationsCommand - videoPasswords?: VideoPasswordsCommand - - storyboard?: StoryboardCommand - - runners?: RunnersCommand - runnerRegistrationTokens?: RunnerRegistrationTokensCommand - runnerJobs?: RunnerJobsCommand - - constructor (options: { serverNumber: number } | { url: string }) { - if ((options as any).url) { - this.setUrl((options as any).url) - } else { - this.setServerNumber((options as any).serverNumber) - } - - this.store = { - client: { - id: null, - secret: null - }, - user: { - username: null, - password: null - } - } - - this.assignCommands() - } - - setServerNumber (serverNumber: number) { - this.serverNumber = serverNumber - - this.parallel = parallelTests() - - this.internalServerNumber = this.parallel ? this.randomServer() : this.serverNumber - this.rtmpPort = this.parallel ? this.randomRTMP() : 1936 - this.rtmpsPort = this.parallel ? this.randomRTMP() : 1937 - this.port = 9000 + this.internalServerNumber - - this.url = `http://127.0.0.1:${this.port}` - this.host = `127.0.0.1:${this.port}` - this.hostname = '127.0.0.1' - } - - setUrl (url: string) { - const parsed = new URL(url) - - this.url = url - this.host = parsed.host - this.hostname = parsed.hostname - this.port = parseInt(parsed.port) - } - - getDirectoryPath (directoryName: string) { - const testDirectory = 'test' + this.internalServerNumber - - return join(root(), testDirectory, directoryName) - } - - async flushAndRun (configOverride?: object, options: RunServerOptions = {}) { - await ServersCommand.flushTests(this.internalServerNumber) - - return this.run(configOverride, options) - } - - async run (configOverrideArg?: any, options: RunServerOptions = {}) { - // These actions are async so we need to be sure that they have both been done - const serverRunString = { - 'HTTP server listening': false - } - const key = 'Database peertube_test' + this.internalServerNumber + ' is ready' - serverRunString[key] = false - - const regexps = { - client_id: 'Client id: (.+)', - client_secret: 'Client secret: (.+)', - user_username: 'Username: (.+)', - user_password: 'User password: (.+)' - } - - await this.assignCustomConfigFile() - - const configOverride = this.buildConfigOverride() - - if (configOverrideArg !== undefined) { - Object.assign(configOverride, configOverrideArg) - } - - // Share the environment - const env = { ...process.env } - env['NODE_ENV'] = 'test' - env['NODE_APP_INSTANCE'] = this.internalServerNumber.toString() - env['NODE_CONFIG'] = JSON.stringify(configOverride) - - if (options.env) { - Object.assign(env, options.env) - } - - const execArgv = options.nodeArgs || [] - // FIXME: too slow :/ - // execArgv.push('--enable-source-maps') - - const forkOptions = { - silent: true, - env, - detached: false, - execArgv - } - - const peertubeArgs = options.peertubeArgs || [] - - return new Promise((res, rej) => { - const self = this - let aggregatedLogs = '' - - this.app = fork(join(root(), 'dist', 'server.js'), peertubeArgs, forkOptions) - - const onPeerTubeExit = () => rej(new Error('Process exited:\n' + aggregatedLogs)) - const onParentExit = () => { - if (!this.app?.pid) return - - try { - process.kill(self.app.pid) - } catch { /* empty */ } - } - - this.app.on('exit', onPeerTubeExit) - process.on('exit', onParentExit) - - this.app.stdout.on('data', function onStdout (data) { - let dontContinue = false - - const log: string = data.toString() - aggregatedLogs += log - - // Capture things if we want to - for (const key of Object.keys(regexps)) { - const regexp = regexps[key] - const matches = log.match(regexp) - if (matches !== null) { - if (key === 'client_id') self.store.client.id = matches[1] - else if (key === 'client_secret') self.store.client.secret = matches[1] - else if (key === 'user_username') self.store.user.username = matches[1] - else if (key === 'user_password') self.store.user.password = matches[1] - } - } - - // Check if all required sentences are here - for (const key of Object.keys(serverRunString)) { - if (log.includes(key)) serverRunString[key] = true - if (serverRunString[key] === false) dontContinue = true - } - - // If no, there is maybe one thing not already initialized (client/user credentials generation...) - if (dontContinue === true) return - - if (options.hideLogs === false) { - console.log(log) - } else { - process.removeListener('exit', onParentExit) - self.app.stdout.removeListener('data', onStdout) - self.app.removeListener('exit', onPeerTubeExit) - } - - res() - }) - }) - } - - kill () { - if (!this.app) return Promise.resolve() - - process.kill(this.app.pid) - - this.app = null - - return Promise.resolve() - } - - private randomServer () { - const low = 2500 - const high = 10000 - - return randomInt(low, high) - } - - private randomRTMP () { - const low = 1900 - const high = 2100 - - return randomInt(low, high) - } - - private async assignCustomConfigFile () { - if (this.internalServerNumber === this.serverNumber) return - - const basePath = join(root(), 'config') - - const tmpConfigFile = join(basePath, `test-${this.internalServerNumber}.yaml`) - await copy(join(basePath, `test-${this.serverNumber}.yaml`), tmpConfigFile) - - this.customConfigFile = tmpConfigFile - } - - private buildConfigOverride () { - if (!this.parallel) return {} - - return { - listen: { - port: this.port - }, - webserver: { - port: this.port - }, - database: { - suffix: '_test' + this.internalServerNumber - }, - storage: { - tmp: this.getDirectoryPath('tmp') + '/', - tmp_persistent: this.getDirectoryPath('tmp-persistent') + '/', - bin: this.getDirectoryPath('bin') + '/', - avatars: this.getDirectoryPath('avatars') + '/', - web_videos: this.getDirectoryPath('web-videos') + '/', - streaming_playlists: this.getDirectoryPath('streaming-playlists') + '/', - redundancy: this.getDirectoryPath('redundancy') + '/', - logs: this.getDirectoryPath('logs') + '/', - previews: this.getDirectoryPath('previews') + '/', - thumbnails: this.getDirectoryPath('thumbnails') + '/', - storyboards: this.getDirectoryPath('storyboards') + '/', - torrents: this.getDirectoryPath('torrents') + '/', - captions: this.getDirectoryPath('captions') + '/', - cache: this.getDirectoryPath('cache') + '/', - plugins: this.getDirectoryPath('plugins') + '/', - well_known: this.getDirectoryPath('well-known') + '/' - }, - admin: { - email: `admin${this.internalServerNumber}@example.com` - }, - live: { - rtmp: { - port: this.rtmpPort - } - } - } - } - - private assignCommands () { - this.bulk = new BulkCommand(this) - this.cli = new CLICommand(this) - this.customPage = new CustomPagesCommand(this) - this.feed = new FeedCommand(this) - this.logs = new LogsCommand(this) - this.abuses = new AbusesCommand(this) - this.overviews = new OverviewsCommand(this) - this.search = new SearchCommand(this) - this.contactForm = new ContactFormCommand(this) - this.debug = new DebugCommand(this) - this.follows = new FollowsCommand(this) - this.jobs = new JobsCommand(this) - this.metrics = new MetricsCommand(this) - this.plugins = new PluginsCommand(this) - this.redundancy = new RedundancyCommand(this) - this.stats = new StatsCommand(this) - this.config = new ConfigCommand(this) - this.socketIO = new SocketIOCommand(this) - this.accounts = new AccountsCommand(this) - this.blocklist = new BlocklistCommand(this) - this.subscriptions = new SubscriptionsCommand(this) - this.live = new LiveCommand(this) - this.services = new ServicesCommand(this) - this.blacklist = new BlacklistCommand(this) - this.captions = new CaptionsCommand(this) - this.changeOwnership = new ChangeOwnershipCommand(this) - this.playlists = new PlaylistsCommand(this) - this.history = new HistoryCommand(this) - this.imports = new ImportsCommand(this) - this.channelSyncs = new ChannelSyncsCommand(this) - this.streamingPlaylists = new StreamingPlaylistsCommand(this) - this.channels = new ChannelsCommand(this) - this.comments = new CommentsCommand(this) - this.notifications = new NotificationsCommand(this) - this.servers = new ServersCommand(this) - this.login = new LoginCommand(this) - this.users = new UsersCommand(this) - this.videos = new VideosCommand(this) - this.videoStudio = new VideoStudioCommand(this) - this.videoStats = new VideoStatsCommand(this) - this.views = new ViewsCommand(this) - this.twoFactor = new TwoFactorCommand(this) - this.videoToken = new VideoTokenCommand(this) - this.registrations = new RegistrationsCommand(this) - - this.storyboard = new StoryboardCommand(this) - - this.runners = new RunnersCommand(this) - this.runnerRegistrationTokens = new RunnerRegistrationTokensCommand(this) - this.runnerJobs = new RunnerJobsCommand(this) - this.videoPasswords = new VideoPasswordsCommand(this) - } -} diff --git a/shared/server-commands/server/servers-command.ts b/shared/server-commands/server/servers-command.ts deleted file mode 100644 index 54e586a18..000000000 --- a/shared/server-commands/server/servers-command.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { exec } from 'child_process' -import { copy, ensureDir, readFile, readdir, remove } from 'fs-extra' -import { basename, join } from 'path' -import { isGithubCI, root, wait } from '@shared/core-utils' -import { getFileSize } from '@shared/extra-utils' -import { HttpStatusCode } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ServersCommand extends AbstractCommand { - - static flushTests (internalServerNumber: number) { - return new Promise((res, rej) => { - const suffix = ` -- ${internalServerNumber}` - - return exec('npm run clean:server:test' + suffix, (err, _stdout, stderr) => { - if (err || stderr) return rej(err || new Error(stderr)) - - return res() - }) - }) - } - - ping (options: OverrideCommandOptions = {}) { - return this.getRequestBody({ - ...options, - - path: '/api/v1/ping', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - cleanupTests () { - const promises: Promise[] = [] - - const saveGithubLogsIfNeeded = async () => { - if (!isGithubCI()) return - - await ensureDir('artifacts') - - const origin = this.buildDirectory('logs/peertube.log') - const destname = `peertube-${this.server.internalServerNumber}.log` - console.log('Saving logs %s.', destname) - - await copy(origin, join('artifacts', destname)) - } - - if (this.server.parallel) { - const promise = saveGithubLogsIfNeeded() - .then(() => ServersCommand.flushTests(this.server.internalServerNumber)) - - promises.push(promise) - } - - if (this.server.customConfigFile) { - promises.push(remove(this.server.customConfigFile)) - } - - return promises - } - - async waitUntilLog (str: string, count = 1, strictCount = true) { - const logfile = this.buildDirectory('logs/peertube.log') - - while (true) { - const buf = await readFile(logfile) - - const matches = buf.toString().match(new RegExp(str, 'g')) - if (matches && matches.length === count) return - if (matches && strictCount === false && matches.length >= count) return - - await wait(1000) - } - } - - buildDirectory (directory: string) { - return join(root(), 'test' + this.server.internalServerNumber, directory) - } - - async countFiles (directory: string) { - const files = await readdir(this.buildDirectory(directory)) - - return files.length - } - - buildWebVideoFilePath (fileUrl: string) { - return this.buildDirectory(join('web-videos', basename(fileUrl))) - } - - buildFragmentedFilePath (videoUUID: string, fileUrl: string) { - return this.buildDirectory(join('streaming-playlists', 'hls', videoUUID, basename(fileUrl))) - } - - getLogContent () { - return readFile(this.buildDirectory('logs/peertube.log')) - } - - async getServerFileSize (subPath: string) { - const path = this.server.servers.buildDirectory(subPath) - - return getFileSize(path) - } -} diff --git a/shared/server-commands/server/servers.ts b/shared/server-commands/server/servers.ts deleted file mode 100644 index fe9da9e63..000000000 --- a/shared/server-commands/server/servers.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { ensureDir } from 'fs-extra' -import { isGithubCI } from '@shared/core-utils' -import { PeerTubeServer, RunServerOptions } from './server' - -async function createSingleServer (serverNumber: number, configOverride?: object, options: RunServerOptions = {}) { - const server = new PeerTubeServer({ serverNumber }) - - await server.flushAndRun(configOverride, options) - - return server -} - -function createMultipleServers (totalServers: number, configOverride?: object, options: RunServerOptions = {}) { - const serverPromises: Promise[] = [] - - for (let i = 1; i <= totalServers; i++) { - serverPromises.push(createSingleServer(i, configOverride, options)) - } - - return Promise.all(serverPromises) -} - -function killallServers (servers: PeerTubeServer[]) { - return Promise.all(servers.map(s => s.kill())) -} - -async function cleanupTests (servers: PeerTubeServer[]) { - await killallServers(servers) - - if (isGithubCI()) { - await ensureDir('artifacts') - } - - let p: Promise[] = [] - for (const server of servers) { - p = p.concat(server.servers.cleanupTests()) - } - - return Promise.all(p) -} - -function getServerImportConfig (mode: 'youtube-dl' | 'yt-dlp') { - return { - import: { - videos: { - http: { - youtube_dl_release: { - url: mode === 'youtube-dl' - ? 'https://yt-dl.org/downloads/latest/youtube-dl' - : 'https://api.github.com/repos/yt-dlp/yt-dlp/releases', - - name: mode - } - } - } - } - } -} - -// --------------------------------------------------------------------------- - -export { - createSingleServer, - createMultipleServers, - cleanupTests, - killallServers, - getServerImportConfig -} diff --git a/shared/server-commands/server/stats-command.ts b/shared/server-commands/server/stats-command.ts deleted file mode 100644 index 64a452306..000000000 --- a/shared/server-commands/server/stats-command.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { HttpStatusCode, ServerStats } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class StatsCommand extends AbstractCommand { - - get (options: OverrideCommandOptions & { - useCache?: boolean // default false - } = {}) { - const { useCache = false } = options - const path = '/api/v1/server/stats' - - const query = { - t: useCache ? undefined : new Date().getTime() - } - - return this.getRequestBody({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/shared/abstract-command.ts b/shared/server-commands/shared/abstract-command.ts deleted file mode 100644 index 463acc26b..000000000 --- a/shared/server-commands/shared/abstract-command.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { isAbsolute, join } from 'path' -import { root } from '@shared/core-utils' -import { - makeDeleteRequest, - makeGetRequest, - makePostBodyRequest, - makePutBodyRequest, - makeUploadRequest, - unwrapBody, - unwrapText -} from '../requests/requests' -import { PeerTubeServer } from '../server/server' - -export interface OverrideCommandOptions { - token?: string - expectedStatus?: number -} - -interface InternalCommonCommandOptions extends OverrideCommandOptions { - // Default to server.url - url?: string - - path: string - // If we automatically send the server token if the token is not provided - implicitToken: boolean - defaultExpectedStatus: number - - // Common optional request parameters - contentType?: string - accept?: string - redirects?: number - range?: string - host?: string - headers?: { [ name: string ]: string } - requestType?: string - responseType?: string - xForwardedFor?: string -} - -interface InternalGetCommandOptions extends InternalCommonCommandOptions { - query?: { [ id: string ]: any } -} - -interface InternalDeleteCommandOptions extends InternalCommonCommandOptions { - query?: { [ id: string ]: any } - rawQuery?: string -} - -abstract class AbstractCommand { - - constructor ( - protected server: PeerTubeServer - ) { - - } - - protected getRequestBody (options: InternalGetCommandOptions) { - return unwrapBody(this.getRequest(options)) - } - - protected getRequestText (options: InternalGetCommandOptions) { - return unwrapText(this.getRequest(options)) - } - - protected getRawRequest (options: Omit) { - const { url, range } = options - const { host, protocol, pathname } = new URL(url) - - return this.getRequest({ - ...options, - - token: this.buildCommonRequestToken(options), - defaultExpectedStatus: this.buildExpectedStatus(options), - - url: `${protocol}//${host}`, - path: pathname, - range - }) - } - - protected getRequest (options: InternalGetCommandOptions) { - const { query } = options - - return makeGetRequest({ - ...this.buildCommonRequestOptions(options), - - query - }) - } - - protected deleteRequest (options: InternalDeleteCommandOptions) { - const { query, rawQuery } = options - - return makeDeleteRequest({ - ...this.buildCommonRequestOptions(options), - - query, - rawQuery - }) - } - - protected putBodyRequest (options: InternalCommonCommandOptions & { - fields?: { [ fieldName: string ]: any } - headers?: { [name: string]: string } - }) { - const { fields, headers } = options - - return makePutBodyRequest({ - ...this.buildCommonRequestOptions(options), - - fields, - headers - }) - } - - protected postBodyRequest (options: InternalCommonCommandOptions & { - fields?: { [ fieldName: string ]: any } - headers?: { [name: string]: string } - }) { - const { fields, headers } = options - - return makePostBodyRequest({ - ...this.buildCommonRequestOptions(options), - - fields, - headers - }) - } - - protected postUploadRequest (options: InternalCommonCommandOptions & { - fields?: { [ fieldName: string ]: any } - attaches?: { [ fieldName: string ]: any } - }) { - const { fields, attaches } = options - - return makeUploadRequest({ - ...this.buildCommonRequestOptions(options), - - method: 'POST', - fields, - attaches - }) - } - - protected putUploadRequest (options: InternalCommonCommandOptions & { - fields?: { [ fieldName: string ]: any } - attaches?: { [ fieldName: string ]: any } - }) { - const { fields, attaches } = options - - return makeUploadRequest({ - ...this.buildCommonRequestOptions(options), - - method: 'PUT', - fields, - attaches - }) - } - - protected updateImageRequest (options: InternalCommonCommandOptions & { - fixture: string - fieldname: string - }) { - const filePath = isAbsolute(options.fixture) - ? options.fixture - : join(root(), 'server', 'tests', 'fixtures', options.fixture) - - return this.postUploadRequest({ - ...options, - - fields: {}, - attaches: { [options.fieldname]: filePath } - }) - } - - protected buildCommonRequestOptions (options: InternalCommonCommandOptions) { - const { url, path, redirects, contentType, accept, range, host, headers, requestType, xForwardedFor, responseType } = options - - return { - url: url ?? this.server.url, - path, - - token: this.buildCommonRequestToken(options), - expectedStatus: this.buildExpectedStatus(options), - - redirects, - contentType, - range, - host, - accept, - headers, - type: requestType, - responseType, - xForwardedFor - } - } - - protected buildCommonRequestToken (options: Pick) { - const { token } = options - - const fallbackToken = options.implicitToken - ? this.server.accessToken - : undefined - - return token !== undefined ? token : fallbackToken - } - - protected buildExpectedStatus (options: Pick) { - const { expectedStatus, defaultExpectedStatus } = options - - return expectedStatus !== undefined ? expectedStatus : defaultExpectedStatus - } - - protected buildVideoPasswordHeader (videoPassword: string) { - return videoPassword !== undefined && videoPassword !== null - ? { 'x-peertube-video-password': videoPassword } - : undefined - } -} - -export { - AbstractCommand -} diff --git a/shared/server-commands/shared/index.ts b/shared/server-commands/shared/index.ts deleted file mode 100644 index e807ab4f7..000000000 --- a/shared/server-commands/shared/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './abstract-command' diff --git a/shared/server-commands/socket/index.ts b/shared/server-commands/socket/index.ts deleted file mode 100644 index 594329b2f..000000000 --- a/shared/server-commands/socket/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './socket-io-command' diff --git a/shared/server-commands/socket/socket-io-command.ts b/shared/server-commands/socket/socket-io-command.ts deleted file mode 100644 index c28a86366..000000000 --- a/shared/server-commands/socket/socket-io-command.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { io } from 'socket.io-client' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class SocketIOCommand extends AbstractCommand { - - getUserNotificationSocket (options: OverrideCommandOptions = {}) { - return io(this.server.url + '/user-notifications', { - query: { accessToken: options.token ?? this.server.accessToken } - }) - } - - getLiveNotificationSocket () { - return io(this.server.url + '/live-videos') - } - - getRunnersSocket (options: { - runnerToken: string - }) { - return io(this.server.url + '/runners', { - reconnection: false, - auth: { runnerToken: options.runnerToken } - }) - } -} diff --git a/shared/server-commands/users/accounts-command.ts b/shared/server-commands/users/accounts-command.ts deleted file mode 100644 index 5844b330b..000000000 --- a/shared/server-commands/users/accounts-command.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Account, AccountVideoRate, ActorFollow, HttpStatusCode, ResultList, VideoRateType } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class AccountsCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - sort?: string // default -createdAt - } = {}) { - const { sort = '-createdAt' } = options - const path = '/api/v1/accounts' - - return this.getRequestBody>({ - ...options, - - path, - query: { sort }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - get (options: OverrideCommandOptions & { - accountName: string - }) { - const path = '/api/v1/accounts/' + options.accountName - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listRatings (options: OverrideCommandOptions & { - accountName: string - rating?: VideoRateType - }) { - const { rating, accountName } = options - const path = '/api/v1/accounts/' + accountName + '/ratings' - - const query = { rating } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listFollowers (options: OverrideCommandOptions & { - accountName: string - start?: number - count?: number - sort?: string - search?: string - }) { - const { accountName, start, count, sort, search } = options - const path = '/api/v1/accounts/' + accountName + '/followers' - - const query = { start, count, sort, search } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/users/accounts.ts b/shared/server-commands/users/accounts.ts deleted file mode 100644 index 6387891f4..000000000 --- a/shared/server-commands/users/accounts.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { PeerTubeServer } from '../server/server' - -async function setDefaultAccountAvatar (serversArg: PeerTubeServer | PeerTubeServer[], token?: string) { - const servers = Array.isArray(serversArg) - ? serversArg - : [ serversArg ] - - for (const server of servers) { - await server.users.updateMyAvatar({ fixture: 'avatar.png', token }) - } -} - -export { - setDefaultAccountAvatar -} diff --git a/shared/server-commands/users/blocklist-command.ts b/shared/server-commands/users/blocklist-command.ts deleted file mode 100644 index 862d8945e..000000000 --- a/shared/server-commands/users/blocklist-command.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import { AccountBlock, BlockStatus, HttpStatusCode, ResultList, ServerBlock } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -type ListBlocklistOptions = OverrideCommandOptions & { - start: number - count: number - - sort?: string // default -createdAt - - search?: string -} - -export class BlocklistCommand extends AbstractCommand { - - listMyAccountBlocklist (options: ListBlocklistOptions) { - const path = '/api/v1/users/me/blocklist/accounts' - - return this.listBlocklist(options, path) - } - - listMyServerBlocklist (options: ListBlocklistOptions) { - const path = '/api/v1/users/me/blocklist/servers' - - return this.listBlocklist(options, path) - } - - listServerAccountBlocklist (options: ListBlocklistOptions) { - const path = '/api/v1/server/blocklist/accounts' - - return this.listBlocklist(options, path) - } - - listServerServerBlocklist (options: ListBlocklistOptions) { - const path = '/api/v1/server/blocklist/servers' - - return this.listBlocklist(options, path) - } - - // --------------------------------------------------------------------------- - - getStatus (options: OverrideCommandOptions & { - accounts?: string[] - hosts?: string[] - }) { - const { accounts, hosts } = options - - const path = '/api/v1/blocklist/status' - - return this.getRequestBody({ - ...options, - - path, - query: { - accounts, - hosts - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - addToMyBlocklist (options: OverrideCommandOptions & { - account?: string - server?: string - }) { - const { account, server } = options - - const path = account - ? '/api/v1/users/me/blocklist/accounts' - : '/api/v1/users/me/blocklist/servers' - - return this.postBodyRequest({ - ...options, - - path, - fields: { - accountName: account, - host: server - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - addToServerBlocklist (options: OverrideCommandOptions & { - account?: string - server?: string - }) { - const { account, server } = options - - const path = account - ? '/api/v1/server/blocklist/accounts' - : '/api/v1/server/blocklist/servers' - - return this.postBodyRequest({ - ...options, - - path, - fields: { - accountName: account, - host: server - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - removeFromMyBlocklist (options: OverrideCommandOptions & { - account?: string - server?: string - }) { - const { account, server } = options - - const path = account - ? '/api/v1/users/me/blocklist/accounts/' + account - : '/api/v1/users/me/blocklist/servers/' + server - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeFromServerBlocklist (options: OverrideCommandOptions & { - account?: string - server?: string - }) { - const { account, server } = options - - const path = account - ? '/api/v1/server/blocklist/accounts/' + account - : '/api/v1/server/blocklist/servers/' + server - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - private listBlocklist (options: ListBlocklistOptions, path: string) { - const { start, count, search, sort = '-createdAt' } = options - - return this.getRequestBody>({ - ...options, - - path, - query: { start, count, sort, search }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - -} diff --git a/shared/server-commands/users/index.ts b/shared/server-commands/users/index.ts deleted file mode 100644 index 404756539..000000000 --- a/shared/server-commands/users/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './accounts-command' -export * from './accounts' -export * from './blocklist-command' -export * from './login' -export * from './login-command' -export * from './notifications-command' -export * from './registrations-command' -export * from './subscriptions-command' -export * from './two-factor-command' -export * from './users-command' diff --git a/shared/server-commands/users/login-command.ts b/shared/server-commands/users/login-command.ts deleted file mode 100644 index f2fc6d1c5..000000000 --- a/shared/server-commands/users/login-command.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { HttpStatusCode, PeerTubeProblemDocument } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -type LoginOptions = OverrideCommandOptions & { - client?: { id?: string, secret?: string } - user?: { username: string, password?: string } - otpToken?: string -} - -export class LoginCommand extends AbstractCommand { - - async login (options: LoginOptions = {}) { - const res = await this._login(options) - - return this.unwrapLoginBody(res.body) - } - - async loginAndGetResponse (options: LoginOptions = {}) { - const res = await this._login(options) - - return { - res, - body: this.unwrapLoginBody(res.body) - } - } - - getAccessToken (arg1?: { username: string, password?: string }): Promise - getAccessToken (arg1: string, password?: string): Promise - async getAccessToken (arg1?: { username: string, password?: string } | string, password?: string) { - let user: { username: string, password?: string } - - if (!arg1) user = this.server.store.user - else if (typeof arg1 === 'object') user = arg1 - else user = { username: arg1, password } - - try { - const body = await this.login({ user }) - - return body.access_token - } catch (err) { - throw new Error(`Cannot authenticate. Please check your username/password. (${err})`) - } - } - - loginUsingExternalToken (options: OverrideCommandOptions & { - username: string - externalAuthToken: string - }) { - const { username, externalAuthToken } = options - const path = '/api/v1/users/token' - - const body = { - client_id: this.server.store.client.id, - client_secret: this.server.store.client.secret, - username, - response_type: 'code', - grant_type: 'password', - scope: 'upload', - externalAuthToken - } - - return this.postBodyRequest({ - ...options, - - path, - requestType: 'form', - fields: body, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - logout (options: OverrideCommandOptions & { - token: string - }) { - const path = '/api/v1/users/revoke-token' - - return unwrapBody<{ redirectUrl: string }>(this.postBodyRequest({ - ...options, - - path, - requestType: 'form', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - refreshToken (options: OverrideCommandOptions & { - refreshToken: string - }) { - const path = '/api/v1/users/token' - - const body = { - client_id: this.server.store.client.id, - client_secret: this.server.store.client.secret, - refresh_token: options.refreshToken, - response_type: 'code', - grant_type: 'refresh_token' - } - - return this.postBodyRequest({ - ...options, - - path, - requestType: 'form', - fields: body, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getClient (options: OverrideCommandOptions = {}) { - const path = '/api/v1/oauth-clients/local' - - return this.getRequestBody<{ client_id: string, client_secret: string }>({ - ...options, - - path, - host: this.server.host, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - private _login (options: LoginOptions) { - const { client = this.server.store.client, user = this.server.store.user, otpToken } = options - const path = '/api/v1/users/token' - - const body = { - client_id: client.id, - client_secret: client.secret, - username: user.username, - password: user.password ?? 'password', - response_type: 'code', - grant_type: 'password', - scope: 'upload' - } - - const headers = otpToken - ? { 'x-peertube-otp': otpToken } - : {} - - return this.postBodyRequest({ - ...options, - - path, - headers, - requestType: 'form', - fields: body, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - private unwrapLoginBody (body: any) { - return body as { access_token: string, refresh_token: string } & PeerTubeProblemDocument - } -} diff --git a/shared/server-commands/users/login.ts b/shared/server-commands/users/login.ts deleted file mode 100644 index f1df027d3..000000000 --- a/shared/server-commands/users/login.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { PeerTubeServer } from '../server/server' - -function setAccessTokensToServers (servers: PeerTubeServer[]) { - const tasks: Promise[] = [] - - for (const server of servers) { - const p = server.login.getAccessToken() - .then(t => { server.accessToken = t }) - tasks.push(p) - } - - return Promise.all(tasks) -} - -// --------------------------------------------------------------------------- - -export { - setAccessTokensToServers -} diff --git a/shared/server-commands/users/notifications-command.ts b/shared/server-commands/users/notifications-command.ts deleted file mode 100644 index 6bd815daa..000000000 --- a/shared/server-commands/users/notifications-command.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { HttpStatusCode, ResultList, UserNotification, UserNotificationSetting } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class NotificationsCommand extends AbstractCommand { - - updateMySettings (options: OverrideCommandOptions & { - settings: UserNotificationSetting - }) { - const path = '/api/v1/users/me/notification-settings' - - return this.putBodyRequest({ - ...options, - - path, - fields: options.settings, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions & { - start?: number - count?: number - unread?: boolean - sort?: string - }) { - const { start, count, unread, sort = '-createdAt' } = options - const path = '/api/v1/users/me/notifications' - - return this.getRequestBody>({ - ...options, - - path, - query: { - start, - count, - sort, - unread - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - markAsRead (options: OverrideCommandOptions & { - ids: number[] - }) { - const { ids } = options - const path = '/api/v1/users/me/notifications/read' - - return this.postBodyRequest({ - ...options, - - path, - fields: { ids }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - markAsReadAll (options: OverrideCommandOptions) { - const path = '/api/v1/users/me/notifications/read-all' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async getLatest (options: OverrideCommandOptions = {}) { - const { total, data } = await this.list({ - ...options, - start: 0, - count: 1, - sort: '-createdAt' - }) - - if (total === 0) return undefined - - return data[0] - } -} diff --git a/shared/server-commands/users/registrations-command.ts b/shared/server-commands/users/registrations-command.ts deleted file mode 100644 index f57f54b34..000000000 --- a/shared/server-commands/users/registrations-command.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { pick } from '@shared/core-utils' -import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest, UserRegistrationUpdateState } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class RegistrationsCommand extends AbstractCommand { - - register (options: OverrideCommandOptions & Partial & Pick) { - const { password = 'password', email = options.username + '@example.com' } = options - const path = '/api/v1/users/register' - - return this.postBodyRequest({ - ...options, - - path, - fields: { - ...pick(options, [ 'username', 'displayName', 'channel' ]), - - password, - email - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - requestRegistration ( - options: OverrideCommandOptions & Partial & Pick - ) { - const { password = 'password', email = options.username + '@example.com' } = options - const path = '/api/v1/users/registrations/request' - - return unwrapBody(this.postBodyRequest({ - ...options, - - path, - fields: { - ...pick(options, [ 'username', 'displayName', 'channel', 'registrationReason' ]), - - password, - email - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - // --------------------------------------------------------------------------- - - accept (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) { - const { id } = options - const path = '/api/v1/users/registrations/' + id + '/accept' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - reject (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) { - const { id } = options - const path = '/api/v1/users/registrations/' + id + '/reject' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - delete (options: OverrideCommandOptions & { - id: number - }) { - const { id } = options - const path = '/api/v1/users/registrations/' + id - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - search?: string - } = {}) { - const path = '/api/v1/users/registrations' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort', 'search' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - askSendVerifyEmail (options: OverrideCommandOptions & { - email: string - }) { - const { email } = options - const path = '/api/v1/users/registrations/ask-send-verify-email' - - return this.postBodyRequest({ - ...options, - - path, - fields: { email }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - verifyEmail (options: OverrideCommandOptions & { - registrationId: number - verificationString: string - }) { - const { registrationId, verificationString } = options - const path = '/api/v1/users/registrations/' + registrationId + '/verify-email' - - return this.postBodyRequest({ - ...options, - - path, - fields: { - verificationString - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/users/subscriptions-command.ts b/shared/server-commands/users/subscriptions-command.ts deleted file mode 100644 index b92f037f8..000000000 --- a/shared/server-commands/users/subscriptions-command.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { HttpStatusCode, ResultList, VideoChannel } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class SubscriptionsCommand extends AbstractCommand { - - add (options: OverrideCommandOptions & { - targetUri: string - }) { - const path = '/api/v1/users/me/subscriptions' - - return this.postBodyRequest({ - ...options, - - path, - fields: { uri: options.targetUri }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions & { - sort?: string // default -createdAt - search?: string - } = {}) { - const { sort = '-createdAt', search } = options - const path = '/api/v1/users/me/subscriptions' - - return this.getRequestBody>({ - ...options, - - path, - query: { - sort, - search - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - get (options: OverrideCommandOptions & { - uri: string - }) { - const path = '/api/v1/users/me/subscriptions/' + options.uri - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - remove (options: OverrideCommandOptions & { - uri: string - }) { - const path = '/api/v1/users/me/subscriptions/' + options.uri - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - exist (options: OverrideCommandOptions & { - uris: string[] - }) { - const path = '/api/v1/users/me/subscriptions/exist' - - return this.getRequestBody<{ [id: string ]: boolean }>({ - ...options, - - path, - query: { 'uris[]': options.uris }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/users/two-factor-command.ts b/shared/server-commands/users/two-factor-command.ts deleted file mode 100644 index 5542acfda..000000000 --- a/shared/server-commands/users/two-factor-command.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { TOTP } from 'otpauth' -import { HttpStatusCode, TwoFactorEnableResult } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class TwoFactorCommand extends AbstractCommand { - - static buildOTP (options: { - secret: string - }) { - const { secret } = options - - return new TOTP({ - issuer: 'PeerTube', - algorithm: 'SHA1', - digits: 6, - period: 30, - secret - }) - } - - request (options: OverrideCommandOptions & { - userId: number - currentPassword?: string - }) { - const { currentPassword, userId } = options - - const path = '/api/v1/users/' + userId + '/two-factor/request' - - return unwrapBody(this.postBodyRequest({ - ...options, - - path, - fields: { currentPassword }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - confirmRequest (options: OverrideCommandOptions & { - userId: number - requestToken: string - otpToken: string - }) { - const { userId, requestToken, otpToken } = options - - const path = '/api/v1/users/' + userId + '/two-factor/confirm-request' - - return this.postBodyRequest({ - ...options, - - path, - fields: { requestToken, otpToken }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - disable (options: OverrideCommandOptions & { - userId: number - currentPassword?: string - }) { - const { userId, currentPassword } = options - const path = '/api/v1/users/' + userId + '/two-factor/disable' - - return this.postBodyRequest({ - ...options, - - path, - fields: { currentPassword }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async requestAndConfirm (options: OverrideCommandOptions & { - userId: number - currentPassword?: string - }) { - const { userId, currentPassword } = options - - const { otpRequest } = await this.request({ userId, currentPassword }) - - await this.confirmRequest({ - userId, - requestToken: otpRequest.requestToken, - otpToken: TwoFactorCommand.buildOTP({ secret: otpRequest.secret }).generate() - }) - - return otpRequest - } -} diff --git a/shared/server-commands/users/users-command.ts b/shared/server-commands/users/users-command.ts deleted file mode 100644 index 5b39d3488..000000000 --- a/shared/server-commands/users/users-command.ts +++ /dev/null @@ -1,388 +0,0 @@ -import { omit, pick } from '@shared/core-utils' -import { - HttpStatusCode, - MyUser, - ResultList, - ScopedToken, - User, - UserAdminFlag, - UserCreateResult, - UserRole, - UserUpdate, - UserUpdateMe, - UserVideoQuota, - UserVideoRate -} from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class UsersCommand extends AbstractCommand { - - askResetPassword (options: OverrideCommandOptions & { - email: string - }) { - const { email } = options - const path = '/api/v1/users/ask-reset-password' - - return this.postBodyRequest({ - ...options, - - path, - fields: { email }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - resetPassword (options: OverrideCommandOptions & { - userId: number - verificationString: string - password: string - }) { - const { userId, verificationString, password } = options - const path = '/api/v1/users/' + userId + '/reset-password' - - return this.postBodyRequest({ - ...options, - - path, - fields: { password, verificationString }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - askSendVerifyEmail (options: OverrideCommandOptions & { - email: string - }) { - const { email } = options - const path = '/api/v1/users/ask-send-verify-email' - - return this.postBodyRequest({ - ...options, - - path, - fields: { email }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - verifyEmail (options: OverrideCommandOptions & { - userId: number - verificationString: string - isPendingEmail?: boolean // default false - }) { - const { userId, verificationString, isPendingEmail = false } = options - const path = '/api/v1/users/' + userId + '/verify-email' - - return this.postBodyRequest({ - ...options, - - path, - fields: { - verificationString, - isPendingEmail - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - banUser (options: OverrideCommandOptions & { - userId: number - reason?: string - }) { - const { userId, reason } = options - const path = '/api/v1/users' + '/' + userId + '/block' - - return this.postBodyRequest({ - ...options, - - path, - fields: { reason }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - unbanUser (options: OverrideCommandOptions & { - userId: number - }) { - const { userId } = options - const path = '/api/v1/users' + '/' + userId + '/unblock' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - getMyScopedTokens (options: OverrideCommandOptions = {}) { - const path = '/api/v1/users/scoped-tokens' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - renewMyScopedTokens (options: OverrideCommandOptions = {}) { - const path = '/api/v1/users/scoped-tokens' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - create (options: OverrideCommandOptions & { - username: string - password?: string - videoQuota?: number - videoQuotaDaily?: number - role?: UserRole - adminFlags?: UserAdminFlag - }) { - const { - username, - adminFlags, - password = 'password', - videoQuota, - videoQuotaDaily, - role = UserRole.USER - } = options - - const path = '/api/v1/users' - - return unwrapBody<{ user: UserCreateResult }>(this.postBodyRequest({ - ...options, - - path, - fields: { - username, - password, - role, - adminFlags, - email: username + '@example.com', - videoQuota, - videoQuotaDaily - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })).then(res => res.user) - } - - async generate (username: string, role?: UserRole) { - const password = 'password' - const user = await this.create({ username, password, role }) - - const token = await this.server.login.getAccessToken({ username, password }) - - const me = await this.getMyInfo({ token }) - - return { - token, - userId: user.id, - userChannelId: me.videoChannels[0].id, - userChannelName: me.videoChannels[0].name, - password - } - } - - async generateUserAndToken (username: string, role?: UserRole) { - const password = 'password' - await this.create({ username, password, role }) - - return this.server.login.getAccessToken({ username, password }) - } - - // --------------------------------------------------------------------------- - - getMyInfo (options: OverrideCommandOptions = {}) { - const path = '/api/v1/users/me' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getMyQuotaUsed (options: OverrideCommandOptions = {}) { - const path = '/api/v1/users/me/video-quota-used' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getMyRating (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { videoId } = options - const path = '/api/v1/users/me/videos/' + videoId + '/rating' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - deleteMe (options: OverrideCommandOptions = {}) { - const path = '/api/v1/users/me' - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - updateMe (options: OverrideCommandOptions & UserUpdateMe) { - const path = '/api/v1/users/me' - - const toSend: UserUpdateMe = omit(options, [ 'expectedStatus', 'token' ]) - - return this.putBodyRequest({ - ...options, - - path, - fields: toSend, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - updateMyAvatar (options: OverrideCommandOptions & { - fixture: string - }) { - const { fixture } = options - const path = '/api/v1/users/me/avatar/pick' - - return this.updateImageRequest({ - ...options, - - path, - fixture, - fieldname: 'avatarfile', - - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - get (options: OverrideCommandOptions & { - userId: number - withStats?: boolean // default false - }) { - const { userId, withStats } = options - const path = '/api/v1/users/' + userId - - return this.getRequestBody({ - ...options, - - path, - query: { withStats }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - search?: string - blocked?: boolean - } = {}) { - const path = '/api/v1/users' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort', 'search', 'blocked' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - remove (options: OverrideCommandOptions & { - userId: number - }) { - const { userId } = options - const path = '/api/v1/users/' + userId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - update (options: OverrideCommandOptions & { - userId: number - email?: string - emailVerified?: boolean - videoQuota?: number - videoQuotaDaily?: number - password?: string - adminFlags?: UserAdminFlag - pluginAuth?: string - role?: UserRole - }) { - const path = '/api/v1/users/' + options.userId - - const toSend: UserUpdate = {} - if (options.password !== undefined && options.password !== null) toSend.password = options.password - if (options.email !== undefined && options.email !== null) toSend.email = options.email - if (options.emailVerified !== undefined && options.emailVerified !== null) toSend.emailVerified = options.emailVerified - if (options.videoQuota !== undefined && options.videoQuota !== null) toSend.videoQuota = options.videoQuota - if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend.videoQuotaDaily = options.videoQuotaDaily - if (options.role !== undefined && options.role !== null) toSend.role = options.role - if (options.adminFlags !== undefined && options.adminFlags !== null) toSend.adminFlags = options.adminFlags - if (options.pluginAuth !== undefined) toSend.pluginAuth = options.pluginAuth - - return this.putBodyRequest({ - ...options, - - path, - fields: toSend, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/blacklist-command.ts b/shared/server-commands/videos/blacklist-command.ts deleted file mode 100644 index 47e23ebc8..000000000 --- a/shared/server-commands/videos/blacklist-command.ts +++ /dev/null @@ -1,75 +0,0 @@ - -import { HttpStatusCode, ResultList, VideoBlacklist, VideoBlacklistType } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class BlacklistCommand extends AbstractCommand { - - add (options: OverrideCommandOptions & { - videoId: number | string - reason?: string - unfederate?: boolean - }) { - const { videoId, reason, unfederate } = options - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return this.postBodyRequest({ - ...options, - - path, - fields: { reason, unfederate }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - update (options: OverrideCommandOptions & { - videoId: number | string - reason?: string - }) { - const { videoId, reason } = options - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return this.putBodyRequest({ - ...options, - - path, - fields: { reason }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - remove (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { videoId } = options - const path = '/api/v1/videos/' + videoId + '/blacklist' - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions & { - sort?: string - type?: VideoBlacklistType - } = {}) { - const { sort, type } = options - const path = '/api/v1/videos/blacklist/' - - const query = { sort, type } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/captions-command.ts b/shared/server-commands/videos/captions-command.ts deleted file mode 100644 index a26fcb57d..000000000 --- a/shared/server-commands/videos/captions-command.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { buildAbsoluteFixturePath } from '@shared/core-utils' -import { HttpStatusCode, ResultList, VideoCaption } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class CaptionsCommand extends AbstractCommand { - - add (options: OverrideCommandOptions & { - videoId: string | number - language: string - fixture: string - mimeType?: string - }) { - const { videoId, language, fixture, mimeType } = options - - const path = '/api/v1/videos/' + videoId + '/captions/' + language - - const captionfile = buildAbsoluteFixturePath(fixture) - const captionfileAttach = mimeType - ? [ captionfile, { contentType: mimeType } ] - : captionfile - - return this.putUploadRequest({ - ...options, - - path, - fields: {}, - attaches: { - captionfile: captionfileAttach - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions & { - videoId: string | number - videoPassword?: string - }) { - const { videoId, videoPassword } = options - const path = '/api/v1/videos/' + videoId + '/captions' - - return this.getRequestBody>({ - ...options, - - path, - headers: this.buildVideoPasswordHeader(videoPassword), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - delete (options: OverrideCommandOptions & { - videoId: string | number - language: string - }) { - const { videoId, language } = options - const path = '/api/v1/videos/' + videoId + '/captions/' + language - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/change-ownership-command.ts b/shared/server-commands/videos/change-ownership-command.ts deleted file mode 100644 index ad4c726ef..000000000 --- a/shared/server-commands/videos/change-ownership-command.ts +++ /dev/null @@ -1,68 +0,0 @@ - -import { HttpStatusCode, ResultList, VideoChangeOwnership } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ChangeOwnershipCommand extends AbstractCommand { - - create (options: OverrideCommandOptions & { - videoId: number | string - username: string - }) { - const { videoId, username } = options - const path = '/api/v1/videos/' + videoId + '/give-ownership' - - return this.postBodyRequest({ - ...options, - - path, - fields: { username }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - list (options: OverrideCommandOptions = {}) { - const path = '/api/v1/videos/ownership' - - return this.getRequestBody>({ - ...options, - - path, - query: { sort: '-createdAt' }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - accept (options: OverrideCommandOptions & { - ownershipId: number - channelId: number - }) { - const { ownershipId, channelId } = options - const path = '/api/v1/videos/ownership/' + ownershipId + '/accept' - - return this.postBodyRequest({ - ...options, - - path, - fields: { channelId }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - refuse (options: OverrideCommandOptions & { - ownershipId: number - }) { - const { ownershipId } = options - const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/channel-syncs-command.ts b/shared/server-commands/videos/channel-syncs-command.ts deleted file mode 100644 index de4a160ec..000000000 --- a/shared/server-commands/videos/channel-syncs-command.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { HttpStatusCode, ResultList, VideoChannelSync, VideoChannelSyncCreate } from '@shared/models' -import { pick } from '@shared/core-utils' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ChannelSyncsCommand extends AbstractCommand { - private static readonly API_PATH = '/api/v1/video-channel-syncs' - - listByAccount (options: OverrideCommandOptions & { - accountName: string - start?: number - count?: number - sort?: string - }) { - const { accountName, sort = 'createdAt' } = options - - const path = `/api/v1/accounts/${accountName}/video-channel-syncs` - - return this.getRequestBody>({ - ...options, - - path, - query: { sort, ...pick(options, [ 'start', 'count' ]) }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async create (options: OverrideCommandOptions & { - attributes: VideoChannelSyncCreate - }) { - return unwrapBody<{ videoChannelSync: VideoChannelSync }>(this.postBodyRequest({ - ...options, - - path: ChannelSyncsCommand.API_PATH, - fields: options.attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - delete (options: OverrideCommandOptions & { - channelSyncId: number - }) { - const path = `${ChannelSyncsCommand.API_PATH}/${options.channelSyncId}` - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/channels-command.ts b/shared/server-commands/videos/channels-command.ts deleted file mode 100644 index 385d0fe73..000000000 --- a/shared/server-commands/videos/channels-command.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { pick } from '@shared/core-utils' -import { - ActorFollow, - HttpStatusCode, - ResultList, - VideoChannel, - VideoChannelCreate, - VideoChannelCreateResult, - VideoChannelUpdate, - VideosImportInChannelCreate -} from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ChannelsCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - withStats?: boolean - } = {}) { - const path = '/api/v1/video-channels' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort', 'withStats' ]), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - 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' - - return this.getRequestBody>({ - ...options, - - path, - query: { sort, ...pick(options, [ 'start', 'count', 'withStats', 'search' ]) }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async create (options: OverrideCommandOptions & { - attributes: Partial - }) { - const path = '/api/v1/video-channels/' - - // Default attributes - const defaultAttributes = { - displayName: 'my super video channel', - description: 'my super channel description', - support: 'my super channel support' - } - const attributes = { ...defaultAttributes, ...options.attributes } - - const body = await unwrapBody<{ videoChannel: VideoChannelCreateResult }>(this.postBodyRequest({ - ...options, - - path, - fields: attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return body.videoChannel - } - - update (options: OverrideCommandOptions & { - channelName: string - attributes: VideoChannelUpdate - }) { - const { channelName, attributes } = options - const path = '/api/v1/video-channels/' + channelName - - return this.putBodyRequest({ - ...options, - - path, - fields: attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - delete (options: OverrideCommandOptions & { - channelName: string - }) { - const path = '/api/v1/video-channels/' + options.channelName - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - get (options: OverrideCommandOptions & { - channelName: string - }) { - const path = '/api/v1/video-channels/' + options.channelName - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - 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` - - return this.updateImageRequest({ - ...options, - - path, - fixture, - fieldname: type + 'file', - - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - deleteImage (options: OverrideCommandOptions & { - channelName: string | number - type: 'avatar' | 'banner' - }) { - const { channelName, type } = options - - const path = `/api/v1/video-channels/${channelName}/${type}` - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - 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' - - const query = { start, count, sort, search } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - importVideos (options: OverrideCommandOptions & VideosImportInChannelCreate & { - channelName: string - }) { - const { channelName, externalChannelUrl, videoChannelSyncId } = options - - const path = `/api/v1/video-channels/${channelName}/import-videos` - - return this.postBodyRequest({ - ...options, - - path, - fields: { externalChannelUrl, videoChannelSyncId }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/channels.ts b/shared/server-commands/videos/channels.ts deleted file mode 100644 index 3c0d4b723..000000000 --- a/shared/server-commands/videos/channels.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PeerTubeServer } from '../server/server' - -function setDefaultVideoChannel (servers: PeerTubeServer[]) { - const tasks: Promise[] = [] - - for (const server of servers) { - const p = server.users.getMyInfo() - .then(user => { server.store.channel = user.videoChannels[0] }) - - tasks.push(p) - } - - return Promise.all(tasks) -} - -async function setDefaultChannelAvatar (serversArg: PeerTubeServer | PeerTubeServer[], channelName: string = 'root_channel') { - const servers = Array.isArray(serversArg) - ? serversArg - : [ serversArg ] - - for (const server of servers) { - await server.channels.updateImage({ channelName, fixture: 'avatar.png', type: 'avatar' }) - } -} - -export { - setDefaultVideoChannel, - setDefaultChannelAvatar -} diff --git a/shared/server-commands/videos/comments-command.ts b/shared/server-commands/videos/comments-command.ts deleted file mode 100644 index 0dab1b66a..000000000 --- a/shared/server-commands/videos/comments-command.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { pick } from '@shared/core-utils' -import { HttpStatusCode, ResultList, VideoComment, VideoCommentThreads, VideoCommentThreadTree } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class CommentsCommand extends AbstractCommand { - - private lastVideoId: number | string - private lastThreadId: number - private lastReplyId: number - - listForAdmin (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - isLocal?: boolean - onLocalVideo?: boolean - search?: string - searchAccount?: string - searchVideo?: string - } = {}) { - const { sort = '-createdAt' } = options - const path = '/api/v1/videos/comments' - - const query = { sort, ...pick(options, [ 'start', 'count', 'isLocal', 'onLocalVideo', 'search', 'searchAccount', 'searchVideo' ]) } - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listThreads (options: OverrideCommandOptions & { - videoId: number | string - videoPassword?: string - start?: number - count?: number - sort?: string - }) { - const { start, count, sort, videoId, videoPassword } = options - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - return this.getRequestBody({ - ...options, - - path, - query: { start, count, sort }, - headers: this.buildVideoPasswordHeader(videoPassword), - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getThread (options: OverrideCommandOptions & { - videoId: number | string - threadId: number - }) { - const { videoId, threadId } = options - const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async createThread (options: OverrideCommandOptions & { - videoId: number | string - text: string - videoPassword?: string - }) { - const { videoId, text, videoPassword } = options - const path = '/api/v1/videos/' + videoId + '/comment-threads' - - const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ - ...options, - - path, - fields: { text }, - headers: this.buildVideoPasswordHeader(videoPassword), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - this.lastThreadId = body.comment?.id - this.lastVideoId = videoId - - return body.comment - } - - async addReply (options: OverrideCommandOptions & { - videoId: number | string - toCommentId: number - text: string - videoPassword?: string - }) { - const { videoId, toCommentId, text, videoPassword } = options - const path = '/api/v1/videos/' + videoId + '/comments/' + toCommentId - - const body = await unwrapBody<{ comment: VideoComment }>(this.postBodyRequest({ - ...options, - - path, - fields: { text }, - headers: this.buildVideoPasswordHeader(videoPassword), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - this.lastReplyId = body.comment?.id - - return body.comment - } - - async addReplyToLastReply (options: OverrideCommandOptions & { - text: string - }) { - return this.addReply({ ...options, videoId: this.lastVideoId, toCommentId: this.lastReplyId }) - } - - async addReplyToLastThread (options: OverrideCommandOptions & { - text: string - }) { - return this.addReply({ ...options, videoId: this.lastVideoId, toCommentId: this.lastThreadId }) - } - - async findCommentId (options: OverrideCommandOptions & { - videoId: number | string - text: string - }) { - const { videoId, text } = options - const { data } = await this.listThreads({ videoId, count: 25, sort: '-createdAt' }) - - return data.find(c => c.text === text).id - } - - delete (options: OverrideCommandOptions & { - videoId: number | string - commentId: number - }) { - const { videoId, commentId } = options - const path = '/api/v1/videos/' + videoId + '/comments/' + commentId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/history-command.ts b/shared/server-commands/videos/history-command.ts deleted file mode 100644 index d27afcff2..000000000 --- a/shared/server-commands/videos/history-command.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { HttpStatusCode, ResultList, Video } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class HistoryCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - search?: string - } = {}) { - const { search } = options - const path = '/api/v1/users/me/history/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: { - search - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - removeElement (options: OverrideCommandOptions & { - videoId: number - }) { - const { videoId } = options - const path = '/api/v1/users/me/history/videos/' + videoId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeAll (options: OverrideCommandOptions & { - beforeDate?: string - } = {}) { - const { beforeDate } = options - const path = '/api/v1/users/me/history/videos/remove' - - return this.postBodyRequest({ - ...options, - - path, - fields: { beforeDate }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/imports-command.ts b/shared/server-commands/videos/imports-command.ts deleted file mode 100644 index e307a79be..000000000 --- a/shared/server-commands/videos/imports-command.ts +++ /dev/null @@ -1,77 +0,0 @@ - -import { HttpStatusCode, ResultList } from '@shared/models' -import { VideoImport, VideoImportCreate } from '../../models/videos' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ImportsCommand extends AbstractCommand { - - importVideo (options: OverrideCommandOptions & { - attributes: (VideoImportCreate | { torrentfile?: string, previewfile?: string, thumbnailfile?: string }) - }) { - const { attributes } = options - const path = '/api/v1/videos/imports' - - let attaches: any = {} - if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile } - if (attributes.thumbnailfile) attaches = { thumbnailfile: attributes.thumbnailfile } - if (attributes.previewfile) attaches = { previewfile: attributes.previewfile } - - return unwrapBody(this.postUploadRequest({ - ...options, - - path, - attaches, - fields: options.attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - delete (options: OverrideCommandOptions & { - importId: number - }) { - const path = '/api/v1/videos/imports/' + options.importId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - cancel (options: OverrideCommandOptions & { - importId: number - }) { - const path = '/api/v1/videos/imports/' + options.importId + '/cancel' - - return this.postBodyRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getMyVideoImports (options: OverrideCommandOptions & { - sort?: string - targetUrl?: string - videoChannelSyncId?: number - search?: string - } = {}) { - const { sort, targetUrl, videoChannelSyncId, search } = options - const path = '/api/v1/users/me/videos/imports' - - return this.getRequestBody>({ - ...options, - - path, - query: { sort, targetUrl, videoChannelSyncId, search }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/index.ts b/shared/server-commands/videos/index.ts deleted file mode 100644 index 106d80af0..000000000 --- a/shared/server-commands/videos/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export * from './blacklist-command' -export * from './captions-command' -export * from './change-ownership-command' -export * from './channels' -export * from './channels-command' -export * from './channel-syncs-command' -export * from './comments-command' -export * from './history-command' -export * from './imports-command' -export * from './live-command' -export * from './live' -export * from './playlists-command' -export * from './services-command' -export * from './storyboard-command' -export * from './streaming-playlists-command' -export * from './comments-command' -export * from './video-studio-command' -export * from './video-token-command' -export * from './views-command' -export * from './videos-command' -export * from './video-passwords-command' diff --git a/shared/server-commands/videos/live-command.ts b/shared/server-commands/videos/live-command.ts deleted file mode 100644 index 6006d9fe9..000000000 --- a/shared/server-commands/videos/live-command.ts +++ /dev/null @@ -1,337 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ - -import { readdir } from 'fs-extra' -import { join } from 'path' -import { omit, wait } from '@shared/core-utils' -import { - HttpStatusCode, - LiveVideo, - LiveVideoCreate, - LiveVideoSession, - LiveVideoUpdate, - ResultList, - VideoCreateResult, - VideoDetails, - VideoPrivacy, - VideoState -} from '@shared/models' -import { unwrapBody } from '../requests' -import { ObjectStorageCommand, PeerTubeServer } from '../server' -import { AbstractCommand, OverrideCommandOptions } from '../shared' -import { sendRTMPStream, testFfmpegStreamError } from './live' - -export class LiveCommand extends AbstractCommand { - - get (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = '/api/v1/videos/live' - - return this.getRequestBody({ - ...options, - - path: path + '/' + options.videoId, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - listSessions (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = `/api/v1/videos/live/${options.videoId}/sessions` - - return this.getRequestBody>({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async findLatestSession (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { data: sessions } = await this.listSessions(options) - - return sessions[sessions.length - 1] - } - - getReplaySession (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = `/api/v1/videos/${options.videoId}/live-session` - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - update (options: OverrideCommandOptions & { - videoId: number | string - fields: LiveVideoUpdate - }) { - const { videoId, fields } = options - const path = '/api/v1/videos/live' - - return this.putBodyRequest({ - ...options, - - path: path + '/' + videoId, - fields, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async create (options: OverrideCommandOptions & { - fields: LiveVideoCreate - }) { - const { fields } = options - const path = '/api/v1/videos/live' - - const attaches: any = {} - if (fields.thumbnailfile) attaches.thumbnailfile = fields.thumbnailfile - if (fields.previewfile) attaches.previewfile = fields.previewfile - - const body = await unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({ - ...options, - - path, - attaches, - fields: omit(fields, [ 'thumbnailfile', 'previewfile' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return body.video - } - - async quickCreate (options: OverrideCommandOptions & { - saveReplay: boolean - permanentLive: boolean - privacy?: VideoPrivacy - videoPasswords?: string[] - }) { - const { saveReplay, permanentLive, privacy = VideoPrivacy.PUBLIC, videoPasswords } = options - - const replaySettings = privacy === VideoPrivacy.PASSWORD_PROTECTED - ? { privacy: VideoPrivacy.PRIVATE } - : { privacy } - - const { uuid } = await this.create({ - ...options, - - fields: { - name: 'live', - permanentLive, - saveReplay, - replaySettings, - channelId: this.server.store.channel.id, - privacy, - videoPasswords - } - }) - - const video = await this.server.videos.getWithToken({ id: uuid }) - const live = await this.get({ videoId: uuid }) - - return { video, live } - } - - // --------------------------------------------------------------------------- - - async sendRTMPStreamInVideo (options: OverrideCommandOptions & { - videoId: number | string - fixtureName?: string - copyCodecs?: boolean - }) { - const { videoId, fixtureName, copyCodecs } = options - const videoLive = await this.get({ videoId }) - - return sendRTMPStream({ rtmpBaseUrl: videoLive.rtmpUrl, streamKey: videoLive.streamKey, fixtureName, copyCodecs }) - } - - async runAndTestStreamError (options: OverrideCommandOptions & { - videoId: number | string - shouldHaveError: boolean - }) { - const command = await this.sendRTMPStreamInVideo(options) - - return testFfmpegStreamError(command, options.shouldHaveError) - } - - // --------------------------------------------------------------------------- - - waitUntilPublished (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { videoId } = options - return this.waitUntilState({ videoId, state: VideoState.PUBLISHED }) - } - - waitUntilWaiting (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { videoId } = options - return this.waitUntilState({ videoId, state: VideoState.WAITING_FOR_LIVE }) - } - - waitUntilEnded (options: OverrideCommandOptions & { - videoId: number | string - }) { - const { videoId } = options - return this.waitUntilState({ videoId, state: VideoState.LIVE_ENDED }) - } - - async waitUntilSegmentGeneration (options: OverrideCommandOptions & { - server: PeerTubeServer - videoUUID: string - playlistNumber: number - segment: number - objectStorage?: ObjectStorageCommand - objectStorageBaseUrl?: string - }) { - const { - server, - objectStorage, - playlistNumber, - segment, - videoUUID, - objectStorageBaseUrl - } = options - - const segmentName = `${playlistNumber}-00000${segment}.ts` - const baseUrl = objectStorage - ? join(objectStorageBaseUrl || objectStorage.getMockPlaylistBaseUrl(), 'hls') - : server.url + '/static/streaming-playlists/hls' - - let error = true - - while (error) { - try { - // Check fragment exists - await this.getRawRequest({ - ...options, - - url: `${baseUrl}/${videoUUID}/${segmentName}`, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - - const video = await server.videos.get({ id: videoUUID }) - const hlsPlaylist = video.streamingPlaylists[0] - - // Check SHA generation - const shaBody = await server.streamingPlaylists.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url, withRetry: !!objectStorage }) - if (!shaBody[segmentName]) { - throw new Error('Segment SHA does not exist') - } - - // Check fragment is in m3u8 playlist - const subPlaylist = await server.streamingPlaylists.get({ url: `${baseUrl}/${video.uuid}/${playlistNumber}.m3u8` }) - if (!subPlaylist.includes(segmentName)) throw new Error('Fragment does not exist in playlist') - - error = false - } catch { - error = true - await wait(100) - } - } - } - - async waitUntilReplacedByReplay (options: OverrideCommandOptions & { - videoId: number | string - }) { - let video: VideoDetails - - do { - video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) - - await wait(500) - } while (video.isLive === true || video.state.id !== VideoState.PUBLISHED) - } - - // --------------------------------------------------------------------------- - - getSegmentFile (options: OverrideCommandOptions & { - videoUUID: string - playlistNumber: number - segment: number - objectStorage?: ObjectStorageCommand - }) { - const { playlistNumber, segment, videoUUID, objectStorage } = options - - const segmentName = `${playlistNumber}-00000${segment}.ts` - const baseUrl = objectStorage - ? objectStorage.getMockPlaylistBaseUrl() - : `${this.server.url}/static/streaming-playlists/hls` - - const url = `${baseUrl}/${videoUUID}/${segmentName}` - - return this.getRawRequest({ - ...options, - - url, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getPlaylistFile (options: OverrideCommandOptions & { - videoUUID: string - playlistName: string - objectStorage?: ObjectStorageCommand - }) { - const { playlistName, videoUUID, objectStorage } = options - - const baseUrl = objectStorage - ? objectStorage.getMockPlaylistBaseUrl() - : `${this.server.url}/static/streaming-playlists/hls` - - const url = `${baseUrl}/${videoUUID}/${playlistName}` - - return this.getRawRequest({ - ...options, - - url, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - async countPlaylists (options: OverrideCommandOptions & { - videoUUID: string - }) { - const basePath = this.server.servers.buildDirectory('streaming-playlists') - const hlsPath = join(basePath, 'hls', options.videoUUID) - - const files = await readdir(hlsPath) - - return files.filter(f => f.endsWith('.m3u8')).length - } - - private async waitUntilState (options: OverrideCommandOptions & { - videoId: number | string - state: VideoState - }) { - let video: VideoDetails - - do { - video = await this.server.videos.getWithToken({ token: options.token, id: options.videoId }) - - await wait(500) - } while (video.state.id !== options.state) - } -} diff --git a/shared/server-commands/videos/live.ts b/shared/server-commands/videos/live.ts deleted file mode 100644 index cebadb1db..000000000 --- a/shared/server-commands/videos/live.ts +++ /dev/null @@ -1,128 +0,0 @@ -import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' -import { truncate } from 'lodash' -import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' -import { VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models' -import { PeerTubeServer } from '../server/server' - -function sendRTMPStream (options: { - rtmpBaseUrl: string - streamKey: string - fixtureName?: string // default video_short.mp4 - copyCodecs?: boolean // default false -}) { - const { rtmpBaseUrl, streamKey, fixtureName = 'video_short.mp4', copyCodecs = false } = options - - const fixture = buildAbsoluteFixturePath(fixtureName) - - const command = ffmpeg(fixture) - command.inputOption('-stream_loop -1') - command.inputOption('-re') - - if (copyCodecs) { - command.outputOption('-c copy') - } else { - command.outputOption('-c:v libx264') - command.outputOption('-g 120') - command.outputOption('-x264-params "no-scenecut=1"') - command.outputOption('-r 60') - } - - command.outputOption('-f flv') - - const rtmpUrl = rtmpBaseUrl + '/' + streamKey - command.output(rtmpUrl) - - command.on('error', err => { - if (err?.message?.includes('Exiting normally')) return - - if (process.env.DEBUG) console.error(err) - }) - - if (process.env.DEBUG) { - command.on('stderr', data => console.log(data)) - command.on('stdout', data => console.log(data)) - } - - command.run() - - return command -} - -function waitFfmpegUntilError (command: FfmpegCommand, successAfterMS = 10000) { - return new Promise((res, rej) => { - command.on('error', err => { - return rej(err) - }) - - setTimeout(() => { - res() - }, successAfterMS) - }) -} - -async function testFfmpegStreamError (command: FfmpegCommand, shouldHaveError: boolean) { - let error: Error - - try { - await waitFfmpegUntilError(command, 45000) - } catch (err) { - error = err - } - - await stopFfmpeg(command) - - if (shouldHaveError && !error) throw new Error('Ffmpeg did not have an error') - if (!shouldHaveError && error) throw error -} - -async function stopFfmpeg (command: FfmpegCommand) { - command.kill('SIGINT') - - await wait(500) -} - -async function waitUntilLivePublishedOnAllServers (servers: PeerTubeServer[], videoId: string) { - for (const server of servers) { - await server.live.waitUntilPublished({ videoId }) - } -} - -async function waitUntilLiveWaitingOnAllServers (servers: PeerTubeServer[], videoId: string) { - for (const server of servers) { - await server.live.waitUntilWaiting({ videoId }) - } -} - -async function waitUntilLiveReplacedByReplayOnAllServers (servers: PeerTubeServer[], videoId: string) { - for (const server of servers) { - await server.live.waitUntilReplacedByReplay({ videoId }) - } -} - -async function findExternalSavedVideo (server: PeerTubeServer, liveDetails: VideoDetails) { - const include = VideoInclude.BLACKLISTED - const privacyOneOf = [ VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.PUBLIC, VideoPrivacy.UNLISTED ] - - const { data } = await server.videos.list({ token: server.accessToken, sort: '-publishedAt', include, privacyOneOf }) - - const videoNameSuffix = ` - ${new Date(liveDetails.publishedAt).toLocaleString()}` - const truncatedVideoName = truncate(liveDetails.name, { - length: 120 - videoNameSuffix.length - }) - const toFind = truncatedVideoName + videoNameSuffix - - return data.find(v => v.name === toFind) -} - -export { - sendRTMPStream, - waitFfmpegUntilError, - testFfmpegStreamError, - stopFfmpeg, - - waitUntilLivePublishedOnAllServers, - waitUntilLiveReplacedByReplayOnAllServers, - waitUntilLiveWaitingOnAllServers, - - findExternalSavedVideo -} diff --git a/shared/server-commands/videos/playlists-command.ts b/shared/server-commands/videos/playlists-command.ts deleted file mode 100644 index da3bef7b0..000000000 --- a/shared/server-commands/videos/playlists-command.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { omit, pick } from '@shared/core-utils' -import { - BooleanBothQuery, - HttpStatusCode, - ResultList, - VideoExistInPlaylist, - VideoPlaylist, - VideoPlaylistCreate, - VideoPlaylistCreateResult, - VideoPlaylistElement, - VideoPlaylistElementCreate, - VideoPlaylistElementCreateResult, - VideoPlaylistElementUpdate, - VideoPlaylistReorder, - VideoPlaylistType, - VideoPlaylistUpdate -} from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class PlaylistsCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - playlistType?: VideoPlaylistType - }) { - const path = '/api/v1/video-playlists' - const query = pick(options, [ 'start', 'count', 'sort', 'playlistType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listByChannel (options: OverrideCommandOptions & { - handle: string - start?: number - count?: number - sort?: string - playlistType?: VideoPlaylistType - }) { - const path = '/api/v1/video-channels/' + options.handle + '/video-playlists' - const query = pick(options, [ 'start', 'count', 'sort', 'playlistType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listByAccount (options: OverrideCommandOptions & { - handle: string - start?: number - count?: number - sort?: string - search?: string - playlistType?: VideoPlaylistType - }) { - const path = '/api/v1/accounts/' + options.handle + '/video-playlists' - const query = pick(options, [ 'start', 'count', 'sort', 'search', 'playlistType' ]) - - return this.getRequestBody>({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - get (options: OverrideCommandOptions & { - playlistId: number | string - }) { - const { playlistId } = options - const path = '/api/v1/video-playlists/' + playlistId - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listVideos (options: OverrideCommandOptions & { - playlistId: number | string - start?: number - count?: number - query?: { nsfw?: BooleanBothQuery } - }) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' - const query = options.query ?? {} - - return this.getRequestBody>({ - ...options, - - path, - query: { - ...query, - start: options.start, - count: options.count - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - delete (options: OverrideCommandOptions & { - playlistId: number | string - }) { - const path = '/api/v1/video-playlists/' + options.playlistId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async create (options: OverrideCommandOptions & { - attributes: VideoPlaylistCreate - }) { - const path = '/api/v1/video-playlists' - - const fields = omit(options.attributes, [ 'thumbnailfile' ]) - - const attaches = options.attributes.thumbnailfile - ? { thumbnailfile: options.attributes.thumbnailfile } - : {} - - const body = await unwrapBody<{ videoPlaylist: VideoPlaylistCreateResult }>(this.postUploadRequest({ - ...options, - - path, - fields, - attaches, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return body.videoPlaylist - } - - update (options: OverrideCommandOptions & { - attributes: VideoPlaylistUpdate - playlistId: number | string - }) { - const path = '/api/v1/video-playlists/' + options.playlistId - - const fields = omit(options.attributes, [ 'thumbnailfile' ]) - - const attaches = options.attributes.thumbnailfile - ? { thumbnailfile: options.attributes.thumbnailfile } - : {} - - return this.putUploadRequest({ - ...options, - - path, - fields, - attaches, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async addElement (options: OverrideCommandOptions & { - playlistId: number | string - attributes: VideoPlaylistElementCreate | { videoId: string } - }) { - const attributes = { - ...options.attributes, - - videoId: await this.server.videos.getId({ ...options, uuid: options.attributes.videoId }) - } - - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos' - - const body = await unwrapBody<{ videoPlaylistElement: VideoPlaylistElementCreateResult }>(this.postBodyRequest({ - ...options, - - path, - fields: attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return body.videoPlaylistElement - } - - updateElement (options: OverrideCommandOptions & { - playlistId: number | string - elementId: number | string - attributes: VideoPlaylistElementUpdate - }) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.elementId - - return this.putBodyRequest({ - ...options, - - path, - fields: options.attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeElement (options: OverrideCommandOptions & { - playlistId: number | string - elementId: number - }) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.elementId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - reorderElements (options: OverrideCommandOptions & { - playlistId: number | string - attributes: VideoPlaylistReorder - }) { - const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' - - return this.postBodyRequest({ - ...options, - - path, - fields: options.attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - getPrivacies (options: OverrideCommandOptions = {}) { - const path = '/api/v1/video-playlists/privacies' - - return this.getRequestBody<{ [ id: number ]: string }>({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - videosExist (options: OverrideCommandOptions & { - videoIds: number[] - }) { - const { videoIds } = options - const path = '/api/v1/users/me/video-playlists/videos-exist' - - return this.getRequestBody({ - ...options, - - path, - query: { videoIds }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/services-command.ts b/shared/server-commands/videos/services-command.ts deleted file mode 100644 index 06760df42..000000000 --- a/shared/server-commands/videos/services-command.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { HttpStatusCode } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ServicesCommand extends AbstractCommand { - - getOEmbed (options: OverrideCommandOptions & { - oembedUrl: string - format?: string - maxHeight?: number - maxWidth?: number - }) { - const path = '/services/oembed' - const query = { - url: options.oembedUrl, - format: options.format, - maxheight: options.maxHeight, - maxwidth: options.maxWidth - } - - return this.getRequest({ - ...options, - - path, - query, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/storyboard-command.ts b/shared/server-commands/videos/storyboard-command.ts deleted file mode 100644 index 06d90fc12..000000000 --- a/shared/server-commands/videos/storyboard-command.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HttpStatusCode, Storyboard } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class StoryboardCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - id: number | string - }) { - const path = '/api/v1/videos/' + options.id + '/storyboards' - - return this.getRequestBody<{ storyboards: Storyboard[] }>({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/streaming-playlists-command.ts b/shared/server-commands/videos/streaming-playlists-command.ts deleted file mode 100644 index b988ac4b2..000000000 --- a/shared/server-commands/videos/streaming-playlists-command.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { wait } from '@shared/core-utils' -import { HttpStatusCode } from '@shared/models' -import { unwrapBody, unwrapBodyOrDecodeToJSON, unwrapTextOrDecode } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class StreamingPlaylistsCommand extends AbstractCommand { - - async get (options: OverrideCommandOptions & { - url: string - - videoFileToken?: string - reinjectVideoFileToken?: boolean - - withRetry?: boolean // default false - currentRetry?: number - }): Promise { - const { videoFileToken, reinjectVideoFileToken, expectedStatus, withRetry = false, currentRetry = 1 } = options - - try { - const result = await unwrapTextOrDecode(this.getRawRequest({ - ...options, - - url: options.url, - query: { - videoFileToken, - reinjectVideoFileToken - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - // master.m3u8 could be empty - if (!result && (!expectedStatus || expectedStatus === HttpStatusCode.OK_200)) { - throw new Error('Empty result') - } - - return result - } catch (err) { - if (!withRetry || currentRetry > 10) throw err - - await wait(250) - - return this.get({ - ...options, - - withRetry, - currentRetry: currentRetry + 1 - }) - } - } - - async getFragmentedSegment (options: OverrideCommandOptions & { - url: string - range?: string - - withRetry?: boolean // default false - currentRetry?: number - }) { - const { withRetry = false, currentRetry = 1 } = options - - try { - const result = await unwrapBody(this.getRawRequest({ - ...options, - - url: options.url, - range: options.range, - implicitToken: false, - responseType: 'application/octet-stream', - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return result - } catch (err) { - if (!withRetry || currentRetry > 10) throw err - - await wait(250) - - return this.getFragmentedSegment({ - ...options, - - withRetry, - currentRetry: currentRetry + 1 - }) - } - } - - async getSegmentSha256 (options: OverrideCommandOptions & { - url: string - - withRetry?: boolean // default false - currentRetry?: number - }) { - const { withRetry = false, currentRetry = 1 } = options - - try { - const result = await unwrapBodyOrDecodeToJSON<{ [ id: string ]: string }>(this.getRawRequest({ - ...options, - - url: options.url, - contentType: 'application/json', - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - - return result - } catch (err) { - if (!withRetry || currentRetry > 10) throw err - - await wait(250) - - return this.getSegmentSha256({ - ...options, - - withRetry, - currentRetry: currentRetry + 1 - }) - } - } -} diff --git a/shared/server-commands/videos/video-passwords-command.ts b/shared/server-commands/videos/video-passwords-command.ts deleted file mode 100644 index bf10335b4..000000000 --- a/shared/server-commands/videos/video-passwords-command.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { HttpStatusCode, ResultList, VideoPassword } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' -export class VideoPasswordsCommand extends AbstractCommand { - - list (options: OverrideCommandOptions & { - videoId: number | string - start?: number - count?: number - sort?: string - }) { - const { start, count, sort, videoId } = options - const path = '/api/v1/videos/' + videoId + '/passwords' - - return this.getRequestBody>({ - ...options, - - path, - query: { start, count, sort }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - updateAll (options: OverrideCommandOptions & { - videoId: number | string - passwords: string[] - }) { - const { videoId, passwords } = options - const path = `/api/v1/videos/${videoId}/passwords` - - return this.putBodyRequest({ - ...options, - path, - fields: { passwords }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - remove (options: OverrideCommandOptions & { - id: number - videoId: number | string - }) { - const { id, videoId } = options - const path = `/api/v1/videos/${videoId}/passwords/${id}` - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/video-stats-command.ts b/shared/server-commands/videos/video-stats-command.ts deleted file mode 100644 index b9b99bfb5..000000000 --- a/shared/server-commands/videos/video-stats-command.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { pick } from '@shared/core-utils' -import { HttpStatusCode, VideoStatsOverall, VideoStatsRetention, VideoStatsTimeserie, VideoStatsTimeserieMetric } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class VideoStatsCommand extends AbstractCommand { - - getOverallStats (options: OverrideCommandOptions & { - videoId: number | string - startDate?: string - endDate?: string - }) { - const path = '/api/v1/videos/' + options.videoId + '/stats/overall' - - return this.getRequestBody({ - ...options, - path, - - query: pick(options, [ 'startDate', 'endDate' ]), - - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getTimeserieStats (options: OverrideCommandOptions & { - videoId: number | string - metric: VideoStatsTimeserieMetric - startDate?: Date - endDate?: Date - }) { - const path = '/api/v1/videos/' + options.videoId + '/stats/timeseries/' + options.metric - - return this.getRequestBody({ - ...options, - path, - - query: pick(options, [ 'startDate', 'endDate' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getRetentionStats (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = '/api/v1/videos/' + options.videoId + '/stats/retention' - - return this.getRequestBody({ - ...options, - path, - - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } -} diff --git a/shared/server-commands/videos/video-studio-command.ts b/shared/server-commands/videos/video-studio-command.ts deleted file mode 100644 index 675cd84b7..000000000 --- a/shared/server-commands/videos/video-studio-command.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { HttpStatusCode, VideoStudioTask } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class VideoStudioCommand extends AbstractCommand { - - static getComplexTask (): VideoStudioTask[] { - return [ - // Total duration: 2 - { - name: 'cut', - options: { - start: 1, - end: 3 - } - }, - - // Total duration: 7 - { - name: 'add-outro', - options: { - file: 'video_short.webm' - } - }, - - { - name: 'add-watermark', - options: { - file: 'custom-thumbnail.png' - } - }, - - // Total duration: 9 - { - name: 'add-intro', - options: { - file: 'video_very_short_240p.mp4' - } - } - ] - } - - createEditionTasks (options: OverrideCommandOptions & { - videoId: number | string - tasks: VideoStudioTask[] - }) { - const path = '/api/v1/videos/' + options.videoId + '/studio/edit' - const attaches: { [id: string]: any } = {} - - for (let i = 0; i < options.tasks.length; i++) { - const task = options.tasks[i] - - if (task.name === 'add-intro' || task.name === 'add-outro' || task.name === 'add-watermark') { - attaches[`tasks[${i}][options][file]`] = task.options.file - } - } - - return this.postUploadRequest({ - ...options, - - path, - attaches, - fields: { tasks: options.tasks }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } -} diff --git a/shared/server-commands/videos/video-token-command.ts b/shared/server-commands/videos/video-token-command.ts deleted file mode 100644 index c4ed29a8c..000000000 --- a/shared/server-commands/videos/video-token-command.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ - -import { HttpStatusCode, VideoToken } from '@shared/models' -import { unwrapBody } from '../requests' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class VideoTokenCommand extends AbstractCommand { - - create (options: OverrideCommandOptions & { - videoId: number | string - videoPassword?: string - }) { - const { videoId, videoPassword } = options - const path = '/api/v1/videos/' + videoId + '/token' - - return unwrapBody(this.postBodyRequest({ - ...options, - headers: this.buildVideoPasswordHeader(videoPassword), - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - async getVideoFileToken (options: OverrideCommandOptions & { - videoId: number | string - videoPassword?: string - }) { - const { files } = await this.create(options) - - return files.token - } -} diff --git a/shared/server-commands/videos/videos-command.ts b/shared/server-commands/videos/videos-command.ts deleted file mode 100644 index 4c3513ed4..000000000 --- a/shared/server-commands/videos/videos-command.ts +++ /dev/null @@ -1,829 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ - -import { expect } from 'chai' -import { createReadStream, stat } from 'fs-extra' -import got, { Response as GotResponse } from 'got' -import validator from 'validator' -import { buildAbsoluteFixturePath, getAllPrivacies, omit, pick, wait } from '@shared/core-utils' -import { buildUUID } from '@shared/extra-utils' -import { - HttpStatusCode, - ResultList, - UserVideoRateType, - Video, - VideoCreate, - VideoCreateResult, - VideoDetails, - VideoFileMetadata, - VideoInclude, - VideoPrivacy, - VideosCommonQuery, - VideoTranscodingCreate -} from '@shared/models' -import { VideoSource } from '@shared/models/videos/video-source' -import { unwrapBody } from '../requests' -import { waitJobs } from '../server' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export type VideoEdit = Partial> & { - fixture?: string - thumbnailfile?: string - previewfile?: string -} - -export class VideosCommand extends AbstractCommand { - - getCategories (options: OverrideCommandOptions = {}) { - const path = '/api/v1/videos/categories' - - return this.getRequestBody<{ [id: number]: string }>({ - ...options, - path, - - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getLicences (options: OverrideCommandOptions = {}) { - const path = '/api/v1/videos/licences' - - return this.getRequestBody<{ [id: number]: string }>({ - ...options, - path, - - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getLanguages (options: OverrideCommandOptions = {}) { - const path = '/api/v1/videos/languages' - - return this.getRequestBody<{ [id: string]: string }>({ - ...options, - path, - - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getPrivacies (options: OverrideCommandOptions = {}) { - const path = '/api/v1/videos/privacies' - - return this.getRequestBody<{ [id in VideoPrivacy]: string }>({ - ...options, - path, - - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - getDescription (options: OverrideCommandOptions & { - descriptionPath: string - }) { - return this.getRequestBody<{ description: string }>({ - ...options, - path: options.descriptionPath, - - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getFileMetadata (options: OverrideCommandOptions & { - url: string - }) { - return unwrapBody(this.getRawRequest({ - ...options, - - url: options.url, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - })) - } - - // --------------------------------------------------------------------------- - - rate (options: OverrideCommandOptions & { - id: number | string - rating: UserVideoRateType - videoPassword?: string - }) { - const { id, rating, videoPassword } = options - const path = '/api/v1/videos/' + id + '/rate' - - return this.putBodyRequest({ - ...options, - - path, - fields: { rating }, - headers: this.buildVideoPasswordHeader(videoPassword), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - get (options: OverrideCommandOptions & { - id: number | string - }) { - const path = '/api/v1/videos/' + options.id - - return this.getRequestBody({ - ...options, - - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getWithToken (options: OverrideCommandOptions & { - id: number | string - }) { - return this.get({ - ...options, - - token: this.buildCommonRequestToken({ ...options, implicitToken: true }) - }) - } - - getWithPassword (options: OverrideCommandOptions & { - id: number | string - password?: string - }) { - const path = '/api/v1/videos/' + options.id - - return this.getRequestBody({ - ...options, - headers:{ - 'x-peertube-video-password': options.password - }, - path, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - getSource (options: OverrideCommandOptions & { - id: number | string - }) { - const path = '/api/v1/videos/' + options.id + '/source' - - return this.getRequestBody({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - async getId (options: OverrideCommandOptions & { - uuid: number | string - }) { - const { uuid } = options - - if (validator.isUUID('' + uuid) === false) return uuid as number - - const { id } = await this.get({ ...options, id: uuid }) - - return id - } - - async listFiles (options: OverrideCommandOptions & { - id: number | string - }) { - const video = await this.get(options) - - const files = video.files || [] - const hlsFiles = video.streamingPlaylists[0]?.files || [] - - return files.concat(hlsFiles) - } - - // --------------------------------------------------------------------------- - - listMyVideos (options: OverrideCommandOptions & { - start?: number - count?: number - sort?: string - search?: string - isLive?: boolean - channelId?: number - } = {}) { - const path = '/api/v1/users/me/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: pick(options, [ 'start', 'count', 'sort', 'search', 'isLive', 'channelId' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listMySubscriptionVideos (options: OverrideCommandOptions & VideosCommonQuery = {}) { - const { sort = '-createdAt' } = options - const path = '/api/v1/users/me/subscriptions/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: { sort, ...this.buildListQuery(options) }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - list (options: OverrideCommandOptions & VideosCommonQuery = {}) { - const path = '/api/v1/videos' - - const query = this.buildListQuery(options) - - return this.getRequestBody>({ - ...options, - - path, - query: { sort: 'name', ...query }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listWithToken (options: OverrideCommandOptions & VideosCommonQuery = {}) { - return this.list({ - ...options, - - token: this.buildCommonRequestToken({ ...options, implicitToken: true }) - }) - } - - listAllForAdmin (options: OverrideCommandOptions & VideosCommonQuery = {}) { - const include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER - const nsfw = 'both' - const privacyOneOf = getAllPrivacies() - - return this.list({ - ...options, - - include, - nsfw, - privacyOneOf, - - token: this.buildCommonRequestToken({ ...options, implicitToken: true }) - }) - } - - listByAccount (options: OverrideCommandOptions & VideosCommonQuery & { - handle: string - }) { - const { handle, search } = options - const path = '/api/v1/accounts/' + handle + '/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: { search, ...this.buildListQuery(options) }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - listByChannel (options: OverrideCommandOptions & VideosCommonQuery & { - handle: string - }) { - const { handle } = options - const path = '/api/v1/video-channels/' + handle + '/videos' - - return this.getRequestBody>({ - ...options, - - path, - query: this.buildListQuery(options), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - }) - } - - // --------------------------------------------------------------------------- - - async find (options: OverrideCommandOptions & { - name: string - }) { - const { data } = await this.list(options) - - return data.find(v => v.name === options.name) - } - - // --------------------------------------------------------------------------- - - update (options: OverrideCommandOptions & { - id: number | string - attributes?: VideoEdit - }) { - const { id, attributes = {} } = options - const path = '/api/v1/videos/' + id - - // Upload request - if (attributes.thumbnailfile || attributes.previewfile) { - const attaches: any = {} - if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile - if (attributes.previewfile) attaches.previewfile = attributes.previewfile - - return this.putUploadRequest({ - ...options, - - path, - fields: options.attributes, - attaches: { - thumbnailfile: attributes.thumbnailfile, - previewfile: attributes.previewfile - }, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - return this.putBodyRequest({ - ...options, - - path, - fields: options.attributes, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - remove (options: OverrideCommandOptions & { - id: number | string - }) { - const path = '/api/v1/videos/' + options.id - - return unwrapBody(this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - })) - } - - async removeAll () { - const { data } = await this.list() - - for (const v of data) { - await this.remove({ id: v.id }) - } - } - - // --------------------------------------------------------------------------- - - async upload (options: OverrideCommandOptions & { - attributes?: VideoEdit - mode?: 'legacy' | 'resumable' // default legacy - waitTorrentGeneration?: boolean // default true - completedExpectedStatus?: HttpStatusCode - } = {}) { - const { mode = 'legacy', waitTorrentGeneration = true } = options - let defaultChannelId = 1 - - try { - const { videoChannels } = await this.server.users.getMyInfo({ token: options.token }) - defaultChannelId = videoChannels[0].id - } catch (e) { /* empty */ } - - // Override default attributes - const attributes = { - name: 'my super video', - category: 5, - licence: 4, - language: 'zh', - channelId: defaultChannelId, - nsfw: true, - waitTranscoding: false, - description: 'my super description', - support: 'my super support text', - tags: [ 'tag' ], - privacy: VideoPrivacy.PUBLIC, - commentsEnabled: true, - downloadEnabled: true, - fixture: 'video_short.webm', - - ...options.attributes - } - - const created = mode === 'legacy' - ? await this.buildLegacyUpload({ ...options, attributes }) - : await this.buildResumeUpload({ ...options, path: '/api/v1/videos/upload-resumable', attributes }) - - // Wait torrent generation - const expectedStatus = this.buildExpectedStatus({ ...options, defaultExpectedStatus: HttpStatusCode.OK_200 }) - if (expectedStatus === HttpStatusCode.OK_200 && waitTorrentGeneration) { - let video: VideoDetails - - do { - video = await this.getWithToken({ ...options, id: created.uuid }) - - await wait(50) - } while (!video.files[0].torrentUrl) - } - - return created - } - - async buildLegacyUpload (options: OverrideCommandOptions & { - attributes: VideoEdit - }): Promise { - const path = '/api/v1/videos/upload' - - return unwrapBody<{ video: VideoCreateResult }>(this.postUploadRequest({ - ...options, - - path, - fields: this.buildUploadFields(options.attributes), - attaches: this.buildUploadAttaches(options.attributes), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.OK_200 - })).then(body => body.video || body as any) - } - - async buildResumeUpload (options: OverrideCommandOptions & { - path: string - attributes: { fixture?: string } & { [id: string]: any } - completedExpectedStatus?: HttpStatusCode // When the upload is finished - }): Promise { - const { path, attributes, expectedStatus = HttpStatusCode.OK_200, completedExpectedStatus } = options - - let size = 0 - let videoFilePath: string - let mimetype = 'video/mp4' - - if (attributes.fixture) { - videoFilePath = buildAbsoluteFixturePath(attributes.fixture) - size = (await stat(videoFilePath)).size - - if (videoFilePath.endsWith('.mkv')) { - mimetype = 'video/x-matroska' - } else if (videoFilePath.endsWith('.webm')) { - mimetype = 'video/webm' - } - } - - // Do not check status automatically, we'll check it manually - const initializeSessionRes = await this.prepareResumableUpload({ - ...options, - - path, - expectedStatus: null, - attributes, - size, - mimetype - }) - const initStatus = initializeSessionRes.status - - if (videoFilePath && initStatus === HttpStatusCode.CREATED_201) { - const locationHeader = initializeSessionRes.header['location'] - expect(locationHeader).to.not.be.undefined - - const pathUploadId = locationHeader.split('?')[1] - - const result = await this.sendResumableChunks({ - ...options, - - path, - pathUploadId, - videoFilePath, - size, - expectedStatus: completedExpectedStatus - }) - - if (result.statusCode === HttpStatusCode.OK_200) { - await this.endResumableUpload({ - ...options, - - expectedStatus: HttpStatusCode.NO_CONTENT_204, - path, - pathUploadId - }) - } - - return result.body?.video || result.body as any - } - - const expectedInitStatus = expectedStatus === HttpStatusCode.OK_200 - ? HttpStatusCode.CREATED_201 - : expectedStatus - - expect(initStatus).to.equal(expectedInitStatus) - - return initializeSessionRes.body.video || initializeSessionRes.body - } - - async prepareResumableUpload (options: OverrideCommandOptions & { - path: string - attributes: { fixture?: string } & { [id: string]: any } - size: number - mimetype: string - - originalName?: string - lastModified?: number - }) { - const { path, attributes, originalName, lastModified, size, mimetype } = options - - const attaches = this.buildUploadAttaches(omit(options.attributes, [ 'fixture' ])) - - const uploadOptions = { - ...options, - - path, - headers: { - 'X-Upload-Content-Type': mimetype, - 'X-Upload-Content-Length': size.toString() - }, - fields: { - filename: attributes.fixture, - originalName, - lastModified, - - ...this.buildUploadFields(options.attributes) - }, - - // Fixture will be sent later - attaches: this.buildUploadAttaches(omit(options.attributes, [ 'fixture' ])), - implicitToken: true, - - defaultExpectedStatus: null - } - - if (Object.keys(attaches).length === 0) return this.postBodyRequest(uploadOptions) - - return this.postUploadRequest(uploadOptions) - } - - sendResumableChunks (options: OverrideCommandOptions & { - pathUploadId: string - path: string - videoFilePath: string - size: number - contentLength?: number - contentRangeBuilder?: (start: number, chunk: any) => string - digestBuilder?: (chunk: any) => string - }) { - const { - path, - pathUploadId, - videoFilePath, - size, - contentLength, - contentRangeBuilder, - digestBuilder, - expectedStatus = HttpStatusCode.OK_200 - } = options - - let start = 0 - - const token = this.buildCommonRequestToken({ ...options, implicitToken: true }) - const url = this.server.url - - const readable = createReadStream(videoFilePath, { highWaterMark: 8 * 1024 }) - return new Promise>((resolve, reject) => { - readable.on('data', async function onData (chunk) { - try { - readable.pause() - - const byterangeStart = start + chunk.length - 1 - - const headers = { - 'Authorization': 'Bearer ' + token, - 'Content-Type': 'application/octet-stream', - 'Content-Range': contentRangeBuilder - ? contentRangeBuilder(start, chunk) - : `bytes ${start}-${byterangeStart}/${size}`, - 'Content-Length': contentLength ? contentLength + '' : chunk.length + '' - } - - if (digestBuilder) { - Object.assign(headers, { digest: digestBuilder(chunk) }) - } - - const res = await got<{ video: VideoCreateResult }>({ - url, - method: 'put', - headers, - path: path + '?' + pathUploadId, - body: chunk, - responseType: 'json', - throwHttpErrors: false - }) - - start += chunk.length - - // Last request, check final status - if (byterangeStart + 1 === size) { - if (res.statusCode === expectedStatus) { - return resolve(res) - } - - if (res.statusCode !== HttpStatusCode.PERMANENT_REDIRECT_308) { - readable.off('data', onData) - - // eslint-disable-next-line max-len - const message = `Incorrect transient behaviour sending intermediary chunks. Status code is ${res.statusCode} instead of ${expectedStatus}` - return reject(new Error(message)) - } - } - - readable.resume() - } catch (err) { - reject(err) - } - }) - }) - } - - endResumableUpload (options: OverrideCommandOptions & { - path: string - pathUploadId: string - }) { - return this.deleteRequest({ - ...options, - - path: options.path, - rawQuery: options.pathUploadId, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - quickUpload (options: OverrideCommandOptions & { - name: string - nsfw?: boolean - privacy?: VideoPrivacy - fixture?: string - videoPasswords?: string[] - }) { - const attributes: VideoEdit = { name: options.name } - if (options.nsfw) attributes.nsfw = options.nsfw - if (options.privacy) attributes.privacy = options.privacy - if (options.fixture) attributes.fixture = options.fixture - if (options.videoPasswords) attributes.videoPasswords = options.videoPasswords - - return this.upload({ ...options, attributes }) - } - - async randomUpload (options: OverrideCommandOptions & { - wait?: boolean // default true - additionalParams?: VideoEdit & { prefixName?: string } - } = {}) { - const { wait = true, additionalParams } = options - const prefixName = additionalParams?.prefixName || '' - const name = prefixName + buildUUID() - - const attributes = { name, ...additionalParams } - - const result = await this.upload({ ...options, attributes }) - - if (wait) await waitJobs([ this.server ]) - - return { ...result, name } - } - - // --------------------------------------------------------------------------- - - replaceSourceFile (options: OverrideCommandOptions & { - videoId: number | string - fixture: string - completedExpectedStatus?: HttpStatusCode - }) { - return this.buildResumeUpload({ - ...options, - - path: '/api/v1/videos/' + options.videoId + '/source/replace-resumable', - attributes: { fixture: options.fixture } - }) - } - - // --------------------------------------------------------------------------- - - removeHLSPlaylist (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = '/api/v1/videos/' + options.videoId + '/hls' - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeHLSFile (options: OverrideCommandOptions & { - videoId: number | string - fileId: number - }) { - const path = '/api/v1/videos/' + options.videoId + '/hls/' + options.fileId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeAllWebVideoFiles (options: OverrideCommandOptions & { - videoId: number | string - }) { - const path = '/api/v1/videos/' + options.videoId + '/web-videos' - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - removeWebVideoFile (options: OverrideCommandOptions & { - videoId: number | string - fileId: number - }) { - const path = '/api/v1/videos/' + options.videoId + '/web-videos/' + options.fileId - - return this.deleteRequest({ - ...options, - - path, - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - runTranscoding (options: OverrideCommandOptions & VideoTranscodingCreate & { - videoId: number | string - }) { - const path = '/api/v1/videos/' + options.videoId + '/transcoding' - - return this.postBodyRequest({ - ...options, - - path, - fields: pick(options, [ 'transcodingType', 'forceTranscoding' ]), - implicitToken: true, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - // --------------------------------------------------------------------------- - - private buildListQuery (options: VideosCommonQuery) { - return pick(options, [ - 'start', - 'count', - 'sort', - 'nsfw', - 'isLive', - 'categoryOneOf', - 'licenceOneOf', - 'languageOneOf', - 'privacyOneOf', - 'tagsOneOf', - 'tagsAllOf', - 'isLocal', - 'include', - 'skipCount' - ]) - } - - private buildUploadFields (attributes: VideoEdit) { - return omit(attributes, [ 'fixture', 'thumbnailfile', 'previewfile' ]) - } - - private buildUploadAttaches (attributes: VideoEdit) { - const attaches: { [ name: string ]: string } = {} - - for (const key of [ 'thumbnailfile', 'previewfile' ]) { - if (attributes[key]) attaches[key] = buildAbsoluteFixturePath(attributes[key]) - } - - if (attributes.fixture) attaches.videofile = buildAbsoluteFixturePath(attributes.fixture) - - return attaches - } -} diff --git a/shared/server-commands/videos/views-command.ts b/shared/server-commands/videos/views-command.ts deleted file mode 100644 index bdb8daaa4..000000000 --- a/shared/server-commands/videos/views-command.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */ -import { HttpStatusCode, VideoViewEvent } from '@shared/models' -import { AbstractCommand, OverrideCommandOptions } from '../shared' - -export class ViewsCommand extends AbstractCommand { - - view (options: OverrideCommandOptions & { - id: number | string - currentTime: number - viewEvent?: VideoViewEvent - xForwardedFor?: string - }) { - const { id, xForwardedFor, viewEvent, currentTime } = options - const path = '/api/v1/videos/' + id + '/views' - - return this.postBodyRequest({ - ...options, - - path, - xForwardedFor, - fields: { - currentTime, - viewEvent - }, - implicitToken: false, - defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204 - }) - } - - async simulateView (options: OverrideCommandOptions & { - id: number | string - xForwardedFor?: string - }) { - await this.view({ ...options, currentTime: 0 }) - await this.view({ ...options, currentTime: 5 }) - } - - async simulateViewer (options: OverrideCommandOptions & { - id: number | string - currentTimes: number[] - xForwardedFor?: string - }) { - let viewEvent: VideoViewEvent = 'seek' - - for (const currentTime of options.currentTimes) { - await this.view({ ...options, currentTime, viewEvent }) - - viewEvent = undefined - } - } -} -- cgit v1.2.3