Basic re-auth framework in place

This commit is contained in:
Fergal Moran
2018-04-22 14:05:55 +01:00
parent 33a780a734
commit 6da5c22904
63 changed files with 2772 additions and 517 deletions

View File

@@ -1,78 +1,79 @@
{
"name": "pod-noms.web",
"version": "0.21.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --aot",
"build": "ng build",
"test": "ng test",
"lint": "ng lint"
},
"ngrxGen": {
"basePath": "./src/app",
"seperateDirectory": true
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.10",
"@angular/common": "^5.2.10",
"@angular/compiler": "^5.2.10",
"@angular/core": "^5.2.10",
"@angular/forms": "^5.2.10",
"@angular/http": "^5.2.10",
"@angular/platform-browser": "^5.2.10",
"@angular/platform-browser-dynamic": "^5.2.10",
"@angular/router": "^5.2.10",
"@aspnet/signalr": "1.0.0-rc1-30631",
"@ngrx/effects": "^5.1.0",
"@ngrx/store": "^5.1.0",
"@ngrx/store-devtools": "^5.1.0",
"@qontu/ngx-inline-editor": "^0.2.0-alpha.12",
"angular2-jwt": "^0.2.3",
"angular2-moment": "^1.8.0",
"angularfire2": "^5.0.0-rc.6",
"applicationinsights-js": "^1.0.15",
"auth0": "^2.9.1",
"auth0-lock": "^11.4.0",
"bootstrap": "4.1.0",
"core-js": "^2.5.3",
"dropzone": "^5.3.0",
"firebase": "^4.13.1",
"font-awesome": "^4.7.0",
"howler": "^2.0.9",
"jquery": "^3.3.1",
"lodash": "^4.17.5",
"ng2-toasty": "^4.0.3",
"ngx-bootstrap": "^2.0.4",
"ngx-clipboard": "^10.0.0",
"ngx-moment": "^2.0.0-rc.0",
"popper.js": "^1.13.0",
"rxjs": "5.5.6",
"simple-line-icons": "^2.4.1",
"tether": "^1.4.3",
"uglify-es": "^3.3.10",
"zone.js": "^0.8.20"
},
"devDependencies": {
"@angular/cli": "1.7.4",
"@angular/compiler-cli": "^5.2.6",
"@angular/language-service": "^5.2.6",
"@types/applicationinsights-js": "^1.0.5",
"@types/jasmine": "^2.8.6",
"@types/node": "~9.4.6",
"codelyzer": "^4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.4.1",
"karma-jasmine": "^1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"ts-node": "^5.0.1",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
"name": "pod-noms.web",
"version": "0.21.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --aot",
"build": "ng build",
"test": "ng test",
"lint": "ng lint"
},
"ngrxGen": {
"basePath": "./src/app",
"seperateDirectory": true
},
"private": true,
"dependencies": {
"@angular/animations": "^5.2.10",
"@angular/common": "^5.2.10",
"@angular/compiler": "^5.2.10",
"@angular/core": "^5.2.10",
"@angular/forms": "^5.2.10",
"@angular/http": "^5.2.10",
"@angular/platform-browser": "^5.2.10",
"@angular/platform-browser-dynamic": "^5.2.10",
"@angular/router": "^5.2.10",
"@aspnet/signalr": "1.0.0-rc1-30631",
"@ngrx/effects": "^5.1.0",
"@ngrx/store": "^5.1.0",
"@ngrx/store-devtools": "^5.1.0",
"@qontu/ngx-inline-editor": "^0.2.0-alpha.12",
"angular2-jwt": "^0.2.3",
"angular2-moment": "^1.8.0",
"angularfire2": "^5.0.0-rc.6",
"angularx-social-login": "^1.1.8",
"applicationinsights-js": "^1.0.15",
"auth0": "^2.9.1",
"auth0-lock": "^11.4.0",
"bootstrap": "4.1.0",
"core-js": "^2.5.3",
"dropzone": "^5.3.0",
"firebase": "^4.13.1",
"font-awesome": "^4.7.0",
"howler": "^2.0.9",
"jquery": "^3.3.1",
"lodash": "^4.17.5",
"ng2-toasty": "^4.0.3",
"ngx-bootstrap": "^2.0.4",
"ngx-clipboard": "^10.0.0",
"ngx-moment": "^2.0.0-rc.0",
"popper.js": "^1.13.0",
"rxjs": "5.5.6",
"simple-line-icons": "^2.4.1",
"tether": "^1.4.3",
"uglify-es": "^3.3.10",
"zone.js": "^0.8.20"
},
"devDependencies": {
"@angular/cli": "1.7.4",
"@angular/compiler-cli": "^5.2.6",
"@angular/language-service": "^5.2.6",
"@types/applicationinsights-js": "^1.0.5",
"@types/jasmine": "^2.8.6",
"@types/node": "~9.4.6",
"codelyzer": "^4.2.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.4.1",
"karma-jasmine": "^1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.3.0",
"ts-node": "^5.0.1",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
}

View File

@@ -2,7 +2,7 @@ import { GlobalsService } from './services/globals.service';
import { Component, HostBinding, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { ToastyService } from 'ng2-toasty';
import { AuthService } from 'app/services/auth.service';
import { PodnomsAuthService } from 'app/services/podnoms-auth.service';
import { AppInsightsService } from 'app/services/app-insights.service';
import { SignalRService } from 'app/services/signalr.service';
import { ProfileService } from './services/profile.service';
@@ -15,15 +15,13 @@ import { MessagingService } from 'app/services/messaging.service';
})
export class AppComponent implements OnInit {
constructor(
private _authService: AuthService,
private _authService: PodnomsAuthService,
private _toastyService: ToastyService,
private _signalrService: SignalRService,
private _profileService: ProfileService,
private _messagingService: MessagingService,
_appInsights: AppInsightsService
) {
_authService.handleAuthentication();
_authService.scheduleRenewal();
}
loggedIn() {
return this._authService.isAuthenticated();

View File

@@ -12,10 +12,14 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFireModule } from 'angularfire2';
import { SocialLoginModule, AuthServiceConfig } from 'angularx-social-login';
import {
GoogleLoginProvider,
FacebookLoginProvider
} from 'angularx-social-login';
import { ModalModule } from 'ngx-bootstrap/modal';
import { AuthGuard } from './services/auth.guard';
import { AuthConfig, AuthHttp } from 'angular2-jwt';
import { ImageService } from './services/image.service';
import { DebugService } from './services/debug.service';
import { ChatterService } from './services/chatter.service';
@@ -31,7 +35,7 @@ import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';
import { LoginComponent } from './components/login/login.component';
import { NavbarComponent } from './components/navbar/navbar.component';
import { AuthService } from './services/auth.service';
import { PodnomsAuthService } from './services/podnoms-auth.service';
import { ProfileService } from './services/profile.service';
import { MomentModule } from 'angular2-moment';
import { FilterEntryPipe } from './pipes/filter-entry.pipe';
@@ -61,17 +65,21 @@ import { environment } from 'environments/environment';
import { FooterPlayerComponent } from 'app/components/footer-player/footer-player.component';
import { AudioService } from 'app/services/audio.service';
import { HumaniseTimePipe } from './pipes/humanise-time.pipe';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { PodNomsApiInterceptor } from './interceptors/podnoms-api.interceptor';
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(
new AuthConfig({
noClientCheck: true,
globalHeaders: [{ 'Content-Type': 'application/json' }],
tokenGetter: () => localStorage.getItem('id_token')
}),
http,
options
);
let config = new AuthServiceConfig([
{
id: GoogleLoginProvider.PROVIDER_ID,
provider: new GoogleLoginProvider('Google-OAuth-Client-Id')
},
{
id: FacebookLoginProvider.PROVIDER_ID,
provider: new FacebookLoginProvider('117715354940616')
}
]);
export function provideConfig() {
return config;
}
@NgModule({
@@ -112,7 +120,7 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
}),
AngularFireDatabaseModule,
AngularFireAuthModule,
HttpClientModule,
AppRoutingModule,
HttpModule,
FormsModule,
@@ -123,6 +131,7 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
ToastyModule.forRoot(),
DropzoneModule,
ClipboardModule,
SocialLoginModule,
StoreModule.forRoot(reducers),
@@ -136,12 +145,16 @@ export function authHttpServiceFactory(http: Http, options: RequestOptions) {
})
],
providers: [
AuthService,
PodnomsAuthService,
AuthGuard,
{
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
provide: HTTP_INTERCEPTORS,
useClass: PodNomsApiInterceptor,
multi: true
},
{
provide: AuthServiceConfig,
useFactory: provideConfig
},
SignalRService,
ProfileService,

View File

@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'app/services/auth.service';
import { PodnomsAuthService } from 'app/services/podnoms-auth.service';
import { Router } from '@angular/router';
@Component({
@@ -8,7 +8,7 @@ import { Router } from '@angular/router';
styleUrls: ['./callback.component.css']
})
export class CallbackComponent implements OnInit {
constructor(private _authService: AuthService, private _router: Router) {}
constructor(private _authService: PodnomsAuthService, private _router: Router) {}
ngOnInit() {
this._router.navigate(['/podcasts']);

View File

@@ -1,4 +1,4 @@
import { AuthService } from 'app/services/auth.service';
import { PodnomsAuthService } from 'app/services/podnoms-auth.service';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@@ -8,7 +8,7 @@ import { Router } from '@angular/router';
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor(private _router: Router, private _authService: AuthService) {}
constructor(private _router: Router, private _authService: PodnomsAuthService) {}
ngOnInit() {
if (this._authService.isAuthenticated) {

View File

@@ -0,0 +1,3 @@
.new-user-alert {
padding-top: 2.5rem;
}

View File

@@ -1,8 +1,6 @@
<div id="page-container"
class="main-content-boxed">
<div id="page-container" class="main-content-boxed">
<main id="main-container">
<div class="bg-image"
style="background-image: url('/assets/img/robothand.jpg'); background-size: 100% 100%;">
<div class="bg-image" style="background-image: url('/assets/img/robothand.jpg'); background-size: 100% 100%;">
<div class="hero-static content content-full bg-white">
<div class="text-center">
<a class="link-effect font-w700">
@@ -15,37 +13,26 @@
<div class="row justify-content-center">
<div class="row">
<div class="col-sm-4 mb-5">
<button class="btn btn-facebook"
(click)="login('facebook')"
style="width: 100%"><i class="fa fa-facebook"></i>&nbsp;Facebook</button>
<button class="btn btn-facebook" (click)="login('facebook')" style="width: 100%">
<i class="fa fa-facebook"></i>&nbsp;Facebook</button>
</div>
<div class="col-sm-4 mb-5">
<button class="btn btn-google-plus"
(click)="login('google-oauth2')"
style="width: 100%"><i class="fa fa-google"></i>&nbsp;Google</button>
<button class="btn btn-google-plus" (click)="login('google-oauth2')" style="width: 100%">
<i class="fa fa-google"></i>&nbsp;Google</button>
</div>
<div class="col-sm-4 mb-5">
<button class="btn btn-twitter"
(click)="login('twitter')"
style="width: 100%"><i class="fa fa-twitter"></i>&nbsp;Twitter</button>
<button class="btn btn-twitter" (click)="login('twitter')" style="width: 100%">
<i class="fa fa-twitter"></i>&nbsp;Twitter</button>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-sm-8 col-md-6 col-xl-4">
<form class="js-validation-signin"
action=""
(ngSubmit)="login()"
method="post">
<form class="js-validation-signin" action="" (ngSubmit)="login()" method="post">
<div class="form-group row">
<div class="col-12">
<div class="form-material floating">
<input type="text"
autocomplete="username"
class="form-control"
id="login-username"
name="login-username"
[(ngModel)]="username">
<input type="text" autocomplete="username" class="form-control" id="login-username" name="login-username" [(ngModel)]="username">
<label for="login-username">Email Address</label>
</div>
</div>
@@ -53,37 +40,32 @@
<div class="form-group row">
<div class="col-12">
<div class="form-material floating">
<input type="password"
autocomplete="current-password"
class="form-control"
id="login-password"
name="login-password"
[(ngModel)]="password">
<input type="password" autocomplete="current-password" class="form-control" id="login-password" name="login-password" [(ngModel)]="password">
<label for="login-password">Password</label>
</div>
</div>
</div>
<div class="alert alert-danger"
*ngIf="errorMessage"
role="alert">
<div class="alert alert-danger" *ngIf="errorMessage" role="alert">
{{errorMessage}}
</div>
<div class="col-md-12 new-user-alert" *ngIf="brandNew">
<div class="alert alert-success" role="alert">
<strong>Account created!</strong> Please login with your new details
</div>
</div>
<div class="form-group row gutters-tiny">
<div class="col-12 mb-10">
<button type="submit"
class="btn btn-block btn-hero btn-noborder btn-rounded btn-alt-primary">
<button type="submit" class="btn btn-block btn-hero btn-noborder btn-rounded btn-alt-primary">
<i class="icon-login mr-10"></i> Sign In
</button>
</div>
<div class="col-sm-6 mb-5">
<a class="btn btn-block btn-noborder btn-rounded btn-alt-secondary"
[routerLink]="['/register']">
<a class="btn btn-block btn-noborder btn-rounded btn-alt-secondary" [routerLink]="['/register']">
<i class="fa fa-plus text-muted mr-5"></i> New Account
</a>
</div>
<div class="col-sm-6 mb-5">
<a class="btn btn-block btn-noborder btn-rounded btn-alt-secondary"
[routerLink]="['/reset']">
<a class="btn btn-block btn-noborder btn-rounded btn-alt-secondary" [routerLink]="['/reset']">
<i class="fa fa-warning text-muted mr-5"></i> Forgot password
</a>
</div>
@@ -94,4 +76,4 @@
</div>
</div>
</main>
</div>
</div>

View File

@@ -1,32 +1,65 @@
import { AuthService } from './../../services/auth.service';
import { PodnomsAuthService } from './../../services/podnoms-auth.service';
import { AuthService } from 'angularx-social-login';
import {
FacebookLoginProvider,
GoogleLoginProvider,
LinkedInLoginProvider
} from 'angularx-social-login';
import { Component, NgZone, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
@Component({
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
private _authWindow: Window;
private _subscription: Subscription;
brandNew: boolean = false;
user: any;
username: string;
password: string;
isRequesting: boolean = false;
signIn;
widget;
errorMessage: string = '';
constructor(private _authService: AuthService) {}
constructor(
private _authService: PodnomsAuthService,
private _socialAuthService: AuthService,
private _activatedRoute: ActivatedRoute,
private _router: Router
) {}
ngOnInit() {}
ngOnInit() {
this._subscription = this._activatedRoute.queryParams.subscribe(
(param: any) => {
this.brandNew = param['brandNew'];
this.username = param['email'];
}
);
}
login(provider?: string) {
if (!provider) {
this._authService.loginUsername(
this.username,
this.password,
success => this.loginSuccess(success),
error => this.loginError(error)
);
this.isRequesting = true;
if (provider === 'facebook') {
this._socialAuthService.signIn(FacebookLoginProvider.PROVIDER_ID);
} else {
this._authService.loginSocial(provider);
this._authService
.login(this.username, this.password)
.finally(() => (this.isRequesting = false))
.subscribe((result) => {
if (result) {
this._router.navigate(['/']);
}
}, (error) => (this.errorMessage = error));
}
this._socialAuthService.authState.subscribe((user) => {
this.user = user;
});
}
logout() {}
loginSuccess(data) {

View File

@@ -1,6 +1,6 @@
import { ProfileModel } from 'app/models/profile.model';
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { PodnomsAuthService } from '../../services/podnoms-auth.service';
import { ProfileService } from '../../services/profile.service';
import { Observable } from 'rxjs/Observable';
@@ -13,7 +13,7 @@ export class NavbarComponent implements OnInit {
user$: Observable<ProfileModel>;
constructor(
private _authService: AuthService,
private _authService: PodnomsAuthService,
private _profileService: ProfileService
) {}

View File

@@ -40,16 +40,16 @@ export class PodcastAddUrlFormComponent implements AfterViewInit {
this.isPosting = true;
const entry = new PodcastEntryModel(this.podcast.id, urlToCheck);
this._service.addEntry(entry).subscribe(
e => {
(e) => {
if (e) {
if (e.processingStatus == 6) {
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;

View File

@@ -11,7 +11,7 @@ import {
ViewChild
} from '@angular/core';
import { AuthService } from 'app/services/auth.service';
import { PodnomsAuthService } from 'app/services/podnoms-auth.service';
import { PodcastModel } from 'app/models/podcasts.models';
import { environment } from 'environments/environment';
@@ -29,7 +29,7 @@ export class PodcastUploadFormComponent implements OnInit {
constructor(
private _toastyService: ToastyService,
private _auth: AuthService
private _auth: PodnomsAuthService
) {}
ngOnInit() {
const config = {

View File

@@ -25,7 +25,7 @@ export class PodcastComponent {
selectedPodcast$: Observable<PodcastModel>;
pendingEntry: PodcastEntryModel = null;
entries$: Observable<PodcastEntryModel[]>;
uploadMode = true;
uploadMode = false;
urlMode = false;
firstRun = true;

View File

@@ -73,4 +73,4 @@
</div>
</div>
</main>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import { Observable } from 'rxjs/Observable';
import { AuthService } from './../../services/auth.service';
import { PodnomsAuthService } from './../../services/podnoms-auth.service';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-register',
@@ -12,21 +13,23 @@ export class RegisterComponent implements OnInit {
password: string;
passwordRepeat: string;
sending = false;
_isRequesting: boolean = false;
errorMessage: string;
constructor(private _authService: AuthService) {}
constructor(private _authService: PodnomsAuthService, private _router: Router) {}
ngOnInit() {}
doRegister() {
this._isRequesting = true;
this._authService
.signup(this.username, this.password)
.catch(err => {
if ((err.code = 'user_exists')) this.errorMessage = 'A user with this email address already exists';
else this.errorMessage = err.description;
return Observable.of(`Error logging in: ${err.description}`);
})
.subscribe(r => console.log('Done'));
.finally(() => (this._isRequesting = false))
.subscribe((result) => {
if (result) {
this._router.navigate(['/login'], {
queryParams: { brandNew: true, email: this.username }
});
}
}, (errors) => (this.errorMessage = errors));
}
}

View File

@@ -1,4 +1,4 @@
import { AuthService } from './../../services/auth.service';
import { PodnomsAuthService } from './../../services/podnoms-auth.service';
import { Component, OnInit } from '@angular/core';
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';
@@ -12,22 +12,22 @@ export class ResetComponent implements OnInit {
username: string;
errorMessage: string;
successMessage: string;
constructor(private _authService: AuthService) {}
constructor(private _authService: PodnomsAuthService) {}
ngOnInit() {}
resetPassword() {
if (this.username) {
this._authService
.resetPassword(this.username)
.catch(err => {
this.errorMessage = err.description;
return Observable.of(`Error resetting password: ${err.description}`);
})
.subscribe(result => {
console.log('reset.component.ts', 'method', result);
this.errorMessage = '';
this.successMessage = `A password reset link has been sent to ${this.username}`;
});
.resetPassword(this.username);
// .catch(err => {
// this.errorMessage = err.description;
// return Observable.of(`Error resetting password: ${err.description}`);
// })
// .subscribe(result => {
// console.log('reset.component.ts', 'method', result);
// this.errorMessage = '';
// this.successMessage = `A password reset link has been sent to ${this.username}`;
// });
} else {
this.errorMessage = 'Please enter your email address';
}

View File

@@ -2,11 +2,11 @@ import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { tokenNotExpired } from 'angular2-jwt';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
import { PodnomsAuthService } from './podnoms-auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private _auth: AuthService) {}
constructor(private _auth: PodnomsAuthService) {}
canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
return new Promise(resolve => {
if (this._auth.isAuthenticated()) {

View File

@@ -1,157 +0,0 @@
import { environment } from 'environments/environment';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { AUTH_CONFIG } from './../constants/auth0';
import * as auth0 from 'auth0-js';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/filter';
@Injectable()
export class AuthService {
errorMessage: string;
refreshSubscription: any;
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.AUTH0_DOMAIN,
clientID: AUTH_CONFIG.AUTH0_CLIENT_ID,
redirectUri: AUTH_CONFIG.AUTH0_CALLBACKURL,
audience: `https://${AUTH_CONFIG.AUTH0_DOMAIN}/userinfo`,
responseType: 'token id_token',
prompt: 'select_account',
scope: 'openid profile email'
});
constructor(private _router: Router) {}
public loginUsername(username: string, password: string, success, error): void {
this.auth0.client.login(
{
realm: 'podnoms-db-connection',
username: username,
password: password
},
(err, authResult) => {
if (err) {
error(err);
console.log(err);
return;
} else if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
success(authResult);
}
}
);
}
public signup(email: string, password: string): Observable<any> {
return Observable.create(observer => {
this.auth0.redirect.signupAndLogin(
{
connection: 'podnoms-db-connection',
email,
password
},
err => {
if (err) {
observer.error(err);
} else observer.next();
}
);
});
}
public resetPassword(email: string): Observable<any> {
return Observable.create(observer => {
this.auth0.changePassword(
{
connection: 'podnoms-db-connection',
email
},
(err, resp) => {
if (err) {
console.error(err);
Observable.throw(err);
} else {
observer.next('success');
}
}
);
});
}
public loginSocial(provider: string): void {
this.auth0.authorize({
connection: provider
});
}
public handleAuthentication(): void {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
this._router.navigate(['/']).then(r => window.location.reload()); // TODO: Remove this for the love of baby Jesus!
console.log(err);
}
});
}
public getToken(): string {
if (this.isAuthenticated()) return localStorage.getItem('id_token');
return '';
}
public renewToken() {
this.auth0.renewAuth(
{
audience: 'https://podnoms/',
redirectUri: `${environment.API_HOST}/silent`,
usePostMessage: true,
postMessageOrigin: environment.BASE_URL
},
(err, result) => {
if (err) {
console.log(err);
} else {
this.setSession(result);
}
}
);
}
public scheduleRenewal() {
if (!this.isAuthenticated()) return;
this.unscheduleRenewal();
const expiresAt = JSON.parse(window.localStorage.getItem('expires_at'));
const source = Observable.of(expiresAt).flatMap(e => {
const now = Date.now();
return Observable.timer(Math.max(1, e - now));
});
this.refreshSubscription = source.subscribe(() => {
console.log('auth.service.ts', 'scheduleRenewal', 'Starting renewal and schedule');
this.renewToken();
this.scheduleRenewal();
});
}
public unscheduleRenewal() {
if (!this.refreshSubscription) return;
this.refreshSubscription.unsubscribe();
}
private setSession(authResult): void {
const expiresAt = JSON.stringify(authResult.expiresIn * 1000 + new Date().getTime());
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
this.scheduleRenewal();
this._router.navigate(['/']);
}
public logout(): void {
localStorage.removeItem('access_token');
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
this._router.navigate(['/']);
window.location.reload();
}
public isAuthenticated(): boolean {
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return new Date().getTime() < expiresAt;
}
}

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
import { AuthHttp } from 'angular2-jwt';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ChatterService {
constructor(private _http: AuthHttp) {}
constructor(private _http: HttpClient) {}
ping(message: string): any {
return this._http.post(

View File

@@ -1,24 +1,29 @@
import { environment } from 'environments/environment';
import { Observable } from 'rxjs/Observable';
import { AuthHttp } from 'angular2-jwt';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class DebugService {
constructor(private _http: AuthHttp) {}
constructor(private _http: HttpClient) {}
sendRealtime(message: string): any {
return this._http.post(environment.API_HOST + '/debug/realtime', JSON.stringify(message));
return this._http.post(
environment.API_HOST + '/debug/realtime',
JSON.stringify(message)
);
}
getDebugInfo(): Observable<string> {
return this._http.get(environment.API_HOST + '/debug').map(r => r.json());
return this._http.get<string>(environment.API_HOST + '/debug');
}
ping(): Observable<string> {
return this._http.get(environment.API_HOST + '/ping').map(r => r.text());
return this._http.get<string>(environment.API_HOST + '/ping');
}
sendPush(): Observable<string>{
return this._http.get(environment.API_HOST + '/debug/serverpush').map(r => r.text());
sendPush(): Observable<string> {
return this._http.get<string>(
environment.API_HOST + '/debug/serverpush'
);
}
}

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class EntriesService {
constructor(private http: Http) {}
constructor(private http: HttpClient) {}
get(): Observable<any> {
return this.http.get('https://api.com');

View File

@@ -1,13 +1,12 @@
import {Http, Headers} from '@angular/http';
import {Injectable} from '@angular/core';
import {AuthService} from './auth.service';
import {PodnomsAuthService} from './podnoms-auth.service';
import { environment } from 'environments/environment';
@Injectable()
export class ImageService {
// TODO: Change this to use AuthHttp when I can figure out why formData is null
constructor(private _http: Http, private _auth: AuthService) {
constructor(private _http: Http, private _auth: PodnomsAuthService) {
}
upload(podcastSlug: string, image) {

View File

@@ -1,21 +1,26 @@
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';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class JobsService {
constructor(private _http: AuthHttp) { }
constructor(private _http: HttpClient) {}
processOrphans(): Observable<Response> {
return this._http.get(environment.API_HOST + '/job/processorphans');
return this._http.get<Response>(
environment.API_HOST + '/job/processorphans'
);
}
processPlaylists(): Observable<Response> {
return this._http.get(environment.API_HOST + '/job/processplaylists');
return this._http.get<Response>(
environment.API_HOST + '/job/processplaylists'
);
}
updateYouTubeDl(): Observable<Response> {
return this._http.get(environment.API_HOST + '/job/updateyoutubedl');
return this._http.get<Response>(
environment.API_HOST + '/job/updateyoutubedl'
);
}
}

View File

@@ -1,9 +1,9 @@
import { environment } from 'environments/environment';
import { PodcastEntryModel } from 'app/models/podcasts.models';
import { PodcastModel } from './../models/podcasts.models';
import { AuthHttp } from 'angular2-jwt';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class PodcastService {
@@ -13,24 +13,25 @@ export class PodcastService {
}
return value;
}
constructor(private _http: AuthHttp) { }
constructor(private _http: HttpClient) {}
//#region Podcasts
get(): Observable<PodcastModel[]> {
return this._http
.get(environment.API_HOST + '/podcast/')
.map(res => res.json());
return this._http.get<PodcastModel[]>(
environment.API_HOST + '/podcast/'
);
}
getPodcast(slug: string): Observable<PodcastModel> {
return this._http
.get(environment.API_HOST + '/podcast/' + slug)
.map(res => res.json());
return this._http.get<PodcastModel>(
environment.API_HOST + '/podcast/' + slug
);
}
addPodcast(podcast: PodcastModel): Observable<PodcastModel> {
console.log('PodcastService', 'addPodcast', podcast);
const data = JSON.stringify(podcast, PodcastService._replacer);
return this._http
.post(environment.API_HOST + '/podcast', data)
.map(res => res.json());
return this._http.post<PodcastModel>(
environment.API_HOST + '/podcast',
data
);
}
updatePodcast(podcast: PodcastModel) {
return this._http.put(environment.API_HOST + '/podcast/', podcast);
@@ -41,43 +42,48 @@ export class PodcastService {
//#endregion
//#region Entries
getEntries(slug: string): any {
return this._http
.get(environment.API_HOST + '/entry/all/' + slug)
.map(res => res.json());
return this._http.get(environment.API_HOST + '/entry/all/' + slug);
}
addEntry(entry: PodcastEntryModel) {
return this._http
.post(environment.API_HOST + '/entry', JSON.stringify(entry))
.map(res => res.json());
addEntry(entry: PodcastEntryModel): Observable<PodcastEntryModel> {
return this._http.post<PodcastEntryModel>(
environment.API_HOST + '/entry',
JSON.stringify(entry)
);
}
updateEntry(entry: PodcastEntryModel) {
return this._http
.post(environment.API_HOST + '/entry', JSON.stringify(entry))
.map(res => res.json());
return this._http.post<PodcastEntryModel>(
environment.API_HOST + '/entry',
JSON.stringify(entry)
);
}
deleteEntry(id: number) {
return this._http.delete(environment.API_HOST + '/entry/' + id);
}
checkEntry(url: string): Observable<boolean> {
return this._http
.post(environment.API_HOST + '/entry/isvalid/', `"${url}"`)
.map(r => (r.status == 200 ? true : false))
.post<Response>(
environment.API_HOST + '/entry/isvalid/',
`"${url}"`
)
.map((r) => (r.status == 200 ? true : false))
.catch((error: any) => {
return Observable.throw(new Error(error.status));
});
}
reSubmitEntry(entry: PodcastEntryModel): Observable<PodcastEntryModel> {
return this._http
.post(environment.API_HOST + '/entry/resubmit', entry)
.map(res => res.json());
return this._http.post<PodcastEntryModel>(
environment.API_HOST + '/entry/resubmit',
entry
);
}
//#endregion
//#region Playlists
addPlaylist(entry: PodcastEntryModel) {
return this._http
.post(environment.API_HOST + '/playlist', JSON.stringify(entry))
.map(res => res.json());
return this._http.post<PodcastEntryModel>(
environment.API_HOST + '/playlist',
JSON.stringify(entry)
);
}
//#endregion
}

View File

@@ -2,21 +2,21 @@ import { environment } from 'environments/environment';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ProfileModel } from 'app/models/profile.model';
import { AuthHttp } from 'angular2-jwt';
import 'rxjs/add/operator/map';
import { Profile } from 'selenium-webdriver/firefox';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ProfileService {
constructor(private _http: AuthHttp) {}
profile: ProfileModel;
constructor(private _http: HttpClient) {}
getProfile(): Observable<ProfileModel> {
if (!this.profile) {
return this._http
.get(environment.API_HOST + '/profile')
.map(res => {
this.profile = res.json();
.get<ProfileModel>(environment.API_HOST + '/profile')
.map((res) => {
this.profile = res;
return this.profile;
});
} else {
@@ -26,20 +26,22 @@ export class ProfileService {
updateProfile(profile): Observable<ProfileModel> {
console.log('ProfileService', 'updateProfile', profile);
return this._http
.post(environment.API_HOST + '/profile', profile)
.map(res => res.json());
return this._http.post<ProfileModel>(
environment.API_HOST + '/profile',
profile
);
}
checkSlug(slug): Observable<string> {
console.log('profile.service.ts', 'checkSlug', slug);
return this._http
.get(environment.API_HOST + '/profile/checkslug/' + slug)
.map(res => res.text());
return this._http.get<string>(
environment.API_HOST + '/profile/checkslug/' + slug
);
}
regenerateApiKey(): Observable<string> {
return this._http
.post(environment.API_HOST + '/profile/updateapikey', null)
.map(res => res.text());
return this._http.post<string>(
environment.API_HOST + '/profile/updateapikey',
null
);
}
}

View File

@@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { AuthHttp } from 'angular2-jwt';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
@@ -7,11 +6,12 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import { environment } from 'environments/environment';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class PushRegistrationService {
private API_URL: string;
constructor(private http: AuthHttp) {
constructor(private _http: HttpClient) {
this.API_URL = environment.API_HOST;
}
@@ -30,7 +30,7 @@ export class PushRegistrationService {
addSubscriber(subscription) {
const url = `${this.API_URL}/webpush/subscribe`;
return this.http.post(url, subscription).catch(this.handleError);
return this._http.post(url, subscription).catch(this.handleError);
}
deleteSubscriber(subscription) {
@@ -40,7 +40,9 @@ export class PushRegistrationService {
subscription: subscription
};
return this.http.post(url, body).catch(this.handleError);
return this._http
.post(url, JSON.stringify(body))
.catch(this.handleError);
}
private handleError(error: Response | any) {

View File

@@ -1,4 +1,4 @@
import { AuthService } from './auth.service';
import { PodnomsAuthService } from './podnoms-auth.service';
import { Injectable } from '@angular/core';
import {
HubConnection,
@@ -12,7 +12,7 @@ import { environment } from 'environments/environment';
export class SignalRService {
public connection: HubConnection;
constructor(private _auth: AuthService) {}
constructor(private _auth: PodnomsAuthService) {}
public init(hub: string): Promise<void> {
const url = `${environment.SIGNALR_HOST}/hubs/${hub}`;

View File

@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PodNoms.Api.Models.ViewModels;
using PodNoms.Api.Persistence;
using PodNoms.Api.Services.Auth;
namespace PodNoms.Api.Controllers {
[Route("[controller]")]
public class AccountsController : Controller {
private readonly IUserRepository _userRepository;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IMapper _mapper;
public AccountsController(IUserRepository userRepository, UserManager<ApplicationUser> userManager, IMapper mapper) {
this._userRepository = userRepository;
this._userManager = userManager;
this._mapper = mapper;
}
// POST api/accounts
[HttpPost]
public async Task<IActionResult> Post([FromBody]RegistrationViewModel model) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<RegistrationViewModel, ApplicationUser>(model);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
// var result = await _userRepository.AddOrUpdate(userIdentity, model.Password);
if (!result.Succeeded) return new BadRequestObjectResult(result);
return new OkObjectResult(model);
}
}
}

View File

@@ -1,31 +1,60 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
using PodNoms.Api.Models.ViewModels;
using PodNoms.Api.Services.Auth;
using PodNoms.Api.Utils;
namespace PodNoms.Api.Controllers {
[Authorize]
[Route("[controller]")]
public class AuthController : Controller {
protected IUserRepository _userRepository { get; }
private readonly UserManager<ApplicationUser> _userManager;
private readonly IJwtFactory _jwtFactory;
private readonly JwtIssuerOptions _jwtOptions;
public AuthController(IUserRepository repository) {
this._userRepository = repository;
public AuthController(UserManager<ApplicationUser> userManager, IJwtFactory jwtFactory, IOptions<JwtIssuerOptions> jwtOptions) {
_userManager = userManager;
_jwtFactory = jwtFactory;
_jwtOptions = jwtOptions.Value;
}
protected async Task<User> GetUserAsync() {
var identifier = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = await this._userRepository.GetAsync(identifier);
return user;
// POST api/auth/login
[HttpPost("login")]
public async Task<IActionResult> Post([FromBody]CredentialsViewModel credentials) {
if (!ModelState.IsValid) {
return BadRequest(ModelState);
}
var identity = await GetClaimsIdentity(credentials.UserName, credentials.Password);
if (identity == null) {
return BadRequest(Errors.AddErrorToModelState("login_failure", "Invalid username or password.", ModelState));
}
var jwt = await Tokens.GenerateJwt(identity, _jwtFactory, credentials.UserName, _jwtOptions,
new JsonSerializerSettings { Formatting = Formatting.Indented });
return new OkObjectResult(jwt);
}
protected async Task<string> GetUserUidAsync() {
var user = await GetUserAsync();
return user.Uid;
}
protected async Task<int> GetUserIdAsync() {
var user = await GetUserAsync();
return user.Id;
private async Task<ClaimsIdentity> GetClaimsIdentity(string userName, string password) {
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
return await Task.FromResult<ClaimsIdentity>(null);
// get the user to verifty
var userToVerify = await _userManager.FindByNameAsync(userName);
if (userToVerify == null) return await Task.FromResult<ClaimsIdentity>(null);
// check the credentials
if (await _userManager.CheckPasswordAsync(userToVerify, password)) {
return await Task.FromResult(_jwtFactory.GenerateClaimsIdentity(userName, userToVerify.Id));
}
// Credentials are invalid, or account doesn't exist
return await Task.FromResult<ClaimsIdentity>(null);
}
}
}
}

View File

@@ -18,7 +18,7 @@ using WebPush = Lib.Net.Http.WebPush;
namespace PodNoms.Api.Controllers {
[Route("[controller]")]
public class DebugController : AuthController {
public class DebugController : UserController {
private readonly StorageSettings _storageSettings;
private readonly AudioFileStorageSettings _audioFileStorageSettings;
private readonly ApplicationsSettings _applicationsSettings;

View File

@@ -19,7 +19,7 @@ using PodNoms.Api.Services.Storage;
namespace PodNoms.Api.Controllers {
[Route("[controller]")]
public class EntryController : AuthController {
public class EntryController : UserController {
private readonly IPodcastRepository _podcastRepository;
private readonly IEntryRepository _repository;
private readonly IUnitOfWork _unitOfWork;

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using AutoMapper;
using Hangfire;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
@@ -24,10 +25,12 @@ namespace PodNoms.Api.Controllers {
private readonly IUserRepository _userRepository;
private readonly IOptions<AppSettings> _settings;
private readonly IMapper _mapper;
private ClaimsPrincipal _caller;
private readonly IUnitOfWork _uow;
public PodcastController(IPodcastRepository repository, IUserRepository userRepository,
IOptions<AppSettings> options, IMapper mapper, IUnitOfWork unitOfWork) {
IOptions<AppSettings> options, IMapper mapper, IUnitOfWork unitOfWork, IHttpContextAccessor httpContextAccessor) {
_caller = httpContextAccessor.HttpContext.User;
this._uow = unitOfWork;
this._repository = repository;
this._userRepository = userRepository;
@@ -37,12 +40,10 @@ namespace PodNoms.Api.Controllers {
[HttpGet]
public async Task<IEnumerable<PodcastViewModel>> Get() {
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
if (!string.IsNullOrEmpty(email)) {
var podcasts = await _repository.GetAllAsync(email);
var ret = _mapper.Map<List<Podcast>, List<PodcastViewModel>>(podcasts.ToList());
return ret;
}
var userId = _caller.Claims.Single(c => c.Type == "id");
var podcasts = await _repository.GetAllAsync(userId.Value);
var ret = _mapper.Map<List<Podcast>, List<PodcastViewModel>>(podcasts.ToList());
return ret;
throw new Exception("No local user stored!");
}
@@ -60,9 +61,9 @@ namespace PodNoms.Api.Controllers {
[HttpPost]
public async Task<IActionResult> Post([FromBody] PodcastViewModel vm) {
var email = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = _userRepository.Get(email);
if (string.IsNullOrEmpty(email) || user == null)
var userId = _caller.Claims.Single(c => c.Type == "id");
var user = _userRepository.Get(userId.Value);
if (user == null)
return new BadRequestObjectResult("Unable to look up user profile");
if (ModelState.IsValid) {

View File

@@ -0,0 +1,33 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PodNoms.Api.Models;
using PodNoms.Api.Persistence;
namespace PodNoms.Api.Controllers {
[Authorize]
[Obsolete("This should be superceded by the new identity stuff")]
public class UserController : Controller {
protected IUserRepository _userRepository { get; }
public UserController(IUserRepository repository) {
this._userRepository = repository;
}
protected async Task<User> GetUserAsync() {
var identifier = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
var user = await this._userRepository.GetAsync(identifier);
return user;
}
protected async Task<string> GetUserUidAsync() {
var user = await GetUserAsync();
return user.Uid;
}
protected async Task<int> GetUserIdAsync() {
var user = await GetUserAsync();
return user.Id;
}
}
}

View File

@@ -10,7 +10,7 @@ namespace PodNoms.Api.Controllers {
// [Authorize]
[Route("[controller]")]
public class WebPushController : AuthController {
public class WebPushController : UserController {
private readonly IPushSubscriptionStore _subscriptionStore;
public readonly IPushNotificationService _notificationService;

View File

@@ -0,0 +1,194 @@
// <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 PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180421161830_AddedAuthTables")]
partial class AddedAuthTables
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview2-30571")
.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,19 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class AddedAuthTables : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -0,0 +1,406 @@
// <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 PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180421162833_ChangeContextType")]
partial class ChangeContextType
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview2-30571")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
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.Services.Auth.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<long?>("FacebookId");
b.Property<string>("FirstName");
b.Property<string>("LastName");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("PictureUrl");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
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,225 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class ChangeContextType : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false),
FirstName = table.Column<string>(nullable: true),
LastName = table.Column<string>(nullable: true),
FacebookId = table.Column<long>(nullable: true),
PictureUrl = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(nullable: false),
ProviderKey = table.Column<string>(nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(nullable: false),
Name = table.Column<string>(nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@@ -0,0 +1,406 @@
// <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 PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180421215724_RenameUserTable")]
partial class RenameUserTable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview2-30571")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
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("UserDetails");
});
modelBuilder.Entity("PodNoms.Api.Services.Auth.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<long?>("FacebookId");
b.Property<string>("FirstName");
b.Property<string>("LastName");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("PictureUrl");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
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,75 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class RenameUserTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Podcasts_Users_UserId",
table: "Podcasts");
migrationBuilder.DropPrimaryKey(
name: "PK_Users",
table: "Users");
migrationBuilder.RenameTable(
name: "Users",
newName: "UserDetails");
migrationBuilder.RenameIndex(
name: "IX_Users_Slug",
table: "UserDetails",
newName: "IX_UserDetails_Slug");
migrationBuilder.AddPrimaryKey(
name: "PK_UserDetails",
table: "UserDetails",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Podcasts_UserDetails_UserId",
table: "Podcasts",
column: "UserId",
principalTable: "UserDetails",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Podcasts_UserDetails_UserId",
table: "Podcasts");
migrationBuilder.DropPrimaryKey(
name: "PK_UserDetails",
table: "UserDetails");
migrationBuilder.RenameTable(
name: "UserDetails",
newName: "Users");
migrationBuilder.RenameIndex(
name: "IX_UserDetails_Slug",
table: "Users",
newName: "IX_Users_Slug");
migrationBuilder.AddPrimaryKey(
name: "PK_Users",
table: "Users",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Podcasts_Users_UserId",
table: "Podcasts",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}

View File

@@ -0,0 +1,414 @@
// <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 PodNoms.Api.Persistence;
namespace PodNoms.Api.Migrations
{
[DbContext(typeof(PodnomsDbContext))]
[Migration("20180422013049_JiggleUserModel")]
partial class JiggleUserModel
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview2-30571")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
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<string>("AppUserId");
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("AppUserId");
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("UserDetails");
});
modelBuilder.Entity("PodNoms.Api.Services.Auth.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<long?>("FacebookId");
b.Property<string>("FirstName");
b.Property<string>("LastName");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("PictureUrl");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
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.Services.Auth.ApplicationUser", "AppUser")
.WithMany()
.HasForeignKey("AppUserId");
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,45 @@
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
namespace PodNoms.Api.Migrations
{
public partial class JiggleUserModel : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "AppUserId",
table: "Podcasts",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Podcasts_AppUserId",
table: "Podcasts",
column: "AppUserId");
migrationBuilder.AddForeignKey(
name: "FK_Podcasts_AspNetUsers_AppUserId",
table: "Podcasts",
column: "AppUserId",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Podcasts_AspNetUsers_AppUserId",
table: "Podcasts");
migrationBuilder.DropIndex(
name: "IX_Podcasts_AppUserId",
table: "Podcasts");
migrationBuilder.DropColumn(
name: "AppUserId",
table: "Podcasts");
}
}
}

View File

@@ -5,9 +5,6 @@ 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
@@ -19,9 +16,117 @@ namespace PodNoms.Api.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.0-preview1-28290")
.HasAnnotation("ProductVersion", "2.1.0-preview2-30571")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
{
b.Property<int>("Id")
@@ -47,6 +152,8 @@ namespace PodNoms.Api.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AppUserId");
b.Property<DateTime>("CreateDate")
.ValueGeneratedOnAdd()
.HasDefaultValueSql("getdate()");
@@ -68,6 +175,8 @@ namespace PodNoms.Api.Migrations
b.HasKey("Id");
b.HasIndex("AppUserId");
b.HasIndex("UserId");
b.ToTable("Podcasts");
@@ -161,7 +270,111 @@ namespace PodNoms.Api.Migrations
.IsUnique()
.HasFilter("[Slug] IS NOT NULL");
b.ToTable("Users");
b.ToTable("UserDetails");
});
modelBuilder.Entity("PodNoms.Api.Services.Auth.ApplicationUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<long?>("FacebookId");
b.Property<string>("FirstName");
b.Property<string>("LastName");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("PictureUrl");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("PodNoms.Api.Models.Playlist", b =>
@@ -174,6 +387,10 @@ namespace PodNoms.Api.Migrations
modelBuilder.Entity("PodNoms.Api.Models.Podcast", b =>
{
b.HasOne("PodNoms.Api.Services.Auth.ApplicationUser", "AppUser")
.WithMany()
.HasForeignKey("AppUserId");
b.HasOne("PodNoms.Api.Models.User", "User")
.WithMany()
.HasForeignKey("UserId");

View File

@@ -1,13 +1,10 @@
namespace PodNoms.Api.Models
{
public class AppSettings
{
namespace PodNoms.Api.Models {
public class AppSettings {
public string Version { get; set; }
public string RssUrl { get; set; }
}
public class StorageSettings
{
public class StorageSettings {
public string ConnectionString { get; set; }
public string CdnUrl { get; set; }

View File

@@ -0,0 +1,56 @@
using System;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
namespace PodNoms.Api.Models {
public class JwtIssuerOptions {
/// <summary>
/// 4.1.1. "iss" (Issuer) Claim - The "iss" (issuer) claim identifies the principal that issued the JWT.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 4.1.2. "sub" (Subject) Claim - The "sub" (subject) claim identifies the principal that is the subject of the JWT.
/// </summary>
public string Subject { get; set; }
/// <summary>
/// 4.1.3. "aud" (Audience) Claim - The "aud" (audience) claim identifies the recipients that the JWT is intended for.
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 4.1.4. "exp" (Expiration Time) Claim - The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime Expiration => IssuedAt.Add(ValidFor);
/// <summary>
/// 4.1.5. "nbf" (Not Before) Claim - The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.
/// </summary>
public DateTime NotBefore => DateTime.UtcNow;
/// <summary>
/// 4.1.6. "iat" (Issued At) Claim - The "iat" (issued at) claim identifies the time at which the JWT was issued.
/// </summary>
public DateTime IssuedAt => DateTime.UtcNow;
/// <summary>
/// Set the timespan the token will be valid for (default is 120 min)
/// </summary>
public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(120);
/// <summary>
/// "jti" (JWT ID) Claim (default ID is a GUID)
/// </summary>
public Func<Task<string>> JtiGenerator =>
() => Task.FromResult(Guid.NewGuid().ToString());
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
}
}

View File

@@ -1,12 +1,14 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.Extensions.Options;
using PodNoms.Api.Services.Auth;
namespace PodNoms.Api.Models {
public class Podcast : BaseModel {
public int Id { get; set; }
public string Uid { get; set; }
public User User { get; set; }
public ApplicationUser AppUser { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Slug { get; set; }

View File

@@ -0,0 +1,8 @@
using FluentValidation.Attributes;
namespace PodNoms.Api.Models.ViewModels {
[Validator(typeof(CredentialsViewModelValidator))]
public class CredentialsViewModel {
public string UserName { get; set; }
public string Password { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using FluentValidation;
namespace PodNoms.Api.Models.ViewModels {
public class CredentialsViewModelValidator : AbstractValidator<CredentialsViewModel> {
public CredentialsViewModelValidator() {
RuleFor(vm => vm.UserName).NotEmpty().WithMessage("Username cannot be empty");
RuleFor(vm => vm.Password).NotEmpty().WithMessage("Password cannot be empty");
RuleFor(vm => vm.Password).Length(6, 12).WithMessage("Password must be between 6 and 12 characters");
}
}
}

View File

@@ -0,0 +1,10 @@
namespace PodNoms.Api.Models.ViewModels {
public class RegistrationViewModel {
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Location { get; set; }
}
}

View File

@@ -3,12 +3,11 @@ using PodNoms.Api.Models;
namespace PodNoms.Api.Persistence {
public interface IUserRepository {
User Get(int id);
User Get(string email);
Task<User> GetAsync(string email);
User Get(string id);
Task<User> GetAsync(string id);
Task<User> GetBySlugAsync(string slug);
User UpdateRegistration(string email, string name, string sid, string providerId, string profileImage, string refreshToken);
string UpdateApiKey(User email);
string UpdateApiKey(User user);
User AddOrUpdate(User user);
}
}

View File

@@ -37,10 +37,11 @@ namespace PodNoms.Api.Persistence {
return ret;
}
public async Task<IEnumerable<Podcast>> GetAllAsync(string emailAddress) {
public async Task<IEnumerable<Podcast>> GetAllAsync(string userId) {
var ret = _context.Podcasts
.Where(u => u.User.EmailAddress == emailAddress)
.Where(u => u.AppUser.Id == userId)
.Include(p => p.User)
.Include(p => p.AppUser)
.OrderByDescending(p => p.Id);
return await ret.ToListAsync();
}

View File

@@ -1,14 +1,16 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using PodNoms.Api.Models;
using PodNoms.Api.Services.Auth;
namespace PodNoms.Api.Persistence {
public class PodnomsDbContext : DbContext {
public class PodnomsDbContext : IdentityDbContext<ApplicationUser> {
public PodnomsDbContext(DbContextOptions<PodnomsDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
@@ -36,6 +38,6 @@ namespace PodNoms.Api.Persistence {
public DbSet<Podcast> Podcasts { get; set; }
public DbSet<PodcastEntry> PodcastEntries { get; set; }
public DbSet<Playlist> Playlists { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<User> UserDetails { get; set; }
}
}

View File

@@ -15,19 +15,19 @@ namespace PodNoms.Api.Persistence {
}
public User Get(int id) {
return _context.Users.FirstOrDefault(u => u.Id == id);
return _context.UserDetails.FirstOrDefault(u => u.Id == id);
}
public User Get(string email) {
return _context.Users.FirstOrDefault(u => u.EmailAddress == email);
return _context.UserDetails.FirstOrDefault(u => u.EmailAddress == email);
}
public async Task<User> GetAsync(string email) {
return await _context.Users
return await _context.UserDetails
.Where(u => u.EmailAddress == email)
.FirstOrDefaultAsync();
}
public async Task<User> GetBySlugAsync(string slug) {
var user = await _context.Users
var user = await _context.UserDetails
.Where(u => u.Slug == slug)
.FirstOrDefaultAsync();
@@ -36,9 +36,9 @@ namespace PodNoms.Api.Persistence {
public User AddOrUpdate(User user) {
if (user.Id != 0) {
_context.Users.Attach(user);
_context.UserDetails.Attach(user);
} else {
_context.Users.Add(user);
_context.UserDetails.Add(user);
}
return user;
@@ -46,7 +46,7 @@ namespace PodNoms.Api.Persistence {
public User UpdateRegistration(string email, string name, string sid, string providerId, string profileImage,
string refreshToken) {
var user = _context.Users.FirstOrDefault(u => u.EmailAddress == email);
var user = _context.UserDetails.FirstOrDefault(u => u.EmailAddress == email);
if (user == null) {
user = new User();
@@ -57,7 +57,7 @@ namespace PodNoms.Api.Persistence {
var c = user.FullName ?? email?.Split('@')[0] ?? string.Empty;
if (!string.IsNullOrEmpty(c)) {
user.Slug = c.Slugify(
from u in _context.Users select u.Slug);
from u in _context.UserDetails select u.Slug);
}
}
if (string.IsNullOrEmpty(user.Uid)) {
@@ -81,7 +81,7 @@ namespace PodNoms.Api.Persistence {
if (user != null) {
do {
newKey = Randomisers.RandomString(16);
} while (_context.Users.FirstOrDefault(u => u.ApiKey == newKey) != null);
} while (_context.UserDetails.FirstOrDefault(u => u.ApiKey == newKey) != null);
}
user.ApiKey = newKey;
return newKey;

View File

@@ -11,6 +11,7 @@
<EmbeddedResource Include="Resources/*.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentValidation.AspNetCore" Version="7.5.2" />
<PackageReference Include="Hangfire" Version="1.6.17" />
<PackageReference Include="Hangfire.MemoryStorage" Version="1.5.2" />
<PackageReference Include="Lib.Net.Http.WebPush" Version="1.3.0" />

View File

@@ -9,23 +9,19 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace PodNoms.Api
{
public class Program
{
namespace PodNoms.Api {
public class Program {
static bool isDevelopment =
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == EnvironmentName.Development;
public static void Main(string[] args)
{
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseUrls("http://0.0.0.0:5000")
.UseKestrel(options =>
{
.UseKestrel(options => {
options.Limits.MaxRequestBodySize = 1073741824; //1Gb
// if (isDevelopment)
// {

View File

@@ -3,15 +3,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
using PodNoms.Api.Models.ViewModels;
using PodNoms.Api.Services.Auth;
namespace PodNoms.Api.Providers
{
public class MappingProvider : Profile
{
namespace PodNoms.Api.Providers {
public class MappingProvider : Profile {
private readonly IConfiguration _options;
public MappingProvider() { }
public MappingProvider(IConfiguration options)
{
public MappingProvider(IConfiguration options) {
this._options = options;
//Domain to API Resource
@@ -29,7 +27,7 @@ namespace PodNoms.Api.Providers
.ForMember(
src => src.AudioUrl,
e => e.MapFrom(m => $"{this._options.GetSection("Storage")["CdnUrl"]}{m.AudioUrl}"));
CreateMap<User, ProfileViewModel>()
.ForMember(
src => src.Name,
@@ -37,13 +35,17 @@ namespace PodNoms.Api.Providers
//API Resource to Domain
CreateMap<PodcastViewModel, Podcast>()
.ForMember(v => v.ImageUrl, opt => opt.Ignore())
.ForMember(v => v.ImageUrl, map => map.Ignore())
;
CreateMap<PodcastEntryViewModel, PodcastEntry>()
.ForMember(
e => e.ImageUrl,
opt => opt.MapFrom(m => m.ImageUrl))
map => map.MapFrom(vm => vm.ImageUrl))
;
CreateMap<RegistrationViewModel, ApplicationUser>()
.ForMember(
e => e.UserName,
map => map.MapFrom(vm => vm.Email));
}
}
}

View File

@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Identity;
namespace PodNoms.Api.Services.Auth {
public class ApplicationUser : IdentityUser {
// Extended Properties
public string FirstName { get; set; }
public string LastName { get; set; }
public long? FacebookId { get; set; }
public string PictureUrl { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
namespace PodNoms.Api.Services.Auth {
public static class Constants {
public static class Strings {
public static class JwtClaimIdentifiers {
public const string Rol = "rol", Id = "id";
}
public static class JwtClaims {
public const string ApiAccess = "api_access";
}
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Security.Claims;
using System.Threading.Tasks;
namespace PodNoms.Api.Services.Auth {
public interface IJwtFactory {
Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity);
ClaimsIdentity GenerateClaimsIdentity(string userName, string id);
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using PodNoms.Api.Models;
namespace PodNoms.Api.Services.Auth {
public class JwtFactory : IJwtFactory {
private readonly JwtIssuerOptions _jwtOptions;
public JwtFactory(IOptions<JwtIssuerOptions> jwtOptions) {
_jwtOptions = jwtOptions.Value;
ThrowIfInvalidOptions(_jwtOptions);
}
public async Task<string> GenerateEncodedToken(string userName, ClaimsIdentity identity) {
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst(Constants.Strings.JwtClaimIdentifiers.Rol),
identity.FindFirst(Constants.Strings.JwtClaimIdentifiers.Id)
};
// Create the JWT security token and encode it.
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
return encodedJwt;
}
public ClaimsIdentity GenerateClaimsIdentity(string userName, string id) {
return new ClaimsIdentity(new GenericIdentity(userName, "Token"), new[]
{
new Claim(Constants.Strings.JwtClaimIdentifiers.Id, id),
new Claim(Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess)
});
}
/// <returns>Date converted to seconds since Unix epoch (Jan 1, 1970, midnight UTC).</returns>
private static long ToUnixEpochDate(DateTime date)
=> (long)Math.Round((date.ToUniversalTime() -
new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero))
.TotalSeconds);
private static void ThrowIfInvalidOptions(JwtIssuerOptions options) {
if (options == null) throw new ArgumentNullException(nameof(options));
if (options.ValidFor <= TimeSpan.Zero) {
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor));
}
if (options.SigningCredentials == null) {
throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials));
}
if (options.JtiGenerator == null) {
throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator));
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Newtonsoft.Json;
using PodNoms.Api.Models;
namespace PodNoms.Api.Services.Auth {
public class Tokens {
public static async Task<string> GenerateJwt(ClaimsIdentity identity, IJwtFactory jwtFactory, string userName,
JwtIssuerOptions jwtOptions, JsonSerializerSettings serializerSettings) {
var response = new {
id = identity.Claims.Single(c => c.Type == "id").Value,
auth_token = await jwtFactory.GenerateEncodedToken(userName, identity),
expires_in = (int)jwtOptions.ValidFor.TotalSeconds
};
return JsonConvert.SerializeObject(response, serializerSettings);
}
}
}

View File

@@ -21,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;
using FluentValidation.AspNetCore;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
@@ -41,9 +42,14 @@ using PodNoms.Api.Services.Push.Extensions;
using Swashbuckle.AspNetCore.Swagger;
using PodNoms.Api.Services.Push.Formatters;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace PodNoms.Api {
public class Startup {
private const string SecretKey = "QGfaEMNASkNMGLKA3LjgPdkPfFEy3n40"; // todo: get this from somewhere secure
private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) {
@@ -105,39 +111,65 @@ namespace PodNoms.Api {
});
services.AddHttpClient();
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
// Configure JwtIssuerOptions
services.Configure<JwtIssuerOptions>(options => {
//TODO: Remove this in production, only for testing
options.ValidFor = TimeSpan.FromDays(28);
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)],
ValidateIssuerSigningKey = true,
IssuerSigningKey = _signingKey,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
options.Audience = Configuration["auth0:clientId"];
options.Authority = $"https://{Configuration["auth0:domain"]}/";
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = "name"
}).AddJwtBearer(configureOptions => {
configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
configureOptions.TokenValidationParameters = tokenValidationParameters;
configureOptions.SaveToken = true;
configureOptions.Events = new JwtBearerEvents() {
//Don't need this now we've removed Auth0
// OnTokenValidated = AuthenticationMiddleware.OnTokenValidated
};
options.Events = new JwtBearerEvents() {
OnTokenValidated = AuthenticationMiddleware.OnTokenValidated
};
options.Events.OnMessageReceived = context => {
configureOptions.Events.OnMessageReceived = context => {
StringValues token;
if (context.Request.Path.Value.StartsWith("/hubs/") && context.Request.Query.TryGetValue("token", out token)) {
context.Token = token;
}
return Task.CompletedTask;
};
};
});
var defaultPolicy =
new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Bearer")
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(j => {
j.DefaultPolicy = defaultPolicy;
j.AddPolicy("ApiUser", policy => policy.RequireClaim(
Constants.Strings.JwtClaimIdentifiers.Rol, Constants.Strings.JwtClaims.ApiAccess));
});
// add identity
var identityBuilder = services.AddIdentityCore<ApplicationUser>(o => {
// configure identity options
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
});
identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(IdentityRole), identityBuilder.Services);
identityBuilder.AddEntityFrameworkStores<PodnomsDbContext>().AddDefaultTokenProviders();
services.AddMvc(options => {
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
@@ -150,7 +182,8 @@ namespace PodNoms.Api {
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
})
.AddXmlSerializerFormatters();
.AddXmlSerializerFormatters()
.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<Startup>());
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new Info { Title = "Podnoms.API", Version = "v1" });
@@ -174,6 +207,8 @@ namespace PodNoms.Api {
services.AddTransient<IFileUploader, AzureFileUploader>();
services.AddTransient<IRealTimeUpdater, SignalRUpdater>();
services.TryAddTransient<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IJwtFactory, JwtFactory>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IPodcastRepository, PodcastRepository>();
services.AddScoped<IEntryRepository, EntryRepository>();

View File

@@ -1,18 +1,13 @@
using System;
namespace PodNoms.Api.Utils
{
public class DateUtils
{
public static DateTime ConvertFromUnixTimestamp(double timestamp)
{
namespace PodNoms.Api.Utils {
public class DateUtils {
public static DateTime ConvertFromUnixTimestamp(double timestamp) {
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
return origin.AddSeconds(timestamp);
}
public static double ConvertToUnixTimestamp(DateTime date)
{
public static double ConvertToUnixTimestamp(DateTime date) {
DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
TimeSpan diff = date.ToUniversalTime() - origin;
return Math.Floor(diff.TotalSeconds);

19
server/Utils/Errors.cs Normal file
View File

@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace PodNoms.Api.Utils {
public static class Errors {
public static ModelStateDictionary AddErrorsToModelState(IdentityResult identityResult, ModelStateDictionary modelState) {
foreach (var e in identityResult.Errors) {
modelState.TryAddModelError(e.Code, e.Description);
}
return modelState;
}
public static ModelStateDictionary AddErrorToModelState(string code, string description, ModelStateDictionary modelState) {
modelState.TryAddModelError(code, description);
return modelState;
}
}
}