Chocobozzz 2 months ago
parent
commit
9e2700b89d
100 changed files with 285 additions and 330 deletions
  1. 2 0
      .eslintrc.json
  2. 2 2
      apps/peertube-runner/src/shared/http.ts
  3. 5 1
      client/.eslintrc.json
  4. 1 2
      client/.stylelintrc.json
  5. 9 9
      client/e2e/src/suites-local/videos-list.e2e-spec.ts
  6. 1 0
      client/package.json
  7. 2 2
      client/src/app/+admin/system/jobs/job.service.ts
  8. 14 14
      client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts
  9. 1 1
      client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts
  10. 1 1
      client/src/app/app-routing.module.ts
  11. 1 1
      client/src/app/core/rest/rest-extractor.service.ts
  12. 1 1
      client/src/app/core/routing/menu-guard.service.ts
  13. 2 0
      client/src/app/menu/menu.component.ts
  14. 2 0
      client/src/app/menu/notification.component.ts
  15. 2 2
      client/src/app/shared/shared-main/video/video-ownership.service.ts
  16. 1 0
      client/src/assets/player/shared/settings/settings-menu-button.ts
  17. 1 1
      client/src/typings.d.ts
  18. 5 0
      client/yarn.lock
  19. 2 2
      packages/server-commands/src/server/jobs.ts
  20. 1 0
      packages/server-commands/src/server/servers.ts
  21. 1 1
      packages/tests/src/api/check-params/my-user.ts
  22. 3 3
      packages/tests/src/api/check-params/users-admin.ts
  23. 2 2
      packages/tests/src/api/check-params/video-blacklist.ts
  24. 5 2
      packages/tests/src/api/videos/video-nsfw.ts
  25. 1 1
      server/core/controllers/api/accounts.ts
  26. 1 1
      server/core/controllers/api/overviews.ts
  27. 1 1
      server/core/controllers/api/search/search-video-channels.ts
  28. 1 1
      server/core/controllers/api/search/search-video-playlists.ts
  29. 1 1
      server/core/controllers/api/search/search-videos.ts
  30. 1 1
      server/core/controllers/api/users/me.ts
  31. 1 1
      server/core/controllers/api/users/my-subscriptions.ts
  32. 2 2
      server/core/controllers/api/video-channel.ts
  33. 1 1
      server/core/controllers/api/video-playlist.ts
  34. 2 2
      server/core/controllers/api/videos/comment.ts
  35. 1 1
      server/core/controllers/api/videos/index.ts
  36. 1 1
      server/core/controllers/download.ts
  37. 4 4
      server/core/helpers/logger.ts
  38. 1 1
      server/core/lib/files-cache/shared/abstract-simple-file-cache.ts
  39. 1 1
      server/core/lib/job-queue/handlers/shared/move-video.ts
  40. 1 1
      server/core/lib/job-queue/handlers/video-studio-edition.ts
  41. 2 2
      server/core/lib/live/live-manager.ts
  42. 2 2
      server/core/lib/object-storage/shared/object-storage-helpers.ts
  43. 6 4
      server/core/lib/peertube-socket.ts
  44. 1 1
      server/core/middlewares/async.ts
  45. 3 3
      server/core/middlewares/cache/shared/api-cache.ts
  46. 1 1
      server/core/middlewares/validators/shared/utils.ts
  47. 3 4
      server/core/models/abuse/abuse-message.ts
  48. 3 6
      server/core/models/abuse/abuse.ts
  49. 3 3
      server/core/models/abuse/video-abuse.ts
  50. 3 3
      server/core/models/abuse/video-comment-abuse.ts
  51. 3 4
      server/core/models/account/account-blocklist.ts
  52. 3 4
      server/core/models/account/account-video-rate.ts
  53. 3 6
      server/core/models/account/account.ts
  54. 3 2
      server/core/models/account/actor-custom-page.ts
  55. 3 6
      server/core/models/actor/actor-follow.ts
  56. 3 6
      server/core/models/actor/actor-image.ts
  57. 3 5
      server/core/models/actor/actor.ts
  58. 3 3
      server/core/models/application/application.ts
  59. 3 3
      server/core/models/oauth/oauth-client.ts
  60. 3 5
      server/core/models/oauth/oauth-token.ts
  61. 3 6
      server/core/models/redundancy/video-redundancy.ts
  62. 3 6
      server/core/models/runner/runner-job.ts
  63. 3 4
      server/core/models/runner/runner-registration-token.ts
  64. 3 4
      server/core/models/runner/runner.ts
  65. 3 4
      server/core/models/server/plugin.ts
  66. 3 4
      server/core/models/server/server-blocklist.ts
  67. 3 4
      server/core/models/server/server.ts
  68. 3 3
      server/core/models/server/tracker.ts
  69. 3 3
      server/core/models/server/video-tracker.ts
  70. 1 0
      server/core/models/shared/index.ts
  71. 6 0
      server/core/models/shared/sequelize-type.ts
  72. 3 3
      server/core/models/user/user-export.ts
  73. 3 3
      server/core/models/user/user-import.ts
  74. 3 6
      server/core/models/user/user-notification-setting.ts
  75. 3 4
      server/core/models/user/user-notification.ts
  76. 3 6
      server/core/models/user/user-registration.ts
  77. 3 3
      server/core/models/user/user-video-history.ts
  78. 3 6
      server/core/models/user/user.ts
  79. 3 3
      server/core/models/video/schedule-video-update.ts
  80. 1 1
      server/core/models/video/sql/comment/video-comment-list-query-builder.ts
  81. 3 3
      server/core/models/video/storyboard.ts
  82. 3 4
      server/core/models/video/tag.ts
  83. 3 5
      server/core/models/video/thumbnail.ts
  84. 3 4
      server/core/models/video/video-blacklist.ts
  85. 3 6
      server/core/models/video/video-caption.ts
  86. 3 4
      server/core/models/video/video-change-ownership.ts
  87. 3 6
      server/core/models/video/video-channel-sync.ts
  88. 3 5
      server/core/models/video/video-channel.ts
  89. 3 3
      server/core/models/video/video-chapter.ts
  90. 20 27
      server/core/models/video/video-comment.ts
  91. 4 7
      server/core/models/video/video-file.ts
  92. 3 6
      server/core/models/video/video-import.ts
  93. 3 3
      server/core/models/video/video-job-info.ts
  94. 3 2
      server/core/models/video/video-live-replay-setting.ts
  95. 3 4
      server/core/models/video/video-live-session.ts
  96. 3 5
      server/core/models/video/video-live.ts
  97. 6 7
      server/core/models/video/video-password.ts
  98. 3 6
      server/core/models/video/video-playlist-element.ts
  99. 3 5
      server/core/models/video/video-playlist.ts
  100. 3 4
      server/core/models/video/video-share.ts

+ 2 - 0
.eslintrc.json

@@ -118,6 +118,8 @@
     "@typescript-eslint/consistent-type-exports": "off",
     "@typescript-eslint/key-spacing": "off",
 
+    "@typescript-eslint/no-unsafe-argument": "off",
+
     "@typescript-eslint/ban-types": [
       "error",
       {

+ 2 - 2
apps/peertube-runner/src/shared/http.ts

@@ -61,7 +61,7 @@ export function downloadFile (options: {
 // ---------------------------------------------------------------------------
 
 function getRequest (url: string) {
-  if (url.startsWith('https://')) return https.request
+  if (url.startsWith('https://')) return https.request.bind(https)
 
-  return http.request
+  return http.request.bind(http)
 }

+ 5 - 1
client/.eslintrc.json

@@ -160,7 +160,11 @@
           "error",
           "consistent-as-needed"
         ],
-        "no-constant-binary-expression": "error"
+        "no-constant-binary-expression": "error",
+        "@typescript-eslint/unbound-method": [
+          "error",
+          { "ignoreStatic": true }
+        ]
       }
     },
     {

+ 1 - 2
client/.stylelintrc.json

@@ -2,7 +2,6 @@
   "extends": "stylelint-config-sass-guidelines",
   "rules": {
     "scss/at-import-no-partial-leading-underscore": null,
-    "color-hex-case": null,
     "color-hex-length": null,
     "selector-pseudo-element-no-unknown": [
       true,
@@ -19,10 +18,10 @@
     "selector-max-compound-selectors": 9,
     "selector-no-qualifying-type": null,
     "scss/at-extend-no-missing-placeholder": null,
-    "number-leading-zero": null,
     "rule-empty-line-before": null,
     "selector-max-id": null,
     "scss/at-function-pattern": null,
+    "scss/load-no-partial-leading-underscore": null,
     "property-no-vendor-prefix": [
       true,
       {

+ 9 - 9
client/e2e/src/suites-local/videos-list.e2e-spec.ts

@@ -51,15 +51,15 @@ describe('Videos list', () => {
 
   async function checkCommonVideoListPages (policy: NSFWPolicy) {
     const promisesWithFilters = [
-      videoListPage.goOnRootAccount,
-      videoListPage.goOnLocal,
-      videoListPage.goOnRecentlyAdded,
-      videoListPage.goOnTrending,
-      videoListPage.goOnRootChannel
+      videoListPage.goOnRootAccount.bind(videoListPage),
+      videoListPage.goOnLocal.bind(videoListPage),
+      videoListPage.goOnRecentlyAdded.bind(videoListPage),
+      videoListPage.goOnTrending.bind(videoListPage),
+      videoListPage.goOnRootChannel.bind(videoListPage)
     ]
 
     for (const p of promisesWithFilters) {
-      await p.call(videoListPage)
+      await p()
 
       const filter = await videoListPage.getNSFWFilter()
       const filterText = await filter.getText()
@@ -69,11 +69,11 @@ describe('Videos list', () => {
     }
 
     const promisesWithoutFilters = [
-      videoListPage.goOnRootAccountChannels,
-      videoListPage.goOnHomepage
+      videoListPage.goOnRootAccountChannels.bind(videoListPage),
+      videoListPage.goOnHomepage.bind(videoListPage)
     ]
     for (const p of promisesWithoutFilters) {
-      await p.call(videoListPage)
+      await p()
 
       await checkNormalVideo()
       await checkNSFWVideo(policy)

+ 1 - 0
client/package.json

@@ -109,6 +109,7 @@
     "linkifyjs": "^4.0.2",
     "lodash-es": "^4.17.4",
     "markdown-it": "14.0.0",
+    "markdown-it-emoji": "^3.0.0",
     "mini-css-extract-plugin": "^2.2.0",
     "ngx-uploadx": "^6.1.0",
     "path-browserify": "^1.0.0",

+ 2 - 2
client/src/app/+admin/system/jobs/job.service.ts

@@ -35,8 +35,8 @@ export class JobService {
     return this.authHttp.get<ResultList<Job>>(JobService.BASE_JOB_URL + `/${jobState || ''}`, { params })
                .pipe(
                  map(res => this.restExtractor.convertResultListDateToHuman(res, [ 'createdAt', 'processedOn', 'finishedOn' ], 'precise')),
-                 map(res => this.restExtractor.applyToResultListData(res, this.prettyPrintData)),
-                 map(res => this.restExtractor.applyToResultListData(res, this.buildUniqId)),
+                 map(res => this.restExtractor.applyToResultListData(res, this.prettyPrintData.bind(this))),
+                 map(res => this.restExtractor.applyToResultListData(res, this.buildUniqId.bind(this))),
                  catchError(err => this.restExtractor.handleError(err))
                )
   }

+ 14 - 14
client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts

@@ -104,22 +104,22 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
 
   private setRating (nextRating: UserVideoRateType) {
     const ratingMethods: { [id in UserVideoRateType]: (id: string, videoPassword: string) => Observable<any> } = {
-      like: this.videoService.setVideoLike,
-      dislike: this.videoService.setVideoDislike,
-      none: this.videoService.unsetVideoLike
+      like: this.videoService.setVideoLike.bind(this.videoService),
+      dislike: this.videoService.setVideoDislike.bind(this.videoService),
+      none: this.videoService.unsetVideoLike.bind(this.videoService)
     }
 
-    ratingMethods[nextRating].call(this.videoService, this.video.uuid, this.videoPassword)
-          .subscribe({
-            next: () => {
-              // Update the video like attribute
-              this.updateVideoRating(this.userRating, nextRating)
-              this.userRating = nextRating
-              this.rateUpdated.emit(this.userRating)
-            },
-
-            error: err => this.notifier.error(err.message)
-          })
+    ratingMethods[nextRating](this.video.uuid, this.videoPassword)
+      .subscribe({
+        next: () => {
+          // Update the video like attribute
+          this.updateVideoRating(this.userRating, nextRating)
+          this.userRating = nextRating
+          this.rateUpdated.emit(this.userRating)
+        },
+
+        error: err => this.notifier.error(err.message)
+      })
   }
 
   private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {

+ 1 - 1
client/src/app/+videos/+video-watch/shared/comment/video-comment-add.component.ts

@@ -85,7 +85,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnChanges,
   getEmojiMarkupList () {
     if (this.emojiMarkupList) return this.emojiMarkupList
 
-    const emojiMarkupObjectList = require('markdown-it-emoji/lib/data/light.json')
+    const emojiMarkupObjectList = require('markdown-it-emoji/lib/data/light.mjs').default
 
     this.emojiMarkupList = []
     for (const name of Object.keys(emojiMarkupObjectList)) {

+ 1 - 1
client/src/app/app-routing.module.ts

@@ -200,7 +200,7 @@ routes.push({
 @NgModule({
   imports: [
     RouterModule.forRoot(routes, {
-      useHash: Boolean(history.pushState) === false,
+      useHash: false,
       // Redefined in app component
       scrollPositionRestoration: 'disabled',
       preloadingStrategy: PreloadSelectedModulesList,

+ 1 - 1
client/src/app/core/rest/rest-extractor.service.ts

@@ -32,7 +32,7 @@ export class RestExtractor {
     fieldsToConvert: string[] = [ 'createdAt' ],
     format?: DateFormat
   ): ResultList<T> {
-    return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert, format ])
+    return this.applyToResultListData(result, this.convertDateToHuman.bind(this), [ fieldsToConvert, format ])
   }
 
   convertDateToHuman (target: any, fieldsToConvert: string[], format?: DateFormat) {

+ 1 - 1
client/src/app/core/routing/menu-guard.service.ts

@@ -3,7 +3,7 @@ import { MenuService } from '../menu'
 import { ScreenService } from '../wrappers'
 
 abstract class MenuGuard {
-  canDeactivate = this.canActivate
+  canDeactivate = this.canActivate.bind(this)
 
   constructor (protected menu: MenuService, protected screen: ScreenService, protected display: boolean) {
 

+ 2 - 0
client/src/app/menu/menu.component.ts

@@ -244,8 +244,10 @@ export class MenuComponent implements OnInit, OnDestroy {
     if (opened) {
       window.addEventListener('scroll', onWindowScroll)
       document.querySelector('nav').scrollTo(0, 0) // Reset menu scroll to easy lock
+      // eslint-disable-next-line @typescript-eslint/unbound-method
       document.querySelector('nav').addEventListener('scroll', this.onMenuScrollEvent)
     } else {
+      // eslint-disable-next-line @typescript-eslint/unbound-method
       document.querySelector('nav').removeEventListener('scroll', this.onMenuScrollEvent)
     }
   }

+ 2 - 0
client/src/app/menu/notification.component.ts

@@ -67,6 +67,7 @@ export class NotificationComponent implements OnInit, OnDestroy {
     this.opened = true
 
     document.querySelector('nav').scrollTo(0, 0) // Reset menu scroll to easy lock
+    // eslint-disable-next-line @typescript-eslint/unbound-method
     document.querySelector('nav').addEventListener('scroll', this.onMenuScrollEvent)
   }
 
@@ -74,6 +75,7 @@ export class NotificationComponent implements OnInit, OnDestroy {
     this.loaded = false
     this.opened = false
 
+    // eslint-disable-next-line @typescript-eslint/unbound-method
     document.querySelector('nav').removeEventListener('scroll', this.onMenuScrollEvent)
   }
 

+ 2 - 2
client/src/app/shared/shared-main/video/video-ownership.service.ts

@@ -41,12 +41,12 @@ export class VideoOwnershipService {
   acceptOwnership (id: number, input: VideoChangeOwnershipAccept) {
     const url = VideoOwnershipService.BASE_VIDEO_CHANGE_OWNERSHIP_URL + 'ownership/' + id + '/accept'
     return this.authHttp.post(url, input)
-      .pipe(catchError(this.restExtractor.handleError))
+      .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 
   refuseOwnership (id: number) {
     const url = VideoOwnershipService.BASE_VIDEO_CHANGE_OWNERSHIP_URL + 'ownership/' + id + '/refuse'
     return this.authHttp.post(url, {})
-      .pipe(catchError(this.restExtractor.handleError))
+      .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 }

+ 1 - 0
client/src/assets/player/shared/settings/settings-menu-button.ts

@@ -244,6 +244,7 @@ class SettingsButton extends Button {
 
     // Hide children to avoid sub menus stacking on top of each other
     // or having multiple menus open
+    // eslint-disable-next-line @typescript-eslint/unbound-method
     settingsMenuItem.on('click', videojs.bind(this, this.hideChildren))
 
     // Whether to add or remove selected class on the settings sub menu element

+ 1 - 1
client/src/typings.d.ts

@@ -6,4 +6,4 @@ interface NodeModule {
   id: string
 }
 
-declare module 'markdown-it-emoji/light'
+declare module 'markdown-it-emoji/lib/light.mjs'

+ 5 - 0
client/yarn.lock

@@ -7555,6 +7555,11 @@ map-stream@~0.1.0:
   resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194"
   integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==
 
+markdown-it-emoji@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-3.0.0.tgz#8475a04d671d7c93f931b76fb90c582768b7f0b5"
+  integrity sha512-+rUD93bXHubA4arpEZO3q80so0qgoFJEKRkRbjKX8RTdca89v2kfyF+xR3i2sQTwql9tpPZPOQN5B+PunspXRg==
+
 markdown-it@14.0.0:
   version "14.0.0"
   resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-14.0.0.tgz#b4b2ddeb0f925e88d981f84c183b59bac9e3741b"

+ 2 - 2
packages/server-commands/src/server/jobs.ts

@@ -18,8 +18,8 @@ async function waitJobs (
 
   let servers: PeerTubeServer[]
 
-  if (Array.isArray(serversArg) === false) servers = [ serversArg as PeerTubeServer ]
-  else servers = serversArg as PeerTubeServer[]
+  if (Array.isArray(serversArg) === false) servers = [ serversArg ]
+  else servers = serversArg
 
   const states: JobState[] = [ 'waiting', 'active' ]
   if (!skipDelayed) states.push('delayed')

+ 1 - 0
packages/server-commands/src/server/servers.ts

@@ -35,6 +35,7 @@ async function cleanupTests (servers: PeerTubeServer[]) {
   for (const server of servers) {
     if (!server) continue
 
+    // eslint-disable-next-line @typescript-eslint/no-floating-promises
     p = p.concat(server.servers.cleanupTests())
   }
 

+ 1 - 1
packages/tests/src/api/check-params/my-user.ts

@@ -149,7 +149,7 @@ describe('Test my user API validators', function () {
       await makePutBodyRequest({
         url: server.url,
         path: path + 'me',
-        token: 'super token',
+        token: 'supertoken',
         fields,
         expectedStatus: HttpStatusCode.UNAUTHORIZED_401
       })

+ 3 - 3
packages/tests/src/api/check-params/users-admin.ts

@@ -187,7 +187,7 @@ describe('Test users admin API validators', function () {
       await makePostBodyRequest({
         url: server.url,
         path,
-        token: 'super token',
+        token: 'supertoken',
         fields: baseCorrectParams,
         expectedStatus: HttpStatusCode.UNAUTHORIZED_401
       })
@@ -309,7 +309,7 @@ describe('Test users admin API validators', function () {
       await makeGetRequest({
         url: server.url,
         path: path + userId,
-        token: 'super token',
+        token: 'supertoken',
         expectedStatus: HttpStatusCode.UNAUTHORIZED_401
       })
     })
@@ -383,7 +383,7 @@ describe('Test users admin API validators', function () {
       await makePutBodyRequest({
         url: server.url,
         path: path + userId,
-        token: 'super token',
+        token: 'supertoken',
         fields,
         expectedStatus: HttpStatusCode.UNAUTHORIZED_401
       })

+ 2 - 2
packages/tests/src/api/check-params/video-blacklist.ts

@@ -226,7 +226,7 @@ describe('Test video blacklist API validators', function () {
 
     it('Should fail with a non authenticated user', async function () {
       await command.remove({
-        token: 'fake token',
+        token: 'faketoken',
         videoId: servers[0].store.videoCreated.uuid,
         expectedStatus: HttpStatusCode.UNAUTHORIZED_401
       })
@@ -258,7 +258,7 @@ describe('Test video blacklist API validators', function () {
     const basePath = '/api/v1/videos/blacklist/'
 
     it('Should fail with a non authenticated user', async function () {
-      await servers[0].blacklist.list({ token: 'fake token', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
+      await servers[0].blacklist.list({ token: 'faketoken', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
     })
 
     it('Should fail with a non admin user', async function () {

+ 5 - 2
packages/tests/src/api/videos/video-nsfw.ts

@@ -24,6 +24,7 @@ describe('Test video NSFW policy', function () {
     let promises: Promise<ResultList<Video>>[]
 
     if (token) {
+      // eslint-disable-next-line @typescript-eslint/no-floating-promises
       promises = [
         server.search.advancedVideoSearch({ token, search: { search: 'n', sort: '-publishedAt', ...query } }),
         server.videos.listWithToken({ token, ...query }),
@@ -34,13 +35,14 @@ describe('Test video NSFW policy', function () {
       // Overviews do not support video filters
       if (!hasQuery) {
         const p = server.overviews.getVideos({ page: 1, token })
-                                         .then(res => createOverviewRes(res))
+          .then(res => createOverviewRes(res))
         promises.push(p)
       }
 
       return Promise.all(promises)
     }
 
+    // eslint-disable-next-line @typescript-eslint/no-floating-promises
     promises = [
       server.search.searchVideos({ search: 'n', sort: '-publishedAt' }),
       server.videos.list(),
@@ -51,7 +53,8 @@ describe('Test video NSFW policy', function () {
     // Overviews do not support video filters
     if (!hasQuery) {
       const p = server.overviews.getVideos({ page: 1 })
-                                       .then(res => createOverviewRes(res))
+        .then(res => createOverviewRes(res))
+
       promises.push(p)
     }
 

+ 1 - 1
server/core/controllers/api/accounts.ts

@@ -226,7 +226,7 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
   }, 'filter:api.accounts.videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.listForApi,
+    VideoModel.listForApi.bind(VideoModel),
     apiOptions,
     'filter:api.accounts.videos.list.result'
   )

+ 1 - 1
server/core/controllers/api/overviews.ts

@@ -130,7 +130,7 @@ async function getVideos (
   }, 'filter:api.overviews.videos.list.params')
 
   const { data } = await Hooks.wrapPromiseFun(
-    VideoModel.listForApi,
+    VideoModel.listForApi.bind(VideoModel),
     query,
     'filter:api.overviews.videos.list.result'
   )

+ 1 - 1
server/core/controllers/api/search/search-video-channels.ts

@@ -102,7 +102,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQueryAfterSaniti
   }, 'filter:api.search.video-channels.local.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoChannelModel.searchForApi,
+    VideoChannelModel.searchForApi.bind(VideoChannelModel),
     apiOptions,
     'filter:api.search.video-channels.local.list.result'
   )

+ 1 - 1
server/core/controllers/api/search/search-video-playlists.ts

@@ -93,7 +93,7 @@ async function searchVideoPlaylistsDB (query: VideoPlaylistsSearchQueryAfterSani
   }, 'filter:api.search.video-playlists.local.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoPlaylistModel.searchForApi,
+    VideoPlaylistModel.searchForApi.bind(VideoPlaylistModel),
     apiOptions,
     'filter:api.search.video-playlists.local.list.result'
   )

+ 1 - 1
server/core/controllers/api/search/search-videos.ts

@@ -121,7 +121,7 @@ async function searchVideosDB (query: VideosSearchQueryAfterSanitize, req: expre
   }, 'filter:api.search.videos.local.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.searchAndPopulateAccountAndServer,
+    VideoModel.searchAndPopulateAccountAndServer.bind(VideoModel),
     apiOptions,
     'filter:api.search.videos.local.list.result'
   )

+ 1 - 1
server/core/controllers/api/users/me.ts

@@ -131,7 +131,7 @@ async function getUserVideos (req: express.Request, res: express.Response) {
   }, 'filter:api.user.me.videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.listUserVideosForApi,
+    VideoModel.listUserVideosForApi.bind(VideoModel),
     apiOptions,
     'filter:api.user.me.videos.list.result'
   )

+ 1 - 1
server/core/controllers/api/users/my-subscriptions.ts

@@ -184,7 +184,7 @@ async function getUserSubscriptionVideos (req: express.Request, res: express.Res
   }, 'filter:api.user.me.subscription-videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.listForApi,
+    VideoModel.listForApi.bind(VideoModel),
     apiOptions,
     'filter:api.user.me.subscription-videos.list.result'
   )

+ 2 - 2
server/core/controllers/api/video-channel.ts

@@ -200,7 +200,7 @@ async function listVideoChannels (req: express.Request, res: express.Response) {
   }, 'filter:api.video-channels.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoChannelModel.listForApi,
+    VideoChannelModel.listForApi.bind(VideoChannelModel),
     apiOptions,
     'filter:api.video-channels.list.result'
   )
@@ -409,7 +409,7 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
   }, 'filter:api.video-channels.videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.listForApi,
+    VideoModel.listForApi.bind(VideoModel),
     apiOptions,
     'filter:api.video-channels.videos.list.result'
   )

+ 1 - 1
server/core/controllers/api/video-playlist.ts

@@ -472,7 +472,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon
   }, 'filter:api.video-playlist.videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoPlaylistElementModel.listForApi,
+    VideoPlaylistElementModel.listForApi.bind(VideoPlaylistElementModel),
     apiOptions,
     'filter:api.video-playlist.videos.list.result'
   )

+ 2 - 2
server/core/controllers/api/videos/comment.ts

@@ -128,7 +128,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) {
     }, 'filter:api.video-threads.list.params')
 
     resultList = await Hooks.wrapPromiseFun(
-      VideoCommentModel.listThreadsForApi,
+      VideoCommentModel.listThreadsForApi.bind(VideoCommentModel),
       apiOptions,
       'filter:api.video-threads.list.result'
     )
@@ -160,7 +160,7 @@ async function listVideoThreadComments (req: express.Request, res: express.Respo
     }, 'filter:api.video-thread-comments.list.params')
 
     resultList = await Hooks.wrapPromiseFun(
-      VideoCommentModel.listThreadCommentsForApi,
+      VideoCommentModel.listThreadCommentsForApi.bind(VideoCommentModel),
       apiOptions,
       'filter:api.video-thread-comments.list.result'
     )

+ 1 - 1
server/core/controllers/api/videos/index.ts

@@ -194,7 +194,7 @@ async function listVideos (req: express.Request, res: express.Response) {
   }, 'filter:api.videos.list.params')
 
   const resultList = await Hooks.wrapPromiseFun(
-    VideoModel.listForApi,
+    VideoModel.listForApi.bind(VideoModel),
     apiOptions,
     'filter:api.videos.list.result'
   )

+ 1 - 1
server/core/controllers/download.ts

@@ -133,7 +133,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
 async function downloadHLSVideoFile (req: express.Request, res: express.Response) {
   const video = res.locals.videoAll
   const streamingPlaylist = getHLSPlaylist(video)
-  if (!streamingPlaylist) return res.status(HttpStatusCode.NOT_FOUND_404).end
+  if (!streamingPlaylist) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
 
   const videoFile = getVideoFile(req, streamingPlaylist.VideoFiles)
   if (!videoFile) {

+ 4 - 4
server/core/helpers/logger.ts

@@ -121,10 +121,10 @@ const bunyanLogger = {
 
 // ---------------------------------------------------------------------------
 
-type LoggerTags = { tags: string[] }
-type LoggerTagsFn = (...tags: string[]) => LoggerTags
-function loggerTagsFactory (...defaultTags: string[]): LoggerTagsFn {
-  return (...tags: string[]) => {
+type LoggerTags = { tags: (string | number)[] }
+type LoggerTagsFn = (...tags: (string | number)[]) => LoggerTags
+function loggerTagsFactory (...defaultTags: (string | number)[]): LoggerTagsFn {
+  return (...tags: (string | number)[]) => {
     return { tags: defaultTags.concat(tags) }
   }
 }

+ 1 - 1
server/core/lib/files-cache/shared/abstract-simple-file-cache.ts

@@ -14,7 +14,7 @@ export abstract class AbstractSimpleFileCache <T> {
   protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult>
 
   init (max: number, maxAge: number) {
-    this.getFilePath = memoizee(this.getFilePathImpl, {
+    this.getFilePath = memoizee(this.getFilePathImpl.bind(this), {
       maxAge,
       max,
       promise: true,

+ 1 - 1
server/core/lib/job-queue/handlers/shared/move-video.ts

@@ -7,7 +7,7 @@ import { MVideoWithAllFiles } from '@server/types/models/index.js'
 export async function moveToJob (options: {
   jobId: string
   videoUUID: string
-  loggerTags: string[]
+  loggerTags: (number | string)[]
 
   moveWebVideoFiles: (video: MVideoWithAllFiles) => Promise<void>
   moveHLSFiles: (video: MVideoWithAllFiles) => Promise<void>

+ 1 - 1
server/core/lib/job-queue/handlers/video-studio-edition.ts

@@ -95,7 +95,7 @@ type TaskProcessorOptions <T extends VideoStudioTaskPayload = VideoStudioTaskPay
   outputPath: string
   video: MVideo
   task: T
-  lTags: { tags: string[] }
+  lTags: { tags: (string | number)[] }
 }
 
 const taskProcessors: { [id in VideoStudioTask['name']]: (options: TaskProcessorOptions) => Promise<any> } = {

+ 2 - 2
server/core/lib/live/live-manager.ts

@@ -86,7 +86,7 @@ class LiveManager {
         .catch(err => logger.error('Cannot handle sessions.', { err, ...lTags(sessionId) }))
     })
 
-    events.on('donePublish', sessionId => {
+    events.on('donePublish', (sessionId: string) => {
       logger.info('Live session ended.', { sessionId, ...lTags(sessionId) })
 
       // Force session aborting, so we kill ffmpeg even if it still has data to process (slow CPU)
@@ -405,7 +405,7 @@ class LiveManager {
       })
   }
 
-  private async publishAndFederateLive (live: MVideoLiveVideo, localLTags: { tags: string[] }) {
+  private async publishAndFederateLive (live: MVideoLiveVideo, localLTags: { tags: (string | number)[] }) {
     const videoId = live.videoId
 
     try {

+ 2 - 2
server/core/lib/object-storage/shared/object-storage-helpers.ts

@@ -11,7 +11,7 @@ import { getInternalUrl } from '../urls.js'
 import { getClient } from './client.js'
 import { lTags } from './logger.js'
 
-import type { _Object, CompleteMultipartUploadCommandOutput, ObjectCannedACL, PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3'
+import type { _Object, ObjectCannedACL, PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3'
 
 type BucketInfo = {
   BUCKET_NAME: string
@@ -317,7 +317,7 @@ async function uploadToStorage (options: {
     params: input
   })
 
-  const response = (await parallelUploads3.done()) as CompleteMultipartUploadCommandOutput
+  const response = await parallelUploads3.done()
   // Check is needed even if the HTTP status code is 200 OK
   // For more information, see https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html
   if (!response.Bucket) {

+ 6 - 4
server/core/lib/peertube-socket.ts

@@ -42,14 +42,16 @@ class PeerTubeSocket {
 
     this.liveVideosNamespace = io.of('/live-videos')
       .on('connection', socket => {
-        socket.on('subscribe', ({ videoId }) => {
+        socket.on('subscribe', params => {
+          const videoId = params.videoId + ''
           if (!isIdValid(videoId)) return
 
           /* eslint-disable @typescript-eslint/no-floating-promises */
           socket.join(videoId)
         })
 
-        socket.on('unsubscribe', ({ videoId }) => {
+        socket.on('unsubscribe', params => {
+          const videoId = params.videoId + ''
           if (!isIdValid(videoId)) return
 
           /* eslint-disable @typescript-eslint/no-floating-promises */
@@ -93,7 +95,7 @@ class PeerTubeSocket {
     logger.debug('Sending video live new state notification of %s.', video.url, { state: video.state })
 
     this.liveVideosNamespace
-      .in(video.id)
+      .in(video.id + '')
       .emit(type, data)
   }
 
@@ -104,7 +106,7 @@ class PeerTubeSocket {
     logger.debug('Sending video live views update notification of %s.', video.url, { viewers: numViewers })
 
     this.liveVideosNamespace
-      .in(video.id)
+      .in(video.id + '')
       .emit(type, data)
   }
 

+ 1 - 1
server/core/middlewares/async.ts

@@ -15,7 +15,7 @@ function asyncMiddleware (fun: RequestPromiseHandler | RequestPromiseHandler[])
     }
 
     try {
-      for (const f of (fun as RequestPromiseHandler[])) {
+      for (const f of fun) {
         await new Promise<void>((resolve, reject) => {
           return asyncMiddleware(f)(req, res, err => {
             if (err) return reject(err)

+ 3 - 3
server/core/middlewares/cache/shared/api-cache.ts

@@ -189,9 +189,9 @@ export class ApiCache {
     const self = this
 
     res.locals.apicache = {
-      write: res.write,
-      writeHead: res.writeHead,
-      end: res.end,
+      write: res.write.bind(res),
+      writeHead: res.writeHead.bind(res),
+      end: res.end.bind(res),
       cacheable: true,
       content: undefined,
       headers: undefined

+ 1 - 1
server/core/middlewares/validators/shared/utils.ts

@@ -9,7 +9,7 @@ function areValidationErrors (
   options: {
     omitLog?: boolean
     omitBodyLog?: boolean
-    tags?: string[]
+    tags?: (number | string)[]
   } = {}) {
   const { omitLog = false, omitBodyLog = false, tags = [] } = options
 

+ 3 - 4
server/core/models/abuse/abuse-message.ts

@@ -1,11 +1,10 @@
 import { FindOptions } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses.js'
 import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models/index.js'
 import { AbuseMessage } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account.js'
-import { getSort, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
 import { AbuseModel } from './abuse.js'
 
 @Table({
@@ -19,7 +18,7 @@ import { AbuseModel } from './abuse.js'
     }
   ]
 })
-export class AbuseMessageModel extends Model<Partial<AttributesOnly<AbuseMessageModel>>> {
+export class AbuseMessageModel extends SequelizeModel<AbuseMessageModel> {
 
   @AllowNull(false)
   @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message'))

+ 3 - 6
server/core/models/abuse/abuse.ts

@@ -12,7 +12,6 @@ import {
   UserVideoAbuse,
   type AbuseStateType
 } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses.js'
 import invert from 'lodash-es/invert.js'
 import { Op, QueryTypes, literal } from 'sequelize'
@@ -25,9 +24,7 @@ import {
   Default,
   ForeignKey,
   HasOne,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -41,7 +38,7 @@ import {
   MUserAccountId
 } from '../../types/models/index.js'
 import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account.js'
-import { getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
 import { ThumbnailModel } from '../video/thumbnail.js'
 import { VideoBlacklistModel } from '../video/video-blacklist.js'
 import { SummaryOptions as ChannelSummaryOptions, VideoChannelModel, ScopeNames as VideoChannelScopeNames } from '../video/video-channel.js'
@@ -195,7 +192,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class AbuseModel extends Model<Partial<AttributesOnly<AbuseModel>>> {
+export class AbuseModel extends SequelizeModel<AbuseModel> {
 
   @AllowNull(false)
   @Default(null)

+ 3 - 3
server/core/models/abuse/video-abuse.ts

@@ -1,8 +1,8 @@
 import { type VideoDetails } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoModel } from '../video/video.js'
 import { AbuseModel } from './abuse.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'videoAbuse',
@@ -15,7 +15,7 @@ import { AbuseModel } from './abuse.js'
     }
   ]
 })
-export class VideoAbuseModel extends Model<Partial<AttributesOnly<VideoAbuseModel>>> {
+export class VideoAbuseModel extends SequelizeModel<VideoAbuseModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 3
server/core/models/abuse/video-comment-abuse.ts

@@ -1,7 +1,7 @@
-import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
+import { BelongsTo, Column, CreatedAt, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoCommentModel } from '../video/video-comment.js'
 import { AbuseModel } from './abuse.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'commentAbuse',
@@ -14,7 +14,7 @@ import { AbuseModel } from './abuse.js'
     }
   ]
 })
-export class VideoCommentAbuseModel extends Model<Partial<AttributesOnly<VideoCommentAbuseModel>>> {
+export class VideoCommentAbuseModel extends SequelizeModel<VideoCommentAbuseModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 4
server/core/models/account/account-blocklist.ts

@@ -1,12 +1,11 @@
 import { FindOptions, Op, QueryTypes } from 'sequelize'
-import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { BelongsTo, Column, CreatedAt, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { AccountBlock } from '@peertube/peertube-models'
 import { handlesToNameAndHost } from '@server/helpers/actors.js'
 import { MAccountBlocklist, MAccountBlocklistFormattable } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { ActorModel } from '../actor/actor.js'
 import { ServerModel } from '../server/server.js'
-import { createSafeIn, getSort, searchAttribute } from '../shared/index.js'
+import { SequelizeModel, createSafeIn, getSort, searchAttribute } from '../shared/index.js'
 import { AccountModel } from './account.js'
 import { WEBSERVER } from '@server/initializers/constants.js'
 
@@ -22,7 +21,7 @@ import { WEBSERVER } from '@server/initializers/constants.js'
     }
   ]
 })
-export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountBlocklistModel>>> {
+export class AccountBlocklistModel extends SequelizeModel<AccountBlocklistModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 4
server/core/models/account/account-video-rate.ts

@@ -1,5 +1,4 @@
 import { AccountVideoRate, type VideoRateType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import {
   MAccountVideoRate,
   MAccountVideoRateAccountUrl,
@@ -8,11 +7,11 @@ import {
   MAccountVideoRateVideoUrl
 } from '@server/types/models/index.js'
 import { FindOptions, Op, QueryTypes, Transaction } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
 import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants.js'
 import { ActorModel } from '../actor/actor.js'
-import { getSort, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
 import { SummaryOptions, VideoChannelModel, ScopeNames as VideoChannelScopeNames } from '../video/video-channel.js'
 import { VideoModel } from '../video/video.js'
 import { AccountModel } from './account.js'
@@ -42,7 +41,7 @@ import { AccountModel } from './account.js'
     }
   ]
 })
-export class AccountVideoRateModel extends Model<Partial<AttributesOnly<AccountVideoRateModel>>> {
+export class AccountVideoRateModel extends SequelizeModel<AccountVideoRateModel> {
 
   @AllowNull(false)
   @Column(DataType.ENUM(...Object.values(VIDEO_RATE_TYPES)))

+ 3 - 6
server/core/models/account/account.ts

@@ -10,15 +10,12 @@ import {
   DefaultScope,
   ForeignKey,
   HasMany,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { Account, AccountSummary } from '@peertube/peertube-models'
 import { ModelCache } from '@server/models/shared/model-cache.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts.js'
 import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants.js'
 import { sendDeleteActor } from '../../lib/activitypub/send/send-delete.js'
@@ -36,7 +33,7 @@ import { ActorModel } from '../actor/actor.js'
 import { ApplicationModel } from '../application/application.js'
 import { ServerBlocklistModel } from '../server/server-blocklist.js'
 import { ServerModel } from '../server/server.js'
-import { buildSQLAttributes, getSort, throwIfNotValid } from '../shared/index.js'
+import { buildSQLAttributes, getSort, SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { UserModel } from '../user/user.js'
 import { VideoChannelModel } from '../video/video-channel.js'
 import { VideoCommentModel } from '../video/video-comment.js'
@@ -144,7 +141,7 @@ export type SummaryOptions = {
     }
   ]
 })
-export class AccountModel extends Model<Partial<AttributesOnly<AccountModel>>> {
+export class AccountModel extends SequelizeModel<AccountModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 2
server/core/models/account/actor-custom-page.ts

@@ -1,7 +1,8 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { CustomPage } from '@peertube/peertube-models'
 import { ActorModel } from '../actor/actor.js'
 import { getServerActor } from '../application/application.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'actorCustomPage',
@@ -12,7 +13,7 @@ import { getServerActor } from '../application/application.js'
     }
   ]
 })
-export class ActorCustomPageModel extends Model {
+export class ActorCustomPageModel extends SequelizeModel<ActorCustomPageModel> {
 
   @AllowNull(true)
   @Column(DataType.TEXT)

+ 3 - 6
server/core/models/actor/actor-follow.ts

@@ -1,5 +1,4 @@
 import { ActorFollow, type FollowState } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
 import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
 import { getServerActor } from '@server/models/application/application.js'
@@ -27,16 +26,14 @@ import {
   ForeignKey,
   Is,
   IsInt,
-  Max,
-  Model,
-  Table,
+  Max, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { logger } from '../../helpers/logger.js'
 import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME, SORTABLE_COLUMNS } from '../../initializers/constants.js'
 import { AccountModel } from '../account/account.js'
 import { ServerModel } from '../server/server.js'
-import { buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildSQLAttributes, createSafeIn, getSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
 import { doesExist } from '../shared/query.js'
 import { VideoChannelModel } from '../video/video-channel.js'
 import { ActorModel, unusedActorAttributesForAPI } from './actor.js'
@@ -65,7 +62,7 @@ import { InstanceListFollowingQueryBuilder, ListFollowingOptions } from './sql/i
     }
   ]
 })
-export class ActorFollowModel extends Model<Partial<AttributesOnly<ActorFollowModel>>> {
+export class ActorFollowModel extends SequelizeModel<ActorFollowModel> {
 
   @AllowNull(false)
   @Column(DataType.ENUM(...Object.values(FOLLOW_STATES)))

+ 3 - 6
server/core/models/actor/actor-image.ts

@@ -1,6 +1,5 @@
 import { ActivityIconObject, ActorImage, ActorImageType, type ActorImageType_Type } from '@peertube/peertube-models'
 import { getLowercaseExtension } from '@peertube/peertube-node-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { MActorId, MActorImage, MActorImageFormattable } from '@server/types/models/index.js'
 import { remove } from 'fs-extra/esm'
 import { join } from 'path'
@@ -12,16 +11,14 @@ import {
   CreatedAt,
   Default,
   ForeignKey,
-  Is,
-  Model,
-  Table,
+  Is, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
 import { logger } from '../../helpers/logger.js'
 import { CONFIG } from '../../initializers/config.js'
 import { LAZY_STATIC_PATHS, MIMETYPES, WEBSERVER } from '../../initializers/constants.js'
-import { buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
 import { ActorModel } from './actor.js'
 
 @Table({
@@ -37,7 +34,7 @@ import { ActorModel } from './actor.js'
     }
   ]
 })
-export class ActorImageModel extends Model<Partial<AttributesOnly<ActorImageModel>>> {
+export class ActorImageModel extends SequelizeModel<ActorImageModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 5
server/core/models/actor/actor.ts

@@ -17,9 +17,7 @@ import {
   ForeignKey,
   HasMany,
   HasOne,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -58,7 +56,7 @@ import {
 import { AccountModel } from '../account/account.js'
 import { getServerActor } from '../application/application.js'
 import { ServerModel } from '../server/server.js'
-import { buildSQLAttributes, isOutdated, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildSQLAttributes, isOutdated, throwIfNotValid } from '../shared/index.js'
 import { VideoChannelModel } from '../video/video-channel.js'
 import { VideoModel } from '../video/video.js'
 import { ActorFollowModel } from './actor-follow.js'
@@ -165,7 +163,7 @@ export const unusedActorAttributesForAPI: (keyof AttributesOnly<ActorModel>)[] =
     }
   ]
 })
-export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
+export class ActorModel extends SequelizeModel<ActorModel> {
 
   @AllowNull(false)
   @Column(DataType.ENUM(...Object.values(ACTIVITY_PUB_ACTOR_TYPES)))

+ 3 - 3
server/core/models/application/application.ts

@@ -1,8 +1,8 @@
 import memoizee from 'memoizee'
-import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
+import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Table } from 'sequelize-typescript'
 import { getNodeABIVersion } from '@server/helpers/version.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { AccountModel } from '../account/account.js'
+import { SequelizeModel } from '../shared/index.js'
 
 export const getServerActor = memoizee(async function () {
   const application = await ApplicationModel.load()
@@ -26,7 +26,7 @@ export const getServerActor = memoizee(async function () {
   tableName: 'application',
   timestamps: false
 })
-export class ApplicationModel extends Model<Partial<AttributesOnly<ApplicationModel>>> {
+export class ApplicationModel extends SequelizeModel<ApplicationModel> {
 
   @AllowNull(false)
   @Default(0)

+ 3 - 3
server/core/models/oauth/oauth-client.ts

@@ -1,6 +1,6 @@
-import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
+import { AllowNull, Column, CreatedAt, DataType, HasMany, Table, UpdatedAt } from 'sequelize-typescript'
 import { OAuthTokenModel } from './oauth-token.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'oAuthClient',
@@ -15,7 +15,7 @@ import { OAuthTokenModel } from './oauth-token.js'
     }
   ]
 })
-export class OAuthClientModel extends Model<Partial<AttributesOnly<OAuthClientModel>>> {
+export class OAuthClientModel extends SequelizeModel<OAuthClientModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 5
server/core/models/oauth/oauth-token.ts

@@ -6,21 +6,19 @@ import {
   BelongsTo,
   Column,
   CreatedAt,
-  ForeignKey,
-  Model,
-  Scopes,
+  ForeignKey, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { TokensCache } from '@server/lib/auth/tokens-cache.js'
 import { MUserAccountId } from '@server/types/models/index.js'
 import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { logger } from '../../helpers/logger.js'
 import { AccountModel } from '../account/account.js'
 import { ActorModel } from '../actor/actor.js'
 import { UserModel } from '../user/user.js'
 import { OAuthClientModel } from './oauth-client.js'
+import { SequelizeModel } from '../shared/index.js'
 
 export type OAuthTokenInfo = {
   refreshToken: string
@@ -80,7 +78,7 @@ enum ScopeNames {
     }
   ]
 })
-export class OAuthTokenModel extends Model<Partial<AttributesOnly<OAuthTokenModel>>> {
+export class OAuthTokenModel extends SequelizeModel<OAuthTokenModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 6
server/core/models/redundancy/video-redundancy.ts

@@ -8,9 +8,7 @@ import {
   CreatedAt,
   DataType,
   ForeignKey,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -26,7 +24,6 @@ import {
   VideoRedundancyStrategyWithManual
 } from '@peertube/peertube-models'
 import { isTestInstance } from '@peertube/peertube-node-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { getServerActor } from '@server/models/application/application.js'
 import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models/index.js'
 import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
@@ -35,7 +32,7 @@ import { CONFIG } from '../../initializers/config.js'
 import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
 import { ActorModel } from '../actor/actor.js'
 import { ServerModel } from '../server/server.js'
-import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
+import { getSort, getVideoSort, parseAggregateResult, SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { ScheduleVideoUpdateModel } from '../video/schedule-video-update.js'
 import { VideoChannelModel } from '../video/video-channel.js'
 import { VideoFileModel } from '../video/video-file.js'
@@ -92,7 +89,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class VideoRedundancyModel extends Model<Partial<AttributesOnly<VideoRedundancyModel>>> {
+export class VideoRedundancyModel extends SequelizeModel<VideoRedundancyModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 6
server/core/models/runner/runner-job.ts

@@ -7,7 +7,6 @@ import {
   type RunnerJobStateType,
   type RunnerJobType
 } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isArray, isUUIDValid } from '@server/helpers/custom-validators/misc.js'
 import { CONSTRAINTS_FIELDS, RUNNER_JOB_STATES } from '@server/initializers/constants.js'
 import { MRunnerJob, MRunnerJobRunner, MRunnerJobRunnerParent } from '@server/types/models/runners/index.js'
@@ -20,13 +19,11 @@ import {
   DataType,
   Default,
   ForeignKey,
-  IsUUID,
-  Model,
-  Scopes,
+  IsUUID, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
-import { getSort, searchAttribute } from '../shared/index.js'
+import { SequelizeModel, getSort, searchAttribute } from '../shared/index.js'
 import { RunnerModel } from './runner.js'
 
 enum ScopeNames {
@@ -68,7 +65,7 @@ enum ScopeNames {
     }
   ]
 })
-export class RunnerJobModel extends Model<Partial<AttributesOnly<RunnerJobModel>>> {
+export class RunnerJobModel extends SequelizeModel<RunnerJobModel> {
 
   @AllowNull(false)
   @IsUUID(4)

+ 3 - 4
server/core/models/runner/runner-registration-token.ts

@@ -1,9 +1,8 @@
 import { FindOptions, literal } from 'sequelize'
-import { AllowNull, Column, CreatedAt, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, Column, CreatedAt, HasMany, Table, UpdatedAt } from 'sequelize-typescript'
 import { MRunnerRegistrationToken } from '@server/types/models/runners/index.js'
 import { RunnerRegistrationToken } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
-import { getSort } from '../shared/index.js'
+import { SequelizeModel, getSort } from '../shared/index.js'
 import { RunnerModel } from './runner.js'
 
 /**
@@ -21,7 +20,7 @@ import { RunnerModel } from './runner.js'
     }
   ]
 })
-export class RunnerRegistrationTokenModel extends Model<Partial<AttributesOnly<RunnerRegistrationTokenModel>>> {
+export class RunnerRegistrationTokenModel extends SequelizeModel<RunnerRegistrationTokenModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 4
server/core/models/runner/runner.ts

@@ -1,9 +1,8 @@
 import { FindOptions } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { MRunner } from '@server/types/models/runners/index.js'
 import { Runner } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
-import { getSort } from '../shared/index.js'
+import { SequelizeModel, getSort } from '../shared/index.js'
 import { RunnerRegistrationTokenModel } from './runner-registration-token.js'
 import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
 
@@ -23,7 +22,7 @@ import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
     }
   ]
 })
-export class RunnerModel extends Model<Partial<AttributesOnly<RunnerModel>>> {
+export class RunnerModel extends SequelizeModel<RunnerModel> {
 
   // Used to identify the appropriate runner when it uses the runner REST API
   @AllowNull(false)

+ 3 - 4
server/core/models/server/plugin.ts

@@ -6,10 +6,9 @@ import {
   SettingValue,
   type PluginType_Type
 } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { MPlugin, MPluginFormattable } from '@server/types/models/index.js'
 import { FindAndCountOptions, json, QueryTypes } from 'sequelize'
-import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import {
   isPluginDescriptionValid,
   isPluginHomepage,
@@ -18,7 +17,7 @@ import {
   isPluginStableVersionValid,
   isPluginTypeValid
 } from '../../helpers/custom-validators/plugins.js'
-import { getSort, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
 
 @DefaultScope(() => ({
   attributes: {
@@ -35,7 +34,7 @@ import { getSort, throwIfNotValid } from '../shared/index.js'
     }
   ]
 })
-export class PluginModel extends Model<Partial<AttributesOnly<PluginModel>>> {
+export class PluginModel extends SequelizeModel<PluginModel> {
 
   @AllowNull(false)
   @Is('PluginName', value => throwIfNotValid(value, isPluginNameValid, 'name'))

+ 3 - 4
server/core/models/server/server-blocklist.ts

@@ -1,10 +1,9 @@
 import { Op, QueryTypes } from 'sequelize'
-import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { BelongsTo, Column, CreatedAt, ForeignKey, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
 import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models/index.js'
 import { ServerBlock } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { AccountModel } from '../account/account.js'
-import { createSafeIn, getSort, searchAttribute } from '../shared/index.js'
+import { SequelizeModel, createSafeIn, getSort, searchAttribute } from '../shared/index.js'
 import { ServerModel } from './server.js'
 
 enum ScopeNames {
@@ -43,7 +42,7 @@ enum ScopeNames {
     }
   ]
 })
-export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlocklistModel>>> {
+export class ServerBlocklistModel extends SequelizeModel<ServerBlocklistModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 4
server/core/models/server/server.ts

@@ -1,10 +1,9 @@
 import { Transaction } from 'sequelize'
-import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { MServer, MServerFormattable } from '@server/types/models/server/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isHostValid } from '../../helpers/custom-validators/servers.js'
 import { ActorModel } from '../actor/actor.js'
-import { buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
 import { ServerBlocklistModel } from './server-blocklist.js'
 
 @Table({
@@ -16,7 +15,7 @@ import { ServerBlocklistModel } from './server-blocklist.js'
     }
   ]
 })
-export class ServerModel extends Model<Partial<AttributesOnly<ServerModel>>> {
+export class ServerModel extends SequelizeModel<ServerModel> {
 
   @AllowNull(false)
   @Is('Host', value => throwIfNotValid(value, isHostValid, 'valid host'))

+ 3 - 3
server/core/models/server/tracker.ts

@@ -1,9 +1,9 @@
-import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsToMany, Column, CreatedAt, Table, UpdatedAt } from 'sequelize-typescript'
 import { Transaction } from 'sequelize'
 import { MTracker } from '@server/types/models/server/tracker.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { VideoModel } from '../video/video.js'
 import { VideoTrackerModel } from './video-tracker.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 @Table({
   tableName: 'tracker',
@@ -14,7 +14,7 @@ import { VideoTrackerModel } from './video-tracker.js'
     }
   ]
 })
-export class TrackerModel extends Model<Partial<AttributesOnly<TrackerModel>>> {
+export class TrackerModel extends SequelizeModel<TrackerModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 3
server/core/models/server/video-tracker.ts

@@ -1,7 +1,7 @@
-import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
+import { Column, CreatedAt, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoModel } from '../video/video.js'
 import { TrackerModel } from './tracker.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'videoTracker',
@@ -14,7 +14,7 @@ import { TrackerModel } from './tracker.js'
     }
   ]
 })
-export class VideoTrackerModel extends Model<Partial<AttributesOnly<VideoTrackerModel>>> {
+export class VideoTrackerModel extends SequelizeModel<VideoTrackerModel> {
   @CreatedAt
   createdAt: Date
 

+ 1 - 0
server/core/models/shared/index.ts

@@ -3,6 +3,7 @@ export * from './model-builder.js'
 export * from './model-cache.js'
 export * from './query.js'
 export * from './sequelize-helpers.js'
+export * from './sequelize-type.js'
 export * from './sort.js'
 export * from './sql.js'
 export * from './update.js'

+ 6 - 0
server/core/models/shared/sequelize-type.ts

@@ -0,0 +1,6 @@
+import { AttributesOnly } from '@peertube/peertube-typescript-utils'
+import { Model } from 'sequelize-typescript'
+
+export abstract class SequelizeModel <T> extends Model<Partial<AttributesOnly<T>>> {
+  id: number
+}

+ 3 - 3
server/core/models/user/user-export.ts

@@ -1,7 +1,6 @@
 import { FindOptions, Op } from 'sequelize'
-import { AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { MUserAccountId, MUserExport } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { UserModel } from './user.js'
 import { getSort } from '../shared/sort.js'
 import { UserExportState, type UserExport, type UserExportStateType, type FileStorageType, FileStorage } from '@peertube/peertube-models'
@@ -18,6 +17,7 @@ import { join } from 'path'
 import jwt from 'jsonwebtoken'
 import { CONFIG } from '@server/initializers/config.js'
 import { removeUserExportObjectStorage } from '@server/lib/object-storage/user-export.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 @Table({
   tableName: 'userExport',
@@ -31,7 +31,7 @@ import { removeUserExportObjectStorage } from '@server/lib/object-storage/user-e
     }
   ]
 })
-export class UserExportModel extends Model<Partial<AttributesOnly<UserExportModel>>> {
+export class UserExportModel extends SequelizeModel<UserExportModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 3
server/core/models/user/user-import.ts

@@ -1,6 +1,6 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { MUserImport } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
+import { SequelizeModel } from '../shared/index.js'
 import { UserModel } from './user.js'
 import type { UserImportResultSummary, UserImportStateType } from '@peertube/peertube-models'
 import { getSort } from '../shared/sort.js'
@@ -18,7 +18,7 @@ import { USER_IMPORT_STATES } from '@server/initializers/constants.js'
     }
   ]
 })
-export class UserImportModel extends Model<Partial<AttributesOnly<UserImportModel>>> {
+export class UserImportModel extends SequelizeModel<UserImportModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 6
server/core/models/user/user-notification-setting.ts

@@ -1,5 +1,4 @@
 import { type UserNotificationSetting, type UserNotificationSettingValueType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { TokensCache } from '@server/lib/auth/tokens-cache.js'
 import { MNotificationSettingFormattable } from '@server/types/models/index.js'
 import {
@@ -11,13 +10,11 @@ import {
   CreatedAt,
   Default,
   ForeignKey,
-  Is,
-  Model,
-  Table,
+  Is, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications.js'
-import { throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { UserModel } from './user.js'
 
 @Table({
@@ -29,7 +26,7 @@ import { UserModel } from './user.js'
     }
   ]
 })
-export class UserNotificationSettingModel extends Model<Partial<AttributesOnly<UserNotificationSettingModel>>> {
+export class UserNotificationSettingModel extends SequelizeModel<UserNotificationSettingModel> {
 
   @AllowNull(false)
   @Default(null)

+ 3 - 4
server/core/models/user/user-notification.ts

@@ -1,11 +1,10 @@
 import { forceNumber } from '@peertube/peertube-core-utils'
 import { UserNotification, type UserNotificationType_Type } from '@peertube/peertube-models'
 import { uuidToShort } from '@peertube/peertube-node-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { getBiggestActorImage } from '@server/lib/actor-image.js'
 import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user/index.js'
 import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { isBooleanValid } from '../../helpers/custom-validators/misc.js'
 import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications.js'
 import { AbuseModel } from '../abuse/abuse.js'
@@ -13,7 +12,7 @@ import { AccountModel } from '../account/account.js'
 import { ActorFollowModel } from '../actor/actor-follow.js'
 import { ApplicationModel } from '../application/application.js'
 import { PluginModel } from '../server/plugin.js'
-import { throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { VideoBlacklistModel } from '../video/video-blacklist.js'
 import { VideoCommentModel } from '../video/video-comment.js'
 import { VideoImportModel } from '../video/video-import.js'
@@ -110,7 +109,7 @@ import { UserModel } from './user.js'
     }
   ] as (ModelIndexesOptions & { where?: WhereOptions })[]
 })
-export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNotificationModel>>> {
+export class UserNotificationModel extends SequelizeModel<UserNotificationModel> {
 
   @AllowNull(false)
   @Default(null)

+ 3 - 6
server/core/models/user/user-registration.ts

@@ -1,5 +1,4 @@
 import { UserRegistration, type UserRegistrationStateType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import {
   isRegistrationModerationResponseValid,
   isRegistrationReasonValid,
@@ -19,13 +18,11 @@ import {
   DataType,
   ForeignKey,
   Is,
-  IsEmail,
-  Model,
-  Table,
+  IsEmail, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { isUserDisplayNameValid, isUserEmailVerifiedValid, isUserPasswordValid } from '../../helpers/custom-validators/users.js'
-import { getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
 import { UserModel } from './user.js'
 import { forceNumber } from '@peertube/peertube-core-utils'
 
@@ -50,7 +47,7 @@ import { forceNumber } from '@peertube/peertube-core-utils'
     }
   ]
 })
-export class UserRegistrationModel extends Model<Partial<AttributesOnly<UserRegistrationModel>>> {
+export class UserRegistrationModel extends SequelizeModel<UserRegistrationModel> {
 
   @AllowNull(false)
   @Is('RegistrationState', value => throwIfNotValid(value, isRegistrationStateValid, 'state'))

+ 3 - 3
server/core/models/user/user-video-history.ts

@@ -1,10 +1,10 @@
 import { DestroyOptions, Op, Transaction } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Table, UpdatedAt } from 'sequelize-typescript'
 import { ResultList } from '@peertube/peertube-models'
 import { MUserAccountId, MUserId } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { VideoModel } from '../video/video.js'
 import { UserModel } from './user.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 @Table({
   tableName: 'userVideoHistory',
@@ -21,7 +21,7 @@ import { UserModel } from './user.js'
     }
   ]
 })
-export class UserVideoHistoryModel extends Model<Partial<AttributesOnly<UserVideoHistoryModel>>> {
+export class UserVideoHistoryModel extends SequelizeModel<UserVideoHistoryModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 6
server/core/models/user/user.ts

@@ -10,7 +10,6 @@ import {
   type UserAdminFlagType,
   type UserRoleType
 } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { TokensCache } from '@server/lib/auth/tokens-cache.js'
 import { LiveQuotaStore } from '@server/lib/live/index.js'
 import {
@@ -37,9 +36,7 @@ import {
   HasOne,
   Is,
   IsEmail,
-  IsUUID,
-  Model,
-  Scopes,
+  IsUUID, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -70,7 +67,7 @@ import { ActorFollowModel } from '../actor/actor-follow.js'
 import { ActorImageModel } from '../actor/actor-image.js'
 import { ActorModel } from '../actor/actor.js'
 import { OAuthTokenModel } from '../oauth/oauth-token.js'
-import { getAdminUsersSort, throwIfNotValid } from '../shared/index.js'
+import { getAdminUsersSort, SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { VideoChannelModel } from '../video/video-channel.js'
 import { VideoImportModel } from '../video/video-import.js'
 import { VideoLiveModel } from '../video/video-live.js'
@@ -278,7 +275,7 @@ enum ScopeNames {
     }
   ]
 })
-export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
+export class UserModel extends SequelizeModel<UserModel> {
 
   @AllowNull(true)
   @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true))

+ 3 - 3
server/core/models/video/schedule-video-update.ts

@@ -1,9 +1,9 @@
 import { Op, Transaction } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoPrivacy } from '@peertube/peertube-models'
 import { MScheduleVideoUpdate, MScheduleVideoUpdateFormattable } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { VideoModel } from './video.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'scheduleVideoUpdate',
@@ -17,7 +17,7 @@ import { VideoModel } from './video.js'
     }
   ]
 })
-export class ScheduleVideoUpdateModel extends Model<Partial<AttributesOnly<ScheduleVideoUpdateModel>>> {
+export class ScheduleVideoUpdateModel extends SequelizeModel<ScheduleVideoUpdateModel> {
 
   @AllowNull(false)
   @Default(null)

+ 1 - 1
server/core/models/video/sql/comment/video-comment-list-query-builder.ts

@@ -23,7 +23,7 @@ export interface ListVideoCommentsOptions {
   isLocal?: boolean
   onLocalVideo?: boolean
   onPublicVideo?: boolean
-  videoAccountOwnerId?: boolean
+  videoAccountOwnerId?: number
 
   search?: string
   searchAccount?: string

+ 3 - 3
server/core/models/video/storyboard.ts

@@ -1,14 +1,14 @@
 import { remove } from 'fs-extra/esm'
 import { join } from 'path'
-import { AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { CONFIG } from '@server/initializers/config.js'
 import { MStoryboard, MStoryboardVideo, MVideo } from '@server/types/models/index.js'
 import { Storyboard } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { logger } from '../../helpers/logger.js'
 import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, WEBSERVER } from '../../initializers/constants.js'
 import { VideoModel } from './video.js'
 import { Transaction } from 'sequelize'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'storyboard',
@@ -23,7 +23,7 @@ import { Transaction } from 'sequelize'
     }
   ]
 })
-export class StoryboardModel extends Model<Partial<AttributesOnly<StoryboardModel>>> {
+export class StoryboardModel extends SequelizeModel<StoryboardModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 4
server/core/models/video/tag.ts

@@ -1,10 +1,9 @@
 import { col, fn, QueryTypes, Transaction } from 'sequelize'
-import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoPrivacy, VideoState } from '@peertube/peertube-models'
 import { MTag } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isVideoTagValid } from '../../helpers/custom-validators/videos.js'
-import { throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { VideoTagModel } from './video-tag.js'
 import { VideoModel } from './video.js'
 
@@ -22,7 +21,7 @@ import { VideoModel } from './video.js'
     }
   ]
 })
-export class TagModel extends Model<Partial<AttributesOnly<TagModel>>> {
+export class TagModel extends SequelizeModel<TagModel> {
 
   @AllowNull(false)
   @Is('VideoTag', value => throwIfNotValid(value, isVideoTagValid, 'tag'))

+ 3 - 5
server/core/models/video/thumbnail.ts

@@ -1,5 +1,4 @@
 import { ActivityIconObject, ThumbnailType, type ThumbnailType_Type } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
 import { MThumbnail, MThumbnailVideo, MVideo, MVideoPlaylist } from '@server/types/models/index.js'
 import { remove } from 'fs-extra/esm'
@@ -14,9 +13,7 @@ import {
   CreatedAt,
   DataType,
   Default,
-  ForeignKey,
-  Model,
-  Table,
+  ForeignKey, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { logger } from '../../helpers/logger.js'
@@ -24,6 +21,7 @@ import { CONFIG } from '../../initializers/config.js'
 import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, WEBSERVER } from '../../initializers/constants.js'
 import { VideoPlaylistModel } from './video-playlist.js'
 import { VideoModel } from './video.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 @Table({
   tableName: 'thumbnail',
@@ -41,7 +39,7 @@ import { VideoModel } from './video.js'
     }
   ]
 })
-export class ThumbnailModel extends Model<Partial<AttributesOnly<ThumbnailModel>>> {
+export class ThumbnailModel extends SequelizeModel<ThumbnailModel> {
 
   @AllowNull(false)
   @Column

+ 3 - 4
server/core/models/video/video-blacklist.ts

@@ -1,11 +1,10 @@
 import { VideoBlacklist, type VideoBlacklistType_Type } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models/index.js'
 import { FindOptions } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist.js'
 import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
-import { getBlacklistSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getBlacklistSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
 import { ThumbnailModel } from './thumbnail.js'
 import { SummaryOptions, VideoChannelModel, ScopeNames as VideoChannelScopeNames } from './video-channel.js'
 import { VideoModel } from './video.js'
@@ -19,7 +18,7 @@ import { VideoModel } from './video.js'
     }
   ]
 })
-export class VideoBlacklistModel extends Model<Partial<AttributesOnly<VideoBlacklistModel>>> {
+export class VideoBlacklistModel extends SequelizeModel<VideoBlacklistModel> {
 
   @AllowNull(true)
   @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true))

+ 3 - 6
server/core/models/video/video-caption.ts

@@ -9,9 +9,7 @@ import {
   CreatedAt,
   DataType,
   ForeignKey,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -24,12 +22,11 @@ import {
   MVideoCaptionVideo
 } from '@server/types/models/index.js'
 import { buildUUID } from '@peertube/peertube-node-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions.js'
 import { logger } from '../../helpers/logger.js'
 import { CONFIG } from '../../initializers/config.js'
 import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants.js'
-import { buildWhereIdOrUUID, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildWhereIdOrUUID, throwIfNotValid } from '../shared/index.js'
 import { VideoModel } from './video.js'
 
 export enum ScopeNames {
@@ -64,7 +61,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class VideoCaptionModel extends Model<Partial<AttributesOnly<VideoCaptionModel>>> {
+export class VideoCaptionModel extends SequelizeModel<VideoCaptionModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 4
server/core/models/video/video-change-ownership.ts

@@ -1,9 +1,8 @@
 import { VideoChangeOwnership, type VideoChangeOwnershipStatusType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership.js'
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
 import { AccountModel } from '../account/account.js'
-import { getSort } from '../shared/index.js'
+import { SequelizeModel, getSort } from '../shared/index.js'
 import { VideoModel, ScopeNames as VideoScopeNames } from './video.js'
 
 enum ScopeNames {
@@ -54,7 +53,7 @@ enum ScopeNames {
     ]
   }
 }))
-export class VideoChangeOwnershipModel extends Model<Partial<AttributesOnly<VideoChangeOwnershipModel>>> {
+export class VideoChangeOwnershipModel extends SequelizeModel<VideoChangeOwnershipModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 6
server/core/models/video/video-channel-sync.ts

@@ -1,5 +1,4 @@
 import { VideoChannelSync, VideoChannelSyncState, type VideoChannelSyncStateType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc.js'
 import { isVideoChannelSyncStateValid } from '@server/helpers/custom-validators/video-channel-syncs.js'
 import { CONSTRAINTS_FIELDS, VIDEO_CHANNEL_SYNC_STATE } from '@server/initializers/constants.js'
@@ -14,13 +13,11 @@ import {
   Default,
   DefaultScope,
   ForeignKey,
-  Is,
-  Model,
-  Table,
+  Is, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { AccountModel } from '../account/account.js'
-import { getChannelSyncSort, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getChannelSyncSort, throwIfNotValid } from '../shared/index.js'
 import { UserModel } from '../user/user.js'
 import { VideoChannelModel } from './video-channel.js'
 
@@ -40,7 +37,7 @@ import { VideoChannelModel } from './video-channel.js'
     }
   ]
 })
-export class VideoChannelSyncModel extends Model<Partial<AttributesOnly<VideoChannelSyncModel>>> {
+export class VideoChannelSyncModel extends SequelizeModel<VideoChannelSyncModel> {
 
   @AllowNull(false)
   @Default(null)

+ 3 - 5
server/core/models/video/video-channel.ts

@@ -1,6 +1,5 @@
 import { forceNumber, pick } from '@peertube/peertube-core-utils'
 import { ActivityPubActor, VideoChannel, VideoChannelSummary } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { CONFIG } from '@server/initializers/config.js'
 import { InternalEventEmitter } from '@server/lib/internal-event-emitter.js'
 import { MAccountHost } from '@server/types/models/index.js'
@@ -19,9 +18,7 @@ import {
   DefaultScope,
   ForeignKey,
   HasMany,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Sequelize,
   Table,
   UpdatedAt
@@ -47,6 +44,7 @@ import { ActorImageModel } from '../actor/actor-image.js'
 import { ActorModel, unusedActorAttributesForAPI } from '../actor/actor.js'
 import { ServerModel } from '../server/server.js'
 import {
+  SequelizeModel,
   buildServerIdsFollowedBy,
   buildTrigramSearchIndex,
   createSimilarityAttribute,
@@ -352,7 +350,7 @@ export type SummaryOptions = {
     }
   ]
 })
-export class VideoChannelModel extends Model<Partial<AttributesOnly<VideoChannelModel>>> {
+export class VideoChannelModel extends SequelizeModel<VideoChannelModel> {
 
   @AllowNull(false)
   @Is('VideoChannelName', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'name'))

+ 3 - 3
server/core/models/video/video-chapter.ts

@@ -1,10 +1,10 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
 import { MVideo, MVideoChapter } from '@server/types/models/index.js'
 import { VideoChapter, VideoChapterObject } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { VideoModel } from './video.js'
 import { Transaction } from 'sequelize'
 import { getSort } from '../shared/sort.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 @Table({
   tableName: 'videoChapter',
@@ -15,7 +15,7 @@ import { getSort } from '../shared/sort.js'
     }
   ]
 })
-export class VideoChapterModel extends Model<Partial<AttributesOnly<VideoChapterModel>>> {
+export class VideoChapterModel extends SequelizeModel<VideoChapterModel> {
 
   @AllowNull(false)
   @Column

+ 20 - 27
server/core/models/video/video-comment.ts

@@ -1,4 +1,4 @@
-import { FindOptions, Op, Order, QueryTypes, Sequelize, Transaction } from 'sequelize'
+import { Op, Order, QueryTypes, Sequelize, Transaction } from 'sequelize'
 import {
   AllowNull,
   BelongsTo,
@@ -7,9 +7,7 @@ import {
   DataType,
   ForeignKey,
   HasMany,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -18,7 +16,6 @@ import { ActivityTagObject, ActivityTombstoneObject, VideoComment, VideoCommentA
 import { extractMentions } from '@server/helpers/mentions.js'
 import { getServerActor } from '@server/models/application/application.js'
 import { MAccount, MAccountId, MUserAccountId } from '@server/types/models/index.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
 import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
 import {
@@ -38,7 +35,7 @@ import {
 import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse.js'
 import { AccountModel } from '../account/account.js'
 import { ActorModel } from '../actor/actor.js'
-import { buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, buildLocalAccountIdsIn, buildSQLAttributes, throwIfNotValid } from '../shared/index.js'
 import { ListVideoCommentsOptions, VideoCommentListQueryBuilder } from './sql/comment/video-comment-list-query-builder.js'
 import { VideoChannelModel } from './video-channel.js'
 import { VideoModel } from './video.js'
@@ -123,7 +120,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoCommentModel>>> {
+export class VideoCommentModel extends SequelizeModel<VideoCommentModel> {
   @CreatedAt
   createdAt: Date
 
@@ -216,46 +213,43 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment
 
   // ---------------------------------------------------------------------------
 
-  static loadById (id: number, t?: Transaction): Promise<MComment> {
-    const query: FindOptions = {
+  static loadById (id: number, transaction?: Transaction): Promise<MComment> {
+    const query = {
       where: {
         id
-      }
+      },
+      transaction
     }
 
-    if (t !== undefined) query.transaction = t
-
     return VideoCommentModel.findOne(query)
   }
 
-  static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Promise<MCommentOwnerVideoReply> {
-    const query: FindOptions = {
+  static loadByIdAndPopulateVideoAndAccountAndReply (id: number, transaction?: Transaction): Promise<MCommentOwnerVideoReply> {
+    const query = {
       where: {
         id
-      }
+      },
+      transaction
     }
 
-    if (t !== undefined) query.transaction = t
-
     return VideoCommentModel
       .scope([ ScopeNames.WITH_VIDEO, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_IN_REPLY_TO ])
       .findOne(query)
   }
 
-  static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Promise<MCommentOwnerVideo> {
-    const query: FindOptions = {
+  static loadByUrlAndPopulateAccountAndVideo (url: string, transaction?: Transaction): Promise<MCommentOwnerVideo> {
+    const query = {
       where: {
         url
-      }
+      },
+      transaction
     }
 
-    if (t !== undefined) query.transaction = t
-
     return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query)
   }
 
-  static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Promise<MCommentOwnerReplyVideoLight> {
-    const query: FindOptions = {
+  static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, transaction?: Transaction): Promise<MCommentOwnerReplyVideoLight> {
+    const query = {
       where: {
         url
       },
@@ -264,11 +258,10 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment
           attributes: [ 'id', 'url' ],
           model: VideoModel.unscoped()
         }
-      ]
+      ],
+      transaction
     }
 
-    if (t !== undefined) query.transaction = t
-
     return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_ACCOUNT ]).findOne(query)
   }
 

+ 4 - 7
server/core/models/video/video-file.ts

@@ -1,5 +1,4 @@
 import { ActivityVideoUrlObject, VideoResolution, FileStorage, type FileStorageType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { logger } from '@server/helpers/logger.js'
 import { extractVideo } from '@server/helpers/video.js'
 import { CONFIG } from '@server/initializers/config.js'
@@ -27,9 +26,7 @@ import {
   DefaultScope,
   ForeignKey,
   HasMany,
-  Is,
-  Model,
-  Scopes,
+  Is, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -51,7 +48,7 @@ import {
 } from '../../initializers/constants.js'
 import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../types/models/video/video-file.js'
 import { VideoRedundancyModel } from '../redundancy/video-redundancy.js'
-import { doesExist, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, doesExist, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
 import { VideoStreamingPlaylistModel } from './video-streaming-playlist.js'
 import { VideoModel } from './video.js'
 import { getVideoFileMimeType } from '@server/lib/video-file.js'
@@ -158,7 +155,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>>> {
+export class VideoFileModel extends SequelizeModel<VideoFileModel> {
   @CreatedAt
   createdAt: Date
 
@@ -257,7 +254,7 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
   })
   RedundancyVideos: Awaited<VideoRedundancyModel>[]
 
-  static doesInfohashExistCached = memoizee(VideoFileModel.doesInfohashExist, {
+  static doesInfohashExistCached = memoizee(VideoFileModel.doesInfohashExist.bind(VideoFileModel), {
     promise: true,
     max: MEMOIZE_LENGTH.INFO_HASH_EXISTS,
     maxAge: MEMOIZE_TTL.INFO_HASH_EXISTS

+ 3 - 6
server/core/models/video/video-import.ts

@@ -1,5 +1,4 @@
 import { VideoImport, VideoImportState, type VideoImportStateType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { afterCommitIfTransaction } from '@server/helpers/database-utils.js'
 import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import.js'
 import { IncludeOptions, Op, WhereOptions } from 'sequelize'
@@ -13,15 +12,13 @@ import {
   Default,
   DefaultScope,
   ForeignKey,
-  Is,
-  Model,
-  Table,
+  Is, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports.js'
 import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos.js'
 import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants.js'
-import { getSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, searchAttribute, throwIfNotValid } from '../shared/index.js'
 import { UserModel } from '../user/user.js'
 import { VideoChannelSyncModel } from './video-channel-sync.js'
 import { VideoModel, ScopeNames as VideoModelScopeNames } from './video.js'
@@ -63,7 +60,7 @@ const defaultVideoScope = () => {
     }
   ]
 })
-export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportModel>>> {
+export class VideoImportModel extends SequelizeModel<VideoImportModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 3
server/core/models/video/video-job-info.ts

@@ -1,8 +1,8 @@
 import { Op, QueryTypes, Transaction } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, IsInt, Model, Table, Unique, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, IsInt, Table, Unique, UpdatedAt } from 'sequelize-typescript'
 import { forceNumber } from '@peertube/peertube-core-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { VideoModel } from './video.js'
+import { SequelizeModel } from '../shared/sequelize-type.js'
 
 export type VideoJobInfoColumnType = 'pendingMove' | 'pendingTranscode'
 
@@ -20,7 +20,7 @@ export type VideoJobInfoColumnType = 'pendingMove' | 'pendingTranscode'
   ]
 })
 
-export class VideoJobInfoModel extends Model<Partial<AttributesOnly<VideoJobInfoModel>>> {
+export class VideoJobInfoModel extends SequelizeModel<VideoJobInfoModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 2
server/core/models/video/video-live-replay-setting.ts

@@ -2,13 +2,14 @@ import { type VideoPrivacyType } from '@peertube/peertube-models'
 import { isVideoPrivacyValid } from '@server/helpers/custom-validators/videos.js'
 import { MLiveReplaySetting } from '@server/types/models/video/video-live-replay-setting.js'
 import { Transaction } from 'sequelize'
-import { AllowNull, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, Column, CreatedAt, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { throwIfNotValid } from '../shared/sequelize-helpers.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @Table({
   tableName: 'videoLiveReplaySetting'
 })
-export class VideoLiveReplaySettingModel extends Model<VideoLiveReplaySettingModel> {
+export class VideoLiveReplaySettingModel extends SequelizeModel<VideoLiveReplaySettingModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 4
server/core/models/video/video-live-session.ts

@@ -10,14 +10,13 @@ import {
   Column,
   CreatedAt,
   DataType,
-  ForeignKey,
-  Model,
-  Scopes,
+  ForeignKey, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { VideoLiveReplaySettingModel } from './video-live-replay-setting.js'
 import { VideoModel } from './video.js'
+import { SequelizeModel } from '../shared/index.js'
 
 export enum ScopeNames {
   WITH_REPLAY = 'WITH_REPLAY'
@@ -54,7 +53,7 @@ export enum ScopeNames {
     }
   ]
 })
-export class VideoLiveSessionModel extends Model<Partial<AttributesOnly<VideoLiveSessionModel>>> {
+export class VideoLiveSessionModel extends SequelizeModel<VideoLiveSessionModel> {
 
   @CreatedAt
   createdAt: Date

+ 3 - 5
server/core/models/video/video-live.ts

@@ -1,5 +1,4 @@
 import { LiveVideo, VideoState, type LiveVideoLatencyModeType } from '@peertube/peertube-models'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { CONFIG } from '@server/initializers/config.js'
 import { WEBSERVER } from '@server/initializers/constants.js'
 import { MVideoLive, MVideoLiveVideoWithSetting, MVideoLiveWithSetting } from '@server/types/models/index.js'
@@ -12,14 +11,13 @@ import {
   CreatedAt,
   DataType,
   DefaultScope,
-  ForeignKey,
-  Model,
-  Table,
+  ForeignKey, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import { VideoBlacklistModel } from './video-blacklist.js'
 import { VideoLiveReplaySettingModel } from './video-live-replay-setting.js'
 import { VideoModel } from './video.js'
+import { SequelizeModel } from '../shared/index.js'
 
 @DefaultScope(() => ({
   include: [
@@ -52,7 +50,7 @@ import { VideoModel } from './video.js'
     }
   ]
 })
-export class VideoLiveModel extends Model<Partial<AttributesOnly<VideoLiveModel>>> {
+export class VideoLiveModel extends SequelizeModel<VideoLiveModel> {
 
   @AllowNull(true)
   @Column(DataType.STRING)

+ 6 - 7
server/core/models/video/video-password.ts

@@ -1,9 +1,8 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, Is, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoModel } from './video.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { ResultList, VideoPassword } from '@peertube/peertube-models'
-import { getSort, throwIfNotValid } from '../shared/index.js'
-import { FindOptions, Transaction } from 'sequelize'
+import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
+import { Transaction } from 'sequelize'
 import { MVideoPassword } from '@server/types/models/index.js'
 import { isPasswordValid } from '@server/helpers/custom-validators/videos.js'
 import { pick } from '@peertube/peertube-core-utils'
@@ -25,7 +24,7 @@ import { pick } from '@peertube/peertube-core-utils'
     }
   ]
 })
-export class VideoPasswordModel extends Model<Partial<AttributesOnly<VideoPasswordModel>>> {
+export class VideoPasswordModel extends SequelizeModel<VideoPasswordModel> {
 
   @AllowNull(false)
   @Is('VideoPassword', value => throwIfNotValid(value, isPasswordValid, 'videoPassword'))
@@ -51,7 +50,7 @@ export class VideoPasswordModel extends Model<Partial<AttributesOnly<VideoPasswo
   Video: Awaited<VideoModel>
 
   static async countByVideoId (videoId: number, t?: Transaction) {
-    const query: FindOptions = {
+    const query = {
       where: {
         videoId
       },
@@ -63,7 +62,7 @@ export class VideoPasswordModel extends Model<Partial<AttributesOnly<VideoPasswo
 
   static async loadByIdAndVideo (options: { id: number, videoId: number, t?: Transaction }): Promise<MVideoPassword> {
     const { id, videoId, t } = options
-    const query: FindOptions = {
+    const query = {
       where: {
         id,
         videoId

+ 3 - 6
server/core/models/video/video-playlist-element.ts

@@ -9,9 +9,7 @@ import {
   ForeignKey,
   Is,
   IsInt,
-  Min,
-  Model,
-  Table,
+  Min, Table,
   UpdatedAt
 } from 'sequelize-typescript'
 import validator from 'validator'
@@ -32,11 +30,10 @@ import {
   MVideoPlaylistElementVideoThumbnail,
   MVideoPlaylistElementVideoUrl
 } from '@server/types/models/video/video-playlist-element.js'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
 import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
 import { AccountModel } from '../account/account.js'
-import { getSort, throwIfNotValid } from '../shared/index.js'
+import { SequelizeModel, getSort, throwIfNotValid } from '../shared/index.js'
 import { VideoPlaylistModel } from './video-playlist.js'
 import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video.js'
 
@@ -55,7 +52,7 @@ import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './vide
     }
   ]
 })
-export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<VideoPlaylistElementModel>>> {
+export class VideoPlaylistElementModel extends SequelizeModel<VideoPlaylistElementModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 5
server/core/models/video/video-playlist.ts

@@ -9,7 +9,6 @@ import {
   type VideoPlaylistType_Type
 } from '@peertube/peertube-models'
 import { buildUUID, uuidToShort } from '@peertube/peertube-node-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { activityPubCollectionPagination } from '@server/lib/activitypub/collection.js'
 import { MAccountId, MChannelId } from '@server/types/models/index.js'
 import { join } from 'path'
@@ -25,9 +24,7 @@ import {
   HasMany,
   HasOne,
   Is,
-  IsUUID,
-  Model,
-  Scopes,
+  IsUUID, Scopes,
   Table,
   UpdatedAt
 } from 'sequelize-typescript'
@@ -58,6 +55,7 @@ import {
 import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account.js'
 import { ActorModel } from '../actor/actor.js'
 import {
+  SequelizeModel,
   buildServerIdsFollowedBy,
   buildTrigramSearchIndex,
   buildWhereIdOrUUID,
@@ -287,7 +285,7 @@ function getVideoLengthSelect () {
     }
   ]
 })
-export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlaylistModel>>> {
+export class VideoPlaylistModel extends SequelizeModel<VideoPlaylistModel> {
   @CreatedAt
   createdAt: Date
 

+ 3 - 4
server/core/models/video/video-share.ts

@@ -1,13 +1,12 @@
 import { literal, Op, QueryTypes, Transaction } from 'sequelize'
-import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
 import { forceNumber } from '@peertube/peertube-core-utils'
-import { AttributesOnly } from '@peertube/peertube-typescript-utils'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc.js'
 import { CONSTRAINTS_FIELDS } from '../../initializers/constants.js'
 import { MActorDefault, MActorFollowersUrl, MActorId } from '../../types/models/index.js'
 import { MVideoShareActor, MVideoShareFull } from '../../types/models/video/index.js'
 import { ActorModel } from '../actor/actor.js'
-import { buildLocalActorIdsIn, throwIfNotValid } from '../shared/index.js'
+import { buildLocalActorIdsIn, SequelizeModel, throwIfNotValid } from '../shared/index.js'
 import { VideoModel } from './video.js'
 
 enum ScopeNames {
@@ -52,7 +51,7 @@ enum ScopeNames {
     }
   ]
 })
-export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareModel>>> {
+export class VideoShareModel extends SequelizeModel<VideoShareModel> {
 
   @AllowNull(false)
   @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))

Some files were not shown because too many files changed in this diff