diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts
new file mode 100644
index 000000000..82f2529ec
--- /dev/null
+++ b/client/src/app/admin/admin.component.ts
@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router';
+
+@Component({
+ template: '',
+ directives: [ ROUTER_DIRECTIVES ]
+})
+
+export class AdminComponent {
+}
diff --git a/client/src/app/admin/admin.routes.ts b/client/src/app/admin/admin.routes.ts
new file mode 100644
index 000000000..d375a86af
--- /dev/null
+++ b/client/src/app/admin/admin.routes.ts
@@ -0,0 +1,14 @@
+import { RouterConfig } from '@angular/router';
+
+import { AdminComponent } from './admin.component';
+import { UsersRoutes } from './users';
+
+export const AdminRoutes: RouterConfig = [
+ {
+ path: 'admin',
+ component: AdminComponent,
+ children: [
+ ...UsersRoutes
+ ]
+ }
+];
diff --git a/client/src/app/admin/index.ts b/client/src/app/admin/index.ts
new file mode 100644
index 000000000..3b0540818
--- /dev/null
+++ b/client/src/app/admin/index.ts
@@ -0,0 +1,3 @@
+export * from './users';
+export * from './admin.component';
+export * from './admin.routes';
diff --git a/client/src/app/admin/users/index.ts b/client/src/app/admin/users/index.ts
new file mode 100644
index 000000000..e98a81f62
--- /dev/null
+++ b/client/src/app/admin/users/index.ts
@@ -0,0 +1,5 @@
+export * from './shared';
+export * from './user-add';
+export * from './user-list';
+export * from './users.component';
+export * from './users.routes';
diff --git a/client/src/app/admin/users/shared/index.ts b/client/src/app/admin/users/shared/index.ts
new file mode 100644
index 000000000..e17ee5c7a
--- /dev/null
+++ b/client/src/app/admin/users/shared/index.ts
@@ -0,0 +1 @@
+export * from './user.service';
diff --git a/client/src/app/admin/users/shared/user.service.ts b/client/src/app/admin/users/shared/user.service.ts
new file mode 100644
index 000000000..be433f0a1
--- /dev/null
+++ b/client/src/app/admin/users/shared/user.service.ts
@@ -0,0 +1,49 @@
+import { Injectable } from '@angular/core';
+import { Response } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+
+import { AuthHttp, User } from '../../../shared';
+
+@Injectable()
+export class UserService {
+ // TODO: merge this constant with account
+ private static BASE_USERS_URL = '/api/v1/users/';
+
+ constructor(private authHttp: AuthHttp) {}
+
+ addUser(username: string, password: string) {
+ const body = {
+ username,
+ password
+ };
+
+ return this.authHttp.post(UserService.BASE_USERS_URL, body);
+ }
+
+ getUsers() {
+ return this.authHttp.get(UserService.BASE_USERS_URL)
+ .map(res => res.json())
+ .map(this.extractUsers)
+ .catch(this.handleError);
+ }
+
+ removeUser(user: User) {
+ return this.authHttp.delete(UserService.BASE_USERS_URL + user.id);
+ }
+
+ private extractUsers(body: any) {
+ const usersJson = body.data;
+ const totalUsers = body.total;
+ const users = [];
+ for (const userJson of usersJson) {
+ users.push(new User(userJson));
+ }
+
+ return { users, totalUsers };
+ }
+
+ private handleError(error: Response) {
+ console.error(error);
+ return Observable.throw(error.json().error || 'Server error');
+ }
+}
diff --git a/client/src/app/admin/users/user-add/index.ts b/client/src/app/admin/users/user-add/index.ts
new file mode 100644
index 000000000..66d5ca04f
--- /dev/null
+++ b/client/src/app/admin/users/user-add/index.ts
@@ -0,0 +1 @@
+export * from './user-add.component';
diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html
new file mode 100644
index 000000000..aa102358a
--- /dev/null
+++ b/client/src/app/admin/users/user-add/user-add.component.html
@@ -0,0 +1,29 @@
+
Add user
+
+{{ error }}
+
+
diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts
new file mode 100644
index 000000000..30ca947a0
--- /dev/null
+++ b/client/src/app/admin/users/user-add/user-add.component.ts
@@ -0,0 +1,33 @@
+import { Control, ControlGroup, Validators } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { UserService } from '../shared';
+
+@Component({
+ selector: 'my-user-add',
+ template: require('./user-add.component.html'),
+})
+export class UserAddComponent implements OnInit {
+ userAddForm: ControlGroup;
+ error: string = null;
+
+ constructor(private router: Router, private userService: UserService) {}
+
+ ngOnInit() {
+ this.userAddForm = new ControlGroup({
+ username: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(20) ])),
+ password: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])),
+ });
+ }
+
+ addUser(username: string, password: string) {
+ this.error = null;
+
+ this.userService.addUser(username, password).subscribe(
+ ok => this.router.navigate([ '/admin/users/list' ]),
+
+ err => this.error = err
+ );
+ }
+}
diff --git a/client/src/app/admin/users/user-list/index.ts b/client/src/app/admin/users/user-list/index.ts
new file mode 100644
index 000000000..51fbefa80
--- /dev/null
+++ b/client/src/app/admin/users/user-list/index.ts
@@ -0,0 +1 @@
+export * from './user-list.component';
diff --git a/client/src/app/admin/users/user-list/user-list.component.html b/client/src/app/admin/users/user-list/user-list.component.html
new file mode 100644
index 000000000..2aca05f2b
--- /dev/null
+++ b/client/src/app/admin/users/user-list/user-list.component.html
@@ -0,0 +1,24 @@
+
+
+
+ Id |
+ Username |
+ Remove |
+
+
+
+
+
+ {{ user.id }} |
+ {{ user.username }} |
+
+
+ |
+
+
+
+
+
+
+ Add user
+
diff --git a/client/src/app/admin/users/user-list/user-list.component.scss b/client/src/app/admin/users/user-list/user-list.component.scss
new file mode 100644
index 000000000..e9f61e900
--- /dev/null
+++ b/client/src/app/admin/users/user-list/user-list.component.scss
@@ -0,0 +1,7 @@
+.glyphicon-remove {
+ cursor: pointer;
+}
+
+.add-user {
+ margin-top: 10px;
+}
diff --git a/client/src/app/admin/users/user-list/user-list.component.ts b/client/src/app/admin/users/user-list/user-list.component.ts
new file mode 100644
index 000000000..598daa42a
--- /dev/null
+++ b/client/src/app/admin/users/user-list/user-list.component.ts
@@ -0,0 +1,44 @@
+import { Component, OnInit } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router';
+
+import { User } from '../../../shared';
+import { UserService } from '../shared';
+
+@Component({
+ selector: 'my-user-list',
+ template: require('./user-list.component.html'),
+ styles: [ require('./user-list.component.scss') ],
+ directives: [ ROUTER_DIRECTIVES ]
+})
+export class UserListComponent implements OnInit {
+ totalUsers: number;
+ users: User[];
+
+ constructor(private userService: UserService) {}
+
+ ngOnInit() {
+ this.getUsers();
+ }
+
+ getUsers() {
+ this.userService.getUsers().subscribe(
+ ({ users, totalUsers }) => {
+ this.users = users;
+ this.totalUsers = totalUsers;
+ },
+
+ err => alert(err)
+ );
+ }
+
+
+ removeUser(user: User) {
+ if (confirm('Are you sure?')) {
+ this.userService.removeUser(user).subscribe(
+ () => this.getUsers(),
+
+ err => alert(err)
+ );
+ }
+ }
+}
diff --git a/client/src/app/admin/users/users.component.ts b/client/src/app/admin/users/users.component.ts
new file mode 100644
index 000000000..46aa0862f
--- /dev/null
+++ b/client/src/app/admin/users/users.component.ts
@@ -0,0 +1,13 @@
+import { Component } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router';
+
+import { UserService } from './shared';
+
+@Component({
+ template: '',
+ directives: [ ROUTER_DIRECTIVES ],
+ providers: [ UserService ]
+})
+
+export class UsersComponent {
+}
diff --git a/client/src/app/admin/users/users.routes.ts b/client/src/app/admin/users/users.routes.ts
new file mode 100644
index 000000000..0457c3843
--- /dev/null
+++ b/client/src/app/admin/users/users.routes.ts
@@ -0,0 +1,27 @@
+import { RouterConfig } from '@angular/router';
+
+import { UsersComponent } from './users.component';
+import { UserAddComponent } from './user-add';
+import { UserListComponent } from './user-list';
+
+export const UsersRoutes: RouterConfig = [
+ {
+ path: 'users',
+ component: UsersComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'list',
+ pathMatch: 'full'
+ },
+ {
+ path: 'list',
+ component: UserListComponent
+ },
+ {
+ path: 'add',
+ component: UserAddComponent
+ }
+ ]
+ }
+];
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html
index ea4b31421..58967abca 100644
--- a/client/src/app/app.component.html
+++ b/client/src/app/app.component.html
@@ -47,7 +47,12 @@
-
+
+
+
Make friends
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index 5764f24ca..444b6b3b4 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -45,6 +45,10 @@ export class AppComponent {
);
}
+ isUserAdmin() {
+ return this.authService.isAdmin();
+ }
+
logout() {
this.authService.logout();
// Redirect to home page
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts
index 1c414038d..d7194cb4f 100644
--- a/client/src/app/app.routes.ts
+++ b/client/src/app/app.routes.ts
@@ -2,6 +2,7 @@ import { RouterConfig } from '@angular/router';
import { AccountRoutes } from './account';
import { LoginRoutes } from './login';
+import { AdminRoutes } from './admin';
import { VideosRoutes } from './videos';
export const routes: RouterConfig = [
@@ -10,7 +11,7 @@ export const routes: RouterConfig = [
redirectTo: '/videos/list',
pathMatch: 'full'
},
-
+ ...AdminRoutes,
...AccountRoutes,
...LoginRoutes,
...VideosRoutes
diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/auth-user.model.ts
similarity index 79%
rename from client/src/app/shared/auth/user.model.ts
rename to client/src/app/shared/auth/auth-user.model.ts
index e486873ab..bdd5ea5a9 100644
--- a/client/src/app/shared/auth/user.model.ts
+++ b/client/src/app/shared/auth/auth-user.model.ts
@@ -1,4 +1,6 @@
-export class User {
+import { User } from '../users';
+
+export class AuthUser extends User {
private static KEYS = {
ID: 'id',
ROLE: 'role',
@@ -13,10 +15,12 @@ export class User {
static load() {
const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
if (usernameLocalStorage) {
- return new User(
- localStorage.getItem(this.KEYS.ID),
- localStorage.getItem(this.KEYS.USERNAME),
- localStorage.getItem(this.KEYS.ROLE),
+ return new AuthUser(
+ {
+ id: localStorage.getItem(this.KEYS.ID),
+ username: localStorage.getItem(this.KEYS.USERNAME),
+ role: localStorage.getItem(this.KEYS.ROLE)
+ },
Tokens.load()
);
}
@@ -31,11 +35,9 @@ export class User {
Tokens.flush();
}
- constructor(id: string, username: string, role: string, hash_tokens: any) {
- this.id = id;
- this.username = username;
- this.role = role;
- this.tokens = new Tokens(hash_tokens);
+ constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) {
+ super(userHash);
+ this.tokens = new Tokens(hashTokens);
}
getAccessToken() {
@@ -56,9 +58,9 @@ export class User {
}
save() {
- localStorage.setItem(User.KEYS.ID, this.id);
- localStorage.setItem(User.KEYS.USERNAME, this.username);
- localStorage.setItem(User.KEYS.ROLE, this.role);
+ localStorage.setItem(AuthUser.KEYS.ID, this.id);
+ localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
+ localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
this.tokens.save();
}
}
diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts
index 24d1a4fa2..8eea0c4bf 100644
--- a/client/src/app/shared/auth/auth.service.ts
+++ b/client/src/app/shared/auth/auth.service.ts
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { AuthStatus } from './auth-status.model';
-import { User } from './user.model';
+import { AuthUser } from './auth-user.model';
@Injectable()
export class AuthService {
@@ -17,7 +17,7 @@ export class AuthService {
private clientId: string;
private clientSecret: string;
private loginChanged: Subject
;
- private user: User = null;
+ private user: AuthUser = null;
constructor(private http: Http) {
this.loginChanged = new Subject();
@@ -40,7 +40,7 @@ export class AuthService {
);
// Return null if there is nothing to load
- this.user = User.load();
+ this.user = AuthUser.load();
}
getRefreshToken() {
@@ -65,10 +65,16 @@ export class AuthService {
return this.user.getTokenType();
}
- getUser(): User {
+ getUser(): AuthUser {
return this.user;
}
+ isAdmin() {
+ if (this.user === null) return false;
+
+ return this.user.isAdmin();
+ }
+
isLoggedIn() {
if (this.getAccessToken()) {
return true;
@@ -108,7 +114,7 @@ export class AuthService {
logout() {
// TODO: make an HTTP request to revoke the tokens
this.user = null;
- User.flush();
+ AuthUser.flush();
this.setStatus(AuthStatus.LoggedOut);
}
@@ -163,13 +169,13 @@ export class AuthService {
const id = obj.id;
const username = obj.username;
const role = obj.role;
- const hash_tokens = {
+ const hashTokens = {
access_token: obj.access_token,
token_type: obj.token_type,
refresh_token: obj.refresh_token
};
- this.user = new User(id, username, role, hash_tokens);
+ this.user = new AuthUser({ id, username, role }, hashTokens);
this.user.save();
this.setStatus(AuthStatus.LoggedIn);
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts
index aafaacbf1..ebd9e14cd 100644
--- a/client/src/app/shared/auth/index.ts
+++ b/client/src/app/shared/auth/index.ts
@@ -1,4 +1,4 @@
export * from './auth-http.service';
export * from './auth-status.model';
export * from './auth.service';
-export * from './user.model';
+export * from './auth-user.model';
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts
index dfea4c67c..c05e8d253 100644
--- a/client/src/app/shared/index.ts
+++ b/client/src/app/shared/index.ts
@@ -1,2 +1,3 @@
export * from './auth';
export * from './search';
+export * from './users';
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts
new file mode 100644
index 000000000..5a670ce8f
--- /dev/null
+++ b/client/src/app/shared/users/index.ts
@@ -0,0 +1 @@
+export * from './user.model';
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
new file mode 100644
index 000000000..0f34d4480
--- /dev/null
+++ b/client/src/app/shared/users/user.model.ts
@@ -0,0 +1,15 @@
+export class User {
+ id: string;
+ username: string;
+ role: string;
+
+ constructor(hash: { id: string, username: string, role: string }) {
+ this.id = hash.id;
+ this.username = hash.username;
+ this.role = hash.role;
+ }
+
+ isAdmin() {
+ return this.role === 'admin';
+ }
+}
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts
index 5691d684e..062340ec5 100644
--- a/client/src/app/videos/video-list/video-list.component.ts
+++ b/client/src/app/videos/video-list/video-list.component.ts
@@ -12,7 +12,7 @@ import {
Video,
VideoService
} from '../shared';
-import { AuthService, Search, SearchField, User } from '../../shared';
+import { AuthService, AuthUser, Search, SearchField } from '../../shared';
import { VideoMiniatureComponent } from './video-miniature.component';
import { VideoSortComponent } from './video-sort.component';
import { SearchService } from '../../shared';
@@ -33,7 +33,7 @@ export class VideoListComponent implements OnInit, OnDestroy {
totalItems: null
};
sort: SortField;
- user: User = null;
+ user: AuthUser = null;
videos: Video[] = [];
private search: Search;
@@ -51,7 +51,7 @@ export class VideoListComponent implements OnInit, OnDestroy {
ngOnInit() {
if (this.authService.isLoggedIn()) {
- this.user = User.load();
+ this.user = AuthUser.load();
}
// Subscribe to route changes
diff --git a/client/src/index.html b/client/src/index.html
index 5cf491221..f39d8d2cf 100644
--- a/client/src/index.html
+++ b/client/src/index.html
@@ -1,3 +1,4 @@
+
diff --git a/client/tsconfig.json b/client/tsconfig.json
index b10231b7b..5c5f008c3 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -31,6 +31,18 @@
"src/app/account/account.routes.ts",
"src/app/account/account.service.ts",
"src/app/account/index.ts",
+ "src/app/admin/admin.component.ts",
+ "src/app/admin/admin.routes.ts",
+ "src/app/admin/index.ts",
+ "src/app/admin/users/index.ts",
+ "src/app/admin/users/shared/index.ts",
+ "src/app/admin/users/shared/user.service.ts",
+ "src/app/admin/users/user-add/index.ts",
+ "src/app/admin/users/user-add/user-add.component.ts",
+ "src/app/admin/users/user-list/index.ts",
+ "src/app/admin/users/user-list/user-list.component.ts",
+ "src/app/admin/users/users.component.ts",
+ "src/app/admin/users/users.routes.ts",
"src/app/app.component.ts",
"src/app/app.routes.ts",
"src/app/friends/friend.service.ts",
@@ -40,15 +52,17 @@
"src/app/login/login.routes.ts",
"src/app/shared/auth/auth-http.service.ts",
"src/app/shared/auth/auth-status.model.ts",
+ "src/app/shared/auth/auth-user.model.ts",
"src/app/shared/auth/auth.service.ts",
"src/app/shared/auth/index.ts",
- "src/app/shared/auth/user.model.ts",
"src/app/shared/index.ts",
"src/app/shared/search/index.ts",
"src/app/shared/search/search-field.type.ts",
"src/app/shared/search/search.component.ts",
"src/app/shared/search/search.model.ts",
"src/app/shared/search/search.service.ts",
+ "src/app/shared/users/index.ts",
+ "src/app/shared/users/user.model.ts",
"src/app/videos/index.ts",
"src/app/videos/shared/index.ts",
"src/app/videos/shared/loader/index.ts",