mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
feat(notifications): on update available (#352)
This commit is contained in:
parent
b4589243de
commit
2a3a839081
11 changed files with 326 additions and 18 deletions
|
@ -88,7 +88,8 @@ func main() {
|
||||||
|
|
||||||
// setup services
|
// setup services
|
||||||
var (
|
var (
|
||||||
schedulingService = scheduler.NewService(log)
|
notificationService = notification.NewService(log, notificationRepo)
|
||||||
|
schedulingService = scheduler.NewService(log, version, notificationService)
|
||||||
apiService = indexer.NewAPIService(log)
|
apiService = indexer.NewAPIService(log)
|
||||||
userService = user.NewService(userRepo)
|
userService = user.NewService(userRepo)
|
||||||
authService = auth.NewService(log, userService)
|
authService = auth.NewService(log, userService)
|
||||||
|
@ -97,7 +98,6 @@ func main() {
|
||||||
indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService)
|
indexerService = indexer.NewService(log, cfg.Config, indexerRepo, apiService, schedulingService)
|
||||||
filterService = filter.NewService(log, filterRepo, actionRepo, apiService, indexerService)
|
filterService = filter.NewService(log, filterRepo, actionRepo, apiService, indexerService)
|
||||||
releaseService = release.NewService(log, releaseRepo, actionService, filterService)
|
releaseService = release.NewService(log, releaseRepo, actionService, filterService)
|
||||||
notificationService = notification.NewService(log, notificationRepo)
|
|
||||||
ircService = irc.NewService(log, ircRepo, releaseService, indexerService, notificationService)
|
ircService = irc.NewService(log, ircRepo, releaseService, indexerService, notificationService)
|
||||||
feedService = feed.NewService(log, feedRepo, feedCacheRepo, releaseService, schedulingService)
|
feedService = feed.NewService(log, feedRepo, feedCacheRepo, releaseService, schedulingService)
|
||||||
)
|
)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/go-chi/chi v1.5.4
|
github.com/go-chi/chi v1.5.4
|
||||||
github.com/gorilla/sessions v1.2.1
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/gosimple/slug v1.12.0
|
github.com/gosimple/slug v1.12.0
|
||||||
|
github.com/hashicorp/go-version v1.6.0
|
||||||
github.com/hekmon/transmissionrpc/v2 v2.0.1
|
github.com/hekmon/transmissionrpc/v2 v2.0.1
|
||||||
github.com/lib/pq v1.10.4
|
github.com/lib/pq v1.10.4
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -277,6 +277,8 @@ github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn
|
||||||
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
|
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
|
|
@ -78,14 +78,13 @@ const (
|
||||||
type NotificationEvent string
|
type NotificationEvent string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
//NotificationEventAppUpdateAvailable NotificationEvent = "APP_UPDATE_AVAILABLE"
|
NotificationEventAppUpdateAvailable NotificationEvent = "APP_UPDATE_AVAILABLE"
|
||||||
|
NotificationEventPushApproved NotificationEvent = "PUSH_APPROVED"
|
||||||
NotificationEventPushApproved NotificationEvent = "PUSH_APPROVED"
|
NotificationEventPushRejected NotificationEvent = "PUSH_REJECTED"
|
||||||
NotificationEventPushRejected NotificationEvent = "PUSH_REJECTED"
|
NotificationEventPushError NotificationEvent = "PUSH_ERROR"
|
||||||
NotificationEventPushError NotificationEvent = "PUSH_ERROR"
|
NotificationEventIRCDisconnected NotificationEvent = "IRC_DISCONNECTED"
|
||||||
NotificationEventIRCDisconnected NotificationEvent = "IRC_DISCONNECTED"
|
NotificationEventIRCReconnected NotificationEvent = "IRC_RECONNECTED"
|
||||||
NotificationEventIRCReconnected NotificationEvent = "IRC_RECONNECTED"
|
NotificationEventTest NotificationEvent = "TEST"
|
||||||
NotificationEventTest NotificationEvent = "TEST"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationEventArr []NotificationEvent
|
type NotificationEventArr []NotificationEvent
|
||||||
|
|
|
@ -128,7 +128,9 @@ func (s *service) registerSenders() {
|
||||||
|
|
||||||
// Send notifications
|
// Send notifications
|
||||||
func (s *service) Send(event domain.NotificationEvent, payload domain.NotificationPayload) {
|
func (s *service) Send(event domain.NotificationEvent, payload domain.NotificationPayload) {
|
||||||
s.log.Debug().Msgf("sending notification for %v", string(event))
|
if len(s.senders) > 0 {
|
||||||
|
s.log.Debug().Msgf("sending notification for %v", string(event))
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for _, sender := range s.senders {
|
for _, sender := range s.senders {
|
||||||
|
|
51
internal/scheduler/jobs.go
Normal file
51
internal/scheduler/jobs.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package scheduler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/autobrr/autobrr/internal/domain"
|
||||||
|
"github.com/autobrr/autobrr/internal/notification"
|
||||||
|
"github.com/autobrr/autobrr/pkg/version"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckUpdatesJob struct {
|
||||||
|
Name string
|
||||||
|
Log zerolog.Logger
|
||||||
|
Version string
|
||||||
|
NotifSvc notification.Service
|
||||||
|
|
||||||
|
lastCheckVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *CheckUpdatesJob) Run() {
|
||||||
|
v := version.Checker{
|
||||||
|
Owner: "autobrr",
|
||||||
|
Repo: "autobrr",
|
||||||
|
}
|
||||||
|
|
||||||
|
newAvailable, newVersion, err := v.CheckNewVersion(context.TODO(), j.Version)
|
||||||
|
if err != nil {
|
||||||
|
j.Log.Error().Err(err).Msg("could not check for new release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if newAvailable {
|
||||||
|
j.Log.Info().Msgf("a new release has been found: %v Consider updating.", newVersion)
|
||||||
|
|
||||||
|
// this is not persisted so this can trigger more than once
|
||||||
|
// lets check if we have different versions between runs
|
||||||
|
if newVersion != j.lastCheckVersion {
|
||||||
|
j.NotifSvc.Send(domain.NotificationEventAppUpdateAvailable, domain.NotificationPayload{
|
||||||
|
Subject: "New update available!",
|
||||||
|
Message: newVersion,
|
||||||
|
Event: domain.NotificationEventAppUpdateAvailable,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
j.lastCheckVersion = newVersion
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
package scheduler
|
package scheduler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/autobrr/autobrr/internal/logger"
|
"github.com/autobrr/autobrr/internal/logger"
|
||||||
|
"github.com/autobrr/autobrr/internal/notification"
|
||||||
"github.com/autobrr/autobrr/pkg/errors"
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
|
@ -17,15 +20,19 @@ type Service interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
log zerolog.Logger
|
log zerolog.Logger
|
||||||
cron *cron.Cron
|
version string
|
||||||
|
notificationSvc notification.Service
|
||||||
|
|
||||||
|
cron *cron.Cron
|
||||||
jobs map[string]cron.EntryID
|
jobs map[string]cron.EntryID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(log logger.Logger) Service {
|
func NewService(log logger.Logger, version string, notificationSvc notification.Service) Service {
|
||||||
return &service{
|
return &service{
|
||||||
log: log.With().Str("module", "scheduler").Logger(),
|
log: log.With().Str("module", "scheduler").Logger(),
|
||||||
|
version: version,
|
||||||
|
notificationSvc: notificationSvc,
|
||||||
cron: cron.New(cron.WithChain(
|
cron: cron.New(cron.WithChain(
|
||||||
cron.Recover(cron.DefaultLogger),
|
cron.Recover(cron.DefaultLogger),
|
||||||
)),
|
)),
|
||||||
|
@ -36,15 +43,35 @@ func NewService(log logger.Logger) Service {
|
||||||
func (s *service) Start() {
|
func (s *service) Start() {
|
||||||
s.log.Debug().Msg("scheduler.Start")
|
s.log.Debug().Msg("scheduler.Start")
|
||||||
|
|
||||||
|
// start scheduler
|
||||||
s.cron.Start()
|
s.cron.Start()
|
||||||
|
|
||||||
|
// init jobs
|
||||||
|
go s.addAppJobs()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *service) addAppJobs() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
checkUpdates := &CheckUpdatesJob{
|
||||||
|
Name: "app-check-updates",
|
||||||
|
Log: s.log.With().Str("job", "app-check-updates").Logger(),
|
||||||
|
Version: s.version,
|
||||||
|
NotifSvc: s.notificationSvc,
|
||||||
|
lastCheckVersion: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
s.AddJob(checkUpdates, "2 */6 * * *", "app-check-updates")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *service) Stop() {
|
func (s *service) Stop() {
|
||||||
s.log.Debug().Msg("scheduler.Stop")
|
s.log.Debug().Msg("scheduler.Stop")
|
||||||
s.cron.Stop()
|
s.cron.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) AddJob(job cron.Job, interval string, identifier string) (int, error) {
|
func (s *service) AddJob(job cron.Job, interval string, identifier string) (int, error) {
|
||||||
|
|
||||||
id, err := s.cron.AddJob(interval, cron.NewChain(
|
id, err := s.cron.AddJob(interval, cron.NewChain(
|
||||||
|
@ -88,3 +115,14 @@ func (s *service) RemoveJobByIdentifier(id string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GenericJob struct {
|
||||||
|
Name string
|
||||||
|
Log zerolog.Logger
|
||||||
|
|
||||||
|
callback func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *GenericJob) Run() {
|
||||||
|
j.callback()
|
||||||
|
}
|
||||||
|
|
101
pkg/version/version.go
Normal file
101
pkg/version/version.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/autobrr/autobrr/pkg/errors"
|
||||||
|
|
||||||
|
goversion "github.com/hashicorp/go-version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Release is a GitHub release
|
||||||
|
type Release struct {
|
||||||
|
TagName string `json:"tag_name,omitempty"`
|
||||||
|
TargetCommitish *string `json:"target_commitish,omitempty"`
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Body *string `json:"body,omitempty"`
|
||||||
|
Draft *bool `json:"draft,omitempty"`
|
||||||
|
Prerelease *bool `json:"prerelease,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Release) IsPreOrDraft() bool {
|
||||||
|
if *r.Draft || *r.Prerelease {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Checker struct {
|
||||||
|
// user/repo-name or org/repo-name
|
||||||
|
Owner string
|
||||||
|
Repo string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) get(ctx context.Context) (*Release, error) {
|
||||||
|
url := fmt.Sprintf("https://api.github.com/repos/%v/%v/releases/latest", c.Owner, c.Repo)
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||||
|
client := http.DefaultClient
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("error getting releases for %v: %s", c.Repo, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var release Release
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
if err := dec.Decode(&release); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &release, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) CheckNewVersion(ctx context.Context, version string) (bool, string, error) {
|
||||||
|
if version == "dev" {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
release, err := c.get(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.checkNewVersion(version, release)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Checker) checkNewVersion(version string, release *Release) (bool, string, error) {
|
||||||
|
currentVersion, err := goversion.NewVersion(version)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", errors.Wrap(err, "error parsing current version")
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseVersion, err := goversion.NewVersion(release.TagName)
|
||||||
|
if err != nil {
|
||||||
|
return false, "", errors.Wrap(err, "error parsing release version")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(currentVersion.Prerelease()) == 0 && len(releaseVersion.Prerelease()) > 0 {
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if releaseVersion.GreaterThan(currentVersion) {
|
||||||
|
// new update available
|
||||||
|
return true, releaseVersion.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "", nil
|
||||||
|
}
|
109
pkg/version/version_test.go
Normal file
109
pkg/version/version_test.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitHubReleaseChecker_checkNewVersion(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Repo string
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
version string
|
||||||
|
release *Release
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantNew bool
|
||||||
|
wantVersion string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "outdated new available",
|
||||||
|
fields: fields{},
|
||||||
|
args: args{
|
||||||
|
version: "v0.2.0",
|
||||||
|
release: &Release{
|
||||||
|
TagName: "v0.3.0",
|
||||||
|
TargetCommitish: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantNew: true,
|
||||||
|
wantVersion: "0.3.0",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "same version",
|
||||||
|
fields: fields{},
|
||||||
|
args: args{
|
||||||
|
version: "v0.2.0",
|
||||||
|
release: &Release{
|
||||||
|
TagName: "v0.2.0",
|
||||||
|
TargetCommitish: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantNew: false,
|
||||||
|
wantVersion: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no new version",
|
||||||
|
fields: fields{},
|
||||||
|
args: args{
|
||||||
|
version: "v0.3.0",
|
||||||
|
release: &Release{
|
||||||
|
TagName: "v0.2.0",
|
||||||
|
TargetCommitish: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantNew: false,
|
||||||
|
wantVersion: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new rc available",
|
||||||
|
fields: fields{},
|
||||||
|
args: args{
|
||||||
|
version: "v0.3.0",
|
||||||
|
release: &Release{
|
||||||
|
TagName: "v0.3.0-rc1",
|
||||||
|
TargetCommitish: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantNew: false,
|
||||||
|
wantVersion: "",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "new rc available",
|
||||||
|
fields: fields{},
|
||||||
|
args: args{
|
||||||
|
version: "v0.3.0-RC1",
|
||||||
|
release: &Release{
|
||||||
|
TagName: "v0.3.0-RC2",
|
||||||
|
TargetCommitish: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantNew: true,
|
||||||
|
wantVersion: "0.3.0-RC2",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := &Checker{
|
||||||
|
Repo: tt.fields.Repo,
|
||||||
|
}
|
||||||
|
got, gotVersion, err := g.checkNewVersion(tt.args.version, tt.args.release)
|
||||||
|
if tt.wantErr && assert.Error(t, err) {
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.wantNew, got)
|
||||||
|
assert.Equal(t, tt.wantVersion, gotVersion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -305,7 +305,7 @@ export const downloadsPerUnitOptions: OptionBasic[] = [
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
value: string;
|
value: NotificationEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EventOptions: SelectOption[] = [
|
export const EventOptions: SelectOption[] = [
|
||||||
|
@ -333,5 +333,10 @@ export const EventOptions: SelectOption[] = [
|
||||||
label: "IRC Reconnected",
|
label: "IRC Reconnected",
|
||||||
value: "IRC_RECONNECTED",
|
value: "IRC_RECONNECTED",
|
||||||
description: "Reconnected to irc network after error"
|
description: "Reconnected to irc network after error"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
label: "New update",
|
||||||
|
value: "APP_UPDATE_AVAILABLE",
|
||||||
|
description: "Get notified on updates"
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
2
web/src/types/Notification.d.ts
vendored
2
web/src/types/Notification.d.ts
vendored
|
@ -1,5 +1,5 @@
|
||||||
type NotificationType = "DISCORD" | "TELEGRAM";
|
type NotificationType = "DISCORD" | "TELEGRAM";
|
||||||
type NotificationEvent = "PUSH_APPROVED" | "PUSH_REJECTED" | "PUSH_ERROR" | "IRC_DISCONNECTED" | "IRC_RECONNECTED";
|
type NotificationEvent = "PUSH_APPROVED" | "PUSH_REJECTED" | "PUSH_ERROR" | "IRC_DISCONNECTED" | "IRC_RECONNECTED" | "APP_UPDATE_AVAILABLE";
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue