Changes in / [7899209:466d1ac]
- Location:
- src
- Files:
-
- 5 deleted
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
src/Clients/Angular/finki-chattery/src/app/core/state/question-facade.service.ts
r7899209 r466d1ac 5 5 import { catchError, filter, map } from 'rxjs/operators'; 6 6 7 import { 8 PreviewQuestionsOrderEnum, 9 PreviewQuestionViewModel, 10 QuestionStateViewModel, 11 SearchQuestionsQueryViewModel 12 } from 'src/app/shared-app/models'; 7 import { PreviewQuestionsOrderEnum, PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models'; 13 8 import { 14 9 EffectStartedWorking, 15 10 GetPreviewQuestionsLatest, 16 11 GetPreviewQuestionsPopular, 17 GetQuestionState, 18 GetSearchQuestions 12 GetQuestionState 19 13 } from './question-state/question.actions'; 20 14 import { questionStateQuery } from './question-state/question.selectors'; … … 49 43 } 50 44 51 public getSearchQuestions(): Observable<PreviewQuestionViewModel[]> {52 return this.store.select(questionStateQuery.getSearchQuestions);53 }54 55 public getSearchQuestionsQuery(): Observable<SearchQuestionsQueryViewModel> {56 return this.store57 .select(questionStateQuery.getSearchQuestionsQuery)58 .pipe(filter((x: SearchQuestionsQueryViewModel | null): x is SearchQuestionsQueryViewModel => x !== null));59 }60 61 45 public getPreviewQuestionsLatest(): Observable<PreviewQuestionViewModel[]> { 62 46 return this.store.select(questionStateQuery.getPreviewQuestionsLatest); … … 79 63 } 80 64 81 public searchQuestions(searchText: string, categories: string[]): void {82 this.dispatchEffect(new GetSearchQuestions(searchText, categories));83 }84 85 65 private fetchPreviewQuestionsLatest(): void { 86 66 this.dispatchEffect(new GetPreviewQuestionsLatest()); -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.actions.ts
r7899209 r466d1ac 2 2 import { Action } from '@ngrx/store'; 3 3 4 import { PreviewQuestionViewModel, QuestionStateViewModel, SearchQuestionsQueryViewModel } from 'src/app/shared-app/models'; 4 import { PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models'; 5 import { PreviewQuestionResponse } from './question-state.models'; 5 6 6 7 export enum QuestionActionTypes { … … 11 12 GetPreviewQuestionsPopular = '[Question] Get preview questions Popular', 12 13 GetPreviewQuestionsPopularSuccess = '[Question] Get preview questions Popular Success', 13 GetSearchQuestions = '[Question] Get search questions',14 GetSearchQuestionsSuccess = '[Question] Get search questions Success',15 14 EffectStartedWorking = '[Question] Effect Started Working', 16 15 EffectFinishedWorking = '[Question] Effect Finished Working', … … 54 53 } 55 54 56 export class GetSearchQuestions implements Action {57 readonly type = QuestionActionTypes.GetSearchQuestions;58 59 constructor(public searchText: string, public categories: string[]) {}60 }61 62 export class GetSearchQuestionsSuccess implements Action {63 readonly type = QuestionActionTypes.GetSearchQuestionsSuccess;64 65 constructor(public payload: PreviewQuestionViewModel[], public query: SearchQuestionsQueryViewModel) {}66 }67 68 55 export class EffectStartedWorking implements Action { 69 56 readonly type = QuestionActionTypes.EffectStartedWorking; … … 88 75 | GetPreviewQuestionsLatestSuccess 89 76 | GetPreviewQuestionsPopularSuccess 90 | GetSearchQuestionsSuccess91 77 | EffectStartedWorking 92 78 | EffectFinishedWorking -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.effects.ts
r7899209 r466d1ac 1 1 import { Injectable } from '@angular/core'; 2 import { act,Actions, createEffect, ofType } from '@ngrx/effects';3 import { catchError, filter, mergeMap,switchMap, withLatestFrom } from 'rxjs/operators';4 import { PreviewQuestionsOrderEnum , SearchQuestionsQueryViewModel} from 'src/app/shared-app/models';2 import { Actions, createEffect, ofType } from '@ngrx/effects'; 3 import { catchError, filter, switchMap, withLatestFrom } from 'rxjs/operators'; 4 import { PreviewQuestionsOrderEnum } from 'src/app/shared-app/models'; 5 5 import { TranslateFromJsonService } from 'src/app/shared-app/services'; 6 6 … … 11 11 EffectFinishedWorking, 12 12 EffectFinishedWorkingError, 13 GetPreviewQuestionsLatest,14 13 GetPreviewQuestionsLatestSuccess, 15 GetPreviewQuestionsPopular,16 14 GetPreviewQuestionsPopularSuccess, 17 15 GetQuestionState, 18 16 GetQuestionStateSuccess, 19 GetSearchQuestions,20 GetSearchQuestionsSuccess,21 17 QuestionActionTypes 22 18 } from './question.actions'; … … 48 44 getPreviewQuestionsLatest$ = createEffect(() => { 49 45 return this.actions$.pipe( 50 ofType<Get PreviewQuestionsLatest>(QuestionActionTypes.GetPreviewQuestionsLatest),46 ofType<GetQuestionState>(QuestionActionTypes.GetPreviewQuestionsLatest), 51 47 withLatestFrom(this.facade.getPreviewQuestionsLatest()), 52 48 filter(([action, questions]) => questions.length === 0), … … 65 61 getPreviewQuestionsPopular$ = createEffect(() => { 66 62 return this.actions$.pipe( 67 ofType<Get PreviewQuestionsPopular>(QuestionActionTypes.GetPreviewQuestionsPopular),63 ofType<GetQuestionState>(QuestionActionTypes.GetPreviewQuestionsPopular), 68 64 withLatestFrom(this.facade.getPreviewQuestionsPopular()), 69 65 filter(([action, questions]) => questions.length === 0), … … 79 75 ); 80 76 }); 81 82 getSearchQuestions$ = createEffect(() => {83 return this.actions$.pipe(84 ofType<GetSearchQuestions>(QuestionActionTypes.GetSearchQuestions),85 mergeMap((action) => {86 const categoriesAsString = action.categories !== null ? action.categories.join(',') : '';87 return this.api88 .get<PreviewQuestionResponse[]>(`v1/questions/search?searchText=${action.searchText}&categories=${categoriesAsString}`)89 .pipe(90 switchMap((state) => [91 new GetSearchQuestionsSuccess(92 QuestionMapper.ToPreviwQuestionsViewModel(state, this.translate),93 new SearchQuestionsQueryViewModel(action.searchText)94 ),95 new EffectFinishedWorking()96 ]),97 catchError((err) => [new EffectFinishedWorkingError(err)])98 );99 })100 );101 });102 77 } -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.reducers.ts
r7899209 r466d1ac 18 18 ...state, 19 19 previewQuestionsPopular: action.payload 20 };21 case QuestionActionTypes.GetSearchQuestionsSuccess:22 return {23 ...state,24 searchQuestions: action.payload,25 searchQuestionsQuery: action.query26 20 }; 27 21 case QuestionActionTypes.EffectStartedWorking: { -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.selectors.ts
r7899209 r466d1ac 7 7 const getPreviewQuestionsLatest = createSelector(getQuestionState, (state) => state.previewQuestionsLatest); 8 8 const getPreviewQuestionsPopular = createSelector(getQuestionState, (state) => state.previewQuestionsPopular); 9 const getSearchQuestions = createSelector(getQuestionState, (state) => state.searchQuestions);10 const getSearchQuestionsQuery = createSelector(getQuestionState, (state) => state.searchQuestionsQuery);11 9 const effectWorking = createSelector(getQuestionState, (state) => state.effectWorking); 12 10 … … 15 13 getQuestion, 16 14 getPreviewQuestionsLatest, 17 getPreviewQuestionsPopular, 18 getSearchQuestions, 19 getSearchQuestionsQuery 15 getPreviewQuestionsPopular 20 16 }; -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.state.ts
r7899209 r466d1ac 1 1 import { HttpErrorResponse } from '@angular/common/http'; 2 import { PreviewQuestionViewModel, QuestionStateViewModel , SearchQuestionsQueryViewModel} from 'src/app/shared-app/models';2 import { PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models'; 3 3 4 4 export const questionStateKey = 'question'; … … 9 9 previewQuestionsPopular: PreviewQuestionViewModel[]; 10 10 effectWorking: boolean | HttpErrorResponse; 11 searchQuestions: PreviewQuestionViewModel[];12 searchQuestionsQuery: SearchQuestionsQueryViewModel | null;13 11 } 14 12 … … 17 15 previewQuestionsLatest: [], 18 16 previewQuestionsPopular: [], 19 searchQuestions: [], 20 effectWorking: false, 21 searchQuestionsQuery: null 17 effectWorking: false 22 18 }; -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questioning-components.ts
r7899209 r466d1ac 2 2 import { QuestioningGeneralComponent } from './questioning-general/questioning-general.component'; 3 3 import { QuestionsPreviewGeneralComponent } from './questions-preview-general/questions-preview-general.component'; 4 import { QuestionsSearchComponent } from './questions-search/questions-search.component';5 4 6 5 export const QUESTIONING_COMPONENTS: any[] = [ 7 6 QuestionPreviewGeneralComponent, 8 7 QuestionsPreviewGeneralComponent, 9 QuestioningGeneralComponent, 10 QuestionsSearchComponent 8 QuestioningGeneralComponent 11 9 ]; -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questions-preview-general/questions-preview-general.component.html
r7899209 r466d1ac 1 <app-search-question (searched)="routeToSearch()"></app-search-question>1 <app-search-question></app-search-question> 2 2 <div class="margin-x-lg"> 3 3 <h1 class="mat-headline">{{ 'questions-preview' | translate }}</h1> -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questions-preview-general/questions-preview-general.component.ts
r7899209 r466d1ac 36 36 this.router.navigateByUrl(`questioning/${uid}`); 37 37 } 38 39 routeToSearch(): void {40 this.router.navigateByUrl(`questioning/search`);41 }42 38 } -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/questioning.routes.ts
r7899209 r466d1ac 4 4 import { QuestioningGeneralComponent } from './components/questioning-general/questioning-general.component'; 5 5 import { QuestionsPreviewGeneralComponent } from './components/questions-preview-general/questions-preview-general.component'; 6 import { QuestionsSearchComponent } from './components/questions-search/questions-search.component';7 6 8 7 const routes: Routes = [ … … 15 14 pathMatch: 'full', 16 15 component: QuestionsPreviewGeneralComponent 17 },18 {19 path: 'search',20 pathMatch: 'full',21 component: QuestionsSearchComponent22 16 }, 23 17 { -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/question/search-question/search-question.component.html
r7899209 r466d1ac 11 11 <mat-icon matSuffix>search</mat-icon> 12 12 </mat-form-field> 13 <mat-form-field class="full-width"appearance="fill">13 <mat-form-field appearance="fill"> 14 14 <mat-select 15 15 [formControl]="questionCategoriesFormContor" -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/question/search-question/search-question.component.ts
r7899209 r466d1ac 1 import { Component, EventEmitter, OnInit, Output } from '@angular/core';1 import { Component, OnInit } from '@angular/core'; 2 2 import { FormControl, Validators } from '@angular/forms'; 3 4 3 import { CategoryFacadeService } from 'src/app/core/state/category-facade.service'; 5 import { QuestionFacadeService } from 'src/app/core/state/question-facade.service';4 import { CategoryStateViewModel } from 'src/app/shared-app/models'; 6 5 import { ButtonType } from '../../generic/button/button.models'; 7 6 … … 12 11 }) 13 12 export class SearchQuestionComponent implements OnInit { 14 @Output() searched = new EventEmitter();15 16 13 ButtonType = ButtonType; 17 14 questionSearchFormContor = new FormControl('', [Validators.required, Validators.maxLength(250)]); … … 19 16 categories$ = this.categoriesFacade.getCategories(); 20 17 21 constructor(private categoriesFacade: CategoryFacadeService , private questionsFacade: QuestionFacadeService) {}18 constructor(private categoriesFacade: CategoryFacadeService) {} 22 19 23 20 ngOnInit(): void {} … … 30 27 31 28 public searchQuestions(): void { 32 this.questionsFacade.searchQuestions(this.questionSearchFormContor.value, this.questionCategoriesFormContor.value); 33 this.searched.emit(); 29 alert('SEARCH'); 34 30 } 35 31 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/models/question-state-view-models.models.ts
r7899209 r466d1ac 69 69 constructor(public uid: string, public text: string, public nameTranslated: string) {} 70 70 } 71 72 export class SearchQuestionsQueryViewModel {73 constructor(public text: string) {}74 } -
src/Clients/Angular/finki-chattery/src/assets/translations/en.json
r7899209 r466d1ac 43 43 "questions-preview-question-subtitle": "Asked on: {{date}}", 44 44 "questions-preview-question-answers": "Answers", 45 "questions-preview-question-views": "Views", 46 "questions-search-title": "Search results for: {{searchQuery}}" 45 "questions-preview-question-views": "Views" 47 46 } -
src/FinkiChattery/FinkiChattery.Api/Controllers/v1/QuestionsController.cs
r7899209 r466d1ac 35 35 [HttpGet("{questionUid:Guid}")] 36 36 [Authorize] 37 public async Task<IActionResult> GetQuestionState([FromRoute] 37 public async Task<IActionResult> GetQuestionState([FromRoute]Guid questionUid) 38 38 { 39 39 var questionDto = await MediatorService.SendQueryAsync(new GetQuestionStateQuery(questionUid)); … … 48 48 return Ok(questions.ToPreviewQuestionsResponse()); 49 49 } 50 51 [HttpGet("search")]52 [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = AuthenticationPolicy.Student)]53 public async Task<IActionResult> SearchQuestions([FromQuery] string searchText, [FromQuery] string categories)54 {55 var questions = await MediatorService.SendQueryAsync(new SearchQuestionsQuery(searchText, categories));56 return Ok(questions.ToPreviewQuestionsResponse());57 }58 50 } 59 51 } -
src/FinkiChattery/FinkiChattery.Database/FullTextSearch/FullTextIndexQuestion.sql
r7899209 r466d1ac 1 1 CREATE FULLTEXT INDEX ON [dbo].[Question] ([Search]) 2 2 KEY INDEX [PK_Question] ON [QuestionFullTextCatalog] 3 WITH (CHANGE_TRACKING AUTO , STOPLIST OFF)3 WITH (CHANGE_TRACKING AUTO) -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IQuestionRepo.cs
r7899209 r466d1ac 9 9 public interface IQuestionRepo : IRepository<Question> 10 10 { 11 Task<List<QuestionPreviewDto>> SearchQuestions(string searchText, IEnumerable<Guid> categories);12 13 11 Task<QuestionStateDto> GetQuestionState(Guid questionUid); 14 12 -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/QuestionRepo.cs
r7899209 r466d1ac 2 2 using FinkiChattery.Persistence.Models; 3 3 using FinkiChattery.Persistence.Repositories.Contracts; 4 using Microsoft.Data.SqlClient;5 4 using Microsoft.EntityFrameworkCore; 6 5 using System; … … 8 7 using System.Linq; 9 8 using System.Linq.Expressions; 10 using System.Text.RegularExpressions;11 9 using System.Threading.Tasks; 12 10 … … 112 110 return questionDto; 113 111 } 114 115 public async Task<List<QuestionPreviewDto>> SearchQuestions(string searchText, IEnumerable<Guid> categories)116 {117 var search = Regex.Replace(searchText, "[\\\\/:*?\"<>\\]\\[|&'`~^=%,(){}_\\-]", " ")118 .Split(" ".ToArray(), StringSplitOptions.RemoveEmptyEntries)119 .Select(c => $"\"{c}*\"");120 121 var searchString = string.Join(" AND ", search);122 123 var rawQuery = (IQueryable<Question>)124 DbSet.FromSqlRaw(@"125 SELECT [q].[Id], [q].[Uid], [q].[Title], [q].[Views], [q].[AnswersCount], [q].[CreatedOn]126 FROM [dbo].[Question] AS [q]127 INNER JOIN CONTAINSTABLE(Question, Search, @searchString, 30) ccontains ON [q].[Id] = ccontains.[KEY]",128 new SqlParameter("searchString", searchString))129 .Include(x => x.QuestionCategories).ThenInclude(x => x.Category);130 131 if (categories.Any())132 {133 rawQuery = rawQuery.Where(x => x.QuestionCategories.Any(y => categories.Contains(y.Category.Uid)));134 }135 136 return await rawQuery137 .Select(x => new QuestionPreviewDto(x.Id,138 x.Uid,139 x.Title,140 x.Views,141 x.AnswersCount,142 x.CreatedOn,143 x.QuestionCategories.Select(y => new QuestionPreviewCategoryDto(y.Id, y.Uid, y.Category.Name))))144 .ToListAsync();145 }146 112 } 147 113 }
Note:
See TracChangeset
for help on using the changeset viewer.