Changes in / [81e1ed6:79ae621]
- Files:
-
- 93 added
- 5 deleted
- 65 edited
Legend:
- Unmodified
- Added
- Removed
-
.gitignore
r81e1ed6 r79ae621 284 284 *.xsd.cs 285 285 .terraform 286 287 # mac os 288 *.DS_Store -
src/Clients/Angular/finki-chattery/angular.json
r81e1ed6 r79ae621 98 98 "tsConfig": "tsconfig.spec.json", 99 99 "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 ], 102 108 "scripts": [] 103 109 } … … 106 112 "builder": "@angular-devkit/build-angular:tslint", 107 113 "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 ] 110 122 } 111 123 }, … … 125 137 } 126 138 }, 127 "defaultProject": "finki-chattery" 139 "defaultProject": "finki-chattery", 140 "cli": { 141 "analytics": false 142 } 128 143 } -
src/Clients/Angular/finki-chattery/src/app/app-routing.module.ts
r81e1ed6 r79ae621 1 1 import { NgModule } from '@angular/core'; 2 2 import { Routes, RouterModule } from '@angular/router'; 3 import { AuthCallbackComponent } from './auth-callback/auth-callback.component'; 4 import { AuthorizedGuard } from './core/guards/authorized.guard'; 3 5 4 6 const routes: Routes = [ 5 7 { 6 path: ' **',7 redirectTo: 'public/home'8 path: 'auth-callback', 9 component: AuthCallbackComponent 8 10 }, 9 11 { 10 path: 'questions', 12 path: 'questioning', 13 canActivate: [AuthorizedGuard], 11 14 loadChildren: () => import('./modules/questioning/questioning.module').then((x) => x.QuestioningModule) 15 }, 16 { 17 path: '**', 18 redirectTo: 'questioning/preview' 12 19 } 13 20 ]; -
src/Clients/Angular/finki-chattery/src/app/app.component.html
r81e1ed6 r79ae621 1 1 <main> 2 2 <mat-progress-bar class="global-loader" [class.hidden]="!(loader.isLoading | async)" mode="indeterminate"></mat-progress-bar> 3 <app-header></app-header> 3 4 <router-outlet></router-outlet> 4 5 </main> -
src/Clients/Angular/finki-chattery/src/app/app.module.ts
r81e1ed6 r79ae621 10 10 import { CoreModule } from './core/core.module'; 11 11 import { translateConfiguration, TranslateFromJsonService } from './shared-app/services'; 12 import { SharedMaterialModule } from './shared-material/shared-material.module';12 import { AuthCallbackComponent } from './auth-callback/auth-callback.component'; 13 13 14 14 @NgModule({ 15 declarations: [AppComponent ],15 declarations: [AppComponent, AuthCallbackComponent], 16 16 imports: [ 17 17 BrowserModule, 18 18 AppRoutingModule, 19 19 CoreModule, 20 SharedMaterialModule,21 20 BrowserAnimationsModule, 22 21 ToastrModule.forRoot(), -
src/Clients/Angular/finki-chattery/src/app/core/core.module.ts
r81e1ed6 r79ae621 11 11 import { GUARDS } from './guards/guards'; 12 12 import { LoaderInterceptor } from './interceptors/loader.interceptor'; 13 import { SERVICES } from './services/services';14 13 import { reducers } from './state'; 15 14 import { TokenInterceptor } from './interceptors/token.interceptor'; 15 import { EffectsModule } from '@ngrx/effects'; 16 import { QuestionEffects } from './state/question-state/question.effects'; 17 import { CategoriesEffects } from './state/category-state/category.effects'; 16 18 17 19 @NgModule({ 18 20 declarations: [COMPONENTS], 19 21 providers: [ 20 SERVICES,21 22 GUARDS, 22 23 { provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true }, … … 33 34 maxAge: 25, 34 35 logOnly: !environment.production 35 }) 36 }), 37 EffectsModule.forRoot([QuestionEffects, CategoriesEffects]) 36 38 ], 37 exports: [HttpClientModule, COMPONENTS]39 exports: [HttpClientModule, SharedAppModule, COMPONENTS] 38 40 }) 39 41 export class CoreModule {} -
src/Clients/Angular/finki-chattery/src/app/core/interceptors/token.interceptor.ts
r81e1ed6 r79ae621 2 2 import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 3 import { Observable } from 'rxjs'; 4 import { switchMap } from 'rxjs/operators';5 4 6 5 import { AuthService } from '../services'; … … 16 15 } 17 16 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); 26 22 } 27 23 } -
src/Clients/Angular/finki-chattery/src/app/core/services/auth.service.ts
r81e1ed6 r79ae621 1 1 import { Injectable } from '@angular/core'; 2 import { UserManager } from 'oidc-client'; 3 import { Observable, from, of } from 'rxjs'; 4 import { map, switchMap } from 'rxjs/operators'; 2 import { User, UserManager } from 'oidc-client'; 3 import { Observable, of } from 'rxjs'; 5 4 6 5 import { environment } from '@env/environment'; … … 15 14 authority: environment.identityRoute, 16 15 client_id: environment.identityClientId, 17 redirect_uri: `${window.location.origin} `,16 redirect_uri: `${window.location.origin}/auth-callback`, 18 17 response_type: 'id_token token', 19 18 scope: 'openid app.api.finki-chattery profile', 20 19 post_logout_redirect_uri: window.location.origin 21 20 }); 21 22 public user: ApplicationUser | null = null; 23 public oidcUser: User | null = null; 22 24 23 25 constructor(private baseApi: BaseApiService) {} … … 31 33 } 32 34 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; 43 40 } 44 41 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; 61 44 } 62 45 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 } 69 50 70 return ''; 71 }) 72 ); 51 return ''; 73 52 } 74 53 75 54 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); 84 59 } 85 60 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 }); 88 72 } 89 73 } -
src/Clients/Angular/finki-chattery/src/app/core/services/notification.service.ts
r81e1ed6 r79ae621 22 22 23 23 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)); 25 28 } 26 29 } -
src/Clients/Angular/finki-chattery/src/app/core/services/redirect.service.ts
r81e1ed6 r79ae621 1 1 import { Injectable } from '@angular/core'; 2 2 import { Router } from '@angular/router'; 3 import { switchMap } from 'rxjs/operators';4 3 5 4 import { ApplicationUserType } from 'src/app/shared-app/models'; … … 13 12 14 13 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 } 28 25 } 29 26 } -
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 1 1 import { ActionReducerMap } from '@ngrx/store'; 2 import { QuestionState } from './question-state/question.state'; 3 import { reducer as questionReducer } from './question-state/question.reducers'; 4 import { CategoryState } from './category-state/category.state'; 5 import { reducer as categoryReducer } from './category-state/category.reducers'; 2 6 3 export interface State {} 7 export interface State { 8 question: QuestionState; 9 category: CategoryState; 10 } 4 11 5 export const reducers: ActionReducerMap<State, any> = {}; 12 export 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 1 import { QuestionPreviewGeneralComponent } from './question-preview-general/question-preview-general.component'; 2 import { QuestioningGeneralComponent } from './questioning-general/questioning-general.component'; 3 import { QuestionsPreviewGeneralComponent } from './questions-preview-general/questions-preview-general.component'; 4 import { QuestionsSearchComponent } from './questions-search/questions-search.component'; 1 5 import { AskQuestionComponent } from './ask-question/ask-question.component'; 2 6 3 export const QUESTIONING_COMPONENTS: any[] = [AskQuestionComponent]; 7 export 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 1 1 import { NgModule } from '@angular/core'; 2 2 3 import { QuestioningRoutingModule } from './questioning.routes'; 3 4 import { QUESTIONING_COMPONENTS } from './components/questioning-components'; 4 5 import { SharedAppModule } from 'src/app/shared-app/shared-app.module'; 5 import { QuestioningRoutingModule } from './questioning.routes';6 7 6 @NgModule({ 8 7 declarations: [QUESTIONING_COMPONENTS], -
src/Clients/Angular/finki-chattery/src/app/modules/questioning/questioning.routes.ts
r81e1ed6 r79ae621 1 1 import { NgModule } from '@angular/core'; 2 2 import { Routes, RouterModule } from '@angular/router'; 3 import { QuestionPreviewGeneralComponent } from './components/question-preview-general/question-preview-general.component'; 4 import { QuestioningGeneralComponent } from './components/questioning-general/questioning-general.component'; 5 import { QuestionsPreviewGeneralComponent } from './components/questions-preview-general/questions-preview-general.component'; 6 import { QuestionsSearchComponent } from './components/questions-search/questions-search.component'; 3 7 import { AuthorizedStudentGuard } from 'src/app/core/guards/authorized-student.guard'; 4 8 import { AskQuestionComponent } from './components/ask-question/ask-question.component'; … … 6 10 const routes: Routes = [ 7 11 { 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 ] 11 36 } 12 37 ]; -
src/Clients/Angular/finki-chattery/src/app/shared-app/directives/directives.ts
r81e1ed6 r79ae621 1 import { HandleInputFormErrorsDirective, HoverElevationDirective, LoaderDirective, HandleSelectFormErrorsDirective } from '.'; 1 import { 2 HandleInputFormErrorsDirective, 3 HoverElevationDirective, 4 LoaderDirective, 5 HandleSelectFormErrorsDirective, 6 ShareLinkDirective 7 } from '.'; 2 8 3 9 export const DIRECTIVES: any[] = [ … … 5 11 LoaderDirective, 6 12 HoverElevationDirective, 7 HandleSelectFormErrorsDirective 13 HandleSelectFormErrorsDirective, 14 ShareLinkDirective 8 15 ]; -
src/Clients/Angular/finki-chattery/src/app/shared-app/directives/index.ts
r81e1ed6 r79ae621 3 3 export * from './hover-elevation.directive'; 4 4 export * from './handle-select-form-errors.directive'; 5 export * from './share-link.directive'; -
src/Clients/Angular/finki-chattery/src/app/shared-app/models/index.ts
r81e1ed6 r79ae621 1 1 export * from './error.models'; 2 2 export * from './user.models'; 3 export * from './question-state-view-models.models'; 4 export * from './category-state-view-models.models'; 5 export * from './question-state-enums.models'; 3 6 export * from './categories.models'; -
src/Clients/Angular/finki-chattery/src/app/shared-app/pipes/moment-date.pipe.ts
r81e1ed6 r79ae621 6 6 }) 7 7 export class MomentDatePipe implements PipeTransform { 8 transform(value: moment.Moment , dateFormat: string): any {8 transform(value: moment.Moment | undefined | null, dateFormat: string): any { 9 9 return moment(value).format(dateFormat); 10 10 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/services/translate-from-json.service.ts
r81e1ed6 r79ae621 43 43 } 44 44 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); 50 47 } 51 48 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/shared-app.module.ts
r81e1ed6 r79ae621 9 9 import { EditorModule } from '@tinymce/tinymce-angular'; 10 10 11 import { COMPONENTS } from './components/ generic/components';11 import { COMPONENTS } from './components/components'; 12 12 import { SharedMaterialModule } from '../shared-material/shared-material.module'; 13 13 import { DIRECTIVES } from './directives/directives'; 14 14 import { SERVICES } from './services/services'; 15 15 import { 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';18 16 19 17 @NgModule({ 20 declarations: [COMPONENTS, DIRECTIVES, PIPES , FileUploadComponent, HandleSelectFormErrorsDirective],18 declarations: [COMPONENTS, DIRECTIVES, PIPES], 21 19 providers: [SERVICES], 22 20 imports: [ -
src/Clients/Angular/finki-chattery/src/app/shared-material/shared-material.module.ts
r81e1ed6 r79ae621 20 20 import { MatDatepickerModule } from '@angular/material/datepicker'; 21 21 import { MatNativeDateModule } from '@angular/material/core'; 22 import { MatChipsModule } from '@angular/material/chips'; 23 import { MatTooltipModule } from '@angular/material/tooltip'; 24 import { MatButtonToggleModule } from '@angular/material/button-toggle'; 25 import { MatBadgeModule } from '@angular/material/badge'; 26 22 27 @NgModule({ 23 28 imports: [ … … 40 45 MatTableModule, 41 46 MatDatepickerModule, 42 MatNativeDateModule 47 MatNativeDateModule, 48 MatChipsModule, 49 MatTooltipModule, 50 MatButtonToggleModule, 51 MatBadgeModule 43 52 ], 44 53 exports: [ … … 60 69 MatTableModule, 61 70 MatDatepickerModule, 62 MatNativeDateModule 71 MatNativeDateModule, 72 MatChipsModule, 73 MatTooltipModule, 74 MatButtonToggleModule, 75 MatBadgeModule 63 76 ] 64 77 }) -
src/Clients/Angular/finki-chattery/src/assets/translations/en.json
r81e1ed6 r79ae621 14 14 "password-not-match": "Passwords don't match", 15 15 "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}}" 17 47 } -
src/Clients/Angular/finki-chattery/src/styles.scss
r81e1ed6 r79ae621 264 264 265 265 .avatar-image { 266 width: 100px;267 height: 100px;266 width: 80px; 267 height: 80px; 268 268 display: block; 269 269 border-radius: 50%; … … 285 285 z-index: 1100 !important; 286 286 } 287 288 .full-width { 289 width: 100%; 290 } -
src/FinkiChattery/FinkiChattery.Api/Controllers/v1/QuestionsController.cs
r81e1ed6 r79ae621 1 1 using FinkiChattery.Api.ApplicationServices.Authentication; 2 using FinkiChattery.Api.ApplicationServices.Questioning; 2 3 using FinkiChattery.Commands.Questioning; 3 4 using FinkiChattery.Common.Mediator.Interfaces; 4 5 using FinkiChattery.Contracts.Questioning; 6 using FinkiChattery.Queries.Questioning; 5 7 using IdentityServer4.AccessTokenValidation; 6 8 using Microsoft.AspNetCore.Authorization; 7 9 using Microsoft.AspNetCore.Mvc; 10 using System; 8 11 using System.Threading.Tasks; 9 12 … … 29 32 return Ok(); 30 33 } 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 } 31 58 } 32 59 } -
src/FinkiChattery/FinkiChattery.Api/FinkiChattery.Api.csproj
r81e1ed6 r79ae621 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 … … 26 27 <ProjectReference Include="..\FinkiChattery.Contracts\FinkiChattery.Contracts.csproj" /> 27 28 <ProjectReference Include="..\FinkiChattery.Persistence\FinkiChattery.Persistence.csproj" /> 29 <ProjectReference Include="..\FinkiChattery.Queries\FinkiChattery.Queries.csproj" /> 28 30 </ItemGroup> 29 31 -
src/FinkiChattery/FinkiChattery.Api/Services/RegisterServices.cs
r81e1ed6 r79ae621 9 9 using FinkiChattery.Persistence.Models; 10 10 using FinkiChattery.Persistence.Repositories; 11 using FinkiChattery.Persistence.UnitOfWork; 12 using FinkiChattery.Queries.Questioning; 11 13 using Hangfire; 12 14 using Hangfire.SqlServer; … … 29 31 services.AddScoped<IEventService, EventService>(); 30 32 services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); 31 services.AddMediatR(typeof(AskQuestionCommand) );33 services.AddMediatR(typeof(AskQuestionCommand), typeof(GetQuestionStateQuery)); 32 34 } 33 35 34 36 public static void AddHangfireService(this IServiceCollection services, IConfiguration configuration) 35 37 { 38 string connectionString = "HangfireConnection"; 39 #if DEBUG_DOCKER 40 connectionString = "HangfireConnectionDocker"; 41 #endif 42 36 43 services.AddHangfire(x => 37 44 { 38 x.UseSqlServerStorage(configuration.GetConnectionString( "HangfireConnection"), new SqlServerStorageOptions45 x.UseSqlServerStorage(configuration.GetConnectionString(connectionString), new SqlServerStorageOptions 39 46 { 40 47 CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), … … 100 107 } 101 108 102 public static void Add Repos(this IServiceCollection services)109 public static void AddUnitOfWork(this IServiceCollection services) 103 110 { 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>(); 108 112 } 109 113 … … 143 147 services.AddScoped<IStorageService, AwsStorageService>();*/ 144 148 } 145 146 // TODO: ADD HANGFIRE AND SCAFOLD DB IN HANGFIREDB147 149 } 148 150 -
src/FinkiChattery/FinkiChattery.Api/Startup.cs
r81e1ed6 r79ae621 35 35 services.AddOriginUrlSettings(); 36 36 services.AddCurrentUser(); 37 services.Add Repos();37 services.AddUnitOfWork(); 38 38 services.AddAwsClient(Configuration); 39 39 services.AddHangfireService(Configuration); 40 40 41 string connectionString = "DefaultConnection"; 42 #if DEBUG_DOCKER 43 connectionString = "DefaultConnectionDocker"; 44 #endif 45 41 46 services.AddDbContext<ApplicationDbContext>(options => 42 options.UseSqlServer(Configuration.GetConnectionString( "DefaultConnection")));47 options.UseSqlServer(Configuration.GetConnectionString(connectionString))); 43 48 44 49 services.AddControllers() -
src/FinkiChattery/FinkiChattery.Api/appsettings.Development.json
r81e1ed6 r79ae621 25 25 }, 26 26 "corsSettings": { 27 "allowedCorsOrigins": [ 28 "http://localhost:4200" 29 ] 27 "allowedCorsOrigins": ["http://localhost:4200"] 30 28 } 31 29 }, 32 30 "ConnectionStrings": { 33 31 "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;" 35 35 } 36 36 } -
src/FinkiChattery/FinkiChattery.Commands/FinkiChattery.Commands.csproj
r81e1ed6 r79ae621 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 -
src/FinkiChattery/FinkiChattery.Commands/Questioning/AskQuestion/AskQuestionCommand.cs
r81e1ed6 r79ae621 1 1 using FinkiChattery.Common.Mediator.Contracs; 2 2 using FinkiChattery.Common.User; 3 using FinkiChattery.Persistence.Context;4 3 using FinkiChattery.Persistence.Models; 5 using FinkiChattery.Persistence. Repositories;4 using FinkiChattery.Persistence.UnitOfWork; 6 5 using System; 7 6 using System.Collections.Generic; … … 27 26 public class AskQuestionHandler : ICommandHandler<AskQuestionCommand, Guid> 28 27 { 29 public AskQuestionHandler( ApplicationDbContext dbContext, ICategoriesRepo categoriesRepo, IStudentRepo studentRepo, ICurrentUser currentUser)28 public AskQuestionHandler(IUnitOfWork unitOfWork, ICurrentUser currentUser) 30 29 { 31 DbContext = dbContext; 32 CategoriesRepo = categoriesRepo; 33 StudentRepo = studentRepo; 30 UnitOfWork = unitOfWork; 34 31 CurrentUser = currentUser; 35 32 } 36 33 37 public ApplicationDbContext DbContext { get; } 38 public ICategoriesRepo CategoriesRepo { get; } 39 public IStudentRepo StudentRepo { get; } 34 public IUnitOfWork UnitOfWork { get; } 40 35 public ICurrentUser CurrentUser { get; } 41 36 42 37 public async Task<Guid> Handle(AskQuestionCommand request, CancellationToken cancellationToken) 43 38 { 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); 46 41 47 42 var questionDatabaseEntity = new Question() … … 60 55 } 61 56 62 DbContext.Questions.Add(questionDatabaseEntity);63 await DbContext.SaveChangesAsync();57 UnitOfWork.Questions.Add(questionDatabaseEntity); 58 await UnitOfWork.SaveAsync(); 64 59 return questionDatabaseEntity.Uid; 65 60 } -
src/FinkiChattery/FinkiChattery.Commands/Questioning/AskQuestion/AskQuestionValidator.cs
r81e1ed6 r79ae621 1 1 using FinkiChattery.Commands.Questioning.Validators; 2 using FinkiChattery.Persistence. Repositories;2 using FinkiChattery.Persistence.UnitOfWork; 3 3 using FluentValidation; 4 4 … … 7 7 public class AskQuestionValidator : AbstractValidator<AskQuestionCommand> 8 8 { 9 public AskQuestionValidator(I CategoriesRepo categoriesRepo)9 public AskQuestionValidator(IUnitOfWork unitOfWork) 10 10 { 11 11 RuleFor(x => x.Title).QuestionTitleValidate(); 12 12 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)); 14 14 } 15 15 } -
src/FinkiChattery/FinkiChattery.Commands/Questioning/Validators/CategoriesUidsExist.cs
r81e1ed6 r79ae621 1 1 using FinkiChattery.Persistence.Repositories; 2 using FinkiChattery.Persistence.UnitOfWork; 2 3 using FluentValidation.Validators; 3 4 using System; … … 10 11 public class CategoriesUidsExist : AsyncValidatorBase 11 12 { 12 public CategoriesUidsExist(I CategoriesRepo categoriesRepo)13 public CategoriesUidsExist(IUnitOfWork unitOfWork) 13 14 { 14 CategoriesRepo = categoriesRepo;15 UnitOfWork = unitOfWork; 15 16 } 16 17 17 public I CategoriesRepo CategoriesRepo{ get; }18 public IUnitOfWork UnitOfWork { get; } 18 19 19 20 protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation) … … 21 22 var categoriesUids = (IEnumerable<Guid>)context.PropertyValue; 22 23 23 return await CategoriesRepo.CategoriesExist(categoriesUids);24 return await UnitOfWork.Categories.CategoriesExist(categoriesUids); 24 25 } 25 26 -
src/FinkiChattery/FinkiChattery.Commands/Questioning/Validators/TeamWithUidExist.cs
r81e1ed6 r79ae621 1 using FinkiChattery.Persistence. Repositories;1 using FinkiChattery.Persistence.UnitOfWork; 2 2 using FluentValidation.Validators; 3 3 using System; … … 9 9 public class TeamWithUidExist : AsyncValidatorBase 10 10 { 11 public TeamWithUidExist(I TeamRepo teamRepo)11 public TeamWithUidExist(IUnitOfWork unitOfWork) 12 12 { 13 TeamRepo = teamRepo;13 UnitOfWork = unitOfWork; 14 14 } 15 15 16 public I TeamRepo TeamRepo{ get; }16 public IUnitOfWork UnitOfWork { get; } 17 17 18 18 protected override async Task<bool> IsValidAsync(PropertyValidatorContext context, CancellationToken cancellation) 19 19 { 20 20 var teamUid = (Guid)context.PropertyValue; 21 return await TeamRepo.TeamWithUidExists(teamUid);21 return await UnitOfWork.Teams.TeamWithUidExists(teamUid); 22 22 } 23 23 -
src/FinkiChattery/FinkiChattery.Common/FinkiChattery.Common.csproj
r81e1ed6 r79ae621 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 -
src/FinkiChattery/FinkiChattery.Common/Mediator/Interfaces/IMediatorService.cs
r81e1ed6 r79ae621 11 11 Task<TResponse> SendAsync<TResponse>(ICommand<TResponse> request); 12 12 13 Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request, CancellationToken cancellationToken); 14 15 Task<TResponse> SendQueryAsync<TResponse>(IQuery<TResponse> request); 16 13 17 Task PublishAsync<TNotification>(TNotification notification) where TNotification : IEvent; 14 18 -
src/FinkiChattery/FinkiChattery.Common/Mediator/MediatorService.cs
r81e1ed6 r79ae621 37 37 await mediator.Publish(notification, default); 38 38 } 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 } 39 49 } 40 50 } -
src/FinkiChattery/FinkiChattery.Contracts/FinkiChattery.Contracts.csproj
r81e1ed6 r79ae621 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 -
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"?> 2 2 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> 3 3 <PropertyGroup> … … 55 55 <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion> 56 56 </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" /> 59 59 <ItemGroup> 60 60 <Folder Include="Properties" /> … … 70 70 <Folder Include="dbo\Tables\Student" /> 71 71 <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" /> 72 77 </ItemGroup> 73 78 <ItemGroup> 74 79 <Build Include="dbo\Tables\Moderator.sql" /> 75 80 <Build Include="dbo\Tables\Teacher.sql" /> 76 <Build Include="dbo\Tables\Question.sql" />77 81 <Build Include="dbo\Tables\StudentTeam.sql" /> 78 82 <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" />82 83 <Build Include="dbo\Tables\Upvote.sql" /> 83 84 <Build Include="dbo\Tables\User\AspNetRoleClaims.sql" /> … … 91 92 <Build Include="FullTextSearch\FullTextIndexQuestion.sql" /> 92 93 <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" /> 93 102 </ItemGroup> 94 103 <ItemGroup> … … 97 106 <PreDeploy Include="dbo\Scripts\Script.PreDeployment.sql" /> 98 107 <None Include="FinkiChattery.Database.publish.xml" /> 108 <None Include="Snapshots\FinkiChattery.Database_20210922_17-47-58.dacpac" /> 99 109 </ItemGroup> 100 110 <ItemGroup> … … 120 130 </SqlCmdVariable> 121 131 </ItemGroup> 132 <ItemGroup> 133 <RefactorLog Include="FinkiChattery.Database.refactorlog" /> 134 </ItemGroup> 122 135 </Project> -
src/FinkiChattery/FinkiChattery.Database/FullTextSearch/FullTextIndexQuestion.sql
r81e1ed6 r79ae621 1 CREATE FULLTEXT INDEX ON [dbo].[Question] ([ Title], [Text])1 CREATE FULLTEXT INDEX ON [dbo].[Question] ([Search]) 2 2 KEY INDEX [PK_Question] ON [QuestionFullTextCatalog] 3 3 WITH (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.sql1 :r ./../../Tables/Category/Category.Seed.sql -
src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/Script.PostDeployment.sql
r81e1ed6 r79ae621 4 4 PRINT 'Deploying DEBUG scripts'; 5 5 END 6 :r . \PostDeploymentScripts\Debug.PostDeployment.sql6 :r ./PostDeploymentScripts/Debug.PostDeployment.sql 7 7 BEGIN --Run scripts 8 8 PRINT 'End deploying DEBUG scripts'; … … 15 15 PRINT 'Deploying PRODUCTION scripts' 16 16 END 17 :r . \PostDeploymentScripts\Production.PostDeployment.sql17 :r ./PostDeploymentScripts/Production.PostDeployment.sql 18 18 BEGIN --Run scripts 19 19 PRINT 'End deploying PRODUCTION scripts' -
src/FinkiChattery/FinkiChattery.Database/dbo/Scripts/Script.PreDeployment.sql
r81e1ed6 r79ae621 4 4 PRINT 'Deploying DEBUG pre deployment scripts'; 5 5 END 6 :r . \PreDeploymentScripts\Debug.PreDeployment.sql6 :r ./PreDeploymentScripts/Debug.PreDeployment.sql 7 7 BEGIN --Run scripts 8 8 PRINT 'End deploying DEBUG pre deployment scripts'; … … 15 15 PRINT 'Deploying PRODUCTION pre deployment scripts' 16 16 END 17 :r . \PreDeploymentScripts\Production.PreDeployment.sql17 :r ./PreDeploymentScripts/Production.PreDeployment.sql 18 18 BEGIN --Run scripts 19 19 PRINT 'End deploying PRODUCTION pre deployment scripts' -
src/FinkiChattery/FinkiChattery.Database/dbo/Tables/Category/Category.Seed.sql
r81e1ed6 r79ae621 10 10 (2, N'7d19a33f-d4a9-4498-8beb-07a5ce75d638', N'software-engineering'), 11 11 (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') 13 19 ) 14 20 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"?> 2 2 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0"> 3 3 <PropertyGroup> … … 55 55 <VisualStudioVersion Condition="'$(SSDTExists)' == ''">11.0</VisualStudioVersion> 56 56 </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" /> 59 59 <ItemGroup> 60 60 <Folder Include="Properties" /> … … 80 80 <None Include="FinkiChattery.HangfireDatabase.publish.xml" /> 81 81 </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> 82 89 </Project> -
src/FinkiChattery/FinkiChattery.Identity/FinkiChattery.Identity.csproj
r81e1ed6 r79ae621 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 -
src/FinkiChattery/FinkiChattery.Identity/Properties/launchSettings.json
r81e1ed6 r79ae621 9 9 }, 10 10 "profiles": { 11 "FinkiChattery.Identity": { 12 "commandName": "IISExpress", 13 "environmentVariables": { 14 "ASPNETCORE_ENVIRONMENT": "Development" 15 }, 16 "applicationUrl": "https://localhost:44301" 17 }, 11 18 "SelfHost": { 12 19 "commandName": "IISExpress", -
src/FinkiChattery/FinkiChattery.Identity/Startup.cs
r81e1ed6 r79ae621 28 28 services.AddControllersWithViews(); 29 29 30 string connectionString = "DefaultConnection"; 31 #if DEBUG_DOCKER 32 connectionString = "DefaultConnectionDocker"; 33 #endif 34 30 35 services.AddDbContextPool<ApplicationDbContext<ApplicationUser>>(options => 31 options.UseSqlServer(Configuration.GetConnectionString( "DefaultConnection")));36 options.UseSqlServer(Configuration.GetConnectionString(connectionString))); 32 37 33 38 services.AddIdentityServerAndIdentityProvider(AppSettings); -
src/FinkiChattery/FinkiChattery.Identity/appsettings.Development.json
r81e1ed6 r79ae621 10 10 { 11 11 "allowedCorsOrigins": [ "http://localhost:4200" ], 12 "redirectUris": [ "http://localhost:4200 " ],12 "redirectUris": [ "http://localhost:4200/auth-callback" ], 13 13 "postLogoutRedirectUris": [ "http://localhost:4200" ] 14 14 } … … 17 17 }, 18 18 "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;" 20 21 } 21 22 } -
src/FinkiChattery/FinkiChattery.Persistence/Configurations/AnswerConfig.cs
r81e1ed6 r79ae621 22 22 builder.Property(x => x.CorrectAnswer).HasColumnName(@"CorrectAnswer").HasColumnType("bit").IsRequired(); 23 23 builder.Property(x => x.CreatedOn).HasColumnName(@"CreatedOn").HasColumnType("smalldatetime").IsRequired(); 24 builder.Property(x => x.UpvotesCount).HasColumnName(@"UpvotesCount").HasColumnType("bigint").IsRequired().HasDefaultValue(0); 24 25 25 26 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 28 28 29 29 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); 31 31 } 32 32 } -
src/FinkiChattery/FinkiChattery.Persistence/Configurations/QuestionCategoryConfig.cs
r81e1ed6 r79ae621 26 26 27 27 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); 29 29 } 30 30 } -
src/FinkiChattery/FinkiChattery.Persistence/Configurations/QuestionConfig.cs
r81e1ed6 r79ae621 24 24 builder.Property(x => x.Views).HasColumnName(@"Views").HasColumnType("bigint").IsRequired().HasDefaultValue(0); 25 25 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); 26 28 27 29 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 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 -
src/FinkiChattery/FinkiChattery.Persistence/Models/Answer.cs
r81e1ed6 r79ae621 20 20 public DateTime CreatedOn { get; set; } 21 21 22 public long UpvotesCount { get; set; } 23 22 24 public virtual ICollection<Upvote> Upvotes { get; set; } 23 25 -
src/FinkiChattery/FinkiChattery.Persistence/Models/Question.cs
r81e1ed6 r79ae621 32 32 public DateTime LastActiveOn { get; set; } 33 33 34 public string Search { get; set; } 35 36 public long AnswersCount { get; set; } 37 34 38 public virtual ICollection<Answer> Answers { get; set; } 35 39 36 40 public virtual ICollection<QuestionCategory> QuestionCategories { get; set; } 37 38 // TODO: Pole po koe ke pravime queries39 41 } 40 42 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Base/Repository.cs
r81e1ed6 r79ae621 8 8 namespace FinkiChattery.Persistence.Repositories 9 9 { 10 public abstract class Repository<T> where T : BaseEntity10 public abstract class Repository<T> : IRepository<T> where T : BaseEntity 11 11 { 12 12 public Repository(ApplicationDbContext dbContext) -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/ICategoriesRepo.cs
r81e1ed6 r79ae621 8 8 namespace FinkiChattery.Persistence.Repositories 9 9 { 10 public interface ICategoriesRepo 10 public interface ICategoriesRepo : IRepository<Category> 11 11 { 12 12 public Task<bool> CategoriesExist(IEnumerable<Guid> categoriesUids); -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IQuestionRepo.cs
r81e1ed6 r79ae621 1 namespace FinkiChattery.Persistence.Repositories 1 using FinkiChattery.Persistence.Models; 2 using FinkiChattery.Persistence.Repositories.Contracts; 3 using System; 4 using System.Collections.Generic; 5 using System.Threading.Tasks; 6 7 namespace FinkiChattery.Persistence.Repositories 2 8 { 3 public interface IQuestionRepo 9 public interface IQuestionRepo : IRepository<Question> 4 10 { 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(); 5 18 } 6 19 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IStudentRepo.cs
r81e1ed6 r79ae621 4 4 namespace FinkiChattery.Persistence.Repositories 5 5 { 6 public interface IStudentRepo 6 public interface IStudentRepo : IRepository<Student> 7 7 { 8 8 public Task<Student> GetStudent(long applicationUserFk); -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/ITeamRepo.cs
r81e1ed6 r79ae621 1 using System; 1 using FinkiChattery.Persistence.Models; 2 using System; 2 3 using System.Threading.Tasks; 3 4 4 5 namespace FinkiChattery.Persistence.Repositories 5 6 { 6 public interface ITeamRepo 7 public interface ITeamRepo : IRepository<Team> 7 8 { 8 9 public Task<bool> TeamWithUidExists(Guid teamUid); -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/QuestionRepo.cs
r81e1ed6 r79ae621 1 1 using FinkiChattery.Persistence.Context; 2 2 using FinkiChattery.Persistence.Models; 3 using FinkiChattery.Persistence.Repositories.Contracts; 4 using Microsoft.Data.SqlClient; 5 using Microsoft.EntityFrameworkCore; 6 using System; 7 using System.Collections.Generic; 8 using System.Linq; 9 using System.Linq.Expressions; 10 using System.Text.RegularExpressions; 11 using System.Threading.Tasks; 3 12 4 13 namespace FinkiChattery.Persistence.Repositories … … 9 18 { 10 19 } 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 } 11 146 } 12 147 } -
src/FinkiChattery/FinkiChattery.Queries/FinkiChattery.Queries.csproj
r81e1ed6 r79ae621 1 <Project Sdk="Microsoft.NET.Sdk">1 <Project Sdk="Microsoft.NET.Sdk"> 2 2 3 3 <PropertyGroup> 4 4 <TargetFramework>netcoreapp3.1</TargetFramework> 5 <Configurations>Debug;Release;Debug_Docker</Configurations> 5 6 </PropertyGroup> 6 7 8 <ItemGroup> 9 <ProjectReference Include="..\FinkiChattery.Common\FinkiChattery.Common.csproj" /> 10 <ProjectReference Include="..\FinkiChattery.Persistence\FinkiChattery.Persistence.csproj" /> 11 </ItemGroup> 12 7 13 </Project> -
src/FinkiChattery/FinkiChattery.sln
r81e1ed6 r79ae621 22 22 Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "FinkiChattery.HangfireDatabase", "FinkiChattery.HangfireDatabase\FinkiChattery.HangfireDatabase.sqlproj", "{C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}" 23 23 EndProject 24 Project("{ FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinkiChattery.Commands", "FinkiChattery.Commands\FinkiChattery.Commands.csproj", "{7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}"24 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinkiChattery.Commands", "FinkiChattery.Commands\FinkiChattery.Commands.csproj", "{7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}" 25 25 EndProject 26 Project("{ FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinkiChattery.Queries", "FinkiChattery.Queries\FinkiChattery.Queries.csproj", "{D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}"26 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FinkiChattery.Queries", "FinkiChattery.Queries\FinkiChattery.Queries.csproj", "{D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}" 27 27 EndProject 28 28 Global 29 29 GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 Debug_Docker|Any CPU = Debug_Docker|Any CPU 30 31 Debug|Any CPU = Debug|Any CPU 31 32 Release|Any CPU = Release|Any CPU 32 33 EndGlobalSection 33 34 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 34 37 {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 38 {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 39 {8CD7796E-5DC1-413D-8D04-3C95E89BB214}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 40 {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 38 43 {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 44 {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 45 {D2EADD50-983E-48EE-8B36-7CF088DCF8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 46 {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 42 49 {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 50 {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 51 {1BA385FA-32FC-4237-85D2-705EEBCAFD06}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 52 {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 46 55 {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 56 {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 57 {4D2F08BF-44F0-427D-B5B8-079233500FA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 58 {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 50 61 {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 62 {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 63 {E69CDEE7-B2F5-42F4-81F0-11DCDC1C8CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 64 {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 54 68 {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 69 {5E1219F5-FC7D-4138-811D-26934E946D30}.Debug|Any CPU.Build.0 = Debug|Any CPU … … 58 72 {5E1219F5-FC7D-4138-811D-26934E946D30}.Release|Any CPU.Build.0 = Release|Any CPU 59 73 {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 60 77 {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 78 {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Debug|Any CPU.Build.0 = Debug|Any CPU … … 64 81 {C4CB8596-3F3A-4B4E-BEC5-0720FF7CD532}.Release|Any CPU.Build.0 = Release|Any CPU 65 82 {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 66 85 {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 86 {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 87 {7AD9C86F-46FF-442A-B134-3CE2E82CE0D0}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 88 {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 70 91 {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 92 {D19820A3-E6E6-4B1C-927A-C8B8B4B16D25}.Debug|Any CPU.Build.0 = Debug|Any CPU
Note:
See TracChangeset
for help on using the changeset viewer.