aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--server/middlewares/sort.ts23
-rw-r--r--server/models/abuse/abuse-message.ts2
-rw-r--r--server/models/abuse/abuse.ts2
-rw-r--r--server/models/abuse/sql/abuse-query-builder.ts4
-rw-r--r--server/models/account/account-blocklist.ts2
-rw-r--r--server/models/account/account-video-rate.ts2
-rw-r--r--server/models/account/account.ts4
-rw-r--r--server/models/actor/actor-follow.ts4
-rw-r--r--server/models/actor/actor-image.ts2
-rw-r--r--server/models/actor/actor.ts4
-rw-r--r--server/models/actor/sql/instance-list-followers-query-builder.ts2
-rw-r--r--server/models/actor/sql/instance-list-following-query-builder.ts2
-rw-r--r--server/models/actor/sql/shared/instance-list-follows-query-builder.ts2
-rw-r--r--server/models/redundancy/video-redundancy.ts2
-rw-r--r--server/models/server/plugin.ts2
-rw-r--r--server/models/server/server-blocklist.ts2
-rw-r--r--server/models/server/server.ts2
-rw-r--r--server/models/shared/index.ts4
-rw-r--r--server/models/shared/model-cache.ts (renamed from server/models/model-cache.ts)0
-rw-r--r--server/models/shared/query.ts75
-rw-r--r--server/models/shared/sequelize-helpers.ts39
-rw-r--r--server/models/shared/sort.ts160
-rw-r--r--server/models/shared/sql.ts68
-rw-r--r--server/models/shared/update.ts14
-rw-r--r--server/models/user/sql/user-notitication-list-query-builder.ts2
-rw-r--r--server/models/user/user-notification-setting.ts2
-rw-r--r--server/models/user/user-notification.ts2
-rw-r--r--server/models/user/user.ts4
-rw-r--r--server/models/utils.ts317
-rw-r--r--server/models/video/sql/comment/video-comment-list-query-builder.ts2
-rw-r--r--server/models/video/sql/video/shared/abstract-video-query-builder.ts2
-rw-r--r--server/models/video/sql/video/videos-id-list-query-builder.ts7
-rw-r--r--server/models/video/tag.ts2
-rw-r--r--server/models/video/video-blacklist.ts6
-rw-r--r--server/models/video/video-caption.ts2
-rw-r--r--server/models/video/video-change-ownership.ts2
-rw-r--r--server/models/video/video-channel-sync.ts2
-rw-r--r--server/models/video/video-channel.ts12
-rw-r--r--server/models/video/video-comment.ts2
-rw-r--r--server/models/video/video-file.ts11
-rw-r--r--server/models/video/video-import.ts2
-rw-r--r--server/models/video/video-playlist-element.ts2
-rw-r--r--server/models/video/video-playlist.ts12
-rw-r--r--server/models/video/video-share.ts2
-rw-r--r--server/models/video/video-streaming-playlist.ts7
-rw-r--r--server/models/video/video.ts7
46 files changed, 418 insertions, 414 deletions
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 458895898..77a532276 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -1,5 +1,4 @@
1import express from 'express' 1import express from 'express'
2import { SortType } from '../models/utils'
3 2
4const setDefaultSort = setDefaultSortFactory('-createdAt') 3const setDefaultSort = setDefaultSortFactory('-createdAt')
5const setDefaultVideosSort = setDefaultSortFactory('-publishedAt') 4const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
@@ -7,27 +6,7 @@ const setDefaultVideosSort = setDefaultSortFactory('-publishedAt')
7const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name') 6const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
8 7
9const setDefaultSearchSort = setDefaultSortFactory('-match') 8const setDefaultSearchSort = setDefaultSortFactory('-match')
10 9const setBlacklistSort = setDefaultSortFactory('-createdAt')
11function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
12 const newSort: SortType = { sortModel: undefined, sortValue: '' }
13
14 if (!req.query.sort) req.query.sort = '-createdAt'
15
16 // Set model we want to sort onto
17 if (req.query.sort === '-createdAt' || req.query.sort === 'createdAt' ||
18 req.query.sort === '-id' || req.query.sort === 'id') {
19 // If we want to sort onto the BlacklistedVideos relation, we won't specify it in the query parameter...
20 newSort.sortModel = undefined
21 } else {
22 newSort.sortModel = 'Video'
23 }
24
25 newSort.sortValue = req.query.sort
26
27 req.query.sort = newSort
28
29 return next()
30}
31 10
32// --------------------------------------------------------------------------- 11// ---------------------------------------------------------------------------
33 12
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts
index 20008768b..14a5bffa2 100644
--- a/server/models/abuse/abuse-message.ts
+++ b/server/models/abuse/abuse-message.ts
@@ -5,7 +5,7 @@ import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models'
5import { AbuseMessage } from '@shared/models' 5import { AbuseMessage } from '@shared/models'
6import { AttributesOnly } from '@shared/typescript-utils' 6import { AttributesOnly } from '@shared/typescript-utils'
7import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' 7import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
8import { getSort, throwIfNotValid } from '../utils' 8import { getSort, throwIfNotValid } from '../shared'
9import { AbuseModel } from './abuse' 9import { AbuseModel } from './abuse'
10 10
11@Table({ 11@Table({
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index 5610077c9..4ce40bf2f 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -34,7 +34,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
34import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' 34import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
35import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models' 35import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
37import { getSort, throwIfNotValid } from '../utils' 37import { getSort, throwIfNotValid } from '../shared'
38import { ThumbnailModel } from '../video/thumbnail' 38import { ThumbnailModel } from '../video/thumbnail'
39import { ScopeNames as VideoScopeNames, VideoModel } from '../video/video' 39import { ScopeNames as VideoScopeNames, VideoModel } from '../video/video'
40import { VideoBlacklistModel } from '../video/video-blacklist' 40import { VideoBlacklistModel } from '../video/video-blacklist'
diff --git a/server/models/abuse/sql/abuse-query-builder.ts b/server/models/abuse/sql/abuse-query-builder.ts
index 854f0cda8..282d4541a 100644
--- a/server/models/abuse/sql/abuse-query-builder.ts
+++ b/server/models/abuse/sql/abuse-query-builder.ts
@@ -2,7 +2,7 @@
2import { exists } from '@server/helpers/custom-validators/misc' 2import { exists } from '@server/helpers/custom-validators/misc'
3import { forceNumber } from '@shared/core-utils' 3import { forceNumber } from '@shared/core-utils'
4import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models' 4import { AbuseFilter, AbuseState, AbuseVideoIs } from '@shared/models'
5import { buildBlockedAccountSQL, buildDirectionAndField } from '../../utils' 5import { buildBlockedAccountSQL, buildSortDirectionAndField } from '../../shared'
6 6
7export type BuildAbusesQueryOptions = { 7export type BuildAbusesQueryOptions = {
8 start: number 8 start: number
@@ -157,7 +157,7 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
157} 157}
158 158
159function buildAbuseOrder (value: string) { 159function buildAbuseOrder (value: string) {
160 const { direction, field } = buildDirectionAndField(value) 160 const { direction, field } = buildSortDirectionAndField(value)
161 161
162 return `ORDER BY "abuse"."${field}" ${direction}` 162 return `ORDER BY "abuse"."${field}" ${direction}`
163} 163}
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index 377249b38..f6212ff6e 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -6,7 +6,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
6import { AccountBlock } from '../../../shared/models' 6import { AccountBlock } from '../../../shared/models'
7import { ActorModel } from '../actor/actor' 7import { ActorModel } from '../actor/actor'
8import { ServerModel } from '../server/server' 8import { ServerModel } from '../server/server'
9import { createSafeIn, getSort, searchAttribute } from '../utils' 9import { createSafeIn, getSort, searchAttribute } from '../shared'
10import { AccountModel } from './account' 10import { AccountModel } from './account'
11 11
12@Table({ 12@Table({
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index 7afc907da..9e7ef4394 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -11,7 +11,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
11import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 11import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
12import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' 12import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
13import { ActorModel } from '../actor/actor' 13import { ActorModel } from '../actor/actor'
14import { getSort, throwIfNotValid } from '../utils' 14import { getSort, throwIfNotValid } from '../shared'
15import { VideoModel } from '../video/video' 15import { VideoModel } from '../video/video'
16import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' 16import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
17import { AccountModel } from './account' 17import { AccountModel } from './account'
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index fca1b7b6d..dc989417b 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -16,7 +16,7 @@ import {
16 Table, 16 Table,
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { ModelCache } from '@server/models/model-cache' 19import { ModelCache } from '@server/models/shared/model-cache'
20import { AttributesOnly } from '@shared/typescript-utils' 20import { AttributesOnly } from '@shared/typescript-utils'
21import { Account, AccountSummary } from '../../../shared/models/actors' 21import { Account, AccountSummary } from '../../../shared/models/actors'
22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' 22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
@@ -38,7 +38,7 @@ import { ApplicationModel } from '../application/application'
38import { ServerModel } from '../server/server' 38import { ServerModel } from '../server/server'
39import { ServerBlocklistModel } from '../server/server-blocklist' 39import { ServerBlocklistModel } from '../server/server-blocklist'
40import { UserModel } from '../user/user' 40import { UserModel } from '../user/user'
41import { buildSQLAttributes, getSort, throwIfNotValid } from '../utils' 41import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared'
42import { VideoModel } from '../video/video' 42import { VideoModel } from '../video/video'
43import { VideoChannelModel } from '../video/video-channel' 43import { VideoChannelModel } from '../video/video-channel'
44import { VideoCommentModel } from '../video/video-comment' 44import { VideoCommentModel } from '../video/video-comment'
diff --git a/server/models/actor/actor-follow.ts b/server/models/actor/actor-follow.ts
index a849817d5..32e5d78b0 100644
--- a/server/models/actor/actor-follow.ts
+++ b/server/models/actor/actor-follow.ts
@@ -38,7 +38,7 @@ import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAM
38import { AccountModel } from '../account/account' 38import { AccountModel } from '../account/account'
39import { ServerModel } from '../server/server' 39import { ServerModel } from '../server/server'
40import { doesExist } from '../shared/query' 40import { doesExist } from '../shared/query'
41import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../utils' 41import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared'
42import { VideoChannelModel } from '../video/video-channel' 42import { VideoChannelModel } from '../video/video-channel'
43import { ActorModel, unusedActorAttributesForAPI } from './actor' 43import { ActorModel, unusedActorAttributesForAPI } from './actor'
44import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder' 44import { InstanceListFollowersQueryBuilder, ListFollowersOptions } from './sql/instance-list-followers-query-builder'
@@ -225,7 +225,7 @@ export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowMo
225 `WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId AND "state" = 'accepted' ` + 225 `WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId AND "state" = 'accepted' ` +
226 `LIMIT 1` 226 `LIMIT 1`
227 227
228 return doesExist(query, { actorId, followerActorId }) 228 return doesExist(this.sequelize, query, { actorId, followerActorId })
229 } 229 }
230 230
231 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Promise<MActorFollowActorsDefault> { 231 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Promise<MActorFollowActorsDefault> {
diff --git a/server/models/actor/actor-image.ts b/server/models/actor/actor-image.ts
index 83898cd38..9c34a0101 100644
--- a/server/models/actor/actor-image.ts
+++ b/server/models/actor/actor-image.ts
@@ -22,7 +22,7 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp
22import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
23import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
24import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants' 24import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants'
25import { buildSQLAttributes, throwIfNotValid } from '../utils' 25import { buildSQLAttributes, throwIfNotValid } from '../shared'
26import { ActorModel } from './actor' 26import { ActorModel } from './actor'
27 27
28@Table({ 28@Table({
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts
index a62e6030a..1432e8757 100644
--- a/server/models/actor/actor.ts
+++ b/server/models/actor/actor.ts
@@ -17,7 +17,7 @@ import {
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { activityPubContextify } from '@server/lib/activitypub/context' 18import { activityPubContextify } from '@server/lib/activitypub/context'
19import { getBiggestActorImage } from '@server/lib/actor-image' 19import { getBiggestActorImage } from '@server/lib/actor-image'
20import { ModelCache } from '@server/models/model-cache' 20import { ModelCache } from '@server/models/shared/model-cache'
21import { forceNumber, getLowercaseExtension } from '@shared/core-utils' 21import { forceNumber, getLowercaseExtension } from '@shared/core-utils'
22import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models' 22import { ActivityIconObject, ActivityPubActorType, ActorImageType } from '@shared/models'
23import { AttributesOnly } from '@shared/typescript-utils' 23import { AttributesOnly } from '@shared/typescript-utils'
@@ -55,7 +55,7 @@ import {
55import { AccountModel } from '../account/account' 55import { AccountModel } from '../account/account'
56import { getServerActor } from '../application/application' 56import { getServerActor } from '../application/application'
57import { ServerModel } from '../server/server' 57import { ServerModel } from '../server/server'
58import { buildSQLAttributes, isOutdated, throwIfNotValid } from '../utils' 58import { buildSQLAttributes, isOutdated, throwIfNotValid } from '../shared'
59import { VideoModel } from '../video/video' 59import { VideoModel } from '../video/video'
60import { VideoChannelModel } from '../video/video-channel' 60import { VideoChannelModel } from '../video/video-channel'
61import { ActorFollowModel } from './actor-follow' 61import { ActorFollowModel } from './actor-follow'
diff --git a/server/models/actor/sql/instance-list-followers-query-builder.ts b/server/models/actor/sql/instance-list-followers-query-builder.ts
index 4a17a8f11..34ce29b5d 100644
--- a/server/models/actor/sql/instance-list-followers-query-builder.ts
+++ b/server/models/actor/sql/instance-list-followers-query-builder.ts
@@ -1,8 +1,8 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize } from 'sequelize'
2import { ModelBuilder } from '@server/models/shared' 2import { ModelBuilder } from '@server/models/shared'
3import { parseRowCountResult } from '@server/models/utils'
4import { MActorFollowActorsDefault } from '@server/types/models' 3import { MActorFollowActorsDefault } from '@server/types/models'
5import { ActivityPubActorType, FollowState } from '@shared/models' 4import { ActivityPubActorType, FollowState } from '@shared/models'
5import { parseRowCountResult } from '../../shared'
6import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder' 6import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
7 7
8export interface ListFollowersOptions { 8export interface ListFollowersOptions {
diff --git a/server/models/actor/sql/instance-list-following-query-builder.ts b/server/models/actor/sql/instance-list-following-query-builder.ts
index 880170b85..77b4e3dce 100644
--- a/server/models/actor/sql/instance-list-following-query-builder.ts
+++ b/server/models/actor/sql/instance-list-following-query-builder.ts
@@ -1,8 +1,8 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize } from 'sequelize'
2import { ModelBuilder } from '@server/models/shared' 2import { ModelBuilder } from '@server/models/shared'
3import { parseRowCountResult } from '@server/models/utils'
4import { MActorFollowActorsDefault } from '@server/types/models' 3import { MActorFollowActorsDefault } from '@server/types/models'
5import { ActivityPubActorType, FollowState } from '@shared/models' 4import { ActivityPubActorType, FollowState } from '@shared/models'
5import { parseRowCountResult } from '../../shared'
6import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder' 6import { InstanceListFollowsQueryBuilder } from './shared/instance-list-follows-query-builder'
7 7
8export interface ListFollowingOptions { 8export interface ListFollowingOptions {
diff --git a/server/models/actor/sql/shared/instance-list-follows-query-builder.ts b/server/models/actor/sql/shared/instance-list-follows-query-builder.ts
index 1d70fbe70..d9593e48b 100644
--- a/server/models/actor/sql/shared/instance-list-follows-query-builder.ts
+++ b/server/models/actor/sql/shared/instance-list-follows-query-builder.ts
@@ -1,7 +1,7 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize } from 'sequelize'
2import { AbstractRunQuery } from '@server/models/shared' 2import { AbstractRunQuery } from '@server/models/shared'
3import { getInstanceFollowsSort } from '@server/models/utils'
4import { ActorImageType } from '@shared/models' 3import { ActorImageType } from '@shared/models'
4import { getInstanceFollowsSort } from '../../../shared'
5import { ActorFollowTableAttributes } from './actor-follow-table-attributes' 5import { ActorFollowTableAttributes } from './actor-follow-table-attributes'
6 6
7type BaseOptions = { 7type BaseOptions = {
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 15909d5f3..c2a72b71f 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -34,7 +34,7 @@ import { CONFIG } from '../../initializers/config'
34import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' 34import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
35import { ActorModel } from '../actor/actor' 35import { ActorModel } from '../actor/actor'
36import { ServerModel } from '../server/server' 36import { ServerModel } from '../server/server'
37import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' 37import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared'
38import { ScheduleVideoUpdateModel } from '../video/schedule-video-update' 38import { ScheduleVideoUpdateModel } from '../video/schedule-video-update'
39import { VideoModel } from '../video/video' 39import { VideoModel } from '../video/video'
40import { VideoChannelModel } from '../video/video-channel' 40import { VideoChannelModel } from '../video/video-channel'
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index 71c205ffa..9948c9f7a 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -11,7 +11,7 @@ import {
11 isPluginStableVersionValid, 11 isPluginStableVersionValid,
12 isPluginTypeValid 12 isPluginTypeValid
13} from '../../helpers/custom-validators/plugins' 13} from '../../helpers/custom-validators/plugins'
14import { getSort, throwIfNotValid } from '../utils' 14import { getSort, throwIfNotValid } from '../shared'
15 15
16@DefaultScope(() => ({ 16@DefaultScope(() => ({
17 attributes: { 17 attributes: {
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 9752dfbc3..3d755fe4a 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -4,7 +4,7 @@ import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormat
4import { ServerBlock } from '@shared/models' 4import { ServerBlock } from '@shared/models'
5import { AttributesOnly } from '@shared/typescript-utils' 5import { AttributesOnly } from '@shared/typescript-utils'
6import { AccountModel } from '../account/account' 6import { AccountModel } from '../account/account'
7import { createSafeIn, getSort, searchAttribute } from '../utils' 7import { createSafeIn, getSort, searchAttribute } from '../shared'
8import { ServerModel } from './server' 8import { ServerModel } from './server'
9 9
10enum ScopeNames { 10enum ScopeNames {
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index f6cb40f60..a5e05f460 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -4,7 +4,7 @@ import { MServer, MServerFormattable } from '@server/types/models/server'
4import { AttributesOnly } from '@shared/typescript-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { isHostValid } from '../../helpers/custom-validators/servers' 5import { isHostValid } from '../../helpers/custom-validators/servers'
6import { ActorModel } from '../actor/actor' 6import { ActorModel } from '../actor/actor'
7import { buildSQLAttributes, throwIfNotValid } from '../utils' 7import { buildSQLAttributes, throwIfNotValid } from '../shared'
8import { ServerBlocklistModel } from './server-blocklist' 8import { ServerBlocklistModel } from './server-blocklist'
9 9
10@Table({ 10@Table({
diff --git a/server/models/shared/index.ts b/server/models/shared/index.ts
index 04528929c..5a7621e4d 100644
--- a/server/models/shared/index.ts
+++ b/server/models/shared/index.ts
@@ -1,4 +1,8 @@
1export * from './abstract-run-query' 1export * from './abstract-run-query'
2export * from './model-builder' 2export * from './model-builder'
3export * from './model-cache'
3export * from './query' 4export * from './query'
5export * from './sequelize-helpers'
6export * from './sort'
7export * from './sql'
4export * from './update' 8export * from './update'
diff --git a/server/models/model-cache.ts b/server/models/shared/model-cache.ts
index 3651267e7..3651267e7 100644
--- a/server/models/model-cache.ts
+++ b/server/models/shared/model-cache.ts
diff --git a/server/models/shared/query.ts b/server/models/shared/query.ts
index 036cc13c6..934acc21f 100644
--- a/server/models/shared/query.ts
+++ b/server/models/shared/query.ts
@@ -1,17 +1,82 @@
1import { BindOrReplacements, QueryTypes } from 'sequelize' 1import { BindOrReplacements, Op, QueryTypes, Sequelize } from 'sequelize'
2import { sequelizeTypescript } from '@server/initializers/database' 2import validator from 'validator'
3import { forceNumber } from '@shared/core-utils'
3 4
4function doesExist (query: string, bind?: BindOrReplacements) { 5function doesExist (sequelize: Sequelize, query: string, bind?: BindOrReplacements) {
5 const options = { 6 const options = {
6 type: QueryTypes.SELECT as QueryTypes.SELECT, 7 type: QueryTypes.SELECT as QueryTypes.SELECT,
7 bind, 8 bind,
8 raw: true 9 raw: true
9 } 10 }
10 11
11 return sequelizeTypescript.query(query, options) 12 return sequelize.query(query, options)
12 .then(results => results.length === 1) 13 .then(results => results.length === 1)
13} 14}
14 15
16function createSimilarityAttribute (col: string, value: string) {
17 return Sequelize.fn(
18 'similarity',
19
20 searchTrigramNormalizeCol(col),
21
22 searchTrigramNormalizeValue(value)
23 )
24}
25
26function buildWhereIdOrUUID (id: number | string) {
27 return validator.isInt('' + id) ? { id } : { uuid: id }
28}
29
30function parseAggregateResult (result: any) {
31 if (!result) return 0
32
33 const total = forceNumber(result)
34 if (isNaN(total)) return 0
35
36 return total
37}
38
39function parseRowCountResult (result: any) {
40 if (result.length !== 0) return result[0].total
41
42 return 0
43}
44
45function createSafeIn (sequelize: Sequelize, toEscape: (string | number)[], additionalUnescaped: string[] = []) {
46 return toEscape.map(t => {
47 return t === null
48 ? null
49 : sequelize.escape('' + t)
50 }).concat(additionalUnescaped).join(', ')
51}
52
53function searchAttribute (sourceField?: string, targetField?: string) {
54 if (!sourceField) return {}
55
56 return {
57 [targetField]: {
58 // FIXME: ts error
59 [Op.iLike as any]: `%${sourceField}%`
60 }
61 }
62}
63
15export { 64export {
16 doesExist 65 doesExist,
66 createSimilarityAttribute,
67 buildWhereIdOrUUID,
68 parseAggregateResult,
69 parseRowCountResult,
70 createSafeIn,
71 searchAttribute
72}
73
74// ---------------------------------------------------------------------------
75
76function searchTrigramNormalizeValue (value: string) {
77 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
78}
79
80function searchTrigramNormalizeCol (col: string) {
81 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
17} 82}
diff --git a/server/models/shared/sequelize-helpers.ts b/server/models/shared/sequelize-helpers.ts
new file mode 100644
index 000000000..7af8471dc
--- /dev/null
+++ b/server/models/shared/sequelize-helpers.ts
@@ -0,0 +1,39 @@
1import { Sequelize } from 'sequelize'
2
3function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
4 if (!model.createdAt || !model.updatedAt) {
5 throw new Error('Miss createdAt & updatedAt attributes to model')
6 }
7
8 const now = Date.now()
9 const createdAtTime = model.createdAt.getTime()
10 const updatedAtTime = model.updatedAt.getTime()
11
12 return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
13}
14
15function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
16 if (nullable && (value === null || value === undefined)) return
17
18 if (validator(value) === false) {
19 throw new Error(`"${value}" is not a valid ${fieldName}.`)
20 }
21}
22
23function buildTrigramSearchIndex (indexName: string, attribute: string) {
24 return {
25 name: indexName,
26 // FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
27 fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
28 using: 'gin',
29 operator: 'gin_trgm_ops'
30 }
31}
32
33// ---------------------------------------------------------------------------
34
35export {
36 throwIfNotValid,
37 buildTrigramSearchIndex,
38 isOutdated
39}
diff --git a/server/models/shared/sort.ts b/server/models/shared/sort.ts
new file mode 100644
index 000000000..77e84dcf4
--- /dev/null
+++ b/server/models/shared/sort.ts
@@ -0,0 +1,160 @@
1import { literal, OrderItem, Sequelize } from 'sequelize'
2
3// Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ]
4function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
5 const { direction, field } = buildSortDirectionAndField(value)
6
7 let finalField: string | ReturnType<typeof Sequelize.col>
8
9 if (field.toLowerCase() === 'match') { // Search
10 finalField = Sequelize.col('similarity')
11 } else {
12 finalField = field
13 }
14
15 return [ [ finalField, direction ], lastSort ]
16}
17
18function getAdminUsersSort (value: string): OrderItem[] {
19 const { direction, field } = buildSortDirectionAndField(value)
20
21 let finalField: string | ReturnType<typeof Sequelize.col>
22
23 if (field === 'videoQuotaUsed') { // Users list
24 finalField = Sequelize.col('videoQuotaUsed')
25 } else {
26 finalField = field
27 }
28
29 const nullPolicy = direction === 'ASC'
30 ? 'NULLS FIRST'
31 : 'NULLS LAST'
32
33 // FIXME: typings
34 return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ]
35}
36
37function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
38 const { direction, field } = buildSortDirectionAndField(value)
39
40 if (field.toLowerCase() === 'name') {
41 return [ [ 'displayName', direction ], lastSort ]
42 }
43
44 return getSort(value, lastSort)
45}
46
47function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
48 const { direction, field } = buildSortDirectionAndField(value)
49
50 if (field === 'totalReplies') {
51 return [
52 [ Sequelize.literal('"totalReplies"'), direction ],
53 lastSort
54 ]
55 }
56
57 return getSort(value, lastSort)
58}
59
60function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
61 const { direction, field } = buildSortDirectionAndField(value)
62
63 if (field.toLowerCase() === 'trending') { // Sort by aggregation
64 return [
65 [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ],
66
67 [ Sequelize.col('VideoModel.views'), direction ],
68
69 lastSort
70 ]
71 } else if (field === 'publishedAt') {
72 return [
73 [ 'ScheduleVideoUpdate', 'updateAt', direction + ' NULLS LAST' ],
74
75 [ Sequelize.col('VideoModel.publishedAt'), direction ],
76
77 lastSort
78 ]
79 }
80
81 let finalField: string | ReturnType<typeof Sequelize.col>
82
83 // Alias
84 if (field.toLowerCase() === 'match') { // Search
85 finalField = Sequelize.col('similarity')
86 } else {
87 finalField = field
88 }
89
90 const firstSort: OrderItem = typeof finalField === 'string'
91 ? finalField.split('.').concat([ direction ]) as OrderItem
92 : [ finalField, direction ]
93
94 return [ firstSort, lastSort ]
95}
96
97function getBlacklistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
98 const { direction, field } = buildSortDirectionAndField(value)
99
100 const videoFields = new Set([ 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid' ])
101
102 if (videoFields.has(field)) {
103 return [
104 [ literal(`"Video.${field}" ${direction}`) ],
105 lastSort
106 ] as OrderItem[]
107 }
108
109 return getSort(value, lastSort)
110}
111
112function getInstanceFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
113 const { direction, field } = buildSortDirectionAndField(value)
114
115 if (field === 'redundancyAllowed') {
116 return [
117 [ 'ActorFollowing.Server.redundancyAllowed', direction ],
118 lastSort
119 ]
120 }
121
122 return getSort(value, lastSort)
123}
124
125function getChannelSyncSort (value: string): OrderItem[] {
126 const { direction, field } = buildSortDirectionAndField(value)
127 if (field.toLowerCase() === 'videochannel') {
128 return [
129 [ literal('"VideoChannel.name"'), direction ]
130 ]
131 }
132 return [ [ field, direction ] ]
133}
134
135function buildSortDirectionAndField (value: string) {
136 let field: string
137 let direction: 'ASC' | 'DESC'
138
139 if (value.substring(0, 1) === '-') {
140 direction = 'DESC'
141 field = value.substring(1)
142 } else {
143 direction = 'ASC'
144 field = value
145 }
146
147 return { direction, field }
148}
149
150export {
151 buildSortDirectionAndField,
152 getPlaylistSort,
153 getSort,
154 getCommentSort,
155 getAdminUsersSort,
156 getVideoSort,
157 getBlacklistSort,
158 getChannelSyncSort,
159 getInstanceFollowsSort
160}
diff --git a/server/models/shared/sql.ts b/server/models/shared/sql.ts
new file mode 100644
index 000000000..5aaeb49f0
--- /dev/null
+++ b/server/models/shared/sql.ts
@@ -0,0 +1,68 @@
1import { literal, Model, ModelStatic } from 'sequelize'
2import { forceNumber } from '@shared/core-utils'
3import { AttributesOnly } from '@shared/typescript-utils'
4
5function buildLocalAccountIdsIn () {
6 return literal(
7 '(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
8 )
9}
10
11function buildLocalActorIdsIn () {
12 return literal(
13 '(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
14 )
15}
16
17function buildBlockedAccountSQL (blockerIds: number[]) {
18 const blockerIdsString = blockerIds.join(', ')
19
20 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
21 ' UNION ' +
22 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
23 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
24 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
25}
26
27function buildServerIdsFollowedBy (actorId: any) {
28 const actorIdNumber = forceNumber(actorId)
29
30 return '(' +
31 'SELECT "actor"."serverId" FROM "actorFollow" ' +
32 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
33 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
34 ')'
35}
36
37function buildSQLAttributes<M extends Model> (options: {
38 model: ModelStatic<M>
39 tableName: string
40
41 excludeAttributes?: Exclude<keyof AttributesOnly<M>, symbol>[]
42 aliasPrefix?: string
43}) {
44 const { model, tableName, aliasPrefix, excludeAttributes } = options
45
46 const attributes = Object.keys(model.getAttributes()) as Exclude<keyof AttributesOnly<M>, symbol>[]
47
48 return attributes
49 .filter(a => {
50 if (!excludeAttributes) return true
51 if (excludeAttributes.includes(a)) return false
52
53 return true
54 })
55 .map(a => {
56 return `"${tableName}"."${a}" AS "${aliasPrefix || ''}${a}"`
57 })
58}
59
60// ---------------------------------------------------------------------------
61
62export {
63 buildSQLAttributes,
64 buildBlockedAccountSQL,
65 buildServerIdsFollowedBy,
66 buildLocalAccountIdsIn,
67 buildLocalActorIdsIn
68}
diff --git a/server/models/shared/update.ts b/server/models/shared/update.ts
index d338211e3..d02c4535d 100644
--- a/server/models/shared/update.ts
+++ b/server/models/shared/update.ts
@@ -1,9 +1,15 @@
1import { QueryTypes, Transaction } from 'sequelize' 1import { QueryTypes, Sequelize, Transaction } from 'sequelize'
2import { sequelizeTypescript } from '@server/initializers/database'
3 2
4// Sequelize always skip the update if we only update updatedAt field 3// Sequelize always skip the update if we only update updatedAt field
5function setAsUpdated (table: string, id: number, transaction?: Transaction) { 4function setAsUpdated (options: {
6 return sequelizeTypescript.query( 5 sequelize: Sequelize
6 table: string
7 id: number
8 transaction?: Transaction
9}) {
10 const { sequelize, table, id, transaction } = options
11
12 return sequelize.query(
7 `UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`, 13 `UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`,
8 { 14 {
9 replacements: { table, id, updatedAt: new Date() }, 15 replacements: { table, id, updatedAt: new Date() },
diff --git a/server/models/user/sql/user-notitication-list-query-builder.ts b/server/models/user/sql/user-notitication-list-query-builder.ts
index 31b4932bf..d11546df0 100644
--- a/server/models/user/sql/user-notitication-list-query-builder.ts
+++ b/server/models/user/sql/user-notitication-list-query-builder.ts
@@ -1,8 +1,8 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize } from 'sequelize'
2import { AbstractRunQuery, ModelBuilder } from '@server/models/shared' 2import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
3import { getSort } from '@server/models/utils'
4import { UserNotificationModelForApi } from '@server/types/models' 3import { UserNotificationModelForApi } from '@server/types/models'
5import { ActorImageType } from '@shared/models' 4import { ActorImageType } from '@shared/models'
5import { getSort } from '../../shared'
6 6
7export interface ListNotificationsOptions { 7export interface ListNotificationsOptions {
8 userId: number 8 userId: number
diff --git a/server/models/user/user-notification-setting.ts b/server/models/user/user-notification-setting.ts
index 66e1d85b3..394494c0c 100644
--- a/server/models/user/user-notification-setting.ts
+++ b/server/models/user/user-notification-setting.ts
@@ -17,7 +17,7 @@ import { MNotificationSettingFormattable } from '@server/types/models'
17import { AttributesOnly } from '@shared/typescript-utils' 17import { AttributesOnly } from '@shared/typescript-utils'
18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' 18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
19import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 19import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
20import { throwIfNotValid } from '../utils' 20import { throwIfNotValid } from '../shared'
21import { UserModel } from './user' 21import { UserModel } from './user'
22 22
23@Table({ 23@Table({
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts
index d37fa5dc7..6e134158f 100644
--- a/server/models/user/user-notification.ts
+++ b/server/models/user/user-notification.ts
@@ -13,7 +13,7 @@ import { AccountModel } from '../account/account'
13import { ActorFollowModel } from '../actor/actor-follow' 13import { ActorFollowModel } from '../actor/actor-follow'
14import { ApplicationModel } from '../application/application' 14import { ApplicationModel } from '../application/application'
15import { PluginModel } from '../server/plugin' 15import { PluginModel } from '../server/plugin'
16import { throwIfNotValid } from '../utils' 16import { throwIfNotValid } from '../shared'
17import { VideoModel } from '../video/video' 17import { VideoModel } from '../video/video'
18import { VideoBlacklistModel } from '../video/video-blacklist' 18import { VideoBlacklistModel } from '../video/video-blacklist'
19import { VideoCommentModel } from '../video/video-comment' 19import { VideoCommentModel } from '../video/video-comment'
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index 672728a2a..0932a367a 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -30,6 +30,7 @@ import {
30 MUserNotifSettingChannelDefault, 30 MUserNotifSettingChannelDefault,
31 MUserWithNotificationSetting 31 MUserWithNotificationSetting
32} from '@server/types/models' 32} from '@server/types/models'
33import { forceNumber } from '@shared/core-utils'
33import { AttributesOnly } from '@shared/typescript-utils' 34import { AttributesOnly } from '@shared/typescript-utils'
34import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' 35import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
35import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models' 36import { AbuseState, MyUser, UserRight, VideoPlaylistType } from '../../../shared/models'
@@ -63,14 +64,13 @@ import { ActorModel } from '../actor/actor'
63import { ActorFollowModel } from '../actor/actor-follow' 64import { ActorFollowModel } from '../actor/actor-follow'
64import { ActorImageModel } from '../actor/actor-image' 65import { ActorImageModel } from '../actor/actor-image'
65import { OAuthTokenModel } from '../oauth/oauth-token' 66import { OAuthTokenModel } from '../oauth/oauth-token'
66import { getAdminUsersSort, throwIfNotValid } from '../utils' 67import { getAdminUsersSort, throwIfNotValid } from '../shared'
67import { VideoModel } from '../video/video' 68import { VideoModel } from '../video/video'
68import { VideoChannelModel } from '../video/video-channel' 69import { VideoChannelModel } from '../video/video-channel'
69import { VideoImportModel } from '../video/video-import' 70import { VideoImportModel } from '../video/video-import'
70import { VideoLiveModel } from '../video/video-live' 71import { VideoLiveModel } from '../video/video-live'
71import { VideoPlaylistModel } from '../video/video-playlist' 72import { VideoPlaylistModel } from '../video/video-playlist'
72import { UserNotificationSettingModel } from './user-notification-setting' 73import { UserNotificationSettingModel } from './user-notification-setting'
73import { forceNumber } from '@shared/core-utils'
74 74
75enum ScopeNames { 75enum ScopeNames {
76 FOR_ME_API = 'FOR_ME_API', 76 FOR_ME_API = 'FOR_ME_API',
diff --git a/server/models/utils.ts b/server/models/utils.ts
deleted file mode 100644
index 93723816f..000000000
--- a/server/models/utils.ts
+++ /dev/null
@@ -1,317 +0,0 @@
1import { literal, Model, ModelStatic, Op, OrderItem, Sequelize } from 'sequelize'
2import validator from 'validator'
3import { forceNumber } from '@shared/core-utils'
4import { AttributesOnly } from '@shared/typescript-utils'
5
6type SortType = { sortModel: string, sortValue: string }
7
8// Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ]
9function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
10 const { direction, field } = buildDirectionAndField(value)
11
12 let finalField: string | ReturnType<typeof Sequelize.col>
13
14 if (field.toLowerCase() === 'match') { // Search
15 finalField = Sequelize.col('similarity')
16 } else {
17 finalField = field
18 }
19
20 return [ [ finalField, direction ], lastSort ]
21}
22
23function getAdminUsersSort (value: string): OrderItem[] {
24 const { direction, field } = buildDirectionAndField(value)
25
26 let finalField: string | ReturnType<typeof Sequelize.col>
27
28 if (field === 'videoQuotaUsed') { // Users list
29 finalField = Sequelize.col('videoQuotaUsed')
30 } else {
31 finalField = field
32 }
33
34 const nullPolicy = direction === 'ASC'
35 ? 'NULLS FIRST'
36 : 'NULLS LAST'
37
38 // FIXME: typings
39 return [ [ finalField as any, direction, nullPolicy ], [ 'id', 'ASC' ] ]
40}
41
42function getPlaylistSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
43 const { direction, field } = buildDirectionAndField(value)
44
45 if (field.toLowerCase() === 'name') {
46 return [ [ 'displayName', direction ], lastSort ]
47 }
48
49 return getSort(value, lastSort)
50}
51
52function getCommentSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
53 const { direction, field } = buildDirectionAndField(value)
54
55 if (field === 'totalReplies') {
56 return [
57 [ Sequelize.literal('"totalReplies"'), direction ],
58 lastSort
59 ]
60 }
61
62 return getSort(value, lastSort)
63}
64
65function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
66 const { direction, field } = buildDirectionAndField(value)
67
68 if (field.toLowerCase() === 'trending') { // Sort by aggregation
69 return [
70 [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ],
71
72 [ Sequelize.col('VideoModel.views'), direction ],
73
74 lastSort
75 ]
76 } else if (field === 'publishedAt') {
77 return [
78 [ 'ScheduleVideoUpdate', 'updateAt', direction + ' NULLS LAST' ],
79
80 [ Sequelize.col('VideoModel.publishedAt'), direction ],
81
82 lastSort
83 ]
84 }
85
86 let finalField: string | ReturnType<typeof Sequelize.col>
87
88 // Alias
89 if (field.toLowerCase() === 'match') { // Search
90 finalField = Sequelize.col('similarity')
91 } else {
92 finalField = field
93 }
94
95 const firstSort: OrderItem = typeof finalField === 'string'
96 ? finalField.split('.').concat([ direction ]) as OrderItem
97 : [ finalField, direction ]
98
99 return [ firstSort, lastSort ]
100}
101
102function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
103 const [ firstSort ] = getSort(value)
104
105 if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as OrderItem[]
106 return [ firstSort, lastSort ]
107}
108
109function getInstanceFollowsSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
110 const { direction, field } = buildDirectionAndField(value)
111
112 if (field === 'redundancyAllowed') {
113 return [
114 [ 'ActorFollowing.Server.redundancyAllowed', direction ],
115 lastSort
116 ]
117 }
118
119 return getSort(value, lastSort)
120}
121
122function getChannelSyncSort (value: string): OrderItem[] {
123 const { direction, field } = buildDirectionAndField(value)
124 if (field.toLowerCase() === 'videochannel') {
125 return [
126 [ literal('"VideoChannel.name"'), direction ]
127 ]
128 }
129 return [ [ field, direction ] ]
130}
131
132function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
133 if (!model.createdAt || !model.updatedAt) {
134 throw new Error('Miss createdAt & updatedAt attributes to model')
135 }
136
137 const now = Date.now()
138 const createdAtTime = model.createdAt.getTime()
139 const updatedAtTime = model.updatedAt.getTime()
140
141 return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval
142}
143
144function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) {
145 if (nullable && (value === null || value === undefined)) return
146
147 if (validator(value) === false) {
148 throw new Error(`"${value}" is not a valid ${fieldName}.`)
149 }
150}
151
152function buildTrigramSearchIndex (indexName: string, attribute: string) {
153 return {
154 name: indexName,
155 // FIXME: gin_trgm_ops is not taken into account in Sequelize 6, so adding it ourselves in the literal function
156 fields: [ Sequelize.literal('lower(immutable_unaccent(' + attribute + ')) gin_trgm_ops') as any ],
157 using: 'gin',
158 operator: 'gin_trgm_ops'
159 }
160}
161
162function createSimilarityAttribute (col: string, value: string) {
163 return Sequelize.fn(
164 'similarity',
165
166 searchTrigramNormalizeCol(col),
167
168 searchTrigramNormalizeValue(value)
169 )
170}
171
172function buildBlockedAccountSQL (blockerIds: number[]) {
173 const blockerIdsString = blockerIds.join(', ')
174
175 return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' +
176 ' UNION ' +
177 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' +
178 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' +
179 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')'
180}
181
182function buildServerIdsFollowedBy (actorId: any) {
183 const actorIdNumber = forceNumber(actorId)
184
185 return '(' +
186 'SELECT "actor"."serverId" FROM "actorFollow" ' +
187 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
188 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
189 ')'
190}
191
192function buildWhereIdOrUUID (id: number | string) {
193 return validator.isInt('' + id) ? { id } : { uuid: id }
194}
195
196function parseAggregateResult (result: any) {
197 if (!result) return 0
198
199 const total = forceNumber(result)
200 if (isNaN(total)) return 0
201
202 return total
203}
204
205function parseRowCountResult (result: any) {
206 if (result.length !== 0) return result[0].total
207
208 return 0
209}
210
211function createSafeIn (sequelize: Sequelize, toEscape: (string | number)[], additionalUnescaped: string[] = []) {
212 return toEscape.map(t => {
213 return t === null
214 ? null
215 : sequelize.escape('' + t)
216 }).concat(additionalUnescaped).join(', ')
217}
218
219function buildLocalAccountIdsIn () {
220 return literal(
221 '(SELECT "account"."id" FROM "account" INNER JOIN "actor" ON "actor"."id" = "account"."actorId" AND "actor"."serverId" IS NULL)'
222 )
223}
224
225function buildLocalActorIdsIn () {
226 return literal(
227 '(SELECT "actor"."id" FROM "actor" WHERE "actor"."serverId" IS NULL)'
228 )
229}
230
231function buildDirectionAndField (value: string) {
232 let field: string
233 let direction: 'ASC' | 'DESC'
234
235 if (value.substring(0, 1) === '-') {
236 direction = 'DESC'
237 field = value.substring(1)
238 } else {
239 direction = 'ASC'
240 field = value
241 }
242
243 return { direction, field }
244}
245
246function searchAttribute (sourceField?: string, targetField?: string) {
247 if (!sourceField) return {}
248
249 return {
250 [targetField]: {
251 // FIXME: ts error
252 [Op.iLike as any]: `%${sourceField}%`
253 }
254 }
255}
256
257function buildSQLAttributes <M extends Model> (options: {
258 model: ModelStatic<M>
259 tableName: string
260
261 excludeAttributes?: Exclude<keyof AttributesOnly<M>, symbol>[]
262 aliasPrefix?: string
263}) {
264 const { model, tableName, aliasPrefix, excludeAttributes } = options
265
266 const attributes = Object.keys(model.getAttributes()) as Exclude<keyof AttributesOnly<M>, symbol>[]
267
268 return attributes
269 .filter(a => {
270 if (!excludeAttributes) return true
271 if (excludeAttributes.includes(a)) return false
272
273 return true
274 })
275 .map(a => {
276 return `"${tableName}"."${a}" AS "${aliasPrefix || ''}${a}"`
277 })
278}
279
280// ---------------------------------------------------------------------------
281
282export {
283 buildSQLAttributes,
284 buildBlockedAccountSQL,
285 buildLocalActorIdsIn,
286 getPlaylistSort,
287 SortType,
288 buildLocalAccountIdsIn,
289 getSort,
290 getCommentSort,
291 getAdminUsersSort,
292 getVideoSort,
293 getBlacklistSort,
294 getChannelSyncSort,
295 createSimilarityAttribute,
296 throwIfNotValid,
297 buildServerIdsFollowedBy,
298 buildTrigramSearchIndex,
299 buildWhereIdOrUUID,
300 isOutdated,
301 parseAggregateResult,
302 getInstanceFollowsSort,
303 buildDirectionAndField,
304 createSafeIn,
305 searchAttribute,
306 parseRowCountResult
307}
308
309// ---------------------------------------------------------------------------
310
311function searchTrigramNormalizeValue (value: string) {
312 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', value))
313}
314
315function searchTrigramNormalizeCol (col: string) {
316 return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col)))
317}
diff --git a/server/models/video/sql/comment/video-comment-list-query-builder.ts b/server/models/video/sql/comment/video-comment-list-query-builder.ts
index 49b41b6d8..3960f6b13 100644
--- a/server/models/video/sql/comment/video-comment-list-query-builder.ts
+++ b/server/models/video/sql/comment/video-comment-list-query-builder.ts
@@ -1,7 +1,7 @@
1import { Model, Sequelize, Transaction } from 'sequelize' 1import { Model, Sequelize, Transaction } from 'sequelize'
2import { AbstractRunQuery, ModelBuilder } from '@server/models/shared' 2import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
3import { createSafeIn, getCommentSort, parseRowCountResult } from '@server/models/utils'
4import { ActorImageType, VideoPrivacy } from '@shared/models' 3import { ActorImageType, VideoPrivacy } from '@shared/models'
4import { createSafeIn, getCommentSort, parseRowCountResult } from '../../../shared'
5import { VideoCommentTableAttributes } from './video-comment-table-attributes' 5import { VideoCommentTableAttributes } from './video-comment-table-attributes'
6 6
7export interface ListVideoCommentsOptions { 7export interface ListVideoCommentsOptions {
diff --git a/server/models/video/sql/video/shared/abstract-video-query-builder.ts b/server/models/video/sql/video/shared/abstract-video-query-builder.ts
index f0ce69501..cbd57ad8c 100644
--- a/server/models/video/sql/video/shared/abstract-video-query-builder.ts
+++ b/server/models/video/sql/video/shared/abstract-video-query-builder.ts
@@ -1,9 +1,9 @@
1import { Sequelize } from 'sequelize' 1import { Sequelize } from 'sequelize'
2import validator from 'validator' 2import validator from 'validator'
3import { createSafeIn } from '@server/models/utils'
4import { MUserAccountId } from '@server/types/models' 3import { MUserAccountId } from '@server/types/models'
5import { ActorImageType } from '@shared/models' 4import { ActorImageType } from '@shared/models'
6import { AbstractRunQuery } from '../../../../shared/abstract-run-query' 5import { AbstractRunQuery } from '../../../../shared/abstract-run-query'
6import { createSafeIn } from '../../../../shared'
7import { VideoTableAttributes } from './video-table-attributes' 7import { VideoTableAttributes } from './video-table-attributes'
8 8
9/** 9/**
diff --git a/server/models/video/sql/video/videos-id-list-query-builder.ts b/server/models/video/sql/video/videos-id-list-query-builder.ts
index 7c864bf27..62f1855c7 100644
--- a/server/models/video/sql/video/videos-id-list-query-builder.ts
+++ b/server/models/video/sql/video/videos-id-list-query-builder.ts
@@ -2,11 +2,12 @@ import { Sequelize, Transaction } from 'sequelize'
2import validator from 'validator' 2import validator from 'validator'
3import { exists } from '@server/helpers/custom-validators/misc' 3import { exists } from '@server/helpers/custom-validators/misc'
4import { WEBSERVER } from '@server/initializers/constants' 4import { WEBSERVER } from '@server/initializers/constants'
5import { buildDirectionAndField, createSafeIn, parseRowCountResult } from '@server/models/utils' 5import { buildSortDirectionAndField } from '@server/models/shared'
6import { MUserAccountId, MUserId } from '@server/types/models' 6import { MUserAccountId, MUserId } from '@server/types/models'
7import { forceNumber } from '@shared/core-utils'
7import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models' 8import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
9import { createSafeIn, parseRowCountResult } from '../../../shared'
8import { AbstractRunQuery } from '../../../shared/abstract-run-query' 10import { AbstractRunQuery } from '../../../shared/abstract-run-query'
9import { forceNumber } from '@shared/core-utils'
10 11
11/** 12/**
12 * 13 *
@@ -665,7 +666,7 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
665 } 666 }
666 667
667 private buildOrder (value: string) { 668 private buildOrder (value: string) {
668 const { direction, field } = buildDirectionAndField(value) 669 const { direction, field } = buildSortDirectionAndField(value)
669 if (field.match(/^[a-zA-Z."]+$/) === null) throw new Error('Invalid sort column ' + field) 670 if (field.match(/^[a-zA-Z."]+$/) === null) throw new Error('Invalid sort column ' + field)
670 671
671 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' 672 if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()'
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index 653b9694b..cebde3755 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -4,7 +4,7 @@ import { MTag } from '@server/types/models'
4import { AttributesOnly } from '@shared/typescript-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoPrivacy, VideoState } from '../../../shared/models/videos' 5import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
6import { isVideoTagValid } from '../../helpers/custom-validators/videos' 6import { isVideoTagValid } from '../../helpers/custom-validators/videos'
7import { throwIfNotValid } from '../utils' 7import { throwIfNotValid } from '../shared'
8import { VideoModel } from './video' 8import { VideoModel } from './video'
9import { VideoTagModel } from './video-tag' 9import { VideoTagModel } from './video-tag'
10 10
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index 1cd8224c0..9247d0e2b 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -5,7 +5,7 @@ import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 5import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
6import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 6import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
7import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
8import { getBlacklistSort, searchAttribute, SortType, throwIfNotValid } from '../utils' 8import { getBlacklistSort, searchAttribute, throwIfNotValid } from '../shared'
9import { ThumbnailModel } from './thumbnail' 9import { ThumbnailModel } from './thumbnail'
10import { VideoModel } from './video' 10import { VideoModel } from './video'
11import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' 11import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
@@ -57,7 +57,7 @@ export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlack
57 static listForApi (parameters: { 57 static listForApi (parameters: {
58 start: number 58 start: number
59 count: number 59 count: number
60 sort: SortType 60 sort: string
61 search?: string 61 search?: string
62 type?: VideoBlacklistType 62 type?: VideoBlacklistType
63 }) { 63 }) {
@@ -67,7 +67,7 @@ export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlack
67 return { 67 return {
68 offset: start, 68 offset: start,
69 limit: count, 69 limit: count,
70 order: getBlacklistSort(sort.sortModel, sort.sortValue) 70 order: getBlacklistSort(sort)
71 } 71 }
72 } 72 }
73 73
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index 5fbcd6e3b..2eaa77407 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -23,7 +23,7 @@ import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/vid
23import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
24import { CONFIG } from '../../initializers/config' 24import { CONFIG } from '../../initializers/config'
25import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' 25import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
26import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' 26import { buildWhereIdOrUUID, throwIfNotValid } from '../shared'
27import { VideoModel } from './video' 27import { VideoModel } from './video'
28 28
29export enum ScopeNames { 29export enum ScopeNames {
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
index 1a1b8c88d..2db4b523a 100644
--- a/server/models/video/video-change-ownership.ts
+++ b/server/models/video/video-change-ownership.ts
@@ -3,7 +3,7 @@ import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@se
3import { AttributesOnly } from '@shared/typescript-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' 4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
5import { AccountModel } from '../account/account' 5import { AccountModel } from '../account/account'
6import { getSort } from '../utils' 6import { getSort } from '../shared'
7import { ScopeNames as VideoScopeNames, VideoModel } from './video' 7import { ScopeNames as VideoScopeNames, VideoModel } from './video'
8 8
9enum ScopeNames { 9enum ScopeNames {
diff --git a/server/models/video/video-channel-sync.ts b/server/models/video/video-channel-sync.ts
index 6e49cde10..a4cbf51f5 100644
--- a/server/models/video/video-channel-sync.ts
+++ b/server/models/video/video-channel-sync.ts
@@ -21,7 +21,7 @@ import { VideoChannelSync, VideoChannelSyncState } from '@shared/models'
21import { AttributesOnly } from '@shared/typescript-utils' 21import { AttributesOnly } from '@shared/typescript-utils'
22import { AccountModel } from '../account/account' 22import { AccountModel } from '../account/account'
23import { UserModel } from '../user/user' 23import { UserModel } from '../user/user'
24import { getChannelSyncSort, throwIfNotValid } from '../utils' 24import { getChannelSyncSort, throwIfNotValid } from '../shared'
25import { VideoChannelModel } from './video-channel' 25import { VideoChannelModel } from './video-channel'
26 26
27@DefaultScope(() => ({ 27@DefaultScope(() => ({
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 132c8f021..b71f5a197 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -43,8 +43,14 @@ import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor'
43import { ActorFollowModel } from '../actor/actor-follow' 43import { ActorFollowModel } from '../actor/actor-follow'
44import { ActorImageModel } from '../actor/actor-image' 44import { ActorImageModel } from '../actor/actor-image'
45import { ServerModel } from '../server/server' 45import { ServerModel } from '../server/server'
46import { setAsUpdated } from '../shared' 46import {
47import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' 47 buildServerIdsFollowedBy,
48 buildTrigramSearchIndex,
49 createSimilarityAttribute,
50 getSort,
51 setAsUpdated,
52 throwIfNotValid
53} from '../shared'
48import { VideoModel } from './video' 54import { VideoModel } from './video'
49import { VideoPlaylistModel } from './video-playlist' 55import { VideoPlaylistModel } from './video-playlist'
50 56
@@ -831,6 +837,6 @@ export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannel
831 } 837 }
832 838
833 setAsUpdated (transaction?: Transaction) { 839 setAsUpdated (transaction?: Transaction) {
834 return setAsUpdated('videoChannel', this.id, transaction) 840 return setAsUpdated({ sequelize: this.sequelize, table: 'videoChannel', id: this.id, transaction })
835 } 841 }
836} 842}
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 28a3d723d..ff5142809 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -40,7 +40,7 @@ import {
40import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' 40import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
41import { AccountModel } from '../account/account' 41import { AccountModel } from '../account/account'
42import { ActorModel } from '../actor/actor' 42import { ActorModel } from '../actor/actor'
43import { buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../utils' 43import { buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../shared'
44import { ListVideoCommentsOptions, VideoCommentListQueryBuilder } from './sql/comment/video-comment-list-query-builder' 44import { ListVideoCommentsOptions, VideoCommentListQueryBuilder } from './sql/comment/video-comment-list-query-builder'
45import { VideoModel } from './video' 45import { VideoModel } from './video'
46import { VideoChannelModel } from './video-channel' 46import { VideoChannelModel } from './video-channel'
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 9b42955ef..07bc13de1 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -21,6 +21,7 @@ import {
21import validator from 'validator' 21import validator from 'validator'
22import { logger } from '@server/helpers/logger' 22import { logger } from '@server/helpers/logger'
23import { extractVideo } from '@server/helpers/video' 23import { extractVideo } from '@server/helpers/video'
24import { CONFIG } from '@server/initializers/config'
24import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url' 25import { buildRemoteVideoBaseUrl } from '@server/lib/activitypub/url'
25import { 26import {
26 getHLSPrivateFileUrl, 27 getHLSPrivateFileUrl,
@@ -50,11 +51,9 @@ import {
50} from '../../initializers/constants' 51} from '../../initializers/constants'
51import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file' 52import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file'
52import { VideoRedundancyModel } from '../redundancy/video-redundancy' 53import { VideoRedundancyModel } from '../redundancy/video-redundancy'
53import { doesExist } from '../shared' 54import { doesExist, parseAggregateResult, throwIfNotValid } from '../shared'
54import { parseAggregateResult, throwIfNotValid } from '../utils'
55import { VideoModel } from './video' 55import { VideoModel } from './video'
56import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 56import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
57import { CONFIG } from '@server/initializers/config'
58 57
59export enum ScopeNames { 58export enum ScopeNames {
60 WITH_VIDEO = 'WITH_VIDEO', 59 WITH_VIDEO = 'WITH_VIDEO',
@@ -266,7 +265,7 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
266 static doesInfohashExist (infoHash: string) { 265 static doesInfohashExist (infoHash: string) {
267 const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' 266 const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
268 267
269 return doesExist(query, { infoHash }) 268 return doesExist(this.sequelize, query, { infoHash })
270 } 269 }
271 270
272 static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) { 271 static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) {
@@ -282,14 +281,14 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
282 'LEFT JOIN "video" "hlsVideo" ON "hlsVideo"."id" = "videoStreamingPlaylist"."videoId" AND "hlsVideo"."remote" IS FALSE ' + 281 'LEFT JOIN "video" "hlsVideo" ON "hlsVideo"."id" = "videoStreamingPlaylist"."videoId" AND "hlsVideo"."remote" IS FALSE ' +
283 'WHERE "torrentFilename" = $filename AND ("hlsVideo"."id" IS NOT NULL OR "webtorrent"."id" IS NOT NULL) LIMIT 1' 282 'WHERE "torrentFilename" = $filename AND ("hlsVideo"."id" IS NOT NULL OR "webtorrent"."id" IS NOT NULL) LIMIT 1'
284 283
285 return doesExist(query, { filename }) 284 return doesExist(this.sequelize, query, { filename })
286 } 285 }
287 286
288 static async doesOwnedWebTorrentVideoFileExist (filename: string) { 287 static async doesOwnedWebTorrentVideoFileExist (filename: string) {
289 const query = 'SELECT 1 FROM "videoFile" INNER JOIN "video" ON "video"."id" = "videoFile"."videoId" AND "video"."remote" IS FALSE ' + 288 const query = 'SELECT 1 FROM "videoFile" INNER JOIN "video" ON "video"."id" = "videoFile"."videoId" AND "video"."remote" IS FALSE ' +
290 `WHERE "filename" = $filename AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1` 289 `WHERE "filename" = $filename AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
291 290
292 return doesExist(query, { filename }) 291 return doesExist(this.sequelize, query, { filename })
293 } 292 }
294 293
295 static loadByFilename (filename: string) { 294 static loadByFilename (filename: string) {
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index da6b92c7a..c040e0fda 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -22,7 +22,7 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help
22import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 22import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
23import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' 23import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
24import { UserModel } from '../user/user' 24import { UserModel } from '../user/user'
25import { getSort, searchAttribute, throwIfNotValid } from '../utils' 25import { getSort, searchAttribute, throwIfNotValid } from '../shared'
26import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 26import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
27import { VideoChannelSyncModel } from './video-channel-sync' 27import { VideoChannelSyncModel } from './video-channel-sync'
28 28
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index 7181b5599..48f4ed5a9 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -31,7 +31,7 @@ import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/
31import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 31import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
32import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 32import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
33import { AccountModel } from '../account/account' 33import { AccountModel } from '../account/account'
34import { getSort, throwIfNotValid } from '../utils' 34import { getSort, throwIfNotValid } from '../shared'
35import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' 35import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
36import { VideoPlaylistModel } from './video-playlist' 36import { VideoPlaylistModel } from './video-playlist'
37 37
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 8bbe54c49..faf4bea78 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -21,12 +21,8 @@ import { activityPubCollectionPagination } from '@server/lib/activitypub/collect
21import { MAccountId, MChannelId } from '@server/types/models' 21import { MAccountId, MChannelId } from '@server/types/models'
22import { buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils' 22import { buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils'
23import { buildUUID, uuidToShort } from '@shared/extra-utils' 23import { buildUUID, uuidToShort } from '@shared/extra-utils'
24import { ActivityIconObject, PlaylistObject, VideoPlaylist, VideoPlaylistPrivacy, VideoPlaylistType } from '@shared/models'
24import { AttributesOnly } from '@shared/typescript-utils' 25import { AttributesOnly } from '@shared/typescript-utils'
25import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
26import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
27import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
28import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
29import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
30import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 26import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
31import { 27import {
32 isVideoPlaylistDescriptionValid, 28 isVideoPlaylistDescriptionValid,
@@ -53,7 +49,6 @@ import {
53} from '../../types/models/video/video-playlist' 49} from '../../types/models/video/video-playlist'
54import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account' 50import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
55import { ActorModel } from '../actor/actor' 51import { ActorModel } from '../actor/actor'
56import { setAsUpdated } from '../shared'
57import { 52import {
58 buildServerIdsFollowedBy, 53 buildServerIdsFollowedBy,
59 buildTrigramSearchIndex, 54 buildTrigramSearchIndex,
@@ -61,8 +56,9 @@ import {
61 createSimilarityAttribute, 56 createSimilarityAttribute,
62 getPlaylistSort, 57 getPlaylistSort,
63 isOutdated, 58 isOutdated,
59 setAsUpdated,
64 throwIfNotValid 60 throwIfNotValid
65} from '../utils' 61} from '../shared'
66import { ThumbnailModel } from './thumbnail' 62import { ThumbnailModel } from './thumbnail'
67import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 63import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
68import { VideoPlaylistElementModel } from './video-playlist-element' 64import { VideoPlaylistElementModel } from './video-playlist-element'
@@ -641,7 +637,7 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli
641 } 637 }
642 638
643 setAsRefreshed () { 639 setAsRefreshed () {
644 return setAsUpdated('videoPlaylist', this.id) 640 return setAsUpdated({ sequelize: this.sequelize, table: 'videoPlaylist', id: this.id })
645 } 641 }
646 642
647 setVideosLength (videosLength: number) { 643 setVideosLength (videosLength: number) {
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index f2190037e..b4de2b20f 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -7,7 +7,7 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
7import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models' 7import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models'
8import { MVideoShareActor, MVideoShareFull } from '../../types/models/video' 8import { MVideoShareActor, MVideoShareFull } from '../../types/models/video'
9import { ActorModel } from '../actor/actor' 9import { ActorModel } from '../actor/actor'
10import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' 10import { buildLocalActorIdsIn, throwIfNotValid } from '../shared'
11import { VideoModel } from './video' 11import { VideoModel } from './video'
12 12
13enum ScopeNames { 13enum ScopeNames {
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index 0386edf28..a85c79c9f 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -37,8 +37,7 @@ import {
37 WEBSERVER 37 WEBSERVER
38} from '../../initializers/constants' 38} from '../../initializers/constants'
39import { VideoRedundancyModel } from '../redundancy/video-redundancy' 39import { VideoRedundancyModel } from '../redundancy/video-redundancy'
40import { doesExist } from '../shared' 40import { doesExist, throwIfNotValid } from '../shared'
41import { throwIfNotValid } from '../utils'
42import { VideoModel } from './video' 41import { VideoModel } from './video'
43 42
44@Table({ 43@Table({
@@ -138,7 +137,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
138 static doesInfohashExist (infoHash: string) { 137 static doesInfohashExist (infoHash: string) {
139 const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1' 138 const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1'
140 139
141 return doesExist(query, { infoHash }) 140 return doesExist(this.sequelize, query, { infoHash })
142 } 141 }
143 142
144 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) { 143 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, files: unknown[]) {
@@ -237,7 +236,7 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
237 `AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` + 236 `AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` +
238 `AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1` 237 `AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
239 238
240 return doesExist(query, { videoUUID }) 239 return doesExist(this.sequelize, query, { videoUUID })
241 } 240 }
242 241
243 assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) { 242 assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 56cc45cfe..1a10d2da2 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -32,7 +32,7 @@ import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFil
32import { VideoPathManager } from '@server/lib/video-path-manager' 32import { VideoPathManager } from '@server/lib/video-path-manager'
33import { isVideoInPrivateDirectory } from '@server/lib/video-privacy' 33import { isVideoInPrivateDirectory } from '@server/lib/video-privacy'
34import { getServerActor } from '@server/models/application/application' 34import { getServerActor } from '@server/models/application/application'
35import { ModelCache } from '@server/models/model-cache' 35import { ModelCache } from '@server/models/shared/model-cache'
36import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils' 36import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
37import { ffprobePromise, getAudioStream, hasAudioStream, uuidToShort } from '@shared/extra-utils' 37import { ffprobePromise, getAudioStream, hasAudioStream, uuidToShort } from '@shared/extra-utils'
38import { 38import {
@@ -103,10 +103,9 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy'
103import { ServerModel } from '../server/server' 103import { ServerModel } from '../server/server'
104import { TrackerModel } from '../server/tracker' 104import { TrackerModel } from '../server/tracker'
105import { VideoTrackerModel } from '../server/video-tracker' 105import { VideoTrackerModel } from '../server/video-tracker'
106import { setAsUpdated } from '../shared' 106import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, setAsUpdated, throwIfNotValid } from '../shared'
107import { UserModel } from '../user/user' 107import { UserModel } from '../user/user'
108import { UserVideoHistoryModel } from '../user/user-video-history' 108import { UserVideoHistoryModel } from '../user/user-video-history'
109import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
110import { VideoViewModel } from '../view/video-view' 109import { VideoViewModel } from '../view/video-view'
111import { 110import {
112 videoFilesModelToFormattedJSON, 111 videoFilesModelToFormattedJSON,
@@ -1871,7 +1870,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1871 } 1870 }
1872 1871
1873 setAsRefreshed (transaction?: Transaction) { 1872 setAsRefreshed (transaction?: Transaction) {
1874 return setAsUpdated('video', this.id, transaction) 1873 return setAsUpdated({ sequelize: this.sequelize, table: 'video', id: this.id, transaction })
1875 } 1874 }
1876 1875
1877 // --------------------------------------------------------------------------- 1876 // ---------------------------------------------------------------------------