2019-01-02 09:37:43 -06:00
|
|
|
import {
|
|
|
|
AllowNull,
|
|
|
|
BelongsTo,
|
|
|
|
Column,
|
|
|
|
CreatedAt,
|
|
|
|
Default,
|
|
|
|
ForeignKey,
|
|
|
|
IFindOptions,
|
|
|
|
Is,
|
|
|
|
Model,
|
|
|
|
Scopes,
|
|
|
|
Table,
|
|
|
|
UpdatedAt
|
|
|
|
} from 'sequelize-typescript'
|
2018-12-26 03:36:24 -06:00
|
|
|
import { UserNotification, UserNotificationType } from '../../../shared'
|
|
|
|
import { getSort, throwIfNotValid } from '../utils'
|
|
|
|
import { isBooleanValid } from '../../helpers/custom-validators/misc'
|
|
|
|
import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
|
|
|
|
import { UserModel } from './user'
|
|
|
|
import { VideoModel } from '../video/video'
|
|
|
|
import { VideoCommentModel } from '../video/video-comment'
|
|
|
|
import { Op } from 'sequelize'
|
|
|
|
import { VideoChannelModel } from '../video/video-channel'
|
|
|
|
import { AccountModel } from './account'
|
|
|
|
import { VideoAbuseModel } from '../video/video-abuse'
|
|
|
|
import { VideoBlacklistModel } from '../video/video-blacklist'
|
2019-01-02 09:37:43 -06:00
|
|
|
import { VideoImportModel } from '../video/video-import'
|
2019-01-04 01:56:20 -06:00
|
|
|
import { ActorModel } from '../activitypub/actor'
|
|
|
|
import { ActorFollowModel } from '../activitypub/actor-follow'
|
2018-12-26 03:36:24 -06:00
|
|
|
|
|
|
|
enum ScopeNames {
|
|
|
|
WITH_ALL = 'WITH_ALL'
|
|
|
|
}
|
|
|
|
|
2019-01-02 09:37:43 -06:00
|
|
|
function buildVideoInclude (required: boolean) {
|
|
|
|
return {
|
|
|
|
attributes: [ 'id', 'uuid', 'name' ],
|
|
|
|
model: () => VideoModel.unscoped(),
|
|
|
|
required
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 01:56:20 -06:00
|
|
|
function buildChannelInclude (required: boolean) {
|
2019-01-02 09:37:43 -06:00
|
|
|
return {
|
2019-01-04 01:56:20 -06:00
|
|
|
required,
|
2019-01-02 09:37:43 -06:00
|
|
|
attributes: [ 'id', 'name' ],
|
|
|
|
model: () => VideoChannelModel.unscoped()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 01:56:20 -06:00
|
|
|
function buildAccountInclude (required: boolean) {
|
2019-01-02 09:37:43 -06:00
|
|
|
return {
|
2019-01-04 01:56:20 -06:00
|
|
|
required,
|
2019-01-02 09:37:43 -06:00
|
|
|
attributes: [ 'id', 'name' ],
|
|
|
|
model: () => AccountModel.unscoped()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-26 03:36:24 -06:00
|
|
|
@Scopes({
|
|
|
|
[ScopeNames.WITH_ALL]: {
|
|
|
|
include: [
|
2019-01-02 09:37:43 -06:00
|
|
|
Object.assign(buildVideoInclude(false), {
|
2019-01-04 01:56:20 -06:00
|
|
|
include: [ buildChannelInclude(true) ]
|
2019-01-02 09:37:43 -06:00
|
|
|
}),
|
2018-12-26 03:36:24 -06:00
|
|
|
{
|
2019-01-02 09:37:43 -06:00
|
|
|
attributes: [ 'id', 'originCommentId' ],
|
2018-12-26 03:36:24 -06:00
|
|
|
model: () => VideoCommentModel.unscoped(),
|
|
|
|
required: false,
|
|
|
|
include: [
|
2019-01-04 01:56:20 -06:00
|
|
|
buildAccountInclude(true),
|
2019-01-02 09:37:43 -06:00
|
|
|
buildVideoInclude(true)
|
2018-12-26 03:36:24 -06:00
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'id' ],
|
|
|
|
model: () => VideoAbuseModel.unscoped(),
|
|
|
|
required: false,
|
2019-01-02 09:37:43 -06:00
|
|
|
include: [ buildVideoInclude(true) ]
|
2018-12-26 03:36:24 -06:00
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'id' ],
|
|
|
|
model: () => VideoBlacklistModel.unscoped(),
|
|
|
|
required: false,
|
2019-01-02 09:37:43 -06:00
|
|
|
include: [ buildVideoInclude(true) ]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
|
|
|
|
model: () => VideoImportModel.unscoped(),
|
|
|
|
required: false,
|
|
|
|
include: [ buildVideoInclude(false) ]
|
2019-01-04 01:56:20 -06:00
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'id', 'name' ],
|
|
|
|
model: () => AccountModel.unscoped(),
|
|
|
|
required: false,
|
|
|
|
include: [
|
|
|
|
{
|
|
|
|
attributes: [ 'id', 'preferredUsername' ],
|
|
|
|
model: () => ActorModel.unscoped(),
|
|
|
|
required: true
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'id' ],
|
|
|
|
model: () => ActorFollowModel.unscoped(),
|
|
|
|
required: false,
|
|
|
|
include: [
|
|
|
|
{
|
|
|
|
attributes: [ 'preferredUsername' ],
|
|
|
|
model: () => ActorModel.unscoped(),
|
|
|
|
required: true,
|
|
|
|
as: 'ActorFollower',
|
|
|
|
include: [ buildAccountInclude(true) ]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
attributes: [ 'preferredUsername' ],
|
|
|
|
model: () => ActorModel.unscoped(),
|
|
|
|
required: true,
|
|
|
|
as: 'ActorFollowing',
|
|
|
|
include: [
|
|
|
|
buildChannelInclude(false),
|
|
|
|
buildAccountInclude(false)
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2018-12-26 03:36:24 -06:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
@Table({
|
|
|
|
tableName: 'userNotification',
|
|
|
|
indexes: [
|
|
|
|
{
|
|
|
|
fields: [ 'videoId' ]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
fields: [ 'commentId' ]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
})
|
|
|
|
export class UserNotificationModel extends Model<UserNotificationModel> {
|
|
|
|
|
|
|
|
@AllowNull(false)
|
|
|
|
@Default(null)
|
|
|
|
@Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
|
|
|
|
@Column
|
|
|
|
type: UserNotificationType
|
|
|
|
|
|
|
|
@AllowNull(false)
|
|
|
|
@Default(false)
|
|
|
|
@Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
|
|
|
|
@Column
|
|
|
|
read: boolean
|
|
|
|
|
|
|
|
@CreatedAt
|
|
|
|
createdAt: Date
|
|
|
|
|
|
|
|
@UpdatedAt
|
|
|
|
updatedAt: Date
|
|
|
|
|
|
|
|
@ForeignKey(() => UserModel)
|
|
|
|
@Column
|
|
|
|
userId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => UserModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: false
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
User: UserModel
|
|
|
|
|
|
|
|
@ForeignKey(() => VideoModel)
|
|
|
|
@Column
|
|
|
|
videoId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => VideoModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
Video: VideoModel
|
|
|
|
|
|
|
|
@ForeignKey(() => VideoCommentModel)
|
|
|
|
@Column
|
|
|
|
commentId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => VideoCommentModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
Comment: VideoCommentModel
|
|
|
|
|
|
|
|
@ForeignKey(() => VideoAbuseModel)
|
|
|
|
@Column
|
|
|
|
videoAbuseId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => VideoAbuseModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
VideoAbuse: VideoAbuseModel
|
|
|
|
|
|
|
|
@ForeignKey(() => VideoBlacklistModel)
|
|
|
|
@Column
|
|
|
|
videoBlacklistId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => VideoBlacklistModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
VideoBlacklist: VideoBlacklistModel
|
|
|
|
|
2019-01-02 09:37:43 -06:00
|
|
|
@ForeignKey(() => VideoImportModel)
|
|
|
|
@Column
|
|
|
|
videoImportId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => VideoImportModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
VideoImport: VideoImportModel
|
|
|
|
|
2019-01-04 01:56:20 -06:00
|
|
|
@ForeignKey(() => AccountModel)
|
|
|
|
@Column
|
|
|
|
accountId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => AccountModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
Account: AccountModel
|
|
|
|
|
|
|
|
@ForeignKey(() => ActorFollowModel)
|
|
|
|
@Column
|
|
|
|
actorFollowId: number
|
|
|
|
|
|
|
|
@BelongsTo(() => ActorFollowModel, {
|
|
|
|
foreignKey: {
|
|
|
|
allowNull: true
|
|
|
|
},
|
|
|
|
onDelete: 'cascade'
|
|
|
|
})
|
|
|
|
ActorFollow: ActorFollowModel
|
|
|
|
|
2019-01-02 09:37:43 -06:00
|
|
|
static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
|
|
|
|
const query: IFindOptions<UserNotificationModel> = {
|
2018-12-26 03:36:24 -06:00
|
|
|
offset: start,
|
|
|
|
limit: count,
|
|
|
|
order: getSort(sort),
|
|
|
|
where: {
|
|
|
|
userId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-02 09:37:43 -06:00
|
|
|
if (unread !== undefined) query.where['read'] = !unread
|
|
|
|
|
2018-12-26 03:36:24 -06:00
|
|
|
return UserNotificationModel.scope(ScopeNames.WITH_ALL)
|
|
|
|
.findAndCountAll(query)
|
|
|
|
.then(({ rows, count }) => {
|
|
|
|
return {
|
|
|
|
data: rows,
|
|
|
|
total: count
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
static markAsRead (userId: number, notificationIds: number[]) {
|
|
|
|
const query = {
|
|
|
|
where: {
|
|
|
|
userId,
|
|
|
|
id: {
|
|
|
|
[Op.any]: notificationIds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return UserNotificationModel.update({ read: true }, query)
|
|
|
|
}
|
|
|
|
|
2019-01-08 04:26:41 -06:00
|
|
|
static markAllAsRead (userId: number) {
|
|
|
|
const query = { where: { userId } }
|
|
|
|
|
|
|
|
return UserNotificationModel.update({ read: true }, query)
|
|
|
|
}
|
|
|
|
|
2018-12-26 03:36:24 -06:00
|
|
|
toFormattedJSON (): UserNotification {
|
2019-01-02 09:37:43 -06:00
|
|
|
const video = this.Video ? Object.assign(this.formatVideo(this.Video), {
|
2018-12-26 03:36:24 -06:00
|
|
|
channel: {
|
|
|
|
id: this.Video.VideoChannel.id,
|
|
|
|
displayName: this.Video.VideoChannel.getDisplayName()
|
|
|
|
}
|
2019-01-02 09:37:43 -06:00
|
|
|
}) : undefined
|
|
|
|
|
|
|
|
const videoImport = this.VideoImport ? {
|
|
|
|
id: this.VideoImport.id,
|
|
|
|
video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
|
|
|
|
torrentName: this.VideoImport.torrentName,
|
|
|
|
magnetUri: this.VideoImport.magnetUri,
|
|
|
|
targetUrl: this.VideoImport.targetUrl
|
2018-12-26 03:36:24 -06:00
|
|
|
} : undefined
|
|
|
|
|
|
|
|
const comment = this.Comment ? {
|
|
|
|
id: this.Comment.id,
|
2019-01-02 09:37:43 -06:00
|
|
|
threadId: this.Comment.getThreadId(),
|
2018-12-26 03:36:24 -06:00
|
|
|
account: {
|
|
|
|
id: this.Comment.Account.id,
|
|
|
|
displayName: this.Comment.Account.getDisplayName()
|
|
|
|
},
|
2019-01-02 09:37:43 -06:00
|
|
|
video: this.formatVideo(this.Comment.Video)
|
2018-12-26 03:36:24 -06:00
|
|
|
} : undefined
|
|
|
|
|
|
|
|
const videoAbuse = this.VideoAbuse ? {
|
|
|
|
id: this.VideoAbuse.id,
|
2019-01-02 09:37:43 -06:00
|
|
|
video: this.formatVideo(this.VideoAbuse.Video)
|
2018-12-26 03:36:24 -06:00
|
|
|
} : undefined
|
|
|
|
|
|
|
|
const videoBlacklist = this.VideoBlacklist ? {
|
|
|
|
id: this.VideoBlacklist.id,
|
2019-01-02 09:37:43 -06:00
|
|
|
video: this.formatVideo(this.VideoBlacklist.Video)
|
2018-12-26 03:36:24 -06:00
|
|
|
} : undefined
|
|
|
|
|
2019-01-04 01:56:20 -06:00
|
|
|
const account = this.Account ? {
|
|
|
|
id: this.Account.id,
|
|
|
|
displayName: this.Account.getDisplayName(),
|
|
|
|
name: this.Account.Actor.preferredUsername
|
|
|
|
} : undefined
|
|
|
|
|
|
|
|
const actorFollow = this.ActorFollow ? {
|
|
|
|
id: this.ActorFollow.id,
|
|
|
|
follower: {
|
|
|
|
displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
|
|
|
|
name: this.ActorFollow.ActorFollower.preferredUsername
|
|
|
|
},
|
|
|
|
following: {
|
|
|
|
type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
|
|
|
|
displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
|
|
|
|
name: this.ActorFollow.ActorFollowing.preferredUsername
|
|
|
|
}
|
|
|
|
} : undefined
|
|
|
|
|
2018-12-26 03:36:24 -06:00
|
|
|
return {
|
|
|
|
id: this.id,
|
|
|
|
type: this.type,
|
|
|
|
read: this.read,
|
|
|
|
video,
|
2019-01-02 09:37:43 -06:00
|
|
|
videoImport,
|
2018-12-26 03:36:24 -06:00
|
|
|
comment,
|
|
|
|
videoAbuse,
|
|
|
|
videoBlacklist,
|
2019-01-04 01:56:20 -06:00
|
|
|
account,
|
|
|
|
actorFollow,
|
2018-12-26 03:36:24 -06:00
|
|
|
createdAt: this.createdAt.toISOString(),
|
|
|
|
updatedAt: this.updatedAt.toISOString()
|
|
|
|
}
|
|
|
|
}
|
2019-01-02 09:37:43 -06:00
|
|
|
|
|
|
|
private formatVideo (video: VideoModel) {
|
|
|
|
return {
|
|
|
|
id: video.id,
|
|
|
|
uuid: video.uuid,
|
|
|
|
name: video.name
|
|
|
|
}
|
|
|
|
}
|
2018-12-26 03:36:24 -06:00
|
|
|
}
|