Changeset 2a9d9d1
- Timestamp:
- 11/03/21 16:42:35 (3 years ago)
- Branches:
- dev
- Children:
- b9d7ae5
- Parents:
- 80e2fe0
- Location:
- src
- Files:
-
- 23 added
- 24 edited
Legend:
- Unmodified
- Added
- Removed
-
src/Clients/Angular/finki-chattery/src/app/core/services/auth.service.ts
r80e2fe0 r2a9d9d1 24 24 public user: ApplicationUser | null = null; 25 25 public oidcUser: User | null = null; 26 public selfUser: SelfUserResponse | null = null; 26 27 27 28 constructor(private baseApi: BaseApiService) { … … 35 36 user?.profile.isVerified 36 37 ); 38 39 this.selfUserDto().subscribe((selfUser) => { 40 if (selfUser) { 41 this.selfUser = selfUser; 42 } 43 }); 37 44 }); 38 45 } … … 86 93 user.profile.isVerified 87 94 ); 95 96 if (!this.selfUser) { 97 this.selfUserDto().subscribe((selfUser) => { 98 if (selfUser) { 99 this.selfUser = selfUser; 100 } 101 }); 102 } 88 103 }); 89 104 } -
src/Clients/Angular/finki-chattery/src/app/core/state/question-facade.service.ts
r80e2fe0 r2a9d9d1 3 3 import { Action, Store } from '@ngrx/store'; 4 4 import { Observable, Subject } from 'rxjs'; 5 import { filter } from 'rxjs/operators';5 import { filter, map } from 'rxjs/operators'; 6 6 7 7 import { … … 12 12 VoteType 13 13 } from 'src/app/shared-app/models'; 14 import { AuthService } from '../services'; 14 15 import { 15 16 EffectStartedWorking, … … 18 19 GetQuestionState, 19 20 GetSearchQuestions, 21 SetCorrectAnswer, 20 22 VoteAnswer 21 23 } from './question-state/question.actions'; … … 31 33 effectWorking$: Observable<boolean | HttpErrorResponse>; 32 34 33 constructor(private store: Store<QuestionState> ) {35 constructor(private store: Store<QuestionState>, private auth: AuthService) { 34 36 this.effectWorking$ = this.store.select(questionStateQuery.effectWorking).pipe(filter((effect) => effect !== null)); 37 } 38 39 public currentQuestionOwnedByCurrentUser(): Observable<boolean> { 40 return this.getQuestion().pipe(map((question) => this.auth.selfUser?.student?.uid === question.student.uid)); 35 41 } 36 42 … … 71 77 } 72 78 79 public setCorrectAnswer(questionUid: string, answerUid: string): void { 80 this.dispatchEffect(new SetCorrectAnswer(questionUid, answerUid)); 81 } 82 73 83 public voteAnswer(answerUid: string, questionUid: string, voteType: VoteType): void { 74 84 this.dispatchEffect(new VoteAnswer(questionUid, answerUid, voteType)); -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.actions.ts
r80e2fe0 r2a9d9d1 13 13 GetQuestionState = '[Question] Get state', 14 14 GetQuestionStateSuccess = '[Question] Get state success', 15 SetCorrectAnswer = '[Question] Set Correct Answer', 16 SetCorrectAnswerSuccess = '[Question] Set Correct Answer success', 15 17 GetPreviewQuestionsLatest = '[Question] Get preview questions Latest', 16 18 GetPreviewQuestionsLatestSuccess = '[Question] Get preview questions Latest Success', … … 36 38 37 39 constructor(public payload: QuestionStateViewModel) {} 40 } 41 42 export class SetCorrectAnswer implements Action { 43 readonly type = QuestionActionTypes.SetCorrectAnswer; 44 45 constructor(public questionUid: string, public answerUid: string) {} 46 } 47 48 export class SetCorrectAnswerSuccess implements Action { 49 readonly type = QuestionActionTypes.SetCorrectAnswerSuccess; 50 51 constructor(public payload: string) {} 38 52 } 39 53 … … 110 124 | GetSearchQuestionsSuccess 111 125 | VoteAnswerSuccess 126 | SetCorrectAnswerSuccess 112 127 | EffectStartedWorking 113 128 | EffectFinishedWorking -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.effects.ts
r80e2fe0 r2a9d9d1 1 1 import { Injectable } from '@angular/core'; 2 2 import { act, Actions, createEffect, ofType } from '@ngrx/effects'; 3 import { catchError, filter, mergeMap, switchMap, withLatestFrom } from 'rxjs/operators';3 import { catchError, filter, mergeMap, switchMap, tap, withLatestFrom } from 'rxjs/operators'; 4 4 import { PreviewQuestionsOrderEnum, SearchQuestionsQueryViewModel } from 'src/app/shared-app/models'; 5 5 import { TranslateFromJsonService } from 'src/app/shared-app/services'; 6 6 7 7 import { BaseApiService } from 'src/app/shared-app/services/base-api.service'; 8 import { NotificationService } from '../../services/notification.service'; 8 9 import { QuestionFacadeService } from '../question-facade.service'; 9 10 import { VoteAnswerRequest } from './question-state-request.models'; … … 21 22 GetSearchQuestionsSuccess, 22 23 QuestionActionTypes, 24 SetCorrectAnswer, 25 SetCorrectAnswerSuccess, 23 26 VoteAnswer, 24 27 VoteAnswerSuccess … … 34 37 private api: BaseApiService, 35 38 private translate: TranslateFromJsonService, 36 private facade: QuestionFacadeService 39 private facade: QuestionFacadeService, 40 private notification: NotificationService 37 41 ) {} 38 42 … … 113 117 const body = new VoteAnswerRequest(action.voteType); 114 118 return this.api.post<VoteAnswerResponse>(`v1/questions/${action.questionUid}/answers/${action.answerUid}/votes`, body).pipe( 119 tap((state) => this.notification.successNotification('sucess-vote')), 115 120 switchMap((state) => [new VoteAnswerSuccess(QuestionMapper.ToVoteAnswerViewModel(state)), new EffectFinishedWorking()]), 116 121 catchError((err) => [new EffectFinishedWorkingError(err)]) … … 119 124 ); 120 125 }); 126 127 setCorrectAnswer$ = createEffect(() => { 128 return this.actions$.pipe( 129 ofType<SetCorrectAnswer>(QuestionActionTypes.SetCorrectAnswer), 130 mergeMap((action) => { 131 return this.api.put<string>(`v1/questions/${action.questionUid}/answers/${action.answerUid}/correct`).pipe( 132 tap((state) => this.notification.successNotification('success-correct-answer')), 133 switchMap((state) => [new SetCorrectAnswerSuccess(state), new EffectFinishedWorking()]), 134 catchError((err) => [new EffectFinishedWorkingError(err)]) 135 ); 136 }) 137 ); 138 }); 121 139 } -
src/Clients/Angular/finki-chattery/src/app/core/state/question-state/question.reducers.ts
r80e2fe0 r2a9d9d1 1 import { VoteType } from 'src/app/shared-app/models';1 import { AnswerQuestionStateViewModel, VoteType } from 'src/app/shared-app/models'; 2 2 import { QuestionAction, QuestionActionTypes } from './question.actions'; 3 3 import { initialState, QuestionState } from './question.state'; … … 61 61 }; 62 62 } 63 case QuestionActionTypes.SetCorrectAnswerSuccess: { 64 if (state.question) { 65 return { 66 ...state, 67 question: { 68 ...state.question, 69 answers: state.question.answers.map((x) => { 70 if (x.correctAnswer) { 71 return { 72 ...x, 73 correctAnswer: false 74 }; 75 } 76 if (x.uid === action.payload) { 77 return { 78 ...x, 79 correctAnswer: true 80 }; 81 } 82 return x; 83 }) 84 } 85 }; 86 } 87 88 return { 89 ...state 90 }; 91 } 63 92 case QuestionActionTypes.EffectStartedWorking: { 64 93 return { -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/generic/vote/vote.component.html
r80e2fe0 r2a9d9d1 1 <div >1 <div class="main-div"> 2 2 <div class="vote-icons" fxLayout="column" fxLayoutAlign="center center"> 3 3 <mat-icon class="vote-icon" (click)="voted(VoteType.Upvote)" [inline]="true">arrow_drop_up</mat-icon> … … 7 7 >check</mat-icon 8 8 > 9 <mat-icon 10 *ngIf="canSetCorrectAnswer && !correct" 11 [inline]="true" 12 class="text-center text-bold green set-correct-answer" 13 (click)="setCorrectAnswer.emit()" 14 >check</mat-icon 15 > 9 16 </div> 10 17 </div> -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/generic/vote/vote.component.scss
r80e2fe0 r2a9d9d1 14 14 cursor: pointer; 15 15 } 16 17 .show-on-hover { 18 display: none; 19 } 20 21 .set-correct-answer { 22 opacity: 0.3; 23 visibility: hidden; 24 } 25 26 .main-div:hover > .vote-icons > .set-correct-answer { 27 visibility: visible; 28 } 29 30 .main-div { 31 height: 100%; 32 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/generic/vote/vote.component.ts
r80e2fe0 r2a9d9d1 10 10 export class VoteComponent implements OnInit { 11 11 @Input() voteCount: number | undefined; 12 @Input() canSetCorrectAnswer: boolean | null = false; 12 13 @Input() correct = false; 13 14 @Output() voteClicked = new EventEmitter<VoteType>(); 15 @Output() setCorrectAnswer = new EventEmitter<void>(); 14 16 15 17 VoteType = VoteType; -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/question/question-preview/question-preview.component.html
r80e2fe0 r2a9d9d1 15 15 <div fxLayout="row wrap" fxLayoutAlign="space-around none"> 16 16 <app-vote 17 [canSetCorrectAnswer]="canSetCorrectAnswer | async" 17 18 [voteCount]="answer.votesCount" 18 19 [correct]="answer.correctAnswer" 19 20 (voteClicked)="answerVoted($event, answer.uid, question.uid)" 21 (setCorrectAnswer)="setCorrectAnswer(question.uid, answer.uid)" 20 22 fxFlex="6%" 21 23 ></app-vote> -
src/Clients/Angular/finki-chattery/src/app/shared-app/components/question/question-preview/question-preview.component.ts
r80e2fe0 r2a9d9d1 13 13 }) 14 14 export class QuestionPreviewComponent implements OnInit { 15 canSetCorrectAnswer = this.questionFacade.currentQuestionOwnedByCurrentUser(); 15 16 question!: QuestionStateViewModel; 16 17 working = true; … … 34 35 this.questionFacade.voteAnswer(answerUid, questionUid, voteType); 35 36 } 37 38 setCorrectAnswer(questionUid: string, answerUid: string): void { 39 this.questionFacade.setCorrectAnswer(questionUid, answerUid); 40 } 36 41 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/models/user.models.ts
r80e2fe0 r2a9d9d1 9 9 } 10 10 11 export class SelfUserResponse {12 uid!: string;13 }14 15 11 export enum ApplicationUserType { 16 12 Student = 'Student', … … 19 15 Guest = 'Guest' 20 16 } 17 18 export class SelfUserResponse { 19 public student?: StudentSelfResponse | null; 20 public teacher?: TeacherSelfResponse | null; 21 public moderator?: ModeratorSelfResponse | null; 22 } 23 24 export class StudentSelfResponse { 25 public uid!: string; 26 public applicationUserId!: number; 27 public index!: string; 28 public reputation!: number; 29 public imageUrl!: string; 30 public questions!: StudentQuestionResponse[]; 31 public teams!: StudentTeamResponse[]; 32 } 33 34 export class StudentQuestionResponse { 35 public questionUid!: string; 36 public title!: string; 37 } 38 39 export class StudentTeamResponse { 40 public teamUid!: string; 41 public name!: string; 42 } 43 44 export class ModeratorSelfResponse { 45 public uid!: string; 46 public applicationUserId!: number; 47 } 48 49 export class TeacherSelfResponse { 50 public uid!: string; 51 public applicationUserId!: number; 52 public teams!: TeacherTeamResponse[]; 53 } 54 55 export class TeacherTeamResponse { 56 public teamUid!: string; 57 public name!: string; 58 } -
src/Clients/Angular/finki-chattery/src/app/shared-app/services/base-api.service.ts
r80e2fe0 r2a9d9d1 19 19 20 20 public getSelfUser(): Observable<SelfUserResponse> { 21 return this.get<SelfUserResponse>(' self');21 return this.get<SelfUserResponse>('v1/self'); 22 22 } 23 23 -
src/Clients/Angular/finki-chattery/src/assets/translations/en.json
r80e2fe0 r2a9d9d1 53 53 "ask-question-stepper-ask": "Ask question", 54 54 "ask-question-ask-button-back": "Edit question", 55 "sucess-vote": "Successfully voted answer", 56 "success-correct-answer": "Successfully set correct answer", 55 57 "ask-question-ask-button": "Ask question", 56 58 "AnswerAlreadyUpvoted": "You have already upvoted this answer", -
src/FinkiChattery/FinkiChattery.Api/Controllers/v1/AnswersController.cs
r80e2fe0 r2a9d9d1 30 30 return Ok(answerUid); 31 31 } 32 33 [HttpPut("{answerUid:Guid}/correct")] 34 [Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme, Policy = AuthenticationPolicy.Student)] 35 public async Task<IActionResult> MarkAnswerCorrect([FromRoute] Guid questionUid, [FromRoute] Guid answerUid) 36 { 37 await MediatorService.SendAsync(new MarkAnswerCorrectCommand(questionUid, answerUid)); 38 return Ok(answerUid); 39 } 32 40 } 33 41 } -
src/FinkiChattery/FinkiChattery.Commands/Questioning/QuestioningErrorCodes.cs
r80e2fe0 r2a9d9d1 13 13 public const string StudentHasBadReputation = "StudentHasBadReputation"; 14 14 public const string AnswerTextLengthInvalid = "AnswerTextLengthInvalid"; 15 public const string QuestionNotFound = "QuestionNotFound"; 15 public const string QuestionNotFound = "QuestionNotFound"; 16 public const string AnswerInQuestionNotFound = "AnswerInQuestionNotFound"; 17 public const string StudentDoesNotOwnQuestion = "StudentDoesNotOwnQuestion"; 18 public const string AnswerIsAlreadyMarkedAsCorrect = "AnswerIsAlreadyMarkedAsCorrect"; 16 19 } 17 20 } -
src/FinkiChattery/FinkiChattery.Persistence/Models/Moderator.cs
r80e2fe0 r2a9d9d1 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace FinkiChattery.Persistence.Models 1 namespace FinkiChattery.Persistence.Models 8 2 { 9 3 public class Moderator : BaseEntity -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IAnswerRepo.cs
r80e2fe0 r2a9d9d1 1 1 using FinkiChattery.Persistence.Models; 2 using System; 3 using System.Threading.Tasks; 2 4 3 5 namespace FinkiChattery.Persistence.Repositories … … 5 7 public interface IAnswerRepo : IRepository<Answer> 6 8 { 9 Task<bool> AnswerInQuestionExists(Guid questionUid, Guid answerUid); 7 10 } 8 11 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IQuestionRepo.cs
r80e2fe0 r2a9d9d1 16 16 17 17 Task<List<QuestionPreviewDto>> GetPreviewQuestionsPopular(); 18 19 Task<bool> QuestionIsOwnedByStudent(Guid questionUid, long applicationUserId); 20 21 Task<Question> GetQuestionWithAnswersAndStudents(Guid questionUid); 18 22 } 19 23 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Contracts/IStudentRepo.cs
r80e2fe0 r2a9d9d1 1 1 using FinkiChattery.Persistence.Models; 2 using FinkiChattery.Persistence.Repositories.Contracts; 2 3 using System.Threading.Tasks; 3 4 … … 7 8 { 8 9 public Task<Student> GetStudent(long applicationUserFk); 10 public Task<StudentSelfDto> GetStudentSelfDto(long applicationUserFk); 9 11 } 10 12 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/AnswerRepo.cs
r80e2fe0 r2a9d9d1 1 1 using FinkiChattery.Persistence.Context; 2 2 using FinkiChattery.Persistence.Models; 3 using Microsoft.EntityFrameworkCore; 4 using System; 5 using System.Linq; 6 using System.Threading.Tasks; 3 7 4 8 namespace FinkiChattery.Persistence.Repositories … … 9 13 { 10 14 } 15 16 public async Task<bool> AnswerInQuestionExists(Guid questionUid, Guid answerUid) 17 { 18 return await DbSet 19 .AsNoTracking() 20 .Where(x => x.Question.Uid == questionUid && x.Uid == answerUid) 21 .AnyAsync(); 22 } 11 23 } 12 24 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/QuestionRepo.cs
r80e2fe0 r2a9d9d1 144 144 .ToListAsync(); 145 145 } 146 147 public async Task<bool> QuestionIsOwnedByStudent(Guid questionUid, long applicationUserId) 148 { 149 return await DbSet 150 .Where(x => x.Uid == questionUid && x.Student.ApplicationUserFk == applicationUserId) 151 .AnyAsync(); 152 } 153 154 public async Task<Question> GetQuestionWithAnswersAndStudents(Guid questionUid) 155 { 156 return await DbSet 157 .Include(x => x.Answers).ThenInclude(x => x.Student) 158 .FirstOrDefaultAsync(x => x.Uid == questionUid); 159 } 146 160 } 147 161 } -
src/FinkiChattery/FinkiChattery.Persistence/Repositories/Implementations/StudentRepo.cs
r80e2fe0 r2a9d9d1 1 1 using FinkiChattery.Persistence.Context; 2 2 using FinkiChattery.Persistence.Models; 3 using FinkiChattery.Persistence.Repositories.Contracts; 3 4 using Microsoft.EntityFrameworkCore; 5 using System.Linq; 4 6 using System.Threading.Tasks; 5 7 … … 16 18 return await DbSet.FirstOrDefaultAsync(x => x.ApplicationUserFk == applicationUserFk); 17 19 } 20 21 public async Task<StudentSelfDto> GetStudentSelfDto(long applicationUserFk) 22 { 23 return await DbSet 24 .AsNoTracking() 25 .Include(x => x.Questions) 26 .Include(x => x.StudentTeams).ThenInclude(x => x.Team) 27 .Where(x => x.ApplicationUserFk == applicationUserFk) 28 .Select(x => new StudentSelfDto(x.Uid, 29 x.ApplicationUserFk, 30 x.IndexNumber, 31 x.Reputation, 32 x.ImageUrl, 33 x.Questions.Select(y => new StudentQuestionDto(y.Uid, y.Title)), 34 x.StudentTeams.Select(y => new StudentTeamDto(y.Team.Uid, y.Team.Name)))) 35 .FirstOrDefaultAsync(); 36 } 18 37 } 19 38 } -
src/FinkiChattery/FinkiChattery.Persistence/UnitOfWork/Contracts/IUnitOfWork.cs
r80e2fe0 r2a9d9d1 13 13 IVoteRepo Votes { get; } 14 14 IAnswerRepo Answers { get; } 15 ITeacherRepo Teachers { get; } 16 IModeratorRepo Moderators { get; } 15 17 Task<int> SaveAsync(); 16 18 } -
src/FinkiChattery/FinkiChattery.Persistence/UnitOfWork/Implementations/UnitOfWork.cs
r80e2fe0 r2a9d9d1 14 14 private VoteRepo _votes; 15 15 private AnswerRepo _answers; 16 private ModeratorRepo _moderators; 17 private TeacherRepo _teachers; 16 18 17 19 public UnitOfWork(ApplicationDbContext dbContext) 18 20 { 19 21 DbContext = dbContext; 22 } 23 24 public IModeratorRepo Moderators 25 { 26 get 27 { 28 if (_moderators == null) 29 { 30 _moderators = new ModeratorRepo(DbContext); 31 } 32 33 return _moderators; 34 } 35 } 36 37 public ITeacherRepo Teachers 38 { 39 get 40 { 41 if (_teachers == null) 42 { 43 _teachers = new TeacherRepo(DbContext); 44 } 45 46 return _teachers; 47 } 20 48 } 21 49
Note:
See TracChangeset
for help on using the changeset viewer.