Package controllers

Source Code of controllers.ProjectApp

/**
* Yobi, Project Hosting SW
*
* Copyright 2012 NAVER Corp.
* http://yobi.io
*
* @Author Sangcheol Hwang
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers;

import actions.AnonymousCheckAction;
import actions.DefaultProjectCheckAction;

import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.Junction;
import com.avaje.ebean.Page;

import controllers.annotation.IsAllowed;
import info.schleichardt.play2.mailplugin.Mailer;
import models.*;
import models.enumeration.Operation;
import models.enumeration.ProjectScope;
import models.enumeration.RequestState;
import models.enumeration.ResourceType;
import models.enumeration.RoleType;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.mail.HtmlEmail;
import org.codehaus.jackson.node.ObjectNode;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.tmatesoft.svn.core.SVNException;

import play.Logger;
import play.data.Form;
import play.data.validation.ValidationError;
import play.db.ebean.Transactional;
import play.i18n.Messages;
import play.libs.Json;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Http.MultipartFormData.FilePart;
import play.mvc.Result;
import play.mvc.With;
import playRepository.Commit;
import playRepository.PlayRepository;
import playRepository.RepositoryService;
import scala.reflect.io.FileOperationException;
import utils.*;
import play.data.validation.Constraints.PatternValidator;
import validation.ExConstraints.RestrictedValidator;
import views.html.project.create;
import views.html.project.delete;
import views.html.project.home;
import views.html.project.setting;
import views.html.project.transfer;
import views.html.project.change_vcs;

import javax.servlet.ServletException;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import static play.data.Form.form;
import static play.libs.Json.toJson;
import static utils.LogoUtil.*;
import static utils.TemplateHelper.*;

public class ProjectApp extends Controller {

    private static final int ISSUE_MENTION_SHOW_LIMIT = 2000;

    private static final int MAX_FETCH_PROJECTS = 1000;

    private static final int COMMIT_HISTORY_PAGE = 0;

    private static final int COMMIT_HISTORY_SHOW_LIMIT = 10;

    private static final int RECENLTY_ISSUE_SHOW_LIMIT = 10;

    private static final int RECENLTY_POSTING_SHOW_LIMIT = 10;

    private static final int RECENT_PULL_REQUEST_SHOW_LIMIT = 10;

    private static final int PROJECT_COUNT_PER_PAGE = 10;

    private static final String HTML = "text/html";

    private static final String JSON = "application/json";

    @With(AnonymousCheckAction.class)
    @IsAllowed(Operation.UPDATE)
    public static Result projectOverviewUpdate(String ownerId, String projectName){
        Project targetProject = Project.findByOwnerAndProjectName(ownerId, projectName);
        if (targetProject == null) {
            return notFound(ErrorViews.NotFound.render("error.notfound"));
        }

        targetProject.overview = request().body().asJson().findPath("overview").getTextValue();
        targetProject.save();

        ObjectNode result = Json.newObject();
        result.put("overview", targetProject.overview);
        return ok(result);
    }

    @IsAllowed(Operation.READ)
    public static Result project(String ownerId, String projectName)
            throws IOException, ServletException, SVNException, GitAPIException {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        List<History> histories = getProjectHistory(ownerId, project);

        UserApp.currentUser().visits(project);

        String tabId = StringUtils.defaultIfBlank(request().getQueryString("tabId"), "readme");

        return ok(home.render(getTitleMessage(tabId), project, histories, tabId));
    }

    private static String getTitleMessage(String tabId) {
        switch (tabId) {
            case "history":
                return "project.history.recent";
            case "dashboard":
                return "title.projectDashboard";
            default:
            case "readme":
                return "title.projectHome";
        }
    }

    private static List<History> getProjectHistory(String ownerId, Project project)
            throws IOException, ServletException, SVNException, GitAPIException {
        project.fixInvalidForkData();

        PlayRepository repository = RepositoryService.getRepository(project);

        List<Commit> commits = null;

        try {
            commits = repository.getHistory(COMMIT_HISTORY_PAGE, COMMIT_HISTORY_SHOW_LIMIT, null, null);
        } catch (NoHeadException e) {
            // NOOP
        }

        List<Issue> issues = Issue.findRecentlyCreated(project, RECENLTY_ISSUE_SHOW_LIMIT);
        List<Posting> postings = Posting.findRecentlyCreated(project, RECENLTY_POSTING_SHOW_LIMIT);
        List<PullRequest> pullRequests = PullRequest.findRecentlyReceived(project, RECENT_PULL_REQUEST_SHOW_LIMIT);

        return History.makeHistory(ownerId, project, commits, issues, postings, pullRequests);
    }

    @With(AnonymousCheckAction.class)
    public static Result newProjectForm() {
        Form<Project> projectForm = form(Project.class).bindFromRequest("owner");
        projectForm.discardErrors();
        List<OrganizationUser> orgUserList = OrganizationUser.findByAdmin(UserApp.currentUser().id);
        return ok(create.render("title.newProject", projectForm, orgUserList));
    }

    @IsAllowed(Operation.UPDATE)
    public static Result settingForm(String ownerId, String projectName) throws Exception {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        Form<Project> projectForm = form(Project.class).fill(project);
        PlayRepository repository = RepositoryService.getRepository(project);
        return ok(setting.render("title.projectSetting", projectForm, project, repository.getBranches()));
    }

    @Transactional
    public static Result newProject() throws Exception {
        Form<Project> filledNewProjectForm = form(Project.class).bindFromRequest();
        String owner = filledNewProjectForm.field("owner").value();

        User user = UserApp.currentUser();
        Organization organization = Organization.findByName(owner);

        if ((!AccessControl.isGlobalResourceCreatable(user))
                || (Organization.isNameExist(owner) && !OrganizationUser.isAdmin(organization.id, user.id))) {
            return forbidden(ErrorViews.Forbidden.render("'" + user.name + "' has no permission"));
        }

        if (validateWhenNew(filledNewProjectForm)) {
            return badRequest(create.render("title.newProject",
                    filledNewProjectForm, OrganizationUser.findByAdmin(user.id)));
        }

        Project project = filledNewProjectForm.get();
        if (Organization.isNameExist(owner)) {
            project.organization = organization;
        }
        ProjectUser.assignRole(user.id, Project.create(project), RoleType.MANAGER);
        RepositoryService.createRepository(project);

        saveProjectMenuSetting(project);

        return redirect(routes.ProjectApp.project(project.owner, project.name));
    }

    private static boolean validateWhenNew(Form<Project> newProjectForm) {
        String owner = newProjectForm.field("owner").value();
        String name = newProjectForm.field("name").value();

        User user = User.findByLoginId(owner);
        boolean ownerIsUser = User.isLoginIdExist(owner);
        boolean ownerIsOrganization = Organization.isNameExist(owner);

        if (!ownerIsUser && !ownerIsOrganization) {
            newProjectForm.reject("owner", "project.owner.invalidate");
        }

        if (ownerIsUser && !UserApp.currentUser().id.equals(user.id)) {
            newProjectForm.reject("owner", "project.owner.invalidate");
        }

        if (Project.exists(owner, name)) {
            newProjectForm.reject("name", "project.name.duplicate");
        }

        ValidationError error = newProjectForm.error("name");
        if (error != null) {
            if (PatternValidator.message.equals(error.message())) {
                newProjectForm.errors().remove("name");
                newProjectForm.reject("name", "project.name.alert");
            } else if (RestrictedValidator.message.equals(error.message())) {
                newProjectForm.errors().remove("name");
                newProjectForm.reject("name", "project.name.reserved.alert");
            }
        }

        return newProjectForm.hasErrors();
    }

    @Transactional
    @IsAllowed(Operation.UPDATE)
    public static Result settingProject(String ownerId, String projectName)
            throws IOException, NoSuchAlgorithmException, UnsupportedOperationException, ServletException {
        Form<Project> filledUpdatedProjectForm = form(Project.class).bindFromRequest();
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        PlayRepository repository = RepositoryService.getRepository(project);

        if (validateWhenUpdate(ownerId, filledUpdatedProjectForm)) {
            return badRequest(setting.render("title.projectSetting",
                    filledUpdatedProjectForm, project, repository.getBranches()));
        }

        Project updatedProject = filledUpdatedProjectForm.get();

        FilePart filePart = request().body().asMultipartFormData().getFile("logoPath");

        if (!isEmptyFilePart(filePart)) {
            Attachment.deleteAll(updatedProject.asResource());
            new Attachment().store(filePart.getFile(), filePart.getFilename(), updatedProject.asResource());
        }

        Map<String, String[]> data = request().body().asMultipartFormData().asFormUrlEncoded();
        String defaultBranch = HttpUtil.getFirstValueFromQuery(data, "defaultBranch");
        if (StringUtils.isNotEmpty(defaultBranch)) {
            repository.setDefaultBranch(defaultBranch);
        }

        if (!project.name.equals(updatedProject.name)) {
            if (!repository.renameTo(updatedProject.name)) {
                throw new FileOperationException("fail repository rename to " + project.owner + "/" + updatedProject.name);
            }
        }

        updatedProject.update();

        saveProjectMenuSetting(updatedProject);

        return redirect(routes.ProjectApp.settingForm(ownerId, updatedProject.name));
    }

    private static void saveProjectMenuSetting(Project project) {
        Form<ProjectMenuSetting> filledUpdatedProjectMenuSettingForm = form(ProjectMenuSetting.class).bindFromRequest();
        ProjectMenuSetting updatedProjectMenuSetting = filledUpdatedProjectMenuSettingForm.get();

        project.refresh();
        updatedProjectMenuSetting.project = project;

        if (project.menuSetting == null) {
            updatedProjectMenuSetting.save();
        } else {
            updatedProjectMenuSetting.id = project.menuSetting.id;
            updatedProjectMenuSetting.update();
        }
    }

    private static boolean validateWhenUpdate(String loginId, Form<Project> updateProjectForm) {
        Long id = Long.parseLong(updateProjectForm.field("id").value());
        String name = updateProjectForm.field("name").value();

        if (!Project.projectNameChangeable(id, loginId, name)) {
            flash(Constants.WARNING, "project.name.duplicate");
            updateProjectForm.reject("name", "project.name.duplicate");
        }

        FilePart filePart = request().body().asMultipartFormData().getFile("logoPath");

        if (!isEmptyFilePart(filePart)) {
            if (!isImageFile(filePart.getFilename())) {
                flash(Constants.WARNING, "project.logo.alert");
                updateProjectForm.reject("logoPath", "project.logo.alert");
            } else if (filePart.getFile().length() > LOGO_FILE_LIMIT_SIZE) {
                flash(Constants.WARNING, "project.logo.fileSizeAlert");
                updateProjectForm.reject("logoPath", "project.logo.fileSizeAlert");
            }
        }

        ValidationError error = updateProjectForm.error("name");
        if (error != null) {
            if (PatternValidator.message.equals(error.message())) {
                flash(Constants.WARNING, "project.name.alert");
                updateProjectForm.errors().remove("name");
                updateProjectForm.reject("name", "project.name.alert");
            } else if (RestrictedValidator.message.equals(error.message())) {
                flash(Constants.WARNING, "project.name.reserved.alert");
                updateProjectForm.errors().remove("name");
                updateProjectForm.reject("name", "project.name.reserved.alert");
            }
        }

        return updateProjectForm.hasErrors();
    }

    @IsAllowed(Operation.DELETE)
    public static Result deleteForm(String ownerId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        Form<Project> projectForm = form(Project.class).fill(project);
        return ok(delete.render("title.projectDelete", projectForm, project));
    }

    @Transactional
    @IsAllowed(Operation.DELETE)
    public static Result deleteProject(String ownerId, String projectName) throws Exception {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        project.delete();
        RepositoryService.deleteRepository(project);

        if (HttpUtil.isRequestedWithXHR(request())){
            response().setHeader("Location", routes.Application.index().toString());
            return status(204);
        }

        return redirect(routes.Application.index());
    }

    @Transactional
    @IsAllowed(Operation.UPDATE)
    public static Result members(String loginId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(loginId, projectName);
        project.cleanEnrolledUsers();
        return ok(views.html.project.members.render("title.projectMembers",
                ProjectUser.findMemberListByProject(project.id), project,
                Role.findProjectRoles()));
    }

    @IsAllowed(Operation.READ)
    public static Result mentionList(String loginId, String projectName, Long number, String resourceType) {
        String prefer = HttpUtil.getPreferType(request(), HTML, JSON);
        if (prefer == null) {
            return status(Http.Status.NOT_ACCEPTABLE);
        } else {
            response().setHeader("Vary", "Accept");
        }

        Project project = Project.findByOwnerAndProjectName(loginId, projectName);

        List<User> userList = new ArrayList<>();
        collectAuthorAndCommenter(project, number, userList, resourceType);
        addProjectMemberList(project, userList);
        addGroupMemberList(project, userList);

        userList.remove(UserApp.currentUser());
        userList.add(UserApp.currentUser()); //send me last at list

        Map<String, List<Map<String, String>>> result = new HashMap<>();
        result.put("result", getUserList(project, userList));
        result.put("issues", getIssueList(project));

        return ok(toJson(result));
    }

    private static List<Map<String, String>> getIssueList(Project project) {
        List<Map<String, String>> mentionListOfIssues = new ArrayList<>();
        collectedIssuesToMap(mentionListOfIssues, getMentionIssueList(project));
        return mentionListOfIssues;
    }

    private static List<Map<String, String>> getUserList(Project project, List<User> userList) {
        List<Map<String, String>> mentionListOfUser = new ArrayList<>();
        collectedUsersToMentionList(mentionListOfUser, userList);
        addProjectNameToMentionList(mentionListOfUser, project);
        addOrganizationNameToMentionList(mentionListOfUser, project);
        return mentionListOfUser;
    }

    private static void addProjectNameToMentionList(List<Map<String, String>> users, Project project) {
        Map<String, String> projectUserMap = new HashMap<>();
        if(project != null){
            projectUserMap.put("loginid", project.owner+"/" + project.name);
            projectUserMap.put("username", project.name );
            projectUserMap.put("name", project.name);
            projectUserMap.put("image", urlToProjectLogo(project).toString());
            users.add(projectUserMap);
        }
    }

    private static void addOrganizationNameToMentionList(List<Map<String, String>> users, Project project) {
        Map<String, String> projectUserMap = new HashMap<>();
        if(project != null && project.organization != null){
            projectUserMap.put("loginid", project.organization.name);
            projectUserMap.put("username", project.organization.name);
            projectUserMap.put("name", project.organization.name);
            projectUserMap.put("image", urlToOrganizationLogo(project.organization).toString());
            users.add(projectUserMap);
        }
    }

    private static void collectedIssuesToMap(List<Map<String, String>> mentionList,
            List<Issue> issueList) {
        for (Issue issue : issueList) {
            Map<String, String> projectIssueMap = new HashMap<>();
            projectIssueMap.put("name", issue.getNumber().toString() + issue.title);
            projectIssueMap.put("issueNo", issue.getNumber().toString());
            projectIssueMap.put("title", issue.title);
            mentionList.add(projectIssueMap);
        }
    }

    private static List<Issue> getMentionIssueList(Project project) {
        return Issue.finder.where()
                .eq("project.id", project.isForkedFromOrigin() ? project.originalProject.id : project.id)
                .orderBy("createdDate desc")
                .setMaxRows(ISSUE_MENTION_SHOW_LIMIT)
                .findList();
    }

    @IsAllowed(Operation.READ)
    public static Result mentionListAtCommitDiff(String ownerId, String projectName, String commitId, Long pullRequestId)
            throws IOException, UnsupportedOperationException, ServletException, SVNException {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        PullRequest pullRequest;
        Project fromProject = project;
        if (pullRequestId != -1) {
            pullRequest = PullRequest.findById(pullRequestId);
            if (pullRequest != null) {
                fromProject = pullRequest.fromProject;
            }
        }

        Commit commit = RepositoryService.getRepository(fromProject).getCommit(commitId);

        List<User> userList = new ArrayList<>();
        addCommitAuthor(commit, userList);
        addCodeCommenters(commitId, fromProject.id, userList);
        addProjectMemberList(project, userList);
        addGroupMemberList(project, userList);
        userList.remove(UserApp.currentUser());
        userList.add(UserApp.currentUser()); //send me last at list

        Map<String, List<Map<String, String>>> result = new HashMap<>();
        result.put("result", getUserList(project, userList));
        result.put("issues", getIssueList(project));

        return ok(toJson(result));
    }

    @IsAllowed(Operation.READ)
    public static Result mentionListAtPullRequest(String ownerId, String projectName, String commitId, Long pullRequestId)
            throws IOException, UnsupportedOperationException, ServletException, SVNException {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        PullRequest pullRequest = PullRequest.findById(pullRequestId);
        List<User> userList = new ArrayList<>();

        addCommentAuthors(pullRequestId, userList);
        addProjectMemberList(project, userList);
        addGroupMemberList(project, userList);
        if(!commitId.isEmpty()) {
            addCommitAuthor(RepositoryService.getRepository(pullRequest.fromProject).getCommit(commitId), userList);
        }

        User contributor = pullRequest.contributor;
        if(!userList.contains(contributor)) {
            userList.add(contributor);
        }

        userList.remove(UserApp.currentUser());
        userList.add(UserApp.currentUser()); //send me last at list

        Map<String, List<Map<String, String>>> result = new HashMap<>();
        result.put("result", getUserList(project, userList));
        result.put("issues", getIssueList(project));

        return ok(toJson(result));
    }

    private static void addCommentAuthors(Long pullRequestId, List<User> userList) {
        List<CommentThread> threads = PullRequest.findById(pullRequestId).commentThreads;
        for (CommentThread thread : threads) {
            for (ReviewComment comment : thread.reviewComments) {
                final User commenter = User.findByLoginId(comment.author.loginId);
                if(userList.contains(commenter)) {
                    userList.remove(commenter);
                }
                userList.add(commenter);
            }
        }
        Collections.reverse(userList);
    }

    @IsAllowed(Operation.DELETE)
    public static Result transferForm(String ownerId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        Form<Project> projectForm = form(Project.class).fill(project);

        return ok(transfer.render("title.projectTransfer", projectForm, project));
    }

    @Transactional
    @IsAllowed(Operation.DELETE)
    public static Result transferProject(String ownerId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        String destination = request().getQueryString("owner");

        User destOwner = User.findByLoginId(destination);
        Organization destOrg = Organization.findByName(destination);
        if (destOwner.isAnonymous() && destOrg == null) {
            return badRequest(ErrorViews.BadRequest.render());
        }

        User projectOwner = User.findByLoginId(project.owner);
        Organization projectOrg = Organization.findByName(project.owner);
        if((destOwner != null && destOwner.equals(projectOwner)) || (projectOrg != null && projectOrg.equals(destOrg))) {
            flash(Constants.INFO, "project.transfer.has.same.owner");
            Form<Project> projectForm = form(Project.class).fill(project);
            return ok(transfer.render("title.projectTransfer", projectForm, project));
        }

        ProjectTransfer pt = null;
        // make a request to move to an user
        if (!destOwner.isAnonymous()) {
            pt = ProjectTransfer.requestNewTransfer(project, UserApp.currentUser(), destOwner.loginId);
        }
        // make a request to move to an group
        if (destOrg != null) {
            pt = ProjectTransfer.requestNewTransfer(project, UserApp.currentUser(), destOrg.name);
        }
        sendTransferRequestMail(pt);
        flash(Constants.INFO, "project.transfer.is.requested");

        // if the request is sent by XHR, response with 204 204 No Content and Location header.
        String url = routes.ProjectApp.project(ownerId, projectName).url();
        if (HttpUtil.isRequestedWithXHR(request())) {
            response().setHeader("Location", url);
            return status(204);
        }

        return redirect(url);
    }

    @Transactional
    @With(AnonymousCheckAction.class)
    public static synchronized Result acceptTransfer(Long id, String confirmKey) throws IOException, ServletException {
        ProjectTransfer pt = ProjectTransfer.findValidOne(id);
        if (pt == null) {
            return notFound(ErrorViews.NotFound.render());
        }
        if (confirmKey == null || !pt.confirmKey.equals(confirmKey)) {
            return badRequest(ErrorViews.BadRequest.render());
        }

        if (!AccessControl.isAllowed(UserApp.currentUser(), pt.asResource(), Operation.ACCEPT)) {
            return forbidden(ErrorViews.Forbidden.render());
        }

        Project project = pt.project;

        // Change the project's name and move the repository.
        String newProjectName = Project.newProjectName(pt.destination, project.name);
        PlayRepository repository = RepositoryService.getRepository(project);
        repository.move(project.owner, project.name, pt.destination, newProjectName);

        User newOwnerUser = User.findByLoginId(pt.destination);
        Organization newOwnerOrg = Organization.findByName(pt.destination);

        // Change the project's information.
        project.owner = pt.destination;
        project.name = newProjectName;
        if (newOwnerOrg != null) {
            project.organization = newOwnerOrg;
        } else {
            project.organization = null;
        }
        project.update();

        // Change roles.
        if (!newOwnerUser.isAnonymous()) {
            ProjectUser.assignRole(newOwnerUser.id, project.id, RoleType.MANAGER);
        }
        if (ProjectUser.isManager(pt.sender.id, project.id)) {
            ProjectUser.assignRole(pt.sender.id, project.id, RoleType.MEMBER);
        }

        // Change the tranfer's status to be accepted.
        pt.newProjectName = newProjectName;
        pt.accepted = true;
        pt.update();

        // If the opposite request is exists, delete it.
        ProjectTransfer.deleteExisting(project, pt.sender, pt.destination);

        return redirect(routes.ProjectApp.project(project.owner, project.name));
    }

    @IsAllowed(Operation.UPDATE)
    public static Result changeVCSForm(String ownerId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        Form<Project> projectForm = form(Project.class).fill(project);
        return ok(change_vcs.render("title.projectChangeVCS", projectForm, project));
    }

    @IsAllowed(Operation.UPDATE)
    public static Result changeVCS(String ownerId, String projectName) throws Exception {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        try {
            if (project.readme() != null){
                Posting posting = Posting.findREADMEPosting(project);
                if (posting != null){
                    posting.readme = false;
                    posting.save();
                }
            }
            project.changeVCS();
            String url = routes.ProjectApp.project(ownerId, projectName).url();
            response().setHeader("Location", url);
            return noContent();
        } catch (Exception e) {
            Logger.error(e.getMessage());
        }
        return internalServerError();
    }

    private static void sendTransferRequestMail(ProjectTransfer pt) {
        HtmlEmail email = new HtmlEmail();
        try {
            String acceptUrl = pt.getAcceptUrl();
            String message = Messages.get("transfer.message.hello", pt.destination) + "\n\n"
                    + Messages.get("transfer.message.detail", pt.project.name, pt.newProjectName, pt.project.owner, pt.destination) + "\n"
                    + Messages.get("transfer.message.link") + "\n\n"
                    + acceptUrl + "\n\n"
                    + Messages.get("transfer.message.deadline") + "\n\n"
                    + Messages.get("transfer.message.thank");

            email.setFrom(Config.getEmailFromSmtp(), pt.sender.name);
            email.addTo(Config.getEmailFromSmtp(), "Yobi");

            User to = User.findByLoginId(pt.destination);
            if (!to.isAnonymous()) {
                email.addBcc(to.email, to.name);
            }

            Organization org = Organization.findByName(pt.destination);
            if (org != null) {
                List<OrganizationUser> admins = OrganizationUser.findAdminsOf(org);
                for(OrganizationUser admin : admins) {
                    email.addBcc(admin.user.email, admin.user.name);
                }
            }

            email.setSubject(String.format("[%s] @%s wants to transfer project", pt.project.name, pt.sender.loginId));
            email.setHtmlMsg(Markdown.render(message));
            email.setTextMsg(message);
            email.setCharset("utf-8");
            email.addHeader("References", "<" + acceptUrl + "@" + Config.getHostname() + ">");
            email.setSentDate(pt.requested);
            Mailer.send(email);
            String escapedTitle = email.getSubject().replace("\"", "\\\"");
            String logEntry = String.format("\"%s\" %s", escapedTitle, email.getBccAddresses());
            play.Logger.of("mail").info(logEntry);
        } catch (Exception e) {
            Logger.warn("Failed to send a notification: " + email + "\n" + ExceptionUtils.getStackTrace(e));
        }
    }

    private static void addCodeCommenters(String commitId, Long projectId, List<User> userList) {
        Project project = Project.find.byId(projectId);

        if (RepositoryService.VCS_GIT.equals(project.vcs)) {
            List<ReviewComment> comments = ReviewComment.find
                    .fetch("thread")
                    .where()
                    .eq("thread.commitId",commitId)
                    .eq("thread.project", project)
                    .eq("thread.pullRequest", null).findList();

            for (ReviewComment comment : comments) {
                User commentAuthor = User.findByLoginId(comment.author.loginId);
                if (userList.contains(commentAuthor)) {
                    userList.remove(commentAuthor);
                }
                userList.add(commentAuthor);
            }
        } else {
            List<CommitComment> comments = CommitComment.find.where().eq("commitId",
                    commitId).eq("project.id", projectId).findList();

            for (CommitComment codeComment : comments) {
                User commentAuthor = User.findByLoginId(codeComment.authorLoginId);
                if (userList.contains(commentAuthor)) {
                    userList.remove(commentAuthor);
                }
                userList.add(commentAuthor);
            }
        }

        Collections.reverse(userList);
    }

    private static void addCommitAuthor(Commit commit, List<User> userList) {
        if (!commit.getAuthor().isAnonymous() && !userList.contains(commit.getAuthor())) {
            userList.add(commit.getAuthor());
        }

        //fallback: additional search by email id
        if (commit.getAuthorEmail() != null) {
            User authorByEmail = User.findByLoginId(commit.getAuthorEmail().substring(0, commit.getAuthorEmail().lastIndexOf("@")));
            if (!authorByEmail.isAnonymous() && !userList.contains(authorByEmail)) {
                userList.add(authorByEmail);
            }
        }
    }

    private static void collectAuthorAndCommenter(Project project, Long number, List<User> userList, String resourceType) {
        AbstractPosting posting;
        switch (ResourceType.getValue(resourceType)) {
            case ISSUE_POST:
                posting = AbstractPosting.findByNumber(Issue.finder, project, number);
                break;
            case BOARD_POST:
                posting = AbstractPosting.findByNumber(Posting.finder, project, number);
                break;
            default:
                return;
        }

        if (posting != null) {
            for (Comment comment: posting.getComments()) {
                User commentUser = User.findByLoginId(comment.authorLoginId);
                if (userList.contains(commentUser)) {
                    userList.remove(commentUser);
                }
                userList.add(commentUser);
            }
            Collections.reverse(userList); // recent commenter first!
            User postAuthor = User.findByLoginId(posting.authorLoginId);
            if (!userList.contains(postAuthor)) {
                userList.add(postAuthor);
            }
        }
    }

    private static void collectedUsersToMentionList(List<Map<String, String>> users, List<User> userList) {
        for (User user: userList) {
            Map<String, String> projectUserMap = new HashMap<>();
            if (user != null && !user.loginId.equals(Constants.ADMIN_LOGIN_ID)) {
                projectUserMap.put("loginid", user.loginId);
                projectUserMap.put("username", user.name);
                projectUserMap.put("name", user.name + user.loginId);
                projectUserMap.put("image", user.avatarUrl());
                users.add(projectUserMap);
            }
        }
    }

    private static void addProjectMemberList(Project project, List<User> userList) {
        for (ProjectUser projectUser: project.projectUser) {
            if (!userList.contains(projectUser.user)) {
                userList.add(projectUser.user);
            }
        }
    }

    private static void addGroupMemberList(Project project, List<User> userList) {
        if (!project.hasGroup()) {
            return;
        }

        for (OrganizationUser organizationUser : project.organization.users) {
            if (!userList.contains(organizationUser.user)) {
                userList.add(organizationUser.user);
            }
        }
    }

    @Transactional
    @With(DefaultProjectCheckAction.class)
    @IsAllowed(Operation.UPDATE)
    public static Result newMember(String ownerId, String projectName) {
        Form<User> addMemberForm = form(User.class).bindFromRequest();

        User newMember = User.findByLoginId(addMemberForm.field("loginId").value());
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        if (isErrorOnAddMemberForm(newMember, project, addMemberForm)) {
            if(HttpUtil.isJSONPreferred(request())){
                return badRequest(addMemberForm.errorsAsJson());
            }

            List<ValidationError> errors = addMemberForm.errors().get("loginId");
            flash(Constants.WARNING, errors.get(errors.size() - 1).message());
            return redirect(routes.ProjectApp.members(ownerId, projectName));
        }

        ProjectUser.assignRole(newMember.id, project.id, RoleType.MEMBER);
        project.cleanEnrolledUsers();
        NotificationEvent.afterMemberRequest(project, newMember, RequestState.ACCEPT);

        if(HttpUtil.isJSONPreferred(request())){
            return ok("{}");
        }
        return redirect(routes.ProjectApp.members(ownerId, projectName));
    }

    private static boolean isErrorOnAddMemberForm(User user, Project project, Form<User> addMemberForm) {
        if (addMemberForm.hasErrors()) {
            addMemberForm.reject("loginId", "project.members.addMember");
        } else if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.UPDATE)) {
            addMemberForm.reject("loginId", "project.member.isManager");
        } else if (user.isAnonymous()) {
            addMemberForm.reject("loginId", "project.member.notExist");
        } else if (ProjectUser.isMember(user.id, project.id)) {
            addMemberForm.reject("loginId", "project.member.alreadyMember");
        }

        return addMemberForm.hasErrors();
    }


    /**
     * Returns OK(200) with {@code location} which is represented as JSON .
     *
     * Since returning redirect response(3xx) to Ajax request causes unexpected result, this function returns OK(200) containing redirect location.
     * The client will check {@code location} and have a page move to the location.
     *
     * @param location
     * @return
     */
    private static Result okWithLocation(String location) {
        ObjectNode result = Json.newObject();
        result.put("location", location);

        return ok(result);
    }

    /**
     * @param ownerId the user login id
     * @param projectName the project name
     * @param userId
     * @return the result
     */
    @Transactional
    @With(DefaultProjectCheckAction.class)
    public static Result deleteMember(String ownerId, String projectName, Long userId) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        User deleteMember = User.find.byId(userId);

        if (!UserApp.currentUser().id.equals(userId)
                && !AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.UPDATE)) {
            return forbidden(ErrorViews.Forbidden.render("error.forbidden", project));
        }

        if (project.isOwner(deleteMember)) {
            return forbidden(ErrorViews.Forbidden.render("project.member.ownerCannotLeave", project));
        }

        ProjectUser.delete(userId, project.id);

        if (UserApp.currentUser().id.equals(userId)) {
            if (AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
                return okWithLocation(routes.ProjectApp.project(project.owner, project.name).url());
            } else {
                return okWithLocation(routes.Application.index().url());
            }
        } else {
            return okWithLocation(routes.ProjectApp.members(ownerId, projectName).url());
        }
    }

    /**
     * @param ownerId the user login id
     * @param projectName the project name
     * @param userId the user id
     * @return
     */
    @Transactional
    @IsAllowed(Operation.UPDATE)
    public static Result editMember(String ownerId, String projectName, Long userId) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);
        User editMember = User.find.byId(userId);

        if (project.isOwner(editMember)) {
            return badRequest(ErrorViews.Forbidden.render("project.member.ownerMustBeAManager", project));
        }

        ProjectUser.assignRole(userId, project.id, form(Role.class).bindFromRequest().get().id);
        return status(Http.Status.NO_CONTENT);
    }

    /**
     * @param query the query
     * @param pageNum the page num
     * @return
     */
    public static Result projects(String query, int pageNum) {
        String prefer = HttpUtil.getPreferType(request(), HTML, JSON);
        if (prefer == null) {
            return status(Http.Status.NOT_ACCEPTABLE);
        }

        response().setHeader("Vary", "Accept");

        if (prefer.equals(JSON)) {
            return getProjectsToJSON(query);
        } else {
            return getPagingProjects(query, pageNum);
        }
    }

    private static Result getPagingProjects(String query, int pageNum) {
        ExpressionList<Project> el = createProjectSearchExpressionList(query);

        Set<Long> labelIds = LabelSearchUtil.getLabelIds(request());
        if (CollectionUtils.isNotEmpty(labelIds)) {
            el.add(LabelSearchUtil.createLabelSearchExpression(el.query(), labelIds));
        }

        el.orderBy("createdDate desc");
        Page<Project> projects = el.findPagingList(PROJECT_COUNT_PER_PAGE).getPage(pageNum - 1);

        return ok(views.html.project.list.render("title.projectList", projects, query));
    }

    private static Result getProjectsToJSON(String query) {
        ExpressionList<Project> el = createProjectSearchExpressionList(query);

        int total = el.findRowCount();
        if (total > MAX_FETCH_PROJECTS) {
            el.setMaxRows(MAX_FETCH_PROJECTS);
            response().setHeader("Content-Range", "items " + MAX_FETCH_PROJECTS + "/" + total);
        }

        List<String> projectNames = new ArrayList<>();
        for (Project project: el.findList()) {
            projectNames.add(project.owner + "/" + project.name);
        }

        return ok(toJson(projectNames));
    }

    private static ExpressionList<Project> createProjectSearchExpressionList(String query) {
        ExpressionList<Project> el = Project.find.where();

        if (StringUtils.isNotBlank(query)) {
            Junction<Project> junction = el.disjunction();
            junction.icontains("owner", query)
                    .icontains("name", query)
                    .icontains("overview", query);
            List<Object> ids = Project.find.where().icontains("labels.name", query).findIds();
            if (!ids.isEmpty()) {
                junction.idIn(ids);
            }
            junction.endJunction();
        }

        if (!UserApp.currentUser().isSiteManager()) {
            el.eq("projectScope", ProjectScope.PUBLIC);
        }

        return el;
    }

    /**
     * @param ownerId the owner login id
     * @param projectName the project name
     * @return
     */
    @IsAllowed(Operation.READ)
    public static Result labels(String ownerId, String projectName) {
        if (!request().accepts("application/json")) {
            return status(Http.Status.NOT_ACCEPTABLE);
        }

        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        Map<Long, Map<String, String>> labels = new HashMap<>();
        for (Label label: project.labels) {
            labels.put(label.id, convertToMap(label));
        }

        return ok(toJson(labels));
    }

    /**
     * convert from some part of {@link models.Label} to {@link java.util.Map}
     * @param label {@link models.Label} object
     * @return label's map data
     */
    private static Map<String, String> convertToMap(Label label) {
        Map<String, String> tagMap = new HashMap<>();
        tagMap.put("category", label.category);
        tagMap.put("name", label.name);
        return tagMap;
    }

    /**
     * @param ownerId the owner name
     * @param projectName the project name
     * @return the result
     */
    @Transactional
    @With(DefaultProjectCheckAction.class)
    public static Result attachLabel(String ownerId, String projectName) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        if (!AccessControl.isAllowed(UserApp.currentUser(), project.labelsAsResource(), Operation.UPDATE)) {
            return forbidden(ErrorViews.Forbidden.render("error.forbidden", project));
        }

        // Get category and name from the request. Return 400 Bad Request if name is not given.
        Map<String, String[]> data = request().body().asFormUrlEncoded();
        String category = HttpUtil.getFirstValueFromQuery(data, "category");
        String name = HttpUtil.getFirstValueFromQuery(data, "name");
        if (StringUtils.isEmpty(name)) {
            // A label must have its name.
            return badRequest(ErrorViews.BadRequest.render("Label name is missing.", project));
        }

        Label label = Label.find
            .where().eq("category", category).eq("name", name).findUnique();

        boolean isCreated = false;
        if (label == null) {
            // Create new label if there is no label which has the given name.
            label = new Label(category, name);
            label.projects.add(project);
            label.save();
            isCreated = true;
        }

        Boolean isAttached = project.attachLabel(label);

        if (!isCreated && !isAttached) {
            // Something is wrong. This case is not possible.
            play.Logger.warn(
                    "A label '" + label + "' is created but failed to attach to project '" + project + "'.");
        }

        if (isAttached) {
            // Return the attached label. The return type is Map<Long, Map<String, String>>
            // even if there is only one label, to unify the return type with
            // ProjectApp.labels().
            Map<Long, Map<String, String>> labels = new HashMap<>();
            labels.put(label.id, convertToMap(label));

            if (isCreated) {
                return created(toJson(labels));
            } else {
                return ok(toJson(labels));
            }
        } else {
            // Return 204 No Content if the label is already attached.
            return status(Http.Status.NO_CONTENT);
        }
    }

    /**
     * @param ownerId the owner name
     * @param projectName the project name
     * @param id the id
     * @return the result
     */
    @Transactional
    @With(DefaultProjectCheckAction.class)
    public static Result detachLabel(String ownerId, String projectName, Long id) {
        Project project = Project.findByOwnerAndProjectName(ownerId, projectName);

        if (!AccessControl.isAllowed(UserApp.currentUser(), project.labelsAsResource(), Operation.UPDATE)) {
            return forbidden(ErrorViews.Forbidden.render("error.forbidden", project));
        }

        // _method must be 'delete'
        Map<String, String[]> data = request().body().asFormUrlEncoded();
        if (!HttpUtil.getFirstValueFromQuery(data, "_method").toLowerCase()
                .equals("delete")) {
            return badRequest(ErrorViews.BadRequest.render("_method must be 'delete'.", project));
        }

        Label tag = Label.find.byId(id);

        if (tag == null) {
            return notFound(ErrorViews.NotFound.render("error.notfound"));
        }

        project.detachLabel(tag);

        return status(Http.Status.NO_CONTENT);
    }

    @Transactional
    @With(AnonymousCheckAction.class)
    @IsAllowed(Operation.DELETE)
    public static Result deletePushedBranch(String ownerId, String projectName, Long id) {
        PushedBranch pushedBranch = PushedBranch.find.byId(id);
        if (pushedBranch != null) {
            pushedBranch.delete();
        }
        return ok();
    }
}
TOP

Related Classes of controllers.ProjectApp

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.