Make base href truly dynamic

This allows a user to 'install' TaskBoard anywhere without worrying
about the directory structure.

Also, adds api-service which is extended by all services that call the
API to ensure they use the correct path.
This commit is contained in:
Matthew Ross 2020-06-04 08:57:03 -04:00
parent cce6e23aee
commit cccfcc827b
12 changed files with 118 additions and 82 deletions

View File

@ -1,7 +1,6 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { DragDropModule } from '@angular/cdk/drag-drop';
@ -17,16 +16,6 @@ import { DashboardModule } from './dashboard/dashboard.module';
import { SettingsModule } from './settings/settings.module';
import { SharedModule } from './shared/shared.module';
function getBasePath() {
let path = window.location.pathname;
['/boards', '/settings', '/dashboard'].forEach(p => {
path = path.replace(p, '');
});
return path;
}
@NgModule({
imports: [
BrowserModule,
@ -41,17 +30,11 @@ function getBasePath() {
RouterModule.forRoot(ROUTES)
],
providers: [
{
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: ApiInterceptor,
multi: true
},
{
provide: APP_BASE_HREF,
useFactory: getBasePath
}
],
}],
declarations: [
AppComponent,

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import * as marked from 'marked';
import hljs from 'node_modules/highlight.js/lib/core.js';
@ -12,14 +13,8 @@ import php from 'node_modules/highlight.js/lib/languages/php.js';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import {
ApiResponse,
Board,
Column,
Attachment,
Comment,
Task,
} from '../shared/models';
import { ApiResponse, Board, Column, Attachment, Comment, Task } from '../shared/models';
import { ApiService } from '../shared/services';
interface MarkedReturn {
html: string;
@ -27,7 +22,7 @@ interface MarkedReturn {
}
@Injectable()
export class BoardService {
export class BoardService extends ApiService {
private checkCounts = {
total: 0,
complete: 0
@ -36,7 +31,8 @@ export class BoardService {
public activeBoardChanged = this.activeBoard.asObservable();
constructor(private http: HttpClient) {
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat);
this.initMarked();
hljs.registerLanguage('javascript', javascript);
@ -73,7 +69,7 @@ export class BoardService {
}
getBoards(): Observable<ApiResponse> {
return this.http.get('api/boards')
return this.http.get(this.apiBase + 'boards')
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -81,7 +77,7 @@ export class BoardService {
}
toggleCollapsed(userId: number, columnId: number): Observable<ApiResponse> {
return this.http.post('api/users/' + userId + '/cols', { id: columnId })
return this.http.post(this.apiBase + 'users/' + userId + '/cols', { id: columnId })
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -89,7 +85,7 @@ export class BoardService {
}
updateBoard(board: Board): Observable<ApiResponse> {
return this.http.post('api/boards/' + board.id, board)
return this.http.post(this.apiBase + 'boards/' + board.id, board)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -97,7 +93,7 @@ export class BoardService {
}
updateColumn(column: Column): Observable<ApiResponse> {
return this.http.post('api/columns/' + column.id, column)
return this.http.post(this.apiBase + 'columns/' + column.id, column)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -105,7 +101,7 @@ export class BoardService {
}
addTask(task: Task): Observable<ApiResponse> {
return this.http.post('api/tasks', task)
return this.http.post(this.apiBase + 'tasks', task)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -113,7 +109,7 @@ export class BoardService {
}
updateTask(task: Task): Observable<ApiResponse> {
return this.http.post('api/tasks/' + task.id, task)
return this.http.post(this.apiBase + 'tasks/' + task.id, task)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -121,7 +117,7 @@ export class BoardService {
}
removeTask(taskId: number): Observable<ApiResponse> {
return this.http.delete('api/tasks/' + taskId)
return this.http.delete(this.apiBase + 'tasks/' + taskId)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -129,7 +125,7 @@ export class BoardService {
}
getTaskActivity(taskId: number): Observable<ApiResponse> {
return this.http.get('api/activity/task/' + taskId)
return this.http.get(this.apiBase + 'activity/task/' + taskId)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -137,7 +133,7 @@ export class BoardService {
}
addComment(comment: Comment): Observable<ApiResponse> {
return this.http.post('api/comments', comment)
return this.http.post(this.apiBase + 'comments', comment)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -145,7 +141,7 @@ export class BoardService {
}
updateComment(comment: Comment): Observable<ApiResponse> {
return this.http.post('api/comments/' + comment.id, comment)
return this.http.post(this.apiBase + 'comments/' + comment.id, comment)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -153,7 +149,7 @@ export class BoardService {
}
removeComment(commentId: number): Observable<ApiResponse> {
return this.http.delete('api/comments/' + commentId)
return this.http.delete(this.apiBase + 'comments/' + commentId)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -161,7 +157,7 @@ export class BoardService {
}
addAttachment(attachment: Attachment): Observable<ApiResponse> {
return this.http.post('api/attachments', attachment)
return this.http.post(this.apiBase + 'attachments', attachment)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -169,7 +165,7 @@ export class BoardService {
}
uploadAttachment(data: FormData, hash: string): Observable<ApiResponse> {
return this.http.post('api/upload/' + hash, data)
return this.http.post(this.apiBase + 'upload/' + hash, data)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -177,7 +173,7 @@ export class BoardService {
}
removeAttachment(id: number): Observable<ApiResponse> {
return this.http.delete('api/attachments/' + id)
return this.http.delete(this.apiBase + 'attachments/' + id)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -185,7 +181,7 @@ export class BoardService {
}
refreshToken(callback: any): void {
this.http.post('api/refresh', {}).subscribe(() => callback());
this.http.post(this.apiBase + 'refresh', {}).subscribe(() => callback());
}
private async convertBoardData(boardData: any): Promise<Board> {

View File

@ -1,18 +1,22 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ApiResponse } from '../shared/models';
import { ApiService } from '../shared/services';
@Injectable()
export class FileViewerService {
export class FileViewerService extends ApiService {
constructor(private http: HttpClient) {}
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat);
}
getAttachmentInfo(hash: string): Observable<ApiResponse> {
return this.http.get('api/attachments/hash/' + hash)
return this.http.get(this.apiBase +'attachments/hash/' + hash)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@ -8,14 +9,16 @@ import {
ApiResponse,
AutoAction
} from '../../shared/models';
import { ApiService } from 'src/app/shared/services';
@Injectable()
export class AutoActionsService {
constructor(private http: HttpClient) {
export class AutoActionsService extends ApiService {
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat)
}
addAction(action: AutoAction): Observable<ApiResponse> {
return this.http.post('api/autoactions', action)
return this.http.post(this.apiBase + 'autoactions', action)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -23,7 +26,7 @@ export class AutoActionsService {
}
removeAction(action: AutoAction): Observable<ApiResponse> {
return this.http.delete('api/autoactions/' + action.id)
return this.http.delete(this.apiBase + 'autoactions/' + action.id)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))

View File

@ -1,19 +1,22 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ApiResponse } from '../../shared/models';
import { BoardData } from './board-data.model';
import { ApiService } from 'src/app/shared/services';
@Injectable()
export class BoardAdminService {
constructor(private http: HttpClient) {
export class BoardAdminService extends ApiService {
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat);
}
addBoard(board: BoardData): Observable<ApiResponse> {
return this.http.post('api/boards', board)
return this.http.post(this.apiBase + 'boards', board)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -21,7 +24,7 @@ export class BoardAdminService {
}
editBoard(board: BoardData): Observable<ApiResponse> {
return this.http.post('api/boards/' + board.id, board)
return this.http.post(this.apiBase + 'boards/' + board.id, board)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -29,7 +32,7 @@ export class BoardAdminService {
}
removeBoard(boardId: number): Observable<ApiResponse> {
return this.http.delete('api/boards/' + boardId)
return this.http.delete(this.apiBase + 'boards/' + boardId)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@ -10,11 +11,12 @@ import {
Board,
AutoAction
} from '../shared/models';
import { ApiService } from '../shared/services';
@Injectable({
providedIn: 'root'
})
export class SettingsService {
export class SettingsService extends ApiService {
private users = new BehaviorSubject<User[]>([]);
private boards = new BehaviorSubject<Board[]>([]);
private actions = new BehaviorSubject<AutoAction[]>([]);
@ -23,7 +25,8 @@ export class SettingsService {
public boardsChanged = this.boards.asObservable();
public actionsChanged = this.actions.asObservable();
constructor(private http: HttpClient) {
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat);
}
updateUsers(users: User[]): void {
@ -31,7 +34,7 @@ export class SettingsService {
}
getUsers(): Observable<ApiResponse> {
return this.http.get('api/users')
return this.http.get(this.apiBase + 'users')
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -46,7 +49,7 @@ export class SettingsService {
}
getBoards(): Observable<ApiResponse> {
return this.http.get('api/boards')
return this.http.get(this.apiBase + 'boards')
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -58,7 +61,7 @@ export class SettingsService {
}
getActions(): Observable<ApiResponse> {
return this.http.get('api/autoactions')
return this.http.get(this.apiBase + 'autoactions')
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))

View File

@ -1,19 +1,22 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ApiResponse } from '../../shared/models';
import { ApiResponse } from 'src/app/shared/models';
import { ModalUser } from './user-admin.models';
import { ApiService } from 'src/app/shared/services';
@Injectable()
export class UserAdminService {
constructor(private http: HttpClient) {
export class UserAdminService extends ApiService {
constructor(private http: HttpClient, strat: LocationStrategy) {
super(strat);
}
addUser(user: ModalUser): Observable<ApiResponse> {
return this.http.post('api/users', user)
return this.http.post(this.apiBase + 'users', user)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -21,7 +24,7 @@ export class UserAdminService {
}
editUser(user: ModalUser): Observable<ApiResponse> {
return this.http.post('api/users/' + user.id, user)
return this.http.post(this.apiBase + 'users/' + user.id, user)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -29,7 +32,7 @@ export class UserAdminService {
}
removeUser(userId: number): Observable<ApiResponse> {
return this.http.delete('api/users/' + userId)
return this.http.delete(this.apiBase + 'users/' + userId)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))

View File

@ -1,5 +1,6 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LocationStrategy } from '@angular/common';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
@ -9,7 +10,7 @@ import {
UserOptions,
ApiResponse
} from '../../shared/models';
import { AuthService } from '../../shared/services';
import { AuthService, ApiService } from '../../shared/services';
interface UpdateUser extends User {
new_password?: string;
@ -17,17 +18,19 @@ interface UpdateUser extends User {
}
@Injectable()
export class UserSettingsService {
export class UserSettingsService extends ApiService {
activeUser: User = null;
constructor(private auth: AuthService, private http: HttpClient) {
constructor(private auth: AuthService, private http: HttpClient, strat: LocationStrategy) {
super(strat);
auth.userChanged.subscribe(user => this.activeUser = user);
}
changeDefaultBoard(user: User): Observable<ApiResponse> {
const json = JSON.stringify(user);
return this.http.post('api/users/' + this.activeUser.id, json)
return this.http.post(this.apiBase + 'users/' + this.activeUser.id, json)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -41,7 +44,7 @@ export class UserSettingsService {
const json = JSON.stringify(updateUser);
return this.http.post('api/users/' + this.activeUser.id, json)
return this.http.post(this.apiBase + 'users/' + this.activeUser.id, json)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
@ -54,7 +57,7 @@ export class UserSettingsService {
const json = JSON.stringify(updateUser);
return this.http.post('api/users/' + this.activeUser.id, json)
return this.http.post(this.apiBase + 'users/' + this.activeUser.id, json)
.pipe(
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[1]));
@ -70,7 +73,7 @@ export class UserSettingsService {
const json = JSON.stringify(updateUser);
return this.http.post('api/users/' + this.activeUser.id, json)
return this.http.post(this.apiBase + 'users/' + this.activeUser.id, json)
.pipe(
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[1]));
@ -83,7 +86,7 @@ export class UserSettingsService {
changeUserOptions(newOptions: UserOptions): Observable<ApiResponse> {
const json = JSON.stringify(newOptions);
return this.http.post('api/users/' + this.activeUser.id + '/opts', json)
return this.http.post(this.apiBase + 'users/' + this.activeUser.id + '/opts', json)
.pipe(
map((response: ApiResponse) => {
this.auth.updateUser(JSON.parse(response.data[2]),

View File

@ -0,0 +1,11 @@
import { Injectable } from '@angular/core';
import { LocationStrategy } from '@angular/common';
@Injectable()
export class ApiService {
protected apiBase: string;
constructor(private strat: LocationStrategy) {
this.apiBase = this.strat.getBaseHref() + 'api/';
}
}

View File

@ -12,9 +12,11 @@ import {
} from '../models';
import { Constants } from '../constants';
import { StringsService } from '../strings/strings.service';
import { LocationStrategy } from '@angular/common';
import { ApiService } from '../api-service.service';
@Injectable()
export class AuthService {
export class AuthService extends ApiService {
private activeUser = new BehaviorSubject<User>(null);
public userOptions: UserOptions = null;
@ -22,8 +24,9 @@ export class AuthService {
public attemptedRoute: string;
constructor(public constants: Constants, private http: HttpClient,
public router: Router, private strings: StringsService) {
constructor(public constants: Constants, private http: HttpClient, public router: Router,
private strings: StringsService, strat: LocationStrategy) {
super(strat);
}
updateUser(user: User, userOpts?: UserOptions): void {
@ -40,7 +43,7 @@ export class AuthService {
this.attemptedRoute = url;
}
return this.http.post('api/authenticate', null)
return this.http.post(this.apiBase + 'authenticate', null)
.pipe(
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
@ -54,7 +57,7 @@ export class AuthService {
remember: boolean): Observable<ApiResponse> {
const json = JSON.stringify({ username, password, remember });
return this.http.post('api/login', json)
return this.http.post(this.apiBase + 'login', json)
.pipe(
map((response: ApiResponse) => {
this.updateUser(response.data[1], response.data[2]);
@ -68,7 +71,7 @@ export class AuthService {
}
logout(): Observable<ApiResponse> {
return this.http.post('api/logout', null)
return this.http.post(this.apiBase + 'logout', null)
.pipe(
map((response: ApiResponse) => {
return response;

View File

@ -5,4 +5,4 @@ export * from './context-menu/context-menu.service';
export * from './modal/modal.service';
export * from './notifications/notifications.service';
export * from './strings/strings.service';
export * from './api-service.service';

View File

@ -21,7 +21,31 @@
<meta name="theme-color" content="#ffffff">
<title>TaskBoard - Kanban App</title>
<script type="text/javascript">
(function () {
var path = window.location.pathname;
['/boards', '/settings', '/dashboard'].forEach(p => {
const i = path.indexOf(p)
if (i > 0) {
path = path.substring(0, i);
}
});
if (path[path.length - 1] !== '/') {
path += '/';
}
var base = document.createElement('base');
base.href = path;
document.head.append(base);
})();
</script>
</head>
<body>
<tb-app-component>
<div class="loading">TaskBoard is Loading...</div>