Refactor model utils
This commit is contained in:
parent
a8749f7c3b
commit
8c4bbd946d
|
@ -1,5 +1,4 @@
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { SortType } from '../models/utils'
|
|
||||||
|
|
||||||
const setDefaultSort = setDefaultSortFactory('-createdAt')
|
const setDefaultSort = setDefaultSortFactory('-createdAt')
|
||||||
const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
|
const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
|
||||||
|
@ -7,27 +6,7 @@ const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
|
||||||
const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
|
const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
|
||||||
|
|
||||||
const setDefaultSearchSort = setDefaultSortFactory('-match')
|
const setDefaultSearchSort = setDefaultSortFactory('-match')
|
||||||
|
const setBlacklistSort = setDefaultSortFactory('-createdAt')
|
||||||
function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
|
|
||||||
const newSort: SortType = { sortModel: undefined, sortValue: '' }
|
|
||||||
|
|
||||||
if (!req.query.sort) req.query.sort = '-createdAt'
|
|
||||||
|
|
||||||
// Set model we want to sort onto
|
|
||||||
if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' ||
|
|
||||||
req.query.sort === '-id' || req.query.sort === 'id') {
|
|
||||||
// If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter...
|
|
||||||
newSort.sortModel = undefined
|
|
||||||
} else {
|
|
||||||
newSort.sortModel = 'Video'
|
|
||||||
}
|
|
||||||
|
|
||||||
newSort.sortValue = req.query.sort
|
|
||||||
|
|
||||||
req.query.sort = newSort
|
|
||||||
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models'
|
||||||
import { AbuseMessage } from '@shared/models'
|
import { AbuseMessage } from '@shared/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
|
import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../shared'
|
||||||
import { AbuseModel } from './abuse'
|
import { AbuseModel } from './abuse'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||||
import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
|
import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
|
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../shared'
|
||||||
import { ThumbnailModel } from '../video/thumbnail'
|
import { ThumbnailModel } from '../video/thumbnail'
|
||||||
import { ScopeNames as VideoScopeNames, VideoModel } from '../video/video'
|
import { ScopeNames as VideoScopeNames, VideoModel } from '../video/video'
|
||||||
import { VideoBlacklistModel } from '../video/video-blacklist'
|
import { VideoBlacklistModel } from '../video/video-blacklist'
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { exists } from '@server/helpers/custom-validators/misc'
|
import { exists } from '@server/helpers/custom-validators/misc'
|
||||||
import { forceNumber } from '@shared/core-utils'
|
import { forceNumber } from '@shared/core-utils'
|
||||||
import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models'
|
import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models'
|
||||||
import { buildBlockedAccountSQL, buildDirectionAndField } from '../../utils'
|
import { buildBlockedAccountSQL, buildSortDirectionAndField } from '../../shared'
|
||||||
|
|
||||||
export type BuildAbusesQueryOptions = {
|
export type BuildAbusesQueryOptions = {
|
||||||
start: number
|
start: number
|
||||||
|
@ -157,7 +157,7 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAbuseOrder (value: string) {
|
function buildAbuseOrder (value: string) {
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
return `ORDER BY "abuse"."${field}" ${direction}`
|
return `ORDER BY "abuse"."${field}" ${direction}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { AccountBlock } from '../../../shared/models'
|
import { AccountBlock } from '../../../shared/models'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { createSafeIn, getSort, searchAttribute } from '../utils'
|
import { createSafeIn, getSort, searchAttribute } from '../shared'
|
||||||
import { AccountModel } from './account'
|
import { AccountModel } from './account'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
|
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
|
||||||
import { AccountModel } from './account'
|
import { AccountModel } from './account'
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { ModelCache } from '@server/models/model-cache'
|
import { ModelCache } from '@server/models/shared/model-cache'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { Account, AccountSummary } from '../../../shared/models/actors'
|
import { Account, AccountSummary } from '../../../shared/models/actors'
|
||||||
import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
|
import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
|
||||||
|
@ -38,7 +38,7 @@ import { ApplicationModel } from '../application/application'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { ServerBlocklistModel } from '../server/server-blocklist'
|
import { ServerBlocklistModel } from '../server/server-blocklist'
|
||||||
import { UserModel } from '../user/user'
|
import { UserModel } from '../user/user'
|
||||||
import { buildSQLAttributes, getSort, throwIfNotValid } from '../utils'
|
import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { VideoCommentModel } from '../video/video-comment'
|
import { VideoCommentModel } from '../video/video-comment'
|
||||||
|
|
|
@ -38,7 +38,7 @@ import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAM
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { doesExist } from '../shared/query'
|
import { doesExist } from '../shared/query'
|
||||||
import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../utils'
|
import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { ActorModel, unusedActorAttributesForAPI } from './actor'
|
import { ActorModel, unusedActorAttributesForAPI } from './actor'
|
||||||
import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder'
|
import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder'
|
||||||
|
@ -225,7 +225,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
|
||||||
`WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId AND "state" = 'accepted' ` +
|
`WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId AND "state" = 'accepted' ` +
|
||||||
`LIMIT 1`
|
`LIMIT 1`
|
||||||
|
|
||||||
return doesExist(query, { actorId, followerActorId })
|
return doesExist(this.sequelize, query, { actorId, followerActorId })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Promise<MActorFollowActorsDefault> {
|
static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Promise<MActorFollowActorsDefault> {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants'
|
import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants'
|
||||||
import { buildSQLAttributes, throwIfNotValid } from '../utils'
|
import { buildSQLAttributes, throwIfNotValid } from '../shared'
|
||||||
import { ActorModel } from './actor'
|
import { ActorModel } from './actor'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { activityPubContextify } from '@server/lib/activitypub/context'
|
import { activityPubContextify } from '@server/lib/activitypub/context'
|
||||||
import { getBiggestActorImage } from '@server/lib/actor-image'
|
import { getBiggestActorImage } from '@server/lib/actor-image'
|
||||||
import { ModelCache } from '@server/models/model-cache'
|
import { ModelCache } from '@server/models/shared/model-cache'
|
||||||
import { forceNumber, getLowercaseExtension } from '@shared/core-utils'
|
import { forceNumber, getLowercaseExtension } from '@shared/core-utils'
|
||||||
import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models'
|
import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
|
@ -55,7 +55,7 @@ import {
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { getServerActor } from '../application/application'
|
import { getServerActor } from '../application/application'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { buildSQLAttributes, isOutdated, throwIfNotValid } from '../utils'
|
import { buildSQLAttributes, isOutdated, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { ActorFollowModel } from './actor-follow'
|
import { ActorFollowModel } from './actor-follow'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import { ModelBuilder } from '@server/models/shared'
|
import { ModelBuilder } from '@server/models/shared'
|
||||||
import { parseRowCountResult } from '@server/models/utils'
|
|
||||||
import { MActorFollowActorsDefault } from '@server/types/models'
|
import { MActorFollowActorsDefault } from '@server/types/models'
|
||||||
import { ActivityPubActorType, FollowState } from '@shared/models'
|
import { ActivityPubActorType, FollowState } from '@shared/models'
|
||||||
|
import { parseRowCountResult } from '../../shared'
|
||||||
import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
|
import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
|
||||||
|
|
||||||
export interface ListFollowersOptions {
|
export interface ListFollowersOptions {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import { ModelBuilder } from '@server/models/shared'
|
import { ModelBuilder } from '@server/models/shared'
|
||||||
import { parseRowCountResult } from '@server/models/utils'
|
|
||||||
import { MActorFollowActorsDefault } from '@server/types/models'
|
import { MActorFollowActorsDefault } from '@server/types/models'
|
||||||
import { ActivityPubActorType, FollowState } from '@shared/models'
|
import { ActivityPubActorType, FollowState } from '@shared/models'
|
||||||
|
import { parseRowCountResult } from '../../shared'
|
||||||
import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
|
import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
|
||||||
|
|
||||||
export interface ListFollowingOptions {
|
export interface ListFollowingOptions {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import { AbstractRunQuery } from '@server/models/shared'
|
import { AbstractRunQuery } from '@server/models/shared'
|
||||||
import { getInstanceFollowsSort } from '@server/models/utils'
|
|
||||||
import { ActorImageType } from '@shared/models'
|
import { ActorImageType } from '@shared/models'
|
||||||
|
import { getInstanceFollowsSort } from '../../../shared'
|
||||||
import { ActorFollowTableAttributes } from './actor-follow-table-attributes'
|
import { ActorFollowTableAttributes } from './actor-follow-table-attributes'
|
||||||
|
|
||||||
type BaseOptions = {
|
type BaseOptions = {
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { CONFIG } from '../../initializers/config'
|
||||||
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
|
import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared'
|
||||||
import { ScheduleVideoUpdateModel } from '../video/schedule-video-update'
|
import { ScheduleVideoUpdateModel } from '../video/schedule-video-update'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
isPluginStableVersionValid,
|
isPluginStableVersionValid,
|
||||||
isPluginTypeValid
|
isPluginTypeValid
|
||||||
} from '../../helpers/custom-validators/plugins'
|
} from '../../helpers/custom-validators/plugins'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../shared'
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
attributes: {
|
attributes: {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormat
|
||||||
import { ServerBlock } from '@shared/models'
|
import { ServerBlock } from '@shared/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { createSafeIn, getSort, searchAttribute } from '../utils'
|
import { createSafeIn, getSort, searchAttribute } from '../shared'
|
||||||
import { ServerModel } from './server'
|
import { ServerModel } from './server'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { MServer, MServerFormattable } from '@server/types/models/server'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { isHostValid } from '../../helpers/custom-validators/servers'
|
import { isHostValid } from '../../helpers/custom-validators/servers'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { buildSQLAttributes, throwIfNotValid } from '../utils'
|
import { buildSQLAttributes, throwIfNotValid } from '../shared'
|
||||||
import { ServerBlocklistModel } from './server-blocklist'
|
import { ServerBlocklistModel } from './server-blocklist'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
export * from './abstract-run-query'
|
export * from './abstract-run-query'
|
||||||
export * from './model-builder'
|
export * from './model-builder'
|
||||||
|
export * from './model-cache'
|
||||||
export * from './query'
|
export * from './query'
|
||||||
|
export * from './sequelize-helpers'
|
||||||
|
export * from './sort'
|
||||||
|
export * from './sql'
|
||||||
export * from './update'
|
export * from './update'
|
||||||
|
|
|
@ -1,17 +1,82 @@
|
||||||
import { BindOrReplacements, QueryTypes } from 'sequelize'
|
import { BindOrReplacements, Op, QueryTypes, Sequelize } from 'sequelize'
|
||||||
import { sequelizeTypescript } from '@server/initializers/database'
|
import validator from 'validator'
|
||||||
|
import { forceNumber } from '@shared/core-utils'
|
||||||
|
|
||||||
function doesExist (query: string, bind?: BindOrReplacements) {
|
function doesExist (sequelize: Sequelize, query: string, bind?: BindOrReplacements) {
|
||||||
const options = {
|
const options = {
|
||||||
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
||||||
bind,
|
bind,
|
||||||
raw: true
|
raw: true
|
||||||
}
|
}
|
||||||
|
|
||||||
return sequelizeTypescript.query(query, options)
|
return sequelize.query(query, options)
|
||||||
.then(results => results.length === 1)
|
.then(results => results.length === 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
function createSimilarityAttribute (col: string, value: string) {
|
||||||
doesExist
|
return Sequelize.fn(
|
||||||
|
'similarity',
|
||||||
|
|
||||||
|
searchTrigramNormalizeCol(col),
|
||||||
|
|
||||||
|
searchTrigramNormalizeValue(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWhereIdOrUUID (id: number | string) {
|
||||||
|
return validator.isInt('' + id) ? { id } : { uuid: id }
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAggregateResult (result: any) {
|
||||||
|
if (!result) return 0
|
||||||
|
|
||||||
|
const total = forceNumber(result)
|
||||||
|
if (isNaN(total)) return 0
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRowCountResult (result: any) {
|
||||||
|
if (result.length !== 0) return result[0].total
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSafeIn (sequelize: Sequelize, toEscape: (string | number)[], additionalUnescaped: string[] = []) {
|
||||||
|
return toEscape.map(t => {
|
||||||
|
return t === null
|
||||||
|
? null
|
||||||
|
: sequelize.escape('' + t)
|
||||||
|
}).concat(additionalUnescaped).join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchAttribute (sourceField?: string, targetField?: string) {
|
||||||
|
if (!sourceField) return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[targetField]: {
|
||||||
|
// FIXME: ts error
|
||||||
|
[Op.iLike as any]: `%${sourceField}%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
doesExist,
|
||||||
|
createSimilarityAttribute,
|
||||||
|
buildWhereIdOrUUID,
|
||||||
|
parseAggregateResult,
|
||||||
|
parseRowCountResult,
|
||||||
|
createSafeIn,
|
||||||
|
searchAttribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function searchTrigramNormalizeValue (value: string) {
|
||||||
|
return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchTrigramNormalizeCol (col: string) {
|
||||||
|
return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
|
||||||
}
|
}
|
||||||
|
|
39
server/models/shared/sequelize-helpers.ts
Normal file
39
server/models/shared/sequelize-helpers.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { Sequelize } from 'sequelize'
|
||||||
|
|
||||||
|
function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
|
||||||
|
if (!model.createdAt || !model.updatedAt) {
|
||||||
|
throw new Error('Miss createdAt & updatedAt attributes to model')
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now()
|
||||||
|
const createdAtTime = model.createdAt.getTime()
|
||||||
|
const updatedAtTime = model.updatedAt.getTime()
|
||||||
|
|
||||||
|
return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
|
||||||
|
if (nullable && (value === null || value === undefined)) return
|
||||||
|
|
||||||
|
if (validator(value) === false) {
|
||||||
|
throw new Error(`"${value}" is not a valid ${fieldName}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTrigramSearchIndex (indexName: string, attribute: string) {
|
||||||
|
return {
|
||||||
|
name: indexName,
|
||||||
|
// FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
|
||||||
|
fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
|
||||||
|
using: 'gin',
|
||||||
|
operator: 'gin_trgm_ops'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
throwIfNotValid,
|
||||||
|
buildTrigramSearchIndex,
|
||||||
|
isOutdated
|
||||||
|
}
|
160
server/models/shared/sort.ts
Normal file
160
server/models/shared/sort.ts
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import { literal, OrderItem, Sequelize } from 'sequelize'
|
||||||
|
|
||||||
|
// Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ]
|
||||||
|
function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
let finalField: string | ReturnType<typeof Sequelize.col>
|
||||||
|
|
||||||
|
if (field.toLowerCase() === 'match') { // Search
|
||||||
|
finalField = Sequelize.col('similarity')
|
||||||
|
} else {
|
||||||
|
finalField = field
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ [ finalField, direction ], lastSort ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAdminUsersSort (value: string): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
let finalField: string | ReturnType<typeof Sequelize.col>
|
||||||
|
|
||||||
|
if (field === 'videoQuotaUsed') { // Users list
|
||||||
|
finalField = Sequelize.col('videoQuotaUsed')
|
||||||
|
} else {
|
||||||
|
finalField = field
|
||||||
|
}
|
||||||
|
|
||||||
|
const nullPolicy = direction === 'ASC'
|
||||||
|
? 'NULLS FIRST'
|
||||||
|
: 'NULLS LAST'
|
||||||
|
|
||||||
|
// FIXME: typings
|
||||||
|
return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
if (field.toLowerCase() === 'name') {
|
||||||
|
return [ [ 'displayName', direction ], lastSort ]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSort(value, lastSort)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
if (field === 'totalReplies') {
|
||||||
|
return [
|
||||||
|
[ Sequelize.literal('"totalReplies"'), direction ],
|
||||||
|
lastSort
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSort(value, lastSort)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
if (field.toLowerCase() === 'trending') { // Sort by aggregation
|
||||||
|
return [
|
||||||
|
[ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ],
|
||||||
|
|
||||||
|
[ Sequelize.col('VideoModel.views'), direction ],
|
||||||
|
|
||||||
|
lastSort
|
||||||
|
]
|
||||||
|
} else if (field === 'publishedAt') {
|
||||||
|
return [
|
||||||
|
[ 'ScheduleVideoUpdate', 'updateAt', direction + ' NULLS LAST' ],
|
||||||
|
|
||||||
|
[ Sequelize.col('VideoModel.publishedAt'), direction ],
|
||||||
|
|
||||||
|
lastSort
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalField: string | ReturnType<typeof Sequelize.col>
|
||||||
|
|
||||||
|
// Alias
|
||||||
|
if (field.toLowerCase() === 'match') { // Search
|
||||||
|
finalField = Sequelize.col('similarity')
|
||||||
|
} else {
|
||||||
|
finalField = field
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstSort: OrderItem = typeof finalField === 'string'
|
||||||
|
? finalField.split('.').concat([ direction ]) as OrderItem
|
||||||
|
: [ finalField, direction ]
|
||||||
|
|
||||||
|
return [ firstSort, lastSort ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBlacklistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
const videoFields = new Set([ 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid' ])
|
||||||
|
|
||||||
|
if (videoFields.has(field)) {
|
||||||
|
return [
|
||||||
|
[ literal(`"Video.${field}" ${direction}`) ],
|
||||||
|
lastSort
|
||||||
|
] as OrderItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSort(value, lastSort)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInstanceFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
|
||||||
|
if (field === 'redundancyAllowed') {
|
||||||
|
return [
|
||||||
|
[ 'ActorFollowing.Server.redundancyAllowed', direction ],
|
||||||
|
lastSort
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSort(value, lastSort)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChannelSyncSort (value: string): OrderItem[] {
|
||||||
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
|
if (field.toLowerCase() === 'videochannel') {
|
||||||
|
return [
|
||||||
|
[ literal('"VideoChannel.name"'), direction ]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return [ [ field, direction ] ]
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSortDirectionAndField (value: string) {
|
||||||
|
let field: string
|
||||||
|
let direction: 'ASC' | 'DESC'
|
||||||
|
|
||||||
|
if (value.substring(0, 1) === '-') {
|
||||||
|
direction = 'DESC'
|
||||||
|
field = value.substring(1)
|
||||||
|
} else {
|
||||||
|
direction = 'ASC'
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return { direction, field }
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
buildSortDirectionAndField,
|
||||||
|
getPlaylistSort,
|
||||||
|
getSort,
|
||||||
|
getCommentSort,
|
||||||
|
getAdminUsersSort,
|
||||||
|
getVideoSort,
|
||||||
|
getBlacklistSort,
|
||||||
|
getChannelSyncSort,
|
||||||
|
getInstanceFollowsSort
|
||||||
|
}
|
68
server/models/shared/sql.ts
Normal file
68
server/models/shared/sql.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { literal, Model, ModelStatic } from 'sequelize'
|
||||||
|
import { forceNumber } from '@shared/core-utils'
|
||||||
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
|
|
||||||
|
function buildLocalAccountIdsIn () {
|
||||||
|
return literal(
|
||||||
|
'(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLocalActorIdsIn () {
|
||||||
|
return literal(
|
||||||
|
'(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBlockedAccountSQL (blockerIds: number[]) {
|
||||||
|
const blockerIdsString = blockerIds.join(', ')
|
||||||
|
|
||||||
|
return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
|
||||||
|
' UNION ' +
|
||||||
|
'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
|
||||||
|
'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
|
||||||
|
'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildServerIdsFollowedBy (actorId: any) {
|
||||||
|
const actorIdNumber = forceNumber(actorId)
|
||||||
|
|
||||||
|
return '(' +
|
||||||
|
'SELECT "actor"."serverId" FROM "actorFollow" ' +
|
||||||
|
'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
|
||||||
|
'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
|
||||||
|
')'
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSQLAttributes<M extends Model> (options: {
|
||||||
|
model: ModelStatic<M>
|
||||||
|
tableName: string
|
||||||
|
|
||||||
|
excludeAttributes?: Exclude<keyof AttributesOnly<M>, symbol>[]
|
||||||
|
aliasPrefix?: string
|
||||||
|
}) {
|
||||||
|
const { model, tableName, aliasPrefix, excludeAttributes } = options
|
||||||
|
|
||||||
|
const attributes = Object.keys(model.getAttributes()) as Exclude<keyof AttributesOnly<M>, symbol>[]
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
.filter(a => {
|
||||||
|
if (!excludeAttributes) return true
|
||||||
|
if (excludeAttributes.includes(a)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.map(a => {
|
||||||
|
return `"${tableName}"."${a}" AS "${aliasPrefix || ''}${a}"`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
buildSQLAttributes,
|
||||||
|
buildBlockedAccountSQL,
|
||||||
|
buildServerIdsFollowedBy,
|
||||||
|
buildLocalAccountIdsIn,
|
||||||
|
buildLocalActorIdsIn
|
||||||
|
}
|
|
@ -1,9 +1,15 @@
|
||||||
import { QueryTypes, Transaction } from 'sequelize'
|
import { QueryTypes, Sequelize, Transaction } from 'sequelize'
|
||||||
import { sequelizeTypescript } from '@server/initializers/database'
|
|
||||||
|
|
||||||
// Sequelize always skip the update if we only update updatedAt field
|
// Sequelize always skip the update if we only update updatedAt field
|
||||||
function setAsUpdated (table: string, id: number, transaction?: Transaction) {
|
function setAsUpdated (options: {
|
||||||
return sequelizeTypescript.query(
|
sequelize: Sequelize
|
||||||
|
table: string
|
||||||
|
id: number
|
||||||
|
transaction?: Transaction
|
||||||
|
}) {
|
||||||
|
const { sequelize, table, id, transaction } = options
|
||||||
|
|
||||||
|
return sequelize.query(
|
||||||
`UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`,
|
`UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`,
|
||||||
{
|
{
|
||||||
replacements: { table, id, updatedAt: new Date() },
|
replacements: { table, id, updatedAt: new Date() },
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
|
import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
|
||||||
import { getSort } from '@server/models/utils'
|
|
||||||
import { UserNotificationModelForApi } from '@server/types/models'
|
import { UserNotificationModelForApi } from '@server/types/models'
|
||||||
import { ActorImageType } from '@shared/models'
|
import { ActorImageType } from '@shared/models'
|
||||||
|
import { getSort } from '../../shared'
|
||||||
|
|
||||||
export interface ListNotificationsOptions {
|
export interface ListNotificationsOptions {
|
||||||
userId: number
|
userId: number
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { MNotificationSettingFormattable } from '@server/types/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
|
import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
|
||||||
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
|
import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { throwIfNotValid } from '../shared'
|
||||||
import { UserModel } from './user'
|
import { UserModel } from './user'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { AccountModel } from '../account/account'
|
||||||
import { ActorFollowModel } from '../actor/actor-follow'
|
import { ActorFollowModel } from '../actor/actor-follow'
|
||||||
import { ApplicationModel } from '../application/application'
|
import { ApplicationModel } from '../application/application'
|
||||||
import { PluginModel } from '../server/plugin'
|
import { PluginModel } from '../server/plugin'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoBlacklistModel } from '../video/video-blacklist'
|
import { VideoBlacklistModel } from '../video/video-blacklist'
|
||||||
import { VideoCommentModel } from '../video/video-comment'
|
import { VideoCommentModel } from '../video/video-comment'
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
MUserNotifSettingChannelDefault,
|
MUserNotifSettingChannelDefault,
|
||||||
MUserWithNotificationSetting
|
MUserWithNotificationSetting
|
||||||
} from '@server/types/models'
|
} from '@server/types/models'
|
||||||
|
import { forceNumber } from '@shared/core-utils'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
|
import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
|
||||||
import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models'
|
import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models'
|
||||||
|
@ -63,14 +64,13 @@ import { ActorModel } from '../actor/actor'
|
||||||
import { ActorFollowModel } from '../actor/actor-follow'
|
import { ActorFollowModel } from '../actor/actor-follow'
|
||||||
import { ActorImageModel } from '../actor/actor-image'
|
import { ActorImageModel } from '../actor/actor-image'
|
||||||
import { OAuthTokenModel } from '../oauth/oauth-token'
|
import { OAuthTokenModel } from '../oauth/oauth-token'
|
||||||
import { getAdminUsersSort, throwIfNotValid } from '../utils'
|
import { getAdminUsersSort, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from '../video/video'
|
import { VideoModel } from '../video/video'
|
||||||
import { VideoChannelModel } from '../video/video-channel'
|
import { VideoChannelModel } from '../video/video-channel'
|
||||||
import { VideoImportModel } from '../video/video-import'
|
import { VideoImportModel } from '../video/video-import'
|
||||||
import { VideoLiveModel } from '../video/video-live'
|
import { VideoLiveModel } from '../video/video-live'
|
||||||
import { VideoPlaylistModel } from '../video/video-playlist'
|
import { VideoPlaylistModel } from '../video/video-playlist'
|
||||||
import { UserNotificationSettingModel } from './user-notification-setting'
|
import { UserNotificationSettingModel } from './user-notification-setting'
|
||||||
import { forceNumber } from '@shared/core-utils'
|
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
FOR_ME_API = 'FOR_ME_API',
|
FOR_ME_API = 'FOR_ME_API',
|
||||||
|
|
|
@ -1,317 +0,0 @@
|
||||||
import { literal, Model, ModelStatic, Op, OrderItem, Sequelize } from 'sequelize'
|
|
||||||
import validator from 'validator'
|
|
||||||
import { forceNumber } from '@shared/core-utils'
|
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
|
||||||
|
|
||||||
type SortType = { sortModel: string, sortValue: string }
|
|
||||||
|
|
||||||
// Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ]
|
|
||||||
function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
let finalField: string | ReturnType<typeof Sequelize.col>
|
|
||||||
|
|
||||||
if (field.toLowerCase() === 'match') { // Search
|
|
||||||
finalField = Sequelize.col('similarity')
|
|
||||||
} else {
|
|
||||||
finalField = field
|
|
||||||
}
|
|
||||||
|
|
||||||
return [ [ finalField, direction ], lastSort ]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAdminUsersSort (value: string): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
let finalField: string | ReturnType<typeof Sequelize.col>
|
|
||||||
|
|
||||||
if (field === 'videoQuotaUsed') { // Users list
|
|
||||||
finalField = Sequelize.col('videoQuotaUsed')
|
|
||||||
} else {
|
|
||||||
finalField = field
|
|
||||||
}
|
|
||||||
|
|
||||||
const nullPolicy = direction === 'ASC'
|
|
||||||
? 'NULLS FIRST'
|
|
||||||
: 'NULLS LAST'
|
|
||||||
|
|
||||||
// FIXME: typings
|
|
||||||
return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
if (field.toLowerCase() === 'name') {
|
|
||||||
return [ [ 'displayName', direction ], lastSort ]
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSort(value, lastSort)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
if (field === 'totalReplies') {
|
|
||||||
return [
|
|
||||||
[ Sequelize.literal('"totalReplies"'), direction ],
|
|
||||||
lastSort
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSort(value, lastSort)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
if (field.toLowerCase() === 'trending') { // Sort by aggregation
|
|
||||||
return [
|
|
||||||
[ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ],
|
|
||||||
|
|
||||||
[ Sequelize.col('VideoModel.views'), direction ],
|
|
||||||
|
|
||||||
lastSort
|
|
||||||
]
|
|
||||||
} else if (field === 'publishedAt') {
|
|
||||||
return [
|
|
||||||
[ 'ScheduleVideoUpdate', 'updateAt', direction + ' NULLS LAST' ],
|
|
||||||
|
|
||||||
[ Sequelize.col('VideoModel.publishedAt'), direction ],
|
|
||||||
|
|
||||||
lastSort
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalField: string | ReturnType<typeof Sequelize.col>
|
|
||||||
|
|
||||||
// Alias
|
|
||||||
if (field.toLowerCase() === 'match') { // Search
|
|
||||||
finalField = Sequelize.col('similarity')
|
|
||||||
} else {
|
|
||||||
finalField = field
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstSort: OrderItem = typeof finalField === 'string'
|
|
||||||
? finalField.split('.').concat([ direction ]) as OrderItem
|
|
||||||
: [ finalField, direction ]
|
|
||||||
|
|
||||||
return [ firstSort, lastSort ]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const [ firstSort ] = getSort(value)
|
|
||||||
|
|
||||||
if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as OrderItem[]
|
|
||||||
return [ firstSort, lastSort ]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInstanceFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
|
|
||||||
if (field === 'redundancyAllowed') {
|
|
||||||
return [
|
|
||||||
[ 'ActorFollowing.Server.redundancyAllowed', direction ],
|
|
||||||
lastSort
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSort(value, lastSort)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getChannelSyncSort (value: string): OrderItem[] {
|
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
|
||||||
if (field.toLowerCase() === 'videochannel') {
|
|
||||||
return [
|
|
||||||
[ literal('"VideoChannel.name"'), direction ]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return [ [ field, direction ] ]
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
|
|
||||||
if (!model.createdAt || !model.updatedAt) {
|
|
||||||
throw new Error('Miss createdAt & updatedAt attributes to model')
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = Date.now()
|
|
||||||
const createdAtTime = model.createdAt.getTime()
|
|
||||||
const updatedAtTime = model.updatedAt.getTime()
|
|
||||||
|
|
||||||
return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
|
|
||||||
if (nullable && (value === null || value === undefined)) return
|
|
||||||
|
|
||||||
if (validator(value) === false) {
|
|
||||||
throw new Error(`"${value}" is not a valid ${fieldName}.`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTrigramSearchIndex (indexName: string, attribute: string) {
|
|
||||||
return {
|
|
||||||
name: indexName,
|
|
||||||
// FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
|
|
||||||
fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
|
|
||||||
using: 'gin',
|
|
||||||
operator: 'gin_trgm_ops'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSimilarityAttribute (col: string, value: string) {
|
|
||||||
return Sequelize.fn(
|
|
||||||
'similarity',
|
|
||||||
|
|
||||||
searchTrigramNormalizeCol(col),
|
|
||||||
|
|
||||||
searchTrigramNormalizeValue(value)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildBlockedAccountSQL (blockerIds: number[]) {
|
|
||||||
const blockerIdsString = blockerIds.join(', ')
|
|
||||||
|
|
||||||
return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
|
|
||||||
' UNION ' +
|
|
||||||
'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
|
|
||||||
'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
|
|
||||||
'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildServerIdsFollowedBy (actorId: any) {
|
|
||||||
const actorIdNumber = forceNumber(actorId)
|
|
||||||
|
|
||||||
return '(' +
|
|
||||||
'SELECT "actor"."serverId" FROM "actorFollow" ' +
|
|
||||||
'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
|
|
||||||
'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
|
|
||||||
')'
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildWhereIdOrUUID (id: number | string) {
|
|
||||||
return validator.isInt('' + id) ? { id } : { uuid: id }
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseAggregateResult (result: any) {
|
|
||||||
if (!result) return 0
|
|
||||||
|
|
||||||
const total = forceNumber(result)
|
|
||||||
if (isNaN(total)) return 0
|
|
||||||
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseRowCountResult (result: any) {
|
|
||||||
if (result.length !== 0) return result[0].total
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSafeIn (sequelize: Sequelize, toEscape: (string | number)[], additionalUnescaped: string[] = []) {
|
|
||||||
return toEscape.map(t => {
|
|
||||||
return t === null
|
|
||||||
? null
|
|
||||||
: sequelize.escape('' + t)
|
|
||||||
}).concat(additionalUnescaped).join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLocalAccountIdsIn () {
|
|
||||||
return literal(
|
|
||||||
'(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLocalActorIdsIn () {
|
|
||||||
return literal(
|
|
||||||
'(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildDirectionAndField (value: string) {
|
|
||||||
let field: string
|
|
||||||
let direction: 'ASC' | 'DESC'
|
|
||||||
|
|
||||||
if (value.substring(0, 1) === '-') {
|
|
||||||
direction = 'DESC'
|
|
||||||
field = value.substring(1)
|
|
||||||
} else {
|
|
||||||
direction = 'ASC'
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
return { direction, field }
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchAttribute (sourceField?: string, targetField?: string) {
|
|
||||||
if (!sourceField) return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
[targetField]: {
|
|
||||||
// FIXME: ts error
|
|
||||||
[Op.iLike as any]: `%${sourceField}%`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildSQLAttributes <M extends Model> (options: {
|
|
||||||
model: ModelStatic<M>
|
|
||||||
tableName: string
|
|
||||||
|
|
||||||
excludeAttributes?: Exclude<keyof AttributesOnly<M>, symbol>[]
|
|
||||||
aliasPrefix?: string
|
|
||||||
}) {
|
|
||||||
const { model, tableName, aliasPrefix, excludeAttributes } = options
|
|
||||||
|
|
||||||
const attributes = Object.keys(model.getAttributes()) as Exclude<keyof AttributesOnly<M>, symbol>[]
|
|
||||||
|
|
||||||
return attributes
|
|
||||||
.filter(a => {
|
|
||||||
if (!excludeAttributes) return true
|
|
||||||
if (excludeAttributes.includes(a)) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.map(a => {
|
|
||||||
return `"${tableName}"."${a}" AS "${aliasPrefix || ''}${a}"`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
buildSQLAttributes,
|
|
||||||
buildBlockedAccountSQL,
|
|
||||||
buildLocalActorIdsIn,
|
|
||||||
getPlaylistSort,
|
|
||||||
SortType,
|
|
||||||
buildLocalAccountIdsIn,
|
|
||||||
getSort,
|
|
||||||
getCommentSort,
|
|
||||||
getAdminUsersSort,
|
|
||||||
getVideoSort,
|
|
||||||
getBlacklistSort,
|
|
||||||
getChannelSyncSort,
|
|
||||||
createSimilarityAttribute,
|
|
||||||
throwIfNotValid,
|
|
||||||
buildServerIdsFollowedBy,
|
|
||||||
buildTrigramSearchIndex,
|
|
||||||
buildWhereIdOrUUID,
|
|
||||||
isOutdated,
|
|
||||||
parseAggregateResult,
|
|
||||||
getInstanceFollowsSort,
|
|
||||||
buildDirectionAndField,
|
|
||||||
createSafeIn,
|
|
||||||
searchAttribute,
|
|
||||||
parseRowCountResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function searchTrigramNormalizeValue (value: string) {
|
|
||||||
return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchTrigramNormalizeCol (col: string) {
|
|
||||||
return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Model, Sequelize, Transaction } from 'sequelize'
|
import { Model, Sequelize, Transaction } from 'sequelize'
|
||||||
import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
|
import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
|
||||||
import { createSafeIn, getCommentSort, parseRowCountResult } from '@server/models/utils'
|
|
||||||
import { ActorImageType, VideoPrivacy } from '@shared/models'
|
import { ActorImageType, VideoPrivacy } from '@shared/models'
|
||||||
|
import { createSafeIn, getCommentSort, parseRowCountResult } from '../../../shared'
|
||||||
import { VideoCommentTableAttributes } from './video-comment-table-attributes'
|
import { VideoCommentTableAttributes } from './video-comment-table-attributes'
|
||||||
|
|
||||||
export interface ListVideoCommentsOptions {
|
export interface ListVideoCommentsOptions {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Sequelize } from 'sequelize'
|
import { Sequelize } from 'sequelize'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { createSafeIn } from '@server/models/utils'
|
|
||||||
import { MUserAccountId } from '@server/types/models'
|
import { MUserAccountId } from '@server/types/models'
|
||||||
import { ActorImageType } from '@shared/models'
|
import { ActorImageType } from '@shared/models'
|
||||||
import { AbstractRunQuery } from '../../../../shared/abstract-run-query'
|
import { AbstractRunQuery } from '../../../../shared/abstract-run-query'
|
||||||
|
import { createSafeIn } from '../../../../shared'
|
||||||
import { VideoTableAttributes } from './video-table-attributes'
|
import { VideoTableAttributes } from './video-table-attributes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,11 +2,12 @@ import { Sequelize, Transaction } from 'sequelize'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { exists } from '@server/helpers/custom-validators/misc'
|
import { exists } from '@server/helpers/custom-validators/misc'
|
||||||
import { WEBSERVER } from '@server/initializers/constants'
|
import { WEBSERVER } from '@server/initializers/constants'
|
||||||
import { buildDirectionAndField, createSafeIn, parseRowCountResult } from '@server/models/utils'
|
import { buildSortDirectionAndField } from '@server/models/shared'
|
||||||
import { MUserAccountId, MUserId } from '@server/types/models'
|
import { MUserAccountId, MUserId } from '@server/types/models'
|
||||||
import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
|
|
||||||
import { AbstractRunQuery } from '../../../shared/abstract-run-query'
|
|
||||||
import { forceNumber } from '@shared/core-utils'
|
import { forceNumber } from '@shared/core-utils'
|
||||||
|
import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
|
||||||
|
import { createSafeIn, parseRowCountResult } from '../../../shared'
|
||||||
|
import { AbstractRunQuery } from '../../../shared/abstract-run-query'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -665,7 +666,7 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildOrder (value: string) {
|
private buildOrder (value: string) {
|
||||||
const { direction, field } = buildDirectionAndField(value)
|
const { direction, field } = buildSortDirectionAndField(value)
|
||||||
if (field.match(/^[a-zA-Z."]+$/) === null) throw new Error('Invalid sort column ' + field)
|
if (field.match(/^[a-zA-Z."]+$/) === null) throw new Error('Invalid sort column ' + field)
|
||||||
|
|
||||||
if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()'
|
if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()'
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { MTag } from '@server/types/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
|
import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
|
||||||
import { isVideoTagValid } from '../../helpers/custom-validators/videos'
|
import { isVideoTagValid } from '../../helpers/custom-validators/videos'
|
||||||
import { throwIfNotValid } from '../utils'
|
import { throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoTagModel } from './video-tag'
|
import { VideoTagModel } from './video-tag'
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
|
import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
|
||||||
import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
|
import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||||
import { getBlacklistSort, searchAttribute, SortType, throwIfNotValid } from '../utils'
|
import { getBlacklistSort, searchAttribute, throwIfNotValid } from '../shared'
|
||||||
import { ThumbnailModel } from './thumbnail'
|
import { ThumbnailModel } from './thumbnail'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
|
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
|
||||||
|
@ -57,7 +57,7 @@ export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlack
|
||||||
static listForApi (parameters: {
|
static listForApi (parameters: {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: SortType
|
sort: string
|
||||||
search?: string
|
search?: string
|
||||||
type?: VideoBlacklistType
|
type?: VideoBlacklistType
|
||||||
}) {
|
}) {
|
||||||
|
@ -67,7 +67,7 @@ export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlack
|
||||||
return {
|
return {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
order: getBlacklistSort(sort.sortModel, sort.sortValue)
|
order: getBlacklistSort(sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/vid
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
|
||||||
import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
|
import { buildWhereIdOrUUID, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
|
||||||
export enum ScopeNames {
|
export enum ScopeNames {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@se
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
|
import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { getSort } from '../utils'
|
import { getSort } from '../shared'
|
||||||
import { ScopeNames as VideoScopeNames, VideoModel } from './video'
|
import { ScopeNames as VideoScopeNames, VideoModel } from './video'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { VideoChannelSync, VideoChannelSyncState } from '@shared/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { UserModel } from '../user/user'
|
import { UserModel } from '../user/user'
|
||||||
import { getChannelSyncSort, throwIfNotValid } from '../utils'
|
import { getChannelSyncSort, throwIfNotValid } from '../shared'
|
||||||
import { VideoChannelModel } from './video-channel'
|
import { VideoChannelModel } from './video-channel'
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
|
|
|
@ -43,8 +43,14 @@ import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor'
|
||||||
import { ActorFollowModel } from '../actor/actor-follow'
|
import { ActorFollowModel } from '../actor/actor-follow'
|
||||||
import { ActorImageModel } from '../actor/actor-image'
|
import { ActorImageModel } from '../actor/actor-image'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { setAsUpdated } from '../shared'
|
import {
|
||||||
import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
|
buildServerIdsFollowedBy,
|
||||||
|
buildTrigramSearchIndex,
|
||||||
|
createSimilarityAttribute,
|
||||||
|
getSort,
|
||||||
|
setAsUpdated,
|
||||||
|
throwIfNotValid
|
||||||
|
} from '../shared'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoPlaylistModel } from './video-playlist'
|
import { VideoPlaylistModel } from './video-playlist'
|
||||||
|
|
||||||
|
@ -831,6 +837,6 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel
|
||||||
}
|
}
|
||||||
|
|
||||||
setAsUpdated (transaction?: Transaction) {
|
setAsUpdated (transaction?: Transaction) {
|
||||||
return setAsUpdated('videoChannel', this.id, transaction)
|
return setAsUpdated({ sequelize: this.sequelize, table: 'videoChannel', id: this.id, transaction })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ import {
|
||||||
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
|
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../utils'
|
import { buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../shared'
|
||||||
import { ListVideoCommentsOptions, VideoCommentListQueryBuilder } from './sql/comment/video-comment-list-query-builder'
|
import { ListVideoCommentsOptions, VideoCommentListQueryBuilder } from './sql/comment/video-comment-list-query-builder'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoChannelModel } from './video-channel'
|
import { VideoChannelModel } from './video-channel'
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { logger } from '@server/helpers/logger'
|
import { logger } from '@server/helpers/logger'
|
||||||
import { extractVideo } from '@server/helpers/video'
|
import { extractVideo } from '@server/helpers/video'
|
||||||
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url'
|
import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url'
|
||||||
import {
|
import {
|
||||||
getHLSPrivateFileUrl,
|
getHLSPrivateFileUrl,
|
||||||
|
@ -50,11 +51,9 @@ import {
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file'
|
import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file'
|
||||||
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
import { doesExist } from '../shared'
|
import { doesExist, parseAggregateResult, throwIfNotValid } from '../shared'
|
||||||
import { parseAggregateResult, throwIfNotValid } from '../utils'
|
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
|
||||||
import { CONFIG } from '@server/initializers/config'
|
|
||||||
|
|
||||||
export enum ScopeNames {
|
export enum ScopeNames {
|
||||||
WITH_VIDEO = 'WITH_VIDEO',
|
WITH_VIDEO = 'WITH_VIDEO',
|
||||||
|
@ -266,7 +265,7 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
|
||||||
static doesInfohashExist (infoHash: string) {
|
static doesInfohashExist (infoHash: string) {
|
||||||
const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
|
const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
|
||||||
|
|
||||||
return doesExist(query, { infoHash })
|
return doesExist(this.sequelize, query, { infoHash })
|
||||||
}
|
}
|
||||||
|
|
||||||
static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
|
static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
|
||||||
|
@ -282,14 +281,14 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
|
||||||
'LEFT JOIN "video" "hlsVideo" ON "hlsVideo"."id" = "videoStreamingPlaylist"."videoId" AND "hlsVideo"."remote" IS FALSE ' +
|
'LEFT JOIN "video" "hlsVideo" ON "hlsVideo"."id" = "videoStreamingPlaylist"."videoId" AND "hlsVideo"."remote" IS FALSE ' +
|
||||||
'WHERE "torrentFilename" = $filename AND ("hlsVideo"."id" IS NOT NULL OR "webtorrent"."id" IS NOT NULL) LIMIT 1'
|
'WHERE "torrentFilename" = $filename AND ("hlsVideo"."id" IS NOT NULL OR "webtorrent"."id" IS NOT NULL) LIMIT 1'
|
||||||
|
|
||||||
return doesExist(query, { filename })
|
return doesExist(this.sequelize, query, { filename })
|
||||||
}
|
}
|
||||||
|
|
||||||
static async doesOwnedWebTorrentVideoFileExist (filename: string) {
|
static async doesOwnedWebTorrentVideoFileExist (filename: string) {
|
||||||
const query = 'SELECT 1 FROM "videoFile" INNER JOIN "video" ON "video"."id" = "videoFile"."videoId" AND "video"."remote" IS FALSE ' +
|
const query = 'SELECT 1 FROM "videoFile" INNER JOIN "video" ON "video"."id" = "videoFile"."videoId" AND "video"."remote" IS FALSE ' +
|
||||||
`WHERE "filename" = $filename AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
|
`WHERE "filename" = $filename AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
|
||||||
|
|
||||||
return doesExist(query, { filename })
|
return doesExist(this.sequelize, query, { filename })
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByFilename (filename: string) {
|
static loadByFilename (filename: string) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help
|
||||||
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
|
import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
|
||||||
import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
|
||||||
import { UserModel } from '../user/user'
|
import { UserModel } from '../user/user'
|
||||||
import { getSort, searchAttribute, throwIfNotValid } from '../utils'
|
import { getSort, searchAttribute, throwIfNotValid } from '../shared'
|
||||||
import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
|
import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
|
||||||
import { VideoChannelSyncModel } from './video-channel-sync'
|
import { VideoChannelSyncModel } from './video-channel-sync'
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||||
import { AccountModel } from '../account/account'
|
import { AccountModel } from '../account/account'
|
||||||
import { getSort, throwIfNotValid } from '../utils'
|
import { getSort, throwIfNotValid } from '../shared'
|
||||||
import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
|
import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
|
||||||
import { VideoPlaylistModel } from './video-playlist'
|
import { VideoPlaylistModel } from './video-playlist'
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,8 @@ import { activityPubCollectionPagination } from '@server/lib/activitypub/collect
|
||||||
import { MAccountId, MChannelId } from '@server/types/models'
|
import { MAccountId, MChannelId } from '@server/types/models'
|
||||||
import { buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils'
|
import { buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils'
|
||||||
import { buildUUID, uuidToShort } from '@shared/extra-utils'
|
import { buildUUID, uuidToShort } from '@shared/extra-utils'
|
||||||
|
import { ActivityIconObject, PlaylistObject, VideoPlaylist, VideoPlaylistPrivacy, VideoPlaylistType } from '@shared/models'
|
||||||
import { AttributesOnly } from '@shared/typescript-utils'
|
import { AttributesOnly } from '@shared/typescript-utils'
|
||||||
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
|
||||||
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
|
|
||||||
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
|
||||||
import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
|
|
||||||
import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
|
|
||||||
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
|
||||||
import {
|
import {
|
||||||
isVideoPlaylistDescriptionValid,
|
isVideoPlaylistDescriptionValid,
|
||||||
|
@ -53,7 +49,6 @@ import {
|
||||||
} from '../../types/models/video/video-playlist'
|
} from '../../types/models/video/video-playlist'
|
||||||
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { setAsUpdated } from '../shared'
|
|
||||||
import {
|
import {
|
||||||
buildServerIdsFollowedBy,
|
buildServerIdsFollowedBy,
|
||||||
buildTrigramSearchIndex,
|
buildTrigramSearchIndex,
|
||||||
|
@ -61,8 +56,9 @@ import {
|
||||||
createSimilarityAttribute,
|
createSimilarityAttribute,
|
||||||
getPlaylistSort,
|
getPlaylistSort,
|
||||||
isOutdated,
|
isOutdated,
|
||||||
|
setAsUpdated,
|
||||||
throwIfNotValid
|
throwIfNotValid
|
||||||
} from '../utils'
|
} from '../shared'
|
||||||
import { ThumbnailModel } from './thumbnail'
|
import { ThumbnailModel } from './thumbnail'
|
||||||
import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
|
import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
|
||||||
import { VideoPlaylistElementModel } from './video-playlist-element'
|
import { VideoPlaylistElementModel } from './video-playlist-element'
|
||||||
|
@ -641,7 +637,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
|
||||||
}
|
}
|
||||||
|
|
||||||
setAsRefreshed () {
|
setAsRefreshed () {
|
||||||
return setAsUpdated('videoPlaylist', this.id)
|
return setAsUpdated({ sequelize: this.sequelize, table: 'videoPlaylist', id: this.id })
|
||||||
}
|
}
|
||||||
|
|
||||||
setVideosLength (videosLength: number) {
|
setVideosLength (videosLength: number) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
|
||||||
import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models'
|
import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models'
|
||||||
import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
|
import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
|
||||||
import { ActorModel } from '../actor/actor'
|
import { ActorModel } from '../actor/actor'
|
||||||
import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
|
import { buildLocalActorIdsIn, throwIfNotValid } from '../shared'
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
|
|
|
@ -37,8 +37,7 @@ import {
|
||||||
WEBSERVER
|
WEBSERVER
|
||||||
} from '../../initializers/constants'
|
} from '../../initializers/constants'
|
||||||
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
import { doesExist } from '../shared'
|
import { doesExist, throwIfNotValid } from '../shared'
|
||||||
import { throwIfNotValid } from '../utils'
|
|
||||||
import { VideoModel } from './video'
|
import { VideoModel } from './video'
|
||||||
|
|
||||||
@Table({
|
@Table({
|
||||||
|
@ -138,7 +137,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
|
||||||
static doesInfohashExist (infoHash: string) {
|
static doesInfohashExist (infoHash: string) {
|
||||||
const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1'
|
const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1'
|
||||||
|
|
||||||
return doesExist(query, { infoHash })
|
return doesExist(this.sequelize, query, { infoHash })
|
||||||
}
|
}
|
||||||
|
|
||||||
static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) {
|
static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) {
|
||||||
|
@ -237,7 +236,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
|
||||||
`AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` +
|
`AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` +
|
||||||
`AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
|
`AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
|
||||||
|
|
||||||
return doesExist(query, { videoUUID })
|
return doesExist(this.sequelize, query, { videoUUID })
|
||||||
}
|
}
|
||||||
|
|
||||||
assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) {
|
assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFil
|
||||||
import { VideoPathManager } from '@server/lib/video-path-manager'
|
import { VideoPathManager } from '@server/lib/video-path-manager'
|
||||||
import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
|
import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { ModelCache } from '@server/models/model-cache'
|
import { ModelCache } from '@server/models/shared/model-cache'
|
||||||
import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
|
import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
|
||||||
import { ffprobePromise, getAudioStream, hasAudioStream, uuidToShort } from '@shared/extra-utils'
|
import { ffprobePromise, getAudioStream, hasAudioStream, uuidToShort } from '@shared/extra-utils'
|
||||||
import {
|
import {
|
||||||
|
@ -103,10 +103,9 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy'
|
||||||
import { ServerModel } from '../server/server'
|
import { ServerModel } from '../server/server'
|
||||||
import { TrackerModel } from '../server/tracker'
|
import { TrackerModel } from '../server/tracker'
|
||||||
import { VideoTrackerModel } from '../server/video-tracker'
|
import { VideoTrackerModel } from '../server/video-tracker'
|
||||||
import { setAsUpdated } from '../shared'
|
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, setAsUpdated, throwIfNotValid } from '../shared'
|
||||||
import { UserModel } from '../user/user'
|
import { UserModel } from '../user/user'
|
||||||
import { UserVideoHistoryModel } from '../user/user-video-history'
|
import { UserVideoHistoryModel } from '../user/user-video-history'
|
||||||
import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
|
|
||||||
import { VideoViewModel } from '../view/video-view'
|
import { VideoViewModel } from '../view/video-view'
|
||||||
import {
|
import {
|
||||||
videoFilesModelToFormattedJSON,
|
videoFilesModelToFormattedJSON,
|
||||||
|
@ -1871,7 +1870,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setAsRefreshed (transaction?: Transaction) {
|
setAsRefreshed (transaction?: Transaction) {
|
||||||
return setAsUpdated('video', this.id, transaction)
|
return setAsUpdated({ sequelize: this.sequelize, table: 'video', id: this.id, transaction })
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
Loading…
Reference in New Issue
Block a user