diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 317cec70689..bc0061c998c 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts @@ -1,7 +1,10 @@ import { createTransport, Transporter } from 'nodemailer' +import { UserRight } from '../../shared/models/users' import { isTestInstance } from '../helpers/core-utils' import { logger } from '../helpers/logger' import { CONFIG } from '../initializers' +import { UserModel } from '../models/account/user' +import { VideoModel } from '../models/video/video' import { JobQueue } from './job-queue' import { EmailPayload } from './job-queue/handlers/email' import { readFileSync } from 'fs' @@ -82,6 +85,24 @@ class Emailer { return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) } + async addVideoAbuseReport (videoId: number) { + const video = await VideoModel.load(videoId) + + const text = `Hi,\n\n` + + `Your instance received an abuse for video the following video ${video.url}\n\n` + + `Cheers,\n` + + `PeerTube.` + + const to = await UserModel.listEmailsWithRight(UserRight.MANAGE_VIDEO_ABUSES) + const emailPayload: EmailPayload = { + to, + subject: '[PeerTube] Received a video abuse', + text + } + + return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) + } + sendMail (to: string[], subject: string, text: string) { if (!this.transporter) { throw new Error('Cannot send mail because SMTP is not configured.') diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 026a8c9a0d6..65392190781 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -4,7 +4,7 @@ import { Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' -import { User } from '../../../shared/models/users' +import { User, UserRole } from '../../../shared/models/users' import { isUserAutoPlayVideoValid, isUserDisplayNSFWValid, isUserPasswordValid, isUserRoleValid, isUserUsernameValid, isUserVideoQuotaValid @@ -137,6 +137,27 @@ export class UserModel extends Model { }) } + static listEmailsWithRight (right: UserRight) { + const roles = Object.keys(USER_ROLE_LABELS) + .map(k => parseInt(k, 10) as UserRole) + .filter(role => hasUserRight(role, right)) + + console.log(roles) + + const query = { + attribute: [ 'email' ], + where: { + role: { + [Sequelize.Op.in]: roles + } + } + } + + return UserModel.unscoped() + .findAll(query) + .then(u => u.map(u => u.email)) + } + static loadById (id: number) { return UserModel.findById(id) } diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 182971c4e01..cc7078ae796 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -1,7 +1,8 @@ -import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' +import { AfterCreate, AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' import { CONFIG } from '../../initializers' +import { Emailer } from '../../lib/emailer' import { AccountModel } from '../account/account' import { getSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' @@ -54,6 +55,11 @@ export class VideoAbuseModel extends Model { }) Video: VideoModel + @AfterCreate + static sendEmailNotification (instance: VideoAbuseModel) { + return Emailer.Instance.addVideoAbuseReport(instance.videoId) + } + static listForApi (start: number, count: number, sort: string) { const query = { offset: start, diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts index 8eb9c0fa405..068e820c8e5 100644 --- a/server/tests/api/server/email.ts +++ b/server/tests/api/server/email.ts @@ -2,7 +2,7 @@ import * as chai from 'chai' import 'mocha' -import { askResetPassword, createUser, resetPassword, runServer, userLogin, wait } from '../../utils' +import { askResetPassword, createUser, reportVideoAbuse, resetPassword, runServer, uploadVideo, userLogin, wait } from '../../utils' import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index' import { mockSmtpServer } from '../../utils/miscs/email' @@ -11,6 +11,7 @@ const expect = chai.expect describe('Test emails', function () { let server: ServerInfo let userId: number + let videoUUID: string let verificationString: string const emails: object[] = [] const user = { @@ -35,8 +36,18 @@ describe('Test emails', function () { await wait(5000) await setAccessTokensToServers([ server ]) - const res = await createUser(server.url, server.accessToken, user.username, user.password) - userId = res.body.user.id + { + const res = await createUser(server.url, server.accessToken, user.username, user.password) + userId = res.body.user.id + } + + { + const attributes = { + name: 'my super name' + } + const res = await uploadVideo(server.url, server.accessToken, attributes) + videoUUID = res.body.video.uuid + } }) describe('When resetting user password', function () { @@ -83,6 +94,25 @@ describe('Test emails', function () { }) }) + describe('When creating a video abuse', function () { + it('Should send the notification email', async function () { + this.timeout(10000) + + const reason = 'my super bad reason' + await reportVideoAbuse(server.url, server.accessToken, videoUUID, reason) + + await wait(3000) + expect(emails).to.have.lengthOf(2) + + const email = emails[1] + + expect(email['from'][0]['address']).equal('test-admin@localhost') + expect(email['to'][0]['address']).equal('admin1@example.com') + expect(email['subject']).contains('abuse') + expect(email['text']).contains(videoUUID) + }) + }) + after(async function () { killallServers([ server ]) diff --git a/server/tests/utils/videos/video-abuses.ts b/server/tests/utils/videos/video-abuses.ts index f0080923446..0d72bf457a8 100644 --- a/server/tests/utils/videos/video-abuses.ts +++ b/server/tests/utils/videos/video-abuses.ts @@ -1,6 +1,6 @@ import * as request from 'supertest' -function reportVideoAbuse (url: string, token: string, videoId: number, reason: string, specialStatus = 204) { +function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 204) { const path = '/api/v1/videos/' + videoId + '/abuse' return request(url) diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index 0e75444f8f3..271c9a46f23 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts @@ -7,7 +7,8 @@ export enum UserRole { USER = 2 } -export const USER_ROLE_LABELS = { +// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed +export const USER_ROLE_LABELS: { [ id: number ]: string } = { [UserRole.USER]: 'User', [UserRole.MODERATOR]: 'Moderator', [UserRole.ADMINISTRATOR]: 'Administrator'