/// <summary>
		/// Writes the contents of a submission to an archive.
		/// </summary>
		private async Task WriteSubmissionToArchiveAsync(
			ZipArchive archive,
			Project project,
			ClassroomMembership student,
			IArchive templateContents,
			IArchive submissionContents)
		{
			var studentFolder = $"EclipseProjects\\{student.GitHubTeam}";

			// The project will contain all non-immutable submission files, 
			// plus all immutable and private files from the template project.

			var projectContents = submissionContents.Files
				.Where
				(
					entry => project.GetFileType(entry) == FileType.Public
				)
				.Concat
				(
					templateContents.Files.Where
					(
						entry => project.GetFileType(entry) != FileType.Public
					)
				)
				.ToList();

			foreach (var entry in projectContents)
			{
				if (ExcludeEntry(project, entry))
					continue;

				var contents = _transformer.GetFileContents(project, student, entry);

				var archiveFilePath = entry.FullPath;
				var archiveFileFolder = archiveFilePath.Contains("/")
					? archiveFilePath.Substring(0, archiveFilePath.LastIndexOf("/"))
					: archiveFilePath;

				var localFileFolder = $"{studentFolder}\\{archiveFileFolder}";
				var fileName = archiveFilePath.Substring(archiveFilePath.LastIndexOf("/") + 1);
				var localFilePath = $"{localFileFolder}\\{fileName}";

				// Add the file to the student project folder.
				var projectFolderEntry = archive.CreateEntry(localFilePath);
				using (Stream stream = projectFolderEntry.Open())
				{
					await stream.WriteAsync(contents, offset: 0, count: contents.Length);
				}

				// Add the file to the folder containing all files, if applicable.
				if (fileName.EndsWith(".java") && project.GetFileType(entry) == FileType.Public)
				{
					var allFilesEntry = archive.CreateEntry($"AllFiles\\{student.GitHubTeam}-{fileName}");
					using (Stream stream = allFilesEntry.Open())
					{
						await stream.WriteAsync(contents, offset: 0, count: contents.Length);
					}
				}
			}
		}
 /// <summary>
 /// Constructor.
 /// </summary>
 public StudentRepoPushEvents(
     ClassroomMembership student,
     IList<GitHubPushEvent> pushEvents)
 {
     Student = student;
     PushEvents = pushEvents;
 }
 /// <summary>
 /// Constructor.
 /// </summary>
 public StudentDownloadRequest(
     ClassroomMembership student,
     bool submitted)
 {
     Student = student;
     Submitted = submitted;
 }
 /// <summary>
 /// Constructor.
 /// </summary>
 public StudentSubmission(
     ClassroomMembership student,
     IArchive contents)
 {
     Student = student;
     Contents = contents;
 }
		public void GetRepoName_ReturnsCorrectName()
		{
			var repoMetadataRetriever = new RepositoryMetadataRetriever(repoClient: null);
			
			var project = new Project() { Name = "Project1" };
			var student = new ClassroomMembership() { GitHubTeam = "LastNameFirstName" };

			var result = repoMetadataRetriever.GetRepoName(project, student);

			Assert.Equal("Project1_LastNameFirstName", result);
		}
        /// <summary>
        /// Returns the file's contents, with any applicable 
        /// transformations applied.
        /// </summary>
        public byte[] GetFileContents(
            Project project,
            ClassroomMembership student,
            IArchiveFile entry)
        {
            if (entry.FullPath.EndsWith(".project"))
            {
                var newContents = entry.GetEncodedData().Replace
                (
                    $"<name>{project.Name}</name>",
                    $"<name>{project.Name}_{student.GitHubTeam}</name>"
                );

                using (var memoryStream = new MemoryStream())
                using (var streamWriter = new StreamWriter(memoryStream))
                {
                    streamWriter.Write(newContents);
                    streamWriter.Flush();

                    return memoryStream.ToArray();
                }
            }
            else if (entry.FullPath.EndsWith(".classpath"))
            {
                var jUnitPath = "org.eclipse.jdt.junit.JUNIT_CONTAINER/4";
                using (var stream = new MemoryStream(entry.GetRawData()))
                {
                    var projectNode = XElement.Load(stream);
                    var hasJUnit = projectNode
                        .Elements(XName.Get("classpathentry"))
                        .Any
                        (
                               elt => elt.Attribute(XName.Get("path")) != null
                            && elt.Attribute(XName.Get("path")).Value == jUnitPath
                        );

                    if (!hasJUnit)
                        projectNode.Add(XElement.Parse($"<classpathentry kind=\"con\" path=\"{jUnitPath}\"/>"));

                    using (var newStream = new MemoryStream())
                    {
                        projectNode.Save(newStream);
                        newStream.Flush();

                        return newStream.ToArray();
                    }
                }
            }
            else
            {
                return entry.GetRawData();
            }
        }
		/// <summary>
		/// Returns a list of push events for the given project.
		/// </summary>
		private async Task<StudentRepoPushEvents> GetAllPushEventsAsync(
			ClassroomMembership student,
			GitHubRepository repository)
		{
			var pushEvents = await _repoClient.GetPushEventsAsync
			(
				repository.Owner, 
				repository.Name
			);

			return new StudentRepoPushEvents(student, pushEvents);
		}
		/// <summary>
		/// Returns a new student submission.
		/// </summary>
		private StudentSubmission GetSubmission(
			ClassroomMembership student,
			IArchive contents)
		{
			return new StudentSubmission(student, contents);
		}
		/// <summary>
		/// Creates a repository for the given student, and pushes the non-test files
		/// from the source project to the new repository.
		/// </summary>
		private async Task<CreateAndPushResult> CreateAndPushAsync(
			Project project,
			ClassroomMembership student,
			string webhookUrl,
			bool overwriteIfSafe,
			ICollection<GitHubTeam> teams,
			ICollection<GitHubRepository> repositories,
			IArchive templateContents)
		{
			string orgName = project.Classroom.GitHubOrganization;
			string repoName = $"{project.Name}_{student.GitHubTeam}";

			try
			{
				var repository = repositories.SingleOrDefault(repo => repo.Name == repoName);
				var team = teams.First(teamCandidate => teamCandidate.Name == student.GitHubTeam);
				bool repositoryAlreadyExisted = (repository != null);

				if (repositoryAlreadyExisted)
				{
					if (!overwriteIfSafe)
						return CreateAndPushResult.Exists;

					var commits = await _repoClient.GetAllCommitsAsync(orgName, repoName);
					if (commits.Count > c_numInitialCommits)
						return CreateAndPushResult.Exists;
				}
				else
				{
					repository = await _repoClient.CreateRepositoryAsync
					(
						orgName,
						repoName,
						team,
						overwrite: false
					);

					var staffTeam = GetStaffTeam(project.Classroom, teams);
					if (staffTeam != null)
					{
						await _teamClient.AddRepositoryAsync(orgName, repoName, staffTeam);
					}
				}

				await _repoClient.OverwriteRepositoryAsync
				(
					repository,
					c_starterCommitMessage,
					templateContents,
					entry => project.GetFileType(entry) != FileType.Private,
					entry => project.GetFileType(entry) == FileType.Immutable
				);

				await _repoClient.EnsurePushWebhookAsync(repository, webhookUrl);

				return repositoryAlreadyExisted
					? CreateAndPushResult.Overwritten
					: CreateAndPushResult.Created;
			}
			catch (Exception ex)
			{
				_logger.LogError
				(
					(EventId)0,
					ex,
					"Failed to create repository {RepoName} in organization {Org}.", repoName, orgName
				);

				return CreateAndPushResult.Failed;
			}
		}
Example #10
0
 /// <summary>
 /// Returns the name of the student project repository.
 /// </summary>
 public string GetStudentRepoName(ClassroomMembership student)
 {
     return $"{Name}_{student.GitHubTeam}";
 }
		/// <summary>
		/// Returns all commits for the given user/project.
		/// </summary>
		private async Task<ICollection<GitHubCommit>> GetAllCommitsAsync(
			Project project, 
			ClassroomMembership student)
		{
			var orgName = student.Classroom.GitHubOrganization;
			var repoName = project.GetStudentRepoName(student);

			return await _repoClient.GetAllCommitsAsync(orgName, repoName);
		}
Example #12
0
		/// <summary>
		/// Returns whether or not the given user is in the classroom's GitHub organization.
		/// </summary>
		private async Task<bool> EnsureGitHubMembershipUpdated(
			User user, 
			ClassroomMembership membership)
		{
			if (membership.InGitHubOrganization)
			{
				return true;
			}
			
			await _dbContext.Entry(membership)
				.Reference(cm => cm.Classroom)
				.LoadAsync();

			var memberInOrganization = await _gitHubOrgClient.CheckMemberAsync
			(
				membership.Classroom.GitHubOrganization,
				user.GitHubLogin
			);
			
			if (memberInOrganization)
			{
				membership.InGitHubOrganization = true;
				_dbContext.ClassroomMemberships.Update(membership);

				return true;
			}
			else
			{
				return false;
			}
		}
Example #13
0
		/// <summary>
		/// Invites the user to a new GitHub team.
		/// </summary>
		private async Task EnsureUserInGithubOrgAsync(User user, ClassroomMembership membership)
		{
			if (membership.InGitHubOrganization)
				return;

			var team = await _gitHubTeamClient.CreateTeamAsync
			(
				membership.Classroom.GitHubOrganization, 
				membership.GitHubTeam
			);

			await _gitHubTeamClient.InviteUserToTeamAsync
			(
				membership.Classroom.GitHubOrganization,
				team,
				user.GitHubLogin
			);
		}
Example #14
0
		/// <summary>
		/// Ensures a classroom membership exists for the given user and role.
		/// The caller is responsible for saving changes.
		/// </summary>
		private async Task<ClassroomMembership> EnsureClassroomMembershipAsync(
			User user,
			Classroom classroom,
			ClassroomRole role)
		{
			var classroomMembership = user?.ClassroomMemberships
				?.SingleOrDefault(m => m.ClassroomId == classroom.Id);

			if (user.ClassroomMemberships == null)
			{
				user.ClassroomMemberships = new List<ClassroomMembership>();
			}

			if (classroomMembership == null)
			{
				classroomMembership = new ClassroomMembership()
				{
					ClassroomId = classroom.Id,
					Classroom = classroom,
					InGitHubOrganization = false,
					GitHubTeam = await GetNewGitHubTeamNameAsync(classroom, user),
					Role = role
				};

				user.ClassroomMemberships.Add(classroomMembership);
			}
			else if (role > classroomMembership.Role)
			{
				classroomMembership.Role = role;
			}

			return classroomMembership;
		}
		/// <summary>
		/// Returns the repository name for the given student/project.
		/// </summary>
		public string GetRepoName(Project project, ClassroomMembership student)
		{
			return $"{project.Name}_{student.GitHubTeam}";
		}