Push notifications added

This commit is contained in:
Fergal Moran
2018-03-18 19:56:00 +00:00
parent 00c258a294
commit 1d10e553a4
11 changed files with 547 additions and 40 deletions

View File

@@ -5,6 +5,7 @@ import { AuthService } from 'app/services/auth.service';
import { AppInsightsService } from 'app/services/app-insights.service';
import { SignalRService } from 'app/services/signalr.service';
import { ProfileService } from './services/profile.service';
import { PushNotificationsService } from 'app/services/push-notifications.service';
@Component({
selector: 'app-root',
@@ -27,9 +28,9 @@ export class AppComponent implements OnInit {
}
ngOnInit() {
if (this.loggedIn()) {
this._pushNotifications.requestPermission();
this._pushNotifications.requestPermissions();
if (this.loggedIn()) {
const user = this._profileService.getProfile().subscribe(u => {
if (u) {
const chatterChannel = `${u.uid}_chatter`;
@@ -44,22 +45,10 @@ export class AppComponent implements OnInit {
this._signalrService.connection.on(
chatterChannel,
result => {
this._pushNotifications
.create('PodNoms', { body: result })
.subscribe(
res =>
console.log(
'app.component',
'_pushNotifications',
res
),
err =>
console.log(
'app.component',
'_pushNotifications',
err
)
);
this._pushNotifications.createNotification(
'PodNoms',
result
);
}
);
})

View File

@@ -51,6 +51,7 @@ import { AboutComponent } from './components/about/about.component';
import { FooterComponent } from './components/footer/footer.component';
import { JobsService } from 'app/services/jobs.service';
import { AppInsightsService } from 'app/services/app-insights.service';
import { PushNotificationsService } from './services/push-notifications.service';
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(
@@ -100,7 +101,6 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
ToastyModule.forRoot(),
DropzoneModule,
ClipboardModule,
PushNotificationsModule,
StoreModule.forRoot(reducers),
@@ -124,9 +124,9 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
SignalRService,
ProfileService,
PodcastService,
PushNotificationsService,
ImageService,
DebugService,
PushNotificationsService,
ChatterService,
AppInsightsService,
JobsService,

View File

@@ -4,8 +4,8 @@ import { Component, OnInit } from '@angular/core';
import { DebugService } from 'app/services/debug.service';
import { environment } from 'environments/environment';
import { JobsService } from 'app/services/jobs.service';
import { PushNotificationsService } from 'ng-push';
import { ChatterService } from 'app/services/chatter.service';
import { PushNotificationsService } from 'app/services/push-notifications.service';
@Component({
selector: 'app-debug',
@@ -57,7 +57,7 @@ export class DebugComponent implements OnInit {
}
sendChatter() {
this._chatterService.ping('Pong').subscribe(r => {
this._pushNotifications.create('PodNoms', { body: r });
this._pushNotifications.createNotification('PodNoms', r);
});
}
sendDesktopNotification() {
@@ -66,22 +66,10 @@ export class DebugComponent implements OnInit {
'sendDesktopFunction',
this.notificationMessage
);
this._pushNotifications
.create('PodNoms', { body: this.notificationMessage })
.subscribe(
res =>
console.log(
'debug.component',
'sendDesktopNotification',
res
),
err =>
console.log(
'debug.component',
'sendDesktopNotification',
err
)
);
this._pushNotifications.createNotification(
'PodNoms',
this.notificationMessage
);
}
processOrphans() {
this._jobsService

View File

@@ -14,6 +14,7 @@ import * as fromPodcast from 'app/reducers';
import * as fromPodcastActions from 'app/actions/podcast.actions';
import * as fromEntriesActions from 'app/actions/entries.actions';
import { PodcastService } from 'app/services/podcast.service';
import { PushNotificationsService } from 'app/services/push-notifications.service';
@Component({
selector: 'app-podcast',

View File

@@ -1,8 +1,40 @@
import { Injectable } from '@angular/core';
export type Permission = 'denied' | 'granted' | 'default';
@Injectable()
export class PushNotificationsService {
permission: Permission;
constructor() { }
constructor() {}
requestPermissions() {
if ('Notification' in window) {
Notification.requestPermission((status: any) => {
console.log(
'push-notifications.service',
'requestPermissions',
status
);
this.permission = status;
});
}
}
isSupported() {
return 'Notification' in window && this.permission == 'granted';
}
createNotification(subject: string, text: string) {
if (this.isSupported()) {
const options = {
body: text,
icon: 'https://podnoms.com/assets/img/logo-icon.png'
};
const n = new Notification(subject, options);
} else {
console.error(
'push-notifications.service',
'createNotification',
'Notifications are not supported'
);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using PodNoms.Api.Persistence;
using PodNoms.Api.Services.Hubs;
namespace PodNoms.Api.Controllers {
[Authorize]
[Route("[controller]")]
public class ChatterController : Controller {
private readonly IUserRepository _repository;
private readonly HubLifetimeManager<ChatterHub> _chatterHub;
public ChatterController(IUserRepository repository, HubLifetimeManager<ChatterHub> chatterHub) {
this._chatterHub = chatterHub;
this._repository = repository;
}
[HttpPost("ping")]
public async Task<ActionResult<string>> Ping([FromBody] string message) {
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = await this._repository.GetAsync(email);
await _chatterHub.SendAllAsync(
$"{user.Uid}_chatter",
new object[] { message });
return Ok(message);
}
}
}

View File

@@ -0,0 +1,197 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180316223756_UidToUser")]
partial class UidToUser
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview1-28290")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreateDate");
b.Property<int>("PodcastId");
b.Property<string>("SourceUrl");
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("PodcastId");
b.ToTable("Playlists");
});
modelBuilder.Entity("PodNoms.Api.Models.Podcast", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreateDate")
.ValueGeneratedOnAdd()
.HasDefaultValueSql("getdate()");
b.Property<string>("Description");
b.Property<string>("ImageUrl");
b.Property<string>("Slug")
.IsUnicode(true);
b.Property<string>("Title");
b.Property<string>("Uid");
b.Property<DateTime>("UpdateDate");
b.Property<int?>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Podcasts");
});
modelBuilder.Entity("PodNoms.Api.Models.PodcastEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("AudioFileSize");
b.Property<float>("AudioLength");
b.Property<string>("AudioUrl");
b.Property<string>("Author");
b.Property<DateTime>("CreateDate")
.ValueGeneratedOnAdd()
.HasDefaultValueSql("getdate()");
b.Property<string>("Description");
b.Property<string>("ImageUrl");
b.Property<int?>("PlaylistId");
b.Property<int>("PodcastId");
b.Property<bool>("Processed");
b.Property<string>("ProcessingPayload");
b.Property<int>("ProcessingStatus");
b.Property<string>("SourceUrl");
b.Property<string>("Title");
b.Property<string>("Uid");
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("PlaylistId");
b.HasIndex("PodcastId");
b.ToTable("PodcastEntries");
});
modelBuilder.Entity("PodNoms.Api.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ApiKey")
.HasMaxLength(50);
b.Property<DateTime>("CreateDate");
b.Property<string>("EmailAddress")
.HasMaxLength(100);
b.Property<string>("FullName")
.HasMaxLength(100);
b.Property<string>("ProfileImage");
b.Property<string>("ProviderId")
.HasMaxLength(50);
b.Property<string>("RefreshToken");
b.Property<string>("Sid")
.HasMaxLength(50);
b.Property<string>("Slug")
.HasMaxLength(50);
b.Property<string>("Uid")
.HasMaxLength(32);
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("Slug")
.IsUnique()
.HasFilter("[Slug] IS NOT NULL");
b.ToTable("Users");
});
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
{
b.HasOne("PodNoms.Api.Models.Podcast", "Podcast")
.WithMany()
.HasForeignKey("PodcastId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("PodNoms.Api.Models.Podcast", b =>
{
b.HasOne("PodNoms.Api.Models.User", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("PodNoms.Api.Models.PodcastEntry", b =>
{
b.HasOne("PodNoms.Api.Models.Playlist")
.WithMany("PodcastEntries")
.HasForeignKey("PlaylistId");
b.HasOne("PodNoms.Api.Models.Podcast", "Podcast")
.WithMany("PodcastEntries")
.HasForeignKey("PodcastId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class UidToUser : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Uid",
table: "Users",
maxLength: 32,
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Uid",
table: "Users");
}
}
}

View File

@@ -0,0 +1,197 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180317002411_UidLenIncrease")]
partial class UidLenIncrease
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview1-28290")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreateDate");
b.Property<int>("PodcastId");
b.Property<string>("SourceUrl");
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("PodcastId");
b.ToTable("Playlists");
});
modelBuilder.Entity("PodNoms.Api.Models.Podcast", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreateDate")
.ValueGeneratedOnAdd()
.HasDefaultValueSql("getdate()");
b.Property<string>("Description");
b.Property<string>("ImageUrl");
b.Property<string>("Slug")
.IsUnicode(true);
b.Property<string>("Title");
b.Property<string>("Uid");
b.Property<DateTime>("UpdateDate");
b.Property<int?>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Podcasts");
});
modelBuilder.Entity("PodNoms.Api.Models.PodcastEntry", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("AudioFileSize");
b.Property<float>("AudioLength");
b.Property<string>("AudioUrl");
b.Property<string>("Author");
b.Property<DateTime>("CreateDate")
.ValueGeneratedOnAdd()
.HasDefaultValueSql("getdate()");
b.Property<string>("Description");
b.Property<string>("ImageUrl");
b.Property<int?>("PlaylistId");
b.Property<int>("PodcastId");
b.Property<bool>("Processed");
b.Property<string>("ProcessingPayload");
b.Property<int>("ProcessingStatus");
b.Property<string>("SourceUrl");
b.Property<string>("Title");
b.Property<string>("Uid");
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("PlaylistId");
b.HasIndex("PodcastId");
b.ToTable("PodcastEntries");
});
modelBuilder.Entity("PodNoms.Api.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ApiKey")
.HasMaxLength(50);
b.Property<DateTime>("CreateDate");
b.Property<string>("EmailAddress")
.HasMaxLength(100);
b.Property<string>("FullName")
.HasMaxLength(100);
b.Property<string>("ProfileImage");
b.Property<string>("ProviderId")
.HasMaxLength(50);
b.Property<string>("RefreshToken");
b.Property<string>("Sid")
.HasMaxLength(50);
b.Property<string>("Slug")
.HasMaxLength(50);
b.Property<string>("Uid")
.HasMaxLength(50);
b.Property<DateTime>("UpdateDate");
b.HasKey("Id");
b.HasIndex("Slug")
.IsUnique()
.HasFilter("[Slug] IS NOT NULL");
b.ToTable("Users");
});
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
{
b.HasOne("PodNoms.Api.Models.Podcast", "Podcast")
.WithMany()
.HasForeignKey("PodcastId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("PodNoms.Api.Models.Podcast", b =>
{
b.HasOne("PodNoms.Api.Models.User", "User")
.WithMany()
.HasForeignKey("UserId");
});
modelBuilder.Entity("PodNoms.Api.Models.PodcastEntry", b =>
{
b.HasOne("PodNoms.Api.Models.Playlist")
.WithMany("PodcastEntries")
.HasForeignKey("PlaylistId");
b.HasOne("PodNoms.Api.Models.Podcast", "Podcast")
.WithMany("PodcastEntries")
.HasForeignKey("PodcastId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class UidLenIncrease : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Uid",
table: "Users",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 32,
oldNullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Uid",
table: "Users",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 50,
oldNullable: true);
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
namespace PodNoms.Api.Services.Hubs {
[Authorize]
public class ChatterHub : Hub {
public async Task SendMessage(string user, string message) {
string timestamp = DateTime.Now.ToShortTimeString();
await Clients.All.SendAsync($"{user}_chatter", timestamp, user, message);
}
}
}