예제 #1
0
        /* TODO (andgein): move it to GroupController */
        public async Task <bool> CanRevokeAccessAsync(int groupId, string userId, string revokedById)
        {
            var group = await groupsRepo.FindGroupByIdAsync(groupId).ConfigureAwait(false) ?? throw new ArgumentNullException($"Can't find group with id={groupId}");

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(revokedById, @group.CourseId, CourseRoleType.CourseAdmin).ConfigureAwait(false);

            if (group.OwnerId == revokedById || isCourseAdmin)
            {
                return(true);
            }
            return(db.GroupAccesses.Any(a => a.GroupId == groupId && a.UserId == userId && a.GrantedById == revokedById && a.IsEnabled));
        }
예제 #2
0
        public async Task <ActionResult <TempCourseUpdateResponse> > CreateCourse([FromRoute] string courseId)
        {
            var tmpCourseId = GetTmpCourseId(courseId, UserId);

            if (!DontCheckBaseCourseExistsOnCreate && !await courseManager.HasCourseAsync(courseId))
            {
                return(new TempCourseUpdateResponse
                {
                    ErrorType = ErrorType.NotFound,
                    Message = $"Не существует курса {courseId}"
                });
            }

            if (!await courseRolesRepo.HasUserAccessToCourseAsync(UserId, courseId, CourseRoleType.CourseAdmin))
            {
                return(new TempCourseUpdateResponse
                {
                    ErrorType = ErrorType.Forbidden,
                    Message = $"Необходимо быть администратором курса {courseId}"
                });
            }

            var tmpCourse = await tempCoursesRepo.FindAsync(tmpCourseId);

            if (tmpCourse != null)
            {
                return(new TempCourseUpdateResponse
                {
                    ErrorType = ErrorType.Conflict,
                    Message = $"Ваша временная версия курса {courseId} уже существует с id {tmpCourseId}."
                });
            }

            var versionId   = Guid.NewGuid();
            var courseTitle = "Заготовка временного курса";

            if (!courseManager.TryCreateTempCourse(tmpCourseId, courseTitle, versionId))
            {
                throw new Exception();
            }

            var result = await tempCoursesRepo.AddTempCourseAsync(tmpCourseId, UserId);

            var loadingTime = result.LoadingTime;
            await courseRolesRepo.ToggleRoleAsync(tmpCourseId, UserId, CourseRoleType.CourseAdmin, UserId, "Создал временный курс");

            return(new TempCourseUpdateResponse
            {
                Message = $"Временный курс с id {tmpCourseId} успешно создан.",
                LastUploadTime = loadingTime
            });
        }
예제 #3
0
        public async Task <Comment> AddCommentAsync(string authorId, string courseId, Guid slideId, int parentCommentId, bool isForInstructorsOnly, string commentText)
        {
            var commentsPolicy = await commentPoliciesRepo.GetCommentsPolicyAsync(courseId).ConfigureAwait(false);

            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(authorId, courseId, CourseRoleType.Instructor).ConfigureAwait(false);

            var isApproved = commentsPolicy.ModerationPolicy == CommentModerationPolicy.Postmoderation || isInstructor;

            /* Instructors' replies are automatically correct */
            var isReply         = parentCommentId != -1;
            var isCorrectAnswer = isReply && isInstructor && !isForInstructorsOnly;

            var comment = new Comment
            {
                AuthorId             = authorId,
                CourseId             = courseId,
                SlideId              = slideId,
                ParentCommentId      = parentCommentId,
                Text                 = commentText,
                IsApproved           = isApproved,
                IsCorrectAnswer      = isCorrectAnswer,
                IsForInstructorsOnly = isForInstructorsOnly,
                PublishTime          = DateTime.Now
            };

            db.Comments.Add(comment);
            await db.SaveChangesAsync().ConfigureAwait(false);

            return(await FindCommentByIdAsync(comment.Id).ConfigureAwait(false));
        }
예제 #4
0
        public async Task <ActionResult <ApiSlideInfo> > SlideInfo([FromRoute] Course course, [FromRoute] Guid slideId)
        {
            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(User.GetUserId(), course.Id, CourseRoleType.Instructor).ConfigureAwait(false);

            var slide = course?.FindSlideById(slideId, isInstructor);

            if (slide == null)
            {
                var instructorNote = course?.FindInstructorNoteById(slideId);
                if (instructorNote != null && isInstructor)
                {
                    slide = instructorNote.Slide;
                }
            }

            if (slide == null)
            {
                return(NotFound(new { status = "error", message = "Course or slide not found" }));
            }

            var userId = User.GetUserId();
            var getSlideMaxScoreFunc = await BuildGetSlideMaxScoreFunc(solutionsRepo, userQuizzesRepo, visitsRepo, groupsRepo, course, userId);

            var getGitEditLinkFunc = await BuildGetGitEditLinkFunc(userId, course, courseRolesRepo, coursesRepo);

            var baseUrl = CourseUnitUtils.GetDirectoryRelativeWebPath(slide.Info.SlideFile);

            var slideRenderContext = new SlideRenderContext(course.Id, slide, UserId, baseUrl, !isInstructor,
                                                            course.Settings.VideoAnnotationsGoogleDoc, Url);

            return(await slideRenderer.BuildSlideInfo(slideRenderContext, getSlideMaxScoreFunc, getGitEditLinkFunc));
        }
예제 #5
0
        public async Task <ActionResult <RunSolutionResponse> > RunSolution(
            [FromRoute] Course course,
            [FromRoute] Guid slideId,
            [FromBody] RunSolutionParameters parameters,
            [FromQuery] Language language)
        {
            var courseId = course.Id;

            /* Check that no checking solution by this user in last time */
            var delta         = TimeSpan.FromSeconds(30);
            var halfMinuteAgo = DateTime.Now.Subtract(delta);

            if (await userSolutionsRepo.IsCheckingSubmissionByUser(courseId, slideId, User.Identity.GetUserId(), halfMinuteAgo, DateTime.MaxValue))
            {
                return(Json(new RunSolutionResponse(SolutionRunStatus.Ignored)
                {
                    Message = $"Ваше решение по этой задаче уже проверяется. Дождитесь окончания проверки. Вы можете отправить новое решение через {delta.Seconds} секунд."
                }));
            }

            var code = parameters.Solution;

            if (code.Length > TextsRepo.MaxTextSize)
            {
                return(Json(new RunSolutionResponse(SolutionRunStatus.Ignored)
                {
                    Message = "Слишком длинный код"
                }));
            }

            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, courseId, CourseRoleType.Instructor);

            var exerciseSlide = (await courseManager.FindCourseAsync(courseId))?.FindSlideById(slideId, isInstructor) as ExerciseSlide;

            if (exerciseSlide == null)
            {
                return(NotFound(new ErrorResponse("Slide not found")));
            }

            var result = await CheckSolution(
                courseId, exerciseSlide, code, language, UserId, User.Identity.Name,
                waitUntilChecked : true, saveSubmissionOnCompileErrors : false
                ).ConfigureAwait(false);

            return(result);
        }
예제 #6
0
        public async Task <List <Guid> > GetVisibleUnitIdsAsync(Course course, string userId)
        {
            var canSeeEverything = await courseRolesRepo.HasUserAccessToCourseAsync(userId, course.Id, CourseRoleType.Tester);

            if (canSeeEverything)
            {
                return(course.GetUnitsNotSafe().Select(u => u.Id).ToList());
            }

            return(await GetPublishedUnitIdsAsync(course));
        }
예제 #7
0
        protected async Task <bool> CanUserSeeNotApprovedCommentsAsync(string userId, string courseId)
        {
            if (string.IsNullOrEmpty(userId))
            {
                return(false);
            }

            var hasCourseAccessForCommentEditing = await coursesRepo.HasCourseAccessAsync(userId, courseId, CourseAccessType.EditPinAndRemoveComments).ConfigureAwait(false);

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(userId, courseId, CourseRoleType.CourseAdmin).ConfigureAwait(false);

            return(isCourseAdmin || hasCourseAccessForCommentEditing);
        }
예제 #8
0
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CourseAccessRequirement requirement)
        {
            /* Get MVC context. See https://docs.microsoft.com/en-US/aspnet/core/security/authorization/policies#accessing-mvc-request-context-in-handlers */
            if (!(context.Resource is AuthorizationFilterContext mvcContext))
            {
                logger.Error("Can't get MVC context in CourseRoleAuthenticationHandler");
                context.Fail();
                return;
            }

            var routeData = mvcContext.RouteData;

            if (!(routeData.Values["courseId"] is string courseId))
            {
                logger.Error("Can't find `courseId` parameter in route data for checking course access requirement.");
                context.Fail();
                return;
            }

            if (!context.User.Identity.IsAuthenticated)
            {
                context.Fail();
                return;
            }

            var userId = context.User.GetUserId();
            var user   = await usersRepo.FindUserByIdAsync(userId).ConfigureAwait(false);

            if (user == null)
            {
                context.Fail();
                return;
            }

            if (usersRepo.IsSystemAdministrator(user))
            {
                context.Succeed(requirement);
                return;
            }

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(userId, courseId, CourseRoleType.CourseAdmin).ConfigureAwait(false);

            if (isCourseAdmin || await coursesRepo.HasCourseAccessAsync(userId, courseId, requirement.CourseAccessType).ConfigureAwait(false))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
예제 #9
0
        public async Task <IActionResult> ChangeOwner(int groupId, [FromBody] ChangeOwnerParameters parameters)
        {
            var group = await groupsRepo.FindGroupByIdAsync(groupId).ConfigureAwait(false);

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, group.CourseId, CourseRoleType.CourseAdmin).ConfigureAwait(false);

            var canChangeOwner = group.OwnerId == UserId || isCourseAdmin;

            if (!canChangeOwner)
            {
                return(StatusCode((int)HttpStatusCode.Forbidden, new ErrorResponse("You can't change the owner of this group. Only current owner and course admin can change the owner.")));
            }

            /* New owner should exist and be a course instructor */
            var user = await usersRepo.FindUserByIdAsync(parameters.OwnerId).ConfigureAwait(false);

            if (user == null)
            {
                return(NotFound(new ErrorResponse($"Can't find user with id {parameters.OwnerId}")));
            }
            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(parameters.OwnerId, group.CourseId, CourseRoleType.Instructor).ConfigureAwait(false);

            if (!isInstructor)
            {
                return(NotFound(new ErrorResponse($"User {parameters.OwnerId} is not an instructor of course {group.CourseId}")));
            }

            /* Grant full access to previous owner */
            await groupAccessesRepo.GrantAccessAsync(groupId, group.OwnerId, GroupAccessType.FullAccess, UserId).ConfigureAwait(false);

            /* Change owner */
            await groupsRepo.ChangeGroupOwnerAsync(groupId, parameters.OwnerId).ConfigureAwait(false);

            /* Revoke access from new owner */
            await groupAccessesRepo.RevokeAccessAsync(groupId, parameters.OwnerId).ConfigureAwait(false);

            return(Ok(new SuccessResponseWithMessage($"New group's owner is {parameters.OwnerId}")));
        }
예제 #10
0
        public async Task <ActionResult <ReviewCommentResponse> > AddExerciseCodeReviewComment([FromRoute] int reviewId, [FromBody] ReviewCreateCommentParameters parameters)
        {
            var review = await slideCheckingsRepo.FindExerciseCodeReviewById(reviewId);

            var submissionUserId   = review.ExerciseCheckingId.HasValue ? review.ExerciseChecking.UserId : review.Submission.UserId;
            var submissionCourseId = review.ExerciseCheckingId.HasValue ? review.ExerciseChecking.CourseId : review.Submission.CourseId;
            var isInstructor       = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, submissionCourseId, CourseRoleType.Instructor);

            if (submissionUserId != UserId && !isInstructor)
            {
                return(StatusCode((int)HttpStatusCode.Forbidden, new ErrorResponse("You can't comment this review")));
            }

            var canReply = isInstructor || !review.Author.IsUlearnBot() || review.NotDeletedComments.Any(c => !c.Author.IsUlearnBot());

            if (!canReply)
            {
                return(StatusCode((int)HttpStatusCode.Forbidden, new ErrorResponse("You can't reply this review")));
            }

            var comment = await slideCheckingsRepo.AddExerciseCodeReviewComment(UserId, reviewId, parameters.Text);

            if (review.ExerciseCheckingId.HasValue && review.ExerciseChecking.IsChecked)
            {
                var course = await courseManager.FindCourseAsync(submissionCourseId);

                var slideId = review.ExerciseChecking.SlideId;
                var unit    = course?.FindUnitBySlideId(slideId, isInstructor);
                if (unit != null && await unitsRepo.IsUnitVisibleForStudents(course, unit.Id))
                {
                    await NotifyAboutCodeReviewComment(comment);
                }
            }

            return(ReviewCommentResponse.Build(comment));
        }
예제 #11
0
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, CourseRoleRequirement requirement)
        {
            /* Get MVC context. See https://docs.microsoft.com/en-US/aspnet/core/security/authorization/policies#accessing-mvc-request-context-in-handlers */
            if (!(context.Resource is AuthorizationFilterContext mvcContext))
            {
                log.Error("Can't get MVC context in CourseRoleAuthenticationHandler");
                context.Fail();
                return;
            }

            var courseId = GetCourseIdFromRequestAsync(mvcContext);

            if (string.IsNullOrEmpty(courseId))
            {
                context.Fail();
                return;
            }

            if (!context.User.Identity.IsAuthenticated)
            {
                context.Fail();
                return;
            }

            var userId = context.User.GetUserId();
            var user   = await usersRepo.FindUserByIdAsync(userId).ConfigureAwait(false);

            if (user == null)
            {
                context.Fail();
                return;
            }

            if (usersRepo.IsSystemAdministrator(user))
            {
                context.Succeed(requirement);
                return;
            }

            if (await courseRolesRepo.HasUserAccessToCourseAsync(userId, courseId, requirement.minCourseRoleType).ConfigureAwait(false))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
예제 #12
0
        private async Task <IEnumerable <IApiSlideBlock> > RenderBlock(AbstractExerciseBlock b, SlideRenderContext context)
        {
            var submissions = await solutionsRepo
                              .GetAllSubmissionsByUser(context.CourseId, context.Slide.Id, context.UserId)
                              .Include(s => s.AutomaticChecking).ThenInclude(c => c.Output)
                              .Include(s => s.AutomaticChecking).ThenInclude(c => c.CompilationError)
                              .Include(s => s.AutomaticChecking).ThenInclude(c => c.DebugLogs)
                              .Include(s => s.SolutionCode)
                              .Include(s => s.Reviews).ThenInclude(c => c.Author)
                              .Include(s => s.ManualCheckings).ThenInclude(c => c.Reviews).ThenInclude(r => r.Author)
                              .ToListAsync();

            var codeReviewComments = await slideCheckingsRepo.GetExerciseCodeReviewComments(context.CourseId, context.Slide.Id, context.UserId);

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(context.UserId, context.CourseId, CourseRoleType.CourseAdmin);

            ExerciseAttemptsStatistics exerciseAttemptsStatistics = null;

            if (b.HasAutomaticChecking())
            {
                var exerciseUsersCount = await slideCheckingsRepo.GetExerciseUsersCount(context.CourseId, context.Slide.Id);

                var exerciseUsersWithRightAnswerCount = await slideCheckingsRepo.GetExerciseUsersWithRightAnswerCount(context.CourseId, context.Slide.Id);

                var lastSuccessAttemptDate = await slideCheckingsRepo.GetExerciseLastRightAnswerDate(context.CourseId, context.Slide.Id);

                exerciseAttemptsStatistics = new ExerciseAttemptsStatistics
                {
                    AttemptedUsersCount       = exerciseUsersCount,
                    UsersWithRightAnswerCount = exerciseUsersWithRightAnswerCount,
                    LastSuccessAttemptDate    = lastSuccessAttemptDate
                };
            }

            var exerciseSlideRendererContext = new ExerciseSlideRendererContext
            {
                Submissions        = submissions,
                CodeReviewComments = codeReviewComments,
                SlideFile          = context.Slide.Info.SlideFile,
                CanSeeCheckerLogs  = isCourseAdmin,
                AttemptsStatistics = exerciseAttemptsStatistics,
            };

            return(new[] { new ExerciseBlockResponse(b, exerciseSlideRendererContext) });
        }
예제 #13
0
        public async Task <ActionResult <FlashcardsStatistics> > FlashcardsStatistics([FromQuery(Name = "course_id")][BindRequired]
                                                                                      string courseId)
        {
            var course = await courseManager.FindCourseAsync(courseId);

            if (course == null)
            {
                return(NotFound());
            }
            var hasUserAccessToCourse = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, course.Id, CourseRoleType.Instructor);

            if (!hasUserAccessToCourse)
            {
                return(BadRequest($"You don't have access to course with id {course.Id}"));
            }

            var flashcardVisitsByCourse = await usersFlashcardsVisitsRepo.GetUserFlashcardsVisitsAsync(course.Id);

            var statistics = ToFlashcardsStatistics(flashcardVisitsByCourse, course);

            return(statistics);
        }
예제 #14
0
        public async Task <ActionResult <CourseInfo> > CourseInfo(Course course)
        {
            if (course == null)
            {
                return(Json(new { status = "error", message = "Course not found" }));
            }

            var visibleUnits       = unitsRepo.GetVisibleUnits(course, User);
            var containsFlashcards = course.Units.Any(x => x.Slides.OfType <FlashcardSlide>().Any());
            var isInstructor       = await courseRolesRepo.HasUserAccessToCourseAsync(User.GetUserId(), course.Id, CourseRoleType.Instructor).ConfigureAwait(false);

            var showInstructorsSlides = isInstructor;
            var getSlideMaxScoreFunc  = await BuildGetSlideMaxScoreFunc(solutionsRepo, userQuizzesRepo, visitsRepo, groupsRepo, course, User.GetUserId());

            return(new CourseInfo
            {
                Id = course.Id,
                Title = course.Title,
                Description = course.Settings.Description,
                NextUnitPublishTime = unitsRepo.GetNextUnitPublishTime(course.Id),
                Units = visibleUnits.Select(unit => BuildUnitInfo(course.Id, unit, showInstructorsSlides, getSlideMaxScoreFunc)).ToList(),
                ContainsFlashcards = containsFlashcards
            });
        }
예제 #15
0
        public async Task <ActionResult <UsersSearchResponse> > Search([FromQuery] UsersSearchParameters parameters)
        {
            var words = parameters.Query?.Split(' ', '\t').ToList() ?? new List <string>();

            if (words.Count > 10)
            {
                return(BadRequest(new ErrorResponse("Too many words in query")));
            }

            var currentUser = await usersRepo.FindUserByIdAsync(UserId).ConfigureAwait(false);

            var isSystemAdministrator = usersRepo.IsSystemAdministrator(currentUser);

            if (!string.IsNullOrEmpty(parameters.CourseId))
            {
                if (!parameters.CourseRoleType.HasValue)
                {
                    return(BadRequest(new ErrorResponse("You should specify course_role with course_id")));
                }
                if (parameters.CourseRoleType == CourseRoleType.Student)
                {
                    return(BadRequest(new ErrorResponse("You can not search students by this method: there are too many students")));
                }

                /* Only instructors can search by course role */
                var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, parameters.CourseId, CourseRoleType.Instructor).ConfigureAwait(false);

                if (!isInstructor)
                {
                    return(StatusCode((int)HttpStatusCode.Unauthorized, new ErrorResponse("Only instructors can search by course role")));
                }
            }
            else if (parameters.CourseRoleType.HasValue)
            {
                /* Only sys-admins can search all instructors or all course-admins */
                if (!isSystemAdministrator)
                {
                    return(StatusCode((int)HttpStatusCode.Unauthorized, new ErrorResponse("Only system administrator can search by course role without specified course_id")));
                }
            }

            if (parameters.LmsRoleType.HasValue)
            {
                if (!isSystemAdministrator)
                {
                    return(StatusCode((int)HttpStatusCode.Unauthorized, new ErrorResponse("Only system administrator can search by lms role")));
                }
            }

            var request = new UserSearchRequest
            {
                CurrentUser       = currentUser,
                Words             = words,
                CourseId          = parameters.CourseId,
                MinCourseRoleType = parameters.CourseRoleType,
                LmsRole           = parameters.LmsRoleType,
            };

            /* Start the search!
             * First of all we will try to find `strict` users: users with strict match for pattern. These users will be at first place in the response.
             */

            var strictUsers = await userSearcher.SearchUsersAsync(request, strict : true, offset : 0, count : parameters.Offset + parameters.Count).ConfigureAwait(false);

            var users = strictUsers.ToList();

            /* If strict users count is enough for answer, just take needed piece of list */
            if (users.Count >= parameters.Offset + parameters.Count)
            {
                users = users.Skip(parameters.Offset).Take(parameters.Count).ToList();
            }
            else
            {
                /* If there is part of strict users which we should return, then cut off it */
                if (parameters.Offset < users.Count)
                {
                    users = users.Skip(parameters.Offset).ToList();
                }
                else
                {
                    users.Clear();
                }

                /*
                 *  (strict users) (non-strict users)
                 *  0     1    2    3    4    5    6
                 *             ^              ^
                 *             offset         offset+count
                 */
                var nonStrictUsers = await userSearcher.SearchUsersAsync(request, strict : false, offset : parameters.Offset - strictUsers.Count, count : parameters.Count - users.Count).ConfigureAwait(false);

                /* Add all non-strict users if there is no this user in strict users list */
                foreach (var user in nonStrictUsers)
                {
                    var alreadyExistUser = strictUsers.FirstOrDefault(u => u.User.Id == user.User.Id);
                    if (alreadyExistUser != null)
                    {
                        alreadyExistUser.Fields.UnionWith(user.Fields);
                    }
                    else
                    {
                        users.Add(user);
                    }
                }
            }

            var instructors = await courseRolesRepo.GetListOfUsersWithCourseRoleAsync(CourseRoleType.Instructor, null, true).ConfigureAwait(false);

            var currentUserIsInstructor = instructors.Contains(User.GetUserId());

            return(new UsersSearchResponse
            {
                Users = users.Select(u => new FoundUserResponse
                {
                    User = BuildShortUserInfo(u.User,
                                              discloseLogin: u.Fields.Contains(SearchField.Login) || currentUserIsInstructor && instructors.Contains(u.User.Id),
                                              discloseEmail: u.Fields.Contains(SearchField.Email)),
                    Fields = u.Fields.ToList(),
                }).ToList(),
                Pagination = new PaginationResponse
                {
                    Offset = parameters.Offset,
                    Count = users.Count,
                }
            });
        }
예제 #16
0
        public async Task <ActionResult <RunSolutionResponse> > RunSolution(
            [FromRoute] Course course,
            [FromRoute] Guid slideId,
            [FromBody] RunSolutionParameters parameters,
            [FromQuery] Language language,
            [FromQuery] bool isLti = false)
        {
            var courseId = course.Id;

            /* Check that no checking solution by this user in last time */
            var delta         = TimeSpan.FromSeconds(30);
            var halfMinuteAgo = DateTime.Now.Subtract(delta);

            if (await userSolutionsRepo.IsCheckingSubmissionByUser(courseId, slideId, User.Identity.GetUserId(), halfMinuteAgo, DateTime.MaxValue))
            {
                return(Json(new RunSolutionResponse(SolutionRunStatus.Ignored)
                {
                    Message = $"Ваше решение по этой задаче уже проверяется. Дождитесь окончания проверки. Вы можете отправить новое решение через {delta.Seconds} секунд."
                }));
            }

            var code = parameters.Solution;

            if (code.Length > TextsRepo.MaxTextSize)
            {
                return(Json(new RunSolutionResponse(SolutionRunStatus.Ignored)
                {
                    Message = "Слишком длинный код"
                }));
            }

            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, courseId, CourseRoleType.Instructor);

            var exerciseSlide = (await courseManager.FindCourseAsync(courseId))?.FindSlideById(slideId, isInstructor) as ExerciseSlide;

            if (exerciseSlide == null)
            {
                return(NotFound(new ErrorResponse("Slide not found")));
            }

            var result = await CheckSolution(
                courseId, exerciseSlide, code, language, User.Identity.GetUserId(), User.Identity.Name,
                waitUntilChecked : true, saveSubmissionOnCompileErrors : false
                ).ConfigureAwait(false);

            if (isLti)
            {
                try
                {
                    var score = await visitsRepo.GetScore(courseId, slideId, UserId);

                    await LtiUtils.SubmitScore(courseId, exerciseSlide, User.Identity.GetUserId(), score, ltiRequestsRepo, ltiConsumersRepo);
                }
                catch (Exception e)
                {
                    log.Error(e, "Мы не смогли отправить баллы на вашу образовательную платформу");
                    return(Json(new RunSolutionResponse(SolutionRunStatus.InternalServerError)
                    {
                        Message = "Мы не смогли отправить баллы на вашу образовательную платформу. Пожалуйста, обновите страницу — мы попробуем сделать это ещё раз."
                    }));
                }
            }

            return(result);
        }
예제 #17
0
        public async Task <ActionResult <CourseExercisesStatisticsResponse> > CourseStatistics([FromQuery(Name = "course_id")][BindRequired]
                                                                                               string courseId, int count = 10000, DateTime?from = null, DateTime?to = null)
        {
            var course = await courseManager.FindCourseAsync(courseId);

            if (course == null)
            {
                return(NotFound());
            }

            if (!from.HasValue)
            {
                from = DateTime.MinValue;
            }
            if (!to.HasValue)
            {
                to = DateTime.MaxValue;
            }

            count = Math.Min(count, 10000);

            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, course.Id, CourseRoleType.Instructor).ConfigureAwait(false);

            var exerciseSlides = course.GetSlides(isInstructor).OfType <ExerciseSlide>().ToList();

            /* TODO (andgein): I can't select all submissions because ApplicationUserId column doesn't exist in database (ApplicationUser_Id exists).
             * We should remove this column after EF Core 2.1 release (and remove tuples here)
             */
            var submissions = await userSolutionsRepo.GetAllSubmissions(course.Id, includeManualAndAutomaticCheckings : false)
                              .Where(s => s.Timestamp >= from && s.Timestamp <= to)
                              .OrderByDescending(s => s.Timestamp)
                              .Take(count)
                              .Select(s => Tuple.Create(s.SlideId, s.AutomaticCheckingIsRightAnswer, s.Timestamp))
                              .ToListAsync().ConfigureAwait(false);

            var getSlideMaxScoreFunc = await BuildGetSlideMaxScoreFunc(solutionsRepo, userQuizzesRepo, visitsRepo, groupsRepo, course, User.GetUserId());

            var getGitEditLinkFunc = await BuildGetGitEditLinkFunc(User.GetUserId(), course, courseRolesRepo, coursesRepo);

            const int daysLimit = 30;

            var result = new CourseExercisesStatisticsResponse
            {
                AnalyzedSubmissionsCount = submissions.Count,
                Exercises = exerciseSlides.Select(
                    slide =>
                {
                    /* Statistics for this exercise slide: */
                    var exerciseSubmissions = submissions.Where(s => s.Item1 == slide.Id).ToList();
                    return(new OneExerciseStatistics
                    {
                        Exercise = slideRenderer.BuildShortSlideInfo(course.Id, slide, getSlideMaxScoreFunc, getGitEditLinkFunc, Url),
                        SubmissionsCount = exerciseSubmissions.Count,
                        AcceptedCount = exerciseSubmissions.Count(s => s.Item2),
                        /* Select last 30 (`datesLimit`) dates */
                        LastDates = exerciseSubmissions.GroupBy(s => s.Item3.Date).OrderByDescending(g => g.Key).Take(daysLimit).ToDictionary(
                            /* Date: */
                            g => g.Key,
                            /* Statistics for this date: */
                            g => new OneExerciseStatisticsForDate
                        {
                            SubmissionsCount = g.Count(),
                            AcceptedCount = g.Count(s => s.Item2)
                        }
                            )
                    });
                }).ToList()
            };

            return(result);
        }
예제 #18
0
        public async Task <ActionResult <UsersProgressResponse> > UserProgress([FromRoute] string courseId, [FromBody] UserProgressParameters parameters)
        {
            if (!await courseManager.HasCourseAsync(courseId))
            {
                return(NotFound(new ErrorResponse($"Course {courseId} not found")));
            }
            var course = await courseManager.FindCourseAsync(courseId);

            var userIds = parameters.UserIds;

            if (userIds == null || userIds.Count == 0)
            {
                userIds = new List <string> {
                    UserId
                }
            }
            ;
            else
            {
                var userIdsWithProgressNotVisibleForUser = await GetUserIdsWithProgressNotVisibleForUser(course.Id, userIds);

                if (userIdsWithProgressNotVisibleForUser?.Any() ?? false)
                {
                    var userIdsStr = string.Join(", ", userIdsWithProgressNotVisibleForUser);
                    return(NotFound(new ErrorResponse($"Users {userIdsStr} not found")));
                }
            }
            var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, courseId, CourseRoleType.Instructor).ConfigureAwait(false);

            var visibleSlides = course.GetSlides(isInstructor).Select(s => s.Id).ToHashSet();

            var scores = await visitsRepo.GetScoresForSlides(course.Id, userIds);

            var visitsTimestamps = await visitsRepo.GetLastVisitsInCourse(course.Id, UserId);

            var additionalScores = await GetAdditionalScores(course.Id, userIds).ConfigureAwait(false);

            var attempts = await userQuizzesRepo.GetUsedAttemptsCountAsync(course.Id, userIds).ConfigureAwait(false);

            var waitingQuizSlides = await userQuizzesRepo.GetSlideIdsWaitingForManualCheckAsync(course.Id, userIds).ConfigureAwait(false);

            var waitingExerciseSlides = await slideCheckingsRepo.GetSlideIdsWaitingForManualExerciseCheckAsync(course.Id, userIds).ConfigureAwait(false);

            var prohibitFurtherManualCheckingSlides = await slideCheckingsRepo.GetProhibitFurtherManualCheckingSlides(course.Id, userIds).ConfigureAwait(false);

            var skippedSlides = await visitsRepo.GetSkippedSlides(course.Id, userIds);

            var usersProgress = new Dictionary <string, UserProgress>();

            foreach (var userId in scores.Keys)
            {
                var visitedSlides
                    = scores[userId]
                      .Where(kvp => visibleSlides.Contains(kvp.Key))
                      .ToDictionary(kvp => kvp.Key, kvp => new UserProgressSlideResult
                {
                    Visited                  = true,
                    Timestamp                = visitsTimestamps.TryGetValue(kvp.Key, out var visit) ? visit.Timestamp : null,
                    Score                    = kvp.Value,
                    IsSkipped                = skippedSlides.GetValueOrDefault(userId)?.Contains(kvp.Key) ?? false,
                    UsedAttempts             = attempts.GetValueOrDefault(userId)?.GetValueOrDefault(kvp.Key) ?? 0,
                    WaitingForManualChecking = (waitingExerciseSlides.GetValueOrDefault(userId)?.Contains(kvp.Key) ?? false) ||
                                               (waitingQuizSlides.GetValueOrDefault(userId)?.Contains(kvp.Key) ?? false),
                    ProhibitFurtherManualChecking = prohibitFurtherManualCheckingSlides.GetValueOrDefault(userId)?.Contains(kvp.Key) ?? false
                });
예제 #19
0
        public async Task <ActionResult> ExportGroupMembersAsTsv([Required] int groupId, Guid?quizSlideId = null)
        {
            var group = await groupsRepo.FindGroupByIdAsync(groupId).ConfigureAwait(false);

            if (group == null)
            {
                return(StatusCode((int)HttpStatusCode.NotFound, "Group not found"));
            }

            var isSystemAdministrator = await IsSystemAdministratorAsync().ConfigureAwait(false);

            var isCourseAdmin = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, group.CourseId, CourseRoleType.CourseAdmin).ConfigureAwait(false);

            if (!(isSystemAdministrator || isCourseAdmin))
            {
                return(StatusCode((int)HttpStatusCode.Forbidden, "You should be course or system admin"));
            }

            var users = await groupMembersRepo.GetGroupMembersAsUsersAsync(groupId).ConfigureAwait(false);

            var extendedUserInfo = await GetExtendedUserInfo(users).ConfigureAwait(false);

            List <string> questions = null;
            var           courseId  = group.CourseId;
            var           course    = await courseManager.GetCourseAsync(courseId);

            if (quizSlideId != null)
            {
                var slide = course.FindSlideById(quizSlideId.Value, false);
                if (slide == null)
                {
                    return(StatusCode((int)HttpStatusCode.NotFound, $"Slide not found in course {courseId}"));
                }

                if (!(slide is QuizSlide quizSlide))
                {
                    return(StatusCode((int)HttpStatusCode.NotFound, $"Slide is not quiz slide in course {courseId}"));
                }

                List <List <string> > answers;
                (questions, answers) = await GetQuizAnswers(users, courseId, quizSlide).ConfigureAwait(false);

                extendedUserInfo = extendedUserInfo.Zip(answers, (u, a) => { u.Answers = a; return(u); }).ToList();
            }

            var slides = course.GetSlides(false).Where(s => s.ShouldBeSolved).Select(s => s.Id).ToList();
            var scores = GetScoresByScoringGroups(users.Select(u => u.Id).ToList(), slides, course);
            var scoringGroupsWithScores = scores.Select(kvp => kvp.Key.ScoringGroup).ToHashSet();
            var scoringGroups           = course.Settings.Scoring.Groups.Values.Where(sg => scoringGroupsWithScores.Contains(sg.Id)).ToList();

            var headers = new List <string> {
                "Id", "Login", "Email", "FirstName", "LastName", "VisibleName", "Gender", "LastVisit", "IpAddress"
            };

            if (questions != null)
            {
                headers = headers.Concat(questions).ToList();
            }
            if (scoringGroups.Count > 0)
            {
                headers = headers.Concat(scoringGroups.Select(s => s.Abbreviation)).ToList();
            }

            var rows = new List <List <string> > {
                headers
            };

            foreach (var i in extendedUserInfo)
            {
                var row = new List <string> {
                    i.Id, i.Login, i.Email, i.FirstName, i.LastName, i.VisibleName, i.Gender.ToString(), i.LastVisit.ToSortableDate(), i.IpAddress
                };
                if (i.Answers != null)
                {
                    row = row.Concat(i.Answers).ToList();
                }
                row.AddRange(scoringGroups.Select(scoringGroup => (scores.ContainsKey((i.Id, scoringGroup.Id)) ? scores[(i.Id, scoringGroup.Id)] : 0).ToString()));
예제 #20
0
        public async Task <ActionResult <CourseInfo> > CourseInfo([FromRoute] string courseId, [FromQuery][CanBeNull] int?groupId = null)
        {
            if (!await courseManager.HasCourseAsync(courseId))
            {
                return(NotFound(new ErrorResponse("Course not found")));
            }

            var course = await courseManager.FindCourseAsync(courseId);

            List <UnitInfo> units;
            var             visibleUnitsIds = await unitsRepo.GetVisibleUnitIdsAsync(course, UserId);

            var visibleUnits = course.GetUnits(visibleUnitsIds);

            if (groupId == null)
            {
                var isInstructor = await courseRolesRepo.HasUserAccessToCourseAsync(UserId, course.Id, CourseRoleType.Instructor).ConfigureAwait(false);

                if (!isInstructor && visibleUnits.Count == 0)
                {
                    return(NotFound(new ErrorResponse("Course not found")));
                }

                var unitAppearances = !isInstructor
                                        ? new Dictionary <Guid, UnitAppearance>()
                                        : (await unitsRepo.GetUnitAppearancesAsync(course)).ToDictionary(a => a.UnitId, a => a);
                var publishedUnitIds     = new HashSet <Guid>(!isInstructor ? visibleUnitsIds : await unitsRepo.GetPublishedUnitIdsAsync(course));
                var getSlideMaxScoreFunc = await BuildGetSlideMaxScoreFunc(solutionsRepo, userQuizzesRepo, visitsRepo, groupsRepo, course, UserId);

                var getGitEditLinkFunc = await BuildGetGitEditLinkFunc(User.GetUserId(), course, courseRolesRepo, coursesRepo);

                units = visibleUnits.Select(unit => BuildUnitInfo(course.Id, unit,
                                                                  !publishedUnitIds.Contains(unit.Id), publishedUnitIds.Contains(unit.Id) ? null : unitAppearances.GetOrDefault(unit.Id)?.PublishTime,
                                                                  isInstructor, getSlideMaxScoreFunc, getGitEditLinkFunc)).ToList();
            }
            else
            {
                var group = await groupsRepo.FindGroupByIdAsync(groupId.Value).ConfigureAwait(false);

                if (group == null)
                {
                    return(NotFound(new ErrorResponse("Group not found")));
                }

                async Task <bool> IsUserMemberOfGroup() => await groupMembersRepo.IsUserMemberOfGroup(groupId.Value, UserId).ConfigureAwait(false);
                async Task <bool> IsGroupVisibleForUserAsync() => await groupAccessesRepo.IsGroupVisibleForUserAsync(groupId.Value, UserId).ConfigureAwait(false);

                var isGroupAvailableForUser = await IsUserMemberOfGroup() || await IsGroupVisibleForUserAsync();

                if (!isGroupAvailableForUser)
                {
                    return(NotFound(new ErrorResponse("Group not found")));
                }

                if (visibleUnits.Count == 0)
                {
                    return(NotFound(new ErrorResponse("Course not found")));
                }

                var getSlideMaxScoreFunc = BuildGetSlideMaxScoreFunc(course, group);
                var getGitEditLinkFunc   = await BuildGetGitEditLinkFunc(User.GetUserId(), course, courseRolesRepo, coursesRepo);

                units = visibleUnits.Select(unit => BuildUnitInfo(course.Id, unit, false, null, false, getSlideMaxScoreFunc, getGitEditLinkFunc)).ToList();
            }

            var containsFlashcards = visibleUnits.Any(x => x.GetSlides(true).OfType <FlashcardSlide>().Any());
            var scoringSettings    = GetScoringSettings(course);
            var tempCourseError    = (await tempCoursesRepo.GetCourseErrorAsync(courseId))?.Error;
            var isTempCourse       = await tempCoursesRepo.FindAsync(courseId) != null;

            return(new CourseInfo
            {
                Id = course.Id,
                Title = isTempCourse ? "Временный - " + course.Title : course.Title,
                Description = course.Settings.Description,
                Scoring = scoringSettings,
                NextUnitPublishTime = unitsRepo.GetNextUnitPublishTime(course.Id),
                Units = units,
                ContainsFlashcards = containsFlashcards,
                IsTempCourse = isTempCourse,
                TempCourseError = tempCourseError
            });
        }