--- /dev/null
+import { Component } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router';
+
+@Component({
+ template: '<router-outlet></router-outlet>',
+ directives: [ ROUTER_DIRECTIVES ]
+})
+
+export class AdminComponent {
+}
--- /dev/null
+import { RouterConfig } from '@angular/router';
+
+import { AdminComponent } from './admin.component';
+import { UsersRoutes } from './users';
+
+export const AdminRoutes: RouterConfig = [
+ {
+ path: 'admin',
+ component: AdminComponent,
+ children: [
+ ...UsersRoutes
+ ]
+ }
+];
--- /dev/null
+export * from './users';
+export * from './admin.component';
+export * from './admin.routes';
--- /dev/null
+export * from './shared';
+export * from './user-add';
+export * from './user-list';
+export * from './users.component';
+export * from './users.routes';
--- /dev/null
+export * from './user.service';
--- /dev/null
+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');
+ }
+}
--- /dev/null
+export * from './user-add.component';
--- /dev/null
+<h3>Add user</h3>
+
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="addUser(username.value, password.value)" #addUserForm="ngForm">
+ <div class="form-group">
+ <label for="username">Username</label>
+ <input
+ type="text" class="form-control" name="username" id="username" placeholder="Username" required
+ ngControl="username" #username="ngForm"
+ >
+ <div [hidden]="username.valid || username.pristine" class="alert alert-danger">
+ Username is required with a length >= 3 and <= 20
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="password">Password</label>
+ <input
+ type="password" class="form-control" name="password" id="password" placeholder="Password" required
+ ngControl="password" #password="ngForm"
+ >
+ <div [hidden]="password.valid || password.pristine" class="alert alert-danger">
+ Password is required with a length >= 6
+ </div>
+ </div>
+
+ <input type="submit" value="Add user" class="btn btn-default" [disabled]="!addUserForm.form.valid">
+</form>
--- /dev/null
+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
+ );
+ }
+}
--- /dev/null
+export * from './user-list.component';
--- /dev/null
+<table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Id</th>
+ <th>Username</th>
+ <th class="text-right">Remove</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr *ngFor="let user of users">
+ <td>{{ user.id }}</td>
+ <td>{{ user.username }}</td>
+ <td class="text-right">
+ <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
+ <span class="glyphicon glyphicon-plus"></span>
+ Add user
+</a>
--- /dev/null
+.glyphicon-remove {
+ cursor: pointer;
+}
+
+.add-user {
+ margin-top: 10px;
+}
--- /dev/null
+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)
+ );
+ }
+ }
+}
--- /dev/null
+import { Component } from '@angular/core';
+import { ROUTER_DIRECTIVES } from '@angular/router';
+
+import { UserService } from './shared';
+
+@Component({
+ template: '<router-outlet></router-outlet>',
+ directives: [ ROUTER_DIRECTIVES ],
+ providers: [ UserService ]
+})
+
+export class UsersComponent {
+}
--- /dev/null
+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
+ }
+ ]
+ }
+];
</div>
</div>
- <div class="panel-block" *ngIf="isLoggedIn">
+ <div class="panel-block" *ngIf="isUserAdmin()">
+ <div id="panel-users" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-user"></span>
+ <a [routerLink]="['/admin/users/list']">List users</a>
+ </div>
+
<div id="panel-make-friends" class="panel-button">
<span class="hidden-xs glyphicon glyphicon-cloud"></span>
<a (click)='makeFriends()'>Make friends</a>
);
}
+ isUserAdmin() {
+ return this.authService.isAdmin();
+ }
+
logout() {
this.authService.logout();
// Redirect to home page
import { AccountRoutes } from './account';
import { LoginRoutes } from './login';
+import { AdminRoutes } from './admin';
import { VideosRoutes } from './videos';
export const routes: RouterConfig = [
redirectTo: '/videos/list',
pathMatch: 'full'
},
-
+ ...AdminRoutes,
...AccountRoutes,
...LoginRoutes,
...VideosRoutes
--- /dev/null
+import { User } from '../users';
+
+export class AuthUser extends User {
+ private static KEYS = {
+ ID: 'id',
+ ROLE: 'role',
+ USERNAME: 'username'
+ };
+
+ id: string;
+ role: string;
+ username: string;
+ tokens: Tokens;
+
+ static load() {
+ const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
+ if (usernameLocalStorage) {
+ return new AuthUser(
+ {
+ id: localStorage.getItem(this.KEYS.ID),
+ username: localStorage.getItem(this.KEYS.USERNAME),
+ role: localStorage.getItem(this.KEYS.ROLE)
+ },
+ Tokens.load()
+ );
+ }
+
+ return null;
+ }
+
+ static flush() {
+ localStorage.removeItem(this.KEYS.USERNAME);
+ localStorage.removeItem(this.KEYS.ID);
+ localStorage.removeItem(this.KEYS.ROLE);
+ Tokens.flush();
+ }
+
+ constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) {
+ super(userHash);
+ this.tokens = new Tokens(hashTokens);
+ }
+
+ getAccessToken() {
+ return this.tokens.access_token;
+ }
+
+ getRefreshToken() {
+ return this.tokens.refresh_token;
+ }
+
+ getTokenType() {
+ return this.tokens.token_type;
+ }
+
+ refreshTokens(access_token: string, refresh_token: string) {
+ this.tokens.access_token = access_token;
+ this.tokens.refresh_token = refresh_token;
+ }
+
+ save() {
+ localStorage.setItem(AuthUser.KEYS.ID, this.id);
+ localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
+ localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
+ 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);
+ }
+}
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 {
private clientId: string;
private clientSecret: string;
private loginChanged: Subject<AuthStatus>;
- private user: User = null;
+ private user: AuthUser = null;
constructor(private http: Http) {
this.loginChanged = new Subject<AuthStatus>();
);
// Return null if there is nothing to load
- this.user = User.load();
+ this.user = AuthUser.load();
}
getRefreshToken() {
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;
logout() {
// TODO: make an HTTP request to revoke the tokens
this.user = null;
- User.flush();
+ AuthUser.flush();
this.setStatus(AuthStatus.LoggedOut);
}
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);
export * from './auth-http.service';
export * from './auth-status.model';
export * from './auth.service';
-export * from './user.model';
+export * from './auth-user.model';
+++ /dev/null
-export class User {
- private static KEYS = {
- ID: 'id',
- ROLE: 'role',
- USERNAME: 'username'
- };
-
- id: string;
- role: string;
- username: string;
- tokens: Tokens;
-
- 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),
- Tokens.load()
- );
- }
-
- return null;
- }
-
- static flush() {
- localStorage.removeItem(this.KEYS.USERNAME);
- localStorage.removeItem(this.KEYS.ID);
- localStorage.removeItem(this.KEYS.ROLE);
- 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);
- }
-
- getAccessToken() {
- return this.tokens.access_token;
- }
-
- getRefreshToken() {
- return this.tokens.refresh_token;
- }
-
- getTokenType() {
- return this.tokens.token_type;
- }
-
- refreshTokens(access_token: string, refresh_token: string) {
- this.tokens.access_token = access_token;
- this.tokens.refresh_token = refresh_token;
- }
-
- save() {
- localStorage.setItem(User.KEYS.ID, this.id);
- localStorage.setItem(User.KEYS.USERNAME, this.username);
- localStorage.setItem(User.KEYS.ROLE, this.role);
- 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);
- }
-}
export * from './auth';
export * from './search';
+export * from './users';
--- /dev/null
+export * from './user.model';
--- /dev/null
+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';
+ }
+}
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';
totalItems: null
};
sort: SortField;
- user: User = null;
+ user: AuthUser = null;
videos: Video[] = [];
private search: Search;
ngOnInit() {
if (this.authService.isLoggedIn()) {
- this.user = User.load();
+ this.user = AuthUser.load();
}
// Subscribe to route changes
+<!DOCTYPE html>
<html>
<head>
<base href="/">
"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",
"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",