mirror of https://github.com/Chocobozzz/PeerTube
Add new plugin/peertube version notifs
parent
3fbc697433
commit
32a18cbf33
|
@ -42,7 +42,9 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
|
|||
newInstanceFollower: $localize`Your instance has a new follower`,
|
||||
autoInstanceFollowing: $localize`Your instance automatically followed another instance`,
|
||||
abuseNewMessage: $localize`An abuse report received a new message`,
|
||||
abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators`
|
||||
abuseStateChange: $localize`One of your abuse reports has been accepted or rejected by moderators`,
|
||||
newPeerTubeVersion: $localize`A new PeerTube version is available`,
|
||||
newPluginVersion: $localize`One of your plugin/theme has a new available version`
|
||||
}
|
||||
this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[]
|
||||
|
||||
|
@ -51,7 +53,9 @@ export class MyAccountNotificationPreferencesComponent implements OnInit {
|
|||
videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST,
|
||||
newUserRegistration: UserRight.MANAGE_USERS,
|
||||
newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW,
|
||||
autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION
|
||||
autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION,
|
||||
newPeerTubeVersion: UserRight.MANAGE_DEBUG,
|
||||
newPluginVersion: UserRight.MANAGE_DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
AbuseState,
|
||||
ActorInfo,
|
||||
FollowState,
|
||||
PluginType,
|
||||
UserNotification as UserNotificationServer,
|
||||
UserNotificationType,
|
||||
UserRight,
|
||||
|
@ -74,20 +75,40 @@ export class UserNotification implements UserNotificationServer {
|
|||
}
|
||||
}
|
||||
|
||||
plugin?: {
|
||||
name: string
|
||||
type: PluginType
|
||||
latestVersion: string
|
||||
}
|
||||
|
||||
peertube?: {
|
||||
latestVersion: string
|
||||
}
|
||||
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
|
||||
// Additional fields
|
||||
videoUrl?: string
|
||||
commentUrl?: any[]
|
||||
|
||||
abuseUrl?: string
|
||||
abuseQueryParams?: { [id: string]: string } = {}
|
||||
|
||||
videoAutoBlacklistUrl?: string
|
||||
|
||||
accountUrl?: string
|
||||
|
||||
videoImportIdentifier?: string
|
||||
videoImportUrl?: string
|
||||
|
||||
instanceFollowUrl?: string
|
||||
|
||||
peertubeVersionLink?: string
|
||||
|
||||
pluginUrl?: string
|
||||
pluginQueryParams?: { [id: string]: string } = {}
|
||||
|
||||
constructor (hash: UserNotificationServer, user: AuthUser) {
|
||||
this.id = hash.id
|
||||
this.type = hash.type
|
||||
|
@ -114,6 +135,9 @@ export class UserNotification implements UserNotificationServer {
|
|||
this.actorFollow = hash.actorFollow
|
||||
if (this.actorFollow) this.setAccountAvatarUrl(this.actorFollow.follower)
|
||||
|
||||
this.plugin = hash.plugin
|
||||
this.peertube = hash.peertube
|
||||
|
||||
this.createdAt = hash.createdAt
|
||||
this.updatedAt = hash.updatedAt
|
||||
|
||||
|
@ -197,6 +221,15 @@ export class UserNotification implements UserNotificationServer {
|
|||
case UserNotificationType.AUTO_INSTANCE_FOLLOWING:
|
||||
this.instanceFollowUrl = '/admin/follows/following-list'
|
||||
break
|
||||
|
||||
case UserNotificationType.NEW_PEERTUBE_VERSION:
|
||||
this.peertubeVersionLink = 'https://joinpeertube.org/news'
|
||||
break
|
||||
|
||||
case UserNotificationType.NEW_PLUGIN_VERSION:
|
||||
this.pluginUrl = `/admin/plugins/list-installed`
|
||||
this.pluginQueryParams.pluginType = this.plugin.type + ''
|
||||
break
|
||||
}
|
||||
} catch (err) {
|
||||
this.type = null
|
||||
|
|
|
@ -191,6 +191,22 @@
|
|||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="17"> <!-- UserNotificationType.NEW_PLUGIN_VERSION -->
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
|
||||
<div class="message" i18n>
|
||||
<a (click)="markAsRead(notification)" [routerLink]="notification.pluginUrl" [queryParams]="notification.pluginQueryParams">A new version of the plugin/theme {{ notification.plugin.name }}</a> is available: {{ notification.plugin.latestVersion }}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="18"> <!-- UserNotificationType.NEW_PEERTUBE_VERSION -->
|
||||
<my-global-icon iconName="cog" aria-hidden="true"></my-global-icon>
|
||||
|
||||
<div class="message" i18n>
|
||||
<a (click)="markAsRead(notification)" [href]="notification.peertubeVersionLink" target="_blank" rel="noopener noreferer">A new version of PeerTube</a> is available: {{ notification.peertube.latestVersion }}
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchDefault>
|
||||
<my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ export class UserNotificationsComponent implements OnInit {
|
|||
}
|
||||
|
||||
loadNotifications (reset?: boolean) {
|
||||
this.userNotificationService.listMyNotifications({
|
||||
const options = {
|
||||
pagination: this.componentPagination,
|
||||
ignoreLoadingBar: this.ignoreLoadingBar,
|
||||
sort: {
|
||||
|
@ -53,7 +53,9 @@ export class UserNotificationsComponent implements OnInit {
|
|||
// if we order by creation date, we want DESC. all other fields are ASC (like unread).
|
||||
order: this.sortField === 'createdAt' ? -1 : 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.userNotificationService.listMyNotifications(options)
|
||||
.subscribe(
|
||||
result => {
|
||||
this.notifications = reset ? result.data : this.notifications.concat(result.data)
|
||||
|
|
|
@ -198,6 +198,13 @@ federation:
|
|||
# We still suggest you to enable this setting even if your users will loose most of their video's likes/dislikes
|
||||
cleanup_remote_interactions: false
|
||||
|
||||
peertube:
|
||||
check_latest_version:
|
||||
# Check and notify admins of new PeerTube versions
|
||||
enabled: true
|
||||
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
||||
url: 'https://joinpeertube.org/api/v1/versions.json'
|
||||
|
||||
cache:
|
||||
previews:
|
||||
size: 500 # Max number of previews you want to cache
|
||||
|
|
|
@ -196,6 +196,12 @@ federation:
|
|||
# We still suggest you to enable this setting even if your users will loose most of their video's likes/dislikes
|
||||
cleanup_remote_interactions: false
|
||||
|
||||
peertube:
|
||||
check_latest_version:
|
||||
# Check and notify admins of new PeerTube versions
|
||||
enabled: true
|
||||
# You can use a custom URL if your want, that respect the format behind https://joinpeertube.org/api/v1/versions.json
|
||||
url: 'https://joinpeertube.org/api/v1/versions.json'
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
|
|
|
@ -38,6 +38,10 @@ log:
|
|||
contact_form:
|
||||
enabled: true
|
||||
|
||||
peertube:
|
||||
check_latest_version:
|
||||
enabled: false
|
||||
|
||||
redundancy:
|
||||
videos:
|
||||
check_interval: '1 minute'
|
||||
|
|
|
@ -120,6 +120,7 @@ import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
|
|||
import { PeerTubeSocket } from './server/lib/peertube-socket'
|
||||
import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls'
|
||||
import { PluginsCheckScheduler } from './server/lib/schedulers/plugins-check-scheduler'
|
||||
import { PeerTubeVersionCheckScheduler } from './server/lib/schedulers/peertube-version-check-scheduler'
|
||||
import { Hooks } from './server/lib/plugins/hooks'
|
||||
import { PluginManager } from './server/lib/plugins/plugin-manager'
|
||||
import { LiveManager } from './server/lib/live-manager'
|
||||
|
@ -277,6 +278,7 @@ async function startApplication () {
|
|||
RemoveOldHistoryScheduler.Instance.enable()
|
||||
RemoveOldViewsScheduler.Instance.enable()
|
||||
PluginsCheckScheduler.Instance.enable()
|
||||
PeerTubeVersionCheckScheduler.Instance.enable()
|
||||
AutoFollowIndexInstances.Instance.enable()
|
||||
|
||||
// Redis initialization
|
||||
|
|
|
@ -80,7 +80,9 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
|
|||
newInstanceFollower: body.newInstanceFollower,
|
||||
autoInstanceFollowing: body.autoInstanceFollowing,
|
||||
abuseNewMessage: body.abuseNewMessage,
|
||||
abuseStateChange: body.abuseStateChange
|
||||
abuseStateChange: body.abuseStateChange,
|
||||
newPeerTubeVersion: body.newPeerTubeVersion,
|
||||
newPluginVersion: body.newPluginVersion
|
||||
}
|
||||
|
||||
await UserNotificationSettingModel.update(values, query)
|
||||
|
|
|
@ -251,6 +251,7 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
|
|||
}
|
||||
}
|
||||
|
||||
type SemVersion = { major: number, minor: number, patch: number }
|
||||
function parseSemVersion (s: string) {
|
||||
const parsed = s.match(/^v?(\d+)\.(\d+)\.(\d+)$/i)
|
||||
|
||||
|
@ -258,7 +259,7 @@ function parseSemVersion (s: string) {
|
|||
major: parseInt(parsed[1]),
|
||||
minor: parseInt(parsed[2]),
|
||||
patch: parseInt(parsed[3])
|
||||
}
|
||||
} as SemVersion
|
||||
}
|
||||
|
||||
const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
|
||||
|
|
|
@ -37,6 +37,7 @@ function checkMissedConfig () {
|
|||
'theme.default',
|
||||
'remote_redundancy.videos.accept_from',
|
||||
'federation.videos.federate_unlisted', 'federation.videos.cleanup_remote_interactions',
|
||||
'peertube.check_latest_version.enabled', 'peertube.check_latest_version.url',
|
||||
'search.remote_uri.users', 'search.remote_uri.anonymous', 'search.search_index.enabled', 'search.search_index.url',
|
||||
'search.search_index.disable_local_search', 'search.search_index.is_default_search',
|
||||
'live.enabled', 'live.allow_replay', 'live.max_duration', 'live.max_user_lives', 'live.max_instance_lives',
|
||||
|
|
|
@ -163,6 +163,12 @@ const CONFIG = {
|
|||
CLEANUP_REMOTE_INTERACTIONS: config.get<boolean>('federation.videos.cleanup_remote_interactions')
|
||||
}
|
||||
},
|
||||
PEERTUBE: {
|
||||
CHECK_LATEST_VERSION: {
|
||||
ENABLED: config.get<boolean>('peertube.check_latest_version.enabled'),
|
||||
URL: config.get<string>('peertube.check_latest_version.url')
|
||||
}
|
||||
},
|
||||
ADMIN: {
|
||||
get EMAIL () { return config.get<string>('admin.email') }
|
||||
},
|
||||
|
|
|
@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 610
|
||||
const LAST_MIGRATION_VERSION = 625
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -207,6 +207,7 @@ const SCHEDULER_INTERVALS_MS = {
|
|||
updateVideos: 60000, // 1 minute
|
||||
youtubeDLUpdate: 60000 * 60 * 24, // 1 day
|
||||
checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL,
|
||||
checkPeerTubeVersion: 60000 * 60 * 24, // 1 day
|
||||
autoFollowIndexInstances: 60000 * 60 * 24, // 1 day
|
||||
removeOldViews: 60000 * 60 * 24, // 1 day
|
||||
removeOldHistory: 60000 * 60 * 24, // 1 day
|
||||
|
@ -763,6 +764,7 @@ if (isTestInstance() === true) {
|
|||
SCHEDULER_INTERVALS_MS.updateVideos = 5000
|
||||
SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000
|
||||
SCHEDULER_INTERVALS_MS.updateInboxStats = 5000
|
||||
SCHEDULER_INTERVALS_MS.checkPeerTubeVersion = 2000
|
||||
REPEAT_JOBS['videos-views'] = { every: 5000 }
|
||||
REPEAT_JOBS['activitypub-cleaner'] = { every: 5000 }
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const notificationSettingColumns = [ 'newPeerTubeVersion', 'newPluginVersion' ]
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = 'UPDATE "userNotificationSetting" SET "newPeerTubeVersion" = 3, "newPluginVersion" = 1'
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
for (const column of notificationSettingColumns) {
|
||||
const data = {
|
||||
type: Sequelize.INTEGER,
|
||||
defaultValue: null,
|
||||
allowNull: false
|
||||
}
|
||||
await utils.queryInterface.changeColumn('userNotificationSetting', column, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
defaultValue: null,
|
||||
allowNull: true
|
||||
}
|
||||
await utils.queryInterface.addColumn('application', 'latestPeerTubeVersion', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
|
||||
{
|
||||
await utils.sequelize.query(`
|
||||
ALTER TABLE "userNotification"
|
||||
ADD COLUMN "applicationId" INTEGER REFERENCES "application" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
ADD COLUMN "pluginId" INTEGER REFERENCES "plugin" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -12,7 +12,7 @@ import { isTestInstance, root } from '../helpers/core-utils'
|
|||
import { bunyanLogger, logger } from '../helpers/logger'
|
||||
import { CONFIG, isEmailEnabled } from '../initializers/config'
|
||||
import { WEBSERVER } from '../initializers/constants'
|
||||
import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MUser } from '../types/models'
|
||||
import { MAbuseFull, MAbuseMessage, MAccountDefault, MActorFollowActors, MActorFollowFull, MPlugin, MUser } from '../types/models'
|
||||
import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video'
|
||||
import { JobQueue } from './job-queue'
|
||||
|
||||
|
@ -403,7 +403,7 @@ class Emailer {
|
|||
}
|
||||
|
||||
async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) {
|
||||
const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||
const videoAutoBlacklistUrl = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
|
||||
const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
|
||||
const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON()
|
||||
|
||||
|
@ -417,7 +417,7 @@ class Emailer {
|
|||
videoName: videoBlacklist.Video.name,
|
||||
action: {
|
||||
text: 'Review autoblacklist',
|
||||
url: VIDEO_AUTO_BLACKLIST_URL
|
||||
url: videoAutoBlacklistUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,6 +472,42 @@ class Emailer {
|
|||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addNewPeerTubeVersionNotification (to: string[], latestVersion: string) {
|
||||
const subject = `A new PeerTube version is available: ${latestVersion}`
|
||||
|
||||
const emailPayload: EmailPayload = {
|
||||
to,
|
||||
template: 'peertube-version-new',
|
||||
subject,
|
||||
text: subject,
|
||||
locals: {
|
||||
latestVersion
|
||||
}
|
||||
}
|
||||
|
||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addNewPlugionVersionNotification (to: string[], plugin: MPlugin) {
|
||||
const pluginUrl = WEBSERVER.URL + '/admin/plugins/list-installed?pluginType=' + plugin.type
|
||||
|
||||
const subject = `A new plugin/theme version is available: ${plugin.name}@${plugin.latestVersion}`
|
||||
|
||||
const emailPayload: EmailPayload = {
|
||||
to,
|
||||
template: 'plugin-version-new',
|
||||
subject,
|
||||
text: subject,
|
||||
locals: {
|
||||
pluginName: plugin.name,
|
||||
latestVersion: plugin.latestVersion,
|
||||
pluginUrl
|
||||
}
|
||||
}
|
||||
|
||||
return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
|
||||
}
|
||||
|
||||
addPasswordResetEmailJob (username: string, to: string, resetPasswordUrl: string) {
|
||||
const emailPayload: EmailPayload = {
|
||||
template: 'password-reset',
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
extends ../common/greetings
|
||||
|
||||
block title
|
||||
| New PeerTube version available
|
||||
|
||||
block content
|
||||
p
|
||||
| A new version of PeerTube is available: #{latestVersion}.
|
||||
| You can check the latest news on #[a(href="https://joinpeertube.org/news") JoinPeerTube].
|
|
@ -0,0 +1,9 @@
|
|||
extends ../common/greetings
|
||||
|
||||
block title
|
||||
| New plugin version available
|
||||
|
||||
block content
|
||||
p
|
||||
| A new version of the plugin/theme #{pluginName} is available: #{latestVersion}.
|
||||
| You might want to upgrade it on #[a(href=pluginUrl) the PeerTube admin interface].
|
|
@ -19,7 +19,7 @@ import { CONFIG } from '../initializers/config'
|
|||
import { AccountBlocklistModel } from '../models/account/account-blocklist'
|
||||
import { UserModel } from '../models/account/user'
|
||||
import { UserNotificationModel } from '../models/account/user-notification'
|
||||
import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull } from '../types/models'
|
||||
import { MAbuseFull, MAbuseMessage, MAccountServer, MActorFollowFull, MApplication, MPlugin } from '../types/models'
|
||||
import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video'
|
||||
import { isBlockedByServerOrAccount } from './blocklist'
|
||||
import { Emailer } from './emailer'
|
||||
|
@ -144,6 +144,20 @@ class Notifier {
|
|||
})
|
||||
}
|
||||
|
||||
notifyOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
|
||||
this.notifyAdminsOfNewPeerTubeVersion(application, latestVersion)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify on new PeerTubeb version %s.', latestVersion, { err })
|
||||
})
|
||||
}
|
||||
|
||||
notifyOfNewPluginVersion (plugin: MPlugin) {
|
||||
this.notifyAdminsOfNewPluginVersion(plugin)
|
||||
.catch(err => {
|
||||
logger.error('Cannot notify on new plugin version %s.', plugin.name, { err })
|
||||
})
|
||||
}
|
||||
|
||||
private async notifySubscribersOfNewVideo (video: MVideoAccountLight) {
|
||||
// List all followers that are users
|
||||
const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
|
||||
|
@ -667,6 +681,64 @@ class Notifier {
|
|||
return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyAdminsOfNewPeerTubeVersion (application: MApplication, latestVersion: string) {
|
||||
// Use the debug right to know who is an administrator
|
||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
|
||||
if (admins.length === 0) return
|
||||
|
||||
logger.info('Notifying %s admins of new PeerTube version %s.', admins.length, latestVersion)
|
||||
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newPeerTubeVersion
|
||||
}
|
||||
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_PEERTUBE_VERSION,
|
||||
userId: user.id,
|
||||
applicationId: application.id
|
||||
})
|
||||
notification.Application = application
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
function emailSender (emails: string[]) {
|
||||
return Emailer.Instance.addNewPeerTubeVersionNotification(emails, latestVersion)
|
||||
}
|
||||
|
||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notifyAdminsOfNewPluginVersion (plugin: MPlugin) {
|
||||
// Use the debug right to know who is an administrator
|
||||
const admins = await UserModel.listWithRight(UserRight.MANAGE_DEBUG)
|
||||
if (admins.length === 0) return
|
||||
|
||||
logger.info('Notifying %s admins of new plugin version %s@%s.', admins.length, plugin.name, plugin.latestVersion)
|
||||
|
||||
function settingGetter (user: MUserWithNotificationSetting) {
|
||||
return user.NotificationSetting.newPluginVersion
|
||||
}
|
||||
|
||||
async function notificationCreator (user: MUserWithNotificationSetting) {
|
||||
const notification = await UserNotificationModel.create<UserNotificationModelForApi>({
|
||||
type: UserNotificationType.NEW_PLUGIN_VERSION,
|
||||
userId: user.id,
|
||||
pluginId: plugin.id
|
||||
})
|
||||
notification.Plugin = plugin
|
||||
|
||||
return notification
|
||||
}
|
||||
|
||||
function emailSender (emails: string[]) {
|
||||
return Emailer.Instance.addNewPlugionVersionNotification(emails, plugin)
|
||||
}
|
||||
|
||||
return this.notify({ users: admins, settingGetter, notificationCreator, emailSender })
|
||||
}
|
||||
|
||||
private async notify<T extends MUserWithNotificationSetting> (options: {
|
||||
users: T[]
|
||||
notificationCreator: (user: T) => Promise<UserNotificationModelForApi>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
|
||||
import { doJSONRequest } from '@server/helpers/requests'
|
||||
import { ApplicationModel } from '@server/models/application/application'
|
||||
import { compareSemVer } from '@shared/core-utils'
|
||||
import { JoinPeerTubeVersions } from '@shared/models'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { CONFIG } from '../../initializers/config'
|
||||
import { PEERTUBE_VERSION, SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
|
||||
import { Notifier } from '../notifier'
|
||||
import { AbstractScheduler } from './abstract-scheduler'
|
||||
|
||||
export class PeerTubeVersionCheckScheduler extends AbstractScheduler {
|
||||
|
||||
private static instance: AbstractScheduler
|
||||
|
||||
protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.checkPeerTubeVersion
|
||||
|
||||
private constructor () {
|
||||
super()
|
||||
}
|
||||
|
||||
protected async internalExecute () {
|
||||
return this.checkLatestVersion()
|
||||
}
|
||||
|
||||
private async checkLatestVersion () {
|
||||
if (CONFIG.PEERTUBE.CHECK_LATEST_VERSION.ENABLED === false) return
|
||||
|
||||
logger.info('Checking latest PeerTube version.')
|
||||
|
||||
const { body } = await doJSONRequest<JoinPeerTubeVersions>(CONFIG.PEERTUBE.CHECK_LATEST_VERSION.URL)
|
||||
|
||||
if (!body?.peertube?.latestVersion) {
|
||||
logger.warn('Cannot check latest PeerTube version: body is invalid.', { body })
|
||||
return
|
||||
}
|
||||
|
||||
const latestVersion = body.peertube.latestVersion
|
||||
const application = await ApplicationModel.load()
|
||||
|
||||
// Already checked this version
|
||||
if (application.latestPeerTubeVersion === latestVersion) return
|
||||
|
||||
if (compareSemVer(PEERTUBE_VERSION, latestVersion) < 0) {
|
||||
application.latestPeerTubeVersion = latestVersion
|
||||
await application.save()
|
||||
|
||||
Notifier.Instance.notifyOfNewPeerTubeVersion(application, latestVersion)
|
||||
}
|
||||
}
|
||||
|
||||
static get Instance () {
|
||||
return this.instance || (this.instance = new this())
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ import { PluginModel } from '../../models/server/plugin'
|
|||
import { chunk } from 'lodash'
|
||||
import { getLatestPluginsVersion } from '../plugins/plugin-index'
|
||||
import { compareSemVer } from '../../../shared/core-utils/miscs/miscs'
|
||||
import { Notifier } from '../notifier'
|
||||
|
||||
export class PluginsCheckScheduler extends AbstractScheduler {
|
||||
|
||||
|
@ -53,6 +54,11 @@ export class PluginsCheckScheduler extends AbstractScheduler {
|
|||
plugin.latestVersion = result.latestVersion
|
||||
await plugin.save()
|
||||
|
||||
// Notify if there is an higher plugin version available
|
||||
if (compareSemVer(plugin.version, result.latestVersion) < 0) {
|
||||
Notifier.Instance.notifyOfNewPluginVersion(plugin)
|
||||
}
|
||||
|
||||
logger.info('Plugin %s has a new latest version %s.', result.npmName, plugin.latestVersion)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,7 +193,9 @@ function createDefaultUserNotificationSettings (user: MUserId, t: Transaction |
|
|||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB,
|
||||
newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newPluginVersion: UserNotificationSettingValue.WEB
|
||||
}
|
||||
|
||||
return UserNotificationSettingModel.create(values, { transaction: t })
|
||||
|
|
|
@ -156,6 +156,24 @@ export class UserNotificationSettingModel extends Model {
|
|||
@Column
|
||||
abuseNewMessage: UserNotificationSettingValue
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(null)
|
||||
@Is(
|
||||
'UserNotificationSettingNewPeerTubeVersion',
|
||||
value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPeerTubeVersion')
|
||||
)
|
||||
@Column
|
||||
newPeerTubeVersion: UserNotificationSettingValue
|
||||
|
||||
@AllowNull(false)
|
||||
@Default(null)
|
||||
@Is(
|
||||
'UserNotificationSettingNewPeerPluginVersion',
|
||||
value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPluginVersion')
|
||||
)
|
||||
@Column
|
||||
newPluginVersion: UserNotificationSettingValue
|
||||
|
||||
@ForeignKey(() => UserModel)
|
||||
@Column
|
||||
userId: number
|
||||
|
@ -195,7 +213,9 @@ export class UserNotificationSettingModel extends Model {
|
|||
newInstanceFollower: this.newInstanceFollower,
|
||||
autoInstanceFollowing: this.autoInstanceFollowing,
|
||||
abuseNewMessage: this.abuseNewMessage,
|
||||
abuseStateChange: this.abuseStateChange
|
||||
abuseStateChange: this.abuseStateChange,
|
||||
newPeerTubeVersion: this.newPeerTubeVersion,
|
||||
newPluginVersion: this.newPluginVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,9 @@ import { VideoAbuseModel } from '../abuse/video-abuse'
|
|||
import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
|
||||
import { ActorModel } from '../activitypub/actor'
|
||||
import { ActorFollowModel } from '../activitypub/actor-follow'
|
||||
import { ApplicationModel } from '../application/application'
|
||||
import { AvatarModel } from '../avatar/avatar'
|
||||
import { PluginModel } from '../server/plugin'
|
||||
import { ServerModel } from '../server/server'
|
||||
import { getSort, throwIfNotValid } from '../utils'
|
||||
import { VideoModel } from '../video/video'
|
||||
|
@ -96,7 +98,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
attributes: [ 'id' ],
|
||||
model: VideoAbuseModel.unscoped(),
|
||||
required: false,
|
||||
include: [ buildVideoInclude(true) ]
|
||||
include: [ buildVideoInclude(false) ]
|
||||
},
|
||||
{
|
||||
attributes: [ 'id' ],
|
||||
|
@ -106,12 +108,12 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
{
|
||||
attributes: [ 'id', 'originCommentId' ],
|
||||
model: VideoCommentModel.unscoped(),
|
||||
required: true,
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
attributes: [ 'id', 'name', 'uuid' ],
|
||||
model: VideoModel.unscoped(),
|
||||
required: true
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
{
|
||||
model: AccountModel,
|
||||
as: 'FlaggedAccount',
|
||||
required: true,
|
||||
required: false,
|
||||
include: [ buildActorWithAvatarInclude() ]
|
||||
}
|
||||
]
|
||||
|
@ -140,6 +142,18 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
include: [ buildVideoInclude(false) ]
|
||||
},
|
||||
|
||||
{
|
||||
attributes: [ 'id', 'name', 'type', 'latestVersion' ],
|
||||
model: PluginModel.unscoped(),
|
||||
required: false
|
||||
},
|
||||
|
||||
{
|
||||
attributes: [ 'id', 'latestPeerTubeVersion' ],
|
||||
model: ApplicationModel.unscoped(),
|
||||
required: false
|
||||
},
|
||||
|
||||
{
|
||||
attributes: [ 'id', 'state' ],
|
||||
model: ActorFollowModel.unscoped(),
|
||||
|
@ -251,6 +265,22 @@ function buildAccountInclude (required: boolean, withActor = false) {
|
|||
[Op.ne]: null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: [ 'pluginId' ],
|
||||
where: {
|
||||
pluginId: {
|
||||
[Op.ne]: null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: [ 'applicationId' ],
|
||||
where: {
|
||||
applicationId: {
|
||||
[Op.ne]: null
|
||||
}
|
||||
}
|
||||
}
|
||||
] as (ModelIndexesOptions & { where?: WhereOptions })[]
|
||||
})
|
||||
|
@ -370,6 +400,30 @@ export class UserNotificationModel extends Model {
|
|||
})
|
||||
ActorFollow: ActorFollowModel
|
||||
|
||||
@ForeignKey(() => PluginModel)
|
||||
@Column
|
||||
pluginId: number
|
||||
|
||||
@BelongsTo(() => PluginModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Plugin: PluginModel
|
||||
|
||||
@ForeignKey(() => ApplicationModel)
|
||||
@Column
|
||||
applicationId: number
|
||||
|
||||
@BelongsTo(() => ApplicationModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
},
|
||||
onDelete: 'cascade'
|
||||
})
|
||||
Application: ApplicationModel
|
||||
|
||||
static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
|
||||
const where = { userId }
|
||||
|
||||
|
@ -524,6 +578,18 @@ export class UserNotificationModel extends Model {
|
|||
}
|
||||
: undefined
|
||||
|
||||
const plugin = this.Plugin
|
||||
? {
|
||||
name: this.Plugin.name,
|
||||
type: this.Plugin.type,
|
||||
latestVersion: this.Plugin.latestVersion
|
||||
}
|
||||
: undefined
|
||||
|
||||
const peertube = this.Application
|
||||
? { latestVersion: this.Application.latestPeerTubeVersion }
|
||||
: undefined
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
|
@ -535,6 +601,8 @@ export class UserNotificationModel extends Model {
|
|||
videoBlacklist,
|
||||
account,
|
||||
actorFollow,
|
||||
plugin,
|
||||
peertube,
|
||||
createdAt: this.createdAt.toISOString(),
|
||||
updatedAt: this.updatedAt.toISOString()
|
||||
}
|
||||
|
@ -553,17 +621,19 @@ export class UserNotificationModel extends Model {
|
|||
? {
|
||||
threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
|
||||
|
||||
video: {
|
||||
id: abuse.VideoCommentAbuse.VideoComment.Video.id,
|
||||
name: abuse.VideoCommentAbuse.VideoComment.Video.name,
|
||||
uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
|
||||
}
|
||||
video: abuse.VideoCommentAbuse.VideoComment.Video
|
||||
? {
|
||||
id: abuse.VideoCommentAbuse.VideoComment.Video.id,
|
||||
name: abuse.VideoCommentAbuse.VideoComment.Video.name,
|
||||
uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
: undefined
|
||||
|
||||
const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined
|
||||
|
||||
const accountAbuse = (!commentAbuse && !videoAbuse) ? this.formatActor(abuse.FlaggedAccount) : undefined
|
||||
const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined
|
||||
|
||||
return {
|
||||
id: abuse.id,
|
||||
|
|
|
@ -32,6 +32,10 @@ export class ApplicationModel extends Model {
|
|||
@Column
|
||||
migrationVersion: number
|
||||
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
latestPeerTubeVersion: string
|
||||
|
||||
@HasOne(() => AccountModel, {
|
||||
foreignKey: {
|
||||
allowNull: true
|
||||
|
|
|
@ -176,7 +176,9 @@ describe('Test user notifications API validators', function () {
|
|||
newInstanceFollower: UserNotificationSettingValue.WEB,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB,
|
||||
abuseNewMessage: UserNotificationSettingValue.WEB,
|
||||
abuseStateChange: UserNotificationSettingValue.WEB
|
||||
abuseStateChange: UserNotificationSettingValue.WEB,
|
||||
newPeerTubeVersion: UserNotificationSettingValue.WEB,
|
||||
newPluginVersion: UserNotificationSettingValue.WEB
|
||||
}
|
||||
|
||||
it('Should fail with missing fields', async function () {
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { MockJoinPeerTubeVersions } from '@shared/extra-utils/mock-servers/joinpeertube-versions'
|
||||
import { cleanupTests, installPlugin, setPluginLatestVersion, setPluginVersion, wait } from '../../../../shared/extra-utils'
|
||||
import { ServerInfo } from '../../../../shared/extra-utils/index'
|
||||
import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
|
||||
import {
|
||||
CheckerBaseParams,
|
||||
checkNewPeerTubeVersion,
|
||||
checkNewPluginVersion,
|
||||
prepareNotificationsTest
|
||||
} from '../../../../shared/extra-utils/users/user-notifications'
|
||||
import { UserNotification, UserNotificationType } from '../../../../shared/models/users'
|
||||
import { PluginType } from '@shared/models'
|
||||
|
||||
describe('Test admin notifications', function () {
|
||||
let server: ServerInfo
|
||||
let userNotifications: UserNotification[] = []
|
||||
let adminNotifications: UserNotification[] = []
|
||||
let emails: object[] = []
|
||||
let baseParams: CheckerBaseParams
|
||||
let joinPeerTubeServer: MockJoinPeerTubeVersions
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
const config = {
|
||||
peertube: {
|
||||
check_latest_version: {
|
||||
enabled: true,
|
||||
url: 'http://localhost:42102/versions.json'
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
index: {
|
||||
enabled: true,
|
||||
check_latest_versions_interval: '5 seconds'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await prepareNotificationsTest(1, config)
|
||||
emails = res.emails
|
||||
server = res.servers[0]
|
||||
|
||||
userNotifications = res.userNotifications
|
||||
adminNotifications = res.adminNotifications
|
||||
|
||||
baseParams = {
|
||||
server: server,
|
||||
emails,
|
||||
socketNotifications: adminNotifications,
|
||||
token: server.accessToken
|
||||
}
|
||||
|
||||
await installPlugin({
|
||||
url: server.url,
|
||||
accessToken: server.accessToken,
|
||||
npmName: 'peertube-plugin-hello-world'
|
||||
})
|
||||
|
||||
await installPlugin({
|
||||
url: server.url,
|
||||
accessToken: server.accessToken,
|
||||
npmName: 'peertube-theme-background-red'
|
||||
})
|
||||
|
||||
joinPeerTubeServer = new MockJoinPeerTubeVersions()
|
||||
await joinPeerTubeServer.initialize()
|
||||
})
|
||||
|
||||
describe('Latest PeerTube version notification', function () {
|
||||
|
||||
it('Should not send a notification to admins if there is not a new version', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
joinPeerTubeServer.setLatestVersion('1.4.2')
|
||||
|
||||
await wait(3000)
|
||||
await checkNewPeerTubeVersion(baseParams, '1.4.2', 'absence')
|
||||
})
|
||||
|
||||
it('Should send a notification to admins on new plugin version', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
joinPeerTubeServer.setLatestVersion('15.4.2')
|
||||
|
||||
await wait(3000)
|
||||
await checkNewPeerTubeVersion(baseParams, '15.4.2', 'presence')
|
||||
})
|
||||
|
||||
it('Should not send the same notification to admins', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await wait(3000)
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
it('Should not have sent a notification to users', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should send a new notification after a new release', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
joinPeerTubeServer.setLatestVersion('15.4.3')
|
||||
|
||||
await wait(3000)
|
||||
await checkNewPeerTubeVersion(baseParams, '15.4.3', 'presence')
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Latest plugin version notification', function () {
|
||||
|
||||
it('Should not send a notification to admins if there is no new plugin version', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await wait(6000)
|
||||
await checkNewPluginVersion(baseParams, PluginType.PLUGIN, 'hello-world', 'absence')
|
||||
})
|
||||
|
||||
it('Should send a notification to admins on new plugin version', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await setPluginVersion(server.internalServerNumber, 'hello-world', '0.0.1')
|
||||
await setPluginLatestVersion(server.internalServerNumber, 'hello-world', '0.0.1')
|
||||
await wait(6000)
|
||||
|
||||
await checkNewPluginVersion(baseParams, PluginType.PLUGIN, 'hello-world', 'presence')
|
||||
})
|
||||
|
||||
it('Should not send the same notification to admins', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await wait(6000)
|
||||
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
it('Should not have sent a notification to users', async function () {
|
||||
expect(userNotifications.filter(n => n.type === UserNotificationType.NEW_PLUGIN_VERSION)).to.have.lengthOf(0)
|
||||
})
|
||||
|
||||
it('Should send a new notification after a new plugin release', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await setPluginVersion(server.internalServerNumber, 'hello-world', '0.0.1')
|
||||
await setPluginLatestVersion(server.internalServerNumber, 'hello-world', '0.0.1')
|
||||
await wait(6000)
|
||||
|
||||
expect(adminNotifications.filter(n => n.type === UserNotificationType.NEW_PEERTUBE_VERSION)).to.have.lengthOf(2)
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
MockSmtpServer.Instance.kill()
|
||||
|
||||
await cleanupTests([ server ])
|
||||
})
|
||||
})
|
|
@ -1,3 +1,4 @@
|
|||
import './admin-notifications'
|
||||
import './comments-notifications'
|
||||
import './moderation-notifications'
|
||||
import './notifications-api'
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { ApplicationModel } from '@server/models/application/application'
|
||||
|
||||
// ############################################################################
|
||||
|
||||
export type MApplication = Omit<ApplicationModel, 'Account'>
|
|
@ -0,0 +1 @@
|
|||
export * from './application'
|
|
@ -1,4 +1,5 @@
|
|||
export * from './account'
|
||||
export * from './application'
|
||||
export * from './moderation'
|
||||
export * from './oauth'
|
||||
export * from './server'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
|
||||
import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
|
||||
import { ApplicationModel } from '@server/models/application/application'
|
||||
import { PluginModel } from '@server/models/server/plugin'
|
||||
import { PickWith, PickWithOpt } from '@shared/core-utils'
|
||||
import { AbuseModel } from '../../../models/abuse/abuse'
|
||||
import { AccountModel } from '../../../models/account/account'
|
||||
|
@ -85,13 +87,19 @@ export module UserNotificationIncludes {
|
|||
Pick<ActorFollowModel, 'id' | 'state'> &
|
||||
PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
|
||||
PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
|
||||
|
||||
export type PluginInclude =
|
||||
Pick<PluginModel, 'id' | 'name' | 'type' | 'latestVersion'>
|
||||
|
||||
export type ApplicationInclude =
|
||||
Pick<ApplicationModel, 'latestPeerTubeVersion'>
|
||||
}
|
||||
|
||||
// ############################################################################
|
||||
|
||||
export type MUserNotification =
|
||||
Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'Abuse' | 'VideoBlacklist' |
|
||||
'VideoImport' | 'Account' | 'ActorFollow'>
|
||||
'VideoImport' | 'Account' | 'ActorFollow' | 'Plugin' | 'Application'>
|
||||
|
||||
// ############################################################################
|
||||
|
||||
|
@ -103,4 +111,6 @@ export type UserNotificationModelForApi =
|
|||
Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> &
|
||||
Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> &
|
||||
Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> &
|
||||
Use<'Plugin', UserNotificationIncludes.PluginInclude> &
|
||||
Use<'Application', UserNotificationIncludes.ApplicationInclude> &
|
||||
Use<'Account', UserNotificationIncludes.AccountIncludeActor>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export * from './bulk/bulk'
|
||||
export * from './cli/cli'
|
||||
export * from './feeds/feeds'
|
||||
export * from './instances-index/mock-instances-index'
|
||||
export * from './mock-servers/mock-instances-index'
|
||||
export * from './miscs/miscs'
|
||||
export * from './miscs/sql'
|
||||
export * from './miscs/stubs'
|
||||
|
|
|
@ -106,12 +106,20 @@ async function closeAllSequelize (servers: ServerInfo[]) {
|
|||
}
|
||||
}
|
||||
|
||||
function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
|
||||
function setPluginField (internalServerNumber: number, pluginName: string, field: string, value: string) {
|
||||
const seq = getSequelize(internalServerNumber)
|
||||
|
||||
const options = { type: QueryTypes.UPDATE }
|
||||
|
||||
return seq.query(`UPDATE "plugin" SET "version" = '${newVersion}' WHERE "name" = '${pluginName}'`, options)
|
||||
return seq.query(`UPDATE "plugin" SET "${field}" = '${value}' WHERE "name" = '${pluginName}'`, options)
|
||||
}
|
||||
|
||||
function setPluginVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
|
||||
return setPluginField(internalServerNumber, pluginName, 'version', newVersion)
|
||||
}
|
||||
|
||||
function setPluginLatestVersion (internalServerNumber: number, pluginName: string, newVersion: string) {
|
||||
return setPluginField(internalServerNumber, pluginName, 'latestVersion', newVersion)
|
||||
}
|
||||
|
||||
function setActorFollowScores (internalServerNumber: number, newScore: number) {
|
||||
|
@ -128,6 +136,7 @@ export {
|
|||
setActorField,
|
||||
countVideoViewsOf,
|
||||
setPluginVersion,
|
||||
setPluginLatestVersion,
|
||||
selectQuery,
|
||||
deleteAll,
|
||||
updateQuery,
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import * as express from 'express'
|
||||
|
||||
export class MockJoinPeerTubeVersions {
|
||||
private latestVersion: string
|
||||
|
||||
initialize () {
|
||||
return new Promise<void>(res => {
|
||||
const app = express()
|
||||
|
||||
app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
|
||||
|
||||
return next()
|
||||
})
|
||||
|
||||
app.get('/versions.json', (req: express.Request, res: express.Response) => {
|
||||
return res.json({
|
||||
peertube: {
|
||||
latestVersion: this.latestVersion
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.listen(42102, () => res())
|
||||
})
|
||||
}
|
||||
|
||||
setLatestVersion (latestVersion: string) {
|
||||
this.latestVersion = latestVersion
|
||||
}
|
||||
}
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
import { expect } from 'chai'
|
||||
import { inspect } from 'util'
|
||||
import { AbuseState } from '@shared/models'
|
||||
import { AbuseState, PluginType } from '@shared/models'
|
||||
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
|
||||
import { UserNotification, UserNotificationSetting, UserNotificationSettingValue, UserNotificationType } from '../../models/users'
|
||||
import { MockSmtpServer } from '../miscs/email'
|
||||
import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
|
||||
|
@ -11,7 +12,6 @@ import { flushAndRunMultipleServers, ServerInfo } from '../server/servers'
|
|||
import { getUserNotificationSocket } from '../socket/socket-io'
|
||||
import { setAccessTokensToServers, userLogin } from './login'
|
||||
import { createUser, getMyUserInformation } from './users'
|
||||
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
|
||||
|
||||
function updateMyNotificationSettings (
|
||||
url: string,
|
||||
|
@ -629,7 +629,59 @@ async function checkNewBlacklistOnMyVideo (
|
|||
await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
|
||||
}
|
||||
|
||||
function getAllNotificationsSettings () {
|
||||
async function checkNewPeerTubeVersion (base: CheckerBaseParams, latestVersion: string, type: CheckerType) {
|
||||
const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION
|
||||
|
||||
function notificationChecker (notification: UserNotification, type: CheckerType) {
|
||||
if (type === 'presence') {
|
||||
expect(notification).to.not.be.undefined
|
||||
expect(notification.type).to.equal(notificationType)
|
||||
|
||||
expect(notification.peertube).to.exist
|
||||
expect(notification.peertube.latestVersion).to.equal(latestVersion)
|
||||
} else {
|
||||
expect(notification).to.satisfy((n: UserNotification) => {
|
||||
return n === undefined || n.peertube === undefined || n.peertube.latestVersion !== latestVersion
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function emailNotificationFinder (email: object) {
|
||||
const text = email['text']
|
||||
|
||||
return text.includes(latestVersion)
|
||||
}
|
||||
|
||||
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||
}
|
||||
|
||||
async function checkNewPluginVersion (base: CheckerBaseParams, pluginType: PluginType, pluginName: string, type: CheckerType) {
|
||||
const notificationType = UserNotificationType.NEW_PLUGIN_VERSION
|
||||
|
||||
function notificationChecker (notification: UserNotification, type: CheckerType) {
|
||||
if (type === 'presence') {
|
||||
expect(notification).to.not.be.undefined
|
||||
expect(notification.type).to.equal(notificationType)
|
||||
|
||||
expect(notification.plugin.name).to.equal(pluginName)
|
||||
expect(notification.plugin.type).to.equal(pluginType)
|
||||
} else {
|
||||
expect(notification).to.satisfy((n: UserNotification) => {
|
||||
return n === undefined || n.plugin === undefined || n.plugin.name !== pluginName
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function emailNotificationFinder (email: object) {
|
||||
const text = email['text']
|
||||
|
||||
return text.includes(pluginName)
|
||||
}
|
||||
|
||||
await checkNotification(base, notificationChecker, emailNotificationFinder, type)
|
||||
}
|
||||
|
||||
function getAllNotificationsSettings (): UserNotificationSetting {
|
||||
return {
|
||||
newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
|
@ -644,11 +696,13 @@ function getAllNotificationsSettings () {
|
|||
newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||
} as UserNotificationSetting
|
||||
autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
|
||||
newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareNotificationsTest (serversCount = 3) {
|
||||
async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
|
||||
const userNotifications: UserNotification[] = []
|
||||
const adminNotifications: UserNotification[] = []
|
||||
const adminNotificationsServer2: UserNotification[] = []
|
||||
|
@ -665,7 +719,7 @@ async function prepareNotificationsTest (serversCount = 3) {
|
|||
limit: 20
|
||||
}
|
||||
}
|
||||
const servers = await flushAndRunMultipleServers(serversCount, overrideConfig)
|
||||
const servers = await flushAndRunMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
|
||||
|
||||
await setAccessTokensToServers(servers)
|
||||
|
||||
|
@ -749,5 +803,7 @@ export {
|
|||
checkNewInstanceFollower,
|
||||
prepareNotificationsTest,
|
||||
checkNewCommentAbuseForModerators,
|
||||
checkNewAccountAbuseForModerators
|
||||
checkNewAccountAbuseForModerators,
|
||||
checkNewPeerTubeVersion,
|
||||
checkNewPluginVersion
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ export * from './redundancy'
|
|||
export * from './users'
|
||||
export * from './videos'
|
||||
export * from './feeds'
|
||||
export * from './joinpeertube'
|
||||
export * from './overviews'
|
||||
export * from './plugins'
|
||||
export * from './search'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './versions.model'
|
|
@ -0,0 +1,5 @@
|
|||
export interface JoinPeerTubeVersions {
|
||||
peertube: {
|
||||
latestVersion: string
|
||||
}
|
||||
}
|
|
@ -24,4 +24,7 @@ export interface UserNotificationSetting {
|
|||
|
||||
abuseStateChange: UserNotificationSettingValue
|
||||
abuseNewMessage: UserNotificationSettingValue
|
||||
|
||||
newPeerTubeVersion: UserNotificationSettingValue
|
||||
newPluginVersion: UserNotificationSettingValue
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { FollowState } from '../actors'
|
||||
import { AbuseState } from '../moderation'
|
||||
import { PluginType } from '../plugins'
|
||||
|
||||
export const enum UserNotificationType {
|
||||
NEW_VIDEO_FROM_SUBSCRIPTION = 1,
|
||||
|
@ -26,7 +27,10 @@ export const enum UserNotificationType {
|
|||
|
||||
ABUSE_STATE_CHANGE = 15,
|
||||
|
||||
ABUSE_NEW_MESSAGE = 16
|
||||
ABUSE_NEW_MESSAGE = 16,
|
||||
|
||||
NEW_PLUGIN_VERSION = 17,
|
||||
NEW_PEERTUBE_VERSION = 18
|
||||
}
|
||||
|
||||
export interface VideoInfo {
|
||||
|
@ -108,6 +112,16 @@ export interface UserNotification {
|
|||
}
|
||||
}
|
||||
|
||||
plugin?: {
|
||||
name: string
|
||||
type: PluginType
|
||||
latestVersion: string
|
||||
}
|
||||
|
||||
peertube?: {
|
||||
latestVersion: string
|
||||
}
|
||||
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue