Merge branch 'release/v0.16'

This commit is contained in:
Fergal Moran
2018-03-13 22:08:42 +00:00
66 changed files with 271 additions and 169 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "pod-noms.web",
"version": "0.13.0",
"version": "0.16.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -522,14 +522,6 @@
"moment": "2.21.0"
}
},
"angulartics2": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/angulartics2/-/angulartics2-5.1.2.tgz",
"integrity": "sha512-ZTmSXH6t7Set8TT4oY6HcCLr7bIbK56oJnUVlDJNXSnCTS2DeZW0lP/4pcGpdELCTAF5xfosjar+DM+7kqWiPQ==",
"requires": {
"tslib": "1.9.0"
}
},
"ansi-html": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "pod-noms.web",
"version": "0.15.0",
"version": "0.16.0",
"license": "MIT",
"scripts": {
"ng": "ng",

View File

@@ -2,7 +2,6 @@ import { GlobalsService } from './services/globals.service';
import { Component, HostBinding } from '@angular/core';
import { Store } from '@ngrx/store';
import { AuthService } from 'app/services/auth.service';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
@Component({
selector: 'app-root',

View File

@@ -48,6 +48,7 @@ import { ResetComponent } from './components/reset/reset.component';
import { ProfileComponent } from './components/profile/profile.component';
import { AboutComponent } from './components/about/about.component';
import { FooterComponent } from './components/footer/footer.component';
import { JobsService } from 'app/services/jobs.service';
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(
@@ -122,6 +123,7 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
PodcastService,
ImageService,
DebugService,
JobsService,
GlobalsService
],
bootstrap: [AppComponent]

View File

@@ -20,6 +20,17 @@
</div>
</div>
</div>
<div class="row">
<div class="block">
<div class="block-header">
Jobs
</div>
<div class="block-content">
<button class="btn btn-primary"
(click)="processOrphans()">Process Orphans</button>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="block">
@@ -30,11 +41,11 @@
</pre>
</div>
<div class="row">
API Host: {{apiHost}} <br />
SignalR Host: {{signalrHost}} <br />
API Host: {{apiHost}} <br />
SignalR Host: {{signalrHost}} <br />
Ping: {{pingPong}} </div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import { SignalRService } from 'app/services/signalr.service';
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';
@Component({
selector: 'app-debug',
@@ -18,7 +19,8 @@ export class DebugComponent implements OnInit {
signalrHost = environment.SIGNALR_HOST;
pingPong = '';
constructor(private _debugService: DebugService, private _signalrService: SignalRService) {}
constructor(private _debugService: DebugService, private _jobsService: JobsService,
private _signalrService: SignalRService) {}
ngOnInit() {
this._signalrService
.init(`${environment.SIGNALR_HOST}hubs/debug`)
@@ -41,4 +43,9 @@ export class DebugComponent implements OnInit {
doSomething() {
alert('doSomething was did');
}
processOrphans(){
this._jobsService.processOrphans()
.subscribe(e => console.log('debug.component.ts', 'processOrphans', e));
}
}

View File

@@ -4,6 +4,9 @@
<a [routerLink]="['add']">
<i class="fa fa-plus"></i> Add Podcast
</a>
<!-- <a [routerLink]="['debug']">
<i class="fa fa-plus"></i> Debug
</a> -->
</div>
<div class="content-header-section" *ngIf="user$ | async; let user; else loading">
<img [src]="user.profileImage"
@@ -25,4 +28,4 @@
</div>
<ng-template #loading><i class="fa fa-sun-o fa-spin text-white"></i></ng-template>
</div>
</header>
</header>

View File

@@ -17,6 +17,7 @@ import { PodcastService } from 'app/services/podcast.service';
export class PodcastAddUrlFormComponent implements AfterViewInit {
@Input() podcast: PodcastModel;
@Output() onUrlAddComplete: EventEmitter<any> = new EventEmitter();
@Output() onUploadDeferred: EventEmitter<any> = new EventEmitter();
newEntrySourceUrl: string;
errorText: string;
isPosting: boolean = false;
@@ -36,20 +37,23 @@ export class PodcastAddUrlFormComponent implements AfterViewInit {
this.errorText = '';
if (this.isValidURL(urlToCheck)) {
this.isPosting = true;
const entry = new PodcastEntryModel(
this.podcast.id,
urlToCheck
);
this._service.addEntry(entry)
.subscribe(e => {
const entry = new PodcastEntryModel(this.podcast.id, urlToCheck);
this._service.addEntry(entry).subscribe(
e => {
if (e) {
this.onUrlAddComplete.emit(e);
if (e.ProcessingStatus == 6) {
this.onUploadDeferred.emit(e);
} else {
this.onUrlAddComplete.emit(e);
}
}
}, (err) => {
},
err => {
this.isPosting = false;
this.errorText = 'This does not look like a valid URL';
this.newEntrySourceUrl = urlToCheck;
});
}
);
} else {
this.isPosting = false;
this.errorText = 'This does not look like a valid URL';

View File

@@ -45,7 +45,8 @@
</app-podcast-upload-form>
<app-podcast-add-url-form *ngIf="urlMode"
[podcast]="podcast"
(onUrlAddComplete)="onUrlAddComplete($event)">
(onUrlAddComplete)="onUrlAddComplete($event)"
(onUploadDeferred)="onEntryUploadDeferred($event)">
</app-podcast-add-url-form>
<table class="js-table-checkable table table-hover js-table-checkable-enabled">
<tbody>

View File

@@ -7,7 +7,7 @@ import { AppComponent } from 'app/app.component';
import { Store } from '@ngrx/store';
import { ApplicationState } from 'app/store';
import { HostListener } from '@angular/core';
import {Location} from '@angular/common';
import { Location } from '@angular/common';
import { UpdateAction, AddAction } from 'app/actions/entries.actions';
import * as fromPodcast from 'app/reducers';
@@ -34,7 +34,11 @@ export class PodcastComponent {
}
}
constructor(private _store: Store<ApplicationState>, route: ActivatedRoute, private _location: Location) {
constructor(
private _store: Store<ApplicationState>,
route: ActivatedRoute,
private _location: Location
) {
this.selectedPodcast$ = _store.select(fromPodcast.getSelectedPodcast);
this.entries$ = _store.select(fromPodcast.getEntries);
@@ -48,8 +52,12 @@ export class PodcastComponent {
this.selectedPodcast$.subscribe(r => {
if (r) {
slug = r.slug;
_store.dispatch(new fromEntriesActions.LoadAction(slug));
_store.dispatch(new fromPodcastActions.SelectAction(slug));
_store.dispatch(
new fromEntriesActions.LoadAction(slug)
);
_store.dispatch(
new fromPodcastActions.SelectAction(slug)
);
this._location.go('/podcasts/' + slug);
}
@@ -79,6 +87,9 @@ export class PodcastComponent {
// so do a funky success/update dance
this._store.dispatch(new fromEntriesActions.AddSuccessAction(entry));
this._store.dispatch(new fromEntriesActions.UpdateAction(entry));
}
onEntryUploadDeferred($event) {
}
onUrlAddComplete(entry: PodcastEntryModel) {
this.urlMode = false;

0
client/src/app/models/podcasts.models.ts Executable file → Normal file
View File

0
client/src/app/models/profile.model.ts Executable file → Normal file
View File

0
client/src/app/services/auth.guard.ts Executable file → Normal file
View File

0
client/src/app/services/auth.service.ts Executable file → Normal file
View File

View File

@@ -0,0 +1,14 @@
import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { AuthHttp } from 'angular2-jwt';
import { Observable } from 'rxjs/Observable';
import { environment } from 'environments/environment';
@Injectable()
export class JobsService {
constructor(private _http: AuthHttp) {}
processOrphans(): Observable<Response> {
return this._http.get(environment.API_HOST + '/job/processorphans');
}
}

0
client/src/app/services/profile.service.ts Executable file → Normal file
View File

0
client/src/app/services/pusher.service.ts Executable file → Normal file
View File

0
client/src/assets/img/logo-icon.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

0
client/src/assets/img/robothand.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

0
client/src/environments/environment.prod.ts Executable file → Normal file
View File

0
client/src/environments/environment.ts Executable file → Normal file
View File

0
client/upgrade_cli Executable file → Normal file
View File

2
docker/docker-compose.yml Executable file → Normal file
View File

@@ -1,7 +1,7 @@
version: '2'
services:
nginx:
restart: "always"
# restart: "always"
build: ./nginx/
ports:
- "80:80"

13
docker/letsencrypt.sh Executable file → Normal file
View File

@@ -13,6 +13,19 @@ if [[ $web == "Y" || $web == "y" ]]; then
fi
echo \n
read -n1 -p "Renew api? [y,n]" api
if [[ $api == "Y" || $api == "y" ]]; then
docker run -it --rm -p 443:443 -p 80:80 --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly \
--agree-tos \
--renew-by-default \
-d api.podnoms.com \
-m fergal.moran@gmail.com
fi
echo \n
read -n1 -p "Renew realtime? [y,n]" rt
if [[ $rt == "Y" || $rt == "y" ]]; then
docker run -it --rm -p 443:443 -p 80:80 --name certbot \

View File

@@ -42,6 +42,31 @@ server {
}
}
server {
listen 80;
listen 443 ssl;
server_name api.podnoms.com;
ssl_certificate /etc/letsencrypt/live/api.podnoms.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.podnoms.com/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
if ($ssl_protocol = "") {
rewrite ^((?!/rss/).) https://$server_name$request_uri? permanent;
}
location / {
proxy_pass http://api:5000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 80;
listen 443 ssl;
@@ -79,7 +104,7 @@ server {
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
location / {
proxy_pass http://api:5000/hangfire;
proxy_pass http://api:5000/;
}
}

0
server/.gitignore vendored Executable file → Normal file
View File

View File

@@ -11,6 +11,7 @@ using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
using PodNoms.Api.Models.ViewModels;
using PodNoms.Api.Persistence;
using PodNoms.Api.Services;
using PodNoms.Api.Services.Processor;
using PodNoms.Api.Services.Storage;
@@ -67,18 +68,24 @@ namespace PodNoms.Api.Controllers {
// first check url is valid
var entry = _mapper.Map<PodcastEntryViewModel, PodcastEntry>(item);
var podcast = await _podcastRepository.GetAsync(item.PodcastId);
if (podcast != null && await _processor.GetInformation(entry)) {
if (entry.ProcessingStatus == ProcessingStatus.Processing) {
if (string.IsNullOrEmpty(entry.ImageUrl)) {
entry.ImageUrl = $"{_storageSettings.CdnUrl}static/images/default-entry.png";
if (podcast != null) {
var status = await _processor.GetInformation(entry);
if (status == AudioType.Valid) {
if (entry.ProcessingStatus == ProcessingStatus.Processing) {
if (string.IsNullOrEmpty(entry.ImageUrl)) {
entry.ImageUrl = $"{_storageSettings.CdnUrl}static/images/default-entry.png";
}
entry.Podcast = podcast;
entry.Processed = false;
await _repository.AddOrUpdateAsync(entry);
await _unitOfWork.CompleteAsync();
_processEntry(entry);
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel>(entry);
return result;
}
entry.Podcast = podcast;
entry.Processed = false;
await _repository.AddOrUpdateAsync(entry);
await _unitOfWork.CompleteAsync();
_processEntry(entry);
var result = _mapper.Map<PodcastEntry, PodcastEntryViewModel>(entry);
return result;
} else if (status == AudioType.Playlist) {
entry.ProcessingStatus = ProcessingStatus.Deferred;
return Accepted(entry);
}
}
return BadRequest();

0
server/Controllers/PodcastController.cs Executable file → Normal file
View File

0
server/Controllers/ProfileController.cs Executable file → Normal file
View File

0
server/Controllers/RssController.cs Executable file → Normal file
View File

0
server/Dockerfile Executable file → Normal file
View File

0
server/Models/AppSettings.cs Executable file → Normal file
View File

0
server/Models/BaseModel.cs Executable file → Normal file
View File

0
server/Models/Podcast.cs Executable file → Normal file
View File

3
server/Models/PodcastEntry.cs Executable file → Normal file
View File

@@ -7,7 +7,8 @@ namespace PodNoms.Api.Models {
Processing, //2
Uploading, //3
Processed, //4
Failed //5
Failed, //5
Deferred //6
}
public class PodcastEntry : BaseModel {

0
server/Models/User.cs Executable file → Normal file
View File

0
server/Models/ViewModels/ProfileViewModel.cs Executable file → Normal file
View File

View File

0
server/Persistence/IPodcastRepository.cs Executable file → Normal file
View File

0
server/Persistence/IUserRepository.cs Executable file → Normal file
View File

0
server/Persistence/PodcastRepository.cs Executable file → Normal file
View File

0
server/Persistence/PodnomsContext.cs Executable file → Normal file
View File

0
server/Persistence/UserRepository.cs Executable file → Normal file
View File

0
server/Resources/podcast.xml Executable file → Normal file
View File

0
server/Services/Auth/AuthenticationMiddleware.cs Executable file → Normal file
View File

View File

@@ -13,6 +13,7 @@ using static NYoutubeDL.Helpers.Enums;
namespace PodNoms.Api.Services.Downloader {
public class AudioDownloader {
private readonly string _url;
private readonly string _downloader;
public VideoDownloadInfo Properties { get; private set; }
@@ -49,17 +50,22 @@ namespace PodNoms.Api.Services.Downloader {
return $"{{\"Error\": \"{ex.Message}\"}}";
}
}
public async Task<bool> GetInfo() {
var ret = false;
public async Task<AudioType> GetInfo() {
var ret = AudioType.Invalid;
await Task.Run(() => {
var youtubeDl = new YoutubeDL();
youtubeDl.VideoUrl = this._url;
var info = youtubeDl.GetDownloadInfo() as VideoDownloadInfo;
ret = (
info != null &&
info is VideoDownloadInfo && //make sure it's not a playlist
(info.Errors.Count == 0 || info.VideoSize != null));
if (ret) this.Properties = info;
DownloadInfo info = youtubeDl.GetDownloadInfo();
if (info != null &&
(info.Errors.Count == 0 || info.VideoSize != null)) {
if (info is PlaylistDownloadInfo) {
ret = AudioType.Playlist;
} else if (info is VideoDownloadInfo) {
ret = AudioType.Valid;
this.Properties = (VideoDownloadInfo)info;
}
}
});
return ret;
}

View File

@@ -7,18 +7,15 @@ using Microsoft.WindowsAzure.Storage.Blob;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
namespace PodNoms.Api.Services.Jobs
{
public class ClearOrphanAudioJob : IJob
{
namespace PodNoms.Api.Services.Jobs {
public class ClearOrphanAudioJob : IJob {
public readonly IEntryRepository _entryRepository;
public readonly StorageSettings _storageSettings;
public readonly AudioFileStorageSettings _audioStorageSettings;
private readonly ILogger<ClearOrphanAudioJob> _logger;
public ClearOrphanAudioJob(IEntryRepository entryRepository, IOptions<StorageSettings> storageSettings,
IOptions<AudioFileStorageSettings> audioStorageSettings, ILoggerFactory logger)
{
IOptions<AudioFileStorageSettings> audioStorageSettings, ILoggerFactory logger) {
this._storageSettings = storageSettings.Value;
this._audioStorageSettings = audioStorageSettings.Value;
this._entryRepository = entryRepository;
@@ -26,38 +23,28 @@ namespace PodNoms.Api.Services.Jobs
this._logger = logger.CreateLogger<ClearOrphanAudioJob>();
}
public async Task Execute()
{
try
{
public async Task Execute() {
try {
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(_storageSettings.ConnectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(_audioStorageSettings.ContainerName);
var blobs = await container.ListBlobsSegmentedAsync(null);
foreach (CloudBlockBlob blob in blobs.Results)
{
try
{
foreach (CloudBlockBlob blob in blobs.Results) {
try {
Console.WriteLine(blob.StorageUri);
var guid = blob.Name.Split('.')[0];
if (!string.IsNullOrEmpty(guid))
{
if (!string.IsNullOrEmpty(guid)) {
var entry = await _entryRepository.GetByUidAsync(guid);
if (entry == null)
{
if (entry == null) {
await blob.DeleteIfExistsAsync();
}
}
}
catch (Exception e)
{
} catch (Exception e) {
_logger.LogWarning($"Error processing blob {blob.Uri}\n{e.Message}");
}
}
}
catch (Exception ex)
{
} catch (Exception ex) {
_logger.LogError($"Error clearing orphans\n{ex.Message}");
}
}

View File

@@ -0,0 +1,13 @@
using Hangfire;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
namespace PodNoms.Api.Services.Jobs {
public static class JobBootstrapper {
public static void BootstrapJobs() {
RecurringJob.AddOrUpdate<ClearOrphanAudioJob>(x => x.Execute(), Cron.Daily(1));
}
}
}

View File

@@ -3,8 +3,9 @@ using PodNoms.Api.Models;
namespace PodNoms.Api.Services.Processor {
public interface IUrlProcessService {
Task<bool> GetInformation (int entryId);
Task<bool> GetInformation (PodcastEntry entry);
Task<bool> DownloadAudio (int entryId);
Task<AudioType> GetInformation(int entryId);
Task<AudioType> GetInformation(PodcastEntry entry);
Task<bool> DownloadAudio(int entryId);
}
}

View File

@@ -37,23 +37,23 @@ namespace PodNoms.Api.Services.Processor {
uid,
e);
}
public async Task<bool> GetInformation(int entryId) {
public async Task<AudioType> GetInformation(int entryId) {
var entry = await _repository.GetAsync(entryId);
if (entry == null || string.IsNullOrEmpty(entry.SourceUrl)) {
_logger.LogError("Unable to process item");
return false;
return AudioType.Invalid;
}
if (entry.SourceUrl.EndsWith(".mp3") || entry.SourceUrl.EndsWith(".wav") || entry.SourceUrl.EndsWith(".aif")) {
return true;
return AudioType.Valid;
}
return await GetInformation(entry);
}
public async Task<bool> GetInformation(PodcastEntry entry) {
public async Task<AudioType> GetInformation(PodcastEntry entry) {
var downloader = new AudioDownloader(entry.SourceUrl, _applicationsSettings.Downloader);
await downloader.GetInfo();
if (downloader.Properties != null) {
var ret = await downloader.GetInfo();
if (ret == AudioType.Valid) {
entry.Title = downloader.Properties?.Title;
entry.Description = downloader.Properties?.Description;
entry.ImageUrl = downloader.Properties?.Thumbnail;
@@ -68,11 +68,8 @@ namespace PodNoms.Api.Services.Processor {
_logger.LogDebug("***DOWNLOAD INFO RETRIEVED****\n");
_logger.LogDebug($"Title: {entry.Title}\nDescription: {entry.Description}\nAuthor: {entry.Author}\n");
// var pusherResult = await _sendProcessCompleteMessage(entry);
return true;
}
return false;
return ret;
}
public async Task<bool> DownloadAudio(int entryId) {
var entry = await _repository.GetAsync(entryId);

View File

@@ -0,0 +1,7 @@
namespace PodNoms.Api.Services {
public enum AudioType {
Invalid,
Valid,
Playlist
}
}

View File

@@ -39,61 +39,61 @@ namespace PodNoms.Api {
public class Startup {
public IConfiguration Configuration { get; }
public Startup (IConfiguration configuration) {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureProductionServices (IServiceCollection services) {
ConfigureServices (services);
services.AddHangfire (config => {
config.UseSqlServerStorage (Configuration["ConnectionStrings:DefaultConnection"]);
public void ConfigureProductionServices(IServiceCollection services) {
ConfigureServices(services);
services.AddHangfire(config => {
config.UseSqlServerStorage(Configuration["ConnectionStrings:DefaultConnection"]);
});
}
public void ConfigureDevelopmentServices (IServiceCollection services) {
ConfigureServices (services);
services.AddHangfire (config => {
config.UseMemoryStorage ();
public void ConfigureDevelopmentServices(IServiceCollection services) {
ConfigureServices(services);
services.AddHangfire(config => {
config.UseMemoryStorage();
});
}
public void ConfigureServices (IServiceCollection services) {
Console.WriteLine ($"Configuring services: {Configuration.ToString()}");
public void ConfigureServices(IServiceCollection services) {
Console.WriteLine($"Configuring services: {Configuration.ToString()}");
services.AddDbContext<PodnomsDbContext> (options =>
options.UseSqlServer (Configuration.GetConnectionString ("DefaultConnection")));
services.AddDbContext<PodnomsDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddOptions ();
services.Configure<AppSettings> (Configuration.GetSection ("App"));
services.Configure<StorageSettings> (Configuration.GetSection ("Storage"));
services.Configure<ApplicationsSettings> (Configuration.GetSection ("ApplicationsSettings"));
services.Configure<ImageFileStorageSettings> (Configuration.GetSection ("ImageFileStorageSettings"));
services.Configure<AudioFileStorageSettings> (Configuration.GetSection ("AudioFileStorageSettings"));
services.Configure<FormOptions> (options => {
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("App"));
services.Configure<StorageSettings>(Configuration.GetSection("Storage"));
services.Configure<ApplicationsSettings>(Configuration.GetSection("ApplicationsSettings"));
services.Configure<ImageFileStorageSettings>(Configuration.GetSection("ImageFileStorageSettings"));
services.Configure<AudioFileStorageSettings>(Configuration.GetSection("AudioFileStorageSettings"));
services.Configure<FormOptions>(options => {
options.ValueCountLimit = 10;
options.ValueLengthLimit = int.MaxValue;
options.MemoryBufferThreshold = Int32.MaxValue;
options.MultipartBodyLengthLimit = long.MaxValue;
});
services.AddAutoMapper (e => {
e.AddProfile (new MappingProvider (Configuration));
services.AddAutoMapper(e => {
e.AddProfile(new MappingProvider(Configuration));
});
services.AddAuthentication (options => {
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer (options => {
}).AddJwtBearer(options => {
options.Audience = Configuration["auth0:clientId"];
options.Authority = $"https://{Configuration["auth0:domain"]}/";
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = "name"
};
options.Events = new JwtBearerEvents () {
options.Events = new JwtBearerEvents() {
OnTokenValidated = AuthenticationMiddleware.OnTokenValidated
};
options.Events.OnMessageReceived = context => {
StringValues token;
if (context.Request.Path.Value.StartsWith ("/hubs/") && context.Request.Query.TryGetValue ("token", out token)) {
if (context.Request.Path.Value.StartsWith("/hubs/") && context.Request.Query.TryGetValue("token", out token)) {
context.Token = token;
}
@@ -102,107 +102,107 @@ namespace PodNoms.Api {
});
var defaultPolicy =
new AuthorizationPolicyBuilder ()
.AddAuthenticationSchemes ("Bearer")
.RequireAuthenticatedUser ()
.Build ();
new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Bearer")
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization (j => {
services.AddAuthorization(j => {
j.DefaultPolicy = defaultPolicy;
});
services.AddMvc (options => {
options.OutputFormatters.Add (new XmlSerializerOutputFormatter ());
options.OutputFormatters
.OfType<StringOutputFormatter> ()
.Single ().SupportedMediaTypes.Add ("text/html");
})
.SetCompatibilityVersion (CompatibilityVersion.Version_2_1)
.AddJsonOptions (options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver ();
services.AddMvc(options => {
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
options.OutputFormatters
.OfType<StringOutputFormatter>()
.Single().SupportedMediaTypes.Add("text/html");
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(options => {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
})
.AddXmlSerializerFormatters ();
.AddXmlSerializerFormatters();
services.Configure<FormOptions> (x => {
services.Configure<FormOptions>(x => {
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue; // In case of multipart
});
services.AddSignalR (config => { });
services.AddSignalR(config => { });
services.AddCors (options => {
options.AddPolicy ("AllowAllOrigins",
services.AddCors(options => {
options.AddPolicy("AllowAllOrigins",
builder => builder
.AllowAnyOrigin ()
.AllowAnyMethod ()
.AllowAnyHeader ()
.AllowCredentials ());
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddTransient<IFileUploader, AzureFileUploader> ();
services.AddTransient<IRealTimeUpdater, SignalRUpdater> ();
services.AddScoped<IUnitOfWork, UnitOfWork> ();
services.AddScoped<IPodcastRepository, PodcastRepository> ();
services.AddScoped<IEntryRepository, EntryRepository> ();
services.AddScoped<IUserRepository, UserRepository> ();
services.AddScoped<IUrlProcessService, UrlProcessService> ();
services.AddScoped<IAudioUploadProcessService, AudioUploadProcessService> ();
services.AddTransient<IFileUploader, AzureFileUploader>();
services.AddTransient<IRealTimeUpdater, SignalRUpdater>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IPodcastRepository, PodcastRepository>();
services.AddScoped<IEntryRepository, EntryRepository>();
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUrlProcessService, UrlProcessService>();
services.AddScoped<IAudioUploadProcessService, AudioUploadProcessService>();
services.AddSingleton (typeof (HubLifetimeManager<DebugHub>),
typeof (DebugHubLifetimeManager<DebugHub>));
services.AddSingleton(typeof(HubLifetimeManager<DebugHub>),
typeof(DebugHubLifetimeManager<DebugHub>));
//register the codepages (required for slugify)
var instance = CodePagesEncodingProvider.Instance;
Encoding.RegisterProvider (instance);
Encoding.RegisterProvider(instance);
}
public void Configure (IApplicationBuilder app, IHostingEnvironment env,
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory, IServiceProvider serviceProvider) {
if (env.IsDevelopment ()) {
app.UseDeveloperExceptionPage ();
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
} else {
app.UseExceptionHandler ("/Home/Error");
app.UseExceptionHandler("/Home/Error");
}
Console.WriteLine ("Performing migrations");
Console.WriteLine("Performing migrations");
//TODO: Fix this when EF sucks less
// using (var context = new PodnomsDbContext(
// app.ApplicationServices.GetRequiredService<DbContextOptions<PodnomsDbContext>>()))
// {
// context.Database.Migrate();
// }
Console.WriteLine ("Successfully migrated");
Console.WriteLine("Successfully migrated");
// app.UseHsts();
// app.UseHttpsRedirection();
app.UseStaticFiles ();
app.UseStaticFiles();
GlobalConfiguration.Configuration.UseActivator (new ServiceProviderActivator (serviceProvider));
GlobalConfiguration.Configuration.UseActivator(new ServiceProviderActivator(serviceProvider));
if ((env.IsProduction () || true)) {
app.UseHangfireServer ();
app.UseHangfireDashboard ("/hangfire", new DashboardOptions {
Authorization = new [] { new HangFireAuthorizationFilter () }
if ((env.IsProduction() || true)) {
app.UseHangfireServer();
app.UseHangfireDashboard("/hangfire", new DashboardOptions {
Authorization = new[] { new HangFireAuthorizationFilter() }
});
}
app.UseCors ("AllowAllOrigins");
app.UseCors("AllowAllOrigins");
app.UseSignalR (routes => {
routes.MapHub<AudioProcessingHub> ("/hubs/audioprocessing");
routes.MapHub<DebugHub> ("/hubs/debug");
app.UseSignalR(routes => {
routes.MapHub<AudioProcessingHub>("/hubs/audioprocessing");
routes.MapHub<DebugHub>("/hubs/debug");
});
app.UseMvc (routes => {
routes.MapRoute (
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
//start hangfire jobs
//RecurringJob.AddOrUpdate<ClearOrphanAudioJob>(x => x.Execute(), Cron.Hourly);
JobBootstrapper.BootstrapJobs();
}
}
}

0
server/Utils/Crypt/HMACGenerator.cs Executable file → Normal file
View File

0
server/Utils/Crypt/MD5Generator.cs Executable file → Normal file
View File

0
server/Utils/DateUtils.cs Executable file → Normal file
View File

0
server/Utils/Extensions/DateTime.cs Executable file → Normal file
View File

0
server/Utils/Extensions/String.cs Executable file → Normal file
View File

0
server/Utils/HttpUtils.cs Executable file → Normal file
View File

0
server/Utils/HumanFriendlyInteger.cs Executable file → Normal file
View File

0
server/Utils/ImageUtils.cs Executable file → Normal file
View File

0
server/Utils/Randomisers.cs Executable file → Normal file
View File

0
server/Utils/ResourceReader.cs Executable file → Normal file
View File

View File

@@ -8,7 +8,7 @@
}
},
"App": {
"Version": "0.15.0"
"Version": "0.16.0"
},
"ConnectionStrings": {
"DefaultConnection": "server=localhost;database=PodNoms;user id=sa;password=cTXu1nJLCpC/c"

View File

@@ -3,3 +3,4 @@ unset DOCKER_HOST
unset DOCKER_TLS_VERIFY
docker build --rm -f Dockerfile -t fergalmoran/podnoms.api . && docker push fergalmoran/podnoms.api
docker push fergalmoran/podnoms.api

0
server/rebuild.sh Executable file → Normal file
View File

0
server/resetdb.sh Executable file → Normal file
View File