Merge branch 'feature/user-profile' into develop
This commit is contained in:
commit
94601dd6c8
@ -131,3 +131,7 @@ CORS_ALLOW_CREDENTIALS = True
|
||||
CORS_ORIGIN_WHITELIST = [
|
||||
'http://localhost:4200',
|
||||
]
|
||||
CORS_ALLOW_HEADERS = [
|
||||
'Bearer',
|
||||
'content-type'
|
||||
]
|
25
frontend/src/app/actions/available-forums.actions.ts
Normal file
25
frontend/src/app/actions/available-forums.actions.ts
Normal 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 }>()
|
||||
);
|
@ -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 {}
|
||||
|
120
frontend/src/app/effects/available-forums.effects.ts
Normal file
120
frontend/src/app/effects/available-forums.effects.ts
Normal 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 }
|
||||
);
|
||||
}
|
27
frontend/src/app/interceptors/auth.interceptor.ts
Normal file
27
frontend/src/app/interceptors/auth.interceptor.ts
Normal 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);
|
||||
}
|
||||
}
|
8
frontend/src/app/interceptors/index.ts
Normal file
8
frontend/src/app/interceptors/index.ts
Normal 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 },
|
||||
];
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MyProfileComponent,
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -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 {}
|
||||
|
@ -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>
|
||||
|
47
frontend/src/app/reducers/available-forums.reducers.ts
Normal file
47
frontend/src/app/reducers/available-forums.reducers.ts
Normal 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);
|
||||
}
|
@ -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
|
||||
|
28
frontend/src/app/selectors/available-forums.selectors.ts
Normal file
28
frontend/src/app/selectors/available-forums.selectors.ts
Normal 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
|
||||
);
|
@ -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/');
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user