aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/account/account.ts3
-rw-r--r--server/models/account/user-video-history.ts55
-rw-r--r--server/models/video/video-format-utils.ts9
-rw-r--r--server/models/video/video.ts79
4 files changed, 128 insertions, 18 deletions
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 27c75d886..5a237d733 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -248,7 +248,8 @@ export class AccountModel extends Model<AccountModel> {
248 displayName: this.getDisplayName(), 248 displayName: this.getDisplayName(),
249 description: this.description, 249 description: this.description,
250 createdAt: this.createdAt, 250 createdAt: this.createdAt,
251 updatedAt: this.updatedAt 251 updatedAt: this.updatedAt,
252 userId: this.userId ? this.userId : undefined
252 } 253 }
253 254
254 return Object.assign(actor, account) 255 return Object.assign(actor, account)
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts
new file mode 100644
index 000000000..0476cad9d
--- /dev/null
+++ b/server/models/account/user-video-history.ts
@@ -0,0 +1,55 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Min, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoModel } from '../video/video'
3import { UserModel } from './user'
4
5@Table({
6 tableName: 'userVideoHistory',
7 indexes: [
8 {
9 fields: [ 'userId', 'videoId' ],
10 unique: true
11 },
12 {
13 fields: [ 'userId' ]
14 },
15 {
16 fields: [ 'videoId' ]
17 }
18 ]
19})
20export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
21 @CreatedAt
22 createdAt: Date
23
24 @UpdatedAt
25 updatedAt: Date
26
27 @AllowNull(false)
28 @IsInt
29 @Column
30 currentTime: number
31
32 @ForeignKey(() => VideoModel)
33 @Column
34 videoId: number
35
36 @BelongsTo(() => VideoModel, {
37 foreignKey: {
38 allowNull: false
39 },
40 onDelete: 'CASCADE'
41 })
42 Video: VideoModel
43
44 @ForeignKey(() => UserModel)
45 @Column
46 userId: number
47
48 @BelongsTo(() => UserModel, {
49 foreignKey: {
50 allowNull: false
51 },
52 onDelete: 'CASCADE'
53 })
54 User: UserModel
55}
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index f23dde9b8..905e84449 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -10,6 +10,7 @@ import {
10 getVideoLikesActivityPubUrl, 10 getVideoLikesActivityPubUrl,
11 getVideoSharesActivityPubUrl 11 getVideoSharesActivityPubUrl
12} from '../../lib/activitypub' 12} from '../../lib/activitypub'
13import { isArray } from '../../helpers/custom-validators/misc'
13 14
14export type VideoFormattingJSONOptions = { 15export type VideoFormattingJSONOptions = {
15 completeDescription?: boolean 16 completeDescription?: boolean
@@ -24,6 +25,8 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
24 const formattedAccount = video.VideoChannel.Account.toFormattedJSON() 25 const formattedAccount = video.VideoChannel.Account.toFormattedJSON()
25 const formattedVideoChannel = video.VideoChannel.toFormattedJSON() 26 const formattedVideoChannel = video.VideoChannel.toFormattedJSON()
26 27
28 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
29
27 const videoObject: Video = { 30 const videoObject: Video = {
28 id: video.id, 31 id: video.id,
29 uuid: video.uuid, 32 uuid: video.uuid,
@@ -74,7 +77,11 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
74 url: formattedVideoChannel.url, 77 url: formattedVideoChannel.url,
75 host: formattedVideoChannel.host, 78 host: formattedVideoChannel.host,
76 avatar: formattedVideoChannel.avatar 79 avatar: formattedVideoChannel.avatar
77 } 80 },
81
82 userHistory: userHistory ? {
83 currentTime: userHistory.currentTime
84 } : undefined
78 } 85 }
79 86
80 if (options) { 87 if (options) {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 6c89c16bf..46d823240 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -92,6 +92,7 @@ import {
92 videoModelToFormattedJSON 92 videoModelToFormattedJSON
93} from './video-format-utils' 93} from './video-format-utils'
94import * as validator from 'validator' 94import * as validator from 'validator'
95import { UserVideoHistoryModel } from '../account/user-video-history'
95 96
96// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 97// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
97const indexes: Sequelize.DefineIndexesOptions[] = [ 98const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -127,7 +128,8 @@ export enum ScopeNames {
127 WITH_TAGS = 'WITH_TAGS', 128 WITH_TAGS = 'WITH_TAGS',
128 WITH_FILES = 'WITH_FILES', 129 WITH_FILES = 'WITH_FILES',
129 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', 130 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
130 WITH_BLACKLISTED = 'WITH_BLACKLISTED' 131 WITH_BLACKLISTED = 'WITH_BLACKLISTED',
132 WITH_USER_HISTORY = 'WITH_USER_HISTORY'
131} 133}
132 134
133type ForAPIOptions = { 135type ForAPIOptions = {
@@ -464,6 +466,8 @@ type AvailableForListIDsOptions = {
464 include: [ 466 include: [
465 { 467 {
466 model: () => VideoFileModel.unscoped(), 468 model: () => VideoFileModel.unscoped(),
469 // FIXME: typings
470 [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
467 required: false, 471 required: false,
468 include: [ 472 include: [
469 { 473 {
@@ -482,6 +486,20 @@ type AvailableForListIDsOptions = {
482 required: false 486 required: false
483 } 487 }
484 ] 488 ]
489 },
490 [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => {
491 return {
492 include: [
493 {
494 attributes: [ 'currentTime' ],
495 model: UserVideoHistoryModel.unscoped(),
496 required: false,
497 where: {
498 userId
499 }
500 }
501 ]
502 }
485 } 503 }
486}) 504})
487@Table({ 505@Table({
@@ -672,11 +690,19 @@ export class VideoModel extends Model<VideoModel> {
672 name: 'videoId', 690 name: 'videoId',
673 allowNull: false 691 allowNull: false
674 }, 692 },
675 onDelete: 'cascade', 693 onDelete: 'cascade'
676 hooks: true
677 }) 694 })
678 VideoViews: VideoViewModel[] 695 VideoViews: VideoViewModel[]
679 696
697 @HasMany(() => UserVideoHistoryModel, {
698 foreignKey: {
699 name: 'videoId',
700 allowNull: false
701 },
702 onDelete: 'cascade'
703 })
704 UserVideoHistories: UserVideoHistoryModel[]
705
680 @HasOne(() => ScheduleVideoUpdateModel, { 706 @HasOne(() => ScheduleVideoUpdateModel, {
681 foreignKey: { 707 foreignKey: {
682 name: 'videoId', 708 name: 'videoId',
@@ -930,7 +956,8 @@ export class VideoModel extends Model<VideoModel> {
930 accountId?: number, 956 accountId?: number,
931 videoChannelId?: number, 957 videoChannelId?: number,
932 actorId?: number 958 actorId?: number
933 trendingDays?: number 959 trendingDays?: number,
960 userId?: number
934 }, countVideos = true) { 961 }, countVideos = true) {
935 const query: IFindOptions<VideoModel> = { 962 const query: IFindOptions<VideoModel> = {
936 offset: options.start, 963 offset: options.start,
@@ -961,6 +988,7 @@ export class VideoModel extends Model<VideoModel> {
961 accountId: options.accountId, 988 accountId: options.accountId,
962 videoChannelId: options.videoChannelId, 989 videoChannelId: options.videoChannelId,
963 includeLocalVideos: options.includeLocalVideos, 990 includeLocalVideos: options.includeLocalVideos,
991 userId: options.userId,
964 trendingDays 992 trendingDays
965 } 993 }
966 994
@@ -983,6 +1011,7 @@ export class VideoModel extends Model<VideoModel> {
983 tagsAllOf?: string[] 1011 tagsAllOf?: string[]
984 durationMin?: number // seconds 1012 durationMin?: number // seconds
985 durationMax?: number // seconds 1013 durationMax?: number // seconds
1014 userId?: number
986 }) { 1015 }) {
987 const whereAnd = [] 1016 const whereAnd = []
988 1017
@@ -1058,7 +1087,8 @@ export class VideoModel extends Model<VideoModel> {
1058 licenceOneOf: options.licenceOneOf, 1087 licenceOneOf: options.licenceOneOf,
1059 languageOneOf: options.languageOneOf, 1088 languageOneOf: options.languageOneOf,
1060 tagsOneOf: options.tagsOneOf, 1089 tagsOneOf: options.tagsOneOf,
1061 tagsAllOf: options.tagsAllOf 1090 tagsAllOf: options.tagsAllOf,
1091 userId: options.userId
1062 } 1092 }
1063 1093
1064 return VideoModel.getAvailableForApi(query, queryOptions) 1094 return VideoModel.getAvailableForApi(query, queryOptions)
@@ -1125,7 +1155,7 @@ export class VideoModel extends Model<VideoModel> {
1125 return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) 1155 return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query)
1126 } 1156 }
1127 1157
1128 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction) { 1158 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) {
1129 const where = VideoModel.buildWhereIdOrUUID(id) 1159 const where = VideoModel.buildWhereIdOrUUID(id)
1130 1160
1131 const options = { 1161 const options = {
@@ -1134,14 +1164,20 @@ export class VideoModel extends Model<VideoModel> {
1134 transaction: t 1164 transaction: t
1135 } 1165 }
1136 1166
1167 const scopes = [
1168 ScopeNames.WITH_TAGS,
1169 ScopeNames.WITH_BLACKLISTED,
1170 ScopeNames.WITH_FILES,
1171 ScopeNames.WITH_ACCOUNT_DETAILS,
1172 ScopeNames.WITH_SCHEDULED_UPDATE
1173 ]
1174
1175 if (userId) {
1176 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings
1177 }
1178
1137 return VideoModel 1179 return VideoModel
1138 .scope([ 1180 .scope(scopes)
1139 ScopeNames.WITH_TAGS,
1140 ScopeNames.WITH_BLACKLISTED,
1141 ScopeNames.WITH_FILES,
1142 ScopeNames.WITH_ACCOUNT_DETAILS,
1143 ScopeNames.WITH_SCHEDULED_UPDATE
1144 ])
1145 .findOne(options) 1181 .findOne(options)
1146 } 1182 }
1147 1183
@@ -1225,7 +1261,11 @@ export class VideoModel extends Model<VideoModel> {
1225 return {} 1261 return {}
1226 } 1262 }
1227 1263
1228 private static async getAvailableForApi (query: IFindOptions<VideoModel>, options: AvailableForListIDsOptions, countVideos = true) { 1264 private static async getAvailableForApi (
1265 query: IFindOptions<VideoModel>,
1266 options: AvailableForListIDsOptions & { userId?: number},
1267 countVideos = true
1268 ) {
1229 const idsScope = { 1269 const idsScope = {
1230 method: [ 1270 method: [
1231 ScopeNames.AVAILABLE_FOR_LIST_IDS, options 1271 ScopeNames.AVAILABLE_FOR_LIST_IDS, options
@@ -1249,8 +1289,15 @@ export class VideoModel extends Model<VideoModel> {
1249 1289
1250 if (ids.length === 0) return { data: [], total: count } 1290 if (ids.length === 0) return { data: [], total: count }
1251 1291
1252 const apiScope = { 1292 // FIXME: typings
1253 method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ] 1293 const apiScope: any[] = [
1294 {
1295 method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ]
1296 }
1297 ]
1298
1299 if (options.userId) {
1300 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.userId ] })
1254 } 1301 }
1255 1302
1256 const secondQuery = { 1303 const secondQuery = {