├── src ├── test │ ├── jmeter │ │ └── jmeter.properties │ ├── resources │ │ ├── import │ │ │ ├── worktile_tasks_empty.json │ │ │ ├── worktile_tasks_invalid_file_format.jpg │ │ │ ├── worktile_tasks.json │ │ │ ├── worktile_tasks_format_error.json │ │ │ └── worktile_tasks │ │ ├── avatars │ │ │ ├── big-avatar.jpg │ │ │ ├── new-avatar.jpg │ │ │ ├── new-avatar.png │ │ │ └── thiki-upload-test-file.jpg │ │ └── application-local-test.properties │ └── java │ │ └── org │ │ └── thiki │ │ └── kanban │ │ ├── IdentificationTestBase.java │ │ ├── foundation │ │ ├── common │ │ │ └── VerificationCodeServiceTest.java │ │ ├── aspect │ │ │ └── ValidateAspectTest.java │ │ └── mail │ │ │ └── MailServiceTest.java │ │ ├── AuthenticationTestBase.java │ │ ├── TestContextConfiguration.java │ │ ├── publickey │ │ └── PublicKeyControllerTest.java │ │ ├── DBPreparation.java │ │ ├── comment │ │ └── CommentServiceTest.java │ │ ├── notification │ │ └── NotificationServiceTest.java │ │ ├── cardTags │ │ └── CardTagsServiceTest.java │ │ └── entrance │ │ └── EntranceControllerTest.java └── main │ ├── resources │ ├── avatar │ │ └── default-avatar.png │ ├── i18N │ │ ├── message.properties │ │ └── message_zh_CN.properties │ ├── rsakey.pub │ ├── config │ │ ├── application-local-ming.properties │ │ ├── application-local-zz.properties │ │ ├── application-local-tao.properties │ │ ├── application-local.properties │ │ └── application-local-xiong.properties │ ├── mail │ │ └── mail.properties │ ├── roles-resources-mapping.yml │ ├── org │ │ └── thiki │ │ │ └── kanban │ │ │ └── mybatis │ │ │ ├── registrationMapper.xml │ │ │ ├── verificationMapper.xml │ │ │ ├── cardsTagsMapper.xml │ │ │ ├── activityMapper.xml │ │ │ ├── commentMapper.xml │ │ │ ├── riskMapper.xml │ │ │ ├── assignmentMapper.xml │ │ │ ├── invitationMapper.xml │ │ │ ├── notificationMapper.xml │ │ │ ├── pagesMapper.xml │ │ │ └── boardsMapper.xml │ ├── scripts │ │ ├── deploy.sh │ │ ├── db │ │ │ ├── db_install_sit.sh │ │ │ └── init_db_properties.sh │ │ └── killServer.sh │ ├── rsakey.pem │ ├── application.yml │ └── quality │ │ ├── findbugs │ │ └── findbugs-filter.xml │ │ ├── checkstyle │ │ └── suppressions.xml │ │ └── pmd │ │ └── pmd-ruleset.xml │ └── java │ └── org │ └── thiki │ └── kanban │ ├── foundation │ ├── security │ │ ├── authentication │ │ │ ├── Authentication.java │ │ │ ├── AuthenticationResult.java │ │ │ ├── MethodType.java │ │ │ ├── MatchResult.java │ │ │ ├── Role.java │ │ │ ├── rolesAuthenticationProviders │ │ │ │ ├── PersonalAuthenticationProvider.java │ │ │ │ └── VisitorAuthenticationProvider.java │ │ │ ├── AuthenticationProviderFactory.java │ │ │ └── RolesResources.java │ │ ├── identification │ │ │ ├── token │ │ │ │ ├── AuthenticationToken.java │ │ │ │ └── IdentityResult.java │ │ │ └── md5 │ │ │ │ └── MD5Service.java │ │ └── Constants.java │ ├── aspect │ │ └── ValidateParams.java │ ├── common │ │ ├── HTTPUtil.java │ │ ├── SequenceNumber.java │ │ ├── VerificationCodeService.java │ │ ├── date │ │ │ ├── Week.java │ │ │ ├── DateStyle.java │ │ │ └── DateUtil.java │ │ ├── Response.java │ │ ├── MapUtil.java │ │ └── RestResource.java │ ├── exception │ │ ├── ExceptionCode.java │ │ ├── InvalidParamsException.java │ │ ├── ResourceNotFoundException.java │ │ ├── UnauthorisedException.java │ │ ├── AuthenticationException.java │ │ └── BusinessException.java │ ├── annotations │ │ ├── Scenario.java │ │ └── Domain.java │ ├── configuration │ │ ├── ApplicationProperties.java │ │ ├── ApplicationContextProvider.java │ │ └── DataBaseDefaultConfig.java │ ├── logback │ │ ├── ThreadIdConverter.java │ │ └── SessionInterceptor.java │ ├── redis │ │ ├── RedisScheduledTasks.java │ │ └── ApplicationStartup.java │ ├── application │ │ └── DomainOrder.java │ ├── hateoas │ │ └── Action.java │ └── calendar │ │ └── CalendarService.java │ ├── user │ ├── registration │ │ ├── RegistrationPersistence.java │ │ ├── RegistrationController.java │ │ ├── RegistrationResource.java │ │ ├── Registration.java │ │ └── RegistrationService.java │ ├── profile │ │ └── StorageProperties.java │ ├── UsersPersistence.java │ ├── UsersCodes.java │ ├── Profile.java │ ├── ProfileResource.java │ └── AvatarResource.java │ ├── worktile │ ├── domains │ │ ├── Entry.java │ │ ├── Todo.java │ │ └── Task.java │ ├── WorktileCodes.java │ └── WorktileController.java │ ├── publickey │ ├── PublicKey.java │ ├── PublicKeyService.java │ ├── PublicKeyController.java │ └── PublicKeyResource.java │ ├── projects │ ├── members │ │ ├── Member.java │ │ ├── MembersCodes.java │ │ ├── MembersPersistence.java │ │ └── MembersResource.java │ ├── invitation │ │ ├── InvitationPersistence.java │ │ ├── InvitationCodes.java │ │ ├── InvitationEmail.java │ │ ├── InvitationResource.java │ │ └── Invitation.java │ └── project │ │ ├── ProjectsPersistence.java │ │ ├── ProjectCodes.java │ │ ├── ProjectsResource.java │ │ ├── Project.java │ │ └── ProjectResource.java │ ├── activity │ ├── ActivityPersistence.java │ └── ActivityType.java │ ├── cardTags │ ├── CardTagPersistence.java │ ├── CardTagsCodes.java │ ├── CardTagsController.java │ └── CardTagResource.java │ ├── risk │ ├── RiskPersistence.java │ ├── RiskCodes.java │ ├── RiskService.java │ └── RiskResources.java │ ├── comment │ ├── CommentPersistence.java │ └── CommentCodes.java │ ├── notification │ ├── NotificationPersistence.java │ ├── NotificationType.java │ ├── UnreadNotificationsResource.java │ ├── NotificationResource.java │ └── NotificationsResource.java │ ├── verification │ ├── VerificationPersistence.java │ ├── VerificationCodes.java │ └── VerificationMail.java │ ├── password │ ├── passwordRetrieval │ │ ├── VerificationCodeEmailData.java │ │ └── PasswordRetrievalResource.java │ ├── password │ │ ├── PasswordCodes.java │ │ ├── PasswordResource.java │ │ └── PasswordPersistence.java │ └── passwordReset │ │ ├── PasswordResetApplication.java │ │ ├── PasswordReset.java │ │ └── PasswordResetResource.java │ ├── assignment │ ├── AssignmentPersistence.java │ ├── AssignmentCodes.java │ └── AssignmentController.java │ ├── board │ ├── BoardsPersistence.java │ ├── snapshot │ │ └── BoardsSnapshotController.java │ └── BoardCodes.java │ ├── page │ ├── PageCodes.java │ └── PagesPersistence.java │ ├── login │ ├── Identification.java │ ├── LoginCodes.java │ └── LoginController.java │ ├── sprint │ ├── SprintPersistence.java │ ├── SprintCodes.java │ └── SprintResource.java │ ├── Application.java │ ├── tag │ ├── TagPersistence.java │ ├── TagsCodes.java │ └── TagResource.java │ ├── entrance │ ├── EntranceController.java │ └── EntranceResource.java │ ├── card │ └── CardsPersistence.java │ ├── acceptanceCriteria │ ├── AcceptanceCriteriaCodes.java │ └── AcceptanceCriteriaPersistence.java │ ├── statistics │ └── burnDownChart │ │ ├── BurnDownChartPersistence.java │ │ └── BurnDownChartController.java │ └── stage │ ├── StagesPersistence.java │ ├── ResortStagesResource.java │ └── StageCodes.java ├── settings.gradle ├── META-INF └── MANIFEST.MF ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .travis.yml └── .gitignore /src/test/jmeter/jmeter.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'kanban' 2 | -------------------------------------------------------------------------------- /src/test/resources/import/worktile_tasks_empty.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: org.thiki.kanban.Application 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/test/resources/avatars/big-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/test/resources/avatars/big-avatar.jpg -------------------------------------------------------------------------------- /src/test/resources/avatars/new-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/test/resources/avatars/new-avatar.jpg -------------------------------------------------------------------------------- /src/test/resources/avatars/new-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/test/resources/avatars/new-avatar.png -------------------------------------------------------------------------------- /src/main/resources/avatar/default-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/main/resources/avatar/default-avatar.png -------------------------------------------------------------------------------- /src/main/resources/i18N/message.properties: -------------------------------------------------------------------------------- 1 | NotNull.stage.title=Stage title can not be null. 2 | Length.stage.title=Stage title's length is between {min} to {max}. 3 | -------------------------------------------------------------------------------- /src/test/resources/avatars/thiki-upload-test-file.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/test/resources/avatars/thiki-upload-test-file.jpg -------------------------------------------------------------------------------- /src/test/resources/import/worktile_tasks_invalid_file_format.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thiki-org/thiki-kanban-backend/HEAD/src/test/resources/import/worktile_tasks_invalid_file_format.jpg -------------------------------------------------------------------------------- /src/main/resources/rsakey.pub: -------------------------------------------------------------------------------- 1 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvt2Q1aDhixfv0VWZTEzVYmf4QQtVMSwSC1bYociaw/dgGaQY/c+bcdubcY5LrZdaj9BPJApGvEIQGnXDDIURXW8p5w+xZ6ntbb8vextGg38TD3MasCpcabb18bBsi/hiEVgSxGL4yZtR7gtwA5aTQbzDxii9j27vAVQX6ZGiG4QIDAQAB 2 | -------------------------------------------------------------------------------- /src/main/resources/i18N/message_zh_CN.properties: -------------------------------------------------------------------------------- 1 | ##验证错误message的key的书写格式如下 2 | #验证错误注解简单类名.验证对象名.字段名 3 | #验证错误注解简单类名.字段名 4 | #验证错误注解简单类名.字段类型全限定类名 5 | #验证错误注解简单类名 6 | NotNull.stage.title=Stage title can not be null. 7 | Length.stage.title=Stage title's length is between {min} to {max}. 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 20 15:37:27 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-3.2.1-all.zip 7 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local-ming.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=com.mysql.jdbc.Driver 2 | jdbc.template=jdbc:mysql://127.0.0.1:3306/thiki-kanban?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false 3 | jdbc.username=root 4 | jdbc.password=1234 5 | http.port=8089 6 | server.id=1 7 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local-zz.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=com.mysql.jdbc.Driver 2 | jdbc.template=jdbc:mysql://23.234.54.252:3306/THIKI_KANBAN_PROD?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false 3 | jdbc.username=thiki_db_prod 4 | jdbc.password=pmaldox2 5 | http.port=8095 6 | server.id=1 7 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/Authentication.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created by xubt on 21/10/2016. 7 | */ 8 | public interface Authentication { 9 | AuthenticationResult authenticate(Map pathValues, String userName); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/aspect/ValidateParams.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.aspect; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Component 8 | @Target({ElementType.METHOD}) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Documented 11 | public @interface ValidateParams { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/registration/RegistrationPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.registration; 2 | 3 | 4 | import org.springframework.stereotype.Repository; 5 | 6 | /** 7 | * Created by joeaniu on 6/21/16. 8 | */ 9 | @Repository 10 | public interface RegistrationPersistence { 11 | void register(Registration userRegistration); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/AuthenticationResult.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | /** 4 | * Created by xubt on 18/12/2016. 5 | */ 6 | public class AuthenticationResult { 7 | private boolean failed = false; 8 | 9 | public boolean isFailed() { 10 | return failed; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/mail/mail.properties: -------------------------------------------------------------------------------- 1 | #mail sender settings 2 | # for example: smtp.sina.cn 3 | mail.server=hwsmtp.exmail.qq.com 4 | #the sender mail 5 | mail.sender=git-ci@thiki.org 6 | #the sender nickname 7 | mail.nickname=\u601D\u5947\u770B\u677F\u56E2\u961F 8 | #sender mail username 9 | mail.username=git-ci@thiki.org 10 | #sender mail password 11 | mail.password=tEst.oN.1 12 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/worktile/domains/Entry.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.worktile.domains; 2 | 3 | /** 4 | * Created by xubt on 01/11/2016. 5 | */ 6 | public class Entry { 7 | private String name; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/HTTPUtil.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import org.thiki.kanban.foundation.security.Constants; 4 | 5 | /** 6 | * Created by xubt on 21/11/2016. 7 | */ 8 | public class HTTPUtil { 9 | public static boolean isLocalRequest(String remoteAddr) { 10 | return Constants.LOCAL_ADDRESS.equals(remoteAddr); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/publickey/PublicKey.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.publickey; 2 | 3 | /** 4 | * Created by xubt on 7/5/16. 5 | */ 6 | public class PublicKey { 7 | private String publicKey; 8 | 9 | public String getPublicKey() { 10 | return publicKey; 11 | } 12 | 13 | public void setPublicKey(String publicKey) { 14 | this.publicKey = publicKey; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | before_install: 3 | - chmod +x gradlew 4 | notifications: 5 | slack: 6 | on_success: always 7 | on_failure: always 8 | email: 9 | - airlink@gmail.com 10 | - btao.cn@gmail.com 11 | - 411172392@qq.com 12 | - 103831536@qq.com 13 | - 286913517@qq.com 14 | - 766191920@qq.com 15 | - 634178463@qq.com 16 | 17 | jdk: 18 | - oraclejdk8 19 | 20 | after_success: 21 | - ./gradlew jacocoTestReport coveralls 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/members/Member.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.members; 2 | 3 | import org.thiki.kanban.user.User; 4 | 5 | /** 6 | * Created by xubt on 9/10/16. 7 | */ 8 | public class Member extends User { 9 | private String projectId; 10 | 11 | public String getProjectId() { 12 | return projectId; 13 | } 14 | 15 | public void setProjectId(String projectId) { 16 | this.projectId = projectId; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/roles-resources-mapping.yml: -------------------------------------------------------------------------------- 1 | roles: 2 | - name: visitor 3 | resources: 4 | - template: /entrance 5 | GET: true 6 | - template: /publicKey 7 | GET: true 8 | - template: /passwordRetrievalApplication 9 | GET: true 10 | 11 | - name: personal 12 | resources: 13 | - template: /{userName}/boards/{boardId} 14 | GET: true 15 | POST: true 16 | DELETE: true 17 | PUT: true 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/ExceptionCode.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | public enum ExceptionCode { 4 | 5 | UNKNOWN_EX(-99), 6 | INVALID_PARAMS(400), 7 | UNAUTHORIZED(401), 8 | resourceNotFound(404), 9 | USER_EXISTS(100101); 10 | 11 | private int code; 12 | 13 | private ExceptionCode(int code) { 14 | this.code = code; 15 | } 16 | 17 | public int code() { 18 | return code; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/annotations/Scenario.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created by xubt on 6/24/16. 10 | */ 11 | @Target({ElementType.METHOD}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Scenario { 14 | String value() default ""; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/annotations/Domain.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created by xubt on 9/15/16. 10 | */ 11 | @Target({ElementType.TYPE}) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface Domain { 14 | int order() default 0; 15 | 16 | String name() default ""; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/activity/ActivityPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.activity; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubt on 16/01/2017. 10 | */ 11 | 12 | @Repository 13 | public interface ActivityPersistence { 14 | Integer record(Activity activity); 15 | 16 | List loadActivitiesByCard(@Param("operationTypeCode") String operationTypeCode, @Param("cardId") String cardId); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/worktile/domains/Todo.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.worktile.domains; 2 | 3 | /** 4 | * Created by xubt on 01/11/2016. 5 | */ 6 | public class Todo { 7 | private String name; 8 | private int checked; 9 | 10 | public String getName() { 11 | return name; 12 | } 13 | 14 | public void setName(String name) { 15 | this.name = name; 16 | } 17 | 18 | public int getChecked() { 19 | return checked; 20 | } 21 | 22 | public void setChecked(int checked) { 23 | this.checked = checked; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/registrationMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | INSERT INTO 7 | kb_user_registration(id, password,user_name,email,salt) 8 | VALUES (#{id}, #{password},#{userName}, #{email},#{salt}) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | artifact=$1 3 | server_host=$2 4 | server_dir=$3 5 | 6 | echo "-> upload artifact to remote server" 7 | scp $artifact $server_host:$server_dir 8 | scp killServer.sh $server_host:$server_dir 9 | 10 | echo "-> login remote server" 11 | ssh -t -t $server_host dir_to_deploy=$server_dir 'bash -s' <<'ENDSSH' 12 | 13 | cd $dir_to_deploy 14 | ls 15 | 16 | echo "-> stop server" 17 | sh killServer.sh 18 | 19 | echo "-> start server" 20 | nohup java -jar kanban-1.0-SNAPSHOT.jar & 21 | echo "-> everything is done." 22 | 23 | exit 24 | ENDSSH 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | logs/ 3 | .idea 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | *.project 12 | kanban/kanban-war/.settings/ 13 | *.classpath 14 | kanban/kanban-jar/.settings/ 15 | kanban/.settings/ 16 | kanban/rest-endpoint/.settings/ 17 | mbg/.settings/ 18 | mbg/generated/ 19 | README.html 20 | /bin/ 21 | .settings/ 22 | kanban.iml 23 | /src/main/resources/static/ 24 | /src/main/resources/config/application-local.properties 25 | /thiki-kanban.pid 26 | /build/ 27 | /.gradle/ 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/cardTags/CardTagPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.cardTags; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubt on 11/14/16. 10 | */ 11 | @Repository 12 | public interface CardTagPersistence { 13 | 14 | Integer stick(@Param("cardId") String cardId, @Param("cardTag") CardTag cardTag, @Param("userName") String userName); 15 | 16 | List findByCardId(String cardId); 17 | 18 | Integer removeTagsByCardId(String cardId); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/profile/StorageProperties.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.profile; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties("storage") 6 | public class StorageProperties { 7 | 8 | /** 9 | * Folder AVATAR_FILES_LOCATION for storing files 10 | */ 11 | private String location = "uploadFiles/avatars"; 12 | 13 | public String getLocation() { 14 | return location; 15 | } 16 | 17 | public void setLocation(String location) { 18 | this.location = location; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/resources/application-local-test.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=org.hsqldb.jdbcDriver 2 | jdbc.url=jdbc:hsqldb:mem:testdb 3 | jdbc.username= 4 | jdbc.password= 5 | server.port=9120 6 | server.contextPath=/kanban 7 | druid.enabled=false 8 | redis.enabled=false 9 | spring.redis.host=localhost 10 | spring.redis.password=KJmLRT9.Q 11 | spring.redis.pool.max-idle=8 12 | spring.redis.pool.min-idle=0 13 | spring.redis.pool.max-active=8 14 | spring.redis.pool.max-wait=-1 15 | liquibase.change-log=classpath:/scripts/db/changelog/db.changelog-master.xml 16 | redis.scheduled.flush.cron=0 2 * * * * 17 | performanceMonitor.enabled=false 18 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/invitation/InvitationPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.invitation; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | /** 7 | * Created by bogehu on 7/11/16. 8 | */ 9 | @Repository 10 | public interface InvitationPersistence { 11 | Integer invite(Invitation invitation); 12 | 13 | Invitation findById(String id); 14 | 15 | Integer cancelPreviousInvitation(Invitation invitation); 16 | 17 | Integer acceptInvitation(@Param("invitee") String invitee, @Param("projectId") String projectId); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/risk/RiskPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.risk; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by cain on 2017/2/28. 10 | */ 11 | @Repository 12 | public interface RiskPersistence { 13 | 14 | Integer addRisk(@Param("userName") String userName, @Param("cardId") String cardId, @Param("risk") Risk risk); 15 | 16 | Risk findRiskById(String id); 17 | 18 | List findCardRisks(@Param("cardId") String cardId); 19 | 20 | Integer deleteRisk(String id); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/comment/CommentPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.comment; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubt on 10/31/16. 10 | */ 11 | @Repository 12 | public interface CommentPersistence { 13 | Integer addComment(@Param("userName") String userName, @Param("cardId") String cardId, @Param("comment") Comment comment); 14 | 15 | Comment findById(String id); 16 | 17 | List loadCommentsByCardId(@Param("cardId") String cardId); 18 | 19 | Integer deleteComment(String acceptanceCriteriaId); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/notification/NotificationPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xutao on 09/12/16. 10 | */ 11 | 12 | @Repository 13 | public interface NotificationPersistence { 14 | Integer create(Notification notification); 15 | 16 | Notification read(@Param("id") String id); 17 | 18 | int loadUnreadNotificationTotal(String userName); 19 | 20 | List loadNotificationsByUserName(String userName); 21 | 22 | int setAlreadyRead(String id); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/MethodType.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | /** 4 | * Created by xubt on 24/10/2016. 5 | */ 6 | public enum MethodType { 7 | GET("GET"), 8 | POST("POST"), 9 | PUT("PUT"), 10 | DELETE("DELETE"); 11 | 12 | private String methodType; 13 | 14 | MethodType(String methodType) { 15 | this.methodType = methodType; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return super.toString(); 21 | } 22 | 23 | public boolean equal(String method) { 24 | return methodType.equals(method); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/verification/VerificationPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.verification; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by skytao on 03/06/17. 10 | */ 11 | @Repository 12 | public interface VerificationPersistence { 13 | Integer addVerification(@Param("verification") Verification verification, @Param("acceptanceCriteriaId") String acceptanceCriteriaId, @Param("userName") String userName); 14 | 15 | List loadVerificationsByAcceptanceCriteria(@Param("acceptanceCriteriaId") String acceptanceCriteriaId); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/passwordRetrieval/VerificationCodeEmailData.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.passwordRetrieval; 2 | 3 | import org.thiki.kanban.foundation.mail.MailEntity; 4 | 5 | /** 6 | * Created by xubt on 8/14/16. 7 | */ 8 | public class VerificationCodeEmailData extends MailEntity { 9 | private String verificationCode; 10 | 11 | public String getVerificationCode() { 12 | return verificationCode; 13 | } 14 | 15 | public void setVerificationCode(String verificationCode) { 16 | this.verificationCode = verificationCode; 17 | } 18 | 19 | @Override 20 | public String getSubject() { 21 | return "密码找回"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/assignment/AssignmentPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.assignment; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubitao on 6/16/16. 10 | */ 11 | 12 | @Repository 13 | public interface AssignmentPersistence { 14 | Integer create(Assignment assignment); 15 | 16 | Assignment findById(@Param("id") String id); 17 | 18 | List findByCardId(@Param("cardId") String cardId); 19 | 20 | int deleteById(String id); 21 | 22 | boolean isAlreadyAssigned(@Param("assignee") String assignee, @Param("cardId") String cardId); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/scripts/db/db_install_sit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | env_params_path=$1 4 | 5 | echo "env_params_path:$1" 6 | 7 | source $1 8 | 9 | echo "init database $database_name_sit ..." 10 | mysql -u "$username_sit" "-p$password_sit" -e "DROP DATABASE IF EXISTS $database_name_sit;" 11 | mysql -u "$username_sit" "-p$password_sit" -e "CREATE DATABASE $database_name_sit DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;" 12 | mysql -u "$username_sit" "-p$password_sit" -e "USE $database_name_sit;" 13 | 14 | #echo "current path:" 15 | #pwd 16 | 17 | #mysql -u "$username_sit" "-p$password_sit" "$database_name_sit" < "src/main/resources/scripts/db/mysql_init.sql" 18 | 19 | echo "create database done." 20 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/configuration/ApplicationProperties.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.configuration; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubt on 03/02/2017. 10 | */ 11 | @Component 12 | @ConfigurationProperties(prefix = "security") 13 | public class ApplicationProperties { 14 | private List whiteList; 15 | 16 | public List getWhiteList() { 17 | return whiteList; 18 | } 19 | 20 | public void setWhiteList(List whiteList) { 21 | this.whiteList = whiteList; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/MatchResult.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created by xubt on 18/12/2016. 7 | */ 8 | public class MatchResult { 9 | private String roleName; 10 | private Map pathValues; 11 | 12 | public String getRoleName() { 13 | return roleName; 14 | } 15 | 16 | public void setRoleName(String roleName) { 17 | this.roleName = roleName; 18 | } 19 | 20 | public Map getPathValues() { 21 | return pathValues; 22 | } 23 | 24 | public void setPathValues(Map pathValues) { 25 | this.pathValues = pathValues; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/SequenceNumber.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import com.fasterxml.uuid.EthernetAddress; 4 | import com.fasterxml.uuid.Generators; 5 | import com.fasterxml.uuid.impl.TimeBasedGenerator; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.UUID; 9 | 10 | @Service("sequenceNumber") 11 | public class SequenceNumber { 12 | public static String random() { 13 | TimeBasedGenerator gen = Generators.timeBasedGenerator(EthernetAddress.fromInterface()); 14 | UUID uuid = gen.generate(); 15 | return uuid.toString(); 16 | } 17 | 18 | public String generate() { 19 | return random(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/Role.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by xubt on 17/12/2016. 7 | */ 8 | public class Role { 9 | private String name; 10 | private List resources; 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public List getResources() { 21 | return resources; 22 | } 23 | 24 | public void setResources(List resources) { 25 | this.resources = resources; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/publickey/PublicKeyService.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.publickey; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.security.identification.rsa.RSAService; 5 | 6 | import javax.annotation.Resource; 7 | 8 | /** 9 | * Created by xubt on 7/5/16. 10 | */ 11 | @Service 12 | public class PublicKeyService { 13 | @Resource 14 | private RSAService rsaService; 15 | 16 | public PublicKey authenticate() throws Exception { 17 | PublicKey publicPublicKey = new PublicKey(); 18 | String publicKeyContent = rsaService.loadDefaultPublicKey(); 19 | publicPublicKey.setPublicKey(publicKeyContent); 20 | return publicPublicKey; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/IdentificationTestBase.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban; 2 | 3 | import com.jayway.restassured.RestAssured; 4 | import com.jayway.restassured.builder.RequestSpecBuilder; 5 | import org.junit.Before; 6 | 7 | /** 8 | * Created by xubt on 5/14/16. 9 | */ 10 | public class IdentificationTestBase extends TestBase { 11 | @Before 12 | public void setUp() throws Exception { 13 | super.setUp(); 14 | requestSpecification = new RequestSpecBuilder() 15 | .addHeader("identification", "yes") 16 | .setBasePath(contextPath) 17 | .setPort(port) 18 | .build(); 19 | RestAssured.requestSpecification = requestSpecification; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/logback/ThreadIdConverter.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.logback; 2 | 3 | import ch.qos.logback.classic.pattern.ClassicConverter; 4 | import ch.qos.logback.classic.spi.ILoggingEvent; 5 | import org.thiki.kanban.foundation.common.SequenceNumber; 6 | 7 | /** 8 | * Created by xubt on 28/11/2016. 9 | */ 10 | public class ThreadIdConverter extends ClassicConverter { 11 | private static final ThreadLocal threadId = ThreadLocal.withInitial(() -> { 12 | SequenceNumber sequenceNumber = new SequenceNumber(); 13 | return sequenceNumber.generate(); 14 | }); 15 | 16 | @Override 17 | public String convert(ILoggingEvent event) { 18 | return threadId.get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/board/BoardsPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.board; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubitao on 05/26/16. 10 | */ 11 | 12 | @Repository 13 | public interface BoardsPersistence { 14 | Integer create(Board board); 15 | 16 | Board findById(@Param("id") String id); 17 | 18 | Integer update(Board board); 19 | 20 | Integer deleteById(@Param("id") String id, @Param("userName") String userName); 21 | 22 | boolean unique(@Param("id") String id, @Param("name") String boardName, @Param("projectId") String projectId); 23 | 24 | List loadBoardsByProject(String projectId); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/InvalidParamsException.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | /** 7 | * Created by xubt on 8/24/16. 8 | */ 9 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 10 | public class InvalidParamsException extends BusinessException { 11 | 12 | private static final long serialVersionUID = -5266231525138811045L; 13 | 14 | public InvalidParamsException(int errorCode, String message) { 15 | super(errorCode, message, HttpStatus.BAD_REQUEST); 16 | } 17 | 18 | public InvalidParamsException(String message) { 19 | super(HttpStatus.BAD_REQUEST.value(), message, HttpStatus.BAD_REQUEST); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/members/MembersCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.members; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum MembersCodes { 9 | CURRENT_USER_IS_NOT_A_MEMBER_OF_THE_PROJECT("001", "当前用户非该团队成员。"), 10 | USER_IS_ALREADY_A_MEMBER_OF_THE_PROJECT("002", "你已是团队成员,无须再次加入。"); 11 | private String code; 12 | private String message; 13 | 14 | MembersCodes(String code, String message) { 15 | this.code = code; 16 | this.message = message; 17 | } 18 | 19 | public int code() { 20 | return Integer.parseInt(DomainOrder.PROJECT_MEMBER + "" + code); 21 | } 22 | 23 | public String message() { 24 | return message; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/rolesAuthenticationProviders/PersonalAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication.rolesAuthenticationProviders; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.security.authentication.Authentication; 5 | import org.thiki.kanban.foundation.security.authentication.AuthenticationResult; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by xubt on 24/10/2016. 11 | */ 12 | @Service("personalAuthenticationProvider") 13 | public class PersonalAuthenticationProvider implements Authentication { 14 | 15 | @Override 16 | public AuthenticationResult authenticate(Map pathValues, String userName) { 17 | return new AuthenticationResult(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/rolesAuthenticationProviders/VisitorAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication.rolesAuthenticationProviders; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.security.authentication.Authentication; 5 | import org.thiki.kanban.foundation.security.authentication.AuthenticationResult; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by xubt on 24/10/2016. 11 | */ 12 | @Service("visitorAuthenticationProvider") 13 | public class VisitorAuthenticationProvider implements Authentication { 14 | 15 | @Override 16 | public AuthenticationResult authenticate(Map pathValues, String userName) { 17 | return new AuthenticationResult(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/page/PageCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.page; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by winie on 2017/2/6. 7 | */ 8 | public enum PageCodes { 9 | 10 | PAGE_IS_NOT_EXISTS("001", "文章未找到。"); 11 | public static final String titleIsRequired = "文章标题不能为空。"; 12 | public static final String titleIsInvalid = "文章标题长度超限,请保持在40个字符以内。"; 13 | private String code; 14 | private String message; 15 | 16 | PageCodes(String code, String message) { 17 | this.code = code; 18 | this.message = message; 19 | } 20 | 21 | public int code() { 22 | return Integer.parseInt(DomainOrder.PAGE + "" + code); 23 | } 24 | 25 | public String message() { 26 | return message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/configuration/ApplicationContextProvider.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.configuration; 2 | 3 | 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.ApplicationContextAware; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | * Created by xubt on 21/10/2016. 11 | */ 12 | @Service 13 | public class ApplicationContextProvider implements ApplicationContextAware { 14 | private static ApplicationContext ctx = null; 15 | 16 | public static ApplicationContext getApplicationContext() { 17 | return ctx; 18 | } 19 | 20 | public void setApplicationContext(ApplicationContext ctx) throws BeansException { 21 | this.ctx = ctx; 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends BusinessException { 8 | 9 | private static final long serialVersionUID = -5266231525138811045L; 10 | 11 | public ResourceNotFoundException(String message) { 12 | super(ExceptionCode.resourceNotFound.code(), message); 13 | } 14 | 15 | public ResourceNotFoundException(Object codeObject) { 16 | super(codeObject); 17 | } 18 | 19 | @Override 20 | public HttpStatus getStatus() { 21 | return HttpStatus.NOT_FOUND; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/VerificationCodeService.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Created by xubt on 8/14/16. 9 | */ 10 | 11 | @Service 12 | public class VerificationCodeService { 13 | private static int codeLength = 6; 14 | 15 | public String generate() { 16 | String charValue = ""; 17 | for (int i = 0; i < codeLength; i++) { 18 | char c = (char) (randomInt(0, 10) + '0'); 19 | charValue += String.valueOf(c); 20 | } 21 | return charValue; 22 | } 23 | 24 | public int randomInt(int from, int to) { 25 | Random random = new Random(); 26 | return from + random.nextInt(to - from); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/UnauthorisedException.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED) 7 | public class UnauthorisedException extends BusinessException { 8 | 9 | private static final long serialVersionUID = -5266231525138811045L; 10 | 11 | public UnauthorisedException(int errorCode, String message) { 12 | super(errorCode, message, HttpStatus.UNAUTHORIZED); 13 | } 14 | 15 | public UnauthorisedException(Object codeObject) { 16 | super(codeObject); 17 | } 18 | 19 | @Override 20 | public HttpStatus getStatus() { 21 | return HttpStatus.UNAUTHORIZED; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/worktile/domains/Task.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.worktile.domains; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by xubt on 01/11/2016. 7 | */ 8 | public class Task { 9 | private String name; 10 | private Entry entry; 11 | private List todos; 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | 17 | public void setName(String name) { 18 | this.name = name; 19 | } 20 | 21 | public Entry getEntry() { 22 | return entry; 23 | } 24 | 25 | public void setEntry(Entry entry) { 26 | this.entry = entry; 27 | } 28 | 29 | public List getTodos() { 30 | return todos; 31 | } 32 | 33 | public void setTodos(List todos) { 34 | this.todos = todos; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/login/Identification.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.login; 2 | 3 | /** 4 | * Created by xubt on 7/8/16. 5 | */ 6 | public class Identification { 7 | private String token; 8 | private String userName; 9 | 10 | public String getToken() { 11 | return token; 12 | } 13 | 14 | public void setToken(String token) { 15 | this.token = token; 16 | } 17 | 18 | public String getUserName() { 19 | return userName; 20 | } 21 | 22 | public void setUserName(String userName) { 23 | this.userName = userName; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Identification{" + 29 | "token='" + token + '\'' + 30 | ", userName='" + userName + '\'' + 31 | '}'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/rsakey.pem: -------------------------------------------------------------------------------- 1 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK+3ZDVoOGLF+/RVZlMTNViZ/hBC1UxLBILVtihyJrD92AZpBj9z5tx25txjkutl1qP0E8kCka8QhAadcMMhRFdbynnD7Fnqe1tvy97G0aDfxMPcxqwKlxptvXxsGyL+GIRWBLEYvjJm1HuC3ADlpNBvMPGKL2Pbu8BVBfpkaIbhAgMBAAECgYA4/8le1bbsu4J2iLlPm3yDiDh09+kO/YqyEjcruZO5eC56Ldlb/fHWdC+BMD+5YmiU+JjubInrevUI3Et20LTFAz1BIEM+n72iCPv64ZGCwmeQTLz9oFBL4t6+0n+rjojq0+3THQcz613OjOTULdGzuP1OiIRGc4T9aAVVe6k4bQJBAO3aU9rEAqaWqyxf452y5igIEDHh0sJO+HVfGwsfZbNwAX4tnWkFpek3OBb+6cnX2We63NFbKjYWZ8dgRwMItw8CQQC9H2610Xi3y0hetW03J/9rCbnLFB8AapH+mUsZKMBodNWufgOZodOE838wbpemEV5pv1NtNTMQBOyALOnuNWMPAkBedaV1rQBMfmuubMptd33WChW8aa2Uw14C5ulLioWONH4zSRRJgBe6vdZFs6jPIyzQ+DH35telsVI3qPGqr8xbAkB7/dqgu7fwkAdfiIUVL1UHATZdTVDR/gy/phMVaKFVGpxprVaA6Bb8SIQv5aHpD+QdYoG4zLMwonnHwyqPsVLDAkEAhmMoWl0q5vDlJpwqnitbeAv8YJJ2klwM/lGBUAIdXFtQ4cIf3FDuBVquqBPsFcsWwvjO/Vda5Z3dbsCEGANJrQ== 2 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | security: 2 | whiteList: 3 | - "/entrance" 4 | - "/publicKey" 5 | - "/registration" 6 | - "/login" 7 | - "/passwordRetrievalApplication" 8 | - "/{userName}/passwordResetApplication" 9 | - "/{userName}/password" 10 | - "/unauthorised" 11 | - "/error/{}" 12 | - "/index.html" 13 | - "/favicon.ico" 14 | - "/js/{}" 15 | - "/css/{}" 16 | - "/fonts/{}" 17 | - "/img/{}" 18 | - "/component/{}" 19 | - "/foundation/{}" 20 | allowedOrigins: 21 | - "http://localhost:9120" 22 | - "http://127.0.0.1:8008" 23 | - "http://23.234.54.252:8089" 24 | - "http://www.thiki.org" 25 | - "http://localhost:8008" 26 | - "http://localhost:8096/druid/index.html" 27 | - "http://127.0.0.1:8096/kanban/health" 28 | - "http://127.0.0.1:8096/kanban/#/" 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/foundation/common/VerificationCodeServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | import org.thiki.kanban.TestBase; 7 | 8 | import javax.annotation.Resource; 9 | 10 | import static org.junit.Assert.assertTrue; 11 | 12 | /** 13 | * Created by xubt on 8/14/16. 14 | */ 15 | @RunWith(SpringJUnit4ClassRunner.class) 16 | public class VerificationCodeServiceTest extends TestBase { 17 | @Resource 18 | private VerificationCodeService verificationCodeService; 19 | 20 | @Test 21 | public void generateVerificationCode() { 22 | String newVerificationCode = verificationCodeService.generate(); 23 | assertTrue(newVerificationCode.length() == 6); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/project/ProjectsPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.project; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by bogehu on 7/11/16. 10 | */ 11 | @Repository 12 | public interface ProjectsPersistence { 13 | List loadAll(); 14 | 15 | void create(@Param("userName") String userName, @Param("project") Project project); 16 | 17 | Project findById(@Param("id") String id); 18 | 19 | List findByUserName(String userName); 20 | 21 | boolean isTeamExist(String projectId); 22 | 23 | boolean isNameConflict(@Param("userName") String userName, @Param("projectName") String projectName); 24 | 25 | Integer update(@Param("projectId") String projectId, @Param("project") Project project); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/login/LoginCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.login; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum LoginCodes { 9 | USER_IS_NOT_EXISTS("001", "用户不存在,请先注册。"), 10 | USER_OR_PASSWORD_IS_INCORRECT("002", "用户名或密码错误。"); 11 | 12 | public static final String IdentityIsRequired = "登录失败,请输入您的用户名或邮箱。"; 13 | public static final String PasswordIsRequired = "登录失败,您尚未输入密码。"; 14 | 15 | 16 | private String code; 17 | private String message; 18 | 19 | LoginCodes(String code, String message) { 20 | this.code = code; 21 | this.message = message; 22 | } 23 | 24 | public int code() { 25 | return Integer.parseInt(DomainOrder.LOGIN + code); 26 | } 27 | 28 | public String message() { 29 | return message; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/project/ProjectCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.project; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum ProjectCodes { 9 | PROJECT_IS_ALREADY_EXISTS("001", "同名团队已经存在。"), 10 | PROJECT_IS_NOT_EXISTS("002", "团队不存在。"); 11 | public static final String nameIsRequired = "团队名称不可以为空。"; 12 | public static final String nameIsInvalid = "看板名称过长,请保持在10个字以内。"; 13 | private String code; 14 | private String message; 15 | 16 | ProjectCodes(String code, String message) { 17 | this.code = code; 18 | this.message = message; 19 | } 20 | 21 | public int code() { 22 | return Integer.parseInt(DomainOrder.PROJECT + "" + code); 23 | } 24 | 25 | public String message() { 26 | return message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/AuthenticationTestBase.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban; 2 | 3 | import com.jayway.restassured.RestAssured; 4 | import com.jayway.restassured.builder.RequestSpecBuilder; 5 | import org.junit.Before; 6 | import org.thiki.kanban.foundation.security.Constants; 7 | 8 | /** 9 | * Created by xubt on 10/24/16. 10 | */ 11 | public class AuthenticationTestBase extends TestBase { 12 | @Before 13 | public void setUp() throws Exception { 14 | super.setUp(); 15 | requestSpecification = new RequestSpecBuilder() 16 | .addHeader(Constants.HEADER_PARAMS_IDENTIFICATION, "no") 17 | .addHeader(Constants.HEADER_PARAMS_AUTHENTICATION, "yes") 18 | .setBasePath(contextPath) 19 | .setPort(port) 20 | .build(); 21 | RestAssured.requestSpecification = requestSpecification; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/identification/token/AuthenticationToken.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.identification.token; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | /** 6 | * Created by xubt on 7/8/16. 7 | */ 8 | public class AuthenticationToken { 9 | private String userName; 10 | private String expirationTime; 11 | 12 | public String getUserName() { 13 | return userName; 14 | } 15 | 16 | public void setUserName(String userName) { 17 | this.userName = userName; 18 | } 19 | 20 | public String getExpirationTime() { 21 | return expirationTime; 22 | } 23 | 24 | public void setExpirationTime(String expirationTime) { 25 | this.expirationTime = expirationTime; 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return JSONObject.toJSONString(this); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/page/PagesPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.page; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | @Repository 9 | public interface PagesPersistence { 10 | 11 | Integer addPage(@Param("page") Page page, @Param("boardId") String boardId, @Param("userName") String userName); 12 | 13 | Page findById(@Param("pageId") String pageId, @Param("boardId") String boardId); 14 | 15 | Integer modify(@Param("pageId") String pageId, @Param("boardId") String boardId, @Param("page") Page page); 16 | 17 | List findByBoardId(String boardId); 18 | 19 | Integer deleteById(@Param("id") String id); 20 | 21 | Integer removePage(@Param("pageId") String pageId, @Param("boardId") String boardId); 22 | 23 | List findByBoard(@Param("boardId") String boardId); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/comment/CommentCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.comment; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 10/31/16. 7 | */ 8 | public enum CommentCodes { 9 | SUMMARY_IS_EMPTY("001", "验收标准的概述不能为空。"), AUTH_THE_COMMENT_YOU_WANT_TO_DELETE_IS_NOT_YOURS("002", "该条评论并非你所有撰写,你不可以删除。"); 10 | public static final String summaryIsRequired = "验收标准的概述不能为空。"; 11 | public static final String summaryIsInvalid = "卡片概述长度超限,请保持在200个字符以内。"; 12 | private String code; 13 | private String message; 14 | 15 | CommentCodes(String code, String message) { 16 | this.code = code; 17 | this.message = message; 18 | } 19 | 20 | public int code() { 21 | return Integer.parseInt(DomainOrder.COMMENT + "" + code); 22 | } 23 | 24 | public String message() { 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/sprint/SprintPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.sprint; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | /** 7 | * Created by xubt on 04/02/2017. 8 | */ 9 | 10 | @Repository 11 | public interface SprintPersistence { 12 | Integer create(@Param("sprint") Sprint sprint, @Param("boardId") String boardId, @Param("userName") String userName); 13 | 14 | Sprint findById(String sprintId); 15 | 16 | boolean isExistUnArchivedSprint(String boardId); 17 | 18 | Integer update(@Param("sprintId") String sprintId, @Param("sprint") Sprint sprint, @Param("boardId") String boardId); 19 | 20 | Sprint loadActiveSprint(@Param("boardId") String boardId); 21 | 22 | boolean isSprintNameAlreadyExist(@Param("sprintId") String sprintId, @Param("boardId") String boardId, @Param("sprintName") String sprintName); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/AuthenticationProviderFactory.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.configuration.ApplicationContextProvider; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by xubt on 18/12/2016. 10 | */ 11 | @Service 12 | public class AuthenticationProviderFactory { 13 | public Authentication loadProviderByRole(String roleName) { 14 | Map authProviders = ApplicationContextProvider.getApplicationContext().getBeansOfType(Authentication.class); 15 | for (Map.Entry entry : authProviders.entrySet()) { 16 | if (entry.getKey().equals(roleName + "AuthenticationProvider")) { 17 | return (Authentication) entry.getValue(); 18 | } 19 | } 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/assignment/AssignmentCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.assignment; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 11/4/16. 7 | */ 8 | public enum AssignmentCodes { 9 | ALREADY_ASSIGNED("001", "你此前已经认领该任务,请勿重新认领。"), CARD_IS_ALREADY_ARCHIVED("002", "卡片已经归档,不允许再进行分配操作。"); 10 | public static final String CARD_ASSIGNMENT_TEMPLATE = "card-assignment-template.ftl"; 11 | public static final String CARD_CANCEL_ASSIGNMENT = "card-cancel-assignment-template.ftl"; 12 | private String code; 13 | private String message; 14 | 15 | AssignmentCodes(String code, String message) { 16 | this.code = code; 17 | this.message = message; 18 | } 19 | 20 | public int code() { 21 | return Integer.parseInt(DomainOrder.BOARD + code); 22 | } 23 | 24 | public String message() { 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/TestContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban; 2 | 3 | import freemarker.template.TemplateException; 4 | import org.springframework.context.annotation.Bean; 5 | import org.thiki.kanban.foundation.mail.MailService; 6 | 7 | import javax.mail.MessagingException; 8 | import java.io.IOException; 9 | 10 | import static org.mockito.Matchers.any; 11 | import static org.mockito.Matchers.anyString; 12 | import static org.mockito.Mockito.doNothing; 13 | import static org.mockito.Mockito.mock; 14 | 15 | /** 16 | * Created by xubt on 5/11/16. 17 | */ 18 | public class TestContextConfiguration { 19 | @Bean 20 | public MailService mailService() throws TemplateException, IOException, MessagingException { 21 | MailService mailService = mock(MailService.class); 22 | doNothing().when(mailService).sendMailByTemplate(anyString(), anyString(), any(), anyString()); 23 | return mailService; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/members/MembersPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.members; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | import org.thiki.kanban.projects.project.Project; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by 濤 on 7/26/16. 11 | */ 12 | @Repository 13 | public interface MembersPersistence { 14 | void joinProject(Member projectMember); 15 | 16 | Member findById(@Param("id") String id); 17 | 18 | Member findMember(@Param("userName") String userName, @Param("projectId") String projectId); 19 | 20 | List loadMembersByProject(String projectId); 21 | 22 | boolean isAMemberOfTheProject(@Param("userName") String userName, @Param("projectId") String projectId); 23 | 24 | Integer leaveProject(@Param("projectId") String projectId, @Param("userName") String userName); 25 | 26 | List findProjects(String userName); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/board/snapshot/BoardsSnapshotController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.board.snapshot; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.thiki.kanban.foundation.common.Response; 6 | 7 | import javax.annotation.Resource; 8 | 9 | /** 10 | * Created by xubitao on 05/26/16. 11 | */ 12 | @RestController 13 | public class BoardsSnapshotController { 14 | @Resource 15 | private SnapshotService snapshotService; 16 | 17 | @RequestMapping(value = "/{userName}/projects/{projectId}/boards/{boardId}/snapshot", method = RequestMethod.GET) 18 | public HttpEntity load(@PathVariable String projectId, @PathVariable String boardId, @RequestParam(required = false) String viewType, @PathVariable String userName) throws Exception { 19 | Object board = snapshotService.loadSnapshotByBoard(projectId, boardId, viewType, userName); 20 | return Response.build(board); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/password/PasswordCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.password; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum PasswordCodes { 9 | EMAIL_IS_NOT_EXISTS("001", "邮箱不存在。"), 10 | SECURITY_CODE_TIMEOUT("002", "验证码已过期。"), 11 | NO_PASSWORD_RETRIEVAL_RECORD("003", "未找到密码找回申请记录,请核对你的邮箱或重新发送验证码。"), 12 | NO_PASSWORD_RESET_RECORD("004", "未找到密码重置申请记录。"), 13 | SECURITY_CODE_IS_NOT_CORRECT("005", "验证码错误。"), 14 | OLD_PASSWORD_NOT_EXISTS("006", "密码错误。"); 15 | private String code; 16 | private String message; 17 | 18 | PasswordCodes(String code, String message) { 19 | this.code = code; 20 | this.message = message; 21 | } 22 | 23 | public int code() { 24 | return Integer.parseInt(DomainOrder.PASSWORD + code); 25 | } 26 | 27 | public String message() { 28 | return message; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/verificationMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 8 | kb_verification(id,is_passed,remark,acceptance_criteria_id,author) 9 | VALUES 10 | (#{verification.id},#{verification.isPassed},#{verification.remark},#{acceptanceCriteriaId},#{userName}) 11 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/Application.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban; 2 | 3 | import de.codecentric.boot.admin.config.EnableAdminServer; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.system.ApplicationPidFileWriter; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | import org.springframework.transaction.annotation.EnableTransactionManagement; 9 | 10 | 11 | /** 12 | * Created by xubitao on 04/26/16. 13 | */ 14 | @EnableAdminServer 15 | @SpringBootApplication 16 | @EnableScheduling 17 | @EnableTransactionManagement 18 | public class Application { 19 | public static void main(String[] args) throws Exception { 20 | SpringApplication springApplication = new SpringApplication(Application.class); 21 | springApplication.addListeners(new ApplicationPidFileWriter("thiki-kanban.pid")); 22 | springApplication.run(args); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/publickey/PublicKeyController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.publickey; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import org.thiki.kanban.foundation.common.Response; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * Created by xubt on 7/5/16. 13 | */ 14 | @RestController 15 | public class PublicKeyController { 16 | @Resource 17 | private PublicKeyService publicKeyService; 18 | @Resource 19 | private PublicKeyResource publicKeyResource; 20 | 21 | @RequestMapping(value = "/publicKey", method = RequestMethod.GET) 22 | public HttpEntity identify() throws Exception { 23 | PublicKey publicPublicKey = publicKeyService.authenticate(); 24 | 25 | return Response.build(publicKeyResource.toResource(publicPublicKey)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/UsersPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | 7 | @Repository 8 | public interface UsersPersistence { 9 | User findById(@Param("id") String id); 10 | 11 | Integer deleteById(@Param("id") String id); 12 | 13 | User findByName(String userName); 14 | 15 | User findByIdentity(@Param("identity") String identity); 16 | 17 | User findByCredential(@Param("identity") String identity, @Param("password") String password); 18 | 19 | boolean isNameExist(String userName); 20 | 21 | boolean isEmailExist(String email); 22 | 23 | Profile findProfile(String userName); 24 | 25 | Integer initProfile(Profile profile); 26 | 27 | Integer updateAvatar(@Param("userName") String userName, @Param("avatarName") String avatarName); 28 | 29 | Integer updateProfile(@Param("userName") String userName, @Param("profile") Profile profile); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/cardsTagsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 8 | kb_cards_tags(id,card_id,tag_id, author) 9 | VALUES 10 | (#{cardTag.id},#{cardId},#{cardTag.tagId},#{userName}) 11 | 12 | 13 | 17 | 18 | 19 | UPDATE kb_cards_tags SET delete_status=1 WHERE card_id=#{cardId} AND delete_status=0 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/identification/token/IdentityResult.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.identification.token; 2 | 3 | /** 4 | * Created by xubt on 8/5/16. 5 | */ 6 | public class IdentityResult { 7 | private int errorCode; 8 | private String errorMessage; 9 | 10 | public static IdentityResult result(int errorCode, String errorMessage) { 11 | IdentityResult identityResult = new IdentityResult(); 12 | identityResult.setErrorCode(errorCode); 13 | identityResult.setErrorMessage(errorMessage); 14 | return identityResult; 15 | } 16 | 17 | public int getErrorCode() { 18 | return errorCode; 19 | } 20 | 21 | public void setErrorCode(int errorCode) { 22 | this.errorCode = errorCode; 23 | } 24 | 25 | public String getErrorMessage() { 26 | return errorMessage; 27 | } 28 | 29 | public void setErrorMessage(String errorMessage) { 30 | this.errorMessage = errorMessage; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/risk/RiskCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.risk; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by wisdom on 3/12/17. 7 | */ 8 | public enum RiskCodes { 9 | 10 | RISK_REASON_IS_EMPTY("001", "风险的原因不能为空"); 11 | 12 | public final static String RISK_REASON_IS_EMPTY_MESSAGE = "风险的原因不能为空"; 13 | public static final String RISK_REASON_IS_INVALID = "风险的理由限制早在200个字符"; 14 | private String code; 15 | private String message; 16 | 17 | RiskCodes(String code, String message) { 18 | this.code = code; 19 | this.message = message; 20 | } 21 | 22 | public String getCode() { 23 | return code; 24 | } 25 | 26 | public void setCode(String code) { 27 | this.code = DomainOrder.RISK + "" + code; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | 34 | public void setMessage(String message) { 35 | this.message = message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/tag/TagPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.tag; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubt on 11/7/16. 10 | */ 11 | @Repository 12 | public interface TagPersistence { 13 | Integer addTag(@Param("boardId") String boardId, @Param("tag") Tag tag); 14 | 15 | Tag findById(String id); 16 | 17 | List loadTagsByBoard(@Param("boardId") String boardId); 18 | 19 | Integer resort(@Param("tag") Tag tag); 20 | 21 | Integer updateTag(@Param("tagId") String tagId, @Param("tag") Tag tag); 22 | 23 | Integer deleteTag(String tagId); 24 | 25 | boolean isNameDuplicate(@Param("boardId") String boardId, @Param("tag") Tag tag); 26 | 27 | boolean isColorDuplicate(@Param("boardId") String boardId, @Param("tag") Tag tag); 28 | 29 | void cloneTagsFromOtherBoard(@Param("boardId") String boardId, @Param("fromBoardId") String fromBoardId); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/worktile/WorktileCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.worktile; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 11/1/16. 7 | */ 8 | public enum WorktileCodes { 9 | FILE_IS_EMPTY("001", "导入文件为空,请重新提交文件。"), 10 | FILE_CONTENT_FORMAT_INVALID("002", "文件内容格式错误,请检查修复后重新提交文件。"), 11 | FILE_FORMAT_INVALID("003", "文件格式错误,请检查修复后重新提交文件。"), 12 | FILE_NAME_INVALID("004", "文件名称错误,请确保文件名及拓展名完整。"), 13 | FILE_TYPE_INVALID("005", "文件类型错误,请上传以.json结尾的文本文件"), 14 | FILE_CONTENT_READ_ERROR("006", "读取文件文本时出错,请确认文件内容及格式。"), 15 | FILE_IS_UN_UPLOAD("007", "未上传文件!"); 16 | 17 | private String code; 18 | private String message; 19 | 20 | WorktileCodes(String code, String message) { 21 | this.code = code; 22 | this.message = message; 23 | } 24 | 25 | public int code() { 26 | return Integer.parseInt(DomainOrder.BOARD + code); 27 | } 28 | 29 | public String message() { 30 | return message; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/passwordReset/PasswordResetApplication.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.passwordReset; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * Created by xubt on 8/15/16. 9 | */ 10 | public class PasswordResetApplication { 11 | private String id; 12 | private String userName; 13 | 14 | @NotNull(message = "验证码不能为空。") 15 | @Length(min = 6, max = 6, message = "验证码格式错误,应为6为数字。") 16 | private String verificationCode; 17 | 18 | public String getUserName() { 19 | return userName; 20 | } 21 | 22 | public void setUserName(String userName) { 23 | this.userName = userName; 24 | } 25 | 26 | public String getVerificationCode() { 27 | return verificationCode; 28 | } 29 | 30 | public void setVerificationCode(String verificationCode) { 31 | this.verificationCode = verificationCode; 32 | } 33 | 34 | public String getId() { 35 | return id; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/entrance/EntranceController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.entrance; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpEntity; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.thiki.kanban.foundation.common.Response; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * Created by xubitao on 04/26/16. 15 | */ 16 | @RestController 17 | @RequestMapping(value = "/entrance") 18 | public class EntranceController { 19 | private final static Logger logger = LoggerFactory.getLogger(EntranceController.class); 20 | @Resource 21 | private EntranceResource entranceResource; 22 | 23 | @RequestMapping(method = RequestMethod.GET) 24 | public HttpEntity enter() throws Exception { 25 | logger.warn("entrance called!"); 26 | return Response.build(entranceResource.toResource()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/tag/TagsCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.tag; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 11/7/16. 7 | */ 8 | public enum TagsCodes { 9 | SUMMARY_IS_EMPTY("001", "标签名称不能为空。"), 10 | NAME_IS_ALREADY_EXIST("002", "当前看板下,该标签名称已经存在。"), 11 | COLOR_IS_ALREADY_EXIST("003", "当前看板下,该标签颜色已经存在。"); 12 | public static final String nameIsRequired = "标签名称不能为空。"; 13 | public static final String nameIsInvalid = "标签长度超限,请保持在20个字符以内。"; 14 | 15 | public static final String colorIsRequired = "标签颜色不能为空。"; 16 | public static final String colorIsInvalid = "标签颜色长度超限,请保持在10个字符以内。"; 17 | 18 | private String code; 19 | private String message; 20 | 21 | TagsCodes(String code, String message) { 22 | this.code = code; 23 | this.message = message; 24 | } 25 | 26 | public int code() { 27 | return Integer.parseInt(DomainOrder.CARD + "" + code); 28 | } 29 | 30 | public String message() { 31 | return message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/identification/md5/MD5Service.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.identification.md5; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.security.MessageDigest; 5 | import java.security.NoSuchAlgorithmException; 6 | 7 | /** 8 | * Created by xubt on 7/7/16. 9 | */ 10 | public class MD5Service { 11 | public static String encrypt(String string) { 12 | MessageDigest m = null; 13 | String result = ""; 14 | try { 15 | m = MessageDigest.getInstance("MD5"); 16 | m.update(string.getBytes("UTF8")); 17 | byte s[] = m.digest(); 18 | for (int i = 0; i < s.length; i++) { 19 | result += Integer.toHexString((0x000000ff & s[i]) | 0xffffff00).substring(6); 20 | } 21 | } catch (NoSuchAlgorithmException e) { 22 | throw new RuntimeException(e); 23 | } catch (UnsupportedEncodingException e) { 24 | throw new RuntimeException(e); 25 | } 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/cardTags/CardTagsCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.cardTags; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 11/14/16. 7 | */ 8 | public enum CardTagsCodes { 9 | SUMMARY_IS_EMPTY("001", "标签名称不能为空。"), 10 | NAME_IS_ALREADY_EXIST("002", "当前看板下,该标签名称已经存在。"), 11 | COLOR_IS_ALREADY_EXIST("003", "当前看板下,该标签颜色已经存在。"); 12 | public static final String nameIsRequired = "标签名称不能为空。"; 13 | public static final String nameIsInvalid = "标签长度超限,请保持在20个字符以内。"; 14 | 15 | public static final String colorIsRequired = "标签颜色不能为空。"; 16 | public static final String colorIsInvalid = "标签颜色长度超限,请保持在10个字符以内。"; 17 | 18 | private String code; 19 | private String message; 20 | 21 | CardTagsCodes(String code, String message) { 22 | this.code = code; 23 | this.message = message; 24 | } 25 | 26 | public int code() { 27 | return Integer.parseInt(DomainOrder.CARD + "" + code); 28 | } 29 | 30 | public String message() { 31 | return message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/invitation/InvitationCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.invitation; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum InvitationCodes { 9 | PROJECT_IS_NOT_EXISTS("001", "团队不存在。"), 10 | INVITEE_IS_NOT_EXISTS("002", "被邀请的成员不存在。"), 11 | INVITER_IS_NOT_A_MEMBER_OF_THE_PROJECT("003", "邀请人并不是当前团队的成员,不允许邀请他人进入该团队。"), 12 | INVITEE_IS_ALREADY_A_MEMBER_OF_THE_PROJECT("004", "邀请对象已经是该团队成员,无须再次邀请。"), 13 | INVITATION_IS_NOT_EXIST("005", "邀请函不存在或已失效。"), 14 | INVITATION_IS_ALREADY_ACCEPTED("006", "您此前已经接受邀请。"); 15 | public static final String InviteeIsRequired = "请指定被邀请的成员。"; 16 | private String code; 17 | private String message; 18 | 19 | InvitationCodes(String code, String message) { 20 | this.code = code; 21 | this.message = message; 22 | } 23 | 24 | public int code() { 25 | return Integer.parseInt(DomainOrder.INVITATION + code); 26 | } 27 | 28 | public String message() { 29 | return message; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/password/PasswordResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.password; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.stereotype.Service; 5 | import org.thiki.kanban.foundation.common.RestResource; 6 | import org.thiki.kanban.foundation.hateoas.TLink; 7 | import org.thiki.kanban.login.LoginController; 8 | 9 | import javax.annotation.Resource; 10 | 11 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | /** 15 | * Created by xubt on 8/15/16. 16 | */ 17 | @Service 18 | public class PasswordResource extends RestResource { 19 | @Resource 20 | private TLink tlink; 21 | 22 | public Object toResource() throws Exception { 23 | PasswordResource passwordResource = new PasswordResource(); 24 | Link loginLink = linkTo(methodOn(LoginController.class).login(null, null)).withRel("login"); 25 | passwordResource.add(tlink.from(loginLink).build()); 26 | return passwordResource.getResource(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/redis/RedisScheduledTasks.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.redis; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * Created by xubt on 14/02/2017. 14 | */ 15 | @Component 16 | @ConditionalOnProperty("redis.enabled") 17 | public class RedisScheduledTasks { 18 | public static Logger logger = LoggerFactory.getLogger(RedisScheduledTasks.class); 19 | @Resource 20 | private RedisTemplate redisTemplate; 21 | 22 | @Scheduled(cron = "${redis.scheduled.flush.cron}") 23 | public void flushAll() { 24 | logger.info("ScheduledTask:Flushing all cache."); 25 | redisTemplate.getConnectionFactory().getConnection().flushAll(); 26 | logger.info("ScheduledTask:Flushing all cache completed."); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/UsersCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 9/14/16. 7 | */ 8 | public enum UsersCodes { 9 | USERNAME_IS_ALREADY_EXISTS("001", "用户名已经被使用,请使用其他用户名。"), 10 | EMAIL_IS_ALREADY_EXISTS("002", "邮箱已经存在,请使用其他邮箱。"), AVATAR_IS_EMPTY("003", "请上传头像文件。"), 11 | AVATAR_IS_OUT_OF_MAX_SIZE("004", "头像文件已经超过100KB限制。"); 12 | public static final String identityIsRequired = "请提供用户标识:用户名或者密码。"; 13 | public static final String emailIsRequired = "邮箱不可以为空。"; 14 | public static final String userNameIsRequired = "用户名不可以为空。"; 15 | public static final String md5PasswordIsRequired = "MD5密码不可以为空。"; 16 | private String code; 17 | private String message; 18 | 19 | UsersCodes(String code, String message) { 20 | this.code = code; 21 | this.message = message; 22 | } 23 | 24 | public int code() { 25 | return Integer.parseInt(DomainOrder.REGISTRATION + "" + code); 26 | } 27 | 28 | public String message() { 29 | return message; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/passwordReset/PasswordReset.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.passwordReset; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | import org.thiki.kanban.foundation.security.identification.md5.MD5Service; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * Created by xubt on 8/15/16. 10 | */ 11 | public class PasswordReset { 12 | 13 | @NotNull(message = "密码不能为空。") 14 | @Length(max = 200, message = "密码超出长度限制。") 15 | private String password; 16 | private String oldPassword; 17 | 18 | public String getPassword() { 19 | return password; 20 | } 21 | 22 | public void setPassword(String password) { 23 | this.password = password; 24 | } 25 | 26 | public String encryptPassword(String slat, String dencryptPassword) throws Exception { 27 | return MD5Service.encrypt(dencryptPassword + slat); 28 | } 29 | 30 | public String getOldPassword() { 31 | return oldPassword; 32 | } 33 | 34 | public void setOldPassword(String oldPassword) { 35 | this.oldPassword = oldPassword; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/notification/NotificationType.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | /** 4 | * Created by xubt on 23/09/2016. 5 | */ 6 | public enum NotificationType { 7 | PROJECT_MEMBER_INVITATION("project-members-invitation", "团队邀请"), 8 | 9 | ASSIGNMENT_INVITATION("assignment", "卡片处理"), 10 | CANCEL_ASSIGNMENT_INVITATION("cancel-assignment", "取消卡片处理"), 11 | VERIFICATION_IS_NOT_PASSED("verification-is-not-passed", "取消卡片处理"); 12 | 13 | private String type; 14 | private String name; 15 | 16 | NotificationType(String type, String name) { 17 | this.type = type; 18 | this.name = name; 19 | } 20 | 21 | public static String getNameByType(String type) { 22 | for (NotificationType notificationType : NotificationType.values()) { 23 | if (notificationType.type.equals(type)) { 24 | return notificationType.typeName(); 25 | } 26 | } 27 | return type; 28 | } 29 | 30 | public String type() { 31 | return type; 32 | } 33 | 34 | public String typeName() { 35 | return name; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/activityMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 8 | kb_activity(id,card_id, 9 | prev_stage_id,stage_id,prev_stage_snapShot,stage_snapShot,operation_type_code,operation_type_name,summary,detail,userName) 10 | VALUES 11 | (#{id},#{cardId}, #{prevStageId}, 12 | #{stageId},#{prevStageSnapShot},#{stageSnapShot},#{operationTypeCode}, #{operationTypeName}, 13 | #{summary}, 14 | #{detail}, #{userName}) 15 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/card/CardsPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.card; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | @Repository 9 | public interface CardsPersistence { 10 | 11 | void create(@Param("userName") String userName, @Param("card") Card card); 12 | 13 | Card findById(@Param("cardId") String cardId); 14 | 15 | Integer modify(@Param("cardId") String cardId, @Param("card") Card card); 16 | 17 | List findByStageId(@Param("stageId") String stageId); 18 | 19 | Integer deleteById(@Param("cardId") String cardId); 20 | 21 | int totalCardsIncludingDeleted(@Param("boardId") String boardId, @Param("currentMonth") String currentMonth); 22 | 23 | List loadUnArchivedCards(); 24 | 25 | boolean hasChild(String cardId); 26 | 27 | List loadChildCards(String cardId); 28 | 29 | Integer archive(String cardId, String archivedCard, String userName); 30 | 31 | void move(@Param("cardId") String cardId, @Param("targetStageId") String targetStageId, @Param("sortNumber") Integer sortNumber); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/acceptanceCriteria/AcceptanceCriteriaCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.acceptanceCriteria; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 10/17/16. 7 | */ 8 | public enum AcceptanceCriteriaCodes { 9 | SUMMARY_IS_EMPTY("001", "验收标准的概述不能为空。"), ACCEPTANCE_CRITERIA_IS_NOT_FOUND("002", "验收标准不存在。"), CARD_WAS_ALREADY_DONE_OR_ARCHIVED("003", "所属卡片已经完成或已归档,不允许再修改或删除验收标准。"); 10 | public static final String summaryIsRequired = "验收标准的概述不能为空。"; 11 | public static final String summaryIsInvalid = "卡片概述长度超限,请保持在200个字符以内。"; 12 | public static final Integer STATUS_UNVERIFIED = 0; 13 | public static final Integer STATUS_UNPASSED = -1; 14 | public static final Integer STATUS_PASSED = 1; 15 | private String code; 16 | private String message; 17 | 18 | AcceptanceCriteriaCodes(String code, String message) { 19 | this.code = code; 20 | this.message = message; 21 | } 22 | 23 | public int code() { 24 | return Integer.parseInt(DomainOrder.CARD + "" + code); 25 | } 26 | 27 | public String message() { 28 | return message; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/password/PasswordPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.password; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | import org.thiki.kanban.password.passwordReset.PasswordResetApplication; 6 | import org.thiki.kanban.password.passwordRetrieval.PasswordRetrievalApplication; 7 | 8 | /** 9 | * Created by xubitao on 04/26/16. 10 | */ 11 | 12 | @Repository 13 | public interface PasswordPersistence { 14 | Integer createPasswordRetrievalApplication(PasswordRetrievalApplication passwordRetrievalApplication); 15 | 16 | Integer createPasswordResetApplication(PasswordResetApplication passwordResetApplication); 17 | 18 | PasswordRetrievalApplication loadRetrievalApplication(String userName); 19 | 20 | Integer resetPassword(@Param("userName") String userName, @Param("password") String password); 21 | 22 | Integer cleanResetApplication(String userName); 23 | 24 | Integer makeRetrievalApplicationPassed(String userName); 25 | 26 | boolean isPasswordResetApplicationExists(String userName); 27 | 28 | Integer clearUnfinishedApplication(String userName); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/registration/RegistrationController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.registration; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import org.thiki.kanban.foundation.common.Response; 9 | import org.thiki.kanban.user.User; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * Created by joeaniu on 6/21/16. 15 | */ 16 | @RestController 17 | public class RegistrationController { 18 | @Resource 19 | private RegistrationService registrationService; 20 | @Resource 21 | private RegistrationResource registrationResource; 22 | 23 | @RequestMapping(value = "/registration", method = RequestMethod.POST) 24 | public HttpEntity registerNewUser(@RequestBody Registration registration) throws Exception { 25 | User registeredUser = registrationService.register(registration); 26 | return Response.post(registrationResource.toResource(registeredUser)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/redis/ApplicationStartup.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.redis; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.boot.context.event.ApplicationReadyEvent; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * Created by xubt on 10/02/2017. 15 | */ 16 | @Component 17 | @ConditionalOnProperty("redis.enabled") 18 | public class ApplicationStartup implements ApplicationListener { 19 | public static Logger logger = LoggerFactory.getLogger(ApplicationStartup.class); 20 | 21 | @Resource 22 | private RedisTemplate redisTemplate; 23 | 24 | @Override 25 | public void onApplicationEvent(ApplicationReadyEvent event) { 26 | logger.info("Flush all cache."); 27 | redisTemplate.getConnectionFactory().getConnection().flushAll(); 28 | logger.info("Flush all cache completed."); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/statistics/burnDownChart/BurnDownChartPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.statistics.burnDownChart; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | import org.thiki.kanban.sprint.Sprint; 6 | import org.thiki.kanban.stage.Stage; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by winie on 2017/3/23. 13 | */ 14 | @Repository 15 | public interface BurnDownChartPersistence { 16 | 17 | void create(@Param("burnDownChart") BurnDownChart burnDownChart); 18 | void update(@Param("burnDownChart") BurnDownChart burnDownChart); 19 | List findAllSprint(@Param("nowDate") Date nowDate); 20 | Sprint findSprintBySprintId(@Param("sprintId") String sprintId); 21 | List findStageByBoardId(@Param("boardId") String boardId); 22 | int statisticsCardSizeByStageId(@Param("stageId") String stageId); 23 | int findBySprintIdAndSprintAnalyseTime(@Param("sprintId") String sprintId, @Param("sprintAnalyseTime") String sprintAnalyseTime); 24 | List findBurnDownChartBySprintIdAndBoardId(@Param("boardId") String boardId,@Param("sprintId") String sprintId); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/scripts/killServer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | PIDFile="thiki-kanban.pid" 3 | JVM_OPTS="-Xmx2g" 4 | SPRING_OPTS="--logging.file=application.log" 5 | 6 | function check_if_pid_file_exists { 7 | if [ ! -f $PIDFile ] 8 | then 9 | echo "PID file not found: $PIDFile" 10 | exit 0 11 | fi 12 | } 13 | 14 | function check_if_process_is_running { 15 | if ps -p $(print_process) > /dev/null 16 | then 17 | return 0 18 | else 19 | return 1 20 | fi 21 | } 22 | 23 | function print_process { 24 | echo $(<"$PIDFile") 25 | } 26 | check_if_pid_file_exists 27 | if ! check_if_process_is_running 28 | then 29 | echo "Process $(print_process) already stopped" 30 | exit 0 31 | fi 32 | kill -TERM $(print_process) 33 | echo -ne "Waiting for process from stop" 34 | NOT_KILLED=1 35 | for i in {1..20}; do 36 | if check_if_process_is_running 37 | then 38 | echo -ne "." 39 | sleep 5 40 | else 41 | NOT_KILLED=0 42 | fi 43 | done 44 | echo 45 | if [ $NOT_KILLED = 1 ] 46 | then 47 | echo "Cannot kill process $(print_process)" 48 | exit 1 49 | fi 50 | echo "Process stopped" 51 | exit 0 52 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/commentMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT 8 | INTO 9 | kb_comment(id,summary, card_id, author) 10 | VALUES 11 | (#{comment.id},#{comment.summary},#{cardId},#{userName}) 12 | 13 | 17 | 18 | 22 | 23 | 24 | UPDATE kb_comment SET delete_status=1 WHERE id=#{commentId} 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/activity/ActivityType.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.activity; 2 | 3 | /** 4 | * Created by xubt on 17/01/2017. 5 | */ 6 | public enum ActivityType { 7 | CARD_CREATION("001", "卡片创建"), 8 | CARD_MODIFYING("002", "卡片修改"), 9 | CARD_DELETING("003", "删除卡片"), 10 | ACCEPTANCE_CRITERIA_CREATION("004", "增加验收标准"), 11 | ACCEPTANCE_CRITERIA_MODIFYING("005", "更新验收标准"), 12 | ACCEPTANCE_CRITERIA_DELETING("006", "删除验收标准"), 13 | TAG_MODIFYING("007", "更新标签"), 14 | COMMENT_CREATION("008", "创建评论"), 15 | CARD_ARCHIVED("009", "卡片归档"), 16 | CARD_UNDO_ARCHIVED("010", "卡片撤销归档"), 17 | CARD_RESORT("011", "卡片排序"), 18 | CARD_MOVING("012", "卡片挪动"), 19 | ACCEPTANCE_CRITERIA_RESORTING("013", "重新排序验收标准"), 20 | ASSIGNMENT("014", "认领卡片"), 21 | UNDO_ASSIGNMENT("015", "离开卡片"), 22 | CARD_ALL_ACCEPTANCE_CRITERIAS_ARE_FINISHED("016", "卡片任务全部完成"); 23 | private String code; 24 | private String type; 25 | 26 | ActivityType(String code, String type) { 27 | this.code = code; 28 | this.type = type; 29 | } 30 | 31 | public String code() { 32 | return code; 33 | } 34 | 35 | public String type() { 36 | return type; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/board/BoardCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.board; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum BoardCodes { 9 | BOARD_IS_ALREADY_EXISTS("001", "同名看板已经存在,请使用其它名称。"), 10 | BOARD_IS_NOT_EXISTS("002", "看板不存在。"), 11 | FORBID_CURRENT_IS_NOT_A_MEMBER_OF_THE_PROJECT("003", "操作被阻止!你非当前看板所属团队成员,看板亦非你个人所有。"); 12 | public static final String nameIsRequired = "看板名称不能为空。"; 13 | public static final String nameIsInvalid = "看板名称过长,请保持在30个字以内。"; 14 | public static final String VIEW_TYPE_FULL_VIEW = "fullView"; 15 | public static final String VIEW_TYPE_SPRINT = "sprintView"; 16 | public static final String VIEW_TYPE_ROAD_MAP = "roadMapView"; 17 | public static final String VIEW_TYPE_ARCHIVE = "archiveView"; 18 | private String code; 19 | private String message; 20 | 21 | BoardCodes(String code, String message) { 22 | this.code = code; 23 | this.message = message; 24 | } 25 | 26 | public int code() { 27 | return Integer.parseInt(DomainOrder.BOARD + code); 28 | } 29 | 30 | public String message() { 31 | return message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/passwordReset/PasswordResetResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.passwordReset; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.hateoas.mvc.ControllerLinkBuilder; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | import org.thiki.kanban.password.PasswordController; 9 | 10 | import javax.annotation.Resource; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | /** 15 | * Created by xubt on 8/15/16. 16 | */ 17 | @Service 18 | public class PasswordResetResource extends RestResource { 19 | @Resource 20 | private TLink tlink; 21 | 22 | public Object toResource(String userName) throws Exception { 23 | PasswordResetResource passwordResetResource = new PasswordResetResource(); 24 | Link passwordRetrievalLink = ControllerLinkBuilder.linkTo(methodOn(PasswordController.class).password(null, userName)).withRel("password"); 25 | passwordResetResource.add(tlink.from(passwordRetrievalLink).build(userName)); 26 | return passwordResetResource.getResource(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/riskMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 8 | kb_risk(id, risk_reason,card_id,type,author,is_resolved) 9 | VALUES 10 | (#{risk.id},#{risk.riskReason},#{cardId},#{risk.type},#{userName},#{risk.isResolved}) 11 | 12 | 13 | 18 | 19 | 22 | 23 | 24 | UPDATE kb_risk SET is_resolved = true WHERE id = #{id} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/statistics/burnDownChart/BurnDownChartController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.statistics.burnDownChart; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.thiki.kanban.foundation.common.Response; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by winie on 2017/3/27. 12 | */ 13 | @RestController 14 | @RequestMapping(value = "") 15 | public class BurnDownChartController { 16 | @Resource 17 | private BurnDownChartService burnDownChartService; 18 | @Resource 19 | private BurnDownChartsResource burnDownChartsResource; 20 | 21 | @RequestMapping(value = "/statistics/projects/{projectId}/boards/{boardId}/sprint/{sprintId}", method = RequestMethod.GET) 22 | public HttpEntity findBySprintIdAndBoardId(@PathVariable String boardId, @PathVariable String sprintId,@PathVariable String projectId, @RequestHeader String userName) throws Exception { 23 | List burnDownCharts=burnDownChartService.findBurnDownChartBySprintIdAndBoardId(boardId,sprintId); 24 | return Response.build(burnDownChartsResource.toResource(burnDownCharts, projectId,boardId,sprintId, userName)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/worktile/WorktileController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.worktile; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.springframework.web.multipart.MultipartFile; 6 | import org.thiki.kanban.board.Board; 7 | import org.thiki.kanban.board.BoardResource; 8 | import org.thiki.kanban.foundation.common.Response; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * Created by xubt on 11/1/16. 14 | */ 15 | @RestController 16 | public class WorktileController { 17 | @Resource 18 | private WorktileService worktileService; 19 | 20 | @Resource 21 | private BoardResource boardResource; 22 | 23 | @RequestMapping(value = "/{userName}/projects/{projectId}/worktileTasks", method = RequestMethod.POST) 24 | public HttpEntity importTasks(@PathVariable("userName") String userName, @PathVariable String projectId, @RequestParam(value = "worktileTasks", required = false) Object worktileTasks) throws Exception { 25 | Board savedWorktileCards = worktileService.importWorktileTasks(projectId, userName, (MultipartFile) worktileTasks); 26 | return Response.post(boardResource.toResource(savedWorktileCards, projectId, userName)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/application/DomainOrder.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.application; 2 | 3 | /** 4 | * Created by xubt on 9/15/16. 5 | */ 6 | public class DomainOrder { 7 | 8 | public static final int ENTRANCE = 0; 9 | public static final int REGISTRATION = 1; 10 | public static final int LOGIN = 2; 11 | public static final int BOARD = 3; 12 | public static final int STAGE = 4; 13 | public static final int CARD = 5; 14 | public static final int PROJECT = 6; 15 | public static final int INVITATION = 7; 16 | public static final int PROJECT_MEMBER = 8; 17 | public static final int PASSWORD = 9; 18 | public static final int NOTIFICATION = 10; 19 | public static final int ASSIGNMENT = 11; 20 | public static final int USER = 12; 21 | public static final int ACCEPT_CRITERIA = 13; 22 | public static final int COMMENT = 14; 23 | public static final int WORKTILE = 15; 24 | public static final int TAG = 16; 25 | public static final int CARD_TAGS = 17; 26 | public static final int SPRINT = 18; 27 | public static final int PAGE = 19; 28 | public static final int VERIFICATION = 20; 29 | public static final int RISK = 21; 30 | public static final int BURNDOWNCHART = 22; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/password/passwordRetrieval/PasswordRetrievalResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.password.passwordRetrieval; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.hateoas.mvc.ControllerLinkBuilder; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | import org.thiki.kanban.password.PasswordController; 9 | 10 | import javax.annotation.Resource; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | /** 15 | * Created by xubt on 7/5/16. 16 | */ 17 | @Service 18 | public class PasswordRetrievalResource extends RestResource { 19 | @Resource 20 | private TLink tlink; 21 | 22 | public Object toResource(String userName) throws Exception { 23 | PasswordRetrievalResource passwordRetrievalResource = new PasswordRetrievalResource(); 24 | Link passwordRetrievalLink = ControllerLinkBuilder.linkTo(methodOn(PasswordController.class).passwordRetrieval(null, userName)).withRel("passwordResetApplication"); 25 | passwordRetrievalResource.add(tlink.from(passwordRetrievalLink).build(userName)); 26 | return passwordRetrievalResource.getResource(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/quality/findbugs/findbugs-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/publickey/PublicKeyControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.publickey; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | import org.thiki.kanban.TestBase; 7 | import org.thiki.kanban.foundation.annotations.Scenario; 8 | import org.thiki.kanban.foundation.common.FileUtil; 9 | 10 | import static com.jayway.restassured.RestAssured.given; 11 | import static org.hamcrest.CoreMatchers.equalTo; 12 | import static org.hamcrest.core.StringEndsWith.endsWith; 13 | 14 | /** 15 | * Created by xubt on 7/13/16. 16 | */ 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | public class PublicKeyControllerTest extends TestBase { 19 | @Scenario("当用户请求登录或注册时,首先需要向系统发送一次认证请求,将公钥发送至客户端") 20 | @Test 21 | public void identification_askForAuthenticationWhenUserIsExists() { 22 | String publicKey = FileUtil.readFile(publicKeyFilePath); 23 | given().when() 24 | .get("/publicKey") 25 | .then() 26 | .statusCode(200) 27 | .body("publicKey", equalTo(publicKey)) 28 | .body("_links.login.href", endsWith("/login")) 29 | .body("_links.registration.href", endsWith("/registration")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/date/Week.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common.date; 2 | 3 | /** 4 | * Created by 濤 on 2016/1/12. 5 | */ 6 | public enum Week { 7 | 8 | MONDAY("星期一", "Monday", "Mon.", 1), 9 | TUESDAY("星期二", "Tuesday", "Tues.", 2), 10 | WEDNESDAY("星期三", "Wednesday", "Wed.", 3), 11 | THURSDAY("星期四", "Thursday", "Thur.", 4), 12 | FRIDAY("星期五", "Friday", "Fri.", 5), 13 | SATURDAY("星期六", "Saturday", "Sat.", 6), 14 | SUNDAY("星期日", "Sunday", "Sun.", 7); 15 | 16 | String name_cn; 17 | String name_en; 18 | String name_enShort; 19 | int number; 20 | 21 | Week(String name_cn, String name_en, String name_enShort, int number) { 22 | this.name_cn = name_cn; 23 | this.name_en = name_en; 24 | this.name_enShort = name_enShort; 25 | this.number = number; 26 | } 27 | 28 | public String getChineseName() { 29 | return name_cn; 30 | } 31 | 32 | public String getName() { 33 | return name_en; 34 | } 35 | 36 | public String getShortName() { 37 | return name_enShort; 38 | } 39 | 40 | public int getNumber() { 41 | return number; 42 | } 43 | 44 | public boolean isWeekend() { 45 | return this.number == 6 || this.number == 7; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/project/ProjectsResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.project; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.stereotype.Service; 5 | import org.thiki.kanban.foundation.common.RestResource; 6 | 7 | import javax.annotation.Resource; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by xubt on 9/9/16. 13 | */ 14 | @Service 15 | public class ProjectsResource extends RestResource { 16 | @Resource 17 | private ProjectResource projectResourceService; 18 | 19 | @Cacheable(value = "project", key = "'resource-projects'+#userName") 20 | public Object toResource(String userName, List projectList) throws Exception { 21 | ProjectsResource projectsResource = new ProjectsResource(); 22 | projectsResource.domainObject = projectList; 23 | 24 | List projectsResources = new ArrayList<>(); 25 | for (Project project : projectList) { 26 | Object projectResource = projectResourceService.toResource(userName, project); 27 | projectsResources.add(projectResource); 28 | } 29 | projectsResource.buildDataObject("projects", projectsResources); 30 | return projectsResource.getResource(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/sprint/SprintCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.sprint; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 04/02/2017. 7 | */ 8 | public enum SprintCodes { 9 | START_TIME_IS_AFTER_END_TIME("001", "迭代开始日期晚于结束日期。"), 10 | UNARCHIVE_SPRINT_EXIST("002", "存在尚未归档的迭代,不允许创建新的迭代。"), 11 | ACTIVE_SPRINT_IS_NOT_FOUND("003", "当前不存在激活的迭代。"), 12 | SPRINT_IS_NOT_EXIST("004", "迭代不存在。"), 13 | SPRINT_ALREADY_ARCHIVED("005", "迭代此前已经归档。"), 14 | SPRINT_NAME_ALREADY_EXISTS("006", "当前看板下,已经存在相同名称的迭代。"); 15 | public static final Integer SPRINT_IN_PROGRESS = 1; 16 | public static final Integer SPRINT_COMPLETED = 2; 17 | public static final String startTimeIsRequired = "迭代开始时间不可以为空。"; 18 | public static final String endTimeIsRequired = "迭代结束时间不可以为空。"; 19 | public static final String sprintNameIsRequired = "迭代名称不可以为空。"; 20 | private String code; 21 | 22 | private String message; 23 | 24 | SprintCodes(String code, String message) { 25 | this.code = code; 26 | this.message = message; 27 | } 28 | 29 | public int code() { 30 | return Integer.parseInt(DomainOrder.SPRINT + "" + code); 31 | } 32 | 33 | public String message() { 34 | return message; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/foundation/aspect/ValidateAspectTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.aspect; 2 | 3 | import com.jayway.restassured.http.ContentType; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 7 | import org.thiki.kanban.TestBase; 8 | import org.thiki.kanban.foundation.annotations.Scenario; 9 | import org.thiki.kanban.foundation.security.Constants; 10 | 11 | import static com.jayway.restassured.RestAssured.given; 12 | import static org.hamcrest.CoreMatchers.equalTo; 13 | 14 | /** 15 | * Created by xubt on 8/28/16. 16 | */ 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | public class ValidateAspectTest extends TestBase { 19 | @Scenario("当头部信息的userName和路径中的不一致时,告知客户端错误") 20 | @Test 21 | public void throwExceptionIfUserNameInHeaderIsNotEqualWithItInPath() throws Exception { 22 | given().header("userName", "someone") 23 | .body("{\"name\":\"projectName\"}") 24 | .contentType(ContentType.JSON) 25 | .when() 26 | .post("/thief/projects") 27 | .then() 28 | .statusCode(400) 29 | .body("message", equalTo(Constants.SECURITY_USERNAME_IN_HEADER_IS_NOT_CONSISTENT_WITH_PATH)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/Response.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import org.springframework.core.io.UrlResource; 4 | import org.springframework.http.HttpEntity; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.http.ResponseEntity; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Created by xubitao on 1/2/16. 13 | */ 14 | public class Response { 15 | public static HttpEntity build(UrlResource urlResource) throws IOException { 16 | return Response.build(FileUtil.urlResourceToString(urlResource)); 17 | } 18 | 19 | public static HttpEntity build(String s) { 20 | return ResponseEntity 21 | .ok() 22 | .contentType(MediaType.TEXT_PLAIN) 23 | .body(s); 24 | } 25 | 26 | public static HttpEntity build(Object o) { 27 | return ResponseEntity 28 | .ok() 29 | .contentType(MediaType.APPLICATION_JSON) 30 | .body(o); 31 | } 32 | 33 | public static HttpEntity post(Object o) { 34 | return ResponseEntity 35 | .status(HttpStatus.CREATED) 36 | .contentType(MediaType.APPLICATION_JSON) 37 | .body(o); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/invitation/InvitationEmail.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.invitation; 2 | 3 | import org.thiki.kanban.foundation.mail.MailEntity; 4 | 5 | /** 6 | * Created by xubt on 8/14/16. 7 | */ 8 | public class InvitationEmail extends MailEntity { 9 | private String inviter; 10 | private String invitee; 11 | private String projectName; 12 | private String invitationLink; 13 | 14 | @Override 15 | public String getSubject() { 16 | return "邀请加入团队"; 17 | } 18 | 19 | public String getInviter() { 20 | return inviter; 21 | } 22 | 23 | public void setInviter(String inviter) { 24 | this.inviter = inviter; 25 | } 26 | 27 | public String getInvitee() { 28 | return invitee; 29 | } 30 | 31 | public void setInvitee(String invitee) { 32 | this.invitee = invitee; 33 | } 34 | 35 | public String getInvitationLink() { 36 | return invitationLink; 37 | } 38 | 39 | public void setInvitationLink(String invitationLink) { 40 | this.invitationLink = invitationLink; 41 | } 42 | 43 | public String getProjectName() { 44 | return projectName; 45 | } 46 | 47 | public void setProjectName(String projectName) { 48 | this.projectName = projectName; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/verification/VerificationCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.verification; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by skytao on 03/06/17. 7 | */ 8 | public enum VerificationCodes { 9 | ACCEPTANCE_CRITERIA_IS_NOT_FINISHED("001", "验收标准尚未完成,不能验收。"), 10 | ACCEPTANCE_CRITERIA_IS_NOT_FOUND("002", "验收标准不存在。"), 11 | CARD_HAS_ALREADY_BEEN_ARCHIVED_OR_DONE("003", "验收标准所属卡片已经处于完成或归档环节,不允许再验收。"); 12 | public static final String IS_PASSED_NOT_VALID = "请指定验收是否已经通过。"; 13 | public static final String REMARK_IS_NOT_VALID = "验收意见不能为空且不能超过100个字符。"; 14 | public static final String ACCEPTANCE_CRITERIA_ID__IS_NOT_VALID = "待验收的验收标准未指定。"; 15 | public static final Integer IS_NOT_PASSED = -1; 16 | public static final Integer IS_PASSED = 1; 17 | public static final String VERIFICATION_FAILED_EMAIL_TEMPLATE = "verification-template.ftl"; 18 | 19 | private String code; 20 | private String message; 21 | 22 | VerificationCodes(String code, String message) { 23 | this.code = code; 24 | this.message = message; 25 | } 26 | 27 | public int code() { 28 | return Integer.parseInt(DomainOrder.CARD + "" + code); 29 | } 30 | 31 | public String message() { 32 | return message; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/registration/RegistrationResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.registration; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.stereotype.Service; 5 | import org.thiki.kanban.foundation.common.RestResource; 6 | import org.thiki.kanban.foundation.hateoas.TLink; 7 | import org.thiki.kanban.login.LoginController; 8 | import org.thiki.kanban.user.User; 9 | 10 | import javax.annotation.Resource; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | /** 16 | * Created by mac on 6/22/16. 17 | */ 18 | @Service 19 | public class RegistrationResource extends RestResource { 20 | @Resource 21 | private TLink tlink; 22 | 23 | public Object toResource(User registeredUser) throws Exception { 24 | RegistrationResource registrationResource = new RegistrationResource(); 25 | registrationResource.domainObject = registeredUser; 26 | if (registeredUser != null) { 27 | Link loginLink = linkTo(methodOn(LoginController.class).login(null, null)).withRel("login"); 28 | registrationResource.add(tlink.from(loginLink).build()); 29 | } 30 | return registrationResource.getResource(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local-tao.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=com.mysql.jdbc.Driver 2 | jdbc.url=jdbc:mysql://127.0.0.1:3306/thiki-kanban?useUnicode=true&characterEncoding=utf8 3 | jdbc.username=root 4 | jdbc.password=000000 5 | druid.enabled=true 6 | #datasource.type=com.alibaba.druid.pool.DruidDataSource 7 | datasource.initialSize=5 8 | datasource.minIdle=5 9 | datasource.maxActive=20 10 | datasource.maxWait=60000 11 | datasource.timeBetweenEvictionRunsMillis=60000 12 | datasource.minEvictableIdleTimeMillis=300000 13 | datasource.validationQuery=SELECT 1 FROM DUAL 14 | datasource.testWhileIdle=true 15 | datasource.testOnBorrow=false 16 | datasource.testOnReturn=false 17 | datasource.poolPreparedStatements=true 18 | datasource.maxPoolPreparedStatementPerConnectionSize=20 19 | datasource.filters=stat,wall,log4j 20 | datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 21 | datasource.druidUserName=admin 22 | datasource.druidPassword=admin 23 | server.port=8096 24 | server.contextPath=/kanban 25 | # REDIS (RedisProperties) 26 | redis.enabled=false 27 | spring.redis.host=localhost 28 | spring.redis.password=KJmLRT9.Q 29 | spring.redis.pool.max-idle=8 30 | spring.redis.pool.min-idle=0 31 | spring.redis.pool.max-active=8 32 | spring.redis.pool.max-wait=-1 33 | liquibase.change-log=classpath:/scripts/db/changelog/db.changelog-master.xml 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/stage/StagesPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.stage; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Created by xubitao on 04/26/16. 10 | */ 11 | 12 | @Repository 13 | public interface StagesPersistence { 14 | Integer create(@Param("stage") Stage stage, @Param("userName") String userName, @Param("boardId") String boardId); 15 | 16 | Stage findById(@Param("stageId") String stageId); 17 | 18 | List loadByBoardIdAndType(@Param("boardId") String boardId, @Param("viewType") String type); 19 | 20 | Integer update(@Param("stageId") String stageId, @Param("stage") Stage stage); 21 | 22 | Integer deleteById(@Param("stageId") String stageId); 23 | 24 | Integer resort(Stage stage); 25 | 26 | boolean uniqueTitle(@Param("boardId") String boardId, @Param("title") String title); 27 | 28 | boolean isDoneStageAlreadyExist(@Param("stageId") String stageId, @Param("boardId") String boardId); 29 | 30 | Stage findDoneStage(@Param("boardId") String boardId); 31 | 32 | boolean hasNewArchivedStageExist(Stage archivedStage); 33 | 34 | Stage findStageByStatus(@Param("boardId") String boardId, @Param("status") Integer status); 35 | 36 | Integer countCardsNumber(@Param("stageId") String stageId); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/registration/Registration.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.registration; 2 | 3 | import org.hibernate.validator.constraints.Email; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * Created by xubt on 7/7/16. 9 | */ 10 | public class Registration { 11 | 12 | private String id; 13 | @Email(message = "邮箱格式错误") 14 | private String email; 15 | @NotNull(message = "用户名不可以为空") 16 | private String userName; 17 | @NotNull(message = "密码不可以为空") 18 | private String password; 19 | 20 | private String salt; 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public String getEmail() { 27 | return email; 28 | } 29 | 30 | public void setEmail(String email) { 31 | this.email = email; 32 | } 33 | 34 | public String getUserName() { 35 | return userName; 36 | } 37 | 38 | public void setUserName(String userName) { 39 | this.userName = userName; 40 | } 41 | 42 | public String getPassword() { 43 | return password; 44 | } 45 | 46 | public void setPassword(String password) { 47 | this.password = password; 48 | } 49 | 50 | public String getSalt() { 51 | return salt; 52 | } 53 | 54 | public void setSalt(String salt) { 55 | this.salt = salt; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/login/LoginController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.login; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpEntity; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import org.thiki.kanban.foundation.common.Response; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * Created by xubt on 7/5/16. 16 | */ 17 | @RestController 18 | public class LoginController { 19 | public static Logger logger = LoggerFactory.getLogger(LoginController.class); 20 | @Resource 21 | public LoginService loginService; 22 | 23 | @Resource 24 | private IdentificationResource identificationResource; 25 | 26 | @RequestMapping(value = "/login", method = RequestMethod.GET) 27 | public HttpEntity login(@RequestParam(required = false) String identity, @RequestParam(required = false) String password) throws Exception { 28 | logger.info("Login request arrived controller,identity:" + identity); 29 | Identification identification = loginService.login(identity, password); 30 | return Response.build(identificationResource.toResource(identification)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/quality/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=com.mysql.jdbc.Driver 2 | jdbc.url=jdbc:mysql://127.0.0.1:3306/thiki-kanban?useUnicode=true&characterEncoding=utf8 3 | jdbc.username=root 4 | jdbc.password=000000 5 | druid.enabled=true 6 | #datasource.type=com.alibaba.druid.pool.DruidDataSource 7 | datasource.initialSize=5 8 | datasource.minIdle=5 9 | datasource.maxActive=20 10 | datasource.maxWait=60000 11 | datasource.timeBetweenEvictionRunsMillis=60000 12 | datasource.minEvictableIdleTimeMillis=300000 13 | datasource.validationQuery=SELECT 1 FROM DUAL 14 | datasource.testWhileIdle=true 15 | datasource.testOnBorrow=false 16 | datasource.testOnReturn=false 17 | datasource.poolPreparedStatements=true 18 | datasource.maxPoolPreparedStatementPerConnectionSize=20 19 | datasource.filters=stat,wall,log4j 20 | datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 21 | datasource.druidUserName=admin 22 | datasource.druidPassword=admin 23 | server.port=8096 24 | server.contextPath=/kanban 25 | # REDIS (RedisProperties) 26 | redis.enabled=false 27 | spring.redis.host=localhost 28 | spring.redis.pool.max-idle=1000 29 | spring.redis.pool.min-idle=0 30 | spring.redis.pool.max-active=1000 31 | spring.redis.pool.max-wait=-1 32 | liquibase.change-log=classpath:/scripts/db/changelog/db.changelog-master.xml 33 | redis.scheduled.flush.cron=0 2 * * * * 34 | performanceMonitor.enabled=true 35 | -------------------------------------------------------------------------------- /src/main/resources/scripts/db/init_db_properties.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | env_name=$1 4 | env_params_path=$2 5 | server_dir=$3 6 | 7 | echo "init database properties ..." 8 | 9 | echo "env:$1" 10 | echo "env_params_path:$2" 11 | 12 | source $2 13 | 14 | if [ "$1" = "sit" ] 15 | then 16 | username=$username_sit 17 | password=$password_sit 18 | host_name=$host_name_sit 19 | http_port=$http_port_sit 20 | database_name=$database_name_sit 21 | fi 22 | 23 | if [ "$1" = "prod" ] 24 | then 25 | username=$username_prod 26 | password=$password_prod 27 | host_name=$host_name_prod 28 | http_port=$http_port_prod 29 | database_name=$database_name_prod 30 | fi 31 | 32 | echo "username: $username" 33 | echo "password: $password" 34 | echo "host_name: $host_name" 35 | echo "http_port: $http_port" 36 | 37 | echo "jdbc.driver=com.mysql.jdbc.Driver" >>src/main/resources/kanban.properties 38 | echo "jdbc.url=jdbc:mysql://$host_name:3306/$database_name?useUnicode=true&characterEncoding=utf8" >>src/main/resources/kanban.properties 39 | echo "jdbc.username=$username" >>src/main/resources/kanban.properties 40 | echo "jdbc.password=$password" >>src/main/resources/kanban.properties 41 | echo "http.port=$http_port" >>src/main/resources/kanban.properties 42 | 43 | echo "init database properties done." 44 | 45 | echo "copy deploy.sh from $server_dir directory" 46 | mv src/main/resources/scripts/deploy.sh $server_dir 47 | echo "copy done." 48 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/Profile.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * Created by xubt on 28/09/2016. 7 | */ 8 | public class Profile { 9 | 10 | private String id; 11 | private String userName; 12 | private String avatar; 13 | private String nickName; 14 | private String email; 15 | 16 | public Profile(String userName) { 17 | this.userName = userName; 18 | } 19 | 20 | public Profile() { 21 | } 22 | 23 | public String getId() { 24 | return id; 25 | } 26 | 27 | public void setId(String id) { 28 | this.id = id; 29 | } 30 | 31 | public String getAvatar() { 32 | return avatar; 33 | } 34 | 35 | public void setAvatar(String avatar) { 36 | this.avatar = avatar; 37 | } 38 | 39 | public String getUserName() { 40 | return userName; 41 | } 42 | 43 | public String getNickName() { 44 | return nickName == null ? userName : nickName; 45 | } 46 | 47 | public void setNickName(String nickName) { 48 | this.nickName = nickName; 49 | } 50 | 51 | public String getEmail() { 52 | return email; 53 | } 54 | 55 | public void setEmail(String email) { 56 | this.email = email; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return JSON.toJSONString(this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/assignmentMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | INSERT INTO kb_card_assignment(id,card_id, assignee, assigner, author) VALUES (#{id},#{cardId}, #{assignee}, 9 | #{assigner}, #{author}) 10 | 11 | 12 | 15 | 16 | 19 | 20 | 23 | 24 | 25 | UPDATE kb_card_assignment SET delete_status=1 WHERE id=#{id} AND delete_status=0 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/invitationMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | INSERT INTO 7 | kb_project_member_invitation(id, inviter,project_id,invitee) VALUES (#{id}, #{inviter}, #{projectId},#{invitee}) 8 | 9 | 10 | 13 | 14 | 16 | UPDATE kb_project_member_invitation SET delete_status=1 WHERE project_id=#{projectId} AND invitee=#{invitee} AND 17 | inviter=#{inviter} AND 18 | is_accepted=0 AND delete_status=0 19 | 20 | 21 | 22 | UPDATE kb_project_member_invitation SET is_accepted=1 WHERE project_id=#{projectId} AND invitee=#{invitee} 23 | AND is_accepted=0 AND delete_status=0 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/configuration/DataBaseDefaultConfig.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.configuration; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.jdbc.datasource.DriverManagerDataSource; 10 | 11 | import javax.sql.DataSource; 12 | 13 | /** 14 | * Created by xubt on 13/02/2017. 15 | */ 16 | @Configuration 17 | @ComponentScan 18 | @MapperScan("org.thiki") 19 | @ConditionalOnProperty(name = "druid.enabled", havingValue = "false") 20 | public class DataBaseDefaultConfig { 21 | @Value("${jdbc.driver}") 22 | private String driver; 23 | @Value("${jdbc.url}") 24 | private String url; 25 | @Value("${jdbc.username}") 26 | private String userName; 27 | @Value("${jdbc.password}") 28 | private String password; 29 | 30 | @Bean 31 | public DataSource dataSource() { 32 | DriverManagerDataSource mysql = new DriverManagerDataSource(); 33 | mysql.setDriverClassName(driver); 34 | mysql.setUrl(url); 35 | mysql.setUsername(userName); 36 | mysql.setPassword(password); 37 | return mysql; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/DBPreparation.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.springframework.jdbc.core.JdbcTemplate; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * Created by xubt on 9/14/16. 9 | */ 10 | @Service 11 | public class DBPreparation { 12 | private JdbcTemplate jdbcTemplate; 13 | private String tableName; 14 | 15 | private String fieldsNames; 16 | 17 | private Object[] fieldsValues; 18 | 19 | public void setJDBCTemplate(JdbcTemplate jdbcTemplate) { 20 | this.jdbcTemplate = jdbcTemplate; 21 | } 22 | 23 | public DBPreparation table(String tableName) { 24 | this.tableName = tableName; 25 | return this; 26 | } 27 | 28 | public DBPreparation names(String fieldsNames) { 29 | this.fieldsNames = fieldsNames; 30 | return this; 31 | } 32 | 33 | public DBPreparation values(Object... fieldsValues) { 34 | this.fieldsValues = fieldsValues; 35 | return this; 36 | } 37 | 38 | public void exec() { 39 | String[] values = new String[this.fieldsValues.length]; 40 | for (int i = 0; i < this.fieldsValues.length; i++) { 41 | values[i] = "'" + this.fieldsValues[i] + "'"; 42 | } 43 | String sql = String.format("INSERT INTO %s (%s) values(%s)", tableName, fieldsNames, StringUtils.join(values, ",").replace("''", "'")); 44 | jdbcTemplate.execute(sql); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.test.util.ReflectionTestUtils; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | /** 8 | * @author joeaniu 9 | */ 10 | @ResponseStatus(value = HttpStatus.UNAUTHORIZED) 11 | public class AuthenticationException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = 1L; 14 | protected int code; 15 | 16 | private HttpStatus httpStatus; 17 | 18 | public AuthenticationException(Object codeObject) { 19 | super((String) ReflectionTestUtils.getField(codeObject, "message")); 20 | this.code = (int) ReflectionTestUtils.invokeGetterMethod(codeObject, "code"); 21 | } 22 | 23 | public AuthenticationException(int code, String message) { 24 | super(message); 25 | this.code = code; 26 | this.httpStatus = HttpStatus.UNAUTHORIZED; 27 | } 28 | 29 | public AuthenticationException(int code, String message, HttpStatus httpStatus) { 30 | super(message); 31 | this.code = code; 32 | this.httpStatus = httpStatus; 33 | } 34 | 35 | public int getCode() { 36 | return code; 37 | } 38 | 39 | public HttpStatus getStatus() { 40 | if (httpStatus != null) { 41 | return httpStatus; 42 | } 43 | return HttpStatus.UNAUTHORIZED; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/tag/TagResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.tag; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import javax.annotation.Resource; 10 | import java.io.IOException; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | /** 16 | * Created by xubt on 11/7/16. 17 | */ 18 | @Service 19 | public class TagResource extends RestResource { 20 | @Resource 21 | private TLink tlink; 22 | 23 | @Cacheable(value = "tag", key = "#userName+#boardId+#tag.id") 24 | public Object toResource(Tag tag, String boardId, String userName) throws IOException { 25 | TagResource tagResource = new TagResource(); 26 | tagResource.domainObject = tag; 27 | if (tag != null) { 28 | Link selfLink = linkTo(methodOn(TagsController.class).findById(boardId, tag.getId(), userName)).withSelfRel(); 29 | tagResource.add(tlink.from(selfLink).build(userName)); 30 | 31 | Link tagsLink = linkTo(methodOn(TagsController.class).loadTagsByBoard(boardId, userName)).withRel("tags"); 32 | tagResource.add(tlink.from(tagsLink).build(userName)); 33 | } 34 | return tagResource.getResource(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/comment/CommentServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.comment; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.MockitoJUnit; 9 | import org.mockito.junit.MockitoRule; 10 | import org.thiki.kanban.acceptanceCriteria.AcceptanceCriteriaCodes; 11 | import org.thiki.kanban.card.CardsService; 12 | import org.thiki.kanban.foundation.exception.BusinessException; 13 | 14 | import static org.mockito.Matchers.any; 15 | import static org.mockito.Mockito.when; 16 | 17 | /** 18 | * Created by xubt on 22/03/2017. 19 | */ 20 | public class CommentServiceTest { 21 | @Rule 22 | public MockitoRule rule = MockitoJUnit.rule(); 23 | @Rule 24 | public ExpectedException expectedException = ExpectedException.none(); 25 | @InjectMocks 26 | private CommentService commentService; 27 | @Mock 28 | private CardsService cardsService; 29 | private String cardId = "card-fooId"; 30 | private String userName = "someone"; 31 | 32 | @Test 33 | public void should_adding_comment_failed_when_card_is_done_or_archived() { 34 | expectedException.expect(BusinessException.class); 35 | expectedException.expectMessage(AcceptanceCriteriaCodes.CARD_WAS_ALREADY_DONE_OR_ARCHIVED.message()); 36 | 37 | when(cardsService.isCardArchivedOrDone(cardId)).thenReturn(true); 38 | commentService.addComment(userName, cardId, any()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/notificationMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | INSERT INTO 8 | kb_notification(id,receiver,sender,title,content,link,type) VALUES 9 | (#{id},#{receiver},#{sender},#{title},#{content},#{link},#{type}) 10 | 11 | 12 | 15 | 16 | 20 | 21 | 24 | 25 | 27 | UPDATE kb_notification SET is_read=1 WHERE id=#{id} AND delete_status=0 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/hateoas/Action.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.hateoas; 2 | 3 | import org.thiki.kanban.foundation.security.authentication.MethodType; 4 | 5 | /** 6 | * Created by xubt on 16/12/2016. 7 | */ 8 | public class Action { 9 | private String actionName; 10 | private boolean isAllowed; 11 | 12 | public String getActionName() { 13 | return actionName; 14 | } 15 | 16 | public void setActionName(String actionName) { 17 | if (MethodType.GET.name().equals(actionName)) { 18 | this.actionName = "read"; 19 | } 20 | if (MethodType.POST.name().equals(actionName)) { 21 | this.actionName = "saveCard"; 22 | } 23 | if (MethodType.PUT.name().equals(actionName)) { 24 | this.actionName = "modify"; 25 | } 26 | if (MethodType.DELETE.name().equals(actionName)) { 27 | this.actionName = "delete"; 28 | } 29 | } 30 | 31 | public boolean isAllowed() { 32 | return isAllowed; 33 | } 34 | 35 | public void setAllowed(boolean allowed) { 36 | isAllowed = allowed; 37 | } 38 | 39 | public void setMethod(String actionName, boolean isAllowed) { 40 | this.actionName = actionName; 41 | this.isAllowed = isAllowed; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "Action{" + 47 | "actionName='" + actionName + '\'' + 48 | ", isAllowed=" + isAllowed + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/notification/NotificationServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnit; 8 | import org.mockito.junit.MockitoRule; 9 | import org.thiki.kanban.assignment.AssignmentMail; 10 | import org.thiki.kanban.foundation.mail.MailService; 11 | 12 | import static org.mockito.Matchers.any; 13 | import static org.mockito.Matchers.eq; 14 | import static org.mockito.Mockito.*; 15 | 16 | /** 17 | * Created by xubt on 28/02/2017. 18 | */ 19 | public class NotificationServiceTest { 20 | @Rule 21 | public MockitoRule rule = MockitoJUnit.rule(); 22 | @Mock 23 | private MailService mailService; 24 | @Mock 25 | private NotificationPersistence notificationPersistence; 26 | @InjectMocks 27 | private NotificationService notificationService; 28 | 29 | @Test 30 | public void should_send_email_after_notifying() throws Exception { 31 | Notification notification = new Notification(); 32 | when(notificationPersistence.create(notification)).thenReturn(1); 33 | doNothing().when(mailService).sendMailByTemplate(any(), eq(notification.getTitle())); 34 | AssignmentMail assignmentMail = new AssignmentMail(); 35 | assignmentMail.setNotificationType(NotificationType.ASSIGNMENT_INVITATION); 36 | notificationService.sendEmailAfterNotifying(assignmentMail); 37 | 38 | verify(mailService).sendMailByTemplate(any()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/ProfileResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import javax.annotation.Resource; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | /** 16 | * Created by xubt on 30/09/2016. 17 | */ 18 | @Service 19 | public class ProfileResource extends RestResource { 20 | @Resource 21 | private TLink tlink; 22 | 23 | @Cacheable(value = "profile", key = "'profile'+#userName") 24 | public Object toResource(Profile profile, String userName) throws Exception { 25 | ProfileResource profileResource = new ProfileResource(); 26 | profileResource.domainObject = profile; 27 | Link selfLink = linkTo(methodOn(UsersController.class).loadProfile(profile.getUserName())).withSelfRel(); 28 | profileResource.add(tlink.from(selfLink).build(userName)); 29 | Link avatarLink = linkTo(UsersController.class, UsersController.class.getMethod("loadAvatar", String.class, HttpServletResponse.class), profile.getUserName()).withRel("avatar"); 30 | profileResource.add(tlink.from(avatarLink).build(userName)); 31 | return profileResource.getResource(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/acceptanceCriteria/AcceptanceCriteriaPersistence.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.acceptanceCriteria; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.springframework.stereotype.Repository; 5 | import org.thiki.kanban.verification.Verification; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by xubt on 10/17/16. 11 | */ 12 | @Repository 13 | public interface AcceptanceCriteriaPersistence { 14 | Integer addAcceptCriteria(@Param("userName") String userName, @Param("cardId") String cardId, @Param("acceptanceCriteria") AcceptanceCriteria acceptanceCriteria); 15 | 16 | AcceptanceCriteria findById(String id); 17 | 18 | List loadAcceptanceCriteriasByCardId(@Param("cardId") String cardId); 19 | 20 | Integer updateAcceptCriteria(@Param("acceptanceCriteriaId") String acceptanceCriteriaId, @Param("acceptanceCriteria") AcceptanceCriteria acceptanceCriteria); 21 | 22 | Integer deleteAcceptanceCriteria(String acceptanceCriteriaId); 23 | 24 | Integer resort(@Param("acceptanceCriteria") AcceptanceCriteria acceptanceCriteria); 25 | 26 | boolean isHasUnFinishedAcceptanceCriterias(@Param("cardId") String cardId); 27 | 28 | void verify(@Param("cardId") String cardId, @Param("acceptanceCriteriaId") String acceptanceCriteriaId, @Param("verification") Verification verification); 29 | 30 | boolean isHasAcceptanceCriterias(@Param("cardId") String cardId); 31 | 32 | boolean isExistSpecifiedPassedStatusAcceptanceCriteria(@Param("cardId") String cardId, @Param("passedStatus") Integer passedStatus); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/invitation/InvitationResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.invitation; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.stereotype.Service; 5 | import org.thiki.kanban.foundation.common.RestResource; 6 | import org.thiki.kanban.foundation.hateoas.TLink; 7 | import org.thiki.kanban.projects.project.ProjectsController; 8 | 9 | import javax.annotation.Resource; 10 | 11 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | /** 15 | * Created by bogehu on 9/12/16. 16 | */ 17 | @Service 18 | public class InvitationResource extends RestResource { 19 | 20 | @Resource 21 | private TLink tlink; 22 | 23 | public Object toResource(String userName, String projectId, Invitation invitation) throws Exception { 24 | InvitationResource invitationResource = new InvitationResource(); 25 | invitationResource.domainObject = invitation; 26 | if (invitation != null) { 27 | Link selfLink = linkTo(methodOn(InvitationController.class).acceptInvitation(projectId, invitation.getId(), userName)).withSelfRel(); 28 | invitationResource.add(tlink.from(selfLink).build(userName)); 29 | 30 | Link projectLink = linkTo(methodOn(ProjectsController.class).findById(projectId, userName)).withRel("project"); 31 | invitationResource.add(tlink.from(projectLink).build(userName)); 32 | } 33 | return invitationResource.getResource(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.test.util.ReflectionTestUtils; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | 7 | /** 8 | * @author joeaniu 9 | */ 10 | @ResponseStatus(value = HttpStatus.BAD_REQUEST) 11 | public class BusinessException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = 1L; 14 | protected int code; 15 | 16 | private HttpStatus httpStatus; 17 | 18 | public BusinessException(String message) { 19 | this(ExceptionCode.UNKNOWN_EX.code(), message); 20 | } 21 | 22 | public BusinessException(Object codeObject) { 23 | super((String) ReflectionTestUtils.getField(codeObject, "message")); 24 | this.code = (int) ReflectionTestUtils.invokeGetterMethod(codeObject, "code"); 25 | } 26 | 27 | public BusinessException(int code, String message) { 28 | super(message); 29 | this.code = code; 30 | this.httpStatus = HttpStatus.BAD_REQUEST; 31 | } 32 | 33 | public BusinessException(int code, String message, HttpStatus httpStatus) { 34 | super(message); 35 | this.code = code; 36 | this.httpStatus = httpStatus; 37 | } 38 | 39 | public int getCode() { 40 | return code; 41 | } 42 | 43 | public HttpStatus getStatus() { 44 | if (httpStatus != null) { 45 | return httpStatus; 46 | } 47 | return HttpStatus.BAD_REQUEST; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/publickey/PublicKeyResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.publickey; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | import org.thiki.kanban.login.LoginController; 9 | import org.thiki.kanban.user.registration.RegistrationController; 10 | 11 | import javax.annotation.Resource; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | /** 17 | * Created by xubt on 7/5/16. 18 | */ 19 | @Service 20 | public class PublicKeyResource extends RestResource { 21 | @Resource 22 | private TLink tlink; 23 | 24 | @Cacheable(value = "publicKey", key = "'publicKey-'+#root.methodName") 25 | public Object toResource(PublicKey publicPublicKey) throws Exception { 26 | PublicKeyResource publicKeyResource = new PublicKeyResource(); 27 | publicKeyResource.domainObject = publicPublicKey; 28 | Link loginLink = linkTo(methodOn(LoginController.class).login(null, null)).withRel("login"); 29 | publicKeyResource.add(tlink.from(loginLink).build()); 30 | 31 | Link registrationLink = linkTo(methodOn(RegistrationController.class).registerNewUser(null)).withRel("registration"); 32 | publicKeyResource.add(tlink.from(registrationLink).build()); 33 | return publicKeyResource.getResource(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/risk/RiskService.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.risk; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.cache.annotation.CacheEvict; 7 | import org.springframework.cache.annotation.Cacheable; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by cain on 2017/2/28. 14 | */ 15 | @Service 16 | public class RiskService { 17 | 18 | public static Logger logger = LoggerFactory.getLogger(RiskService.class); 19 | @Autowired 20 | RiskPersistence riskPersistence; 21 | 22 | @CacheEvict(value = "risk", key = "contains('#cardId')", allEntries = true) 23 | public Risk addRisk(String userName, String cardId, Risk risk) { 24 | risk.setRiskResolved(false); 25 | riskPersistence.addRisk(userName, cardId, risk); 26 | Risk savedRisk = riskPersistence.findRiskById(risk.getId()); 27 | logger.info("Saved risk:{}", savedRisk); 28 | return savedRisk; 29 | } 30 | 31 | public Risk loadRiskById(String riskId) { 32 | return riskPersistence.findRiskById(riskId); 33 | } 34 | 35 | @Cacheable(value = "risk", key = "'risk'+#cardId") 36 | public List loadCardRisks(String cardId) { 37 | return riskPersistence.findCardRisks(cardId); 38 | } 39 | 40 | @CacheEvict(value = "risk", key = "contains('#cardId')", allEntries = true) 41 | public Integer removeRisk(String riskId) { 42 | return riskPersistence.deleteRisk(riskId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/MapUtil.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import java.util.Comparator; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.TreeMap; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Created by xubt on 9/17/16. 12 | */ 13 | public class MapUtil { 14 | public static Map sortMapByKey(Map oriMap) { 15 | if (oriMap == null || oriMap.isEmpty()) { 16 | return null; 17 | } 18 | Map sortedMap = new TreeMap<>(new Comparator() { 19 | public int compare(String key1, String key2) { 20 | int intKey1, 21 | intKey2; 22 | try { 23 | intKey1 = getInt(key1); 24 | intKey2 = getInt(key2); 25 | } catch (Exception e) { 26 | intKey1 = 0; 27 | intKey2 = 0; 28 | } 29 | return intKey1 - intKey2; 30 | } 31 | }); 32 | sortedMap.putAll(oriMap); 33 | return sortedMap; 34 | } 35 | 36 | private static int getInt(String str) { 37 | int i = 0; 38 | try { 39 | Pattern p = Pattern.compile("^\\d+"); 40 | Matcher m = p.matcher(str); 41 | if (m.find()) { 42 | i = Integer.valueOf(m.group()); 43 | } 44 | } catch (NumberFormatException e) { 45 | e.printStackTrace(); 46 | } 47 | return i; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/project/Project.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.project; 2 | 3 | import org.hibernate.validator.constraints.Length; 4 | import org.hibernate.validator.constraints.NotEmpty; 5 | 6 | import javax.validation.constraints.NotNull; 7 | 8 | /** 9 | * Created by bogehu on 7/11/16. 10 | */ 11 | 12 | public class Project { 13 | private String id; 14 | @NotEmpty(message = ProjectCodes.nameIsRequired) 15 | @NotNull(message = ProjectCodes.nameIsRequired) 16 | @Length(max = 20, message = ProjectCodes.nameIsInvalid) 17 | private String name; 18 | private String author; 19 | private String creationTime; 20 | private String modificationTime; 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String getAuthor() { 39 | return author; 40 | } 41 | 42 | public void setAuthor(String author) { 43 | this.author = author; 44 | } 45 | 46 | public String getModificationTime() { 47 | return modificationTime; 48 | } 49 | 50 | public void setModificationTime(String modificationTime) { 51 | this.modificationTime = modificationTime; 52 | } 53 | 54 | public String getCreationTime() { 55 | return creationTime; 56 | } 57 | 58 | public void setCreationTime(String creationTime) { 59 | this.creationTime = creationTime; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/date/DateStyle.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common.date; 2 | 3 | /** 4 | * Created by 濤 on 2016/1/12. 5 | */ 6 | public enum DateStyle { 7 | YYYY_MM("yyyy-MM", false), 8 | YYYY_MM_DD("yyyy-MM-dd", false), 9 | YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm", false), 10 | YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss", false), 11 | 12 | YYYY_MM_EN("yyyy/MM", false), 13 | YYYY_MM_DD_EN("yyyy/MM/dd", false), 14 | YYYY_MM_DD_HH_MM_EN("yyyy/MM/dd HH:mm", false), 15 | YYYY_MM_DD_HH_MM_SS_EN("yyyy/MM/dd HH:mm:ss", false), 16 | 17 | YYYY_MM_CN("yyyy年MM月", false), 18 | YYYY_MM_DD_CN("yyyy年MM月dd日", false), 19 | YYYY_MM_DD_HH_MM_CN("yyyy年MM月dd日 HH:mm", false), 20 | YYYY_MM_DD_HH_MM_SS_CN("yyyy年MM月dd日 HH:mm:ss", false), 21 | 22 | HH_MM("HH:mm", true), 23 | HH_MM_SS("HH:mm:ss", true), 24 | 25 | MM_DD("MM-dd", true), 26 | MM_DD_HH_MM("MM-dd HH:mm", true), 27 | MM_DD_HH_MM_SS("MM-dd HH:mm:ss", true), 28 | 29 | MM_DD_EN("MM/dd", true), 30 | MM_DD_HH_MM_EN("MM/dd HH:mm", true), 31 | MM_DD_HH_MM_SS_EN("MM/dd HH:mm:ss", true), 32 | 33 | MM_DD_CN("MM月dd日", true), 34 | MM_DD_HH_MM_CN("MM月dd日 HH:mm", true), 35 | MM_DD_HH_MM_SS_CN("MM月dd日 HH:mm:ss", true); 36 | 37 | private String value; 38 | 39 | private boolean isShowOnly; 40 | 41 | DateStyle(String value, boolean isShowOnly) { 42 | this.value = value; 43 | this.isShowOnly = isShowOnly; 44 | } 45 | 46 | public String getValue() { 47 | return value; 48 | } 49 | 50 | public boolean isShowOnly() { 51 | return isShowOnly; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/cardTags/CardTagsServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.cardTags; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.MockitoJUnit; 9 | import org.mockito.junit.MockitoRule; 10 | import org.thiki.kanban.acceptanceCriteria.AcceptanceCriteriaCodes; 11 | import org.thiki.kanban.card.CardsService; 12 | import org.thiki.kanban.foundation.exception.BusinessException; 13 | 14 | import static org.mockito.Matchers.anyList; 15 | import static org.mockito.Mockito.when; 16 | 17 | /** 18 | * Created by xubt on 22/03/2017. 19 | */ 20 | public class CardTagsServiceTest { 21 | @Rule 22 | public MockitoRule rule = MockitoJUnit.rule(); 23 | @Rule 24 | public ExpectedException expectedException = ExpectedException.none(); 25 | @InjectMocks 26 | private CardTagsService cardTagsService; 27 | @Mock 28 | private CardsService cardsService; 29 | private String cardId = "card-fooId"; 30 | private String boardId = "board-fooId"; 31 | private String userName = "someone"; 32 | private String acceptanceCriteriaId = "acceptanceCriteria-fooId"; 33 | 34 | @Test 35 | public void should_stick_failed_when_card_is_done_or_archived() { 36 | expectedException.expect(BusinessException.class); 37 | expectedException.expectMessage(AcceptanceCriteriaCodes.CARD_WAS_ALREADY_DONE_OR_ARCHIVED.message()); 38 | 39 | when(cardsService.isCardArchivedOrDone(cardId)).thenReturn(true); 40 | cardTagsService.stickTags(anyList(), cardId, boardId, userName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/resources/import/worktile_tasks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tid": "fa2815d18b254a4abeb8b2f453148355", 4 | "name": "任务名称", 5 | "desc": "", 6 | "pos": 1589228.75, 7 | "expire_date": "", 8 | "completed": 0, 9 | "archived": 0, 10 | "completed_at": "", 11 | "create_at": "2016-06-21T00:54:13.501Z", 12 | "create_by": { 13 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 14 | "name": "xubitao", 15 | "display_name": "徐必涛" 16 | }, 17 | "update_at": "2016-08-05T15:39:27.390Z", 18 | "update_by": { 19 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 20 | "name": "xubitao", 21 | "display_name": "徐必涛" 22 | }, 23 | "project": { 24 | "pid": "2187f189117544e8b1397613afac921c", 25 | "name": "thiki-kanban product backlog" 26 | }, 27 | "entry": { 28 | "entry_id": "0eae9291d2ad4b029df3f798dbf820a9", 29 | "name": "Product Backlog" 30 | }, 31 | "labels": [ 32 | { 33 | "name": "young_blue", 34 | "desc": "用户故事", 35 | "_id": "5774c5da3a70fd66302dcfd8" 36 | } 37 | ], 38 | "assignee": [], 39 | "watchers": [ 40 | { 41 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 42 | "name": "xubitao", 43 | "display_name": "徐必涛" 44 | }, 45 | { 46 | "uid": "bd636b0c317d400b870d5455f7e330a8", 47 | "name": "zengzhu_hoolai", 48 | "display_name": "曾著" 49 | } 50 | ], 51 | "todos": [ 52 | { 53 | "todo_id": "e17f667ec35e42ea85a314e0bb9af635", 54 | "name": "todo", 55 | "pos": 65535, 56 | "checked": 1 57 | } 58 | ], 59 | "attachments": [] 60 | } 61 | ] 62 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/Constants.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security; 2 | 3 | /** 4 | * Created by xubt on 7/10/16. 5 | */ 6 | public class Constants { 7 | public static final String SECURITY_IDENTIFY_PASSED = "passed"; 8 | public static final String SECURITY_IDENTITY_NO_AUTHENTICATION_TOKEN = "当前用户未认证,请先登录认证。"; 9 | public static final String SECURITY_IDENTITY_AUTHENTICATION_TOKEN_HAS_EXPIRE = "认证已经过期,请重新认证获取token."; 10 | public static final String SECURITY_IDENTITY_USER_NAME_IS_NOT_CONSISTENT = "请求头部的用户名与token中的不一致,请求可能被篡改。"; 11 | public static final String SECURITY_USERNAME_IN_HEADER_IS_NOT_CONSISTENT_WITH_PATH = "请求头部的用户名与URL中的不一致,请求可能被篡改。"; 12 | public static final String LOCAL_ADDRESS = "127.0.0.1"; 13 | public static final String FREE_IDENTIFICATION = "no"; 14 | public static final String HEADER_PARAMS_TOKEN = "token"; 15 | public static final String HEADER_PARAMS_USER_NAME = "userName"; 16 | public static final String HEADER_PARAMS_IDENTIFICATION = "identification"; 17 | public static final int TOKEN_EXPIRED_TIME = 1000; 18 | public static final int SECURITY_IDENTITY_NO_AUTHENTICATION_TOKEN_CODE = 1101; 19 | public static final int SECURITY_IDENTITY_AUTHENTICATION_TOKEN_HAS_EXPIRE_CODE = 1102; 20 | public static final int SECURITY_IDENTITY_USER_NAME_IS_NOT_CONSISTENT_CODE = 1103; 21 | public static final int SECURITY_IDENTIFY_PASSED_CODE = 1100; 22 | public static final int SECURITY_IDENTIFY_UN_KNOW = 9999; 23 | public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; 24 | public static final String HEADER_PARAMS_AUTHENTICATION = "authentication"; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/notification/UnreadNotificationsResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import javax.annotation.Resource; 10 | 11 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 13 | 14 | /** 15 | * Created by xubt on 9/18/16. 16 | */ 17 | @Service 18 | public class UnreadNotificationsResource extends RestResource { 19 | @Resource 20 | private TLink tlink; 21 | 22 | @Cacheable(value = "notification", key = "#userName+'unreadNotificationTotal'") 23 | public Object toResource(String userName, Integer unreadNotificationTotal) throws Exception { 24 | UnreadNotificationsResource unreadNotificationsResource = new UnreadNotificationsResource(); 25 | unreadNotificationsResource.buildDataObject("unreadNotificationsTotal", unreadNotificationTotal); 26 | Link selfLink = linkTo(methodOn(NotificationController.class).loadUnreadNotificationsTotal(userName)).withSelfRel(); 27 | unreadNotificationsResource.add(tlink.from(selfLink).build(userName)); 28 | 29 | Link notificationsLink = linkTo(methodOn(NotificationController.class).loadNotifications(userName)).withRel("notifications"); 30 | unreadNotificationsResource.add(tlink.from(notificationsLink).build(userName)); 31 | return unreadNotificationsResource.getResource(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/cardTags/CardTagsController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.cardTags; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpEntity; 5 | import org.springframework.web.bind.annotation.*; 6 | import org.thiki.kanban.foundation.common.Response; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by xubt on 11/14/16. 13 | */ 14 | @RestController 15 | public class CardTagsController { 16 | @Autowired 17 | private CardTagsService cardTagsService; 18 | @Resource 19 | private CardTagsResource cardTagsResource; 20 | 21 | @RequestMapping(value = "/boards/{boardId}/stages/{stageId}/cards/{cardId}/tags", method = RequestMethod.POST) 22 | public HttpEntity stick(@RequestBody List cardTags, @PathVariable String boardId, @PathVariable String stageId, @PathVariable String cardId, @RequestHeader String userName) throws Exception { 23 | List stickCardTags = cardTagsService.stickTags(cardTags, cardId, boardId, userName); 24 | 25 | return Response.post(cardTagsResource.toResource(stickCardTags, boardId, stageId, cardId, userName)); 26 | } 27 | 28 | @RequestMapping(value = "/boards/{boardId}/stages/{stageId}/cards/{cardId}/tags", method = RequestMethod.GET) 29 | public HttpEntity loadTags(@PathVariable String boardId, @PathVariable String stageId, @PathVariable String cardId, @RequestHeader String userName) throws Exception { 30 | List stickCardTags = cardTagsService.loadTags(cardId, boardId); 31 | 32 | return Response.build(cardTagsResource.toResource(stickCardTags, boardId, stageId, cardId, userName)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/entrance/EntranceControllerTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.entrance; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | import org.thiki.kanban.AuthenticationTestBase; 7 | import org.thiki.kanban.foundation.annotations.Domain; 8 | import org.thiki.kanban.foundation.annotations.Scenario; 9 | import org.thiki.kanban.foundation.application.DomainOrder; 10 | 11 | import static com.jayway.restassured.RestAssured.given; 12 | import static org.hamcrest.CoreMatchers.equalTo; 13 | import static org.hamcrest.CoreMatchers.nullValue; 14 | import static org.hamcrest.core.StringEndsWith.endsWith; 15 | 16 | /** 17 | * Created by xubt on 5/18/16. 18 | */ 19 | @Domain(order = DomainOrder.ENTRANCE, name = "入口") 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | public class EntranceControllerTest extends AuthenticationTestBase { 22 | @Scenario("初次访问系统时入口") 23 | @Test 24 | public void enter_shouldReturnEntranceSuccessfully() throws Exception { 25 | given().when() 26 | .get("/entrance") 27 | .then() 28 | .statusCode(200) 29 | .body("description", equalTo("Welcome!")) 30 | .body("_links.self.href", endsWith("/entrance")) 31 | .body("_links.self.actions.assign", nullValue()) 32 | .body("_links.self.actions.read.isAllowed", equalTo(true)) 33 | .body("_links.self.actions.modify", nullValue()) 34 | .body("_links.self.actions.delete", nullValue()) 35 | .body("_links.passwordRetrievalApplication.href", endsWith("/passwordRetrievalApplication")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/security/authentication/RolesResources.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.security.authentication; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * Created by xubt on 17/12/2016. 14 | */ 15 | @Configuration 16 | @EnableConfigurationProperties 17 | @ConfigurationProperties(locations = {"classpath:roles-resources-mapping.yml"}) 18 | @Service 19 | public class RolesResources { 20 | @Value("${server.contextPath}") 21 | protected String contextPath; 22 | private List roles = new ArrayList<>(); 23 | 24 | public List getRoles() { 25 | return roles; 26 | } 27 | 28 | public void setRoles(List roles) { 29 | this.roles = roles; 30 | } 31 | 32 | public MatchResult match(String url, String method) { 33 | MatchResult matchResult = new MatchResult(); 34 | for (Role role : roles) { 35 | List resourceTemplates = role.getResources(); 36 | for (ResourceTemplate resourceTemplate : resourceTemplates) { 37 | if (resourceTemplate.match(url, method, contextPath)) { 38 | matchResult.setRoleName(role.getName()); 39 | matchResult.setPathValues(resourceTemplate.getPathValues(url)); 40 | } 41 | } 42 | } 43 | return matchResult; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/config/application-local-xiong.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=com.mysql.jdbc.Driver 2 | jdbc.url=jdbc:mysql://192.168.10.239:3306/thiki_kanban?useUnicode=true&characterEncoding=utf8 3 | jdbc.username=mylive 4 | jdbc.password=devsgo 5 | druid.enabled=false 6 | #datasource.type=com.alibaba.druid.pool.DruidDataSource 7 | datasource.initialSize=5 8 | datasource.minIdle=5 9 | datasource.maxActive=20 10 | datasource.maxWait=60000 11 | datasource.timeBetweenEvictionRunsMillis=60000 12 | datasource.minEvictableIdleTimeMillis=300000 13 | datasource.validationQuery=SELECT 1 FROM DUAL 14 | datasource.testWhileIdle=true 15 | datasource.testOnBorrow=false 16 | datasource.testOnReturn=false 17 | datasource.poolPreparedStatements=true 18 | datasource.maxPoolPreparedStatementPerConnectionSize=20 19 | datasource.filters=stat,wall,log4j 20 | datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 21 | datasource.druidUserName=admin 22 | datasource.druidPassword=admin 23 | server.port=8096 24 | server.contextPath=/kanban 25 | # REDIS (RedisProperties) 26 | redis.enabled=false 27 | spring.redis.host=localhost 28 | spring.redis.pool.max-idle=1000 29 | spring.redis.pool.min-idle=0 30 | spring.redis.pool.max-active=1000 31 | spring.redis.pool.max-wait=-1 32 | liquibase.change-log=classpath:/scripts/db/changelog/db.changelog-master.xml 33 | spring.boot.admin.client.enabled=false 34 | spring.boot.admin.url=http://localhost:8096/kanban 35 | spring.jackson.serialization.indent_output=true 36 | endpoints.health.sensitive=false 37 | management.security.enabled=false 38 | security.user.name=admin 39 | security.user.password=secret 40 | spring.boot.admin.username=admin 41 | spring.boot.admin.password=secret 42 | spring.application.name=kanban 43 | info.version=0.1 44 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/notification/NotificationResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.Optional; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | /** 16 | * Created by xubt on 9/17/16. 17 | */ 18 | 19 | @Service 20 | public class NotificationResource extends RestResource { 21 | @Resource 22 | private TLink tlink; 23 | 24 | @Cacheable(value = "notification", key = "#userName+#notification.id") 25 | public Object toResource(String userName, Notification notification) throws Exception { 26 | NotificationResource notificationResource = new NotificationResource(); 27 | notificationResource.domainObject = notification; 28 | Optional notificationEmpty = Optional.ofNullable(notification); 29 | if (notificationEmpty.isPresent()) { 30 | Link selfLink = linkTo(methodOn(NotificationController.class).loadNotificationById(notification.getId(), userName)).withSelfRel(); 31 | notificationResource.add(tlink.from(selfLink).build(userName)); 32 | } 33 | Link notificationsLink = linkTo(methodOn(NotificationController.class).loadNotifications(userName)).withRel("notifications"); 34 | notificationResource.add(tlink.from(notificationsLink).build(userName)); 35 | return notificationResource.getResource(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/pagesMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | INSERT INTO 9 | kb_page(id,title, content, board_id,author) VALUES (#{page.id},#{page.title}, 10 | #{page.content}, #{boardId}, #{userName}) 11 | 12 | 13 | 14 | UPDATE kb_page SET 15 | title = #{page.title}, 16 | content = #{page.content} 17 | WHERE id=#{pageId} AND board_id=#{boardId} AND delete_status=0 18 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 33 | UPDATE kb_page SET delete_status=1 WHERE id=#{pageId} AND board_id=#{boardId} AND delete_status=0 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/logback/SessionInterceptor.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.logback; 2 | 3 | /** 4 | * Created by xubt on 20/12/2016. 5 | */ 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.slf4j.MDC; 10 | import org.springframework.web.servlet.ModelAndView; 11 | import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 12 | import org.thiki.kanban.foundation.common.SequenceNumber; 13 | 14 | import javax.servlet.http.HttpServletRequest; 15 | import javax.servlet.http.HttpServletResponse; 16 | 17 | public class SessionInterceptor extends HandlerInterceptorAdapter { 18 | private final static String SESSION_KEY = "sessionId"; 19 | private final static String IP_KEY = "ip"; 20 | private static Logger logger = LoggerFactory.getLogger(SessionInterceptor.class); 21 | 22 | @Override 23 | public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse arg1, Object arg2, Exception arg3) 24 | throws Exception { 25 | logger.info("remove sessionId."); 26 | MDC.remove(SESSION_KEY); 27 | } 28 | 29 | @Override 30 | public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { 31 | } 32 | 33 | @Override 34 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 35 | SequenceNumber sequenceNumber = new SequenceNumber(); 36 | String sessionId = sequenceNumber.generate(); 37 | String ip = request.getRemoteHost(); 38 | MDC.put(SESSION_KEY, sessionId); 39 | MDC.put(IP_KEY, ip); 40 | logger.info("init session:{}", sessionId); 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/org/thiki/kanban/mybatis/boardsMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | INSERT INTO 9 | kb_board(id,name,project_id, author,owner) VALUES (#{id},#{name},#{projectId}, #{author}, #{author}) 10 | 11 | 12 | 13 | UPDATE kb_board SET 14 | name=#{name} 15 | 16 | ,owner=#{projectId} 17 | ,project_id=#{projectId} 18 | 19 | WHERE id=#{id} AND delete_status=0 20 | 21 | 29 | 30 | 33 | 34 | 35 | 36 | UPDATE kb_board SET delete_status=1 WHERE id=#{id} AND owner= #{userName} 37 | 38 | 39 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/sprint/SprintResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.sprint; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.hateoas.Link; 6 | import org.springframework.stereotype.Service; 7 | import org.thiki.kanban.board.BoardsController; 8 | import org.thiki.kanban.foundation.common.RestResource; 9 | import org.thiki.kanban.foundation.hateoas.TLink; 10 | 11 | import javax.annotation.Resource; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | /** 17 | * Created by xubt on 04/02/2017. 18 | */ 19 | @Service 20 | public class SprintResource extends RestResource { 21 | public static Logger logger = LoggerFactory.getLogger(SprintResource.class); 22 | @Resource 23 | private TLink tlink; 24 | 25 | public Object toResource(Sprint sprint, String boardId, String projectId, String userName) throws Exception { 26 | logger.info("build sprint resource.board:{},userName:{}", boardId, userName); 27 | SprintResource sprintResource = new SprintResource(); 28 | sprintResource.domainObject = sprint; 29 | if (sprint != null) { 30 | Link selfLink = linkTo(methodOn(SprintController.class).findById(sprint.getId(), boardId, projectId, userName)).withSelfRel(); 31 | sprintResource.add(tlink.from(selfLink).build(userName)); 32 | 33 | Link boardLink = linkTo(methodOn(BoardsController.class).findById(boardId, projectId, userName)).withRel("board"); 34 | sprintResource.add(tlink.from(boardLink).build(userName)); 35 | } 36 | logger.info("sprint resource building completed.board:{},userName:{}", boardId, userName); 37 | return sprintResource.getResource(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/entrance/EntranceResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.entrance; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.springframework.cache.annotation.Cacheable; 5 | import org.springframework.hateoas.Link; 6 | import org.springframework.stereotype.Service; 7 | import org.thiki.kanban.foundation.common.RestResource; 8 | import org.thiki.kanban.foundation.hateoas.TLink; 9 | import org.thiki.kanban.password.PasswordController; 10 | import org.thiki.kanban.publickey.PublicKeyController; 11 | 12 | import javax.annotation.Resource; 13 | 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 15 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 16 | 17 | /** 18 | * Created by xubitao on 04/26/16. 19 | */ 20 | @Service 21 | public class EntranceResource extends RestResource { 22 | @Resource 23 | private TLink tlink; 24 | 25 | @Cacheable(value = "entrance-desc", key = "#root.methodName") 26 | public Object toResource() throws Exception { 27 | EntranceResource entranceResource = new EntranceResource(); 28 | entranceResource.domainObject = new JSONObject() {{ 29 | put("description", "Welcome!"); 30 | }}; 31 | 32 | Link selfLink = linkTo(EntranceController.class).withSelfRel(); 33 | Link publicKeyLink = linkTo(methodOn(PublicKeyController.class).identify()).withRel("publicKey"); 34 | Link passwordRetrievalLink = linkTo(methodOn(PasswordController.class).passwordRetrievalApply(null)).withRel("passwordRetrievalApplication"); 35 | 36 | entranceResource.add(tlink.from(selfLink).build()); 37 | entranceResource.add(tlink.from(publicKeyLink).build()); 38 | entranceResource.add(tlink.from(passwordRetrievalLink).build()); 39 | return entranceResource.getResource(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/date/DateUtil.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common.date; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | /** 7 | * Created by xubt on 23/09/2016. 8 | */ 9 | public class DateUtil { 10 | 11 | public static String showTime(String date) { 12 | 13 | DateService dateService = new DateService(); 14 | Date time = dateService.StringToDate(date, DateStyle.YYYY_MM_DD_HH_MM_SS); 15 | 16 | String convertResult = ""; 17 | if (time == null) return convertResult; 18 | String format; 19 | 20 | long nowTimeLong = System.currentTimeMillis(); 21 | 22 | long originTimeLong = time.getTime(); 23 | long diffTimeLong = Math.abs(nowTimeLong - originTimeLong); 24 | 25 | if (diffTimeLong < 60000) {// 一分钟内 26 | long seconds = diffTimeLong / 1000; 27 | if (seconds == 0) { 28 | convertResult = "刚刚"; 29 | } else { 30 | convertResult = seconds + "秒前"; 31 | } 32 | } else if (diffTimeLong >= 60000 && diffTimeLong < 3600000) {// 一小时内 33 | long seconds = diffTimeLong / 60000; 34 | convertResult = seconds + "分钟前"; 35 | } else if (diffTimeLong >= 3600000 && diffTimeLong < 86400000) {// 一天内 36 | long seconds = diffTimeLong / 3600000; 37 | if (dateService.getDay(date) < dateService.getDay(new Date())) { 38 | convertResult = "昨天"; 39 | } else { 40 | convertResult = seconds + "小时前"; 41 | } 42 | } else {// 日期格式 43 | format = "MM-dd HH:mm"; 44 | SimpleDateFormat df = new SimpleDateFormat(format); 45 | convertResult = df.format(time).toString(); 46 | } 47 | return convertResult; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/stage/ResortStagesResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.stage; 2 | 3 | import org.springframework.hateoas.Link; 4 | import org.springframework.stereotype.Service; 5 | import org.thiki.kanban.foundation.common.RestResource; 6 | import org.thiki.kanban.foundation.hateoas.TLink; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 14 | 15 | /** 16 | * Created by xubitao on 04/26/16. 17 | */ 18 | @Service 19 | public class ResortStagesResource extends RestResource { 20 | 21 | @Resource 22 | private StageResource stageResourceService; 23 | 24 | @Resource 25 | private TLink tlink; 26 | 27 | public Object toResource(List stageList, String boardId, String viewType, String userName) throws Exception { 28 | ResortStagesResource resortStagesResource = new ResortStagesResource(); 29 | List stageResources = new ArrayList<>(); 30 | for (Stage stage : stageList) { 31 | Object stageResource = stageResourceService.toResource(stage, boardId, userName); 32 | stageResources.add(stageResource); 33 | } 34 | 35 | resortStagesResource.buildDataObject("stages", stageResources); 36 | Link stagesLink = linkTo(methodOn(StagesController.class).loadAll(boardId, viewType, userName)).withRel("stages"); 37 | resortStagesResource.add(tlink.from(stagesLink).build(userName)); 38 | 39 | Link selfLink = linkTo(methodOn(StagesController.class).resort(stageList, boardId, userName)).withSelfRel(); 40 | resortStagesResource.add(tlink.from(selfLink).build(userName)); 41 | return resortStagesResource.getResource(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/stage/StageCodes.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.stage; 2 | 3 | import org.thiki.kanban.foundation.application.DomainOrder; 4 | 5 | /** 6 | * Created by xubt on 8/8/16. 7 | */ 8 | public enum StageCodes { 9 | TITLE_IS_ALREADY_EXISTS("001", "该名称已经被使用,请使用其它名称。"), 10 | IS_NOT_CURRENT_PROJECT_MEMBER("002", "当前团队并非团队成员。"), 11 | STAGE_IS_NOT_EXIST("003", "你正在操作的环节不存在。"), 12 | DONE_STAGE_IS_ALREADY_EXIST("004", "完成环节已经存在。"), 13 | STAGE_TYPE_IS_NOT_IN_SPRINT("005", "当前环节非迭代中的环节,不可以设置完成列。"), 14 | NOT_ALLOW_SET_STAGE_TO_ARCHIVE_DIRECTLY("006", "不允许直接将环节设置为归档状态。"), 15 | STAGE_IS_NOT_IN_DONE_STATUS("007", "当前环节并非处于完成状态,不允许撤销归档。"), 16 | NO_DONE_STAGE_WAS_FOUND("008", "当前看板尚未设置完成列,不允许撤销归档。"), 17 | NEW_ARCHIVED_STAGE_WAS_FOUND("009", "已经存在较新的归档,不允许撤销归档。"), 18 | NO_ARCHIVED_STAGE_WAS_FOUND("010", "归档不存在,无法进行撤销归档操作。"), 19 | DONE_STAGE_IS_NOT_EXIST("011", "完成环节不存在。"), 20 | DONE_STAGE_IN_SPRINT("012", "在迭代完成环节中,不能删除。"); 21 | public static final String titleIsRequired = "环节名称不能为空。"; 22 | public static final String titleIsInvalid = "环节名称长度超限,请保持在30个字符以内。"; 23 | public static final String descriptionIsInvalid = "环节描述长度超限,请保持在100个字符以内。"; 24 | public static final Integer STAGE_STATUS_TODO = 0; 25 | public static final Integer STAGE_STATUS_DOING = 1; 26 | public static final Integer STAGE_STATUS_DONE = 9; 27 | public static final Integer STAGE_TYPE_IN_PLAN = 1; 28 | public static final Integer STAGE_TYPE_ARCHIVE = 9; 29 | 30 | private String code; 31 | private String message; 32 | 33 | StageCodes(String code, String message) { 34 | this.code = code; 35 | this.message = message; 36 | } 37 | 38 | public int code() { 39 | return Integer.parseInt(DomainOrder.STAGE + "" + code); 40 | } 41 | 42 | public String message() { 43 | return message; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/project/ProjectResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.project; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.board.BoardsController; 7 | import org.thiki.kanban.foundation.common.RestResource; 8 | import org.thiki.kanban.foundation.hateoas.TLink; 9 | import org.thiki.kanban.projects.members.MembersController; 10 | 11 | import javax.annotation.Resource; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | /** 17 | * Created by bogehu on 7/11/16. 18 | */ 19 | @Service 20 | public class ProjectResource extends RestResource { 21 | @Resource 22 | private TLink tlink; 23 | 24 | @Cacheable(value = "project", key = "'project'+#project.id+#userName") 25 | public Object toResource(String userName, Project project) throws Exception { 26 | ProjectResource projectResource = new ProjectResource(); 27 | projectResource.domainObject = project; 28 | if (project != null) { 29 | Link selfLink = linkTo(methodOn(ProjectsController.class).findById(project.getId(), userName)).withSelfRel(); 30 | projectResource.add(tlink.from(selfLink).build(userName)); 31 | 32 | Link membersLink = linkTo(methodOn(MembersController.class).loadMembersByProjectId(project.getId(), userName)).withRel("members"); 33 | projectResource.add(tlink.from(membersLink).build(userName)); 34 | 35 | Link boardsLink = linkTo(methodOn(BoardsController.class).loadByProject(project.getId(), userName)).withRel("boards"); 36 | projectResource.add(tlink.from(boardsLink).build(userName)); 37 | } 38 | return projectResource.getResource(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/AvatarResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.FileUtil; 7 | import org.thiki.kanban.foundation.common.RestResource; 8 | import org.thiki.kanban.foundation.hateoas.TLink; 9 | 10 | import javax.annotation.Resource; 11 | import java.io.File; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | /** 17 | * Created by xubt on 28/09/2016. 18 | */ 19 | @Service 20 | public class AvatarResource extends RestResource { 21 | @Resource 22 | private TLink tlink; 23 | 24 | public Object toResource(String userName) throws Exception { 25 | AvatarResource avatarResource = new AvatarResource(); 26 | Link selfLink = linkTo(methodOn(UsersController.class).uploadAvatar(userName, null)).withSelfRel(); 27 | avatarResource.add(tlink.from(selfLink).build(userName)); 28 | 29 | Link profileLink = linkTo(methodOn(UsersController.class).loadProfile(userName)).withRel("profile"); 30 | avatarResource.add(tlink.from(profileLink).build(userName)); 31 | return avatarResource.getResource(); 32 | } 33 | 34 | @Cacheable(value = "avatar", key = "'avatar'+#userName") 35 | public Object toResource(String userName, File avatar) throws Exception { 36 | AvatarResource avatarResource = new AvatarResource(); 37 | avatarResource.buildDataObject("avatar", FileUtil.fileString(avatar)); 38 | Link selfLink = linkTo(methodOn(UsersController.class).uploadAvatar(userName, null)).withSelfRel(); 39 | avatarResource.add(tlink.from(selfLink).build(userName)); 40 | return avatarResource.getResource(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/thiki/kanban/foundation/mail/MailServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.mail; 2 | 3 | import freemarker.template.TemplateException; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 7 | import org.thiki.kanban.TestBase; 8 | import org.thiki.kanban.foundation.annotations.Scenario; 9 | 10 | import javax.annotation.Resource; 11 | import javax.mail.MessagingException; 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Created by xubt on 8/7/16. 18 | */ 19 | @RunWith(SpringJUnit4ClassRunner.class) 20 | public class MailServiceTest extends TestBase { 21 | @Resource 22 | private MailService mailService; 23 | 24 | @Scenario("通过模版发送邮件,且传入模版的数据是Map类型") 25 | @Test 26 | public void testMailTemplate() throws TemplateException, IOException, MessagingException { 27 | String templateName = "template_demo.ftl"; 28 | Map dataMap = new HashMap(); 29 | dataMap.put("userName", "王大锤"); 30 | mailService.sendMailByTemplate("766191920@qq.com", "王大锤-邮箱注册认证", dataMap, 31 | templateName); 32 | mailService.sendMailByTemplate("thiki2016@163.com", "王大锤-邮箱注册认证", dataMap, 33 | templateName); 34 | 35 | } 36 | 37 | @Scenario("通过模版发送邮件,且传入模版的数据是实体类型") 38 | @Test 39 | public void testEntity() throws TemplateException, IOException, MessagingException { 40 | String templateName = "template_demo.ftl"; 41 | MailEntity mailEntity = new MailEntity(); 42 | 43 | mailEntity.setUserName("小茗"); 44 | mailService.sendMailByTemplate("766191920@qq.com", "王大锤-邮箱注册认证", mailEntity, 45 | templateName); 46 | mailService.sendMailByTemplate("thiki2016@163.com", "王大锤-邮箱注册认证", mailEntity, 47 | templateName); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/import/worktile_tasks_format_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "tid": "fa2815d18b254a4abeb8b2f453148355", 3 | "name": "任务名称", 4 | "desc": "", 5 | "pos": 1589228.75, 6 | "expire_date": "", 7 | "completed": 0, 8 | "archived": 0, 9 | "completed_at": "", 10 | "create_at": "2016-06-21T00:54:13.501Z", 11 | "create_by": { 12 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 13 | "name": "xubitao", 14 | "display_name": "徐必涛" 15 | }, 16 | "update_at": "2016-08-05T15:39:27.390Z", 17 | "update_by": { 18 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 19 | "name": "xubitao", 20 | "display_name": "徐必涛" 21 | }, 22 | "project": { 23 | "pid": "2187f189117544e8b1397613afac921c", 24 | "name": "thiki-kanban product backlog" 25 | }, 26 | "entry": { 27 | "entry_id": "0eae9291d2ad4b029df3f798dbf820a9", 28 | "name": "Product Backlog" 29 | }, 30 | "labels": [ 31 | { 32 | "name": "young_blue", 33 | "desc": "用户故事", 34 | "_id": "5774c5da3a70fd66302dcfd8" 35 | } 36 | ], 37 | "assignee": [], 38 | "watchers": [ 39 | { 40 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 41 | "name": "xubitao", 42 | "display_name": "徐必涛" 43 | }, 44 | { 45 | "uid": "bd636b0c317d400b870d5455f7e330a8", 46 | "name": "zengzhu_hoolai", 47 | "display_name": "曾著" 48 | } 49 | ], 50 | "todos": [ 51 | { 52 | "todo_id": "e17f667ec35e42ea85a314e0bb9af635", 53 | "name": "客户端的board名称为空时不允许创或更新建;", 54 | "pos": 65535, 55 | "checked": 0 56 | }, 57 | { 58 | "todo_id": "321e0bffb657454bb3a8a53a6417e347", 59 | "name": "客户端的entry名称或boardID为空时不允许创建或更新;", 60 | "pos": 131070, 61 | "checked": 0 62 | }, 63 | { 64 | "todo_id": "2ed0eb98fe034b46a8980697b6e53ddd", 65 | "name": "客户端的task概述或entryID为空时不允许创建或更新。", 66 | "pos": 196605, 67 | "checked": 0 68 | } 69 | ], 70 | "attachments": [] 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/assignment/AssignmentController.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.assignment; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.thiki.kanban.foundation.common.Response; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by xubitao on 6/16/16. 15 | */ 16 | @RestController 17 | public class AssignmentController { 18 | private static Logger logger = LoggerFactory.getLogger(AssignmentController.class); 19 | @Autowired 20 | private AssignmentService assignmentService; 21 | 22 | @Resource 23 | private AssignmentsResource assignmentsResource; 24 | 25 | @RequestMapping(value = "/boards/{boardId}/stages/{stageId}/cards/{cardId}/assignments", method = RequestMethod.POST) 26 | public HttpEntity create(@RequestBody List assignments, @PathVariable String boardId, @PathVariable String stageId, @PathVariable String cardId, @RequestHeader String userName) throws Exception { 27 | List savedAssignments = assignmentService.assign(assignments, cardId, boardId, userName); 28 | return Response.build(assignmentsResource.toResource(savedAssignments, boardId, stageId, cardId, userName)); 29 | } 30 | 31 | @RequestMapping(value = "/boards/{boardId}/stages/{stageId}/cards/{cardId}/assignments", method = RequestMethod.GET) 32 | public HttpEntity findByCardId(@PathVariable String boardId, @PathVariable String stageId, @PathVariable String cardId, @RequestHeader String userName) throws Exception { 33 | logger.info("Loading assignments by board [{}]", boardId); 34 | List assignmentList = assignmentService.findByCardId(cardId); 35 | return Response.build(assignmentsResource.toResource(assignmentList, boardId, stageId, cardId, userName)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/user/registration/RegistrationService.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.user.registration; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.common.SequenceNumber; 5 | import org.thiki.kanban.foundation.exception.BusinessException; 6 | import org.thiki.kanban.foundation.security.identification.md5.MD5Service; 7 | import org.thiki.kanban.foundation.security.identification.rsa.RSAService; 8 | import org.thiki.kanban.user.User; 9 | import org.thiki.kanban.user.UsersCodes; 10 | import org.thiki.kanban.user.UsersService; 11 | 12 | import javax.annotation.Resource; 13 | 14 | /** 15 | * Created by joeaniu on 6/21/16. 16 | */ 17 | @Service 18 | public class RegistrationService { 19 | 20 | @Resource 21 | private RegistrationPersistence registrationPersistence; 22 | @Resource 23 | private UsersService usersService; 24 | 25 | @Resource 26 | private SequenceNumber sequenceNumber; 27 | 28 | @Resource 29 | private RSAService rsaService; 30 | 31 | public User register(Registration registration) throws Exception { 32 | boolean isNameExist = usersService.isNameExist(registration.getUserName()); 33 | if (isNameExist) { 34 | throw new BusinessException(UsersCodes.USERNAME_IS_ALREADY_EXISTS); 35 | } 36 | 37 | boolean isEmailExist = usersService.isEmailExist(registration.getEmail()); 38 | if (isEmailExist) { 39 | throw new BusinessException(UsersCodes.EMAIL_IS_ALREADY_EXISTS); 40 | } 41 | 42 | registration.setSalt(sequenceNumber.generate()); 43 | 44 | String password = rsaService.dencrypt(registration.getPassword()); 45 | password = MD5Service.encrypt(password + registration.getSalt()); 46 | if (password != null) { 47 | registration.setPassword(password); 48 | } 49 | registrationPersistence.register(registration); 50 | return usersService.findByName(registration.getUserName()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/verification/VerificationMail.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.verification; 2 | 3 | import org.thiki.kanban.acceptanceCriteria.AcceptanceCriteria; 4 | import org.thiki.kanban.board.Board; 5 | import org.thiki.kanban.card.Card; 6 | import org.thiki.kanban.foundation.mail.MailEntity; 7 | import org.thiki.kanban.notification.NotificationType; 8 | 9 | /** 10 | * Created by xubt on 03/07/2017. 11 | */ 12 | public class VerificationMail extends MailEntity { 13 | private String boardName; 14 | private String cardCode; 15 | private String acceptanceCriteria; 16 | 17 | 18 | public static MailEntity newMail(AcceptanceCriteria acceptanceCriteria, Card card, Board board) { 19 | VerificationMail verificationMail = new VerificationMail(); 20 | verificationMail.setSubject(String.format("您所参与处理的卡片%s未通过验收", card.getCode())); 21 | verificationMail.setBoardName(board.getName()); 22 | verificationMail.setCardCode(card.getCode()); 23 | verificationMail.setAcceptanceCriteria(acceptanceCriteria.getSummary()); 24 | verificationMail.setNotificationType(NotificationType.VERIFICATION_IS_NOT_PASSED); 25 | String content = String.format("验收标准:【%s】未通过验收,所属看板【%s】,卡片编号:【%s】。请知悉。", acceptanceCriteria.getSummary(), board.getName(), card.getCode()); 26 | verificationMail.setContent(content); 27 | return verificationMail; 28 | } 29 | 30 | public String getBoardName() { 31 | return boardName; 32 | } 33 | 34 | public void setBoardName(String boardName) { 35 | this.boardName = boardName; 36 | } 37 | 38 | public String getCardCode() { 39 | return cardCode; 40 | } 41 | 42 | public void setCardCode(String cardCode) { 43 | this.cardCode = cardCode; 44 | } 45 | 46 | public String getAcceptanceCriteria() { 47 | return acceptanceCriteria; 48 | } 49 | 50 | public void setAcceptanceCriteria(String acceptanceCriteria) { 51 | this.acceptanceCriteria = acceptanceCriteria; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/common/RestResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.common; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.springframework.hateoas.Link; 6 | import org.springframework.hateoas.ResourceSupport; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Created by xubt on 5/26/16. 13 | */ 14 | public class RestResource extends ResourceSupport { 15 | protected Object domainObject; 16 | protected JSONArray resourcesJSON; 17 | 18 | public JSONObject getResource() { 19 | JSONObject resourceJSON = new JSONObject(); 20 | JSONObject links = new JSONObject(); 21 | for (Link link : super.getLinks()) { 22 | links.put(link.getRel(), ((TLink) link).toJSON()); 23 | } 24 | JSONObject domainJSON = JSONObject.parseObject(JSONObject.toJSONString(domainObject)); 25 | if (domainJSON != null) { 26 | resourceJSON.putAll(domainJSON); 27 | } 28 | resourceJSON.put("_links", links); 29 | return resourceJSON; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | if (resourcesJSON != null) { 35 | return resourcesJSON.toJSONString(); 36 | } 37 | return getResource().toString(); 38 | } 39 | 40 | public void buildDataObject(String key, Object value) { 41 | JSONArray domainResources; 42 | Object domainResourcesValue = value; 43 | if (domainResourcesValue instanceof List) { 44 | domainResources = new JSONArray(); 45 | for (int i = 0; i < ((List) domainResourcesValue).size(); i++) { 46 | domainResources.add(((List) domainResourcesValue).get(i)); 47 | } 48 | domainResourcesValue = domainResources; 49 | } 50 | JSONObject dataObject = new JSONObject(); 51 | dataObject.put(key, domainResourcesValue); 52 | this.domainObject = dataObject; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/risk/RiskResources.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.risk; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.card.CardsController; 7 | import org.thiki.kanban.foundation.common.RestResource; 8 | import org.thiki.kanban.foundation.hateoas.TLink; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 15 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 16 | 17 | /** 18 | * Created by wisdom on 3/19/17. 19 | */ 20 | @Service 21 | public class RiskResources extends RestResource { 22 | 23 | @Resource 24 | private TLink tlink; 25 | @Resource 26 | private RiskResource riskResourceService; 27 | 28 | 29 | @Cacheable(value = "risk", key = "'risks'+#boardId+#stageId+#cardId+#userName") 30 | public Object toResource(List risks, String boardId, String stageId, String cardId, String userName) throws Exception { 31 | RiskResources risksResources = new RiskResources(); 32 | 33 | List riskResourceList = new ArrayList<>(); 34 | 35 | for (Risk risk : risks) { 36 | Object riskResource = riskResourceService.toResource(risk, boardId, stageId, cardId, userName); 37 | riskResourceList.add(riskResource); 38 | } 39 | risksResources.buildDataObject("risks", riskResourceList); 40 | 41 | Link selfLink = linkTo(methodOn(RiskController.class).loadCardRisks(userName, boardId, stageId, cardId)).withSelfRel(); 42 | risksResources.add(tlink.from(selfLink).build(userName)); 43 | 44 | Link cardLink = linkTo(methodOn(CardsController.class).findById(boardId, stageId, cardId, userName)).withRel("card"); 45 | risksResources.add(tlink.from(cardLink).build(userName)); 46 | 47 | return risksResources.getResource(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/foundation/calendar/CalendarService.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.foundation.calendar; 2 | 3 | import org.springframework.stereotype.Service; 4 | import org.thiki.kanban.foundation.common.date.DateService; 5 | import org.thiki.kanban.foundation.common.date.Week; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * Created by xubt on 15/02/2017. 11 | */ 12 | @Service 13 | public class CalendarService { 14 | public int holidaysBetweenTwoDays(String startTime, String endTime) { 15 | int holidaysCount = 0; 16 | Date endDate = DateService.instance().StringToDate(endTime); 17 | for (Date startDate = DateService.instance().StringToDate(startTime); startDate.before(endDate) || startDate.equals(endDate); startDate = DateService.instance().addDay(startDate, 1)) { 18 | Week week = DateService.instance().getWeek(startDate); 19 | if (week.isWeekend()) { 20 | holidaysCount++; 21 | } 22 | } 23 | return holidaysCount; 24 | } 25 | 26 | public int holidaysBetweenTwoDays(String startTime, String endTime, Date... excludeDates) { 27 | int holidaysCount = 0; 28 | Date endDate = DateService.instance().StringToDate(endTime); 29 | for (Date startDate = DateService.instance().StringToDate(startTime); startDate.before(endDate) || startDate.equals(endDate); startDate = DateService.instance().addDay(startDate, 1)) { 30 | if (isExcludeDay(startDate, excludeDates)) { 31 | continue; 32 | } 33 | Week week = DateService.instance().getWeek(startDate); 34 | if (week.isWeekend()) { 35 | holidaysCount++; 36 | } 37 | } 38 | return holidaysCount; 39 | } 40 | 41 | private boolean isExcludeDay(Date startDate, Date[] excludeDates) { 42 | for (Date excludeDay : excludeDates) { 43 | if (startDate.equals(excludeDay)) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/members/MembersResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.members; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | import org.thiki.kanban.projects.invitation.InvitationController; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 15 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 16 | 17 | /** 18 | * Created by xubt on 9/10/16. 19 | */ 20 | @Service 21 | public class MembersResource extends RestResource { 22 | @Resource 23 | private TLink tlink; 24 | 25 | @Resource 26 | private MemberResource memberResourceService; 27 | 28 | @Cacheable(value = "project", key = "'members'+#projectId+#userName") 29 | public Object toResource(String projectId, List members, String userName) throws Exception { 30 | MembersResource membersResource = new MembersResource(); 31 | List memberResources = new ArrayList<>(); 32 | for (Member member : members) { 33 | Object memberResource = memberResourceService.toResource(projectId, member, userName); 34 | memberResources.add(memberResource); 35 | } 36 | 37 | membersResource.buildDataObject("members", memberResources); 38 | 39 | Link invitationLink = linkTo(methodOn(InvitationController.class).invite(null, projectId, userName)).withRel("invitation"); 40 | membersResource.add(tlink.from(invitationLink).build(userName)); 41 | 42 | Link memberLink = linkTo(methodOn(MembersController.class).getMember(projectId, userName)).withRel("member"); 43 | membersResource.add(tlink.from(memberLink).build(userName)); 44 | 45 | return membersResource.getResource(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/notification/NotificationsResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.notification; 2 | 3 | import org.springframework.cache.annotation.Cacheable; 4 | import org.springframework.hateoas.Link; 5 | import org.springframework.stereotype.Service; 6 | import org.thiki.kanban.foundation.common.RestResource; 7 | import org.thiki.kanban.foundation.hateoas.TLink; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 15 | 16 | /** 17 | * Created by xubt on 9/17/16. 18 | */ 19 | @Service 20 | public class NotificationsResource extends RestResource { 21 | @Resource 22 | private TLink tlink; 23 | @Resource 24 | private NotificationResource notificationResourceService; 25 | 26 | @Cacheable(value = "notification", key = "'notifications'+#userName") 27 | public Object toResource(String userName, List notifications) throws Exception { 28 | NotificationsResource notificationsResource = new NotificationsResource(); 29 | List notificationResources = new ArrayList<>(); 30 | for (Notification notification : notifications) { 31 | Object notificationResource = notificationResourceService.toResource(userName, notification); 32 | notificationResources.add(notificationResource); 33 | } 34 | 35 | notificationsResource.buildDataObject("notifications", notificationResources); 36 | Link selfLink = linkTo(methodOn(NotificationController.class).loadNotifications(userName)).withSelfRel(); 37 | notificationsResource.add(tlink.from(selfLink).build(userName)); 38 | 39 | Link notificationsLink = linkTo(methodOn(NotificationController.class).loadNotifications(userName)).withRel("notifications"); 40 | notificationsResource.add(tlink.from(notificationsLink).build(userName)); 41 | return notificationsResource.getResource(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/resources/import/worktile_tasks: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tid": "fa2815d18b254a4abeb8b2f453148355", 4 | "name": "任务名称", 5 | "desc": "", 6 | "pos": 1589228.75, 7 | "expire_date": "", 8 | "completed": 0, 9 | "archived": 0, 10 | "completed_at": "", 11 | "create_at": "2016-06-21T00:54:13.501Z", 12 | "create_by": { 13 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 14 | "name": "xubitao", 15 | "display_name": "徐必涛" 16 | }, 17 | "update_at": "2016-08-05T15:39:27.390Z", 18 | "update_by": { 19 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 20 | "name": "xubitao", 21 | "display_name": "徐必涛" 22 | }, 23 | "project": { 24 | "pid": "2187f189117544e8b1397613afac921c", 25 | "name": "thiki-kanban product backlog" 26 | }, 27 | "entry": { 28 | "entry_id": "0eae9291d2ad4b029df3f798dbf820a9", 29 | "name": "Product Backlog" 30 | }, 31 | "labels": [ 32 | { 33 | "name": "young_blue", 34 | "desc": "用户故事", 35 | "_id": "5774c5da3a70fd66302dcfd8" 36 | } 37 | ], 38 | "assignee": [], 39 | "watchers": [ 40 | { 41 | "uid": "79572b3454a54d35980e7b8b8ca9d2c6", 42 | "name": "xubitao", 43 | "display_name": "徐必涛" 44 | }, 45 | { 46 | "uid": "bd636b0c317d400b870d5455f7e330a8", 47 | "name": "zengzhu_hoolai", 48 | "display_name": "曾著" 49 | } 50 | ], 51 | "todos": [ 52 | { 53 | "todo_id": "e17f667ec35e42ea85a314e0bb9af635", 54 | "name": "客户端的board名称为空时不允许创或更新建;", 55 | "pos": 65535, 56 | "checked": 0 57 | }, 58 | { 59 | "todo_id": "321e0bffb657454bb3a8a53a6417e347", 60 | "name": "客户端的entry名称或boardID为空时不允许创建或更新;", 61 | "pos": 131070, 62 | "checked": 0 63 | }, 64 | { 65 | "todo_id": "2ed0eb98fe034b46a8980697b6e53ddd", 66 | "name": "客户端的task概述或entryID为空时不允许创建或更新。", 67 | "pos": 196605, 68 | "checked": 0 69 | } 70 | ], 71 | "attachments": [] 72 | } 73 | ] 74 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/cardTags/CardTagResource.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.cardTags; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.cache.annotation.Cacheable; 6 | import org.springframework.hateoas.Link; 7 | import org.springframework.stereotype.Service; 8 | import org.thiki.kanban.card.CardsController; 9 | import org.thiki.kanban.foundation.common.RestResource; 10 | import org.thiki.kanban.foundation.hateoas.TLink; 11 | 12 | import javax.annotation.Resource; 13 | 14 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; 15 | import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; 16 | 17 | 18 | /** 19 | * Created by xubt on 11/14/16. 20 | */ 21 | @Service 22 | public class CardTagResource extends RestResource { 23 | public static Logger logger = LoggerFactory.getLogger(CardTagResource.class); 24 | 25 | @Resource 26 | private TLink tlink; 27 | 28 | @Cacheable(value = "card-tag", key = "#userName+'card-tag'+#boardId+#stageId+#cardId+#cardTag.id") 29 | public Object toResource(CardTag cardTag, String boardId, String stageId, String cardId, String userName) throws Exception { 30 | logger.info("build card tag resource.board:{},stageId:{},userName:{}", boardId, stageId, userName); 31 | CardTagResource cardTagResource = new CardTagResource(); 32 | cardTagResource.domainObject = cardTag; 33 | if (cardTag != null) { 34 | Link tagsLink = linkTo(methodOn(CardTagsController.class).stick(null, boardId, stageId, cardId, null)).withRel("tags"); 35 | cardTagResource.add(tlink.from(tagsLink).build(userName)); 36 | 37 | Link cardLink = linkTo(methodOn(CardsController.class).findById(boardId, stageId, cardId, userName)).withRel("card"); 38 | cardTagResource.add(tlink.from(cardLink).build(userName)); 39 | } 40 | logger.info("card tag resource building completed.board:{},stageId:{},userName:{}", boardId, stageId, userName); 41 | return cardTagResource.getResource(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/thiki/kanban/projects/invitation/Invitation.java: -------------------------------------------------------------------------------- 1 | package org.thiki.kanban.projects.invitation; 2 | 3 | import org.hibernate.validator.constraints.NotEmpty; 4 | 5 | import javax.validation.constraints.NotNull; 6 | 7 | /** 8 | * Created by xutao on 9/11/16. 9 | */ 10 | 11 | public class Invitation { 12 | private String id; 13 | @NotEmpty(message = InvitationCodes.InviteeIsRequired) 14 | @NotNull(message = InvitationCodes.InviteeIsRequired) 15 | private String invitee; 16 | private String inviter; 17 | private String projectId; 18 | private Integer isAccepted; 19 | private String creationTime; 20 | private String modificationTime; 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | public String getModificationTime() { 31 | return modificationTime; 32 | } 33 | 34 | public void setModificationTime(String modificationTime) { 35 | this.modificationTime = modificationTime; 36 | } 37 | 38 | public String getCreationTime() { 39 | return creationTime; 40 | } 41 | 42 | public void setCreationTime(String creationTime) { 43 | this.creationTime = creationTime; 44 | } 45 | 46 | public String getInviter() { 47 | return inviter; 48 | } 49 | 50 | public void setInviter(String inviter) { 51 | this.inviter = inviter; 52 | } 53 | 54 | public String getTeamId() { 55 | return projectId; 56 | } 57 | 58 | public void setTeamId(String projectId) { 59 | this.projectId = projectId; 60 | } 61 | 62 | public String getInvitee() { 63 | return invitee; 64 | } 65 | 66 | public void setInvitee(String invitee) { 67 | this.invitee = invitee; 68 | } 69 | 70 | public boolean getIsAccepted() { 71 | return (isAccepted == null || isAccepted == 0) ? false : true; 72 | } 73 | 74 | public void setIsAccepted(Integer isAccepted) { 75 | this.isAccepted = isAccepted; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/resources/quality/pmd/pmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | Custom ruleset for Android application 24 | 25 | .*/R.java 26 | .*/gen/.* 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | --------------------------------------------------------------------------------