Changes in / [466d1ac:7899209]
- Location:
- src
- Files:
-
- 5 added
- 18 edited
Legend:
- Unmodified
- Added
- Removed
-
src/Clients/Angular/finki-chattery/src/app/core/state/question-facade.service.ts
r466d1ac r7899209 5 5 import { catchError, filter, map } from 'rxjs/operators'; 6 6 7 import { PreviewQuestionsOrderEnum, PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models'; 7 import { 8 PreviewQuestionsOrderEnum, 9 PreviewQuestionViewModel, 10 QuestionStateViewModel, 11 SearchQuestionsQueryViewModel 12 } from 'src/app/shared-app/models'; 8 13 import { 9 14 EffectStartedWorking, 10 15 GetPreviewQuestionsLatest, 11 16 GetPreviewQuestionsPopular, 12 GetQuestionState 17 GetQuestionState, 18 GetSearchQuestions 13 19 } from './question-state/question.actions'; 14 20 import { questionStateQuery } from './question-state/question.selectors'; … … 43 49 } 44 50 51 public getSearchQuestions(): Observable<PreviewQuestionViewModel[]> { 52 return this.store.select(questionStateQuery.getSearchQuestions); 53 } 54 55 public getSearchQuestionsQuery(): Observable<SearchQuestionsQueryViewModel> { 56 return this.store 57 .select(questionStateQuery.getSearchQuestionsQuery) 58 .pipe(filter((x: SearchQuestionsQueryViewModel | null): x is SearchQuestionsQueryViewModel => x !== null)); 59 } 60 45 61 public getPreviewQuestionsLatest(): Observable<PreviewQuestionViewModel[]> { 46 62 return this.store.select(questionStateQuery.getPreviewQuestionsLatest); … … 63 79 } 64 80 81 public searchQuestions(searchText: string, categories: string[]): void { 82 this.dispatchEffect(new GetSearchQuestions(searchText, categories)); 83 } 84 65 85 private fetchPreviewQuestionsLatest(): void { 66 86 this.dispatchEffect(new GetPreviewQuestionsLatest()); -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.actions.ts
r466d1ac r7899209 2 2 import { Action } from '@ngrx/store'; 3 3 4 import { PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models'; 5 import { PreviewQuestionResponse } from './question-state.models'; 4 import { PreviewQuestionViewModel, QuestionStateViewModel, SearchQuestionsQueryViewModel } from 'src/app/shared-app/models'; 6 5 7 6 export enum QuestionActionTypes { … … 12 11 GetPreviewQuestionsPopular = '[Question] Get preview questions Popular', 13 12 GetPreviewQuestionsPopularSuccess = '[Question] Get preview questions Popular Success', 13 GetSearchQuestions = '[Question] Get search questions', 14 GetSearchQuestionsSuccess = '[Question] Get search questions Success', 14 15 EffectStartedWorking = '[Question] Effect Started Working', 15 16 EffectFinishedWorking = '[Question] Effect Finished Working', … … 53 54 } 54 55 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 55 68 export class EffectStartedWorking implements Action { 56 69 readonly type = QuestionActionTypes.EffectStartedWorking; … … 75 88 | GetPreviewQuestionsLatestSuccess 76 89 | GetPreviewQuestionsPopularSuccess 90 | GetSearchQuestionsSuccess 77 91 | EffectStartedWorking 78 92 | EffectFinishedWorking -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.effects.ts
r466d1ac r7899209 1 1 import { Injectable } from '@angular/core'; 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';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'; 5 5 import { TranslateFromJsonService } from 'src/app/shared-app/services'; 6 6 … … 11 11 EffectFinishedWorking, 12 12 EffectFinishedWorkingError, 13 GetPreviewQuestionsLatest, 13 14 GetPreviewQuestionsLatestSuccess, 15 GetPreviewQuestionsPopular, 14 16 GetPreviewQuestionsPopularSuccess, 15 17 GetQuestionState, 16 18 GetQuestionStateSuccess, 19 GetSearchQuestions, 20 GetSearchQuestionsSuccess, 17 21 QuestionActionTypes 18 22 } from './question.actions'; … … 44 48 getPreviewQuestionsLatest$ = createEffect(() => { 45 49 return this.actions$.pipe( 46 ofType<Get QuestionState>(QuestionActionTypes.GetPreviewQuestionsLatest),50 ofType<GetPreviewQuestionsLatest>(QuestionActionTypes.GetPreviewQuestionsLatest), 47 51 withLatestFrom(this.facade.getPreviewQuestionsLatest()), 48 52 filter(([action, questions]) => questions.length === 0), … … 61 65 getPreviewQuestionsPopular$ = createEffect(() => { 62 66 return this.actions$.pipe( 63 ofType<Get QuestionState>(QuestionActionTypes.GetPreviewQuestionsPopular),67 ofType<GetPreviewQuestionsPopular>(QuestionActionTypes.GetPreviewQuestionsPopular), 64 68 withLatestFrom(this.facade.getPreviewQuestionsPopular()), 65 69 filter(([action, questions]) => questions.length === 0), … … 75 79 ); 76 80 }); 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.api 88 .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 }); 77 102 } -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.reducers.ts
r466d1ac r7899209 18 18 ...state, 19 19 previewQuestionsPopular: action.payload 20 }; 21 case QuestionActionTypes.GetSearchQuestionsSuccess: 22 return { 23 ...state, 24 searchQuestions: action.payload, 25 searchQuestionsQuery: action.query 20 26 }; 21 27 case QuestionActionTypes.EffectStartedWorking: { -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.selectors.ts
r466d1ac r7899209 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); 9 11 const effectWorking = createSelector(getQuestionState, (state) => state.effectWorking); 10 12 … … 13 15 getQuestion, 14 16 getPreviewQuestionsLatest, 15 getPreviewQuestionsPopular 17 getPreviewQuestionsPopular, 18 getSearchQuestions, 19 getSearchQuestionsQuery 16 20 }; -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.state.ts
r466d1ac r7899209 1 1 import { HttpErrorResponse } from '@angular/common/http'; 2 import { PreviewQuestionViewModel, QuestionStateViewModel } from 'src/app/shared-app/models';2 import { PreviewQuestionViewModel, QuestionStateViewModel, SearchQuestionsQueryViewModel } 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; 11 13 } 12 14 … … 15 17 previewQuestionsLatest: [], 16 18 previewQuestionsPopular: [], 17 effectWorking: false 19 searchQuestions: [], 20 effectWorking: false, 21 searchQuestionsQuery: null 18 22 }; -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questioning-components.ts
r466d1ac r7899209 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'; 4 5 5 6 export const QUESTIONING_COMPONENTS: any[] = [ 6 7 QuestionPreviewGeneralComponent, 7 8 QuestionsPreviewGeneralComponent, 8 QuestioningGeneralComponent 9 QuestioningGeneralComponent, 10 QuestionsSearchComponent 9 11 ]; -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questions-preview-general/questions-preview-general.component.html
r466d1ac r7899209 1 <app-search-question ></app-search-question>1 <app-search-question (searched)="routeToSearch()"></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
r466d1ac r7899209 36 36 this.router.navigateByUrl(`questioning/${uid}`); 37 37 } 38 39 routeToSearch(): void { 40 this.router.navigateByUrl(`questioning/search`); 41 } 38 42 } -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/questioning.routes.ts
r466d1ac r7899209 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'; 6 7 7 8 const routes: Routes = [ … … 14 15 pathMatch: 'full', 15 16 component: QuestionsPreviewGeneralComponent 17 }, 18 { 19 path: 'search', 20 pathMatch: 'full', 21 component: QuestionsSearchComponent 16 22 }, 17 23 { -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/question/search-question/search-question.component.html
r466d1ac r7899209 11 11 <mat-icon matSuffix>search</mat-icon> 12 12 </mat-form-field> 13 <mat-form-field appearance="fill">13 <mat-form-field class="full-width" 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
r466d1ac r7899209 1 import { Component, OnInit } from '@angular/core';1 import { Component, EventEmitter, OnInit, Output } from '@angular/core'; 2 2 import { FormControl, Validators } from '@angular/forms'; 3 3 4 import { CategoryFacadeService } from 'src/app/core/state/category-facade.service'; 4 import { CategoryStateViewModel } from 'src/app/shared-app/models';5 import { QuestionFacadeService } from 'src/app/core/state/question-facade.service'; 5 6 import { ButtonType } from '../../generic/button/button.models'; 6 7 … … 11 12 }) 12 13 export class SearchQuestionComponent implements OnInit { 14 @Output() searched = new EventEmitter(); 15 13 16 ButtonType = ButtonType; 14 17 questionSearchFormContor = new FormControl('', [Validators.required, Validators.maxLength(250)]); … … 16 19 categories$ = this.categoriesFacade.getCategories(); 17 20 18 constructor(private categoriesFacade: CategoryFacadeService ) {}21 constructor(private categoriesFacade: CategoryFacadeService, private questionsFacade: QuestionFacadeService) {} 19 22 20 23 ngOnInit(): void {} … … 27 30 28 31 public searchQuestions(): void { 29 alert('SEARCH'); 32 this.questionsFacade.searchQuestions(this.questionSearchFormContor.value, this.questionCategoriesFormContor.value); 33 this.searched.emit(); 30 34 } 31 35 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/models/question-state-view-models.models.ts
r466d1ac r7899209 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
r466d1ac r7899209 43 43 "questions-preview-question-subtitle": "Asked on: {{date}}", 44 44 "questions-preview-question-answers": "Answers", 45 "questions-preview-question-views": "Views" 45 "questions-preview-question-views": "Views", 46 "questions-search-title": "Search results for: {{searchQuery}}" 46 47 } -
src/FinkiChattery/FinkiChattery.Api/Controllers/v1/QuestionsController.cs
r466d1ac r7899209 35 35 [HttpGet("{questionUid:Guid}")] 36 36 [Authorize] 37 public async Task<IActionResult> GetQuestionState([FromRoute] Guid questionUid)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 } 50 58 } 51 59 } -
src/FinkiChattery/FinkiChattery.Database/FullTextSearch/FullTextIndexQuestion.sql
r466d1ac r7899209 1 1 CREATE FULLTEXT INDEX ON [dbo].[Question] ([Search]) 2 2 KEY INDEX [PK_Question] ON [QuestionFullTextCatalog] 3 WITH (CHANGE_TRACKING AUTO )3 WITH (CHANGE_TRACKING AUTO, STOPLIST OFF) -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IQuestionRepo.cs
r466d1ac r7899209 9 9 public interface IQuestionRepo : IRepository<Question> 10 10 { 11 Task<List<QuestionPreviewDto>> SearchQuestions(string searchText, IEnumerable<Guid> categories); 12 11 13 Task<QuestionStateDto> GetQuestionState(Guid questionUid); 12 14 -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/QuestionRepo.cs
r466d1ac r7899209 2 2 using FinkiChattery.Persistence.Models; 3 3 using FinkiChattery.Persistence.Repositories.Contracts; 4 using Microsoft.Data.SqlClient; 4 5 using Microsoft.EntityFrameworkCore; 5 6 using System; … … 7 8 using System.Linq; 8 9 using System.Linq.Expressions; 10 using System.Text.RegularExpressions; 9 11 using System.Threading.Tasks; 10 12 … … 110 112 return questionDto; 111 113 } 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 rawQuery 137 .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 } 112 146 } 113 147 }
Note:
See TracChangeset
for help on using the changeset viewer.