Merge branch 'feature/user-profile' into develop

This commit is contained in:
Michał Romaszkin 2020-11-17 00:41:18 +01:00
commit 94601dd6c8
16 changed files with 394 additions and 3 deletions

View File

@ -131,3 +131,7 @@ CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
'http://localhost:4200',
]
CORS_ALLOW_HEADERS = [
'Bearer',
'content-type'
]

View File

@ -0,0 +1,25 @@
import { HttpErrorResponse } from '@angular/common/http';
import { createAction, props } from '@ngrx/store';
export const getAvailableForums = createAction(
'[AvailableForums Component] Get Forums'
);
export const fetchForum = createAction(
'[AvailableForums Component] Fetch Forum',
props<{ payload: File }>()
);
export const sendForum = createAction('[AvailableForums Component] Send Forum');
export const sendFileSuccess = createAction(
'[AvailableForums Component] Send Success'
);
export const sendFileError = createAction(
'[AvailableForums Component] Send Failure',
props<{ error: HttpErrorResponse }>()
);
export const getAvailableForumsSuccess = createAction(
'[AvailableForums Component] Get Forums Success',
props<{ files: any }>()
);

View File

@ -29,6 +29,7 @@ import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { AuthGuard } from './services/auth-guard.service';
import { httpInterceptorProviders } from './interceptors/index';
@NgModule({
declarations: [AppComponent],
@ -95,6 +96,7 @@ import { AuthGuard } from './services/auth-guard.service';
NbSidebarService,
SidebarItemsService,
AuthGuard,
httpInterceptorProviders,
],
})
export class AppModule {}

View File

@ -0,0 +1,120 @@
import { Injectable } from '@angular/core';
import { SendDataService } from '../services/send-data.service';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { State as AppState } from '../reducers/index';
import { Store } from '@ngrx/store';
import {
withLatestFrom,
tap,
map,
catchError,
concatMap,
flatMap,
} from 'rxjs/operators';
import { EMPTY, of } from 'rxjs';
import { NbToastrService } from '@nebular/theme';
import * as AvailableForumActions from '../actions/available-forums.actions';
import * as AvailableForumSelectors from '../selectors/available-forums.selectors';
import { Router } from '@angular/router';
import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class AvailableForumsEffect {
constructor(
private sendDataService: SendDataService,
private actions$: Actions,
private store$: Store<AppState>,
private toastService: NbToastrService,
private router: Router
) {}
file$ = createEffect(
() =>
this.actions$.pipe(
ofType(AvailableForumActions.sendForum),
concatMap((action) =>
of(action).pipe(
withLatestFrom(
this.store$.select(AvailableForumSelectors.selectFile)
)
)
),
flatMap(([_, file]) =>
this.sendDataService.postFile(file).pipe(
tap(() => {
this.store$.dispatch(AvailableForumActions.sendFileSuccess());
this.store$.dispatch(AvailableForumActions.getAvailableForums());
}),
catchError((error) =>
of(
this.store$.dispatch(
AvailableForumActions.sendFileError({ error })
)
)
)
)
)
),
{ dispatch: false }
);
$get = createEffect(() =>
this.actions$.pipe(
ofType(AvailableForumActions.getAvailableForums),
flatMap(() =>
this.sendDataService.getForums().pipe(
map((result) =>
AvailableForumActions.getAvailableForumsSuccess({
files: result.files,
})
),
catchError(() => EMPTY)
)
)
)
);
$toastSuccess = createEffect(
() =>
this.actions$.pipe(
ofType(AvailableForumActions.sendFileSuccess),
tap(() =>
this.toastService.success('', 'Plik dodano pomyślnie!', {
icon: 'checkmark-circle2',
})
)
),
{ dispatch: false }
);
$toast = createEffect(
() =>
this.actions$.pipe(
ofType(AvailableForumActions.sendFileError),
tap(({ error }) => {
switch (error.status) {
case 500: {
this.toastService.danger('', 'Zła struktura pliku!', {
icon: 'alert-circle',
});
break;
}
case 400: {
this.router.navigate(['/login']);
break;
}
case 401: {
this.router.navigate(['/login']);
break;
}
case 422: {
this.toastService.danger('', 'Nie można przetworzyć pliku!', {
icon: 'alert-circle',
});
}
}
})
),
{ dispatch: false }
);
}

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { NbAuthService, NbAuthToken } from '@nebular/auth';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: NbAuthService) {}
token: NbAuthToken;
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
this.authService.getToken().subscribe((token) => (this.token = token));
const authRequest = request.clone({
headers: request.headers.set('Bearer', this.token.toString()),
});
console.log(authRequest);
return next.handle(authRequest);
}
}

View File

@ -0,0 +1,8 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';
/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
];

View File

@ -0,0 +1,38 @@
<nb-card accent="info">
<nb-card-header class="h5"> Moje fora </nb-card-header>
<nb-list>
<nb-list-item *ngFor="let forum of forums$ | async">
{{ forum.id }} {{ forum.file }} {{ forum.uploaded_at }}
</nb-list-item>
</nb-list>
</nb-card>
<nb-card>
<nb-card-header class="h6">Dodaj nowe forum</nb-card-header>
<nb-card-body class="addButtons__container">
<button
nbButton
status="primary"
(click)="openFileDialog($event)"
class="choose-file-button"
>
Wybierz plik
</button>
<input
id="input-for-file"
type="file"
(change)="fetchFile($event)"
accept=".xml"
/>
<button
nbButton
status="success"
(click)="sendFile($event)"
[disabled]="!(isFileFetched$ | async)"
>
Wyślij!
</button>
<span *ngIf="fileName$ | async" class="addButtons__fileName">
<nb-icon icon="file-add-outline"></nb-icon> {{ fileName$ | async }}
</span>
</nb-card-body>
</nb-card>

View File

@ -0,0 +1,18 @@
input[type="file"] {
display: none;
}
.choose-file-button {
margin-right: 0.5rem;
}
.addButtons {
&__container {
display: flex;
align-items: center;
}
&__fileName {
display: flex;
margin: 0 0.5rem;
}
}

View File

@ -0,0 +1,43 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { State } from 'src/app/reducers';
import * as Selectors from '../../selectors/available-forums.selectors';
import * as Actions from '../../actions/available-forums.actions';
@Component({
selector: 'available-forums',
templateUrl: './available-forums.component.html',
styleUrls: ['./available-forums.component.scss'],
})
export class AvailableForumsComponent implements OnInit {
file$: Observable<File>;
fileName$: Observable<string>;
isFileFetched$: Observable<boolean>;
forums$: Observable<Array<{ id: string; file: string; uploaded_at: string }>>;
constructor(private store: Store<State>) {}
ngOnInit(): void {
this.store.dispatch(Actions.getAvailableForums());
this.file$ = this.store.select(Selectors.selectFile);
this.fileName$ = this.store.select(Selectors.selectFileName);
this.isFileFetched$ = this.store.select(Selectors.selectFetchStatus);
this.forums$ = this.store.select(Selectors.selectForums);
}
openFileDialog(event: any): void {
event.preventDefault();
const element: HTMLElement = document.getElementById(
'input-for-file'
) as HTMLElement;
element.click();
}
fetchFile(event: any) {
this.store.dispatch(Actions.fetchForum({ payload: event.target.files[0] }));
}
sendFile(event: any) {
this.store.dispatch(Actions.sendForum());
}
}

View File

@ -6,6 +6,7 @@ const routes: Routes = [
{
path: '',
component: MyProfileComponent,
children: [],
},
];

View File

@ -2,16 +2,32 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyProfileComponent } from './my-profile/my-profile.component';
import { MyProfileRoutingModule } from './my-profile-routing.module';
import { NbActionsModule, NbLayoutModule, NbMenuModule } from '@nebular/theme';
import {
NbActionsModule,
NbButtonModule,
NbCardModule,
NbIconModule,
NbLayoutModule,
NbListModule,
NbMenuModule,
} from '@nebular/theme';
import { AvailableForumsComponent } from './available-forums/available-forums.component';
import { EffectsModule } from '@ngrx/effects';
import { AvailableForumsEffect } from '../effects/available-forums.effects';
@NgModule({
declarations: [MyProfileComponent],
declarations: [MyProfileComponent, AvailableForumsComponent],
imports: [
CommonModule,
MyProfileRoutingModule,
NbLayoutModule,
NbMenuModule,
NbActionsModule,
NbButtonModule,
NbIconModule,
NbListModule,
EffectsModule.forFeature([AvailableForumsEffect]),
NbCardModule,
],
})
export class MyProfileModule {}

View File

@ -12,4 +12,8 @@
</nb-actions>
</div>
</nb-layout-header>
<nb-layout-column>
<available-forums></available-forums>
</nb-layout-column>
<nb-layout-column> </nb-layout-column>
</nb-layout>

View File

@ -0,0 +1,47 @@
import { Action, createReducer, on } from '@ngrx/store';
import * as Actions from '../actions/available-forums.actions';
export interface AvailableForumsState {
files: Array<{ id: string; file: string; uploaded_at: string }>;
fileToSend: { file: File; fileName: string; isFileFetched: boolean };
}
export const initialState: AvailableForumsState = {
files: [],
fileToSend: {
file: {} as File,
isFileFetched: false,
fileName: '',
},
};
const _availableForumsReducer = createReducer(
initialState,
on(Actions.fetchForum, (state, { payload }) => ({
...state,
fileToSend: {
file: payload,
isFileFetched: true,
fileName: payload.name,
},
})),
on(Actions.getAvailableForumsSuccess, (state, { files }) => ({
...state,
files,
})),
on(Actions.sendFileSuccess, (state) => ({
...state,
fileToSend: {
file: {} as File,
isFileFetched: false,
fileName: '',
},
}))
);
export function availableForumsReducer(
state: AvailableForumsState | undefined,
action: Action
) {
return _availableForumsReducer(state, action);
}

View File

@ -11,6 +11,10 @@ import { dataReducer, DataState } from './data.reducers';
import * as fromRouter from '@ngrx/router-store';
import { PredictedPost } from '../_interfaces/predictedposts';
import { discussionReducer } from './discussion.reducers';
import {
availableForumsReducer,
AvailableForumsState,
} from './available-forums.reducers';
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
return function (state, action) {
@ -26,6 +30,7 @@ export interface State {
dataState: DataState;
router: fromRouter.RouterReducerState<any>;
currentDiscussion: PredictedPost[];
availableForums: AvailableForumsState;
}
export const reducers: ActionReducerMap<State> = {
@ -33,6 +38,7 @@ export const reducers: ActionReducerMap<State> = {
dataState: dataReducer,
router: fromRouter.routerReducer,
currentDiscussion: discussionReducer,
availableForums: availableForumsReducer,
};
export const metaReducers: MetaReducer<State>[] = !environment.production

View File

@ -0,0 +1,28 @@
import { createSelector, createFeatureSelector } from '@ngrx/store';
import { State as AppState } from '../reducers/index';
import { AvailableForumsState } from '../reducers/available-forums.reducers';
export const selectFeature = createFeatureSelector<
AppState,
AvailableForumsState
>('availableForums');
export const selectFile = createSelector(
selectFeature,
(state: AvailableForumsState) => state.fileToSend.file
);
export const selectFileName = createSelector(
selectFeature,
(state: AvailableForumsState) => state.fileToSend.fileName
);
export const selectFetchStatus = createSelector(
selectFeature,
(state: AvailableForumsState) => state.fileToSend.isFileFetched
);
export const selectForums = createSelector(
selectFeature,
(state: AvailableForumsState) => state.files
);

View File

@ -14,9 +14,13 @@ export class SendDataService {
formData.append('file', file, file.name);
return this.http.post<File>(
'http://127.0.0.1:8000/prototype/form/',
'http://127.0.0.1:8000/file/',
formData,
requestOptions
);
}
getForums(): Observable<{ files: any }> {
return this.http.get<{ files: any }>('http://127.0.0.1:8000/file/');
}
}