Changes in / [81e1ed6:79ae621]


Ignore:
Files:
93 added
5 deleted
65 edited

Legend:

Unmodified
Added
Removed
  • .gitignore

    r81e1ed6 r79ae621  
    284284*.xsd.cs
    285285.terraform
     286
     287# mac os
     288*.DS_Store
  • src/Clients/Angular/finki-chattery/angular.json

    r81e1ed6 r79ae621  
    9898            "tsConfig": "tsconfig.spec.json",
    9999            "karmaConfig": "karma.conf.js",
    100             "assets": ["src/favicon.ico", "src/assets"],
    101             "styles": ["./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss"],
     100            "assets": [
     101              "src/favicon.ico",
     102              "src/assets"
     103            ],
     104            "styles": [
     105              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
     106              "src/styles.scss"
     107            ],
    102108            "scripts": []
    103109          }
     
    106112          "builder": "@angular-devkit/build-angular:tslint",
    107113          "options": {
    108             "tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"],
    109             "exclude": ["**/node_modules/**"]
     114            "tsConfig": [
     115              "tsconfig.app.json",
     116              "tsconfig.spec.json",
     117              "e2e/tsconfig.json"
     118            ],
     119            "exclude": [
     120              "**/node_modules/**"
     121            ]
    110122          }
    111123        },
     
    125137    }
    126138  },
    127   "defaultProject": "finki-chattery"
     139  "defaultProject": "finki-chattery",
     140  "cli": {
     141    "analytics": false
     142  }
    128143}
  • src/Clients/Angular/finki-chattery/src/app/app-routing.module.ts

    r81e1ed6 r79ae621  
    11import { NgModule } from '@angular/core';
    22import { Routes, RouterModule } from '@angular/router';
     3import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
     4import { AuthorizedGuard } from './core/guards/authorized.guard';
    35
    46const routes: Routes = [
    57  {
    6     path: '**',
    7     redirectTo: 'public/home'
     8    path: 'auth-callback',
     9    component: AuthCallbackComponent
    810  },
    911  {
    10     path: 'questions',
     12    path: 'questioning',
     13    canActivate: [AuthorizedGuard],
    1114    loadChildren: () => import('./modules/questioning/questioning.module').then((x) => x.QuestioningModule)
     15  },
     16  {
     17    path: '**',
     18    redirectTo: 'questioning/preview'
    1219  }
    1320];
  • src/Clients/Angular/finki-chattery/src/app/app.component.html

    r81e1ed6 r79ae621  
    11<main>
    22  <mat-progress-bar class="global-loader" [class.hidden]="!(loader.isLoading | async)" mode="indeterminate"></mat-progress-bar>
     3  <app-header></app-header>
    34  <router-outlet></router-outlet>
    45</main>
  • src/Clients/Angular/finki-chattery/src/app/app.module.ts

    r81e1ed6 r79ae621  
    1010import { CoreModule } from './core/core.module';
    1111import { translateConfiguration, TranslateFromJsonService } from './shared-app/services';
    12 import { SharedMaterialModule } from './shared-material/shared-material.module';
     12import { AuthCallbackComponent } from './auth-callback/auth-callback.component';
    1313
    1414@NgModule({
    15   declarations: [AppComponent],
     15  declarations: [AppComponent, AuthCallbackComponent],
    1616  imports: [
    1717    BrowserModule,
    1818    AppRoutingModule,
    1919    CoreModule,
    20     SharedMaterialModule,
    2120    BrowserAnimationsModule,
    2221    ToastrModule.forRoot(),
  • src/Clients/Angular/finki-chattery/src/app/core/core.module.ts

    r81e1ed6 r79ae621  
    1111import { GUARDS } from './guards/guards';
    1212import { LoaderInterceptor } from './interceptors/loader.interceptor';
    13 import { SERVICES } from './services/services';
    1413import { reducers } from './state';
    1514import { TokenInterceptor } from './interceptors/token.interceptor';
     15import { EffectsModule } from '@ngrx/effects';
     16import { QuestionEffects } from './state/question-state/question.effects';
     17import { CategoriesEffects } from './state/category-state/category.effects';
    1618
    1719@NgModule({
    1820  declarations: [COMPONENTS],
    1921  providers: [
    20     SERVICES,
    2122    GUARDS,
    2223    { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
     
    3334      maxAge: 25,
    3435      logOnly: !environment.production
    35     })
     36    }),
     37    EffectsModule.forRoot([QuestionEffects, CategoriesEffects])
    3638  ],
    37   exports: [HttpClientModule, COMPONENTS]
     39  exports: [HttpClientModule, SharedAppModule, COMPONENTS]
    3840})
    3941export class CoreModule {}
  • src/Clients/Angular/finki-chattery/src/app/core/interceptors/token.interceptor.ts

    r81e1ed6 r79ae621  
    22import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
    33import { Observable } from 'rxjs';
    4 import { switchMap } from 'rxjs/operators';
    54
    65import { AuthService } from '../services';
     
    1615    }
    1716
    18     return this.auth.currentUserToken().pipe(
    19       switchMap((token) => {
    20         const requestToForward = request.clone({
    21           setHeaders: { Authorization: `Bearer ${token}` }
    22         });
    23         return next.handle(requestToForward);
    24       })
    25     );
     17    const requestToForward = request.clone({
     18      setHeaders: { Authorization: `Bearer ${this.auth.currentUserToken()}` }
     19    });
     20
     21    return next.handle(requestToForward);
    2622  }
    2723}
  • src/Clients/Angular/finki-chattery/src/app/core/services/auth.service.ts

    r81e1ed6 r79ae621  
    11import { Injectable } from '@angular/core';
    2 import { UserManager } from 'oidc-client';
    3 import { Observable, from, of } from 'rxjs';
    4 import { map, switchMap } from 'rxjs/operators';
     2import { User, UserManager } from 'oidc-client';
     3import { Observable, of } from 'rxjs';
    54
    65import { environment } from '@env/environment';
     
    1514    authority: environment.identityRoute,
    1615    client_id: environment.identityClientId,
    17     redirect_uri: `${window.location.origin}`,
     16    redirect_uri: `${window.location.origin}/auth-callback`,
    1817    response_type: 'id_token token',
    1918    scope: 'openid app.api.finki-chattery profile',
    2019    post_logout_redirect_uri: window.location.origin
    2120  });
     21
     22  public user: ApplicationUser | null = null;
     23  public oidcUser: User | null = null;
    2224
    2325  constructor(private baseApi: BaseApiService) {}
     
    3133  }
    3234
    33   public isLoggedIn(): Observable<boolean> {
    34     return from(this.userManager.getUser()).pipe(
    35       map((user) => {
    36         if (user) {
    37           return true;
    38         }
    39 
    40         return false;
    41       })
    42     );
     35  public isLoggedIn(): boolean {
     36    if (this.oidcUser) {
     37      return !this.oidcUser.expired;
     38    }
     39    return false;
    4340  }
    4441
    45   public currentUser(): Observable<ApplicationUser | null> {
    46     return from(this.userManager.getUser()).pipe(
    47       map((user) => {
    48         if (!user) {
    49           return null;
    50         }
    51 
    52         return new ApplicationUser(
    53           user.profile.id,
    54           user.profile.userType,
    55           user.profile.emailAddress,
    56           user.profile.username,
    57           user.profile.isVerified
    58         );
    59       })
    60     );
     42  public currentUser(): ApplicationUser | null {
     43    return this.user;
    6144  }
    6245
    63   public currentUserToken(): Observable<string> {
    64     return from(this.userManager.getUser()).pipe(
    65       map((user) => {
    66         if (user?.access_token) {
    67           return user.access_token;
    68         }
     46  public currentUserToken(): string {
     47    if (this.oidcUser) {
     48      return this.oidcUser.access_token;
     49    }
    6950
    70         return '';
    71       })
    72     );
     51    return '';
    7352  }
    7453
    7554  public selfUserDto(): Observable<SelfUserResponse | null> {
    76     return this.isLoggedIn().pipe(
    77       switchMap((loggedIn) => {
    78         if (loggedIn) {
    79           return this.baseApi.getSelfUser();
    80         }
    81         return of(null);
    82       })
    83     );
     55    if (this.isLoggedIn()) {
     56      return this.baseApi.getSelfUser();
     57    }
     58    return of(null);
    8459  }
    8560
    86   public signupCallback(): Observable<boolean> {
    87     return from(this.userManager.signinRedirectCallback()).pipe(map((user) => user !== null));
     61  public async completeAuthentication(): Promise<void> {
     62    return await this.userManager.signinRedirectCallback().then((user: User) => {
     63      this.oidcUser = user;
     64      this.user = new ApplicationUser(
     65        user.profile.id,
     66        user.profile.userType,
     67        user.profile.emailAddress,
     68        user.profile.username,
     69        user.profile.isVerified
     70      );
     71    });
    8872  }
    8973}
  • src/Clients/Angular/finki-chattery/src/app/core/services/notification.service.ts

    r81e1ed6 r79ae621  
    2222
    2323  public successNotification(title: string, description?: string): void {
    24     this.toastr.success(this.translate.instant(description), this.translate.instant(title));
     24    if (description) {
     25      this.toastr.success(this.translate.instant(description), this.translate.instant(title));
     26    }
     27    this.toastr.success(this.translate.instant(title));
    2528  }
    2629}
  • src/Clients/Angular/finki-chattery/src/app/core/services/redirect.service.ts

    r81e1ed6 r79ae621  
    11import { Injectable } from '@angular/core';
    22import { Router } from '@angular/router';
    3 import { switchMap } from 'rxjs/operators';
    43
    54import { ApplicationUserType } from 'src/app/shared-app/models';
     
    1312
    1413  public redirectLoggedInUser(): void {
    15     this.auth
    16       .signupCallback()
    17       .pipe(switchMap(() => this.auth.currentUser()))
    18       .subscribe((currentUser) => {
    19         if (currentUser) {
    20           switch (currentUser.userType) {
    21             case ApplicationUserType.Student:
    22               break;
    23             case ApplicationUserType.Teacher:
    24               break;
    25           }
    26         }
    27       });
     14    const currentUser = this.auth.user;
     15
     16    if (currentUser) {
     17      switch (currentUser.userType) {
     18        case ApplicationUserType.Student:
     19          this.router.navigateByUrl(`questioning/preview`);
     20          break;
     21        case ApplicationUserType.Teacher:
     22          break;
     23      }
     24    }
    2825  }
    2926}
  • src/Clients/Angular/finki-chattery/src/app/core/services/services.ts

    r81e1ed6 r79ae621  
    1 export const SERVICES: any[] = [];
  • src/Clients/Angular/finki-chattery/src/app/core/state/index.ts

    r81e1ed6 r79ae621  
    11import { ActionReducerMap } from '@ngrx/store';
     2import { QuestionState } from './question-state/question.state';
     3import { reducer as questionReducer } from './question-state/question.reducers';
     4import { CategoryState } from './category-state/category.state';
     5import { reducer as categoryReducer } from './category-state/category.reducers';
    26
    3 export interface State {}
     7export interface State {
     8  question: QuestionState;
     9  category: CategoryState;
     10}
    411
    5 export const reducers: ActionReducerMap<State, any> = {};
     12export const reducers: ActionReducerMap<State, any> = {
     13  question: questionReducer,
     14  category: categoryReducer
     15};
  • src/Clients/Angular/finki-chattery/src/app/modules/questioning/components/questioning-components.ts

    r81e1ed6 r79ae621  
     1import { QuestionPreviewGeneralComponent } from './question-preview-general/question-preview-general.component';
     2import { QuestioningGeneralComponent } from './questioning-general/questioning-general.component';
     3import { QuestionsPreviewGeneralComponent } from './questions-preview-general/questions-preview-general.component';
     4import { QuestionsSearchComponent } from './questions-search/questions-search.component';
    15import { AskQuestionComponent } from './ask-question/ask-question.component';
    26
    3 export const QUESTIONING_COMPONENTS: any[] = [AskQuestionComponent];
     7export const QUESTIONING_COMPONENTS: any[] = [
     8  QuestionPreviewGeneralComponent,
     9  QuestionsPreviewGeneralComponent,
     10  QuestioningGeneralComponent,
     11  QuestionsSearchComponent,
     12  AskQuestionComponent
     13];
  • src/Clients/Angular/finki-chattery/src/app/modules/questioning/questioning.module.ts

    r81e1ed6 r79ae621  
    11import { NgModule } from '@angular/core';
    22
     3import { QuestioningRoutingModule } from './questioning.routes';
    34import { QUESTIONING_COMPONENTS } from './components/questioning-components';
    45import { SharedAppModule } from 'src/app/shared-app/shared-app.module';
    5 import { QuestioningRoutingModule } from './questioning.routes';
    6 
    76@NgModule({
    87  declarations: [QUESTIONING_COMPONENTS],
  • src/Clients/Angular/finki-chattery/src/app/modules/questioning/questioning.routes.ts

    r81e1ed6 r79ae621  
    11import { NgModule } from '@angular/core';
    22import { Routes, RouterModule } from '@angular/router';
     3import { QuestionPreviewGeneralComponent } from './components/question-preview-general/question-preview-general.component';
     4import { QuestioningGeneralComponent } from './components/questioning-general/questioning-general.component';
     5import { QuestionsPreviewGeneralComponent } from './components/questions-preview-general/questions-preview-general.component';
     6import { QuestionsSearchComponent } from './components/questions-search/questions-search.component';
    37import { AuthorizedStudentGuard } from 'src/app/core/guards/authorized-student.guard';
    48import { AskQuestionComponent } from './components/ask-question/ask-question.component';
     
    610const routes: Routes = [
    711  {
    8     path: 'ask',
    9     component: AskQuestionComponent,
    10     canActivate: [AuthorizedStudentGuard]
     12    component: QuestioningGeneralComponent,
     13    path: '',
     14    children: [
     15      {
     16        path: 'preview',
     17        pathMatch: 'full',
     18        component: QuestionsPreviewGeneralComponent
     19      },
     20      {
     21        path: 'search',
     22        pathMatch: 'full',
     23        component: QuestionsSearchComponent
     24      },
     25      {
     26        path: 'ask',
     27        pathMatch: 'full',
     28        component: AskQuestionComponent,
     29        canActivate: [AuthorizedStudentGuard]
     30      }
     31      {
     32        path: ':questionUid',
     33        component: QuestionPreviewGeneralComponent
     34      }
     35    ]
    1136  }
    1237];
  • src/Clients/Angular/finki-chattery/src/app/shared-app/directives/directives.ts

    r81e1ed6 r79ae621  
    1 import { HandleInputFormErrorsDirective, HoverElevationDirective, LoaderDirective, HandleSelectFormErrorsDirective } from '.';
     1import {
     2  HandleInputFormErrorsDirective,
     3  HoverElevationDirective,
     4  LoaderDirective,
     5  HandleSelectFormErrorsDirective,
     6  ShareLinkDirective
     7} from '.';
    28
    39export const DIRECTIVES: any[] = [
     
    511  LoaderDirective,
    612  HoverElevationDirective,
    7   HandleSelectFormErrorsDirective
     13  HandleSelectFormErrorsDirective,
     14  ShareLinkDirective
    815];
  • src/Clients/Angular/finki-chattery/src/app/shared-app/directives/index.ts

    r81e1ed6 r79ae621  
    33export * from './hover-elevation.directive';
    44export * from './handle-select-form-errors.directive';
     5export * from './share-link.directive';
  • src/Clients/Angular/finki-chattery/src/app/shared-app/models/index.ts

    r81e1ed6 r79ae621  
    11export * from './error.models';
    22export * from './user.models';
     3export * from './question-state-view-models.models';
     4export * from './category-state-view-models.models';
     5export * from './question-state-enums.models';
    36export * from './categories.models';
  • src/Clients/Angular/finki-chattery/src/app/shared-app/pipes/moment-date.pipe.ts

    r81e1ed6 r79ae621  
    66})
    77export class MomentDatePipe implements PipeTransform {
    8   transform(value: moment.Moment, dateFormat: string): any {
     8  transform(value: moment.Moment | undefined | null, dateFormat: string): any {
    99    return moment(value).format(dateFormat);
    1010  }
  • src/Clients/Angular/finki-chattery/src/app/shared-app/services/translate-from-json.service.ts

    r81e1ed6 r79ae621  
    4343  }
    4444
    45   public instant(key?: string): string | undefined {
    46     if (key) {
    47       return this.translateService.instant(key);
    48     }
    49     return undefined;
     45  public instant(key: string): string {
     46    return this.translateService.instant(key);
    5047  }
    5148}
  • src/Clients/Angular/finki-chattery/src/app/shared-app/shared-app.module.ts

    r81e1ed6 r79ae621  
    99import { EditorModule } from '@tinymce/tinymce-angular';
    1010
    11 import { COMPONENTS } from './components/generic/components';
     11import { COMPONENTS } from './components/components';
    1212import { SharedMaterialModule } from '../shared-material/shared-material.module';
    1313import { DIRECTIVES } from './directives/directives';
    1414import { SERVICES } from './services/services';
    1515import { PIPES } from './pipes/pipes';
    16 import { FileUploadComponent } from './components/generic/file-upload/file-upload.component';
    17 import { HandleSelectFormErrorsDirective } from './directives/handle-select-form-errors.directive';
    1816
    1917@NgModule({
    20   declarations: [COMPONENTS, DIRECTIVES, PIPES, FileUploadComponent, HandleSelectFormErrorsDirective],
     18  declarations: [COMPONENTS, DIRECTIVES, PIPES],
    2119  providers: [SERVICES],
    2220  imports: [
  • src/Clients/Angular/finki-chattery/src/app/shared-material/shared-material.module.ts

    r81e1ed6 r79ae621  
    2020import { MatDatepickerModule } from '@angular/material/datepicker';
    2121import { MatNativeDateModule } from '@angular/material/core';
     22import { MatChipsModule } from '@angular/material/chips';
     23import { MatTooltipModule } from '@angular/material/tooltip';
     24import { MatButtonToggleModule } from '@angular/material/button-toggle';
     25import { MatBadgeModule } from '@angular/material/badge';
     26
    2227@NgModule({
    2328  imports: [
     
    4045    MatTableModule,
    4146    MatDatepickerModule,
    42     MatNativeDateModule
     47    MatNativeDateModule,
     48    MatChipsModule,
     49    MatTooltipModule,
     50    MatButtonToggleModule,
     51    MatBadgeModule
    4352  ],
    4453  exports: [
     
    6069    MatTableModule,
    6170    MatDatepickerModule,
    62     MatNativeDateModule
     71    MatNativeDateModule,
     72    MatChipsModule,
     73    MatTooltipModule,
     74    MatButtonToggleModule,
     75    MatBadgeModule
    6376  ]
    6477})
  • src/Clients/Angular/finki-chattery/src/assets/translations/en.json

    r81e1ed6 r79ae621  
    1414  "password-not-match": "Passwords don't match",
    1515  "code-date-passed": "The code date has passed",
    16   "not-found": "Not found"
     16  "not-found": "Not found",
     17  "question-preview-subtitle": "Asked <b>{{createdOn}}</b>, Last active <b>{{lastActive}}</b>, Viewed <b>{{views}}</b> times",
     18  "share-link": "Share",
     19  "share-link-success": "Successfully copied link for sharing",
     20  "question-asked-by-subtitle": "Asked on: {{date}}",
     21  "question-answers": "{{answerCount}} Answers",
     22  "question-answered-by-subtitle": "Answered on: {{date}}",
     23  "student-reputation": "{{reputation}} reputation",
     24  "vote-correct-answer": "This has been accepted as the correct answer by the owner of the question",
     25  "answer-sort-oldest": "Oldest",
     26  "answer-sort-votes": "Votes",
     27  "internet-techologies": "Internet technologies",
     28  "software-engineering": "Software engineering",
     29  "visual-programming": "Visual programming",
     30  "operating-systems": "Operating systems",
     31  "internet-programming": "Internet programming",
     32  "object-oriented-programming": "Object oriented programming",
     33  "calculus": "Calculus",
     34  "discrete-mathematics": "Discrete mathematics",
     35  "web-programming": "Web programming",
     36  "advanced-programming": "Advanced programming",
     37  "questions-preview-find-question": "Search for a question",
     38  "questions-preview-find-question-categories": "Search in categories",
     39  "questions-search": "Search",
     40  "questions-preview": "Preview questions",
     41  "question-sort-newest": "Newest",
     42  "question-sort-popular": "Most popular",
     43  "questions-preview-question-subtitle": "Asked on: {{date}}",
     44  "questions-preview-question-answers": "Answers",
     45  "questions-preview-question-views": "Views",
     46  "questions-search-title": "Search results for: {{searchQuery}}"
    1747}
  • src/Clients/Angular/finki-chattery/src/styles.scss

    r81e1ed6 r79ae621  
    264264
    265265.avatar-image {
    266   width: 100px;
    267   height: 100px;
     266  width: 80px;
     267  height: 80px;
    268268  display: block;
    269269  border-radius: 50%;
     
    285285  z-index: 1100 !important;
    286286}
     287
     288.full-width {
     289  width: 100%;
     290}
  • src/FinkiChattery/FinkiChattery.Api/Controllers/v1/QuestionsController.cs

    r81e1ed6 r79ae621  
    11using FinkiChattery.Api.ApplicationServices.Authentication;
     2using FinkiChattery.Api.ApplicationServices.Questioning;
    23using FinkiChattery.Commands.Questioning;
    34using FinkiChattery.Common.Mediator.Interfaces;
    45using FinkiChattery.Contracts.Questioning;
     6using FinkiChattery.Queries.Questioning;
    57using IdentityServer4.AccessTokenValidation;
    68using Microsoft.AspNetCore.Authorization;
    79using Microsoft.AspNetCore.Mvc;
     10using System;
    811using System.Threading.Tasks;
    912
     
    2932            return Ok();
    3033        }
     34
     35        [HttpGet("{questionUid:Guid}")]
     36        [Authorize]
     37        public async Task<IActionResult> GetQuestionState([FromRoute] Guid questionUid)
     38        {
     39            var questionDto = await MediatorService.SendQueryAsync(new GetQuestionStateQuery(questionUid));
     40            return Ok(questionDto.ToQuestionStateResponse());
     41        }
     42
     43        [HttpGet("preview")]
     44        [Authorize]
     45        public async Task<IActionResult> PreviewQuestions([FromQuery] GetPreviewQuestionsOrderEnum order)
     46        {
     47            var questions = await MediatorService.SendQueryAsync(new GetPreviewQuestionsQuery(order));
     48            return Ok(questions.ToPreviewQuestionsResponse());
     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        }
    3158    }
    3259}
  • src/FinkiChattery/FinkiChattery.Api/FinkiChattery.Api.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
     
    2627    <ProjectReference Include="..\FinkiChattery.Contracts\FinkiChattery.Contracts.csproj" />
    2728    <ProjectReference Include="..\FinkiChattery.Persistence\FinkiChattery.Persistence.csproj" />
     29    <ProjectReference Include="..\FinkiChattery.Queries\FinkiChattery.Queries.csproj" />
    2830  </ItemGroup>
    2931
  • src/FinkiChattery/FinkiChattery.Api/Services/RegisterServices.cs

    r81e1ed6 r79ae621  
    99using FinkiChattery.Persistence.Models;
    1010using FinkiChattery.Persistence.Repositories;
     11using FinkiChattery.Persistence.UnitOfWork;
     12using FinkiChattery.Queries.Questioning;
    1113using Hangfire;
    1214using Hangfire.SqlServer;
     
    2931            services.AddScoped<IEventService, EventService>();
    3032            services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
    31             services.AddMediatR(typeof(AskQuestionCommand));
     33            services.AddMediatR(typeof(AskQuestionCommand), typeof(GetQuestionStateQuery));
    3234        }
    3335
    3436        public static void AddHangfireService(this IServiceCollection services, IConfiguration configuration)
    3537        {
     38            string connectionString = "HangfireConnection";
     39#if DEBUG_DOCKER
     40            connectionString = "HangfireConnectionDocker";
     41#endif
     42
    3643            services.AddHangfire(x =>
    3744            {
    38                 x.UseSqlServerStorage(configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
     45                x.UseSqlServerStorage(configuration.GetConnectionString(connectionString), new SqlServerStorageOptions
    3946                {
    4047                    CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
     
    100107        }
    101108
    102         public static void AddRepos(this IServiceCollection services)
     109        public static void AddUnitOfWork(this IServiceCollection services)
    103110        {
    104             services.AddScoped<ICategoriesRepo, CategoriesRepo>();
    105             services.AddScoped<ITeamRepo, TeamRepo>();
    106             services.AddScoped<IQuestionRepo, QuestionRepo>();
    107             services.AddScoped<IStudentRepo, StudentRepo>();
     111            services.AddScoped<IUnitOfWork, UnitOfWork>();
    108112        }
    109113
     
    143147                        services.AddScoped<IStorageService, AwsStorageService>();*/
    144148        }
    145 
    146         // TODO: ADD HANGFIRE AND SCAFOLD DB IN HANGFIREDB
    147149    }
    148150
  • src/FinkiChattery/FinkiChattery.Api/Startup.cs

    r81e1ed6 r79ae621  
    3535            services.AddOriginUrlSettings();
    3636            services.AddCurrentUser();
    37             services.AddRepos();
     37            services.AddUnitOfWork();
    3838            services.AddAwsClient(Configuration);
    3939            services.AddHangfireService(Configuration);
    4040
     41            string connectionString = "DefaultConnection";
     42#if DEBUG_DOCKER
     43            connectionString = "DefaultConnectionDocker";
     44#endif
     45
    4146            services.AddDbContext<ApplicationDbContext>(options =>
    42                 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
     47                options.UseSqlServer(Configuration.GetConnectionString(connectionString)));
    4348
    4449            services.AddControllers()
  • src/FinkiChattery/FinkiChattery.Api/appsettings.Development.json

    r81e1ed6 r79ae621  
    2525    },
    2626    "corsSettings": {
    27       "allowedCorsOrigins": [
    28         "http://localhost:4200"
    29       ]
     27      "allowedCorsOrigins": ["http://localhost:4200"]
    3028    }
    3129  },
    3230  "ConnectionStrings": {
    3331    "DefaultConnection": "data source=.;initial catalog=FinkiChattery;integrated security=True;application name=FinkiChattery Base App;Pooling=true;Min Pool Size=5;Max Pool Size=30;",
    34     "HangfireConnection": "data source=.;initial catalog=FinkiChatteryHangfire;integrated security=True;application name=FinkiChattery Hangfire App;Pooling=true;Min Pool Size=5;Max Pool Size=30;"
     32    "HangfireConnection": "data source=.;initial catalog=FinkiChatteryHangfire;integrated security=True;application name=FinkiChattery Hangfire App;Pooling=true;Min Pool Size=5;Max Pool Size=30;",
     33    "DefaultConnectionDocker": "Data Source=localhost;Initial Catalog=FinkiChattery;User ID=sa;Password=Asfa+032;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Pooling=true;Min Pool Size=5;Max Pool Size=30;",
     34    "HangfireConnectionDocker": "Data Source=localhost;Initial Catalog=FinkiChatteryHangfire;User ID=sa;Password=Asfa+032;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Pooling=true;Min Pool Size=5;Max Pool Size=30;"
    3535  }
    3636}
  • src/FinkiChattery/FinkiChattery.Commands/FinkiChattery.Commands.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
  • src/FinkiChattery/FinkiChattery.Commands/Questioning/AskQuestion/AskQuestionCommand.cs

    r81e1ed6 r79ae621  
    11using FinkiChattery.Common.Mediator.Contracs;
    22using FinkiChattery.Common.User;
    3 using FinkiChattery.Persistence.Context;
    43using FinkiChattery.Persistence.Models;
    5 using FinkiChattery.Persistence.Repositories;
     4using FinkiChattery.Persistence.UnitOfWork;
    65using System;
    76using System.Collections.Generic;
     
    2726    public class AskQuestionHandler : ICommandHandler<AskQuestionCommand, Guid>
    2827    {
    29         public AskQuestionHandler(ApplicationDbContext dbContext, ICategoriesRepo categoriesRepo, IStudentRepo studentRepo, ICurrentUser currentUser)
     28        public AskQuestionHandler(IUnitOfWork unitOfWork, ICurrentUser currentUser)
    3029        {
    31             DbContext = dbContext;
    32             CategoriesRepo = categoriesRepo;
    33             StudentRepo = studentRepo;
     30            UnitOfWork = unitOfWork;
    3431            CurrentUser = currentUser;
    3532        }
    3633
    37         public ApplicationDbContext DbContext { get; }
    38         public ICategoriesRepo CategoriesRepo { get; }
    39         public IStudentRepo StudentRepo { get; }
     34        public IUnitOfWork UnitOfWork { get; }
    4035        public ICurrentUser CurrentUser { get; }
    4136
    4237        public async Task<Guid> Handle(AskQuestionCommand request, CancellationToken cancellationToken)
    4338        {
    44             var questionCategories = await CategoriesRepo.GetCategories(request.Categories);
    45             var currentStudent = await StudentRepo.GetStudent(CurrentUser.Id);
     39            var questionCategories = await UnitOfWork.Categories.GetCategories(request.Categories);
     40            var currentStudent = await UnitOfWork.Students.GetStudent(CurrentUser.Id);
    4641
    4742            var questionDatabaseEntity = new Question()
     
    6055            }
    6156
    62             DbContext.Questions.Add(questionDatabaseEntity);
    63             await DbContext.SaveChangesAsync();
     57            UnitOfWork.Questions.Add(questionDatabaseEntity);
     58            await UnitOfWork.SaveAsync();
    6459            return questionDatabaseEntity.Uid;
    6560        }
  • src/FinkiChattery/FinkiChattery.Commands/Questioning/AskQuestion/AskQuestionValidator.cs

    r81e1ed6 r79ae621  
    11using FinkiChattery.Commands.Questioning.Validators;
    2 using FinkiChattery.Persistence.Repositories;
     2using FinkiChattery.Persistence.UnitOfWork;
    33using FluentValidation;
    44
     
    77    public class AskQuestionValidator : AbstractValidator<AskQuestionCommand>
    88    {
    9         public AskQuestionValidator(ICategoriesRepo categoriesRepo)
     9        public AskQuestionValidator(IUnitOfWork unitOfWork)
    1010        {
    1111            RuleFor(x => x.Title).QuestionTitleValidate();
    1212            RuleFor(x => x.Text).QuestionTextValidate();
    13             RuleFor(x => x.Categories).Cascade(CascadeMode.Stop).ListNotNull().SetValidator(new CategoriesUidsExist(categoriesRepo));
     13            RuleFor(x => x.Categories).Cascade(CascadeMode.Stop).ListNotNull().SetValidator(new CategoriesUidsExist(unitOfWork));
    1414        }
    1515    }
  • src/FinkiChattery/FinkiChattery.Commands/Questioning/Validators/CategoriesUidsExist.cs

    r81e1ed6 r79ae621  
    11using FinkiChattery.Persistence.Repositories;
     2using FinkiChattery.Persistence.UnitOfWork;
    23using FluentValidation.Validators;
    34using System;
     
    1011    public class CategoriesUidsExist : AsyncValidatorBase
    1112    {
    12         public CategoriesUidsExist(ICategoriesRepo categoriesRepo)
     13        public CategoriesUidsExist(IUnitOfWork unitOfWork)
    1314        {
    14             CategoriesRepo = categoriesRepo;
     15            UnitOfWork = unitOfWork;
    1516        }
    1617
    17         public ICategoriesRepo CategoriesRepo { get; }
     18        public IUnitOfWork UnitOfWork { get; }
    1819
    1920        protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation)
     
    2122            var categoriesUids = (IEnumerable<Guid>)context.PropertyValue;
    2223
    23             return await CategoriesRepo.CategoriesExist(categoriesUids);
     24            return await UnitOfWork.Categories.CategoriesExist(categoriesUids);
    2425        }
    2526
  • src/FinkiChattery/FinkiChattery.Commands/Questioning/Validators/TeamWithUidExist.cs

    r81e1ed6 r79ae621  
    1 using FinkiChattery.Persistence.Repositories;
     1using FinkiChattery.Persistence.UnitOfWork;
    22using FluentValidation.Validators;
    33using System;
     
    99    public class TeamWithUidExist : AsyncValidatorBase
    1010    {
    11         public TeamWithUidExist(ITeamRepo teamRepo)
     11        public TeamWithUidExist(IUnitOfWork unitOfWork)
    1212        {
    13             TeamRepo = teamRepo;
     13            UnitOfWork = unitOfWork;
    1414        }
    1515
    16         public ITeamRepo TeamRepo { get; }
     16        public IUnitOfWork UnitOfWork { get; }
    1717
    1818        protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation)
    1919        {
    2020            var teamUid = (Guid)context.PropertyValue;
    21             return await TeamRepo.TeamWithUidExists(teamUid);       
     21            return await UnitOfWork.Teams.TeamWithUidExists(teamUid);
    2222        }
    2323
  • src/FinkiChattery/FinkiChattery.Common/FinkiChattery.Common.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
  • src/FinkiChattery/FinkiChattery.Common/Mediator/Interfaces/IMediatorService.cs

    r81e1ed6 r79ae621  
    1111        Task<TResponse> SendAsync<TResponse>(ICommand<TResponse> request);
    1212
     13        Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request, CancellationToken cancellationToken);
     14
     15        Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request);
     16
    1317        Task PublishAsync<TNotification>(TNotification notification) where TNotification : IEvent;
    1418
  • src/FinkiChattery/FinkiChattery.Common/Mediator/MediatorService.cs

    r81e1ed6 r79ae621  
    3737            await mediator.Publish(notification, default);
    3838        }
     39
     40        public async Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request, CancellationToken cancellationToken)
     41        {
     42            return await mediator.Send(request, cancellationToken);
     43        }
     44
     45        public async Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request)
     46        {
     47            return await mediator.Send(request);
     48        }
    3949    }
    4050}
  • src/FinkiChattery/FinkiChattery.Contracts/FinkiChattery.Contracts.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
  • src/FinkiChattery/FinkiChattery.Database/FinkiChattery.Database.sqlproj

    r81e1ed6 r79ae621  
    1 <?xml version="1.0" encoding="utf-8"?>
     1<?xml version="1.0" encoding="utf-8"?>
    22<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
    33  <PropertyGroup>
     
    5555    <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
    5656  </PropertyGroup>
    57   <Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
    58   <Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
     57  <Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
     58  <Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
    5959  <ItemGroup>
    6060    <Folder Include="Properties" />
     
    7070    <Folder Include="dbo\Tables\Student" />
    7171    <Folder Include="FullTextSearch" />
     72    <Folder Include="dbo\Tables\Answer" />
     73    <Folder Include="dbo\Tables\AnswerResponse" />
     74    <Folder Include="dbo\Tables\Question" />
     75    <Folder Include="dbo\Tables\QuestionCategory" />
     76    <Folder Include="Snapshots" />
    7277  </ItemGroup>
    7378  <ItemGroup>
    7479    <Build Include="dbo\Tables\Moderator.sql" />
    7580    <Build Include="dbo\Tables\Teacher.sql" />
    76     <Build Include="dbo\Tables\Question.sql" />
    7781    <Build Include="dbo\Tables\StudentTeam.sql" />
    7882    <Build Include="dbo\Tables\TeacherTeam.sql" />
    79     <Build Include="dbo\Tables\Answer.sql" />
    80     <Build Include="dbo\Tables\QuestionCategory.sql" />
    81     <Build Include="dbo\Tables\AnswerResponse.sql" />
    8283    <Build Include="dbo\Tables\Upvote.sql" />
    8384    <Build Include="dbo\Tables\User\AspNetRoleClaims.sql" />
     
    9192    <Build Include="FullTextSearch\FullTextIndexQuestion.sql" />
    9293    <Build Include="FullTextSearch\QuestionFullTextCatalog.sql" />
     94    <Build Include="dbo\Tables\Question\Question.sql" />
     95    <None Include="dbo\Tables\Question\Question.Debug.Seed.sql" />
     96    <Build Include="dbo\Tables\Answer\Answer.sql" />
     97    <None Include="dbo\Tables\Answer\Answer.Debug.Seed.sql" />
     98    <Build Include="dbo\Tables\AnswerResponse\AnswerResponse.sql" />
     99    <None Include="dbo\Tables\AnswerResponse\AnswerResponse.Debug.Seed.sql" />
     100    <Build Include="dbo\Tables\QuestionCategory\QuestionCategory.sql" />
     101    <None Include="dbo\Tables\QuestionCategory\QuestionCategory.Debug.Seed.sql" />
    93102  </ItemGroup>
    94103  <ItemGroup>
     
    97106    <PreDeploy Include="dbo\Scripts\Script.PreDeployment.sql" />
    98107    <None Include="FinkiChattery.Database.publish.xml" />
     108    <None Include="Snapshots\FinkiChattery.Database_20210922_17-47-58.dacpac" />
    99109  </ItemGroup>
    100110  <ItemGroup>
     
    120130    </SqlCmdVariable>
    121131  </ItemGroup>
     132  <ItemGroup>
     133    <RefactorLog Include="FinkiChattery.Database.refactorlog" />
     134  </ItemGroup>
    122135</Project>
  • src/FinkiChattery/FinkiChattery.Database/FullTextSearch/FullTextIndexQuestion.sql

    r81e1ed6 r79ae621  
    1 CREATE FULLTEXT INDEX ON [dbo].[Question] ([Title], [Text])
     1CREATE FULLTEXT INDEX ON [dbo].[Question] ([Search])
    22KEY INDEX [PK_Question] ON [QuestionFullTextCatalog]
    33WITH (CHANGE_TRACKING AUTO, STOPLIST OFF)
  • src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/PostDeploymentScripts/Debug.PostDeployment.sql

    r81e1ed6 r79ae621  
    1 :r .\..\..\Tables\User\Seed\Users.Debug.Seed.sql
    2 :r .\..\..\Tables\Category\Category.Seed.sql
    3 :r .\..\..\Tables\Student\Student.Debug.Seed.sql
     1:r ./../../Tables/User/Seed/Users.Debug.Seed.sql
     2:r ./../../Tables/Category/Category.Seed.sql
     3:r ./../../Tables/Student/Student.Debug.Seed.sql
     4:r ./../../Tables/Question/Question.Debug.Seed.sql
     5:r ./../../Tables/Answer/Answer.Debug.Seed.sql
     6:r ./../../Tables/AnswerResponse/AnswerResponse.Debug.Seed.sql
     7:r ./../../Tables/QuestionCategory/QuestionCategory.Debug.Seed.sql
  • src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/PostDeploymentScripts/Production.PostDeployment.sql

    r81e1ed6 r79ae621  
    1 :r .\..\..\Tables\Category\Category.Seed.sql
     1:r ./../../Tables/Category/Category.Seed.sql
  • src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/Script.PostDeployment.sql

    r81e1ed6 r79ae621  
    44                PRINT 'Deploying DEBUG scripts';
    55        END
    6         :r .\PostDeploymentScripts\Debug.PostDeployment.sql
     6        :r ./PostDeploymentScripts/Debug.PostDeployment.sql
    77        BEGIN --Run scripts
    88                PRINT 'End deploying DEBUG scripts';
     
    1515                PRINT 'Deploying PRODUCTION scripts'
    1616        END
    17                 :r .\PostDeploymentScripts\Production.PostDeployment.sql
     17                :r ./PostDeploymentScripts/Production.PostDeployment.sql
    1818                BEGIN --Run scripts
    1919                PRINT 'End deploying PRODUCTION scripts'
  • src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/Script.PreDeployment.sql

    r81e1ed6 r79ae621  
    44                PRINT 'Deploying DEBUG pre deployment scripts';
    55        END
    6         :r .\PreDeploymentScripts\Debug.PreDeployment.sql
     6        :r ./PreDeploymentScripts/Debug.PreDeployment.sql
    77        BEGIN --Run scripts
    88                PRINT 'End deploying DEBUG pre deployment scripts';
     
    1515                PRINT 'Deploying PRODUCTION pre deployment scripts'
    1616        END
    17         :r .\PreDeploymentScripts\Production.PreDeployment.sql
     17        :r ./PreDeploymentScripts/Production.PreDeployment.sql
    1818        BEGIN --Run scripts
    1919                PRINT 'End deploying PRODUCTION pre deployment scripts'
  • src/FinkiChattery/FinkiChattery.Database/dbo/Tables/Category/Category.Seed.sql

    r81e1ed6 r79ae621  
    1010                (2, N'7d19a33f-d4a9-4498-8beb-07a5ce75d638',  N'software-engineering'),
    1111                (3, N'8317013c-7eb8-4ca9-83c5-0342895e4061',  N'visual-programming'),
    12                 (4, N'a4493d42-e01f-4092-a700-47a0847c8c4e',  N'operating-systems')
     12                (4, N'11d42b97-a343-499e-b3c6-13fbfd3785f4',  N'internet-programming'),
     13                (5, N'b2e4f54d-664e-4301-9642-9a6e9ef8df84',  N'object-oriented-programming'),
     14                (6, N'77146a56-48b3-4e29-910a-54b7449f8c2b',  N'calculus'),
     15                (7, N'c1a7bc19-34b5-4434-ad6b-f6e0fa90c946',  N'discrete-mathematics'),
     16                (8, N'9f324879-0e63-4d3d-a945-e88d290a23f1',  N'advanced-programming'),
     17                (9, N'6dd8f274-32ba-4b4b-8270-fea799e9b2e2',  N'web-programming'),
     18                (10, N'a4493d42-e01f-4092-a700-47a0847c8c4e',  N'operating-systems')
    1319                )
    1420                AS temp ([ID], [Uid], [Name])
  • src/FinkiChattery/FinkiChattery.HangfireDatabase/FinkiChattery.HangfireDatabase.sqlproj

    r81e1ed6 r79ae621  
    1 <?xml version="1.0" encoding="utf-8"?>
     1<?xml version="1.0" encoding="utf-8"?>
    22<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
    33  <PropertyGroup>
     
    5555    <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion>
    5656  </PropertyGroup>
    57   <Import Condition="'$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
    58   <Import Condition="'$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
     57  <Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' != ''" Project="$(SQLDBExtensionsRefPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
     58  <Import Condition="'$(NetCoreBuild)' != 'true' AND '$(SQLDBExtensionsRefPath)' == ''" Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\SSDT\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
    5959  <ItemGroup>
    6060    <Folder Include="Properties" />
     
    8080    <None Include="FinkiChattery.HangfireDatabase.publish.xml" />
    8181  </ItemGroup>
     82  <Import Condition="'$(NetCoreBuild)' == 'true'" Project="$(NETCoreTargetsPath)\Microsoft.Data.Tools.Schema.SqlTasks.targets" />
     83  <ItemGroup>
     84    <PackageReference Condition="'$(NetCoreBuild)' == 'true'" Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
     85  </ItemGroup>
     86  <Target Name="BeforeBuild">
     87    <Delete Files="$(BaseIntermediateOutputPath)\project.assets.json" />
     88  </Target>
    8289</Project>
  • src/FinkiChattery/FinkiChattery.Identity/FinkiChattery.Identity.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
  • src/FinkiChattery/FinkiChattery.Identity/Properties/launchSettings.json

    r81e1ed6 r79ae621  
    99  },
    1010  "profiles": {
     11    "FinkiChattery.Identity": {
     12      "commandName": "IISExpress",
     13      "environmentVariables": {
     14        "ASPNETCORE_ENVIRONMENT": "Development"
     15      },
     16      "applicationUrl": "https://localhost:44301"
     17    },
    1118    "SelfHost": {
    1219      "commandName": "IISExpress",
  • src/FinkiChattery/FinkiChattery.Identity/Startup.cs

    r81e1ed6 r79ae621  
    2828            services.AddControllersWithViews();
    2929
     30            string connectionString = "DefaultConnection";
     31#if DEBUG_DOCKER
     32            connectionString = "DefaultConnectionDocker";
     33#endif
     34
    3035            services.AddDbContextPool<ApplicationDbContext<ApplicationUser>>(options =>
    31                 options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
     36                options.UseSqlServer(Configuration.GetConnectionString(connectionString)));
    3237
    3338            services.AddIdentityServerAndIdentityProvider(AppSettings);
  • src/FinkiChattery/FinkiChattery.Identity/appsettings.Development.json

    r81e1ed6 r79ae621  
    1010                                {
    1111                                        "allowedCorsOrigins": [ "http://localhost:4200" ],
    12                                         "redirectUris": [ "http://localhost:4200" ],
     12                                        "redirectUris": [ "http://localhost:4200/auth-callback" ],
    1313                                        "postLogoutRedirectUris": [ "http://localhost:4200" ]
    1414                                }
     
    1717        },
    1818        "ConnectionStrings": {
    19                 "DefaultConnection": "data source=.;initial catalog=FinkiChattery;integrated security=True;application name=FinkiChattery Base App;Pooling=true;Min Pool Size=5;Max Pool Size=30;"
     19                "DefaultConnection": "data source=.;initial catalog=FinkiChattery;integrated security=True;application name=FinkiChattery Base App;Pooling=true;Min Pool Size=5;Max Pool Size=30;",
     20                "DefaultConnectionDocker": "Data Source=localhost;Initial Catalog=FinkiChattery;User ID=sa;Password=Asfa+032;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;Pooling=true;Min Pool Size=5;Max Pool Size=30;"
    2021        }
    2122}
  • src/FinkiChattery/FinkiChattery.Persistence/Configurations/AnswerConfig.cs

    r81e1ed6 r79ae621  
    2222            builder.Property(x => x.CorrectAnswer).HasColumnName(@"CorrectAnswer").HasColumnType("bit").IsRequired();
    2323            builder.Property(x => x.CreatedOn).HasColumnName(@"CreatedOn").HasColumnType("smalldatetime").IsRequired();
     24            builder.Property(x => x.UpvotesCount).HasColumnName(@"UpvotesCount").HasColumnType("bigint").IsRequired().HasDefaultValue(0);
    2425
    2526            builder.HasOne(x => x.Question).WithMany(x => x.Answers).HasForeignKey(x => x.QuestionFk).OnDelete(DeleteBehavior.Restrict);
  • src/FinkiChattery/FinkiChattery.Persistence/Configurations/AnswerResponseConfig.cs

    r81e1ed6 r79ae621  
    2828
    2929            builder.HasOne(x => x.Answer).WithMany(x => x.AnswerResponses).HasForeignKey(x => x.AnswerFk).OnDelete(DeleteBehavior.Restrict);
    30             builder.HasOne(x => x.Student).WithMany().HasForeignKey(x => x.AnswerFk).OnDelete(DeleteBehavior.Restrict);
     30            builder.HasOne(x => x.Student).WithMany().HasForeignKey(x => x.StudentFk).OnDelete(DeleteBehavior.Restrict);
    3131        }
    3232    }
  • src/FinkiChattery/FinkiChattery.Persistence/Configurations/QuestionCategoryConfig.cs

    r81e1ed6 r79ae621  
    2626
    2727            builder.HasOne(x => x.Question).WithMany(x => x.QuestionCategories).HasForeignKey(x => x.QuestionFk).OnDelete(DeleteBehavior.Restrict);
    28             builder.HasOne(x => x.Category).WithMany().HasForeignKey(x => x.QuestionFk).OnDelete(DeleteBehavior.Restrict);
     28            builder.HasOne(x => x.Category).WithMany().HasForeignKey(x => x.CategoryFk).OnDelete(DeleteBehavior.Restrict);
    2929        }
    3030    }
  • src/FinkiChattery/FinkiChattery.Persistence/Configurations/QuestionConfig.cs

    r81e1ed6 r79ae621  
    2424            builder.Property(x => x.Views).HasColumnName(@"Views").HasColumnType("bigint").IsRequired().HasDefaultValue(0);
    2525            builder.Property(x => x.LastActiveOn).HasColumnName(@"LastActiveOn").HasColumnType("smalldatetime").IsRequired();
     26            builder.Property(x => x.Search).HasColumnType(@"Search").HasColumnType("nvarchar").HasMaxLength(4000).IsRequired();
     27            builder.Property(x => x.AnswersCount).HasColumnType(@"AnswersCount").HasColumnType("bigint").IsRequired().HasDefaultValue(0);
    2628
    2729            builder.HasOne(x => x.Student).WithMany(x => x.Questions).HasForeignKey(x => x.StudentFk).OnDelete(DeleteBehavior.NoAction);
  • src/FinkiChattery/FinkiChattery.Persistence/FinkiChattery.Persistence.csproj

    r81e1ed6 r79ae621  
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
  • src/FinkiChattery/FinkiChattery.Persistence/Models/Answer.cs

    r81e1ed6 r79ae621  
    2020        public DateTime CreatedOn { get; set; }
    2121
     22        public long UpvotesCount { get; set; }
     23
    2224        public virtual ICollection<Upvote> Upvotes { get; set; }
    2325
  • src/FinkiChattery/FinkiChattery.Persistence/Models/Question.cs

    r81e1ed6 r79ae621  
    3232        public DateTime LastActiveOn { get; set; }
    3333
     34        public string Search { get; set; }
     35
     36        public long AnswersCount { get; set; }
     37
    3438        public virtual ICollection<Answer> Answers { get; set; }
    3539
    3640        public virtual ICollection<QuestionCategory> QuestionCategories { get; set; }
    37 
    38         // TODO: Pole po koe ke pravime queries
    3941    }
    4042}
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Base/Repository.cs

    r81e1ed6 r79ae621  
    88namespace FinkiChattery.Persistence.Repositories
    99{
    10     public abstract class Repository<T> where T : BaseEntity
     10    public abstract class Repository<T> : IRepository<T> where T : BaseEntity
    1111    {
    1212        public Repository(ApplicationDbContext dbContext)
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/ICategoriesRepo.cs

    r81e1ed6 r79ae621  
    88namespace FinkiChattery.Persistence.Repositories
    99{
    10     public interface ICategoriesRepo
     10    public interface ICategoriesRepo : IRepository<Category>
    1111    {
    1212        public Task<bool> CategoriesExist(IEnumerable<Guid> categoriesUids);
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IQuestionRepo.cs

    r81e1ed6 r79ae621  
    1 namespace FinkiChattery.Persistence.Repositories
     1using FinkiChattery.Persistence.Models;
     2using FinkiChattery.Persistence.Repositories.Contracts;
     3using System;
     4using System.Collections.Generic;
     5using System.Threading.Tasks;
     6
     7namespace FinkiChattery.Persistence.Repositories
    28{
    3     public interface IQuestionRepo
     9    public interface IQuestionRepo : IRepository<Question>
    410    {
     11        Task<List<QuestionPreviewDto>> SearchQuestions(string searchText, IEnumerable<Guid> categories);
     12
     13        Task<QuestionStateDto> GetQuestionState(Guid questionUid);
     14
     15        Task<List<QuestionPreviewDto>> GetPreviewQuestionsLatest();
     16     
     17        Task<List<QuestionPreviewDto>> GetPreviewQuestionsPopular();
    518    }
    619}
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IStudentRepo.cs

    r81e1ed6 r79ae621  
    44namespace FinkiChattery.Persistence.Repositories
    55{
    6     public interface IStudentRepo
     6    public interface IStudentRepo : IRepository<Student>
    77    {
    88        public Task<Student> GetStudent(long applicationUserFk);
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/ITeamRepo.cs

    r81e1ed6 r79ae621  
    1 using System;
     1using FinkiChattery.Persistence.Models;
     2using System;
    23using System.Threading.Tasks;
    34
    45namespace FinkiChattery.Persistence.Repositories
    56{
    6     public interface ITeamRepo
     7    public interface ITeamRepo : IRepository<Team>
    78    {
    89        public Task<bool> TeamWithUidExists(Guid teamUid);
  • src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/QuestionRepo.cs

    r81e1ed6 r79ae621  
    11using FinkiChattery.Persistence.Context;
    22using FinkiChattery.Persistence.Models;
     3using FinkiChattery.Persistence.Repositories.Contracts;
     4using Microsoft.Data.SqlClient;
     5using Microsoft.EntityFrameworkCore;
     6using System;
     7using System.Collections.Generic;
     8using System.Linq;
     9using System.Linq.Expressions;
     10using System.Text.RegularExpressions;
     11using System.Threading.Tasks;
    312
    413namespace FinkiChattery.Persistence.Repositories
     
    918        {
    1019        }
     20
     21        public async Task<List<QuestionPreviewDto>> GetPreviewQuestionsLatest()
     22        {
     23            Expression<Func<Question, DateTime>> orderBy = x => x.CreatedOn;
     24            return await GetPreviewQuestions(orderBy);
     25        }
     26
     27        public async Task<List<QuestionPreviewDto>> GetPreviewQuestionsPopular()
     28        {
     29            Expression<Func<Question, long>> orderBy = x => x.Views;
     30            return await GetPreviewQuestions(orderBy);
     31        }
     32
     33        private async Task<List<QuestionPreviewDto>> GetPreviewQuestions<T>(Expression<Func<Question, T>> orderBy)
     34        {
     35            return await DbSet
     36                .AsNoTracking()
     37                .Include(x => x.QuestionCategories).ThenInclude(x => x.Category)
     38                .OrderByDescending(orderBy)
     39                .Select(x => new QuestionPreviewDto(x.Id,
     40                                                    x.Uid,
     41                                                    x.Title,
     42                                                    x.Views,
     43                                                    x.AnswersCount,
     44                                                    x.CreatedOn,
     45                                                    x.QuestionCategories.Select(y => new QuestionPreviewCategoryDto(y.Id, y.Uid, y.Category.Name))))
     46                .Skip(0).Take(30)
     47                .ToListAsync();
     48        }
     49
     50        public async Task<QuestionStateDto> GetQuestionState(Guid questionUid)
     51        {
     52            // TODO: MAYBE WRITE THIS QUERY AS SP ??
     53            var questionDto = await DbSet
     54              .AsNoTracking()
     55              .Include(x => x.Student)
     56              .Include(x => x.Team)
     57              .Include(x => x.Answers).ThenInclude(y => y.Student)
     58              .Include(x => x.Answers).ThenInclude(y => y.AnswerResponses).ThenInclude(y => y.Student)
     59              .Include(x => x.QuestionCategories).ThenInclude(y => y.Category)
     60              .Where(x => x.Uid == questionUid)
     61              .Select(x => new QuestionStateDto(
     62                                                x.Id,
     63                                                x.Uid,
     64                                                x.Title,
     65                                                x.Text,
     66                                                x.CreatedOn,
     67                                                x.Views,
     68                                                x.LastActiveOn,
     69                                                new StudentQuestionStateDto(
     70                                                  x.Student.Id,
     71                                                  x.Student.Uid,
     72                                                  x.Student.IndexNumber,
     73                                                  x.Student.ImageUrl,
     74                                                  x.Student.Reputation),
     75                                                x.Answers.Select(y =>
     76                                                new AnswerQuestionStateDto(
     77                                                    y.Id,
     78                                                    y.Uid,
     79                                                    y.Text,
     80                                                    y.CorrectAnswer,
     81                                                    y.CreatedOn,
     82                                                    y.UpvotesCount,
     83                                                    new AnswerStudentQuestionStateDto(
     84                                                        y.Student.Id,
     85                                                        y.Student.Uid,
     86                                                        y.Student.IndexNumber,
     87                                                        y.Student.ImageUrl,
     88                                                        y.Student.Reputation),
     89                                                    y.AnswerResponses.Select(z =>
     90                                                    new AnswerResponseQuestionStateDto(
     91                                                        z.Id,
     92                                                        z.Uid,
     93                                                        z.Text,
     94                                                        z.CreatedOn,
     95                                                        new AnswerResponseStudentQuestionStateDto(
     96                                                            z.Student.Id,
     97                                                            z.Student.Uid,
     98                                                            z.Student.IndexNumber,
     99                                                            z.Student.ImageUrl,
     100                                                            z.Student.Reputation))))),
     101                                                x.QuestionCategories.Select(y =>
     102                                                new QuestionCategoryQuestionStateDto(
     103                                                    y.Id,
     104                                                    y.Uid,
     105                                                    y.Category.Name)),
     106                                                x.Team == null ? null : new TeamQuestionStateDto(
     107                                                    x.Team.Id,
     108                                                    x.Team.Uid,
     109                                                    x.Team.Name)))
     110              .FirstOrDefaultAsync();
     111
     112            return questionDto;
     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        }
    11146    }
    12147}
  • src/FinkiChattery/FinkiChattery.Queries/FinkiChattery.Queries.csproj

    r81e1ed6 r79ae621  
    1 <Project Sdk="Microsoft.NET.Sdk">
     1<Project Sdk="Microsoft.NET.Sdk">
    22
    33  <PropertyGroup>
    44    <TargetFramework>netcoreapp3.1</TargetFramework>
     5    <Configurations>Debug;Release;Debug_Docker</Configurations>
    56  </PropertyGroup>
    67
     8  <ItemGroup>
     9    <ProjectReference Include="..\FinkiChattery.Common\FinkiChattery.Common.csproj" />
     10    <ProjectReference Include="..\FinkiChattery.Persistence\FinkiChattery.Persistence.csproj" />
     11  </ItemGroup>
     12
    713</Project>
  • src/FinkiChattery/FinkiChattery.sln

    r81e1ed6 r79ae621  
    2222Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "FinkiChattery.HangfireDatabase", "FinkiChattery.HangfireDatabase\FinkiChattery.HangfireDatabase.sqlproj", "{C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}"
    2323EndProject
    24 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinkiChattery.Commands", "FinkiChattery.Commands\FinkiChattery.Commands.csproj", "{7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}"
     24Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinkiChattery.Commands", "FinkiChattery.Commands\FinkiChattery.Commands.csproj", "{7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}"
    2525EndProject
    26 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinkiChattery.Queries", "FinkiChattery.Queries\FinkiChattery.Queries.csproj", "{D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}"
     26Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinkiChattery.Queries", "FinkiChattery.Queries\FinkiChattery.Queries.csproj", "{D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}"
    2727EndProject
    2828Global
    2929        GlobalSection(SolutionConfigurationPlatforms) = preSolution
     30                Debug_Docker|Any CPU = Debug_Docker|Any CPU
    3031                Debug|Any CPU = Debug|Any CPU
    3132                Release|Any CPU = Release|Any CPU
    3233        EndGlobalSection
    3334        GlobalSection(ProjectConfigurationPlatforms) = postSolution
     35                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     36                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    3437                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    3538                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug|Any CPU.Build.0 = Debug|Any CPU
    3639                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Release|Any CPU.ActiveCfg = Release|Any CPU
    3740                {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Release|Any CPU.Build.0 = Release|Any CPU
     41                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     42                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    3843                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    3944                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
    4045                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
    4146                {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Release|Any CPU.Build.0 = Release|Any CPU
     47                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     48                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    4249                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    4350                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug|Any CPU.Build.0 = Debug|Any CPU
    4451                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Release|Any CPU.ActiveCfg = Release|Any CPU
    4552                {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Release|Any CPU.Build.0 = Release|Any CPU
     53                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     54                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    4655                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    4756                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
    4857                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
    4958                {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Release|Any CPU.Build.0 = Release|Any CPU
     59                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     60                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    5061                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    5162                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
    5263                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
    5364                {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Release|Any CPU.Build.0 = Release|Any CPU
     65                {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     66                {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
     67                {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug_Docker|Any CPU.Deploy.0 = Debug_Docker|Any CPU
    5468                {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    5569                {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug|Any CPU.Build.0 = Debug|Any CPU
     
    5872                {5E1219F5-FC7D-4138-811D-26934E946D30}.Release|Any CPU.Build.0 = Release|Any CPU
    5973                {5E1219F5-FC7D-4138-811D-26934E946D30}.Release|Any CPU.Deploy.0 = Release|Any CPU
     74                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     75                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
     76                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug_Docker|Any CPU.Deploy.0 = Debug_Docker|Any CPU
    6077                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    6178                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug|Any CPU.Build.0 = Debug|Any CPU
     
    6481                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Release|Any CPU.Build.0 = Release|Any CPU
    6582                {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Release|Any CPU.Deploy.0 = Release|Any CPU
     83                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     84                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    6685                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    6786                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
    6887                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
    6988                {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Release|Any CPU.Build.0 = Release|Any CPU
     89                {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug_Docker|Any CPU.ActiveCfg = Debug_Docker|Any CPU
     90                {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug_Docker|Any CPU.Build.0 = Debug_Docker|Any CPU
    7091                {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
    7192                {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug|Any CPU.Build.0 = Debug|Any CPU
Note: See TracChangeset for help on using the changeset viewer.