this.files = hash.files
this.channel = hash.channel
this.account = hash.account
+ this.tags = hash.tags
this.likesPercent = (this.likes / (this.likes + this.dislikes)) * 100
this.dislikesPercent = (this.dislikes / (this.likes + this.dislikes)) * 100
isLocal: boolean
name: string
serverHost: string
- tags: string[]
thumbnailPath: string
thumbnailUrl: string
previewPath: string
this.isLocal = hash.isLocal
this.name = hash.name
this.serverHost = hash.serverHost
- this.tags = hash.tags
this.thumbnailPath = hash.thumbnailPath
this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
this.previewPath = hash.previewPath
if (!await isVideoChannelExist(req.params.id, res)) return
// Check if the user who did the request is able to delete the video
- if (!checkUserCanDeleteVideoChannel(res.locals.user, res.locals.videoChannel, res)) return
+ if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
if (!await checkVideoChannelIsNotTheLastOne(res)) return
return next()
BeforeUpdate,
Column, CreatedAt,
DataType,
- Default,
+ Default, DefaultScope,
HasMany,
HasOne,
Is,
IsEmail,
- Model,
+ Model, Scopes,
Table, UpdatedAt
} from 'sequelize-typescript'
import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
import { VideoChannelModel } from '../video/video-channel'
import { AccountModel } from './account'
+@DefaultScope({
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ }
+ ]
+})
+@Scopes({
+ withVideoChannel: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true,
+ include: [ () => VideoChannelModel ]
+ }
+ ]
+ }
+})
@Table({
tableName: 'user',
indexes: [
const query = {
offset: start,
limit: count,
- order: [ getSort(sort) ],
- include: [ { model: AccountModel, required: true } ]
+ order: [ getSort(sort) ]
}
return UserModel.findAndCountAll(query)
}
static loadById (id: number) {
- const options = {
- include: [ { model: AccountModel, required: true } ]
- }
-
- return UserModel.findById(id, options)
+ return UserModel.findById(id)
}
static loadByUsername (username: string) {
const query = {
where: {
username
- },
- include: [ { model: AccountModel, required: true } ]
+ }
}
return UserModel.findOne(query)
const query = {
where: {
username
- },
- include: [
- {
- model: AccountModel,
- required: true,
- include: [ VideoChannelModel ]
- }
- ]
+ }
}
- return UserModel.findOne(query)
+ return UserModel.scope('withVideoChannel').findOne(query)
}
static loadByUsernameOrEmail (username: string, email: string) {
const query = {
- include: [ { model: AccountModel, required: true } ],
where: {
[ Sequelize.Op.or ]: [ { username }, { email } ]
}
}
- // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387
- return (UserModel as any).findOne(query)
+ return UserModel.findOne(query)
}
private static getOriginalVideoFileTotalFromUser (user: UserModel) {
-import { Transaction } from 'sequelize'
import { AllowNull, Column, Default, IsInt, Model, Table } from 'sequelize-typescript'
@Table({
static countTotal () {
return ApplicationModel.count()
}
-
- static loadMigrationVersion () {
- const query = {
- attributes: [ 'migrationVersion' ]
- }
-
- return ApplicationModel.findOne(query).then(data => data ? data.migrationVersion : null)
- }
-
- static updateMigrationVersion (newVersion: number, transaction: Transaction) {
- const options = {
- where: {},
- transaction: transaction
- }
-
- return ApplicationModel.update({ migrationVersion: newVersion }, options)
- }
}
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { logger } from '../../helpers'
import { AccountModel } from '../account/account'
import { UserModel } from '../account/user'
}
}
+enum ScopeNames {
+ WITH_ACCOUNT = 'WITH_ACCOUNT'
+}
+
+@Scopes({
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => UserModel,
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ }
+ ]
+ }
+ ]
+ }
+})
@Table({
tableName: 'oAuthToken',
indexes: [
const query = {
where: {
accessToken: bearerToken
- },
- include: [
- {
- model: UserModel,
- include: [
- {
- model: AccountModel,
- required: true
- }
- ]
- }
- ]
+ }
}
- return OAuthTokenModel.findOne(query).then(token => {
+ return OAuthTokenModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query).then(token => {
if (token) token['user'] = token.User
return token
const query = {
where: {
refreshToken: refreshToken
- },
- include: [
- {
- model: UserModel,
- include: [
- {
- model: AccountModel,
- required: true
- }
- ]
- }
- ]
+ }
}
- return OAuthTokenModel.findOne(query).then(token => {
- token['user'] = token.User
+ return OAuthTokenModel.scope(ScopeNames.WITH_ACCOUNT)
+ .findOne(query)
+ .then(token => {
+ token['user'] = token.User
- return token
- })
+ return token
+ })
}
}
import * as Sequelize from 'sequelize'
-import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { AccountModel } from '../account/account'
import { VideoChannelModel } from './video-channel'
+enum ScopeNames {
+ FULL = 'FULL',
+ WITH_ACCOUNT = 'WITH_ACCOUNT'
+}
+
+@Scopes({
+ [ScopeNames.FULL]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ },
+ {
+ model: () => VideoChannelModel,
+ required: true
+ }
+ ]
+ },
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ }
+ ]
+ }
+})
@Table({
tableName: 'videoChannelShare',
indexes: [
VideoChannel: VideoChannelModel
static load (accountId: number, videoChannelId: number, t: Sequelize.Transaction) {
- return VideoChannelShareModel.findOne({
+ return VideoChannelShareModel.scope(ScopeNames.FULL).findOne({
where: {
accountId,
videoChannelId
},
- include: [
- AccountModel,
- VideoChannelModel
- ],
transaction: t
})
}
where: {
videoChannelId
},
- include: [
- {
- model: AccountModel,
- required: true
- }
- ],
transaction: t
}
- return VideoChannelShareModel.findAll(query)
+ return VideoChannelShareModel.scope(ScopeNames.WITH_ACCOUNT).findAll(query)
.then(res => res.map(r => r.Account))
}
}
HasMany,
Is,
IsUUID,
- Model,
+ Model, Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { VideoModel } from './video'
import { VideoChannelShareModel } from './video-channel-share'
+enum ScopeNames {
+ WITH_ACCOUNT = 'WITH_ACCOUNT',
+ WITH_VIDEOS = 'WITH_VIDEOS'
+}
+
+@Scopes({
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => AccountModel,
+ include: [ { model: () => ServerModel, required: false } ]
+ }
+ ]
+ },
+ [ScopeNames.WITH_VIDEOS]: {
+ include: [
+ () => VideoModel
+ ]
+ }
+})
@Table({
tableName: 'videoChannel',
indexes: [
const query = {
offset: start,
limit: count,
- order: [ getSort(sort) ],
- include: [
- {
- model: AccountModel,
- required: true,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
+ order: [ getSort(sort) ]
}
- return VideoChannelModel.findAndCountAll(query)
+ return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findAndCountAll(query)
.then(({ rows, count }) => {
return { total: count, data: rows }
})
})
}
- static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
- const query: IFindOptions<VideoChannelModel> = {
- where: {
- uuid
- }
- }
-
- if (t !== undefined) query.transaction = t
-
- return VideoChannelModel.findOne(query)
- }
-
static loadByUrl (url: string, t?: Sequelize.Transaction) {
const query: IFindOptions<VideoChannelModel> = {
where: {
url
- },
- include: [ AccountModel ]
+ }
}
if (t !== undefined) query.transaction = t
- return VideoChannelModel.findOne(query)
+ return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
}
static loadByUUIDOrUrl (uuid: string, url: string, t?: Sequelize.Transaction) {
return VideoChannelModel.findOne(query)
}
- static loadByHostAndUUID (fromHost: string, uuid: string, t?: Sequelize.Transaction) {
- const query: IFindOptions<VideoChannelModel> = {
- where: {
- uuid
- },
- include: [
- {
- model: AccountModel,
- include: [
- {
- model: ServerModel,
- required: true,
- where: {
- host: fromHost
- }
- }
- ]
- }
- ]
- }
-
- if (t !== undefined) query.transaction = t
-
- return VideoChannelModel.findOne(query)
- }
-
static loadByIdAndAccount (id: number, accountId: number) {
const options = {
where: {
id,
accountId
- },
- include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
+ }
}
- return VideoChannelModel.findOne(options)
+ return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
}
static loadAndPopulateAccount (id: number) {
- const options = {
- include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
- }
-
- return VideoChannelModel.findById(id, options)
+ return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findById(id)
}
static loadByUUIDAndPopulateAccount (uuid: string) {
const options = {
where: {
uuid
- },
- include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
+ }
}
- return VideoChannelModel.findOne(options)
+ return VideoChannelModel.scope(ScopeNames.WITH_ACCOUNT).findOne(options)
}
static loadAndPopulateAccountAndVideos (id: number) {
const options = {
include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- },
VideoModel
]
}
- return VideoChannelModel.findById(id, options)
+ return VideoChannelModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]).findById(id, options)
}
isOwned () {
import * as Sequelize from 'sequelize'
-import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { AccountModel } from '../account/account'
import { VideoModel } from './video'
+enum ScopeNames {
+ FULL = 'FULL',
+ WITH_ACCOUNT = 'WITH_ACCOUNT'
+}
+
+@Scopes({
+ [ScopeNames.FULL]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ },
+ {
+ model: () => VideoModel,
+ required: true
+ }
+ ]
+ },
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => AccountModel,
+ required: true
+ }
+ ]
+ }
+})
@Table({
tableName: 'videoShare',
indexes: [
Video: VideoModel
static load (accountId: number, videoId: number, t: Sequelize.Transaction) {
- return VideoShareModel.findOne({
+ return VideoShareModel.scope(ScopeNames.WITH_ACCOUNT).findOne({
where: {
accountId,
videoId
},
- include: [
- AccountModel
- ],
transaction: t
})
}
transaction: t
}
- return VideoShareModel.findAll(query)
+ return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
.then(res => res.map(r => r.Account))
}
}
IsUUID,
Min,
Model,
+ Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions'
import { VideoPrivacy, VideoResolution } from '../../../shared'
import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
+import { Video, VideoDetails } from '../../../shared/models/videos'
import {
activityPubCollection,
createTorrentPromise,
import { VideoShareModel } from './video-share'
import { VideoTagModel } from './video-tag'
+enum ScopeNames {
+ NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST',
+ PUBLIC = 'PUBLIC',
+ WITH_ACCOUNT = 'WITH_ACCOUNT',
+ WITH_TAGS = 'WITH_TAGS',
+ WITH_FILES = 'WITH_FILES',
+ WITH_SHARES = 'WITH_SHARES',
+ WITH_RATES = 'WITH_RATES'
+}
+
+@Scopes({
+ [ScopeNames.NOT_IN_BLACKLIST]: {
+ where: {
+ id: {
+ [Sequelize.Op.notIn]: Sequelize.literal(
+ '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
+ )
+ }
+ }
+ },
+ [ScopeNames.PUBLIC]: {
+ where: {
+ privacy: VideoPrivacy.PUBLIC
+ }
+ },
+ [ScopeNames.WITH_ACCOUNT]: {
+ include: [
+ {
+ model: () => VideoChannelModel,
+ required: true,
+ include: [
+ {
+ model: () => AccountModel,
+ required: true,
+ include: [
+ {
+ model: () => ServerModel,
+ required: false
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ [ScopeNames.WITH_TAGS]: {
+ include: [ () => TagModel ]
+ },
+ [ScopeNames.WITH_FILES]: {
+ include: [
+ {
+ model: () => VideoFileModel,
+ required: true
+ }
+ ]
+ },
+ [ScopeNames.WITH_SHARES]: {
+ include: [
+ {
+ model: () => VideoShareModel,
+ include: [ () => AccountModel ]
+ }
+ ]
+ },
+ [ScopeNames.WITH_RATES]: {
+ include: [
+ {
+ model: () => AccountVideoRateModel,
+ include: [ () => AccountModel ]
+ }
+ ]
+ }
+})
@Table({
tableName: 'video',
indexes: [
}
static list () {
- const query = {
- include: [ VideoFileModel ]
- }
-
- return VideoModel.findAll(query)
+ return VideoModel.scope(ScopeNames.WITH_FILES).findAll()
}
static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) {
static listUserVideosForApi (userId: number, start: number, count: number, sort: string) {
const query = {
- distinct: true,
offset: start,
limit: count,
- order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ],
+ order: [ getSort(sort) ],
include: [
{
model: VideoChannelModel,
required: true
}
]
- },
- TagModel
+ }
]
}
static listForApi (start: number, count: number, sort: string) {
const query = {
- distinct: true,
offset: start,
limit: count,
- order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ],
- include: [
- {
- model: VideoChannelModel,
- required: true,
- include: [
- {
- model: AccountModel,
- required: true,
- include: [
- {
- model: ServerModel,
- required: false
- }
- ]
- }
- ]
- },
- TagModel
- ],
- where: this.createBaseVideosWhere()
+ order: [ getSort(sort) ]
}
- return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
- return {
- data: rows,
- total: count
- }
- })
+ return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ])
+ .findAndCountAll(query)
+ .then(({ rows, count }) => {
+ return {
+ data: rows,
+ total: count
+ }
+ })
}
static load (id: number) {
return VideoModel.findById(id)
}
- static loadByUUID (uuid: string, t?: Sequelize.Transaction) {
- const query: IFindOptions<VideoModel> = {
- where: {
- uuid
- },
- include: [ VideoFileModel ]
- }
-
- if (t !== undefined) query.transaction = t
-
- return VideoModel.findOne(query)
- }
-
static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) {
const query: IFindOptions<VideoModel> = {
where: {
url
- },
- include: [
- VideoFileModel,
- {
- model: VideoChannelModel,
- include: [ AccountModel ]
- }
- ]
+ }
}
if (t !== undefined) query.transaction = t
- return VideoModel.findOne(query)
+ return VideoModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_FILES ]).findOne(query)
}
static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) {
{ uuid },
{ url }
]
- },
- include: [ VideoFileModel ]
+ }
}
if (t !== undefined) query.transaction = t
- return VideoModel.findOne(query)
+ return VideoModel.scope(ScopeNames.WITH_FILES).findOne(query)
}
static loadAndPopulateAccountAndServerAndTags (id: number) {
const options = {
- order: [ [ 'Tags', 'name', 'ASC' ] ],
- include: [
- {
- model: VideoChannelModel,
- include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
- },
- {
- model: AccountVideoRateModel,
- include: [ AccountModel ]
- },
- {
- model: VideoShareModel,
- include: [ AccountModel ]
- },
- TagModel,
- VideoFileModel
- ]
+ order: [ [ 'Tags', 'name', 'ASC' ] ]
}
- return VideoModel.findById(id, options)
+ return VideoModel
+ .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ])
+ .findById(id, options)
}
static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) {
order: [ [ 'Tags', 'name', 'ASC' ] ],
where: {
uuid
- },
- include: [
- {
- model: VideoChannelModel,
- include: [
- {
- model: AccountModel,
- include: [ { model: ServerModel, required: false } ]
- }
- ]
- },
- {
- model: AccountVideoRateModel,
- include: [ AccountModel ]
- },
- {
- model: VideoShareModel,
- include: [ AccountModel ]
- },
- TagModel,
- VideoFileModel
- ]
+ }
}
- return VideoModel.findOne(options)
+ return VideoModel
+ .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ])
+ .findOne(options)
}
static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) {
}
const query: IFindOptions<VideoModel> = {
- distinct: true,
- where: this.createBaseVideosWhere(),
+ distinct: true, // Because we have tags
offset: start,
limit: count,
- order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ]
+ order: [ getSort(sort) ],
+ where: {}
}
// TODO: search on tags too
videoChannelInclude, tagInclude
]
- return VideoModel.findAndCountAll(query).then(({ rows, count }) => {
- return {
- data: rows,
- total: count
- }
- })
- }
-
- private static createBaseVideosWhere () {
- return {
- id: {
- [Sequelize.Op.notIn]: VideoModel.sequelize.literal(
- '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
- )
- },
- privacy: VideoPrivacy.PUBLIC
- }
+ return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ])
+ .findAndCountAll(query).then(({ rows, count }) => {
+ return {
+ data: rows,
+ total: count
+ }
+ })
}
getOriginalFile () {
views: this.views,
likes: this.likes,
dislikes: this.dislikes,
- tags: map<TagModel, string>(this.Tags, 'name'),
thumbnailPath: this.getThumbnailPath(),
previewPath: this.getPreviewPath(),
embedPath: this.getEmbedPath(),
createdAt: this.createdAt,
updatedAt: this.updatedAt
- }
+ } as Video
}
toFormattedDetailsJSON () {
descriptionPath: this.getDescriptionPath(),
channel: this.VideoChannel.toFormattedJSON(),
account: this.VideoChannel.Account.toFormattedJSON(),
+ tags: map<TagModel, string>(this.Tags, 'name'),
files: []
}
return -1
})
- return Object.assign(formattedJson, detailsJson)
+ return Object.assign(formattedJson, detailsJson) as VideoDetails
}
toActivityPubObject (): VideoTorrentObject {
expect(video.serverHost).to.equal('localhost:9001')
expect(video.accountName).to.equal('root')
expect(video.isLocal).to.be.true
- expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
expect(dateIsValid(video.createdAt)).to.be.true
expect(dateIsValid(video.updatedAt)).to.be.true
expect(video.serverHost).to.equal('localhost:9001')
expect(video.accountName).to.equal('root')
expect(video.isLocal).to.be.true
- expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
expect(dateIsValid(video.createdAt)).to.be.true
expect(dateIsValid(video.updatedAt)).to.be.true
expect(video.channel.name).to.equal('Default root channel')
expect(video.serverHost).to.equal('localhost:9001')
expect(video.accountName).to.equal('root')
expect(video.isLocal).to.be.true
- expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
expect(dateIsValid(video.createdAt)).to.be.true
expect(dateIsValid(video.updatedAt)).to.be.true
await uploadVideo(servers[1].url, servers[1].accessToken, video2Attributes)
// Wait videos propagation, server 2 has transcoding enabled
- await wait(10000)
+ await wait(15000)
const res = await getVideosList(servers[0].url)
const videos = res.body.data
isLocal: boolean
name: string
serverHost: string
- tags: string[]
thumbnailPath: string
previewPath: string
embedPath: string
privacyLabel: string
descriptionPath: string
channel: VideoChannel
+ tags: string[]
files: VideoFile[]
account: Account
}