diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/process/process-announce.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 14 | ||||
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/videos.ts | 15 | ||||
-rw-r--r-- | server/lib/client-html.ts | 4 | ||||
-rw-r--r-- | server/lib/emailer.ts | 116 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-file.ts | 5 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-import.ts | 2 | ||||
-rw-r--r-- | server/lib/notifier.ts | 235 | ||||
-rw-r--r-- | server/lib/oauth-model.ts | 3 | ||||
-rw-r--r-- | server/lib/peertube-socket.ts | 52 | ||||
-rw-r--r-- | server/lib/schedulers/update-videos-scheduler.ts | 5 | ||||
-rw-r--r-- | server/lib/user.ts | 16 |
13 files changed, 425 insertions, 54 deletions
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index cc88b5423..23310b41e 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts | |||
@@ -5,6 +5,8 @@ import { ActorModel } from '../../../models/activitypub/actor' | |||
5 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
6 | import { forwardVideoRelatedActivity } from '../send/utils' | 6 | import { forwardVideoRelatedActivity } from '../send/utils' |
7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
8 | import { VideoPrivacy } from '../../../../shared/models/videos' | ||
9 | import { Notifier } from '../../notifier' | ||
8 | 10 | ||
9 | async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { | 11 | async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { |
10 | return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) | 12 | return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) |
@@ -21,9 +23,9 @@ export { | |||
21 | async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { | 23 | async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { |
22 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id | 24 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id |
23 | 25 | ||
24 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) | 26 | const { video, created: videoCreated } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) |
25 | 27 | ||
26 | return sequelizeTypescript.transaction(async t => { | 28 | await sequelizeTypescript.transaction(async t => { |
27 | // Add share entry | 29 | // Add share entry |
28 | 30 | ||
29 | const share = { | 31 | const share = { |
@@ -49,4 +51,6 @@ async function processVideoShare (actorAnnouncer: ActorModel, activity: Activity | |||
49 | 51 | ||
50 | return undefined | 52 | return undefined |
51 | }) | 53 | }) |
54 | |||
55 | if (videoCreated) Notifier.Instance.notifyOnNewVideo(video) | ||
52 | } | 56 | } |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index df05ee452..2e04ee843 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -13,6 +13,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' | |||
13 | import { Redis } from '../../redis' | 13 | import { Redis } from '../../redis' |
14 | import { createOrUpdateCacheFile } from '../cache-file' | 14 | import { createOrUpdateCacheFile } from '../cache-file' |
15 | import { getVideoDislikeActivityPubUrl } from '../url' | 15 | import { getVideoDislikeActivityPubUrl } from '../url' |
16 | import { Notifier } from '../../notifier' | ||
16 | 17 | ||
17 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { | 18 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { |
18 | const activityObject = activity.object | 19 | const activityObject = activity.object |
@@ -47,7 +48,9 @@ export { | |||
47 | async function processCreateVideo (activity: ActivityCreate) { | 48 | async function processCreateVideo (activity: ActivityCreate) { |
48 | const videoToCreateData = activity.object as VideoTorrentObject | 49 | const videoToCreateData = activity.object as VideoTorrentObject |
49 | 50 | ||
50 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData }) | 51 | const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData }) |
52 | |||
53 | if (created) Notifier.Instance.notifyOnNewVideo(video) | ||
51 | 54 | ||
52 | return video | 55 | return video |
53 | } | 56 | } |
@@ -133,7 +136,10 @@ async function processCreateVideoAbuse (byActor: ActorModel, videoAbuseToCreateD | |||
133 | state: VideoAbuseState.PENDING | 136 | state: VideoAbuseState.PENDING |
134 | } | 137 | } |
135 | 138 | ||
136 | await VideoAbuseModel.create(videoAbuseData, { transaction: t }) | 139 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) |
140 | videoAbuseInstance.Video = video | ||
141 | |||
142 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuseInstance) | ||
137 | 143 | ||
138 | logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) | 144 | logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) |
139 | }) | 145 | }) |
@@ -147,7 +153,7 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit | |||
147 | 153 | ||
148 | const { video } = await resolveThread(commentObject.inReplyTo) | 154 | const { video } = await resolveThread(commentObject.inReplyTo) |
149 | 155 | ||
150 | const { created } = await addVideoComment(video, commentObject.id) | 156 | const { comment, created } = await addVideoComment(video, commentObject.id) |
151 | 157 | ||
152 | if (video.isOwned() && created === true) { | 158 | if (video.isOwned() && created === true) { |
153 | // Don't resend the activity to the sender | 159 | // Don't resend the activity to the sender |
@@ -155,4 +161,6 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit | |||
155 | 161 | ||
156 | await forwardVideoRelatedActivity(activity, undefined, exceptions, video) | 162 | await forwardVideoRelatedActivity(activity, undefined, exceptions, video) |
157 | } | 163 | } |
164 | |||
165 | if (created === true) Notifier.Instance.notifyOnNewComment(comment) | ||
158 | } | 166 | } |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 5868e7297..e87301fe7 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -70,7 +70,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | |||
70 | throw new Error(`Comment url ${commentUrl} host is different from the AP object id ${body.id}`) | 70 | throw new Error(`Comment url ${commentUrl} host is different from the AP object id ${body.id}`) |
71 | } | 71 | } |
72 | 72 | ||
73 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | 73 | const actor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') |
74 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) | 74 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) |
75 | if (!entry) return { created: false } | 75 | if (!entry) return { created: false } |
76 | 76 | ||
@@ -80,6 +80,8 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | |||
80 | }, | 80 | }, |
81 | defaults: entry | 81 | defaults: entry |
82 | }) | 82 | }) |
83 | comment.Account = actor.Account | ||
84 | comment.Video = videoInstance | ||
83 | 85 | ||
84 | return { comment, created } | 86 | return { comment, created } |
85 | } | 87 | } |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 379c2a0d7..5794988a5 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -29,6 +29,7 @@ import { addVideoShares, shareVideoByServerAndChannel } from './share' | |||
29 | import { AccountModel } from '../../models/account/account' | 29 | import { AccountModel } from '../../models/account/account' |
30 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 30 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
31 | import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub' | 31 | import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub' |
32 | import { Notifier } from '../notifier' | ||
32 | 33 | ||
33 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 34 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
34 | // If the video is not private and published, we federate it | 35 | // If the video is not private and published, we federate it |
@@ -181,7 +182,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
181 | else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoFromDatabase.url } }) | 182 | else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoFromDatabase.url } }) |
182 | } | 183 | } |
183 | 184 | ||
184 | return { video: videoFromDatabase } | 185 | return { video: videoFromDatabase, created: false } |
185 | } | 186 | } |
186 | 187 | ||
187 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) | 188 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) |
@@ -192,7 +193,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
192 | 193 | ||
193 | await syncVideoExternalAttributes(video, fetchedVideo, syncParam) | 194 | await syncVideoExternalAttributes(video, fetchedVideo, syncParam) |
194 | 195 | ||
195 | return { video } | 196 | return { video, created: true } |
196 | } | 197 | } |
197 | 198 | ||
198 | async function updateVideoFromAP (options: { | 199 | async function updateVideoFromAP (options: { |
@@ -213,6 +214,9 @@ async function updateVideoFromAP (options: { | |||
213 | 214 | ||
214 | videoFieldsSave = options.video.toJSON() | 215 | videoFieldsSave = options.video.toJSON() |
215 | 216 | ||
217 | const wasPrivateVideo = options.video.privacy === VideoPrivacy.PRIVATE | ||
218 | const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED | ||
219 | |||
216 | // Check actor has the right to update the video | 220 | // Check actor has the right to update the video |
217 | const videoChannel = options.video.VideoChannel | 221 | const videoChannel = options.video.VideoChannel |
218 | if (videoChannel.Account.id !== options.account.id) { | 222 | if (videoChannel.Account.id !== options.account.id) { |
@@ -277,6 +281,13 @@ async function updateVideoFromAP (options: { | |||
277 | }) | 281 | }) |
278 | options.video.VideoCaptions = await Promise.all(videoCaptionsPromises) | 282 | options.video.VideoCaptions = await Promise.all(videoCaptionsPromises) |
279 | } | 283 | } |
284 | |||
285 | { | ||
286 | // Notify our users? | ||
287 | if (wasPrivateVideo || wasUnlistedVideo) { | ||
288 | Notifier.Instance.notifyOnNewVideo(options.video) | ||
289 | } | ||
290 | } | ||
280 | }) | 291 | }) |
281 | 292 | ||
282 | logger.info('Remote video with uuid %s updated', options.videoObject.uuid) | 293 | logger.info('Remote video with uuid %s updated', options.videoObject.uuid) |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 2db3f8a34..1875ec1fc 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -115,8 +115,8 @@ export class ClientHtml { | |||
115 | } | 115 | } |
116 | 116 | ||
117 | private static addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { | 117 | private static addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { |
118 | const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() | 118 | const previewUrl = CONFIG.WEBSERVER.URL + video.getPreviewStaticPath() |
119 | const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | 119 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() |
120 | 120 | ||
121 | const videoNameEscaped = escapeHTML(video.name) | 121 | const videoNameEscaped = escapeHTML(video.name) |
122 | const videoDescriptionEscaped = escapeHTML(video.description) | 122 | const videoDescriptionEscaped = escapeHTML(video.description) |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 074d4ad44..d766e655b 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { createTransport, Transporter } from 'nodemailer' | 1 | import { createTransport, Transporter } from 'nodemailer' |
2 | import { UserRight } from '../../shared/models/users' | ||
3 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance } from '../helpers/core-utils' |
4 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
5 | import { CONFIG } from '../initializers' | 4 | import { CONFIG } from '../initializers' |
@@ -8,6 +7,9 @@ import { VideoModel } from '../models/video/video' | |||
8 | import { JobQueue } from './job-queue' | 7 | import { JobQueue } from './job-queue' |
9 | import { EmailPayload } from './job-queue/handlers/email' | 8 | import { EmailPayload } from './job-queue/handlers/email' |
10 | import { readFileSync } from 'fs-extra' | 9 | import { readFileSync } from 'fs-extra' |
10 | import { VideoCommentModel } from '../models/video/video-comment' | ||
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
11 | 13 | ||
12 | class Emailer { | 14 | class Emailer { |
13 | 15 | ||
@@ -79,50 +81,57 @@ class Emailer { | |||
79 | } | 81 | } |
80 | } | 82 | } |
81 | 83 | ||
82 | addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) { | 84 | addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { |
85 | const channelName = video.VideoChannel.getDisplayName() | ||
86 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | ||
87 | |||
83 | const text = `Hi dear user,\n\n` + | 88 | const text = `Hi dear user,\n\n` + |
84 | `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` + | 89 | `Your subscription ${channelName} just published a new video: ${video.name}` + |
85 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + | 90 | `\n\n` + |
86 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 91 | `You can view it on ${videoUrl} ` + |
92 | `\n\n` + | ||
87 | `Cheers,\n` + | 93 | `Cheers,\n` + |
88 | `PeerTube.` | 94 | `PeerTube.` |
89 | 95 | ||
90 | const emailPayload: EmailPayload = { | 96 | const emailPayload: EmailPayload = { |
91 | to: [ to ], | 97 | to, |
92 | subject: 'Reset your PeerTube password', | 98 | subject: channelName + ' just published a new video', |
93 | text | 99 | text |
94 | } | 100 | } |
95 | 101 | ||
96 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 102 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
97 | } | 103 | } |
98 | 104 | ||
99 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 105 | addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { |
100 | const text = `Welcome to PeerTube,\n\n` + | 106 | const accountName = comment.Account.getDisplayName() |
101 | `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` + | 107 | const video = comment.Video |
102 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | 108 | const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath() |
103 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 109 | |
110 | const text = `Hi dear user,\n\n` + | ||
111 | `A new comment has been posted by ${accountName} on your video ${video.name}` + | ||
112 | `\n\n` + | ||
113 | `You can view it on ${commentUrl} ` + | ||
114 | `\n\n` + | ||
104 | `Cheers,\n` + | 115 | `Cheers,\n` + |
105 | `PeerTube.` | 116 | `PeerTube.` |
106 | 117 | ||
107 | const emailPayload: EmailPayload = { | 118 | const emailPayload: EmailPayload = { |
108 | to: [ to ], | 119 | to, |
109 | subject: 'Verify your PeerTube email', | 120 | subject: 'New comment on your video ' + video.name, |
110 | text | 121 | text |
111 | } | 122 | } |
112 | 123 | ||
113 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 124 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
114 | } | 125 | } |
115 | 126 | ||
116 | async addVideoAbuseReportJob (videoId: number) { | 127 | async addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { |
117 | const video = await VideoModel.load(videoId) | 128 | const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() |
118 | if (!video) throw new Error('Unknown Video id during Abuse report.') | ||
119 | 129 | ||
120 | const text = `Hi,\n\n` + | 130 | const text = `Hi,\n\n` + |
121 | `Your instance received an abuse for the following video ${video.url}\n\n` + | 131 | `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + |
122 | `Cheers,\n` + | 132 | `Cheers,\n` + |
123 | `PeerTube.` | 133 | `PeerTube.` |
124 | 134 | ||
125 | const to = await UserModel.listEmailsWithRight(UserRight.MANAGE_VIDEO_ABUSES) | ||
126 | const emailPayload: EmailPayload = { | 135 | const emailPayload: EmailPayload = { |
127 | to, | 136 | to, |
128 | subject: '[PeerTube] Received a video abuse', | 137 | subject: '[PeerTube] Received a video abuse', |
@@ -132,16 +141,12 @@ class Emailer { | |||
132 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 141 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
133 | } | 142 | } |
134 | 143 | ||
135 | async addVideoBlacklistReportJob (videoId: number, reason?: string) { | 144 | async addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { |
136 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 145 | const videoName = videoBlacklist.Video.name |
137 | if (!video) throw new Error('Unknown Video id during Blacklist report.') | 146 | const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
138 | // It's not our user | ||
139 | if (video.remote === true) return | ||
140 | 147 | ||
141 | const user = await UserModel.loadById(video.VideoChannel.Account.userId) | 148 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' |
142 | 149 | const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.` | |
143 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | ||
144 | const blockedString = `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.` | ||
145 | 150 | ||
146 | const text = 'Hi,\n\n' + | 151 | const text = 'Hi,\n\n' + |
147 | blockedString + | 152 | blockedString + |
@@ -149,33 +154,26 @@ class Emailer { | |||
149 | 'Cheers,\n' + | 154 | 'Cheers,\n' + |
150 | `PeerTube.` | 155 | `PeerTube.` |
151 | 156 | ||
152 | const to = user.email | ||
153 | const emailPayload: EmailPayload = { | 157 | const emailPayload: EmailPayload = { |
154 | to: [ to ], | 158 | to, |
155 | subject: `[PeerTube] Video ${video.name} blacklisted`, | 159 | subject: `[PeerTube] Video ${videoName} blacklisted`, |
156 | text | 160 | text |
157 | } | 161 | } |
158 | 162 | ||
159 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 163 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
160 | } | 164 | } |
161 | 165 | ||
162 | async addVideoUnblacklistReportJob (videoId: number) { | 166 | async addVideoUnblacklistNotification (to: string[], video: VideoModel) { |
163 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 167 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() |
164 | if (!video) throw new Error('Unknown Video id during Blacklist report.') | ||
165 | // It's not our user | ||
166 | if (video.remote === true) return | ||
167 | |||
168 | const user = await UserModel.loadById(video.VideoChannel.Account.userId) | ||
169 | 168 | ||
170 | const text = 'Hi,\n\n' + | 169 | const text = 'Hi,\n\n' + |
171 | `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` + | 170 | `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` + |
172 | '\n\n' + | 171 | '\n\n' + |
173 | 'Cheers,\n' + | 172 | 'Cheers,\n' + |
174 | `PeerTube.` | 173 | `PeerTube.` |
175 | 174 | ||
176 | const to = user.email | ||
177 | const emailPayload: EmailPayload = { | 175 | const emailPayload: EmailPayload = { |
178 | to: [ to ], | 176 | to, |
179 | subject: `[PeerTube] Video ${video.name} unblacklisted`, | 177 | subject: `[PeerTube] Video ${video.name} unblacklisted`, |
180 | text | 178 | text |
181 | } | 179 | } |
@@ -183,6 +181,40 @@ class Emailer { | |||
183 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 181 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
184 | } | 182 | } |
185 | 183 | ||
184 | addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) { | ||
185 | const text = `Hi dear user,\n\n` + | ||
186 | `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` + | ||
187 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + | ||
188 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | ||
189 | `Cheers,\n` + | ||
190 | `PeerTube.` | ||
191 | |||
192 | const emailPayload: EmailPayload = { | ||
193 | to: [ to ], | ||
194 | subject: 'Reset your PeerTube password', | ||
195 | text | ||
196 | } | ||
197 | |||
198 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
199 | } | ||
200 | |||
201 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | ||
202 | const text = `Welcome to PeerTube,\n\n` + | ||
203 | `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` + | ||
204 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | ||
205 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | ||
206 | `Cheers,\n` + | ||
207 | `PeerTube.` | ||
208 | |||
209 | const emailPayload: EmailPayload = { | ||
210 | to: [ to ], | ||
211 | subject: 'Verify your PeerTube email', | ||
212 | text | ||
213 | } | ||
214 | |||
215 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
216 | } | ||
217 | |||
186 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { | 218 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { |
187 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 219 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
188 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 220 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
@@ -205,7 +237,7 @@ class Emailer { | |||
205 | } | 237 | } |
206 | 238 | ||
207 | sendMail (to: string[], subject: string, text: string) { | 239 | sendMail (to: string[], subject: string, text: string) { |
208 | if (!this.transporter) { | 240 | if (!this.enabled) { |
209 | throw new Error('Cannot send mail because SMTP is not configured.') | 241 | throw new Error('Cannot send mail because SMTP is not configured.') |
210 | } | 242 | } |
211 | 243 | ||
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-file.ts index 3dca2937f..959cc04fa 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-file.ts | |||
@@ -9,6 +9,7 @@ import { sequelizeTypescript } from '../../../initializers' | |||
9 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { importVideoFile, transcodeOriginalVideofile, optimizeVideofile } from '../../video-transcoding' | 11 | import { importVideoFile, transcodeOriginalVideofile, optimizeVideofile } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | ||
12 | 13 | ||
13 | export type VideoFilePayload = { | 14 | export type VideoFilePayload = { |
14 | videoUUID: string | 15 | videoUUID: string |
@@ -86,6 +87,7 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { | |||
86 | 87 | ||
87 | // If the video was not published, we consider it is a new one for other instances | 88 | // If the video was not published, we consider it is a new one for other instances |
88 | await federateVideoIfNeeded(videoDatabase, isNewVideo, t) | 89 | await federateVideoIfNeeded(videoDatabase, isNewVideo, t) |
90 | if (isNewVideo) Notifier.Instance.notifyOnNewVideo(video) | ||
89 | 91 | ||
90 | return undefined | 92 | return undefined |
91 | }) | 93 | }) |
@@ -134,7 +136,8 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo | |||
134 | logger.info('No transcoding jobs created for video %s (no resolutions).', videoDatabase.uuid, { privacy: videoDatabase.privacy }) | 136 | logger.info('No transcoding jobs created for video %s (no resolutions).', videoDatabase.uuid, { privacy: videoDatabase.privacy }) |
135 | } | 137 | } |
136 | 138 | ||
137 | return federateVideoIfNeeded(videoDatabase, isNewVideo, t) | 139 | await federateVideoIfNeeded(videoDatabase, isNewVideo, t) |
140 | if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) | ||
138 | }) | 141 | }) |
139 | } | 142 | } |
140 | 143 | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 63aacff98..82edb8d5c 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -15,6 +15,7 @@ import { VideoModel } from '../../../models/video/video' | |||
15 | import { downloadWebTorrentVideo } from '../../../helpers/webtorrent' | 15 | import { downloadWebTorrentVideo } from '../../../helpers/webtorrent' |
16 | import { getSecureTorrentName } from '../../../helpers/utils' | 16 | import { getSecureTorrentName } from '../../../helpers/utils' |
17 | import { remove, move, stat } from 'fs-extra' | 17 | import { remove, move, stat } from 'fs-extra' |
18 | import { Notifier } from '../../notifier' | ||
18 | 19 | ||
19 | type VideoImportYoutubeDLPayload = { | 20 | type VideoImportYoutubeDLPayload = { |
20 | type: 'youtube-dl' | 21 | type: 'youtube-dl' |
@@ -184,6 +185,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
184 | // Now we can federate the video (reload from database, we need more attributes) | 185 | // Now we can federate the video (reload from database, we need more attributes) |
185 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 186 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
186 | await federateVideoIfNeeded(videoForFederation, true, t) | 187 | await federateVideoIfNeeded(videoForFederation, true, t) |
188 | Notifier.Instance.notifyOnNewVideo(videoForFederation) | ||
187 | 189 | ||
188 | // Update video import object | 190 | // Update video import object |
189 | videoImport.state = VideoImportState.SUCCESS | 191 | videoImport.state = VideoImportState.SUCCESS |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts new file mode 100644 index 000000000..a21b50b2d --- /dev/null +++ b/server/lib/notifier.ts | |||
@@ -0,0 +1,235 @@ | |||
1 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' | ||
2 | import { logger } from '../helpers/logger' | ||
3 | import { VideoModel } from '../models/video/video' | ||
4 | import { Emailer } from './emailer' | ||
5 | import { UserNotificationModel } from '../models/account/user-notification' | ||
6 | import { VideoCommentModel } from '../models/video/video-comment' | ||
7 | import { UserModel } from '../models/account/user' | ||
8 | import { PeerTubeSocket } from './peertube-socket' | ||
9 | import { CONFIG } from '../initializers/constants' | ||
10 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | ||
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
13 | import * as Bluebird from 'bluebird' | ||
14 | |||
15 | class Notifier { | ||
16 | |||
17 | private static instance: Notifier | ||
18 | |||
19 | private constructor () {} | ||
20 | |||
21 | notifyOnNewVideo (video: VideoModel): void { | ||
22 | // Only notify on public and published videos | ||
23 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED) return | ||
24 | |||
25 | this.notifySubscribersOfNewVideo(video) | ||
26 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | ||
27 | } | ||
28 | |||
29 | notifyOnNewComment (comment: VideoCommentModel): void { | ||
30 | this.notifyVideoOwnerOfNewComment(comment) | ||
31 | .catch(err => logger.error('Cannot notify of new comment %s.', comment.url, { err })) | ||
32 | } | ||
33 | |||
34 | notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { | ||
35 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) | ||
36 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | ||
37 | } | ||
38 | |||
39 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { | ||
40 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | ||
41 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | ||
42 | } | ||
43 | |||
44 | notifyOnVideoUnblacklist (video: VideoModel): void { | ||
45 | this.notifyVideoOwnerOfUnblacklist(video) | ||
46 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err })) | ||
47 | } | ||
48 | |||
49 | private async notifySubscribersOfNewVideo (video: VideoModel) { | ||
50 | // List all followers that are users | ||
51 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) | ||
52 | |||
53 | logger.info('Notifying %d users of new video %s.', users.length, video.url) | ||
54 | |||
55 | function settingGetter (user: UserModel) { | ||
56 | return user.NotificationSetting.newVideoFromSubscription | ||
57 | } | ||
58 | |||
59 | async function notificationCreator (user: UserModel) { | ||
60 | const notification = await UserNotificationModel.create({ | ||
61 | type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, | ||
62 | userId: user.id, | ||
63 | videoId: video.id | ||
64 | }) | ||
65 | notification.Video = video | ||
66 | |||
67 | return notification | ||
68 | } | ||
69 | |||
70 | function emailSender (emails: string[]) { | ||
71 | return Emailer.Instance.addNewVideoFromSubscriberNotification(emails, video) | ||
72 | } | ||
73 | |||
74 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | ||
75 | } | ||
76 | |||
77 | private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { | ||
78 | const user = await UserModel.loadByVideoId(comment.videoId) | ||
79 | |||
80 | // Not our user or user comments its own video | ||
81 | if (!user || comment.Account.userId === user.id) return | ||
82 | |||
83 | logger.info('Notifying user %s of new comment %s.', user.username, comment.url) | ||
84 | |||
85 | function settingGetter (user: UserModel) { | ||
86 | return user.NotificationSetting.newCommentOnMyVideo | ||
87 | } | ||
88 | |||
89 | async function notificationCreator (user: UserModel) { | ||
90 | const notification = await UserNotificationModel.create({ | ||
91 | type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, | ||
92 | userId: user.id, | ||
93 | commentId: comment.id | ||
94 | }) | ||
95 | notification.Comment = comment | ||
96 | |||
97 | return notification | ||
98 | } | ||
99 | |||
100 | function emailSender (emails: string[]) { | ||
101 | return Emailer.Instance.addNewCommentOnMyVideoNotification(emails, comment) | ||
102 | } | ||
103 | |||
104 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | ||
105 | } | ||
106 | |||
107 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { | ||
108 | const users = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | ||
109 | if (users.length === 0) return | ||
110 | |||
111 | logger.info('Notifying %s user/moderators of new video abuse %s.', users.length, videoAbuse.Video.url) | ||
112 | |||
113 | function settingGetter (user: UserModel) { | ||
114 | return user.NotificationSetting.videoAbuseAsModerator | ||
115 | } | ||
116 | |||
117 | async function notificationCreator (user: UserModel) { | ||
118 | const notification = await UserNotificationModel.create({ | ||
119 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | ||
120 | userId: user.id, | ||
121 | videoAbuseId: videoAbuse.id | ||
122 | }) | ||
123 | notification.VideoAbuse = videoAbuse | ||
124 | |||
125 | return notification | ||
126 | } | ||
127 | |||
128 | function emailSender (emails: string[]) { | ||
129 | return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse) | ||
130 | } | ||
131 | |||
132 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | ||
133 | } | ||
134 | |||
135 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { | ||
136 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) | ||
137 | if (!user) return | ||
138 | |||
139 | logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) | ||
140 | |||
141 | function settingGetter (user: UserModel) { | ||
142 | return user.NotificationSetting.blacklistOnMyVideo | ||
143 | } | ||
144 | |||
145 | async function notificationCreator (user: UserModel) { | ||
146 | const notification = await UserNotificationModel.create({ | ||
147 | type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, | ||
148 | userId: user.id, | ||
149 | videoBlacklistId: videoBlacklist.id | ||
150 | }) | ||
151 | notification.VideoBlacklist = videoBlacklist | ||
152 | |||
153 | return notification | ||
154 | } | ||
155 | |||
156 | function emailSender (emails: string[]) { | ||
157 | return Emailer.Instance.addVideoBlacklistNotification(emails, videoBlacklist) | ||
158 | } | ||
159 | |||
160 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | ||
161 | } | ||
162 | |||
163 | private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { | ||
164 | const user = await UserModel.loadByVideoId(video.id) | ||
165 | if (!user) return | ||
166 | |||
167 | logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) | ||
168 | |||
169 | function settingGetter (user: UserModel) { | ||
170 | return user.NotificationSetting.blacklistOnMyVideo | ||
171 | } | ||
172 | |||
173 | async function notificationCreator (user: UserModel) { | ||
174 | const notification = await UserNotificationModel.create({ | ||
175 | type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, | ||
176 | userId: user.id, | ||
177 | videoId: video.id | ||
178 | }) | ||
179 | notification.Video = video | ||
180 | |||
181 | return notification | ||
182 | } | ||
183 | |||
184 | function emailSender (emails: string[]) { | ||
185 | return Emailer.Instance.addVideoUnblacklistNotification(emails, video) | ||
186 | } | ||
187 | |||
188 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | ||
189 | } | ||
190 | |||
191 | private async notify (options: { | ||
192 | users: UserModel[], | ||
193 | notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, | ||
194 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, | ||
195 | settingGetter: (user: UserModel) => UserNotificationSettingValue | ||
196 | }) { | ||
197 | const emails: string[] = [] | ||
198 | |||
199 | for (const user of options.users) { | ||
200 | if (this.isWebNotificationEnabled(options.settingGetter(user))) { | ||
201 | const notification = await options.notificationCreator(user) | ||
202 | |||
203 | PeerTubeSocket.Instance.sendNotification(user.id, notification) | ||
204 | } | ||
205 | |||
206 | if (this.isEmailEnabled(user, options.settingGetter(user))) { | ||
207 | emails.push(user.email) | ||
208 | } | ||
209 | } | ||
210 | |||
211 | if (emails.length !== 0) { | ||
212 | await options.emailSender(emails) | ||
213 | } | ||
214 | } | ||
215 | |||
216 | private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { | ||
217 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified !== true) return false | ||
218 | |||
219 | return value === UserNotificationSettingValue.EMAIL || value === UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL | ||
220 | } | ||
221 | |||
222 | private isWebNotificationEnabled (value: UserNotificationSettingValue) { | ||
223 | return value === UserNotificationSettingValue.WEB_NOTIFICATION || value === UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL | ||
224 | } | ||
225 | |||
226 | static get Instance () { | ||
227 | return this.instance || (this.instance = new this()) | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // --------------------------------------------------------------------------- | ||
232 | |||
233 | export { | ||
234 | Notifier | ||
235 | } | ||
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 5cbe60b82..2cd2ae97c 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
1 | import { AccessDeniedError } from 'oauth2-server' | 2 | import { AccessDeniedError } from 'oauth2-server' |
2 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
3 | import { UserModel } from '../models/account/user' | 4 | import { UserModel } from '../models/account/user' |
@@ -37,7 +38,7 @@ function clearCacheByToken (token: string) { | |||
37 | function getAccessToken (bearerToken: string) { | 38 | function getAccessToken (bearerToken: string) { |
38 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 39 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
39 | 40 | ||
40 | if (accessTokenCache[bearerToken] !== undefined) return accessTokenCache[bearerToken] | 41 | if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken]) |
41 | 42 | ||
42 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) | 43 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
43 | .then(tokenModel => { | 44 | .then(tokenModel => { |
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts new file mode 100644 index 000000000..eb84ecd4b --- /dev/null +++ b/server/lib/peertube-socket.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import * as SocketIO from 'socket.io' | ||
2 | import { authenticateSocket } from '../middlewares' | ||
3 | import { UserNotificationModel } from '../models/account/user-notification' | ||
4 | import { logger } from '../helpers/logger' | ||
5 | import { Server } from 'http' | ||
6 | |||
7 | class PeerTubeSocket { | ||
8 | |||
9 | private static instance: PeerTubeSocket | ||
10 | |||
11 | private userNotificationSockets: { [ userId: number ]: SocketIO.Socket } = {} | ||
12 | |||
13 | private constructor () {} | ||
14 | |||
15 | init (server: Server) { | ||
16 | const io = SocketIO(server) | ||
17 | |||
18 | io.of('/user-notifications') | ||
19 | .use(authenticateSocket) | ||
20 | .on('connection', socket => { | ||
21 | const userId = socket.handshake.query.user.id | ||
22 | |||
23 | logger.debug('User %d connected on the notification system.', userId) | ||
24 | |||
25 | this.userNotificationSockets[userId] = socket | ||
26 | |||
27 | socket.on('disconnect', () => { | ||
28 | logger.debug('User %d disconnected from SocketIO notifications.', userId) | ||
29 | |||
30 | delete this.userNotificationSockets[userId] | ||
31 | }) | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | sendNotification (userId: number, notification: UserNotificationModel) { | ||
36 | const socket = this.userNotificationSockets[userId] | ||
37 | |||
38 | if (!socket) return | ||
39 | |||
40 | socket.emit('new-notification', notification.toFormattedJSON()) | ||
41 | } | ||
42 | |||
43 | static get Instance () { | ||
44 | return this.instance || (this.instance = new this()) | ||
45 | } | ||
46 | } | ||
47 | |||
48 | // --------------------------------------------------------------------------- | ||
49 | |||
50 | export { | ||
51 | PeerTubeSocket | ||
52 | } | ||
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 21f071f9e..b7fb029f1 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -5,6 +5,7 @@ import { retryTransactionWrapper } from '../../helpers/database-utils' | |||
5 | import { federateVideoIfNeeded } from '../activitypub' | 5 | import { federateVideoIfNeeded } from '../activitypub' |
6 | import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers' | 6 | import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers' |
7 | import { VideoPrivacy } from '../../../shared/models/videos' | 7 | import { VideoPrivacy } from '../../../shared/models/videos' |
8 | import { Notifier } from '../notifier' | ||
8 | 9 | ||
9 | export class UpdateVideosScheduler extends AbstractScheduler { | 10 | export class UpdateVideosScheduler extends AbstractScheduler { |
10 | 11 | ||
@@ -39,6 +40,10 @@ export class UpdateVideosScheduler extends AbstractScheduler { | |||
39 | 40 | ||
40 | await video.save({ transaction: t }) | 41 | await video.save({ transaction: t }) |
41 | await federateVideoIfNeeded(video, isNewVideo, t) | 42 | await federateVideoIfNeeded(video, isNewVideo, t) |
43 | |||
44 | if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) { | ||
45 | Notifier.Instance.notifyOnNewVideo(video) | ||
46 | } | ||
42 | } | 47 | } |
43 | 48 | ||
44 | await schedule.destroy({ transaction: t }) | 49 | await schedule.destroy({ transaction: t }) |
diff --git a/server/lib/user.ts b/server/lib/user.ts index 29d6d087d..72127819c 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -9,6 +9,8 @@ import { createVideoChannel } from './video-channel' | |||
9 | import { VideoChannelModel } from '../models/video/video-channel' | 9 | import { VideoChannelModel } from '../models/video/video-channel' |
10 | import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' | 10 | import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' |
11 | import { ActorModel } from '../models/activitypub/actor' | 11 | import { ActorModel } from '../models/activitypub/actor' |
12 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
13 | import { UserNotificationSettingValue } from '../../shared/models/users' | ||
12 | 14 | ||
13 | async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) { | 15 | async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) { |
14 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | 16 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { |
@@ -18,6 +20,8 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse | |||
18 | } | 20 | } |
19 | 21 | ||
20 | const userCreated = await userToCreate.save(userOptions) | 22 | const userCreated = await userToCreate.save(userOptions) |
23 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) | ||
24 | |||
21 | const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) | 25 | const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) |
22 | userCreated.Account = accountCreated | 26 | userCreated.Account = accountCreated |
23 | 27 | ||
@@ -88,3 +92,15 @@ export { | |||
88 | createUserAccountAndChannel, | 92 | createUserAccountAndChannel, |
89 | createLocalAccountWithoutKeys | 93 | createLocalAccountWithoutKeys |
90 | } | 94 | } |
95 | |||
96 | // --------------------------------------------------------------------------- | ||
97 | |||
98 | function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Transaction | undefined) { | ||
99 | return UserNotificationSettingModel.create({ | ||
100 | userId: user.id, | ||
101 | newVideoFromSubscription: UserNotificationSettingValue.WEB_NOTIFICATION, | ||
102 | newCommentOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION, | ||
103 | videoAbuseAsModerator: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL, | ||
104 | blacklistOnMyVideo: UserNotificationSettingValue.WEB_NOTIFICATION_AND_EMAIL | ||
105 | }, { transaction: t }) | ||
106 | } | ||