mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2026-09-03 20:53:09 -05:00
Add ability to list ownership changes of a video
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { CommonModule, NgClass } from '@angular/common'
|
||||
import { Component, inject, viewChild } from '@angular/core'
|
||||
import { Notifier } from '@app/core'
|
||||
import { AuthService, Notifier } from '@app/core'
|
||||
import { Account } from '@app/shared/shared-main/account/account.model'
|
||||
import { PTDatePipe } from '@app/shared/shared-main/common/date.pipe'
|
||||
import { VideoOwnershipService } from '@app/shared/shared-main/video/video-ownership.service'
|
||||
@@ -30,6 +30,7 @@ import { MyAcceptOwnershipComponent } from './my-accept-ownership/my-accept-owne
|
||||
export class MyOwnershipComponent {
|
||||
private notifier = inject(Notifier)
|
||||
private videoOwnershipService = inject(VideoOwnershipService)
|
||||
private authService = inject(AuthService)
|
||||
|
||||
readonly myAccountAcceptOwnershipComponent = viewChild<MyAcceptOwnershipComponent>('myAcceptOwnershipComponent')
|
||||
readonly table = viewChild<TableComponent<VideoChangeOwnership>>('table')
|
||||
@@ -74,6 +75,10 @@ export class MyOwnershipComponent {
|
||||
})
|
||||
}
|
||||
|
||||
isReceiver (videoChangeOwnership: VideoChangeOwnership) {
|
||||
return videoChangeOwnership.nextOwnerAccount.id === this.authService.getUser().account.id
|
||||
}
|
||||
|
||||
private _dataLoader (options: DataLoaderOptionsBase) {
|
||||
return this.videoOwnershipService.getOwnershipChanges(options.pagination, options.sort)
|
||||
.pipe(
|
||||
|
||||
@@ -2,11 +2,12 @@ import { HttpStatusCode, ResultList, VideoChangeOwnership } from '@peertube/peer
|
||||
import { AbstractCommand, OverrideCommandOptions } from '../shared/index.js'
|
||||
|
||||
export class ChangeOwnershipCommand extends AbstractCommand {
|
||||
|
||||
create (options: OverrideCommandOptions & {
|
||||
videoId: number | string
|
||||
username: string
|
||||
}) {
|
||||
create (
|
||||
options: OverrideCommandOptions & {
|
||||
videoId: number | string
|
||||
username: string
|
||||
}
|
||||
) {
|
||||
const { videoId, username } = options
|
||||
const path = '/api/v1/videos/' + videoId + '/give-ownership'
|
||||
|
||||
@@ -33,10 +34,31 @@ export class ChangeOwnershipCommand extends AbstractCommand {
|
||||
})
|
||||
}
|
||||
|
||||
accept (options: OverrideCommandOptions & {
|
||||
ownershipId: number
|
||||
channelId: number
|
||||
}) {
|
||||
listOfVideo (
|
||||
options: OverrideCommandOptions & {
|
||||
videoId: number | string
|
||||
state?: string
|
||||
}
|
||||
) {
|
||||
const { videoId, state } = options
|
||||
const path = '/api/v1/videos/' + videoId + '/ownership'
|
||||
|
||||
return this.getRequestBody<ResultList<VideoChangeOwnership>>({
|
||||
...options,
|
||||
|
||||
path,
|
||||
query: { state, 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'
|
||||
|
||||
@@ -50,9 +72,11 @@ export class ChangeOwnershipCommand extends AbstractCommand {
|
||||
})
|
||||
}
|
||||
|
||||
refuse (options: OverrideCommandOptions & {
|
||||
ownershipId: number
|
||||
}) {
|
||||
refuse (
|
||||
options: OverrideCommandOptions & {
|
||||
ownershipId: number
|
||||
}
|
||||
) {
|
||||
const { ownershipId } = options
|
||||
const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse'
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { HttpStatusCode, VideoCreateResult } from '@peertube/peertube-models'
|
||||
import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@peertube/peertube-server-commands'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('Test video change ownership API validator', function () {
|
||||
let server: PeerTubeServer
|
||||
@@ -98,6 +99,57 @@ describe('Test video change ownership API validator', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('List video ownership changes', function () {
|
||||
it('Should fail if not authenticated', async function () {
|
||||
await server.changeOwnership.listOfVideo({
|
||||
videoId: rootVideo.id,
|
||||
token: null,
|
||||
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a non existing video', async function () {
|
||||
await server.changeOwnership.listOfVideo({
|
||||
videoId: 42,
|
||||
expectedStatus: HttpStatusCode.NOT_FOUND_404
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with a video of another user', async function () {
|
||||
for (const token of [ userToken, userEditorToken ]) {
|
||||
await server.changeOwnership.listOfVideo({
|
||||
videoId: rootVideo.id,
|
||||
token,
|
||||
expectedStatus: HttpStatusCode.FORBIDDEN_403
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('Should fail with an invalid state parameter', async function () {
|
||||
await server.changeOwnership.listOfVideo({
|
||||
videoId: rootVideo.id,
|
||||
state: 'INVALID_STATE' as any,
|
||||
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
||||
})
|
||||
})
|
||||
|
||||
it('Should succeed with valid params', async function () {
|
||||
const { data, total } = await server.changeOwnership.listOfVideo({
|
||||
videoId: rootVideo.id
|
||||
})
|
||||
expect(total).to.be.a('number')
|
||||
expect(data).to.be.an('array')
|
||||
})
|
||||
|
||||
it('Should succeed with a state filter', async function () {
|
||||
const { data } = await server.changeOwnership.listOfVideo({
|
||||
videoId: rootVideo.id,
|
||||
state: 'WAITING'
|
||||
})
|
||||
expect(data).to.be.an('array')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Reject ownership change request', function () {
|
||||
it('Should fail if not authenticated', async function () {
|
||||
await server.changeOwnership.refuse({
|
||||
|
||||
@@ -105,8 +105,6 @@ describe('Test video change ownership - nominal', function () {
|
||||
})
|
||||
|
||||
it('Should send a request to change ownership of a video', async function () {
|
||||
this.timeout(15000)
|
||||
|
||||
await command.create({ token: firstUserToken, videoId: servers[0].store.videoCreated.id, username: secondUser })
|
||||
})
|
||||
|
||||
@@ -215,8 +213,6 @@ describe('Test video change ownership - nominal', function () {
|
||||
})
|
||||
|
||||
it('Should accept a live ownership change', async function () {
|
||||
this.timeout(20000)
|
||||
|
||||
await command.accept({ token: secondUserToken, ownershipId: lastRequestId, channelId: secondUserChannelId })
|
||||
|
||||
await waitJobs(servers)
|
||||
@@ -230,6 +226,28 @@ describe('Test video change ownership - nominal', function () {
|
||||
}
|
||||
})
|
||||
|
||||
it('Should list ownership changes for a specific video', async function () {
|
||||
const body = await command.listOfVideo({ videoId: servers[0].store.videoCreated.id })
|
||||
|
||||
expect(body.total).to.equal(2)
|
||||
expect(body.data).to.be.an('array')
|
||||
expect(body.data.length).to.equal(2)
|
||||
expect(body.data[0].video.id).to.equal(servers[0].store.videoCreated.id)
|
||||
|
||||
expect(body.data.map(i => i.status)).to.have.members([ 'ACCEPTED', 'REFUSED' ])
|
||||
})
|
||||
|
||||
it('Should list ownership changes with state filter', async function () {
|
||||
const body = await command.listOfVideo({
|
||||
videoId: servers[0].store.videoCreated.id,
|
||||
state: 'ACCEPTED'
|
||||
})
|
||||
|
||||
expect(body.total).to.equal(1)
|
||||
|
||||
expect(body.data[0].status).to.equal('ACCEPTED')
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HttpStatusCode, VideoChangeOwnershipStatus, VideoChannelActivityAction } from '@peertube/peertube-models'
|
||||
import { HttpStatusCode, VideoChangeOwnershipStatus, VideoChangeOwnershipStatusType, VideoChannelActivityAction } from '@peertube/peertube-models'
|
||||
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||
import { VideoChannelActivityModel } from '@server/models/video/video-channel-activity.js'
|
||||
import { MVideoFull } from '@server/types/models/index.js'
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
setDefaultPagination,
|
||||
videosAcceptChangeOwnershipValidator,
|
||||
videosChangeOwnershipValidator,
|
||||
videosListVideoOwnershipChangesValidator,
|
||||
videosTerminateChangeOwnershipValidator
|
||||
} from '../../../middlewares/index.js'
|
||||
import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership.js'
|
||||
@@ -31,6 +32,15 @@ ownershipVideoRouter.post(
|
||||
asyncRetryTransactionMiddleware(giveVideoOwnership)
|
||||
)
|
||||
|
||||
ownershipVideoRouter.get(
|
||||
'/:videoId/ownership',
|
||||
authenticate,
|
||||
asyncMiddleware(videosListVideoOwnershipChangesValidator),
|
||||
paginationValidator,
|
||||
setDefaultPagination,
|
||||
asyncRetryTransactionMiddleware(listVideoOwnershipChanges)
|
||||
)
|
||||
|
||||
ownershipVideoRouter.get(
|
||||
'/ownership',
|
||||
authenticate,
|
||||
@@ -99,6 +109,21 @@ async function giveVideoOwnership (req: express.Request, res: express.Response)
|
||||
return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
|
||||
}
|
||||
|
||||
async function listVideoOwnershipChanges (req: express.Request, res: express.Response) {
|
||||
const videoId = res.locals.videoWithRights.id
|
||||
const state = req.query.state as VideoChangeOwnershipStatusType | undefined
|
||||
|
||||
const resultList = await VideoChangeOwnershipModel.listForApi({
|
||||
videoId,
|
||||
state,
|
||||
start: req.query.start,
|
||||
count: req.query.count,
|
||||
sort: req.query.sort || 'createdAt'
|
||||
})
|
||||
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total))
|
||||
}
|
||||
|
||||
async function listVideoOwnership (req: express.Request, res: express.Response) {
|
||||
const currentAccountId = res.locals.oauth.token.User.Account.id
|
||||
|
||||
|
||||
@@ -58,27 +58,36 @@ const lTags = loggerTagsFactory('notifier')
|
||||
class Notifier {
|
||||
private readonly notificationModels = {
|
||||
newVideoOrLive: [ NewVideoOrLiveForSubscribers ],
|
||||
|
||||
publicationAfterTranscoding: [ OwnedPublicationAfterTranscoding ],
|
||||
publicationAfterScheduleUpdate: [ OwnedPublicationAfterScheduleUpdate ],
|
||||
publicationAfterAutoUnblacklist: [ OwnedPublicationAfterAutoUnblacklist ],
|
||||
videoStudioEditionFinished: [ StudioEditionFinishedForOwner ],
|
||||
videoTranscriptionGenerated: [ VideoTranscriptionGeneratedForOwner ],
|
||||
|
||||
newComment: [ CommentMention, NewCommentForVideoOwner ],
|
||||
commentApproval: [ CommentMention ],
|
||||
|
||||
newAbuse: [ NewAbuseForModerators ],
|
||||
abuseStateChange: [ AbuseStateChangeForReporter ],
|
||||
newAbuseMessage: [ NewAbuseMessageForReporter, NewAbuseMessageForModerators ],
|
||||
|
||||
newBlacklist: [ NewBlacklistForOwner ],
|
||||
newAutoBlacklist: [ NewAutoBlacklistForModerators ],
|
||||
unblacklist: [ UnblacklistForOwner ],
|
||||
|
||||
importFinished: [ ImportFinishedForOwner ],
|
||||
|
||||
directRegistration: [ DirectRegistrationForModerators ],
|
||||
registrationRequest: [ RegistrationRequestForModerators ],
|
||||
|
||||
userFollow: [ FollowForUser ],
|
||||
instanceFollow: [ FollowForInstance ],
|
||||
autoInstanceFollow: [ AutoFollowForInstance ],
|
||||
newAutoBlacklist: [ NewAutoBlacklistForModerators ],
|
||||
abuseStateChange: [ AbuseStateChangeForReporter ],
|
||||
newAbuseMessage: [ NewAbuseMessageForReporter, NewAbuseMessageForModerators ],
|
||||
|
||||
newPeertubeVersion: [ NewPeerTubeVersionForAdmins ],
|
||||
newPluginVersion: [ NewPluginVersionForAdmins ],
|
||||
videoStudioEditionFinished: [ StudioEditionFinishedForOwner ],
|
||||
videoTranscriptionGenerated: [ VideoTranscriptionGeneratedForOwner ],
|
||||
|
||||
channelCollaboratorInvitation: [ InvitedToCollaborateToChannel ],
|
||||
channelCollaborationAccepted: [ AcceptedToCollaborateToChannel ],
|
||||
channelCollaborationRefused: [ RefusedToCollaborateToChannel ]
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CONFIG } from '@server/initializers/config.js'
|
||||
import { AccountModel } from '@server/models/account/account.js'
|
||||
import { MUserAccountId, MVideoChangeOwnershipFull, MVideoWithAllFiles } from '@server/types/models/index.js'
|
||||
import express from 'express'
|
||||
import { param } from 'express-validator'
|
||||
import { param, query } from 'express-validator'
|
||||
import {
|
||||
areValidationErrors,
|
||||
checkCanManageAccount,
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isValidVideoIdParam
|
||||
} from '../shared/index.js'
|
||||
import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership.js'
|
||||
import { VideoModel } from '@server/models/video/video.js'
|
||||
|
||||
export const videosChangeOwnershipValidator = [
|
||||
isValidVideoIdParam('videoId'),
|
||||
@@ -98,7 +99,7 @@ export const videosAcceptChangeOwnershipValidator = [
|
||||
|
||||
const videoChangeOwnership = res.locals.videoChangeOwnership
|
||||
|
||||
const video = videoChangeOwnership.Video
|
||||
const video = await VideoModel.loadWithFiles(videoChangeOwnership.Video.id)
|
||||
|
||||
if (!await checkCanAccept(video, req, res)) return
|
||||
|
||||
@@ -106,6 +107,38 @@ export const videosAcceptChangeOwnershipValidator = [
|
||||
}
|
||||
]
|
||||
|
||||
export const videosListVideoOwnershipChangesValidator = [
|
||||
isValidVideoIdParam('videoId'),
|
||||
|
||||
query('state')
|
||||
.optional()
|
||||
.custom(value => {
|
||||
if (!Object.values(VideoChangeOwnershipStatus).includes(value)) return false
|
||||
|
||||
return true
|
||||
}),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await doesVideoExist(req.params.videoId, res, 'with-rights')) return
|
||||
|
||||
// Check if the user who did the request is able to manage the video
|
||||
if (
|
||||
!await checkCanManageVideo({
|
||||
user: res.locals.oauth.token.User,
|
||||
video: res.locals.videoWithRights,
|
||||
right: UserRight.CHANGE_VIDEO_OWNERSHIP,
|
||||
checkIsOwner: false,
|
||||
checkIsLocal: true,
|
||||
req,
|
||||
res
|
||||
})
|
||||
) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+43
-11
@@ -1,11 +1,17 @@
|
||||
import { VideoChannelCollaboratorState } from '@peertube/peertube-models'
|
||||
import { VideoChangeOwnershipStatusType, VideoChannelCollaboratorState } from '@peertube/peertube-models'
|
||||
import { AbstractListQuery, AbstractListQueryOptions } from '@server/models/shared/abstract-list-query.js'
|
||||
import { getActorJoin, getAvatarsJoin, getChannelJoin } from '@server/models/shared/sql/actor-helpers.js'
|
||||
import { Sequelize } from 'sequelize'
|
||||
import { VideoChangeOwnershipTableAttributes } from './video-change-ownership-table-attributes.js'
|
||||
|
||||
export interface ListVideoChangeOwnershipOptions extends AbstractListQueryOptions {
|
||||
accountId: number
|
||||
id?: number
|
||||
|
||||
accountId?: number
|
||||
|
||||
state?: VideoChangeOwnershipStatusType
|
||||
|
||||
videoId?: number
|
||||
}
|
||||
|
||||
export class VideoChangeOwnershipListQueryBuilder extends AbstractListQuery {
|
||||
@@ -31,16 +37,42 @@ export class VideoChangeOwnershipListQueryBuilder extends AbstractListQuery {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
protected buildSubQueryWhere () {
|
||||
this.buildChannelCollaboratorsJoin()
|
||||
this.subQueryWhere = `WHERE "VideoChangeOwnershipModel"."nextOwnerAccountId" = :nextOwnerAccountId ` +
|
||||
`OR (` +
|
||||
`"Video->VideoChannel->VideoChannelCollaborators"."accountId" = :collaborationAccountId OR ` +
|
||||
`"Video->VideoChannel->Account"."id" = :videoAccountId` +
|
||||
`)`
|
||||
const where: string[] = []
|
||||
|
||||
this.replacements.collaborationAccountId = this.options.accountId
|
||||
this.replacements.videoAccountId = this.options.accountId
|
||||
this.replacements.nextOwnerAccountId = this.options.accountId
|
||||
if (this.options.accountId) {
|
||||
this.buildChannelCollaboratorsJoin()
|
||||
|
||||
where.push(
|
||||
`"VideoChangeOwnershipModel"."nextOwnerAccountId" = :nextOwnerAccountId ` +
|
||||
`OR (` +
|
||||
`"Video->VideoChannel->VideoChannelCollaborators"."accountId" = :collaborationAccountId OR ` +
|
||||
`"Video->VideoChannel->Account"."id" = :videoAccountId` +
|
||||
`)`
|
||||
)
|
||||
|
||||
this.replacements.collaborationAccountId = this.options.accountId
|
||||
this.replacements.videoAccountId = this.options.accountId
|
||||
this.replacements.nextOwnerAccountId = this.options.accountId
|
||||
}
|
||||
|
||||
if (this.options.state) {
|
||||
where.push(`"VideoChangeOwnershipModel"."status" = :status`)
|
||||
this.replacements.status = this.options.state
|
||||
}
|
||||
|
||||
if (this.options.videoId) {
|
||||
where.push(`"VideoChangeOwnershipModel"."videoId" = :videoId`)
|
||||
this.replacements.videoId = this.options.videoId
|
||||
}
|
||||
|
||||
if (this.options.id) {
|
||||
where.push(`"VideoChangeOwnershipModel"."id" = :id`)
|
||||
this.replacements.id = this.options.id
|
||||
}
|
||||
|
||||
this.subQueryWhere = where.length !== 0
|
||||
? `WHERE ${where.join(' AND ')}`
|
||||
: ''
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import { VideoChangeOwnership, VideoChangeOwnershipStatus, type VideoChangeOwnershipStatusType } from '@peertube/peertube-models'
|
||||
import {
|
||||
MVideoChangeOwnership,
|
||||
MVideoChangeOwnershipFormattable,
|
||||
MVideoChangeOwnershipFull
|
||||
} from '@server/types/models/video/video-change-ownership.js'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { MVideoChangeOwnership, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership.js'
|
||||
import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
|
||||
import { AccountModel } from '../account/account.js'
|
||||
import { SequelizeModel, buildSQLAttributes } from '../shared/index.js'
|
||||
import {
|
||||
ListVideoChangeOwnershipOptions,
|
||||
VideoChangeOwnershipListQueryBuilder
|
||||
} from './sql/change-ownership/video-change-ownership-list-query-builder.js'
|
||||
import { VideoModel, ScopeNames as VideoScopeNames } from './video.js'
|
||||
|
||||
enum ScopeNames {
|
||||
WITH_ACCOUNTS = 'WITH_ACCOUNTS',
|
||||
WITH_VIDEO = 'WITH_VIDEO'
|
||||
}
|
||||
import { VideoModel } from './video.js'
|
||||
|
||||
@Table({
|
||||
tableName: 'videoChangeOwnership',
|
||||
@@ -32,35 +23,6 @@ enum ScopeNames {
|
||||
}
|
||||
]
|
||||
})
|
||||
@Scopes(() => ({
|
||||
[ScopeNames.WITH_ACCOUNTS]: {
|
||||
include: [
|
||||
{
|
||||
model: AccountModel,
|
||||
as: 'Initiator',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
model: AccountModel,
|
||||
as: 'NextOwner',
|
||||
required: true
|
||||
}
|
||||
]
|
||||
},
|
||||
[ScopeNames.WITH_VIDEO]: {
|
||||
include: [
|
||||
{
|
||||
model: VideoModel.scope([
|
||||
VideoScopeNames.WITH_THUMBNAILS,
|
||||
VideoScopeNames.WITH_WEB_VIDEO_FILES,
|
||||
VideoScopeNames.WITH_STREAMING_PLAYLISTS,
|
||||
VideoScopeNames.WITH_ACCOUNT_DETAILS
|
||||
]),
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
export class VideoChangeOwnershipModel extends SequelizeModel<VideoChangeOwnershipModel> {
|
||||
@CreatedAt
|
||||
declare createdAt: Date
|
||||
@@ -128,20 +90,19 @@ export class VideoChangeOwnershipModel extends SequelizeModel<VideoChangeOwnersh
|
||||
}
|
||||
|
||||
static load (id: number): Promise<MVideoChangeOwnershipFull> {
|
||||
return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ])
|
||||
.findByPk(id)
|
||||
return new VideoChangeOwnershipListQueryBuilder(VideoChangeOwnershipModel.sequelize, { id }).get<MVideoChangeOwnershipFull>()
|
||||
}
|
||||
|
||||
static loadPendingByVideo (videoId: number): Promise<MVideoChangeOwnership> {
|
||||
return VideoChangeOwnershipModel.findOne({
|
||||
where: {
|
||||
videoId,
|
||||
status: VideoChangeOwnershipStatus.WAITING
|
||||
}
|
||||
})
|
||||
return new VideoChangeOwnershipListQueryBuilder(VideoChangeOwnershipModel.sequelize, {
|
||||
videoId,
|
||||
state: VideoChangeOwnershipStatus.WAITING
|
||||
}).get<MVideoChangeOwnershipFull>()
|
||||
}
|
||||
|
||||
toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toFormattedJSON (this: MVideoChangeOwnershipFull): VideoChangeOwnership {
|
||||
return {
|
||||
id: this.id,
|
||||
status: this.status,
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { uuidToShort } from '@peertube/peertube-node-utils'
|
||||
import { VIDEO_CHANNEL_ACTIVITY_ACTIONS, VIDEO_CHANNEL_ACTIVITY_TARGETS } from '@server/initializers/constants.js'
|
||||
import {
|
||||
MAccountActor,
|
||||
MAccountNames,
|
||||
MAccountUrl,
|
||||
MChannelId,
|
||||
MChannelSync,
|
||||
MUserAccountId,
|
||||
@@ -309,7 +310,7 @@ export class VideoChannelActivityModel extends SequelizeModel<VideoChannelActivi
|
||||
user: MUserAccountId
|
||||
channel: MChannelId
|
||||
video: MVideo
|
||||
targetAccount: MAccountActor
|
||||
targetAccount: MAccountNames & MAccountUrl
|
||||
transaction: Transaction
|
||||
}) {
|
||||
const { action, user, channel, video, targetAccount, transaction } = options
|
||||
|
||||
@@ -12,7 +12,8 @@ import {
|
||||
MActorId,
|
||||
MActorSummary,
|
||||
MActorSummaryFormattable,
|
||||
MActorUrl
|
||||
MActorUrl,
|
||||
MActorUsername
|
||||
} from '../actor/index.js'
|
||||
import { MChannelDefault } from '../video/video-channel.js'
|
||||
import { MAccountBlocklistId } from './account-blocklist.js'
|
||||
@@ -71,7 +72,6 @@ export type MAccountLight =
|
||||
|
||||
// ############################################################################
|
||||
|
||||
// Full actor
|
||||
export type MAccountActor =
|
||||
& MAccount
|
||||
& Use<'Actor', MActor>
|
||||
@@ -84,6 +84,10 @@ export type MAccountHost =
|
||||
& MAccount
|
||||
& Use<'Actor', MActorHost>
|
||||
|
||||
export type MAccountNames =
|
||||
& Pick<MAccount, 'id' | 'name'>
|
||||
& Use<'Actor', MActorUsername>
|
||||
|
||||
// ############################################################################
|
||||
|
||||
// For API
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership.js'
|
||||
import { PickWith } from '@peertube/peertube-typescript-utils'
|
||||
import { MAccountDefault, MAccountFormattable } from '../account/account.js'
|
||||
import { MVideoFormattable, MVideoWithAllFiles } from './video.js'
|
||||
import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership.js'
|
||||
import { MAccountFormattable } from '../account/account.js'
|
||||
import { MVideoFormattable } from './video.js'
|
||||
|
||||
type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M>
|
||||
|
||||
@@ -11,16 +11,6 @@ export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator'
|
||||
|
||||
export type MVideoChangeOwnershipFull =
|
||||
& MVideoChangeOwnership
|
||||
& Use<'Initiator', MAccountDefault>
|
||||
& Use<'NextOwner', MAccountDefault>
|
||||
& Use<'Video', MVideoWithAllFiles>
|
||||
|
||||
// ############################################################################
|
||||
|
||||
// Format for API or AP object
|
||||
|
||||
export type MVideoChangeOwnershipFormattable =
|
||||
& Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'>
|
||||
& Use<'Initiator', MAccountFormattable>
|
||||
& Use<'NextOwner', MAccountFormattable>
|
||||
& Use<'Video', MVideoFormattable>
|
||||
|
||||
@@ -2967,6 +2967,7 @@ paths:
|
||||
/api/v1/videos/ownership:
|
||||
get:
|
||||
summary: List video ownership changes
|
||||
description: List ownership change requests received by the authenticated user and requests sent by a channel managed by the authenticated user
|
||||
tags:
|
||||
- Video Ownership Change
|
||||
security:
|
||||
@@ -2974,6 +2975,18 @@ paths:
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
example: 1
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VideoChangeOwnership'
|
||||
|
||||
'/api/v1/videos/ownership/{id}/accept':
|
||||
post:
|
||||
@@ -3009,6 +3022,49 @@ paths:
|
||||
'404':
|
||||
description: video ownership change not found
|
||||
|
||||
'/api/v1/videos/{id}/ownership':
|
||||
get:
|
||||
summary: List ownership change requests for a video
|
||||
description: List all ownership change requests for a specific video. The authenticated user must be able to manage the video.
|
||||
tags:
|
||||
- Video Ownership Change
|
||||
security:
|
||||
- OAuth2: []
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/idOrUUID'
|
||||
- name: state
|
||||
in: query
|
||||
required: false
|
||||
description: Filter by ownership change state (WAITING, ACCEPTED, REFUSED)
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- WAITING
|
||||
- ACCEPTED
|
||||
- REFUSED
|
||||
- $ref: '#/components/parameters/start'
|
||||
- $ref: '#/components/parameters/count'
|
||||
- $ref: '#/components/parameters/sort'
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
example: 1
|
||||
data:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VideoChangeOwnership'
|
||||
'403':
|
||||
description: cannot manage the video
|
||||
'404':
|
||||
description: video not found
|
||||
|
||||
'/api/v1/videos/{id}/give-ownership':
|
||||
post:
|
||||
summary: Request ownership change
|
||||
@@ -9977,6 +10033,28 @@ components:
|
||||
type: integer
|
||||
nsfw:
|
||||
type: boolean
|
||||
VideoChangeOwnership:
|
||||
type: object
|
||||
description: Representation of a video ownership change
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/id'
|
||||
status:
|
||||
type: string
|
||||
description: Status of the ownership change request
|
||||
enum:
|
||||
- WAITING
|
||||
- ACCEPTED
|
||||
- REFUSED
|
||||
initiatorAccount:
|
||||
$ref: '#/components/schemas/Account'
|
||||
nextOwnerAccount:
|
||||
$ref: '#/components/schemas/Account'
|
||||
video:
|
||||
$ref: '#/components/schemas/Video'
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
VideoPlaylist:
|
||||
properties:
|
||||
id:
|
||||
|
||||
Reference in New Issue
Block a user