[Notice] Announcing the End of Demo Server [Read me]
unknown 2012-08-14
fix a bug related to adding comment w/o contents and add isAuthorEditable option on project setting and apply it to issue
@8033200134f33da2423decdeab6927e2047c2a8b
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -5,36 +5,29 @@
 package controllers;
 
 import java.io.File;
-import java.util.List;
-
-import com.avaje.ebean.Page;
 
 import models.Issue;
 import models.IssueComment;
-import models.Milestone;
-import models.Post;
-import models.User;
 import models.Project;
 import models.enumeration.Direction;
 import models.enumeration.IssueState;
-import models.enumeration.PermissionOperation;
-import models.enumeration.PermissionResource;
 import models.enumeration.StateType;
 import models.support.SearchCondition;
-import utils.RoleCheck;
-import play.Logger;
 import play.data.Form;
 import play.mvc.Controller;
 import play.mvc.Http.MultipartFormData;
 import play.mvc.Http.MultipartFormData.FilePart;
 import play.mvc.Http.Request;
 import play.mvc.Result;
+import utils.Constants;
 import views.html.issue.editIssue;
 import views.html.issue.issue;
+import views.html.issue.issueError;
 import views.html.issue.issueList;
 import views.html.issue.newIssue;
 import views.html.issue.notExistingPage;
-import views.html.issue.issueError;
+
+import com.avaje.ebean.Page;
 
 public class IssueApp extends Controller {
 
@@ -132,14 +125,15 @@
                 .bindFromRequest();
         Project project = ProjectApp.getProject(userName, projectName);
         if (commentForm.hasErrors()) {
-            return TODO;
+            flash(Constants.WARNING, "board.comment.empty");
+            return redirect(routes.IssueApp.issue(project.owner, project.name, issueId));
         } else {
             IssueComment comment = commentForm.get();
             comment.issue = Issue.findById(issueId);
             comment.authorId = UserApp.currentUser().id;
             comment.filePath = saveFile(request());
             IssueComment.create(comment);
-//            Issue.updateNumOfComments(issueId);
+            Issue.updateNumOfComments(issueId);
             return redirect(routes.IssueApp.issue(project.owner, project.name, issueId));
         }
     }
@@ -163,7 +157,6 @@
                 issueParam.commentedCheck, issueParam.fileAttachedCheck);
         Issue.excelSave(issues.getList(), project.name + "_" + stateType + "_filter_"
                 + issueParam.filter + "_milestone_" + issueParam.milestone);
-
         return ok(issueList.render("title.issueList", issues, issueParam, project));
     }
 
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -1,26 +1,6 @@
 package models;
 
-import com.avaje.ebean.Page;
-import jxl.Workbook;
-import jxl.format.Alignment;
-import jxl.format.Border;
-import jxl.format.BorderLineStyle;
-import jxl.format.Colour;
-import jxl.format.*;
-import jxl.write.*;
-import models.enumeration.*;
-import models.support.FinderTemplate;
-import models.support.OrderParams;
-import models.support.SearchParams;
-import play.data.format.Formats;
-import play.data.validation.Constraints;
-import play.db.ebean.Model;
-import utils.JodaDateUtil;
-import utils.RoleCheck;
-
-import javax.persistence.*;
-
-import org.joda.time.Duration;
+import static models.enumeration.IssueState.ASSIGNED;
 
 import java.io.File;
 import java.io.IOException;
@@ -28,7 +8,41 @@
 import java.util.Date;
 import java.util.List;
 
-import static models.enumeration.IssueState.ASSIGNED;
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+
+import jxl.Workbook;
+import jxl.format.Alignment;
+import jxl.format.Border;
+import jxl.format.BorderLineStyle;
+import jxl.format.Colour;
+import jxl.format.ScriptStyle;
+import jxl.format.UnderlineStyle;
+import jxl.write.Label;
+import jxl.write.WritableCellFormat;
+import jxl.write.WritableFont;
+import jxl.write.WritableSheet;
+import jxl.write.WritableWorkbook;
+import jxl.write.WriteException;
+import models.enumeration.Direction;
+import models.enumeration.IssueState;
+import models.enumeration.Matching;
+import models.enumeration.StateType;
+import models.support.FinderTemplate;
+import models.support.OrderParams;
+import models.support.SearchParams;
+
+import org.joda.time.Duration;
+
+import play.data.format.Formats;
+import play.data.validation.Constraints;
+import play.db.ebean.Model;
+import utils.JodaDateUtil;
+
+import com.avaje.ebean.Page;
 
 /**
  * @author Taehyun Park
@@ -120,7 +134,7 @@
     public int numOfComments;
 
     public Issue() {
-        this.date = JodaDateUtil.today();
+        this.date = JodaDateUtil.now();
     }
 
     /**
@@ -237,7 +251,7 @@
     public static Page<Issue> findFilteredIssues(String projectName, String filter,
             StateType state, boolean commentedCheck, boolean fileAttachedCheck) {
         return findIssues(projectName, FIRST_PAGE_NUMBER, state, DEFAULT_SORTER, Direction.DESC,
-            filter, null, commentedCheck, fileAttachedCheck);
+                filter, null, commentedCheck, fileAttachedCheck);
     }
 
     /**
@@ -249,7 +263,7 @@
      */
     public static Page<Issue> findCommentedIssues(String projectName, String filter) {
         return findIssues(projectName, FIRST_PAGE_NUMBER, StateType.ALL, DEFAULT_SORTER,
-            Direction.DESC, filter, null, true, false);
+                Direction.DESC, filter, null, true, false);
     }
 
     /**
@@ -308,7 +322,7 @@
         SearchParams searchParams = new SearchParams().add("project.name", projectName,
                 Matching.EQUALS);
 
-        if(filter != null && !filter.isEmpty()) {
+        if (filter != null && !filter.isEmpty()) {
             searchParams.add("title", filter, Matching.CONTAINS);
         }
         if (milestoneId != null) {
@@ -333,7 +347,7 @@
                 break;
             }
         return FinderTemplate.getPage(orderParams, searchParams, find, ISSUE_COUNT_PER_PAGE,
-            pageNumber);
+                pageNumber);
     }
 
     public static Long findAssigneeIdByIssueId(String projectName, Long issueId) {
@@ -460,12 +474,13 @@
         return (uId != null ? User.findNameById(uId) : TO_BE_ASSIGNED);
     }
 
+    // FIXME 이것이 없이 테스트는 잘 작동하나, view에서 댓글이 달린 이슈들을 필터링하는 라디오버튼을 작동시에 이 메쏘드에서
+    // 시행하는 동기화 작업 없이는 작동을 하지 않는다.
+
     /**
-     * comment가 delete되거나 create될 때, numOfComment와 comment.size()를 동기화 시켜준다. 현재는
-     * 이것이 없이도 동작하므로, 추후에 제거하겠음.
+     * comment가 delete되거나 create될 때, numOfComment와 comment.size()를 동기화 시켜준다.
      * 
      * @param id
-     *            Issue Id
      */
     public static void updateNumOfComments(Long id) {
 
@@ -477,20 +492,15 @@
     public boolean isAuthor(Long currentUserId, Long objectId, String projectName) {
 
         boolean authorIs;
-        if (currentUserId == findById(objectId).reporterId
-                || RoleCheck.roleCheck(currentUserId, project.id,
-                        PermissionResource.PROJECT,
-                        PermissionOperation.WRITE)) {
+        if (currentUserId == findById(objectId).reporterId) {
             authorIs = true;
         } else {
             authorIs = false;
         }
-
         return authorIs;
-
     }
-    
-    public Duration ago(){
+
+    public Duration ago() {
         return JodaDateUtil.ago(this.date);
     }
 }
app/models/IssueComment.java
--- app/models/IssueComment.java
+++ app/models/IssueComment.java
@@ -4,12 +4,7 @@
 
 package models;
 
-import models.enumeration.PermissionOperation;
-import models.enumeration.PermissionResource;
-import play.data.validation.Constraints;
-import play.db.ebean.Model;
-import utils.JodaDateUtil;
-import utils.RoleCheck;
+import java.util.Date;
 
 import javax.persistence.Entity;
 import javax.persistence.Id;
@@ -17,7 +12,9 @@
 
 import org.joda.time.Duration;
 
-import java.util.Date;
+import play.data.validation.Constraints;
+import play.db.ebean.Model;
+import utils.JodaDateUtil;
 
 @Entity
 public class IssueComment extends Model {
@@ -41,7 +38,7 @@
     public Issue issue;
 
     public IssueComment() {
-        date = JodaDateUtil.today();
+        date = JodaDateUtil.now();
     }
 
     public static IssueComment findById(Long id) {
@@ -60,19 +57,16 @@
     public static void delete(Long id) {
         find.byId(id).delete();
     }
+    
+    public boolean isAuthor(Long currentUserId, Long objectId, String projectName) {
 
-    public boolean isAuthor(Long currentUserId, String projectName) {
-
-        Project project = Project.findByName(projectName);
-        return checkAuthor(currentUserId, project.id);
-
-    }
-
-    private boolean checkAuthor(Long currentUserId, Long projectId) {
-        return currentUserId.equals(this.id)
-            || RoleCheck.roleCheck(currentUserId, projectId,
-            PermissionResource.PROJECT,
-            PermissionOperation.WRITE);
+        boolean authorIs;
+        if (currentUserId == findById(objectId).authorId) {
+            authorIs = true;
+        } else {
+            authorIs = false;
+        }
+        return authorIs;
     }
     
     public Duration ago(){
app/models/Project.java
--- app/models/Project.java
+++ app/models/Project.java
@@ -29,11 +29,13 @@
     public String name;
     
     public String overview;
-    public boolean share_option;
     public String vcs;
     public String url;
     public String logoPath;
     public String owner;
+
+    public boolean share_option;
+    public boolean isAuthorEditable;
     
     @OneToMany(mappedBy = "project", cascade = CascadeType.ALL)
     public List<Issue> issues;
app/views/issue/editIssue.scala.html
--- app/views/issue/editIssue.scala.html
+++ app/views/issue/editIssue.scala.html
@@ -3,11 +3,14 @@
 @implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) } 
 
 @main(Messages(title), project) {
-
-    @form( action = routes.IssueApp.updateIssue(project.owner, project.name, issueId), 
+<!-- FIXEME 
+(1) 수정했을 시에, 수정시간으로 업데이트 되는 것이 아니라, 본 글이 원래 작성된 시간을 그대로 가지고 있을 것!
+(2) 매니저 및 멤버가 해당 이슈를 수정시에, 작성자가 변하지 않아야 함.
+--> 
+    @form(action = routes.IssueApp.updateIssue(project.owner, project.name, issueId), 
             'enctype -> "multipart/form-data",
             'class -> "form-horizontal"){
-            <fieldset>
+    <fieldset>
     	@inputText(
     	   issueForm("title"), 
     	   '_showConstraints -> false, 
@@ -21,10 +24,11 @@
     	@inputFile(
     	   issueForm("filePath"))
     	@if(issueForm("filePath") != null) {<p>@issueForm("filePath")</p>}
-    	</fieldset>
+    </fieldset>
     	</br></br>
+    	
     	@if(ProjectUser.isMember(session.get("userId").toLong, project.id)){
-    	<fieldset>
+    <fieldset>
             <div class="well">
                 <legend><b>@Messages("issue.new.detailInfo")</b></legend>
                 <div class="well form-inline">
app/views/issue/issue.scala.html
--- app/views/issue/issue.scala.html
+++ app/views/issue/issue.scala.html
@@ -5,8 +5,10 @@
 <div class="row">
 	<div class="span2 offset10">
 		<div id="board_btn_panel">
+		@if(issue.isAuthor(session.get("userId").toLong, issue.id, project.name) && project.isAuthorEditable){
 			<a class="btn btn-primary" href="@routes.IssueApp.editIssue(project.owner, project.name, issue.id)">@Messages("button.edit")</a> <a
 				data-toggle="modal" href="#deleteIssueConfirm" class="btn">@Messages("button.delete")</a>
+		} 
 			<a class="btn" href="@routes.IssueApp.issues(project.owner, project.name,"open")">@Messages("button.list")</a>
 		</div>
 	</div>
@@ -57,11 +59,11 @@
 				
 		</div>
 		
-		@if(ProjectUser.isMember(session.get("userId").toLong, project.id) || comment.isAuthor(session.get("userId").toLong, project.name)){
+		@if(comment.isAuthor(session.get("userId").toLong, comment.id, project.name)){
 		<div class="pull-right"> 
 		      <a data-toggle="modal" href='#deleteCommentConfirm'><i class="icon-remove"> </i>
 		</div>
-		
+		}
 		
 		<div class="modal hide" id="deleteCommentConfirm">
 		    <div class="modal-header">
@@ -84,7 +86,7 @@
 				class="icon-download"> </i> @comment.filePath</a>
 		</p>
 		  }
-		}
+		
 	</div>
 </div>
 }
app/views/issue/issueError.scala.html
--- app/views/issue/issueError.scala.html
+++ app/views/issue/issueError.scala.html
@@ -2,7 +2,7 @@
 
 @main("Warnning!", project){
 	<div class="alert alert-error">
-	@message
+	@Messages(message)
 	</div>
     <a class="btn" href="@returnURL">돌아가기</a>
 }
(No newline at end of file)
app/views/issue/issueList.scala.html
--- app/views/issue/issueList.scala.html
+++ app/views/issue/issueList.scala.html
@@ -64,7 +64,7 @@
 <!--FIXME view 전문가님이 아래의 if-else문 안의 중복된 코드를 업그레이드 해주시길 바랍니다. -->
 		              	<label class="checkbox inline">
 		                	@if(param.commentedCheck==true){
-		                	     <input type="checkbox" name="commentedCheck" id="commentCheckId" value="1" checked="checked"> @Messages("checkbox.commented")
+		                	     <input type="checkbox" name="commentedCheck" id="commentCheckId" value="1" checked> @Messages("checkbox.commented")
 		                	} else {
 		                	     <input type="checkbox" name="commentedCheck" id="commentCheckId" value="1"> @Messages("checkbox.commented")
 		                	}
@@ -129,7 +129,7 @@
 </div>
 }
 	<script>
-		$("#checkboxForm").bind("change check", function(){
+		$("#checkboxForm").bind("change check uncheck", function(){
 				if($("#commentCheckId").is(":checked") && $("#fileAttachedCheckId").is(":checked")) {
 					return $("form[id='checkboxForm']").submit();
 				}
@@ -165,7 +165,7 @@
 	    $("pagination").click(function(){
 			  
 			});
-		$("select[id='milestone']").change(function(){
+		$("select[id='milestone']").bind("change", function(){
 				$(this).parent("form[id='searchForm']").submit();
 			});
 	</script>
app/views/project/newProject.scala.html
--- app/views/project/newProject.scala.html
+++ app/views/project/newProject.scala.html
@@ -42,8 +42,11 @@
                     options = options("false"->Messages("project.shareOption.private"), "true"->Messages("project.shareOption.public")),
                     '_label -> Messages("project.shareOption")
                 )
-                
-
+                 @inputRadioGroup(
+                    newProjectForm("isAuthorEditable"),
+                    options = options("false"->Messages("project.isAuthorEditable.off"), "true"->Messages("project.isAuthorEditable.on")),
+                    '_label -> Messages("project.isAuthorEditable")
+                )
                 @select(
                     newProjectForm("vcs"),
                     options=options("GIT"->"GIT", "Subversion"->"Subversion"), 
app/views/project/setting.scala.html
--- app/views/project/setting.scala.html
+++ app/views/project/setting.scala.html
@@ -59,6 +59,11 @@
                     options = options("false"->Messages("project.shareOption.private"), "true"->Messages("project.shareOption.public")),
                     '_label -> Messages("project.shareOption")
                 )
+                @inputRadioGroup(
+                    projectForm("isAuthorEditable"),
+                    options = options("false"->Messages("project.isAuthorEditable.off"), "true"->Messages("project.isAuthorEditable.on")),
+                    '_label -> Messages("project.isAuthorEditable")
+                )
                 
                 @inputText(
                     projectForm("url"),
conf/initial-data.yml
--- conf/initial-data.yml
+++ conf/initial-data.yml
@@ -35,6 +35,7 @@
         vcs:            GIT
         url:            http://localhost:9000/nForge4java
         owner:          hobi
+        isAuthorEditable: true
     - !!models.Project
         name:           Jindo
         overview:       Jindo는 NHN에서 제작한 Javascript Library 이다.
@@ -42,6 +43,7 @@
         vcs:            Subversion
         url:            http://localhost:9000/Jindo
         owner:          k16wire
+        isAuthorEditable: false
     - !!models.Project
         name:           CUBRID
         overview:       CUBRID는 엔터프라이즈급 오픈 소스 DBMS로서, 인터넷 서비스에 최적화된 DBMS를 지향하고 있습니다.
conf/messages.en
--- conf/messages.en
+++ conf/messages.en
@@ -124,6 +124,7 @@
 issue.new.result.diagnosisResult.works4me= Works for Me
 issue.noAssignee = TBA
 issue.delete.window = Delete Issue
+issue.comment.delete.window = Delete Issue Comment
 issue.new.selectDefault.milestone = --Select a Milestone--
 issue.new.selectDefault.issueType = --Select a Type of Issue--
 issue.new.selectDefault.assignee = --Select an Assignee --
@@ -157,7 +158,10 @@
 project.shareOption = Share Option
 project.shareOption.public = Public
 project.shareOption.private = Private
-project.shareOption.private.description = Basic information (name, description, etc.) is exposed to all users, even thought it is a private project. 
+project.shareOption.private.description = Basic information (name, description, etc.) is exposed to all users, even thought it is a private project.
+project.isAuthorEditable = Can the author do edit/delete his post/issue?
+project.isAuthorEditable.on =  Yes
+project.isAuthorEditable.off = No 
 project.vcs = Repository
 project.new.agreement = I read nForge4's user agreement and accept it.
 project.new.agreement.alert = You should agree with the user agreement.
@@ -226,4 +230,4 @@
 board.post.new = New Post
 board.post.modify = Edit Post
 board.comment.empty = you have to write contents.
-board.notAuthor = You are not author!
(No newline at end of file)
+board.notAuthor = You are not author!
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -124,6 +124,7 @@
 issue.new.result.diagnosisResult.works4me= 잘동작함
 issue.noAssignee = 미배정
 issue.delete.window = 이슈 삭제
+issue.comment.delete.window = 이슈 댓글 삭제
 issue.new.selectDefault.milestone = --마일스톤 선택--
 issue.new.selectDefault.issueType = --유형 선택--
 issue.new.selectDefault.assignee = --담당자 선택--
@@ -158,6 +159,9 @@
 project.shareOption.public = 공개
 project.shareOption.private = 비공개
 project.shareOption.private.description = 비공개 프로젝트도 프로젝트 이름,설명,로고 등은 모든 사용자가 볼수 있습니다.
+project.isAuthorEditable = 작성자 수정/삭제 권한 설정
+project.isAuthorEditable.on =  가능
+project.isAuthorEditable.off = 불가능
 project.vcs = 코드관리시스템
 project.new.agreement = 본인은 약관에 대한 안내를 읽었으며 동의합니다.
 project.new.agreement.alert = 이용 악관에 동의하여야 합니다.
@@ -226,4 +230,4 @@
 board.post.new = 새 게시물
 board.post.modify = 게시물 수정
 board.comment.empty = 댓글내용은 반드시 쓰셔야 합니다.
-board.notAuthor = 글쓴이가 아닙니다.
(No newline at end of file)
+board.notAuthor = 글쓴이가 아닙니다.
Add a comment
List