Use typescript standard and lint all files

This commit is contained in:
Chocobozzz 2017-06-16 14:32:15 +02:00
parent 46757b477c
commit df98563e21
143 changed files with 2016 additions and 1949 deletions

View File

@ -13,7 +13,7 @@
"url": "git://github.com/Chocobozzz/PeerTube.git" "url": "git://github.com/Chocobozzz/PeerTube.git"
}, },
"scripts": { "scripts": {
"test": "standard && tslint -c ./tslint.json src/**/*.ts", "test": "standard && tslint --type-check --project ./tsconfig.json -c ./tslint.json 'src/app/**/*.ts'",
"webpack": "webpack", "webpack": "webpack",
"webpack-dev-server": "webpack-dev-server" "webpack-dev-server": "webpack-dev-server"
}, },
@ -78,7 +78,7 @@
"tslib": "^1.5.0", "tslib": "^1.5.0",
"tslint": "~5.4.3", "tslint": "~5.4.3",
"tslint-loader": "^3.3.0", "tslint-loader": "^3.3.0",
"typescript": "~2.3.0", "typescript": "~2.4.0",
"url-loader": "^0.5.7", "url-loader": "^0.5.7",
"video.js": "^5.19.2", "video.js": "^5.19.2",
"videojs-dock": "^2.0.2", "videojs-dock": "^2.0.2",
@ -93,6 +93,7 @@
"codelyzer": "^3.0.0-beta.4", "codelyzer": "^3.0.0-beta.4",
"ng2-completer": "1.2.2", "ng2-completer": "1.2.2",
"standard": "^10.0.0", "standard": "^10.0.0",
"tslint-config-standard": "^6.0.1",
"webpack-bundle-analyzer": "^2.8.2", "webpack-bundle-analyzer": "^2.8.2",
"webpack-dev-server": "^2.4.5", "webpack-dev-server": "^2.4.5",
"webpack-dll-bundles-plugin": "^1.0.0-beta.5" "webpack-dll-bundles-plugin": "^1.0.0-beta.5"

View File

@ -1,11 +1,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router'
import { AdminComponent } from './admin.component'; import { AdminComponent } from './admin.component'
import { FriendsRoutes } from './friends'; import { FriendsRoutes } from './friends'
import { RequestsRoutes } from './requests'; import { RequestsRoutes } from './requests'
import { UsersRoutes } from './users'; import { UsersRoutes } from './users'
import { VideoAbusesRoutes } from './video-abuses'; import { VideoAbusesRoutes } from './video-abuses'
const adminRoutes: Routes = [ const adminRoutes: Routes = [
{ {
@ -23,7 +23,7 @@ const adminRoutes: Routes = [
...VideoAbusesRoutes ...VideoAbusesRoutes
] ]
} }
]; ]
@NgModule({ @NgModule({
imports: [ RouterModule.forChild(adminRoutes) ], imports: [ RouterModule.forChild(adminRoutes) ],

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
template: '<router-outlet></router-outlet>' template: '<router-outlet></router-outlet>'

View File

@ -1,12 +1,12 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { AdminComponent } from './admin.component'; import { AdminComponent } from './admin.component'
import { AdminRoutingModule } from './admin-routing.module'; import { AdminRoutingModule } from './admin-routing.module'
import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'; import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'
import { RequestsComponent, RequestStatsComponent, RequestService } from './requests'; import { RequestsComponent, RequestStatsComponent, RequestService } from './requests'
import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'; import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'
import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'; import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
import { SharedModule } from '../shared'; import { SharedModule } from '../shared'
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -1,12 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { ConfirmService } from '../../../core'; import { ConfirmService } from '../../../core'
import { validateHost } from '../../../shared'; import { validateHost } from '../../../shared'
import { FriendService } from '../shared'; import { FriendService } from '../shared'
@Component({ @Component({
selector: 'my-friend-add', selector: 'my-friend-add',
@ -14,107 +14,107 @@ import { FriendService } from '../shared';
styleUrls: [ './friend-add.component.scss' ] styleUrls: [ './friend-add.component.scss' ]
}) })
export class FriendAddComponent implements OnInit { export class FriendAddComponent implements OnInit {
form: FormGroup; form: FormGroup
hosts = [ ]; hosts = [ ]
error: string = null; error: string = null
constructor( constructor (
private router: Router, private router: Router,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private confirmService: ConfirmService, private confirmService: ConfirmService,
private friendService: FriendService private friendService: FriendService
) {} ) {}
ngOnInit() { ngOnInit () {
this.form = new FormGroup({}); this.form = new FormGroup({})
this.addField(); this.addField()
} }
addField() { addField () {
this.form.addControl(`host-${this.hosts.length}`, new FormControl('', [ validateHost ])); this.form.addControl(`host-${this.hosts.length}`, new FormControl('', [ validateHost ]))
this.hosts.push(''); this.hosts.push('')
} }
canMakeFriends() { canMakeFriends () {
return window.location.protocol === 'https:'; return window.location.protocol === 'https:'
} }
customTrackBy(index: number, obj: any): any { customTrackBy (index: number, obj: any): any {
return index; return index
} }
displayAddField(index: number) { displayAddField (index: number) {
return index === (this.hosts.length - 1); return index === (this.hosts.length - 1)
} }
displayRemoveField(index: number) { displayRemoveField (index: number) {
return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1); return (index !== 0 || this.hosts.length > 1) && index !== (this.hosts.length - 1)
} }
isFormValid() { isFormValid () {
// Do not check the last input // Do not check the last input
for (let i = 0; i < this.hosts.length - 1; i++) { for (let i = 0; i < this.hosts.length - 1; i++) {
if (!this.form.controls[`host-${i}`].valid) return false; if (!this.form.controls[`host-${i}`].valid) return false
} }
const lastIndex = this.hosts.length - 1; const lastIndex = this.hosts.length - 1
// If the last input (which is not the first) is empty, it's ok // If the last input (which is not the first) is empty, it's ok
if (this.hosts[lastIndex] === '' && lastIndex !== 0) { if (this.hosts[lastIndex] === '' && lastIndex !== 0) {
return true; return true
} else { } else {
return this.form.controls[`host-${lastIndex}`].valid; return this.form.controls[`host-${lastIndex}`].valid
} }
} }
removeField(index: number) { removeField (index: number) {
// Remove the last control // Remove the last control
this.form.removeControl(`host-${this.hosts.length - 1}`); this.form.removeControl(`host-${this.hosts.length - 1}`)
this.hosts.splice(index, 1); this.hosts.splice(index, 1)
} }
makeFriends() { makeFriends () {
this.error = ''; this.error = ''
const notEmptyHosts = this.getNotEmptyHosts(); const notEmptyHosts = this.getNotEmptyHosts()
if (notEmptyHosts.length === 0) { if (notEmptyHosts.length === 0) {
this.error = 'You need to specify at least 1 host.'; this.error = 'You need to specify at least 1 host.'
return; return
} }
if (!this.isHostsUnique(notEmptyHosts)) { if (!this.isHostsUnique(notEmptyHosts)) {
this.error = 'Hosts need to be unique.'; this.error = 'Hosts need to be unique.'
return; return
} }
const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - '); const confirmMessage = 'Are you sure to make friends with:<br /> - ' + notEmptyHosts.join('<br /> - ')
this.confirmService.confirm(confirmMessage, 'Make friends').subscribe( this.confirmService.confirm(confirmMessage, 'Make friends').subscribe(
res => { res => {
if (res === false) return; if (res === false) return
this.friendService.makeFriends(notEmptyHosts).subscribe( this.friendService.makeFriends(notEmptyHosts).subscribe(
status => { status => {
this.notificationsService.success('Sucess', 'Make friends request sent!'); this.notificationsService.success('Sucess', 'Make friends request sent!')
this.router.navigate([ '/admin/friends/list' ]); this.router.navigate([ '/admin/friends/list' ])
}, },
err => this.notificationsService.error('Error', err.text) err => this.notificationsService.error('Error', err.text)
); )
} }
); )
} }
private getNotEmptyHosts() { private getNotEmptyHosts () {
const notEmptyHosts = []; const notEmptyHosts = []
Object.keys(this.form.value).forEach((hostKey) => { Object.keys(this.form.value).forEach((hostKey) => {
const host = this.form.value[hostKey]; const host = this.form.value[hostKey]
if (host !== '') notEmptyHosts.push(host); if (host !== '') notEmptyHosts.push(host)
}); })
return notEmptyHosts; return notEmptyHosts
} }
private isHostsUnique(hosts: string[]) { private isHostsUnique (hosts: string[]) {
return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host)); return hosts.every(host => hosts.indexOf(host) === hosts.lastIndexOf(host))
} }
} }

View File

@ -1 +1 @@
export * from './friend-add.component'; export * from './friend-add.component'

View File

@ -1,11 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { ServerDataSource } from 'ng2-smart-table'; import { ServerDataSource } from 'ng2-smart-table'
import { ConfirmService } from '../../../core'; import { ConfirmService } from '../../../core'
import { Utils } from '../../../shared'; import { Utils } from '../../../shared'
import { Friend, FriendService } from '../shared'; import { Friend, FriendService } from '../shared'
@Component({ @Component({
selector: 'my-friend-list', selector: 'my-friend-list',
@ -13,7 +13,7 @@ import { Friend, FriendService } from '../shared';
styleUrls: [ './friend-list.component.scss' ] styleUrls: [ './friend-list.component.scss' ]
}) })
export class FriendListComponent { export class FriendListComponent {
friendsSource = null; friendsSource = null
tableSettings = { tableSettings = {
attr: { attr: {
class: 'table-hover' class: 'table-hover'
@ -49,36 +49,36 @@ export class FriendListComponent {
valuePrepareFunction: Utils.dateToHuman valuePrepareFunction: Utils.dateToHuman
} }
} }
}; }
constructor( constructor (
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private confirmService: ConfirmService, private confirmService: ConfirmService,
private friendService: FriendService private friendService: FriendService
) { ) {
this.friendsSource = this.friendService.getDataSource(); this.friendsSource = this.friendService.getDataSource()
} }
hasFriends() { hasFriends () {
return this.friendsSource.count() !== 0; return this.friendsSource.count() !== 0
} }
quitFriends() { quitFriends () {
const confirmMessage = 'Do you really want to quit your friends? All their videos will be deleted.'; const confirmMessage = 'Do you really want to quit your friends? All their videos will be deleted.'
this.confirmService.confirm(confirmMessage, 'Quit friends').subscribe( this.confirmService.confirm(confirmMessage, 'Quit friends').subscribe(
res => { res => {
if (res === false) return; if (res === false) return
this.friendService.quitFriends().subscribe( this.friendService.quitFriends().subscribe(
status => { status => {
this.notificationsService.success('Sucess', 'Friends left!'); this.notificationsService.success('Sucess', 'Friends left!')
this.friendsSource.refresh(); this.friendsSource.refresh()
}, },
err => this.notificationsService.error('Error', err.text) err => this.notificationsService.error('Error', err.text)
); )
} }
); )
} }
} }

View File

@ -1 +1 @@
export * from './friend-list.component'; export * from './friend-list.component'

View File

@ -1,8 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
template: '<router-outlet></router-outlet>' template: '<router-outlet></router-outlet>'
}) })
export class FriendsComponent { export class FriendsComponent {
} }

View File

@ -1,37 +1,37 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router'
import { FriendsComponent } from './friends.component'; import { FriendsComponent } from './friends.component'
import { FriendAddComponent } from './friend-add'; import { FriendAddComponent } from './friend-add'
import { FriendListComponent } from './friend-list'; import { FriendListComponent } from './friend-list'
export const FriendsRoutes: Routes = [ export const FriendsRoutes: Routes = [
{ {
path: 'friends', path: 'friends',
component: FriendsComponent, component: FriendsComponent,
children: [ children: [
{ {
path: '', path: '',
redirectTo: 'list', redirectTo: 'list',
pathMatch: 'full' pathMatch: 'full'
}, },
{ {
path: 'list', path: 'list',
component: FriendListComponent, component: FriendListComponent,
data: { data: {
meta: { meta: {
title: 'Friends list' title: 'Friends list'
}
}
},
{
path: 'add',
component: FriendAddComponent,
data: {
meta: {
title: 'Add friends'
}
} }
} }
] },
} {
]; path: 'add',
component: FriendAddComponent,
data: {
meta: {
title: 'Add friends'
}
}
}
]
}
]

View File

@ -1,5 +1,5 @@
export * from './friend-add'; export * from './friend-add'
export * from './friend-list'; export * from './friend-list'
export * from './shared'; export * from './shared'
export * from './friends.component'; export * from './friends.component'
export * from './friends.routes'; export * from './friends.routes'

View File

@ -1,7 +1,7 @@
export interface Friend { export interface Friend {
id: string; id: string
host: string; host: string
score: number; score: number
email: string; email: string
createdAt: Date; createdAt: Date
} }

View File

@ -1,39 +1,39 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import { ServerDataSource } from 'ng2-smart-table'; import { ServerDataSource } from 'ng2-smart-table'
import { Friend } from './friend.model'; import { Friend } from './friend.model'
import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'; import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
@Injectable() @Injectable()
export class FriendService { export class FriendService {
private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/'; private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/'
constructor ( constructor (
private authHttp: AuthHttp, private authHttp: AuthHttp,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
getDataSource() { getDataSource () {
return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL); return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL)
} }
makeFriends(notEmptyHosts) { makeFriends (notEmptyHosts) {
const body = { const body = {
hosts: notEmptyHosts hosts: notEmptyHosts
}; }
return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
quitFriends() { quitFriends () {
return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends')
.map(res => res.status) .map(res => res.status)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
} }

View File

@ -1,2 +1,2 @@
export * from './friend.model'; export * from './friend.model'
export * from './friend.service'; export * from './friend.service'

View File

@ -1,6 +1,6 @@
export * from './friends'; export * from './friends'
export * from './requests'; export * from './requests'
export * from './users'; export * from './users'
export * from './admin-routing.module'; export * from './admin-routing.module'
export * from './admin.module'; export * from './admin.module'
export * from './admin.component'; export * from './admin.component'

View File

@ -1,4 +1,4 @@
export * from './request-stats'; export * from './request-stats'
export * from './shared'; export * from './shared'
export * from './requests.component'; export * from './requests.component'
export * from './requests.routes'; export * from './requests.routes'

View File

@ -1 +1 @@
export * from './request-stats.component'; export * from './request-stats.component'

View File

@ -1,12 +1,12 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { RequestService, RequestStats } from '../shared'; import { RequestService, RequestStats } from '../shared'
@Component({ @Component({
selector: 'my-request-stats', selector: 'my-request-stats',
templateUrl: './request-stats.component.html', templateUrl: './request-stats.component.html',
styleUrls: [ './request-stats.component.scss' ] styleUrls: [ './request-stats.component.scss' ]
}) })
export class RequestStatsComponent implements OnInit, OnDestroy { export class RequestStatsComponent implements OnInit, OnDestroy {
@ -14,70 +14,67 @@ export class RequestStatsComponent implements OnInit, OnDestroy {
requestScheduler: 'Basic request scheduler', requestScheduler: 'Basic request scheduler',
requestVideoEventScheduler: 'Video events request scheduler', requestVideoEventScheduler: 'Video events request scheduler',
requestVideoQaduScheduler: 'Quick and dirty video updates request scheduler' requestVideoQaduScheduler: 'Quick and dirty video updates request scheduler'
}; }
stats: { [ id: string ]: RequestStats } = { stats: { [ id: string ]: RequestStats } = {
requestScheduler: null, requestScheduler: null,
requestVideoEventScheduler: null, requestVideoEventScheduler: null,
requestVideoQaduScheduler: null requestVideoQaduScheduler: null
}; }
private intervals: { [ id: string ]: number } = { private intervals: { [ id: string ]: number } = {
requestScheduler: null, requestScheduler: null,
requestVideoEventScheduler: null, requestVideoEventScheduler: null,
requestVideoQaduScheduler: null requestVideoQaduScheduler: null
}; }
private timeouts: { [ id: string ]: number } = { private timeouts: { [ id: string ]: number } = {
requestScheduler: null, requestScheduler: null,
requestVideoEventScheduler: null, requestVideoEventScheduler: null,
requestVideoQaduScheduler: null requestVideoQaduScheduler: null
};
constructor(
private notificationsService: NotificationsService,
private requestService: RequestService
) { }
ngOnInit() {
this.getStats();
this.runIntervals();
} }
ngOnDestroy() { constructor (
private notificationsService: NotificationsService,
private requestService: RequestService
) { }
ngOnInit () {
this.getStats()
this.runIntervals()
}
ngOnDestroy () {
Object.keys(this.stats).forEach(requestSchedulerName => { Object.keys(this.stats).forEach(requestSchedulerName => {
if (this.intervals[requestSchedulerName] !== null) { if (this.intervals[requestSchedulerName] !== null) {
window.clearInterval(this.intervals[requestSchedulerName]); window.clearInterval(this.intervals[requestSchedulerName])
} }
if (this.timeouts[requestSchedulerName] !== null) { if (this.timeouts[requestSchedulerName] !== null) {
window.clearTimeout(this.timeouts[requestSchedulerName]); window.clearTimeout(this.timeouts[requestSchedulerName])
} }
}); })
} }
getStats() { getStats () {
this.requestService.getStats().subscribe( this.requestService.getStats().subscribe(
stats => this.stats = stats, stats => this.stats = stats,
err => this.notificationsService.error('Error', err.text) err => this.notificationsService.error('Error', err.text)
); )
} }
private runIntervals() { private runIntervals () {
Object.keys(this.intervals).forEach(requestSchedulerName => { Object.keys(this.intervals).forEach(requestSchedulerName => {
this.intervals[requestSchedulerName] = window.setInterval(() => { this.intervals[requestSchedulerName] = window.setInterval(() => {
const stats = this.stats[requestSchedulerName]; const stats = this.stats[requestSchedulerName]
stats.remainingMilliSeconds -= 1000; stats.remainingMilliSeconds -= 1000
if (stats.remainingMilliSeconds <= 0) { if (stats.remainingMilliSeconds <= 0) {
this.timeouts[requestSchedulerName] = window.setTimeout(() => this.getStats(), stats.remainingMilliSeconds + 100); this.timeouts[requestSchedulerName] = window.setTimeout(() => this.getStats(), stats.remainingMilliSeconds + 100)
} }
}, 1000); }, 1000)
}); })
} }
} }

View File

@ -1,8 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
template: '<router-outlet></router-outlet>' template: '<router-outlet></router-outlet>'
}) })
export class RequestsComponent { export class RequestsComponent {
} }

View File

@ -1,27 +1,27 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router'
import { RequestsComponent } from './requests.component'; import { RequestsComponent } from './requests.component'
import { RequestStatsComponent } from './request-stats'; import { RequestStatsComponent } from './request-stats'
export const RequestsRoutes: Routes = [ export const RequestsRoutes: Routes = [
{ {
path: 'requests', path: 'requests',
component: RequestsComponent, component: RequestsComponent,
children: [ children: [
{ {
path: '', path: '',
redirectTo: 'stats', redirectTo: 'stats',
pathMatch: 'full' pathMatch: 'full'
}, },
{ {
path: 'stats', path: 'stats',
component: RequestStatsComponent, component: RequestStatsComponent,
data: { data: {
meta: { meta: {
title: 'Request stats' title: 'Request stats'
}
} }
} }
] }
} ]
]; }
]

View File

@ -1,2 +1,2 @@
export * from './request-stats.model'; export * from './request-stats.model'
export * from './request.service'; export * from './request.service'

View File

@ -1,35 +1,35 @@
export interface Request { export interface Request {
request: any; request: any
to: any; to: any
} }
export class RequestStats { export class RequestStats {
requestsLimitPods: number; requestsLimitPods: number
requestsLimitPerPod: number; requestsLimitPerPod: number
milliSecondsInterval: number; milliSecondsInterval: number
remainingMilliSeconds: number; remainingMilliSeconds: number
totalRequests: number; totalRequests: number
constructor(hash: { constructor (hash: {
requestsLimitPods: number, requestsLimitPods: number,
requestsLimitPerPod: number, requestsLimitPerPod: number,
milliSecondsInterval: number, milliSecondsInterval: number,
remainingMilliSeconds: number, remainingMilliSeconds: number,
totalRequests: number; totalRequests: number
}) { }) {
this.requestsLimitPods = hash.requestsLimitPods; this.requestsLimitPods = hash.requestsLimitPods
this.requestsLimitPerPod = hash.requestsLimitPerPod; this.requestsLimitPerPod = hash.requestsLimitPerPod
this.milliSecondsInterval = hash.milliSecondsInterval; this.milliSecondsInterval = hash.milliSecondsInterval
this.remainingMilliSeconds = hash.remainingMilliSeconds; this.remainingMilliSeconds = hash.remainingMilliSeconds
this.totalRequests = hash.totalRequests; this.totalRequests = hash.totalRequests
} }
get remainingSeconds() { get remainingSeconds () {
return Math.floor(this.remainingMilliSeconds / 1000); return Math.floor(this.remainingMilliSeconds / 1000)
} }
get secondsInterval() { get secondsInterva () {
return Math.floor(this.milliSecondsInterval / 1000); return Math.floor(this.milliSecondsInterval / 1000)
} }
} }

View File

@ -1,34 +1,34 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import { RequestStats } from './request-stats.model'; import { RequestStats } from './request-stats.model'
import { AuthHttp, RestExtractor } from '../../../shared'; import { AuthHttp, RestExtractor } from '../../../shared'
@Injectable() @Injectable()
export class RequestService { export class RequestService {
private static BASE_REQUEST_URL = API_URL + '/api/v1/requests/'; private static BASE_REQUEST_URL = API_URL + '/api/v1/requests/'
constructor ( constructor (
private authHttp: AuthHttp, private authHttp: AuthHttp,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
getStats(): Observable<{ [ id: string ]: RequestStats }> { getStats (): Observable<{ [ id: string ]: RequestStats }> {
return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats') return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats')
.map(this.restExtractor.extractDataGet) .map(this.restExtractor.extractDataGet)
.map(this.buildRequestObjects) .map(this.buildRequestObjects)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
private buildRequestObjects(data: any) { private buildRequestObjects (data: any) {
const requestSchedulers = {}; const requestSchedulers = {}
Object.keys(data).forEach(requestSchedulerName => { Object.keys(data).forEach(requestSchedulerName => {
requestSchedulers[requestSchedulerName] = new RequestStats(data[requestSchedulerName]); requestSchedulers[requestSchedulerName] = new RequestStats(data[requestSchedulerName])
}); })
return requestSchedulers; return requestSchedulers
} }
} }

View File

@ -1,5 +1,5 @@
export * from './shared'; export * from './shared'
export * from './user-add'; export * from './user-add'
export * from './user-list'; export * from './user-list'
export * from './users.component'; export * from './users.component'
export * from './users.routes'; export * from './users.routes'

View File

@ -1 +1 @@
export * from './user.service'; export * from './user.service'

View File

@ -1,35 +1,35 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'; import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
@Injectable() @Injectable()
export class UserService { export class UserService {
private static BASE_USERS_URL = API_URL + '/api/v1/users/'; private static BASE_USERS_URL = API_URL + '/api/v1/users/'
constructor( constructor (
private authHttp: AuthHttp, private authHttp: AuthHttp,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
addUser(username: string, password: string, email: string) { addUser (username: string, password: string, email: string) {
const body = { const body = {
username, username,
email, email,
password password
}; }
return this.authHttp.post(UserService.BASE_USERS_URL, body) return this.authHttp.post(UserService.BASE_USERS_URL, body)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch(this.restExtractor.handleError); .catch(this.restExtractor.handleError)
} }
getDataSource() { getDataSource () {
return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL); return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL)
} }
removeUser(user: User) { removeUser (user: User) {
return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
} }
} }

View File

@ -1 +1 @@
export * from './user-add.component'; export * from './user-add.component'

View File

@ -1,71 +1,71 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { UserService } from '../shared'; import { UserService } from '../shared'
import { import {
FormReactive, FormReactive,
USER_USERNAME, USER_USERNAME,
USER_EMAIL, USER_EMAIL,
USER_PASSWORD USER_PASSWORD
} from '../../../shared'; } from '../../../shared'
@Component({ @Component({
selector: 'my-user-add', selector: 'my-user-add',
templateUrl: './user-add.component.html' templateUrl: './user-add.component.html'
}) })
export class UserAddComponent extends FormReactive implements OnInit { export class UserAddComponent extends FormReactive implements OnInit {
error: string = null; error: string = null
form: FormGroup; form: FormGroup
formErrors = { formErrors = {
'username': '', 'username': '',
'email': '', 'email': '',
'password': '' 'password': ''
}; }
validationMessages = { validationMessages = {
'username': USER_USERNAME.MESSAGES, 'username': USER_USERNAME.MESSAGES,
'email': USER_EMAIL.MESSAGES, 'email': USER_EMAIL.MESSAGES,
'password': USER_PASSWORD.MESSAGES, 'password': USER_PASSWORD.MESSAGES
}; }
constructor( constructor (
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private userService: UserService private userService: UserService
) { ) {
super(); super()
} }
buildForm() { buildForm () {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
username: [ '', USER_USERNAME.VALIDATORS ], username: [ '', USER_USERNAME.VALIDATORS ],
email: [ '', USER_EMAIL.VALIDATORS ], email: [ '', USER_EMAIL.VALIDATORS ],
password: [ '', USER_PASSWORD.VALIDATORS ], password: [ '', USER_PASSWORD.VALIDATORS ]
}); })
this.form.valueChanges.subscribe(data => this.onValueChanged(data)); this.form.valueChanges.subscribe(data => this.onValueChanged(data))
} }
ngOnInit() { ngOnInit () {
this.buildForm(); this.buildForm()
} }
addUser() { addUser () {
this.error = null; this.error = null
const { username, password, email } = this.form.value; const { username, password, email } = this.form.value
this.userService.addUser(username, password, email).subscribe( this.userService.addUser(username, password, email).subscribe(
() => { () => {
this.notificationsService.success('Success', `User ${username} created.`); this.notificationsService.success('Success', `User ${username} created.`)
this.router.navigate([ '/admin/users/list' ]); this.router.navigate([ '/admin/users/list' ])
}, },
err => this.error = err.text err => this.error = err.text
); )
} }
} }

View File

@ -1 +1 @@
export * from './user-list.component'; export * from './user-list.component'

View File

@ -1,10 +1,10 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { ConfirmService } from '../../../core'; import { ConfirmService } from '../../../core'
import { User, Utils } from '../../../shared'; import { User, Utils } from '../../../shared'
import { UserService } from '../shared'; import { UserService } from '../shared'
@Component({ @Component({
selector: 'my-user-list', selector: 'my-user-list',
@ -12,7 +12,7 @@ import { UserService } from '../shared';
styleUrls: [ './user-list.component.scss' ] styleUrls: [ './user-list.component.scss' ]
}) })
export class UserListComponent { export class UserListComponent {
usersSource = null; usersSource = null
tableSettings = { tableSettings = {
mode: 'external', mode: 'external',
attr: { attr: {
@ -52,37 +52,37 @@ export class UserListComponent {
valuePrepareFunction: Utils.dateToHuman valuePrepareFunction: Utils.dateToHuman
} }
} }
}; }
constructor( constructor (
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private confirmService: ConfirmService, private confirmService: ConfirmService,
private userService: UserService private userService: UserService
) { ) {
this.usersSource = this.userService.getDataSource(); this.usersSource = this.userService.getDataSource()
} }
removeUser({ data }) { removeUser ({ data }) {
const user: User = data; const user: User = data
if (user.username === 'root') { if (user.username === 'root') {
this.notificationsService.error('Error', 'You cannot delete root.'); this.notificationsService.error('Error', 'You cannot delete root.')
return; return
} }
this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe( this.confirmService.confirm('Do you really want to delete this user?', 'Delete').subscribe(
res => { res => {
if (res === false) return; if (res === false) return
this.userService.removeUser(user).subscribe( this.userService.removeUser(user).subscribe(
() => { () => {
this.notificationsService.success('Success', `User ${user.username} deleted.`); this.notificationsService.success('Success', `User ${user.username} deleted.`)
this.usersSource.refresh(); this.usersSource.refresh()
}, },
err => this.notificationsService.error('Error', err.text) err => this.notificationsService.error('Error', err.text)
); )
} }
); )
} }
} }

View File

@ -1,8 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
template: '<router-outlet></router-outlet>' template: '<router-outlet></router-outlet>'
}) })
export class UsersComponent { export class UsersComponent {
} }

View File

@ -1,8 +1,8 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router'
import { UsersComponent } from './users.component'; import { UsersComponent } from './users.component'
import { UserAddComponent } from './user-add'; import { UserAddComponent } from './user-add'
import { UserListComponent } from './user-list'; import { UserListComponent } from './user-list'
export const UsersRoutes: Routes = [ export const UsersRoutes: Routes = [
{ {
@ -34,4 +34,4 @@ export const UsersRoutes: Routes = [
} }
] ]
} }
]; ]

View File

@ -1,3 +1,3 @@
export * from './video-abuse-list'; export * from './video-abuse-list'
export * from './video-abuses.component'; export * from './video-abuses.component'
export * from './video-abuses.routes'; export * from './video-abuses.routes'

View File

@ -1 +1 @@
export * from './video-abuse-list.component'; export * from './video-abuse-list.component'

View File

@ -1,15 +1,15 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { Utils, VideoAbuseService, VideoAbuse } from '../../../shared'; import { Utils, VideoAbuseService, VideoAbuse } from '../../../shared'
@Component({ @Component({
selector: 'my-video-abuse-list', selector: 'my-video-abuse-list',
templateUrl: './video-abuse-list.component.html' templateUrl: './video-abuse-list.component.html'
}) })
export class VideoAbuseListComponent { export class VideoAbuseListComponent {
videoAbusesSource = null; videoAbusesSource = null
tableSettings = { tableSettings = {
mode: 'external', mode: 'external',
attr: { attr: {
@ -54,18 +54,18 @@ export class VideoAbuseListComponent {
valuePrepareFunction: Utils.dateToHuman valuePrepareFunction: Utils.dateToHuman
} }
} }
}; }
constructor( constructor (
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private videoAbuseService: VideoAbuseService private videoAbuseService: VideoAbuseService
) { ) {
this.videoAbusesSource = this.videoAbuseService.getDataSource(); this.videoAbusesSource = this.videoAbuseService.getDataSource()
} }
buildVideoLink(videoId: string) { buildVideoLink (videoId: string) {
// TODO: transform to routerLink // TODO: transform to routerLink
// https://github.com/akveo/ng2-smart-table/issues/57 // https://github.com/akveo/ng2-smart-table/issues/57
return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`; return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>`
} }
} }

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
template: '<router-outlet></router-outlet>' template: '<router-outlet></router-outlet>'

View File

@ -1,7 +1,7 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router'
import { VideoAbusesComponent } from './video-abuses.component'; import { VideoAbusesComponent } from './video-abuses.component'
import { VideoAbuseListComponent } from './video-abuse-list'; import { VideoAbuseListComponent } from './video-abuse-list'
export const VideoAbusesRoutes: Routes = [ export const VideoAbusesRoutes: Routes = [
{ {
@ -25,4 +25,4 @@ export const VideoAbusesRoutes: Routes = [
} }
] ]
} }
]; ]

View File

@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { FormReactive, UserService, USER_PASSWORD } from '../../shared'; import { FormReactive, UserService, USER_PASSWORD } from '../../shared'
@Component({ @Component({
selector: 'my-account-change-password', selector: 'my-account-change-password',
@ -12,55 +12,55 @@ import { FormReactive, UserService, USER_PASSWORD } from '../../shared';
}) })
export class AccountChangePasswordComponent extends FormReactive implements OnInit { export class AccountChangePasswordComponent extends FormReactive implements OnInit {
error: string = null; error: string = null
form: FormGroup; form: FormGroup
formErrors = { formErrors = {
'new-password': '', 'new-password': '',
'new-confirmed-password': '' 'new-confirmed-password': ''
}; }
validationMessages = { validationMessages = {
'new-password': USER_PASSWORD.MESSAGES, 'new-password': USER_PASSWORD.MESSAGES,
'new-confirmed-password': USER_PASSWORD.MESSAGES 'new-confirmed-password': USER_PASSWORD.MESSAGES
}; }
constructor( constructor (
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private userService: UserService private userService: UserService
) { ) {
super(); super()
} }
buildForm() { buildForm () {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
'new-password': [ '', USER_PASSWORD.VALIDATORS ], 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ]
}); })
this.form.valueChanges.subscribe(data => this.onValueChanged(data)); this.form.valueChanges.subscribe(data => this.onValueChanged(data))
} }
ngOnInit() { ngOnInit () {
this.buildForm(); this.buildForm()
} }
changePassword() { changePassword () {
const newPassword = this.form.value['new-password']; const newPassword = this.form.value['new-password']
const newConfirmedPassword = this.form.value['new-confirmed-password']; const newConfirmedPassword = this.form.value['new-confirmed-password']
this.error = null; this.error = null
if (newPassword !== newConfirmedPassword) { if (newPassword !== newConfirmedPassword) {
this.error = 'The new password and the confirmed password do not correspond.'; this.error = 'The new password and the confirmed password do not correspond.'
return; return
} }
this.userService.changePassword(newPassword).subscribe( this.userService.changePassword(newPassword).subscribe(
() => this.notificationsService.success('Success', 'Password updated.'), () => this.notificationsService.success('Success', 'Password updated.'),
err => this.error = err err => this.error = err
); )
} }
} }

View File

@ -1 +1 @@
export * from './account-change-password.component'; export * from './account-change-password.component'

View File

@ -1,16 +1,16 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { AuthService } from '../../core'; import { AuthService } from '../../core'
import { import {
FormReactive, FormReactive,
User, User,
UserService, UserService,
USER_PASSWORD USER_PASSWORD
} from '../../shared'; } from '../../shared'
@Component({ @Component({
selector: 'my-account-details', selector: 'my-account-details',
@ -18,51 +18,51 @@ import {
}) })
export class AccountDetailsComponent extends FormReactive implements OnInit { export class AccountDetailsComponent extends FormReactive implements OnInit {
@Input() user: User = null; @Input() user: User = null
error: string = null; error: string = null
form: FormGroup; form: FormGroup
formErrors = {}; formErrors = {}
validationMessages = {}; validationMessages = {}
constructor( constructor (
private authService: AuthService, private authService: AuthService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private userService: UserService private userService: UserService
) { ) {
super(); super()
} }
buildForm() { buildForm () {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
displayNSFW: [ this.user.displayNSFW ], displayNSFW: [ this.user.displayNSFW ]
}); })
this.form.valueChanges.subscribe(data => this.onValueChanged(data)); this.form.valueChanges.subscribe(data => this.onValueChanged(data))
} }
ngOnInit() { ngOnInit () {
this.buildForm(); this.buildForm()
} }
updateDetails() { updateDetails () {
const displayNSFW = this.form.value['displayNSFW']; const displayNSFW = this.form.value['displayNSFW']
const details = { const details = {
displayNSFW displayNSFW
}; }
this.error = null; this.error = null
this.userService.updateDetails(details).subscribe( this.userService.updateDetails(details).subscribe(
() => { () => {
this.notificationsService.success('Success', 'Informations updated.'); this.notificationsService.success('Success', 'Informations updated.')
this.authService.refreshUserInformations(); this.authService.refreshUserInformations()
}, },
err => this.error = err err => this.error = err
); )
} }
} }

View File

@ -1 +1 @@
export * from './account-details.component'; export * from './account-details.component'

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router'
import { AccountComponent } from './account.component'; import { AccountComponent } from './account.component'
const accountRoutes: Routes = [ const accountRoutes: Routes = [
{ {
@ -13,7 +13,7 @@ const accountRoutes: Routes = [
} }
} }
} }
]; ]
@NgModule({ @NgModule({
imports: [ RouterModule.forChild(accountRoutes) ], imports: [ RouterModule.forChild(accountRoutes) ],

View File

@ -1,16 +1,16 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { AuthService } from '../core'; import { AuthService } from '../core'
import { import {
FormReactive, FormReactive,
User, User,
UserService, UserService,
USER_PASSWORD USER_PASSWORD
} from '../shared'; } from '../shared'
@Component({ @Component({
selector: 'my-account', selector: 'my-account',
@ -18,11 +18,11 @@ import {
styleUrls: [ './account.component.scss' ] styleUrls: [ './account.component.scss' ]
}) })
export class AccountComponent implements OnInit { export class AccountComponent implements OnInit {
user: User = null; user: User = null
constructor(private authService: AuthService) {} constructor (private authService: AuthService) {}
ngOnInit() { ngOnInit () {
this.user = this.authService.getUser(); this.user = this.authService.getUser()
} }
} }

View File

@ -1,11 +1,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { AccountRoutingModule } from './account-routing.module'; import { AccountRoutingModule } from './account-routing.module'
import { AccountComponent } from './account.component'; import { AccountComponent } from './account.component'
import { AccountChangePasswordComponent } from './account-change-password'; import { AccountChangePasswordComponent } from './account-change-password'
import { AccountDetailsComponent } from './account-details'; import { AccountDetailsComponent } from './account-details'
import { AccountService } from './account.service'; import { AccountService } from './account.service'
import { SharedModule } from '../shared'; import { SharedModule } from '../shared'
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -1,3 +1,3 @@
export * from './account-routing.module'; export * from './account-routing.module'
export * from './account.component'; export * from './account.component'
export * from './account.module'; export * from './account.module'

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router'
const routes: Routes = [ const routes: Routes = [
{ {
@ -11,11 +11,10 @@ const routes: Routes = [
path: 'admin', path: 'admin',
loadChildren: './+admin#AdminModule' loadChildren: './+admin#AdminModule'
} }
]; ]
@NgModule({ @NgModule({
imports: [ RouterModule.forRoot(routes) ], imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ] exports: [ RouterModule ]
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@ -1,9 +1,9 @@
import { Component, OnInit, ViewContainerRef } from '@angular/core'; import { Component, OnInit, ViewContainerRef } from '@angular/core'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { AuthService, ConfigService } from './core'; import { AuthService, ConfigService } from './core'
import { VideoService } from './videos'; import { VideoService } from './videos'
import { UserService } from './shared'; import { UserService } from './shared'
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
@ -22,11 +22,11 @@ export class AppComponent implements OnInit {
preventDuplicates: false, preventDuplicates: false,
preventLastDuplicates: 'visible', preventLastDuplicates: 'visible',
rtl: false rtl: false
}; }
isMenuDisplayed = true; isMenuDisplayed = true
constructor( constructor (
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private configService: ConfigService, private configService: ConfigService,
@ -35,46 +35,46 @@ export class AppComponent implements OnInit {
viewContainerRef: ViewContainerRef viewContainerRef: ViewContainerRef
) {} ) {}
ngOnInit() { ngOnInit () {
if (this.authService.isLoggedIn()) { if (this.authService.isLoggedIn()) {
// The service will automatically redirect to the login page if the token is not valid anymore // The service will automatically redirect to the login page if the token is not valid anymore
this.userService.checkTokenValidity(); this.userService.checkTokenValidity()
} }
this.configService.loadConfig(); this.configService.loadConfig()
this.videoService.loadVideoCategories(); this.videoService.loadVideoCategories()
this.videoService.loadVideoLicences(); this.videoService.loadVideoLicences()
this.videoService.loadVideoLanguages(); this.videoService.loadVideoLanguages()
// Do not display menu on small screens // Do not display menu on small screens
if (window.innerWidth < 600) { if (window.innerWidth < 600) {
this.isMenuDisplayed = false; this.isMenuDisplayed = false
} }
} }
isInAdmin() { isInAdmin () {
return this.router.url.indexOf('/admin/') !== -1; return this.router.url.indexOf('/admin/') !== -1
} }
toggleMenu() { toggleMenu () {
this.isMenuDisplayed = !this.isMenuDisplayed; this.isMenuDisplayed = !this.isMenuDisplayed
} }
getMainColClasses() { getMainColClasses () {
const colSizes = { const colSizes = {
md: 10, md: 10,
sm: 9, sm: 9,
xs: 9 xs: 9
}; }
// Take all width is the menu is not displayed // Take all width is the menu is not displayed
if (this.isMenuDisplayed === false) { if (this.isMenuDisplayed === false) {
Object.keys(colSizes).forEach(col => colSizes[col] = 12); Object.keys(colSizes).forEach(col => colSizes[col] = 12)
} }
const classes = [ 'main-col' ]; const classes = [ 'main-col' ]
Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`)); Object.keys(colSizes).forEach(col => classes.push(`col-${col}-${colSizes[col]}`))
return classes; return classes
} }
} }

View File

@ -1,29 +1,29 @@
import { ApplicationRef, NgModule } from '@angular/core'; import { ApplicationRef, NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser'
import { import {
removeNgStyles, removeNgStyles,
createNewHosts, createNewHosts,
createInputTransfer createInputTransfer
} from '@angularclass/hmr'; } from '@angularclass/hmr'
import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@nglibs/meta'; import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@nglibs/meta'
// TODO: remove, we need this to avoid error in ng2-smart-table // TODO: remove, we need this to avoid error in ng2-smart-table
import 'rxjs/add/operator/toPromise'; import 'rxjs/add/operator/toPromise'
import 'bootstrap-loader'; import 'bootstrap-loader'
import { ENV_PROVIDERS } from './environment'; import { ENV_PROVIDERS } from './environment'
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'; import { AppComponent } from './app.component'
import { AppState, InternalStateType } from './app.service'; import { AppState, InternalStateType } from './app.service'
import { AccountModule } from './account'; import { AccountModule } from './account'
import { CoreModule } from './core'; import { CoreModule } from './core'
import { LoginModule } from './login'; import { LoginModule } from './login'
import { SignupModule } from './signup'; import { SignupModule } from './signup'
import { SharedModule } from './shared'; import { SharedModule } from './shared'
import { VideosModule } from './videos'; import { VideosModule } from './videos'
export function metaFactory(): MetaLoader { export function metaFactory (): MetaLoader {
return new MetaStaticLoader({ return new MetaStaticLoader({
pageTitlePositioning: PageTitlePositioning.PrependPageTitle, pageTitlePositioning: PageTitlePositioning.PrependPageTitle,
pageTitleSeparator: ' - ', pageTitleSeparator: ' - ',
@ -32,19 +32,19 @@ export function metaFactory(): MetaLoader {
title: 'PeerTube', title: 'PeerTube',
description: 'PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser' description: 'PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser'
} }
}); })
} }
type StoreType = { type StoreType = {
state: InternalStateType, state: InternalStateType,
restoreInputValues: () => void, restoreInputValues: () => void,
disposeOldHosts: () => void disposeOldHosts: () => void
}; }
// Application wide providers // Application wide providers
const APP_PROVIDERS = [ const APP_PROVIDERS = [
AppState AppState
]; ]
@NgModule({ @NgModule({
bootstrap: [ AppComponent ], bootstrap: [ AppComponent ],
@ -77,59 +77,59 @@ const APP_PROVIDERS = [
] ]
}) })
export class AppModule { export class AppModule {
constructor( constructor (
public appRef: ApplicationRef, public appRef: ApplicationRef,
public appState: AppState public appState: AppState
) {} ) {}
public hmrOnInit(store: StoreType) { public hmrOnInit (store: StoreType) {
if (!store || !store.state) { if (!store || !store.state) {
return; return
} }
console.log('HMR store', JSON.stringify(store, null, 2)); console.log('HMR store', JSON.stringify(store, null, 2))
/** /**
* Set state * Set state
*/ */
this.appState._state = store.state; this.appState._state = store.state
/** /**
* Set input values * Set input values
*/ */
if ('restoreInputValues' in store) { if ('restoreInputValues' in store) {
let restoreInputValues = store.restoreInputValues; let restoreInputValues = store.restoreInputValues
setTimeout(restoreInputValues); setTimeout(restoreInputValues)
} }
this.appRef.tick(); this.appRef.tick()
delete store.state; delete store.state
delete store.restoreInputValues; delete store.restoreInputValues
} }
public hmrOnDestroy(store: StoreType) { public hmrOnDestroy (store: StoreType) {
const cmpLocation = this.appRef.components.map((cmp) => cmp.location.nativeElement); const cmpLocation = this.appRef.components.map((cmp) => cmp.location.nativeElement)
/** /**
* Save state * Save state
*/ */
const state = this.appState._state; const state = this.appState._state
store.state = state; store.state = state
/** /**
* Recreate root elements * Recreate root elements
*/ */
store.disposeOldHosts = createNewHosts(cmpLocation); store.disposeOldHosts = createNewHosts(cmpLocation)
/** /**
* Save input values * Save input values
*/ */
store.restoreInputValues = createInputTransfer(); store.restoreInputValues = createInputTransfer()
/** /**
* Remove styles * Remove styles
*/ */
removeNgStyles(); removeNgStyles()
} }
public hmrAfterDestroy(store: StoreType) { public hmrAfterDestroy (store: StoreType) {
/** /**
* Display new elements * Display new elements
*/ */
store.disposeOldHosts(); store.disposeOldHosts ()
delete store.disposeOldHosts; delete store.disposeOldHosts
} }
} }

View File

@ -1,12 +1,14 @@
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; /* tslint:disable */
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'
import 'rxjs/add/observable/of'; import { Injectable } from '@angular/core'
import { Observable } from 'rxjs/Observable'
import 'rxjs/add/observable/of'
@Injectable() @Injectable()
export class DataResolver implements Resolve<any> { export class DataResolver implements Resolve<any> {
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return Observable.of({ res: 'I am data'}); return Observable.of({ res: 'I am data'})
} }
} }
@ -15,4 +17,4 @@ export class DataResolver implements Resolve<any> {
*/ */
export const APP_RESOLVER_PROVIDERS = [ export const APP_RESOLVER_PROVIDERS = [
DataResolver DataResolver
]; ]

View File

@ -1,46 +1,48 @@
import { Injectable } from '@angular/core'; /* tslint:disable */
import { Injectable } from '@angular/core'
export type InternalStateType = { export type InternalStateType = {
[key: string]: any [key: string]: any
}; }
@Injectable() @Injectable()
export class AppState { export class AppState {
public _state: InternalStateType = { }; public _state: InternalStateType = { }
/** /**
* Already return a clone of the current state. * Already return a clone of the current state.
*/ */
public get state() { public get state() {
return this._state = this._clone(this._state); return this._state = this._clone(this._state)
} }
/** /**
* Never allow mutation * Never allow mutation
*/ */
public set state(value) { public set state(value) {
throw new Error('do not mutate the `.state` directly'); throw new Error('do not mutate the `.state` directly')
} }
public get(prop?: any) { public get(prop?: any) {
/** /**
* Use our state getter for the clone. * Use our state getter for the clone.
*/ */
const state = this.state; const state = this.state
return state.hasOwnProperty(prop) ? state[prop] : state; return state.hasOwnProperty(prop) ? state[prop] : state
} }
public set(prop: string, value: any) { public set(prop: string, value: any) {
/** /**
* Internally mutate our state. * Internally mutate our state.
*/ */
return this._state[prop] = value; return this._state[prop] = value
} }
private _clone(object: InternalStateType) { private _clone(object: InternalStateType) {
/** /**
* Simple object clone. * Simple object clone.
*/ */
return JSON.parse(JSON.stringify( object )); return JSON.parse(JSON.stringify( object ))
} }
} }

View File

@ -1,6 +1,66 @@
// Do not use the barrel (dependency loop) // Do not use the barrel (dependency loop)
import { UserRole } from '../../../../../shared/models/user.model' import { UserRole } from '../../../../../shared/models/user.model'
import { User } from '../../shared/users/user.model'; import { User } from '../../shared/users/user.model'
export type TokenOptions = {
accessToken: string
refreshToken: string
tokenType: string
}
// Private class only used by User
class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type'
}
accessToken: string
refreshToken: string
tokenType: string
static load () {
const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN)
const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN)
const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE)
if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
return new Tokens({
accessToken: accessTokenLocalStorage,
refreshToken: refreshTokenLocalStorage,
tokenType: tokenTypeLocalStorage
})
}
return null
}
static flush () {
localStorage.removeItem(this.KEYS.ACCESS_TOKEN)
localStorage.removeItem(this.KEYS.REFRESH_TOKEN)
localStorage.removeItem(this.KEYS.TOKEN_TYPE)
}
constructor (hash?: TokenOptions) {
if (hash) {
this.accessToken = hash.accessToken
this.refreshToken = hash.refreshToken
if (hash.tokenType === 'bearer') {
this.tokenType = 'Bearer'
} else {
this.tokenType = hash.tokenType
}
}
}
save () {
localStorage.setItem(Tokens.KEYS.ACCESS_TOKEN, this.accessToken)
localStorage.setItem(Tokens.KEYS.REFRESH_TOKEN, this.refreshToken)
localStorage.setItem(Tokens.KEYS.TOKEN_TYPE, this.tokenType)
}
}
export class AuthUser extends User { export class AuthUser extends User {
private static KEYS = { private static KEYS = {
@ -9,123 +69,69 @@ export class AuthUser extends User {
EMAIL: 'email', EMAIL: 'email',
USERNAME: 'username', USERNAME: 'username',
DISPLAY_NSFW: 'display_nsfw' DISPLAY_NSFW: 'display_nsfw'
}; }
tokens: Tokens; tokens: Tokens
static load() { static load () {
const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME)
if (usernameLocalStorage) { if (usernameLocalStorage) {
return new AuthUser( return new AuthUser(
{ {
id: parseInt(localStorage.getItem(this.KEYS.ID)), id: parseInt(localStorage.getItem(this.KEYS.ID), 10),
username: localStorage.getItem(this.KEYS.USERNAME), username: localStorage.getItem(this.KEYS.USERNAME),
email: localStorage.getItem(this.KEYS.EMAIL), email: localStorage.getItem(this.KEYS.EMAIL),
role: localStorage.getItem(this.KEYS.ROLE) as UserRole, role: localStorage.getItem(this.KEYS.ROLE) as UserRole,
displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true' displayNSFW: localStorage.getItem(this.KEYS.DISPLAY_NSFW) === 'true'
}, },
Tokens.load() Tokens.load()
); )
} }
return null; return null
} }
static flush() { static flush () {
localStorage.removeItem(this.KEYS.USERNAME); localStorage.removeItem(this.KEYS.USERNAME)
localStorage.removeItem(this.KEYS.ID); localStorage.removeItem(this.KEYS.ID)
localStorage.removeItem(this.KEYS.ROLE); localStorage.removeItem(this.KEYS.ROLE)
localStorage.removeItem(this.KEYS.DISPLAY_NSFW); localStorage.removeItem(this.KEYS.DISPLAY_NSFW)
Tokens.flush(); Tokens.flush()
} }
constructor(userHash: { constructor (userHash: {
id: number, id: number,
username: string, username: string,
role: UserRole, role: UserRole,
email: string, email: string,
displayNSFW: boolean displayNSFW: boolean
}, hashTokens: any) { }, hashTokens: TokenOptions) {
super(userHash); super(userHash)
this.tokens = new Tokens(hashTokens); this.tokens = new Tokens(hashTokens)
} }
getAccessToken() { getAccessToken () {
return this.tokens.access_token; return this.tokens.accessToken
} }
getRefreshToken() { getRefreshToken () {
return this.tokens.refresh_token; return this.tokens.refreshToken
} }
getTokenType() { getTokenType () {
return this.tokens.token_type; return this.tokens.tokenType
} }
refreshTokens(access_token: string, refresh_token: string) { refreshTokens (accessToken: string, refreshToken: string) {
this.tokens.access_token = access_token; this.tokens.accessToken = accessToken
this.tokens.refresh_token = refresh_token; this.tokens.refreshToken = refreshToken
} }
save() { save () {
localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); localStorage.setItem(AuthUser.KEYS.ID, this.id.toString())
localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); localStorage.setItem(AuthUser.KEYS.USERNAME, this.username)
localStorage.setItem(AuthUser.KEYS.ROLE, this.role); localStorage.setItem(AuthUser.KEYS.ROLE, this.role)
localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW)); localStorage.setItem(AuthUser.KEYS.DISPLAY_NSFW, JSON.stringify(this.displayNSFW))
this.tokens.save(); this.tokens.save()
}
}
// Private class only used by User
class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
REFRESH_TOKEN: 'refresh_token',
TOKEN_TYPE: 'token_type',
};
access_token: string;
refresh_token: string;
token_type: string;
static load() {
const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN);
const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN);
const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE);
if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) {
return new Tokens({
access_token: accessTokenLocalStorage,
refresh_token: refreshTokenLocalStorage,
token_type: tokenTypeLocalStorage
});
}
return null;
}
static flush() {
localStorage.removeItem(this.KEYS.ACCESS_TOKEN);
localStorage.removeItem(this.KEYS.REFRESH_TOKEN);
localStorage.removeItem(this.KEYS.TOKEN_TYPE);
}
constructor(hash?: any) {
if (hash) {
this.access_token = hash.access_token;
this.refresh_token = hash.refresh_token;
if (hash.token_type === 'bearer') {
this.token_type = 'Bearer';
} else {
this.token_type = hash.token_type;
}
}
}
save() {
localStorage.setItem('access_token', this.access_token);
localStorage.setItem('refresh_token', this.refresh_token);
localStorage.setItem('token_type', this.token_type);
} }
} }

View File

@ -1,40 +1,40 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Headers, Http, Response, URLSearchParams } from '@angular/http'; import { Headers, Http, Response, URLSearchParams } from '@angular/http'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/mergeMap'
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/throw'
import { NotificationsService } from 'angular2-notifications'; import { NotificationsService } from 'angular2-notifications'
import { AuthStatus } from './auth-status.model'; import { AuthStatus } from './auth-status.model'
import { AuthUser } from './auth-user.model'; import { AuthUser } from './auth-user.model'
// Do not use the barrel (dependency loop) // Do not use the barrel (dependency loop)
import { RestExtractor } from '../../shared/rest'; import { RestExtractor } from '../../shared/rest'
@Injectable() @Injectable()
export class AuthService { export class AuthService {
private static BASE_CLIENT_URL = API_URL + '/api/v1/clients/local'; private static BASE_CLIENT_URL = API_URL + '/api/v1/clients/local'
private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token'; private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token'
private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me'; private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me'
loginChangedSource: Observable<AuthStatus>; loginChangedSource: Observable<AuthStatus>
private clientId: string; private clientId: string
private clientSecret: string; private clientSecret: string
private loginChanged: Subject<AuthStatus>; private loginChanged: Subject<AuthStatus>
private user: AuthUser = null; private user: AuthUser = null
constructor( constructor (
private http: Http, private http: Http,
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private restExtractor: RestExtractor, private restExtractor: RestExtractor,
private router: Router private router: Router
) { ) {
this.loginChanged = new Subject<AuthStatus>(); this.loginChanged = new Subject<AuthStatus>()
this.loginChangedSource = this.loginChanged.asObservable(); this.loginChangedSource = this.loginChanged.asObservable()
// Fetch the client_id/client_secret // Fetch the client_id/client_secret
// FIXME: save in local storage? // FIXME: save in local storage?
@ -43,120 +43,120 @@ export class AuthService {
.catch((res) => this.restExtractor.handleError(res)) .catch((res) => this.restExtractor.handleError(res))
.subscribe( .subscribe(
result => { result => {
this.clientId = result.client_id; this.clientId = result.client_id
this.clientSecret = result.client_secret; this.clientSecret = result.client_secret
console.log('Client credentials loaded.'); console.log('Client credentials loaded.')
}, },
error => { error => {
let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`; let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n`
errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'; errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
// We put a bigger timeout // We put a bigger timeout
// This is an important message // This is an important message
this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }); this.notificationsService.error('Error', errorMessage, { timeOut: 7000 })
} }
); )
// Return null if there is nothing to load // Return null if there is nothing to load
this.user = AuthUser.load(); this.user = AuthUser.load()
} }
getRefreshToken() { getRefreshToken () {
if (this.user === null) return null; if (this.user === null) return null
return this.user.getRefreshToken(); return this.user.getRefreshToken()
} }
getRequestHeaderValue() { getRequestHeaderValue () {
return `${this.getTokenType()} ${this.getAccessToken()}`; return `${this.getTokenType()} ${this.getAccessToken()}`
} }
getAccessToken() { getAccessToken () {
if (this.user === null) return null; if (this.user === null) return null
return this.user.getAccessToken(); return this.user.getAccessToken()
} }
getTokenType() { getTokenType () {
if (this.user === null) return null; if (this.user === null) return null
return this.user.getTokenType(); return this.user.getTokenType()
} }
getUser(): AuthUser { getUser () {
return this.user; return this.user
} }
isAdmin() { isAdmin () {
if (this.user === null) return false; if (this.user === null) return false
return this.user.isAdmin(); return this.user.isAdmin()
} }
isLoggedIn() { isLoggedIn () {
if (this.getAccessToken()) { if (this.getAccessToken()) {
return true; return true
} else { } else {
return false; return false
} }
} }
login(username: string, password: string) { login (username: string, password: string) {
let body = new URLSearchParams(); let body = new URLSearchParams()
body.set('client_id', this.clientId); body.set('client_id', this.clientId)
body.set('client_secret', this.clientSecret); body.set('client_secret', this.clientSecret)
body.set('response_type', 'code'); body.set('response_type', 'code')
body.set('grant_type', 'password'); body.set('grant_type', 'password')
body.set('scope', 'upload'); body.set('scope', 'upload')
body.set('username', username); body.set('username', username)
body.set('password', password); body.set('password', password)
let headers = new Headers(); let headers = new Headers()
headers.append('Content-Type', 'application/x-www-form-urlencoded'); headers.append('Content-Type', 'application/x-www-form-urlencoded')
let options = { let options = {
headers: headers headers: headers
}; }
return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
.map(this.restExtractor.extractDataGet) .map(this.restExtractor.extractDataGet)
.map(res => { .map(res => {
res.username = username; res.username = username
return res; return res
}) })
.flatMap(res => this.mergeUserInformations(res)) .flatMap(res => this.mergeUserInformations(res))
.map(res => this.handleLogin(res)) .map(res => this.handleLogin(res))
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
logout() { logout () {
// TODO: make an HTTP request to revoke the tokens // TODO: make an HTTP request to revoke the tokens
this.user = null; this.user = null
AuthUser.flush(); AuthUser.flush()
this.setStatus(AuthStatus.LoggedOut); this.setStatus(AuthStatus.LoggedOut)
} }
refreshAccessToken() { refreshAccessToken () {
console.log('Refreshing token...'); console.log('Refreshing token...')
const refreshToken = this.getRefreshToken(); const refreshToken = this.getRefreshToken()
let body = new URLSearchParams(); let body = new URLSearchParams()
body.set('refresh_token', refreshToken); body.set('refresh_token', refreshToken)
body.set('client_id', this.clientId); body.set('client_id', this.clientId)
body.set('client_secret', this.clientSecret); body.set('client_secret', this.clientSecret)
body.set('response_type', 'code'); body.set('response_type', 'code')
body.set('grant_type', 'refresh_token'); body.set('grant_type', 'refresh_token')
let headers = new Headers(); let headers = new Headers()
headers.append('Content-Type', 'application/x-www-form-urlencoded'); headers.append('Content-Type', 'application/x-www-form-urlencoded')
let options = { let options = {
headers: headers headers: headers
}; }
return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
.map(this.restExtractor.extractDataGet) .map(this.restExtractor.extractDataGet)
@ -164,41 +164,41 @@ export class AuthService {
.catch((res: Response) => { .catch((res: Response) => {
// The refresh token is invalid? // The refresh token is invalid?
if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') {
console.error('Cannot refresh token -> logout...'); console.error('Cannot refresh token -> logout...')
this.logout(); this.logout()
this.router.navigate(['/login']); this.router.navigate(['/login'])
return Observable.throw({ return Observable.throw({
json: () => '', json: () => '',
text: () => 'You need to reconnect.' text: () => 'You need to reconnect.'
}); })
} }
return this.restExtractor.handleError(res); return this.restExtractor.handleError(res)
}); })
} }
refreshUserInformations() { refreshUserInformations () {
const obj = { const obj = {
access_token: this.user.getAccessToken() access_token: this.user.getAccessToken()
}; }
this.mergeUserInformations(obj) this.mergeUserInformations (obj)
.subscribe( .subscribe(
res => { res => {
this.user.displayNSFW = res.displayNSFW; this.user.displayNSFW = res.displayNSFW
this.user.role = res.role; this.user.role = res.role
this.user.save(); this.user.save()
} }
); )
} }
private mergeUserInformations(obj: { access_token: string }) { private mergeUserInformations (obj: { access_token: string }) {
// Do not call authHttp here to avoid circular dependencies headaches // Do not call authHttp here to avoid circular dependencies headaches
const headers = new Headers(); const headers = new Headers()
headers.set('Authorization', `Bearer ${obj.access_token}`); headers.set('Authorization', `Bearer ${obj.access_token}`)
return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers })
.map(res => res.json()) .map(res => res.json())
@ -207,38 +207,38 @@ export class AuthService {
id: res.id, id: res.id,
role: res.role, role: res.role,
displayNSFW: res.displayNSFW displayNSFW: res.displayNSFW
}; }
return Object.assign(obj, newProperties); return Object.assign(obj, newProperties)
} }
); )
} }
private handleLogin (obj: any) { private handleLogin (obj: any) {
const id = obj.id; const id = obj.id
const username = obj.username; const username = obj.username
const role = obj.role; const role = obj.role
const email = obj.email; const email = obj.email
const displayNSFW = obj.displayNSFW; const displayNSFW = obj.displayNSFW
const hashTokens = { const hashTokens = {
access_token: obj.access_token, accessToken: obj.access_token,
token_type: obj.token_type, tokenType: obj.token_type,
refresh_token: obj.refresh_token refreshToken: obj.refresh_token
}; }
this.user = new AuthUser({ id, username, role, displayNSFW, email }, hashTokens); this.user = new AuthUser({ id, username, role, displayNSFW, email }, hashTokens)
this.user.save(); this.user.save()
this.setStatus(AuthStatus.LoggedIn); this.setStatus(AuthStatus.LoggedIn)
} }
private handleRefreshToken (obj: any) { private handleRefreshToken (obj: any) {
this.user.refreshTokens(obj.access_token, obj.refresh_token); this.user.refreshTokens(obj.access_token, obj.refresh_token)
this.user.save(); this.user.save()
} }
private setStatus(status: AuthStatus) { private setStatus (status: AuthStatus) {
this.loginChanged.next(status); this.loginChanged.next(status)
} }
} }

View File

@ -1,3 +1,3 @@
export * from './auth-status.model'; export * from './auth-status.model'
export * from './auth-user.model'; export * from './auth-user.model'
export * from './auth.service' export * from './auth.service'

View File

@ -1,11 +1,11 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Http } from '@angular/http'; import { Http } from '@angular/http'
import { RestExtractor } from '../../shared/rest'; import { RestExtractor } from '../../shared/rest'
@Injectable() @Injectable()
export class ConfigService { export class ConfigService {
private static BASE_CONFIG_URL = API_URL + '/api/v1/config/'; private static BASE_CONFIG_URL = API_URL + '/api/v1/config/'
private config: { private config: {
signup: { signup: {
@ -15,22 +15,22 @@ export class ConfigService {
signup: { signup: {
enabled: false enabled: false
} }
}; }
constructor( constructor (
private http: Http, private http: Http,
private restExtractor: RestExtractor, private restExtractor: RestExtractor
) {} ) {}
loadConfig() { loadConfig () {
this.http.get(ConfigService.BASE_CONFIG_URL) this.http.get(ConfigService.BASE_CONFIG_URL)
.map(this.restExtractor.extractDataGet) .map(this.restExtractor.extractDataGet)
.subscribe(data => { .subscribe(data => {
this.config = data; this.config = data
}); })
} }
getConfig() { getConfig () {
return this.config; return this.config
} }
} }

View File

@ -1 +1 @@
export * from './config.service'; export * from './config.service'

View File

@ -1,12 +1,12 @@
import { Component, HostListener, OnInit, ViewChild } from '@angular/core'; import { Component, HostListener, OnInit, ViewChild } from '@angular/core'
import { ModalDirective } from 'ngx-bootstrap/modal'; import { ModalDirective } from 'ngx-bootstrap/modal'
import { ConfirmService } from './confirm.service'; import { ConfirmService } from './confirm.service'
export interface ConfigChangedEvent { export interface ConfigChangedEvent {
columns: { [id: string]: { isDisplayed: boolean }; }; columns: { [id: string]: { isDisplayed: boolean } }
config: { resultsPerPage: number }; config: { resultsPerPage: number }
} }
@Component({ @Component({
@ -14,48 +14,48 @@ export interface ConfigChangedEvent {
templateUrl: './confirm.component.html' templateUrl: './confirm.component.html'
}) })
export class ConfirmComponent implements OnInit { export class ConfirmComponent implements OnInit {
@ViewChild('confirmModal') confirmModal: ModalDirective; @ViewChild('confirmModal') confirmModal: ModalDirective
title = ''; title = ''
message = ''; message = ''
constructor (private confirmService: ConfirmService) { constructor (private confirmService: ConfirmService) {
// Empty // Empty
} }
ngOnInit() { ngOnInit () {
this.confirmModal.config = { this.confirmModal.config = {
backdrop: 'static', backdrop: 'static',
keyboard: false keyboard: false
}; }
this.confirmService.showConfirm.subscribe( this.confirmService.showConfirm.subscribe(
({ title, message }) => { ({ title, message }) => {
this.title = title; this.title = title
this.message = message; this.message = message
this.showModal(); this.showModal()
} }
); )
} }
@HostListener('keydown.enter') @HostListener('keydown.enter')
confirm() { confirm () {
this.confirmService.confirmResponse.next(true); this.confirmService.confirmResponse.next(true)
this.hideModal(); this.hideModal()
} }
@HostListener('keydown.esc') @HostListener('keydown.esc')
abort() { abort () {
this.confirmService.confirmResponse.next(false); this.confirmService.confirmResponse.next(false)
this.hideModal(); this.hideModal()
} }
showModal() { showModal () {
this.confirmModal.show(); this.confirmModal.show()
} }
hideModal() { hideModal () {
this.confirmModal.hide(); this.confirmModal.hide()
} }
} }

View File

@ -1,15 +1,15 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject'
import 'rxjs/add/operator/first'; import 'rxjs/add/operator/first'
@Injectable() @Injectable()
export class ConfirmService { export class ConfirmService {
showConfirm = new Subject<{ title, message }>(); showConfirm = new Subject<{ title, message }>()
confirmResponse = new Subject<boolean>(); confirmResponse = new Subject<boolean>()
confirm(message = '', title = '') { confirm (message = '', title = '') {
this.showConfirm.next({ title, message }); this.showConfirm.next({ title, message })
return this.confirmResponse.asObservable().first(); return this.confirmResponse.asObservable().first()
} }
} }

View File

@ -1,2 +1,2 @@
export * from './confirm.component'; export * from './confirm.component'
export * from './confirm.service'; export * from './confirm.service'

View File

@ -1,16 +1,16 @@
import { NgModule, Optional, SkipSelf } from '@angular/core'; import { NgModule, Optional, SkipSelf } from '@angular/core'
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common'
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http'
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router'
import { SimpleNotificationsModule } from 'angular2-notifications'; import { SimpleNotificationsModule } from 'angular2-notifications'
import { ModalModule } from 'ngx-bootstrap/modal'; import { ModalModule } from 'ngx-bootstrap/modal'
import { AuthService } from './auth'; import { AuthService } from './auth'
import { ConfigService } from './config'; import { ConfigService } from './config'
import { ConfirmComponent, ConfirmService } from './confirm'; import { ConfirmComponent, ConfirmService } from './confirm'
import { MenuComponent, MenuAdminComponent } from './menu'; import { MenuComponent, MenuAdminComponent } from './menu'
import { throwIfAlreadyLoaded } from './module-import-guard'; import { throwIfAlreadyLoaded } from './module-import-guard'
@NgModule({ @NgModule({
imports: [ imports: [
@ -43,7 +43,7 @@ import { throwIfAlreadyLoaded } from './module-import-guard';
] ]
}) })
export class CoreModule { export class CoreModule {
constructor( @Optional() @SkipSelf() parentModule: CoreModule) { constructor ( @Optional() @SkipSelf() parentModule: CoreModule) {
throwIfAlreadyLoaded(parentModule, 'CoreModule'); throwIfAlreadyLoaded(parentModule, 'CoreModule')
} }
} }

View File

@ -1,5 +1,5 @@
export * from './auth'; export * from './auth'
export * from './config'; export * from './config'
export * from './confirm'; export * from './confirm'
export * from './menu'; export * from './menu'
export * from './core.module' export * from './core.module'

View File

@ -1,2 +1,2 @@
export * from './menu.component'; export * from './menu.component'
export * from './menu-admin.component'; export * from './menu-admin.component'

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
@Component({ @Component({
selector: 'my-menu-admin', selector: 'my-menu-admin',

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { AuthService, AuthStatus } from '../auth'; import { AuthService, AuthStatus } from '../auth'
import { ConfigService } from '../config'; import { ConfigService } from '../config'
@Component({ @Component({
selector: 'my-menu', selector: 'my-menu',
@ -10,7 +10,7 @@ import { ConfigService } from '../config';
styleUrls: [ './menu.component.scss' ] styleUrls: [ './menu.component.scss' ]
}) })
export class MenuComponent implements OnInit { export class MenuComponent implements OnInit {
isLoggedIn: boolean; isLoggedIn: boolean
constructor ( constructor (
private authService: AuthService, private authService: AuthService,
@ -18,35 +18,35 @@ export class MenuComponent implements OnInit {
private router: Router private router: Router
) {} ) {}
ngOnInit() { ngOnInit () {
this.isLoggedIn = this.authService.isLoggedIn(); this.isLoggedIn = this.authService.isLoggedIn()
this.authService.loginChangedSource.subscribe( this.authService.loginChangedSource.subscribe(
status => { status => {
if (status === AuthStatus.LoggedIn) { if (status === AuthStatus.LoggedIn) {
this.isLoggedIn = true; this.isLoggedIn = true
console.log('Logged in.'); console.log('Logged in.')
} else if (status === AuthStatus.LoggedOut) { } else if (status === AuthStatus.LoggedOut) {
this.isLoggedIn = false; this.isLoggedIn = false
console.log('Logged out.'); console.log('Logged out.')
} else { } else {
console.error('Unknown auth status: ' + status); console.error('Unknown auth status: ' + status)
} }
} }
); )
} }
isRegistrationEnabled() { isRegistrationEnabled () {
return this.configService.getConfig().signup.enabled; return this.configService.getConfig().signup.enabled
} }
isUserAdmin() { isUserAdmin () {
return this.authService.isAdmin(); return this.authService.isAdmin()
} }
logout() { logout () {
this.authService.logout(); this.authService.logout()
// Redirect to home page // Redirect to home page
this.router.navigate(['/videos/list']); this.router.navigate(['/videos/list'])
} }
} }

View File

@ -1,5 +1,5 @@
export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { export function throwIfAlreadyLoaded (parentModule: any, moduleName: string) {
if (parentModule) { if (parentModule) {
throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`); throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`)
} }
} }

View File

@ -1,3 +1,5 @@
/* tslint:disable */
/** /**
* Angular 2 * Angular 2
*/ */

View File

@ -1 +1 @@
export * from './app.module'; export * from './app.module'

View File

@ -1,3 +1,3 @@
export * from './login-routing.module'; export * from './login-routing.module'
export * from './login.component'; export * from './login.component'
export * from './login.module'; export * from './login.module'

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router'
import { LoginComponent } from './login.component'; import { LoginComponent } from './login.component'
const loginRoutes: Routes = [ const loginRoutes: Routes = [
{ {
@ -13,7 +13,7 @@ const loginRoutes: Routes = [
} }
} }
} }
]; ]
@NgModule({ @NgModule({
imports: [ RouterModule.forChild(loginRoutes) ], imports: [ RouterModule.forChild(loginRoutes) ],

View File

@ -1,9 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { AuthService } from '../core'; import { AuthService } from '../core'
import { FormReactive } from '../shared'; import { FormReactive } from '../shared'
@Component({ @Component({
selector: 'my-login', selector: 'my-login',
@ -11,60 +11,60 @@ import { FormReactive } from '../shared';
}) })
export class LoginComponent extends FormReactive implements OnInit { export class LoginComponent extends FormReactive implements OnInit {
error: string = null; error: string = null
form: FormGroup; form: FormGroup
formErrors = { formErrors = {
'username': '', 'username': '',
'password': '' 'password': ''
}; }
validationMessages = { validationMessages = {
'username': { 'username': {
'required': 'Username is required.', 'required': 'Username is required.'
}, },
'password': { 'password': {
'required': 'Password is required.' 'required': 'Password is required.'
} }
}; }
constructor( constructor (
private authService: AuthService, private authService: AuthService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router private router: Router
) { ) {
super(); super()
} }
buildForm() { buildForm () {
this.form = this.formBuilder.group({ this.form = this.formBuilder.group({
username: [ '', Validators.required ], username: [ '', Validators.required ],
password: [ '', Validators.required ], password: [ '', Validators.required ]
}); })
this.form.valueChanges.subscribe(data => this.onValueChanged(data)); this.form.valueChanges.subscribe(data => this.onValueChanged(data))
} }
ngOnInit() { ngOnInit () {
this.buildForm(); this.buildForm()
} }
login() { login () {
this.error = null; this.error = null
const { username, password } = this.form.value; const { username, password } = this.form.value
this.authService.login(username, password).subscribe( this.authService.login(username, password).subscribe(
result => this.router.navigate(['/videos/list']), result => this.router.navigate(['/videos/list']),
error => { error => {
console.error(error.json); console.error(error.json)
if (error.json.error === 'invalid_grant') { if (error.json.error === 'invalid_grant') {
this.error = 'Credentials are invalid.'; this.error = 'Credentials are invalid.'
} else { } else {
this.error = `${error.json.error}: ${error.json.error_description}`; this.error = `${error.json.error}: ${error.json.error_description}`
} }
} }
); )
} }
} }

View File

@ -1,8 +1,8 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { LoginRoutingModule } from './login-routing.module'; import { LoginRoutingModule } from './login-routing.module'
import { LoginComponent } from './login.component'; import { LoginComponent } from './login.component'
import { SharedModule } from '../shared'; import { SharedModule } from '../shared'
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -1,4 +1,4 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { import {
ConnectionBackend, ConnectionBackend,
Headers, Headers,
@ -9,79 +9,79 @@ import {
RequestOptionsArgs, RequestOptionsArgs,
Response, Response,
XHRBackend XHRBackend
} from '@angular/http'; } from '@angular/http'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
import { AuthService } from '../../core'; import { AuthService } from '../../core'
@Injectable() @Injectable()
export class AuthHttp extends Http { export class AuthHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) { constructor (backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) {
super(backend, defaultOptions); super(backend, defaultOptions)
} }
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {}; if (!options) options = {}
options.headers = new Headers(); options.headers = new Headers()
this.setAuthorizationHeader(options.headers); this.setAuthorizationHeader(options.headers)
return super.request(url, options) return super.request(url, options)
.catch((err) => { .catch((err) => {
if (err.status === 401) { if (err.status === 401) {
return this.handleTokenExpired(url, options); return this.handleTokenExpired(url, options)
} }
return Observable.throw(err); return Observable.throw(err)
}); })
} }
delete(url: string, options?: RequestOptionsArgs): Observable<Response> { delete (url: string, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {}; if (!options) options = {}
options.method = RequestMethod.Delete; options.method = RequestMethod.Delete
return this.request(url, options); return this.request(url, options)
} }
get(url: string, options?: RequestOptionsArgs): Observable<Response> { get (url: string, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {}; if (!options) options = {}
options.method = RequestMethod.Get; options.method = RequestMethod.Get
return this.request(url, options); return this.request(url, options)
} }
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { post (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {}; if (!options) options = {}
options.method = RequestMethod.Post; options.method = RequestMethod.Post
options.body = body; options.body = body
return this.request(url, options); return this.request(url, options)
} }
put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { put (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {}; if (!options) options = {}
options.method = RequestMethod.Put; options.method = RequestMethod.Put
options.body = body; options.body = body
return this.request(url, options); return this.request(url, options)
} }
private handleTokenExpired(url: string | Request, options: RequestOptionsArgs) { private handleTokenExpired (url: string | Request, options: RequestOptionsArgs) {
return this.authService.refreshAccessToken() return this.authService.refreshAccessToken()
.flatMap(() => { .flatMap(() => {
this.setAuthorizationHeader(options.headers); this.setAuthorizationHeader(options.headers)
return super.request(url, options); return super.request(url, options)
}); })
} }
private setAuthorizationHeader(headers: Headers) { private setAuthorizationHeader (headers: Headers) {
headers.set('Authorization', this.authService.getRequestHeaderValue()); headers.set('Authorization', this.authService.getRequestHeaderValue())
} }
} }
export function useFactory(backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) { export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) {
return new AuthHttp(backend, defaultOptions, authService); return new AuthHttp(backend, defaultOptions, authService)
} }
export const AUTH_HTTP_PROVIDERS = [ export const AUTH_HTTP_PROVIDERS = [
@ -89,5 +89,5 @@ export const AUTH_HTTP_PROVIDERS = [
provide: AuthHttp, provide: AuthHttp,
useFactory, useFactory,
deps: [ XHRBackend, RequestOptions, AuthService ] deps: [ XHRBackend, RequestOptions, AuthService ]
}, }
]; ]

View File

@ -1 +1 @@
export * from './auth-http.service'; export * from './auth-http.service'

View File

@ -1,38 +1,38 @@
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms'
export abstract class FormReactive { export abstract class FormReactive {
abstract form: FormGroup; abstract form: FormGroup
abstract formErrors: Object; abstract formErrors: Object
abstract validationMessages: Object; abstract validationMessages: Object
abstract buildForm(): void; abstract buildForm (): void
protected onValueChanged(data?: any) { protected onValueChanged (data?: any) {
for (const field in this.formErrors) { for (const field in this.formErrors) {
// clear previous error message (if any) // clear previous error message (if any)
this.formErrors[field] = ''; this.formErrors[field] = ''
const control = this.form.get(field); const control = this.form.get(field)
if (control && control.dirty && !control.valid) { if (control && control.dirty && !control.valid) {
const messages = this.validationMessages[field]; const messages = this.validationMessages[field]
for (const key in control.errors) { for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' '; this.formErrors[field] += messages[key] + ' '
} }
} }
} }
} }
// Same as onValueChanged but force checking even if the field is not dirty // Same as onValueChanged but force checking even if the field is not dirty
protected forceCheck() { protected forceCheck () {
for (const field in this.formErrors) { for (const field in this.formErrors) {
// clear previous error message (if any) // clear previous error message (if any)
this.formErrors[field] = ''; this.formErrors[field] = ''
const control = this.form.get(field); const control = this.form.get(field)
if (control && !control.valid) { if (control && !control.valid) {
const messages = this.validationMessages[field]; const messages = this.validationMessages[field]
for (const key in control.errors) { for (const key in control.errors) {
this.formErrors[field] += messages[key] + ' '; this.formErrors[field] += messages[key] + ' '
} }
} }
} }

View File

@ -1,14 +1,14 @@
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms'
export function validateHost(c: FormControl) { export function validateHost (c: FormControl) {
// Thanks to http://stackoverflow.com/a/106223 // Thanks to http://stackoverflow.com/a/106223
const HOST_REGEXP = new RegExp( const HOST_REGEXP = new RegExp(
'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' '^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$'
); )
return HOST_REGEXP.test(c.value) ? null : { return HOST_REGEXP.test(c.value) ? null : {
validateHost: { validateHost: {
valid: false valid: false
} }
}; }
} }

View File

@ -1,4 +1,4 @@
export * from './host.validator'; export * from './host.validator'
export * from './user'; export * from './user'
export * from './video-abuse'; export * from './video-abuse'
export * from './video'; export * from './video'

View File

@ -1,4 +1,4 @@
import { Validators } from '@angular/forms'; import { Validators } from '@angular/forms'
export const USER_USERNAME = { export const USER_USERNAME = {
VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ], VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
@ -7,18 +7,18 @@ export const USER_USERNAME = {
'minlength': 'Username must be at least 3 characters long.', 'minlength': 'Username must be at least 3 characters long.',
'maxlength': 'Username cannot be more than 20 characters long.' 'maxlength': 'Username cannot be more than 20 characters long.'
} }
}; }
export const USER_EMAIL = { export const USER_EMAIL = {
VALIDATORS: [ Validators.required, Validators.email ], VALIDATORS: [ Validators.required, Validators.email ],
MESSAGES: { MESSAGES: {
'required': 'Email is required.', 'required': 'Email is required.',
'email': 'Email must be valid.', 'email': 'Email must be valid.'
} }
}; }
export const USER_PASSWORD = { export const USER_PASSWORD = {
VALIDATORS: [ Validators.required, Validators.minLength(6) ], VALIDATORS: [ Validators.required, Validators.minLength(6) ],
MESSAGES: { MESSAGES: {
'required': 'Password is required.', 'required': 'Password is required.',
'minlength': 'Password must be at least 6 characters long.', 'minlength': 'Password must be at least 6 characters long.'
} }
}; }

View File

@ -1,4 +1,4 @@
import { Validators } from '@angular/forms'; import { Validators } from '@angular/forms'
export const VIDEO_ABUSE_REASON = { export const VIDEO_ABUSE_REASON = {
VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ], VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
@ -7,4 +7,4 @@ export const VIDEO_ABUSE_REASON = {
'minlength': 'Report reson must be at least 2 characters long.', 'minlength': 'Report reson must be at least 2 characters long.',
'maxlength': 'Report reson cannot be more than 300 characters long.' 'maxlength': 'Report reson cannot be more than 300 characters long.'
} }
}; }

View File

@ -1,4 +1,4 @@
import { Validators } from '@angular/forms'; import { Validators } from '@angular/forms'
export const VIDEO_NAME = { export const VIDEO_NAME = {
VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ], VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ],
@ -7,26 +7,26 @@ export const VIDEO_NAME = {
'minlength': 'Video name must be at least 3 characters long.', 'minlength': 'Video name must be at least 3 characters long.',
'maxlength': 'Video name cannot be more than 50 characters long.' 'maxlength': 'Video name cannot be more than 50 characters long.'
} }
}; }
export const VIDEO_CATEGORY = { export const VIDEO_CATEGORY = {
VALIDATORS: [ Validators.required ], VALIDATORS: [ Validators.required ],
MESSAGES: { MESSAGES: {
'required': 'Video category is required.' 'required': 'Video category is required.'
} }
}; }
export const VIDEO_LICENCE = { export const VIDEO_LICENCE = {
VALIDATORS: [ Validators.required ], VALIDATORS: [ Validators.required ],
MESSAGES: { MESSAGES: {
'required': 'Video licence is required.' 'required': 'Video licence is required.'
} }
}; }
export const VIDEO_LANGUAGE = { export const VIDEO_LANGUAGE = {
VALIDATORS: [ ], VALIDATORS: [ ],
MESSAGES: {} MESSAGES: {}
}; }
export const VIDEO_DESCRIPTION = { export const VIDEO_DESCRIPTION = {
VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ],
@ -35,7 +35,7 @@ export const VIDEO_DESCRIPTION = {
'minlength': 'Video description must be at least 3 characters long.', 'minlength': 'Video description must be at least 3 characters long.',
'maxlength': 'Video description cannot be more than 250 characters long.' 'maxlength': 'Video description cannot be more than 250 characters long.'
} }
}; }
export const VIDEO_TAGS = { export const VIDEO_TAGS = {
VALIDATORS: [ Validators.minLength(2), Validators.maxLength(10) ], VALIDATORS: [ Validators.minLength(2), Validators.maxLength(10) ],
@ -43,4 +43,4 @@ export const VIDEO_TAGS = {
'minlength': 'A tag should be more than 2 characters long.', 'minlength': 'A tag should be more than 2 characters long.',
'maxlength': 'A tag should be less than 10 characters long.' 'maxlength': 'A tag should be less than 10 characters long.'
} }
}; }

View File

@ -1,2 +1,2 @@
export * from './form-validators'; export * from './form-validators'
export * from './form-reactive'; export * from './form-reactive'

View File

@ -1,8 +1,8 @@
export * from './auth'; export * from './auth'
export * from './forms'; export * from './forms'
export * from './rest'; export * from './rest'
export * from './search'; export * from './search'
export * from './users'; export * from './users'
export * from './video-abuse'; export * from './video-abuse'
export * from './shared.module'; export * from './shared.module'
export * from './utils'; export * from './utils'

View File

@ -1,4 +1,4 @@
export * from './rest-data-source'; export * from './rest-data-source'
export * from './rest-extractor.service'; export * from './rest-extractor.service'
export * from './rest-pagination'; export * from './rest-pagination'
export * from './rest.service'; export * from './rest.service'

View File

@ -1,51 +1,51 @@
import { Http, RequestOptionsArgs, URLSearchParams, } from '@angular/http'; import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http'
import { ServerDataSource } from 'ng2-smart-table'; import { ServerDataSource } from 'ng2-smart-table'
export class RestDataSource extends ServerDataSource { export class RestDataSource extends ServerDataSource {
constructor(http: Http, endpoint: string) { constructor (http: Http, endpoint: string) {
const options = { const options = {
endPoint: endpoint, endPoint: endpoint,
sortFieldKey: 'sort', sortFieldKey: 'sort',
dataKey: 'data' dataKey: 'data'
}; }
super(http, options); super(http, options)
}
protected extractTotalFromResponse(res) {
const rawData = res.json();
return rawData ? parseInt(rawData.total) : 0;
} }
protected addSortRequestOptions(requestOptions: RequestOptionsArgs) { protected extractTotalFromResponse (res: Response) {
let searchParams: URLSearchParams = <URLSearchParams> requestOptions.search; const rawData = res.json()
return rawData ? parseInt(rawData.total, 10) : 0
}
protected addSortRequestOptions (requestOptions: RequestOptionsArgs) {
const searchParams = requestOptions.search as URLSearchParams
if (this.sortConf) { if (this.sortConf) {
this.sortConf.forEach((fieldConf) => { this.sortConf.forEach((fieldConf) => {
const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''; const sortPrefix = fieldConf.direction === 'desc' ? '-' : ''
searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field); searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field)
}); })
} }
return requestOptions; return requestOptions
} }
protected addPagerRequestOptions(requestOptions: RequestOptionsArgs) { protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) {
let searchParams: URLSearchParams = <URLSearchParams> requestOptions.search; const searchParams = requestOptions.search as URLSearchParams
if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) {
const perPage = this.pagingConf['perPage']; const perPage = this.pagingConf['perPage']
const page = this.pagingConf['page']; const page = this.pagingConf['page']
const start = (page - 1) * perPage; const start = (page - 1) * perPage
const count = perPage; const count = perPage
searchParams.set('start', start.toString()); searchParams.set('start', start.toString())
searchParams.set('count', count.toString()); searchParams.set('count', count.toString())
} }
return requestOptions; return requestOptions
} }
} }

View File

@ -1,52 +1,52 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Response } from '@angular/http'; import { Response } from '@angular/http'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
export interface ResultList { export interface ResultList {
data: any[]; data: any[]
total: number; total: number
} }
@Injectable() @Injectable()
export class RestExtractor { export class RestExtractor {
constructor () { ; } extractDataBool (res: Response) {
return true
extractDataBool(res: Response) {
return true;
} }
extractDataList(res: Response) { extractDataList (res: Response) {
const body = res.json(); const body = res.json()
const ret: ResultList = { const ret: ResultList = {
data: body.data, data: body.data,
total: body.total total: body.total
}; }
return ret; return ret
} }
extractDataGet(res: Response) { extractDataGet (res: Response) {
return res.json(); return res.json()
} }
handleError(res: Response) { handleError (res: Response) {
let text = 'Server error: '; let text = 'Server error: '
text += res.text(); text += res.text()
let json = ''; let json = ''
try { try {
json = res.json(); json = res.json()
} catch (err) { ; } } catch (err) {
console.error('Cannot get JSON from response.')
}
const error = { const error = {
json, json,
text text
}; }
console.error(error); console.error(error)
return Observable.throw(error); return Observable.throw(error)
} }
} }

View File

@ -1,5 +1,5 @@
export interface RestPagination { export interface RestPagination {
currentPage: number; currentPage: number
itemsPerPage: number; itemsPerPage: number
totalItems: number; totalItems: number
}; }

View File

@ -1,27 +1,27 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { URLSearchParams } from '@angular/http'; import { URLSearchParams } from '@angular/http'
import { RestPagination } from './rest-pagination'; import { RestPagination } from './rest-pagination'
@Injectable() @Injectable()
export class RestService { export class RestService {
buildRestGetParams(pagination?: RestPagination, sort?: string) { buildRestGetParams (pagination?: RestPagination, sort?: string) {
const params = new URLSearchParams(); const params = new URLSearchParams()
if (pagination) { if (pagination) {
const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage; const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage
const count: number = pagination.itemsPerPage; const count: number = pagination.itemsPerPage
params.set('start', start.toString()); params.set('start', start.toString())
params.set('count', count.toString()); params.set('count', count.toString())
} }
if (sort) { if (sort) {
params.set('sort', sort); params.set('sort', sort)
} }
return params; return params
} }
} }

View File

@ -1,4 +1,4 @@
export * from './search-field.type'; export * from './search-field.type'
export * from './search.component'; export * from './search.component'
export * from './search.model'; export * from './search.model'
export * from './search.service'; export * from './search.service'

View File

@ -1 +1 @@
export type SearchField = "name" | "author" | "host" | "magnetUri" | "tags"; export type SearchField = 'name' | 'author' | 'host' | 'magnetUri' | 'tags'

View File

@ -1,9 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { Search } from './search.model'; import { Search } from './search.model'
import { SearchField } from './search-field.type'; import { SearchField } from './search-field.type'
import { SearchService } from './search.service'; import { SearchService } from './search.service'
@Component({ @Component({
selector: 'my-search', selector: 'my-search',
@ -18,53 +18,53 @@ export class SearchComponent implements OnInit {
host: 'Pod Host', host: 'Pod Host',
magnetUri: 'Magnet URI', magnetUri: 'Magnet URI',
tags: 'Tags' tags: 'Tags'
}; }
searchCriterias: Search = { searchCriterias: Search = {
field: 'name', field: 'name',
value: '' value: ''
}; }
constructor(private searchService: SearchService, private router: Router) {} constructor (private searchService: SearchService, private router: Router) {}
ngOnInit() { ngOnInit () {
// Subscribe if the search changed // Subscribe if the search changed
// Usually changed by videos list component // Usually changed by videos list component
this.searchService.updateSearch.subscribe( this.searchService.updateSearch.subscribe(
newSearchCriterias => { newSearchCriterias => {
// Put a field by default // Put a field by default
if (!newSearchCriterias.field) { if (!newSearchCriterias.field) {
newSearchCriterias.field = 'name'; newSearchCriterias.field = 'name'
} }
this.searchCriterias = newSearchCriterias; this.searchCriterias = newSearchCriterias
} }
); )
} }
get choiceKeys() { get choiceKeys () {
return Object.keys(this.fieldChoices); return Object.keys(this.fieldChoices)
} }
choose($event: MouseEvent, choice: SearchField) { choose ($event: MouseEvent, choice: SearchField) {
$event.preventDefault(); $event.preventDefault()
$event.stopPropagation(); $event.stopPropagation()
this.searchCriterias.field = choice; this.searchCriterias.field = choice
if (this.searchCriterias.value) { if (this.searchCriterias.value) {
this.doSearch(); this.doSearch()
} }
} }
doSearch() { doSearch () {
if (this.router.url.indexOf('/videos/list') === -1) { if (this.router.url.indexOf('/videos/list') === -1) {
this.router.navigate([ '/videos/list' ]); this.router.navigate([ '/videos/list' ])
} }
this.searchService.searchUpdated.next(this.searchCriterias); this.searchService.searchUpdated.next(this.searchCriterias)
} }
getStringChoice(choiceKey: SearchField) { getStringChoice (choiceKey: SearchField) {
return this.fieldChoices[choiceKey]; return this.fieldChoices[choiceKey]
} }
} }

View File

@ -1,6 +1,6 @@
import { SearchField } from './search-field.type'; import { SearchField } from './search-field.type'
export interface Search { export interface Search {
field: SearchField; field: SearchField
value: string; value: string
} }

View File

@ -1,18 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject'
import { ReplaySubject } from 'rxjs/ReplaySubject'; import { ReplaySubject } from 'rxjs/ReplaySubject'
import { Search } from './search.model'; import { Search } from './search.model'
// This class is needed to communicate between videos/ and search component // This class is needed to communicate between videos/ and search component
// Remove it when we'll be able to subscribe to router changes // Remove it when we'll be able to subscribe to router changes
@Injectable() @Injectable()
export class SearchService { export class SearchService {
searchUpdated: Subject<Search>; searchUpdated: Subject<Search>
updateSearch: Subject<Search>; updateSearch: Subject<Search>
constructor() { constructor () {
this.updateSearch = new Subject<Search>(); this.updateSearch = new Subject<Search>()
this.searchUpdated = new ReplaySubject<Search>(1); this.searchUpdated = new ReplaySubject<Search>(1)
} }
} }

View File

@ -1,23 +1,23 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common'
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router'
import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
import { KeysPipe } from 'angular-pipes/src/object/keys.pipe'; import { KeysPipe } from 'angular-pipes/src/object/keys.pipe'
import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
import { PaginationModule } from 'ngx-bootstrap/pagination'; import { PaginationModule } from 'ngx-bootstrap/pagination'
import { ModalModule } from 'ngx-bootstrap/modal'; import { ModalModule } from 'ngx-bootstrap/modal'
import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'
import { Ng2SmartTableModule } from 'ng2-smart-table'; import { Ng2SmartTableModule } from 'ng2-smart-table'
import { AUTH_HTTP_PROVIDERS } from './auth'; import { AUTH_HTTP_PROVIDERS } from './auth'
import { RestExtractor, RestService } from './rest'; import { RestExtractor, RestService } from './rest'
import { SearchComponent, SearchService } from './search'; import { SearchComponent, SearchService } from './search'
import { UserService } from './users'; import { UserService } from './users'
import { VideoAbuseService } from './video-abuse'; import { VideoAbuseService } from './video-abuse'
@NgModule({ @NgModule({
imports: [ imports: [

View File

@ -1,2 +1,2 @@
export * from './user.model'; export * from './user.model'
export * from './user.service'; export * from './user.service'

View File

@ -1,33 +1,33 @@
import { User as UserServerModel, UserRole } from '../../../../../shared'; import { User as UserServerModel, UserRole } from '../../../../../shared'
export class User implements UserServerModel { export class User implements UserServerModel {
id: number; id: number
username: string; username: string
email: string; email: string
role: UserRole; role: UserRole
displayNSFW: boolean; displayNSFW: boolean
createdAt: Date; createdAt: Date
constructor(hash: { constructor (hash: {
id: number, id: number,
username: string, username: string,
email: string, email: string,
role: UserRole, role: UserRole,
displayNSFW?: boolean, displayNSFW?: boolean,
createdAt?: Date, createdAt?: Date
}) { }) {
this.id = hash.id; this.id = hash.id
this.username = hash.username; this.username = hash.username
this.email = hash.email; this.email = hash.email
this.role = hash.role; this.role = hash.role
this.displayNSFW = hash.displayNSFW; this.displayNSFW = hash.displayNSFW
if (hash.createdAt) { if (hash.createdAt) {
this.createdAt = hash.createdAt; this.createdAt = hash.createdAt
} }
} }
isAdmin() { isAdmin () {
return this.role === 'admin'; return this.role === 'admin'
} }
} }

View File

@ -1,58 +1,58 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Http } from '@angular/http'; import { Http } from '@angular/http'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import { AuthService } from '../../core'; import { AuthService } from '../../core'
import { AuthHttp } from '../auth'; import { AuthHttp } from '../auth'
import { RestExtractor } from '../rest'; import { RestExtractor } from '../rest'
@Injectable() @Injectable()
export class UserService { export class UserService {
static BASE_USERS_URL = API_URL + '/api/v1/users/'; static BASE_USERS_URL = API_URL + '/api/v1/users/'
constructor( constructor (
private http: Http, private http: Http,
private authHttp: AuthHttp, private authHttp: AuthHttp,
private authService: AuthService, private authService: AuthService,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
checkTokenValidity() { checkTokenValidity () {
const url = UserService.BASE_USERS_URL + 'me'; const url = UserService.BASE_USERS_URL + 'me'
// AuthHttp will redirect us to the login page if the oken is not valid anymore // AuthHttp will redirect us to the login page if the oken is not valid anymore
this.authHttp.get(url).subscribe(() => { ; }); this.authHttp.get(url).subscribe()
} }
changePassword(newPassword: string) { changePassword (newPassword: string) {
const url = UserService.BASE_USERS_URL + this.authService.getUser().id; const url = UserService.BASE_USERS_URL + this.authService.getUser().id
const body = { const body = {
password: newPassword password: newPassword
}; }
return this.authHttp.put(url, body) return this.authHttp.put(url, body)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
updateDetails(details: { displayNSFW: boolean }) { updateDetails (details: { displayNSFW: boolean }) {
const url = UserService.BASE_USERS_URL + this.authService.getUser().id; const url = UserService.BASE_USERS_URL + this.authService.getUser().id
return this.authHttp.put(url, details) return this.authHttp.put(url, details)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
signup(username: string, password: string, email: string) { signup (username: string, password: string, email: string) {
const body = { const body = {
username, username,
email, email,
password password
}; }
return this.http.post(UserService.BASE_USERS_URL + 'register', body) return this.http.post(UserService.BASE_USERS_URL + 'register', body)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch(this.restExtractor.handleError); .catch(this.restExtractor.handleError)
} }
} }

View File

@ -1,12 +1,12 @@
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common'
export class Utils { export class Utils {
static dateToHuman(date: String) { static dateToHuman (date: String) {
return new DatePipe('en').transform(date, 'medium'); return new DatePipe('en').transform(date, 'medium')
} }
static getRowDeleteButton() { static getRowDeleteButton () {
return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'; return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
} }
} }

View File

@ -1,2 +1,2 @@
export * from './video-abuse.service'; export * from './video-abuse.service'
export * from './video-abuse.model'; export * from './video-abuse.model'

View File

@ -1,8 +1,8 @@
export interface VideoAbuse { export interface VideoAbuse {
id: string; id: string
reason: string; reason: string
reporterPodHost: string; reporterPodHost: string
reporterUsername: string; reporterUsername: string
videoId: string; videoId: string
createdAt: Date; createdAt: Date
} }

View File

@ -1,42 +1,42 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core'
import { Http } from '@angular/http'; import { Http } from '@angular/http'
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/catch'
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map'
import { AuthService } from '../core'; import { AuthService } from '../core'
import { AuthHttp } from '../auth'; import { AuthHttp } from '../auth'
import { RestDataSource, RestExtractor, ResultList } from '../rest'; import { RestDataSource, RestExtractor, ResultList } from '../rest'
import { VideoAbuse } from './video-abuse.model'; import { VideoAbuse } from './video-abuse.model'
@Injectable() @Injectable()
export class VideoAbuseService { export class VideoAbuseService {
private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/'; private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/'
constructor( constructor (
private authHttp: AuthHttp, private authHttp: AuthHttp,
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
getDataSource() { getDataSource () {
return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse'); return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse')
} }
reportVideo(id: string, reason: string) { reportVideo (id: string, reason: string) {
const body = { const body = {
reason reason
}; }
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'; const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
return this.authHttp.post(url, body) return this.authHttp.post(url, body)
.map(this.restExtractor.extractDataBool) .map(this.restExtractor.extractDataBool)
.catch((res) => this.restExtractor.handleError(res)); .catch((res) => this.restExtractor.handleError(res))
} }
private extractVideoAbuses(result: ResultList) { private extractVideoAbuses (result: ResultList) {
const videoAbuses: VideoAbuse[] = result.data; const videoAbuses: VideoAbuse[] = result.data
const totalVideoAbuses = result.total; const totalVideoAbuses = result.total
return { videoAbuses, totalVideoAbuses }; return { videoAbuses, totalVideoAbuses }
} }
} }

View File

@ -1,3 +1,3 @@
export * from './signup-routing.module'; export * from './signup-routing.module'
export * from './signup.component'; export * from './signup.component'
export * from './signup.module'; export * from './signup.module'

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router'
import { SignupComponent } from './signup.component'; import { SignupComponent } from './signup.component'
const signupRoutes: Routes = [ const signupRoutes: Routes = [
{ {
@ -13,7 +13,7 @@ const signupRoutes: Routes = [
} }
} }
} }
]; ]
@NgModule({ @NgModule({
imports: [ RouterModule.forChild(signupRoutes) ], imports: [ RouterModule.forChild(signupRoutes) ],

Some files were not shown because too many files have changed in this diff Show More