├── .gitignore ├── .run └── kaif_kaif-fe [webDevServe].run.xml ├── .tool-versions ├── LICENSE ├── README.md ├── build.gradle ├── buildJibToLocal.sh ├── buildJibToProd.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kaif-deploy ├── .gitignore ├── README.md ├── ctl │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── k9s │ │ └── black_and_wtf.yml │ ├── kaif_ctl.sh │ ├── secret │ │ └── .gitkeep │ └── zshrc ├── gce.ssh_config ├── k3d │ └── README.md ├── kaif-local │ ├── .gitignore │ ├── .terraform.lock.hcl │ ├── cert-manager.tf │ ├── kaif-web-secret.yaml │ ├── kaif-web-values.yaml │ ├── kaif-web.tf │ ├── main.tf │ ├── postgresql-values.yaml │ └── postgresql.tf ├── kaif-prod │ ├── .gitignore │ ├── .terraform.lock.hcl │ ├── kaif-web-values.yaml │ ├── kaif-web-version.txt │ ├── kaif-web.tf.disabled │ ├── main.tf │ ├── postgresql-values.yaml │ └── postgresql.tf.disabled ├── kaif-web-chart │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ │ ├── NOTES.txt │ │ ├── _helpers.tpl │ │ ├── deployment.yaml │ │ ├── ingress.yaml │ │ ├── mkcert-issuer.yaml │ │ ├── nginx-config.yaml │ │ ├── secret.yaml │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── spring-json-config.yaml │ └── values.yaml ├── mkcert │ ├── .gitignore │ └── README.md └── reference │ ├── cert-manager-README.md │ ├── gce.md │ ├── k3s │ ├── README.md │ └── ingress-nginx │ │ ├── README.md │ │ └── values.yaml │ └── postgresql-README.md ├── kaif-fe ├── .gitignore ├── build.gradle ├── build.yaml ├── lib │ ├── comp │ │ ├── account │ │ │ ├── account_menu.dart │ │ │ ├── account_settings.dart │ │ │ ├── edit_description_form.dart │ │ │ ├── forget_password_form.dart │ │ │ ├── granted_client_app.dart │ │ │ ├── news_feed.dart │ │ │ ├── reset_password_form.dart │ │ │ ├── sign_in_form.dart │ │ │ └── sign_up_form.dart │ │ ├── article │ │ │ ├── article-list.dart │ │ │ └── article_form.dart │ │ ├── comp_template.dart │ │ ├── debate │ │ │ ├── debate_comp.dart │ │ │ ├── debate_form.dart │ │ │ ├── debate_list.dart │ │ │ ├── debate_tree.dart │ │ │ └── edit_debate_form.dart │ │ ├── developer │ │ │ └── developer_client_app.dart │ │ ├── kmark │ │ │ ├── edit_kmark_form.dart │ │ │ └── kmark_auto_linker.dart │ │ ├── oauth │ │ │ └── oauth_authorize_form.dart │ │ ├── server_part_loader.dart │ │ ├── short_url.dart │ │ ├── vote │ │ │ └── votable.dart │ │ └── zone │ │ │ └── zone_form.dart │ ├── cookies.dart │ ├── intl │ │ ├── i18n.dart │ │ ├── lookup.dart │ │ ├── messages_en.dart │ │ └── messages_zh.dart │ ├── model.dart │ ├── model │ │ ├── account.dart │ │ ├── dao.dart │ │ ├── feed.dart │ │ ├── service.dart │ │ ├── session.dart │ │ └── vote.dart │ ├── route │ │ └── route.dart │ ├── state │ │ ├── dispatch.dart │ │ ├── state.dart │ │ └── state_machine.dart │ ├── util.dart │ ├── util │ │ ├── server_type.dart │ │ ├── strings.dart │ │ └── throttler.dart │ └── view │ │ ├── htmls.dart │ │ ├── view.dart │ │ └── widgets.dart ├── pubspec.lock ├── pubspec.yaml └── web │ ├── less │ ├── base.less │ ├── bootstrap-alert.less │ ├── bootstrap-label.less │ ├── button.less │ ├── comp.less │ ├── flexbox.less │ ├── grid.less │ ├── kaif.less │ ├── kmark.less │ ├── template.less │ ├── util.less │ ├── variable.less │ └── widgets.less │ └── main.dart ├── kaif-web ├── build.gradle └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── kaif │ │ │ ├── Application.java │ │ │ ├── config │ │ │ ├── AppProperties.java │ │ │ ├── ModelConfiguration.java │ │ │ ├── SpringProfile.java │ │ │ ├── SwaggerConfiguration.java │ │ │ ├── UtilConfiguration.java │ │ │ └── WebConfiguration.java │ │ │ ├── database │ │ │ └── DaoOperations.java │ │ │ ├── flake │ │ │ ├── FlakeId.java │ │ │ └── FlakeIdGenerator.java │ │ │ ├── kmark │ │ │ ├── Block.java │ │ │ ├── BlockEmitter.java │ │ │ ├── BlockType.java │ │ │ ├── CodeBlockEmitter.java │ │ │ ├── Configuration.java │ │ │ ├── Decorator.java │ │ │ ├── DefaultDecorator.java │ │ │ ├── Emitter.java │ │ │ ├── HtmlEscapeStringBuilder.java │ │ │ ├── KmarkProcessor.java │ │ │ ├── Line.java │ │ │ ├── LineType.java │ │ │ ├── LinkRef.java │ │ │ ├── MarkToken.java │ │ │ ├── SpanEmitter.java │ │ │ └── Utils.java │ │ │ ├── mail │ │ │ ├── AwsSesMailAgent.java │ │ │ ├── JavaMailAgent.java │ │ │ ├── Mail.java │ │ │ ├── MailAgent.java │ │ │ ├── MailComposer.java │ │ │ └── MailProperties.java │ │ │ ├── model │ │ │ ├── KaifIdGenerator.java │ │ │ ├── account │ │ │ │ ├── Account.java │ │ │ │ ├── AccountAccessToken.java │ │ │ │ ├── AccountAuth.java │ │ │ │ ├── AccountDao.java │ │ │ │ ├── AccountOnceToken.java │ │ │ │ ├── AccountSecret.java │ │ │ │ ├── AccountStats.java │ │ │ │ ├── Authority.java │ │ │ │ └── Authorization.java │ │ │ ├── article │ │ │ │ ├── Article.java │ │ │ │ ├── ArticleContentType.java │ │ │ │ ├── ArticleDao.java │ │ │ │ ├── ArticleList.java │ │ │ │ ├── ArticleWatch.java │ │ │ │ └── ArticleWatchDao.java │ │ │ ├── clientapp │ │ │ │ ├── ClientApp.java │ │ │ │ ├── ClientAppAuthorization.java │ │ │ │ ├── ClientAppDao.java │ │ │ │ ├── ClientAppScope.java │ │ │ │ ├── ClientAppUser.java │ │ │ │ ├── ClientAppUserAccessToken.java │ │ │ │ ├── GrantCode.java │ │ │ │ └── OauthSecret.java │ │ │ ├── debate │ │ │ │ ├── Debate.java │ │ │ │ ├── DebateContentType.java │ │ │ │ ├── DebateDao.java │ │ │ │ ├── DebateList.java │ │ │ │ └── DebateTree.java │ │ │ ├── exception │ │ │ │ ├── AuthenticateFailException.java │ │ │ │ ├── CallbackUriReservedException.java │ │ │ │ ├── ClientAppMaxException.java │ │ │ │ ├── ClientAppNameReservedException.java │ │ │ │ ├── CreditNotEnoughException.java │ │ │ │ ├── DomainException.java │ │ │ │ ├── I18nAware.java │ │ │ │ ├── OldPasswordNotMatchException.java │ │ │ │ └── RequireCitizenException.java │ │ │ ├── feed │ │ │ │ ├── FeedAsset.java │ │ │ │ ├── FeedAssetDao.java │ │ │ │ └── NewsFeed.java │ │ │ ├── vote │ │ │ │ ├── ArticleVoter.java │ │ │ │ ├── ArticleVoterDto.java │ │ │ │ ├── DebateVoter.java │ │ │ │ ├── DebateVoterDto.java │ │ │ │ ├── HonorRoll.java │ │ │ │ ├── HonorRollDao.java │ │ │ │ ├── HonorRollVoter.java │ │ │ │ ├── VoteDao.java │ │ │ │ └── VoteState.java │ │ │ └── zone │ │ │ │ ├── Zone.java │ │ │ │ ├── ZoneDao.java │ │ │ │ └── ZoneInfo.java │ │ │ ├── oauth │ │ │ ├── GrantType.java │ │ │ ├── InsufficientScopeException.java │ │ │ ├── InvalidTokenException.java │ │ │ ├── MissingBearerTokenException.java │ │ │ ├── OauthAccessTokenDto.java │ │ │ ├── OauthErrorDto.java │ │ │ ├── OauthErrors.java │ │ │ ├── OauthException.java │ │ │ └── Oauths.java │ │ │ ├── rank │ │ │ ├── HotRanking.java │ │ │ ├── SortingNode.java │ │ │ └── WilsonScore.java │ │ │ ├── service │ │ │ ├── AccountService.java │ │ │ ├── ArticleService.java │ │ │ ├── ClientAppService.java │ │ │ ├── FeedService.java │ │ │ ├── HonorRollService.java │ │ │ ├── VoteService.java │ │ │ ├── ZoneService.java │ │ │ └── impl │ │ │ │ ├── AccountServiceImpl.java │ │ │ │ ├── ArticleServiceImpl.java │ │ │ │ ├── ClientAppServiceImpl.java │ │ │ │ ├── FeedServiceImpl.java │ │ │ │ ├── HonorRollServiceImpl.java │ │ │ │ ├── VoteServiceImpl.java │ │ │ │ └── ZoneServiceImpl.java │ │ │ ├── token │ │ │ ├── Base62.java │ │ │ ├── Bytes.java │ │ │ ├── Decryptor.java │ │ │ ├── Encryptor.java │ │ │ ├── Hmac.java │ │ │ └── SecureTokenCodec.java │ │ │ ├── util │ │ │ ├── MoreCollectors.java │ │ │ └── Try.java │ │ │ └── web │ │ │ ├── AccountAccessTokenArgumentResolver.java │ │ │ ├── AccountController.java │ │ │ ├── ArticleController.java │ │ │ ├── AwsSnsRestController.java │ │ │ ├── CreateArticleModelAndView.java │ │ │ ├── DevProxyController.java │ │ │ ├── DeveloperController.java │ │ │ ├── HomeController.java │ │ │ ├── HotArticleRssContentView.java │ │ │ ├── OauthController.java │ │ │ ├── PageExceptionHandler.java │ │ │ ├── ShortUrlController.java │ │ │ ├── UserController.java │ │ │ ├── ZoneAdminController.java │ │ │ ├── ZoneController.java │ │ │ ├── api │ │ │ ├── AccountResource.java │ │ │ ├── ArticleResource.java │ │ │ ├── ClientAppResource.java │ │ │ ├── RestExceptionHandler.java │ │ │ ├── SimpleErrorResponse.java │ │ │ ├── VoteResource.java │ │ │ └── ZoneResource.java │ │ │ ├── support │ │ │ ├── AbstractRestExceptionHandler.java │ │ │ ├── AccessDeniedException.java │ │ │ ├── ErrorResponse.java │ │ │ ├── PartTemplate.java │ │ │ ├── RelativeTime.java │ │ │ ├── SingleWrapper.java │ │ │ └── WebDataBinderAdvice.java │ │ │ └── v1 │ │ │ ├── ClientAppUserAccessTokenArgumentResolver.java │ │ │ ├── README.md │ │ │ ├── RequiredScope.java │ │ │ ├── V1ArticleResource.java │ │ │ ├── V1Commons.java │ │ │ ├── V1DebateResource.java │ │ │ ├── V1EchoResource.java │ │ │ ├── V1ErrorResponse.java │ │ │ ├── V1ExceptionHandler.java │ │ │ ├── V1FeedResource.java │ │ │ ├── V1ResponseWrapperAdvice.java │ │ │ ├── V1UserResource.java │ │ │ ├── V1VoteResource.java │ │ │ ├── V1ZoneResource.java │ │ │ └── dto │ │ │ ├── V1ArticleDto.java │ │ │ ├── V1ArticleType.java │ │ │ ├── V1AssetType.java │ │ │ ├── V1DebateDto.java │ │ │ ├── V1DebateNodeDto.java │ │ │ ├── V1FeedAssetDto.java │ │ │ ├── V1UserBasicDto.java │ │ │ ├── V1VoteDto.java │ │ │ └── V1ZoneDto.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-prod.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── db │ │ └── changelog │ │ │ ├── base.sql │ │ │ ├── data.sql │ │ │ └── db.changelog-master.xml │ │ ├── i18n │ │ ├── messages.properties │ │ └── messages_zh.properties │ │ ├── logback-dev.xml │ │ ├── logback-prod.xml │ │ ├── mail │ │ ├── account-activation.ftl │ │ ├── account-password-was-reset.ftl │ │ ├── account-reset-password.ftl │ │ └── mail-composer-test.ftl │ │ ├── static │ │ ├── css │ │ │ ├── z-theme-default.css │ │ │ └── z-theme-kaif.css │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── kaif_black.svg │ │ │ ├── kaif_square.png │ │ │ ├── kaif_square.svg │ │ │ ├── kaif_triangle.svg │ │ │ ├── kaif_white.png │ │ │ ├── kaif_white.svg │ │ │ └── kaif_white@2x.png │ │ └── robots.txt │ │ └── templates │ │ ├── access-denied.ftlh │ │ ├── account │ │ ├── account.ftlh │ │ ├── activation.ftlh │ │ ├── client-app.part.ftlh │ │ ├── forget-password.ftlh │ │ ├── news-feed.part.ftlh │ │ ├── reset-password.ftlh │ │ ├── settings.part.ftlh │ │ ├── sign-in.ftlh │ │ ├── sign-up.ftlh │ │ ├── up-voted.part.ftlh │ │ ├── user-articles.ftlh │ │ ├── user-debates.ftlh │ │ ├── user-honors.ftlh │ │ └── user-profile.ftlh │ │ ├── article │ │ ├── create.ftlh │ │ └── debates.ftlh │ │ ├── developer │ │ ├── client-app.ftlh │ │ ├── client-app.part.ftlh │ │ └── doc.ftlh │ │ ├── error.ftlh │ │ ├── index.ftlh │ │ ├── macros │ │ ├── aside.ftl │ │ ├── comp.ftl │ │ ├── template.ftl │ │ ├── url.ftl │ │ └── util.ftl │ │ ├── part-template.ftlh │ │ ├── v1 │ │ └── authorize.ftlh │ │ └── zone │ │ ├── create.ftlh │ │ ├── zone-a-z.ftlh │ │ └── zone-page.ftlh │ └── test │ ├── java │ └── io │ │ └── kaif │ │ ├── flake │ │ ├── FlakeIdGeneratorTest.java │ │ └── FlakeIdTest.java │ │ ├── kmark │ │ └── KmarkProcessorTest.java │ │ ├── mail │ │ ├── MailAgentTest.java │ │ ├── MailComposerTest.java │ │ └── MailTestCases.java │ │ ├── model │ │ ├── account │ │ │ ├── AccountAccessTokenTest.java │ │ │ ├── AccountTest.java │ │ │ └── AuthorityTest.java │ │ ├── article │ │ │ ├── ArticleDaoTest.java │ │ │ ├── ArticleListTest.java │ │ │ └── ArticleTest.java │ │ ├── clientapp │ │ │ ├── ClientAppDaoTest.java │ │ │ ├── ClientAppScopeTest.java │ │ │ ├── ClientAppUserAccessTokenTest.java │ │ │ └── GrantCodeTest.java │ │ ├── debate │ │ │ ├── DebateTest.java │ │ │ └── DebateTreeTest.java │ │ ├── vote │ │ │ └── HonorRollDaoTest.java │ │ └── zone │ │ │ ├── ZoneInfoTest.java │ │ │ └── ZoneTest.java │ │ ├── rank │ │ ├── HotRankingTest.java │ │ ├── SortingNodeTest.java │ │ └── WilsonScoreTest.java │ │ ├── service │ │ └── impl │ │ │ ├── AccountServiceImplTest.java │ │ │ ├── ArticleServiceImplTest.java │ │ │ ├── ClientAppServiceImplTest.java │ │ │ ├── FeedServiceImplTest.java │ │ │ ├── HonorRollServiceImplTest.java │ │ │ ├── VoteServiceImplTest.java │ │ │ └── ZoneServiceImplTest.java │ │ ├── test │ │ ├── DbIntegrationTests.java │ │ ├── ModelFixture.java │ │ ├── MvcIntegrationTests.java │ │ ├── TimeFixture.java │ │ └── ToolFixture.java │ │ ├── time │ │ └── RelativeTimeTest.java │ │ ├── token │ │ ├── BytesTest.java │ │ ├── DecryptorTest.java │ │ └── SecureTokenCodecTest.java │ │ ├── util │ │ └── MoreCollectorsTest.java │ │ └── web │ │ ├── AccountControllerTest.java │ │ ├── ArticleControllerTest.java │ │ ├── AwsSnsRestControllerTest.java │ │ ├── DeveloperControllerTest.java │ │ ├── HomeControllerTest.java │ │ ├── OauthControllerTest.java │ │ ├── ShortUrlControllerTest.java │ │ ├── UserControllerTest.java │ │ ├── ZoneControllerTest.java │ │ ├── api │ │ ├── ArticleResourceTest.java │ │ └── VoteResourceTest.java │ │ └── v1 │ │ ├── ClassScanner.java │ │ ├── ClientAppUserAccessTokenArgumentResolverTest.java │ │ ├── ScopeAndAccessTokenSignatureTest.java │ │ ├── V1ArticleResourceTest.java │ │ ├── V1DebateResourceTest.java │ │ ├── V1FeedResourceTest.java │ │ ├── V1ResponseWrapperAdviceTest.java │ │ ├── V1UserResourceTest.java │ │ ├── V1VoteResourceTest.java │ │ └── V1ZoneResourceTest.java │ └── resources │ ├── application-test.yml │ ├── kmark │ ├── in1.md │ ├── in10.md │ ├── in11.md │ ├── in12.md │ ├── in13.md │ ├── in14.md │ ├── in2.md │ ├── in3.md │ ├── in4.md │ ├── in5.md │ ├── in6.md │ ├── in7.md │ ├── in8.md │ ├── in9.md │ ├── out1.out │ ├── out10.out │ ├── out11.out │ ├── out12.out │ ├── out13.out │ ├── out14.out │ ├── out2.out │ ├── out3.out │ ├── out4.out │ ├── out5.out │ ├── out6.out │ ├── out7.out │ ├── out8.out │ └── out9.out │ └── logback-test.xml ├── settings.gradle ├── test.sh └── tools └── idea_settings.jar /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | build/ 5 | .DS_Store 6 | *.swp 7 | classes/ 8 | .packages 9 | out/ -------------------------------------------------------------------------------- /.run/kaif_kaif-fe [webDevServe].run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | java corretto-11.0.15.9.1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### io.kaif 2 | 3 | * TODO 4 | 5 | # Development 6 | 7 | ### JDK-11 8 | 9 | * adoptopenjdk is recommended 10 | 11 | ### Prepare k3d and provision local database 12 | 13 | * go to `kaif-deploy`, read README.md. include: 14 | - install mkcert and k3d 15 | - build kaif-web docker image 16 | - provision via terraform + helm 17 | 18 | ### Prepare Dart for IDE 19 | 20 | * install Intellij Dart plugin 21 | * intellij need local copy of dart sdk, you can install it in your OS 22 | * open `pubspec.yaml` and click `Get Dependencies` 23 | 24 | ### Prepare dev web server 25 | 26 | * go to `kaif-web`, execute gradle tomcat, and pub serve 27 | 28 | ``` 29 | cd kaif-web 30 | ../gradlew webDevServe 31 | ../gradlew bootRun 32 | ``` 33 | 34 | * `bootRun` will start a development embed tomcat, you can visit 35 | http://localhost:5980 36 | 37 | * `webDevServe` will start pub server in 15980 port, which used by dev server 38 | 39 | * you can use gradle in Intellij to run `bootRun` and `webDevServe`. 40 | 41 | ### Intellij IDEA configuration 42 | 43 | * import code style in tools/idea_settings.jar (scheme select `lambda_idea`) 44 | * in run configurations, change `Defaults` `JUnit` working directory to $MODULE_DIR$ 45 | * Intellij may prompt you use .less filewatcher, we don't use it, just dismiss. 46 | 47 | ### Development tips 48 | 49 | * append `?kaif-locale=en` can force change locale in bootRun server, default value is zh_TW 50 | 51 | ### Deploy web app to local k3d 52 | 53 | * build kaif-web docker image then deploy to k3d 54 | 55 | ``` 56 | ./buildJibToLocal.sh 57 | 58 | kaif-deploy/ctl/kaif_ct.sh # open kaif_ctl console 59 | 60 | # within kaif_ctl, execute: 61 | cd kaif/kaif-deploy/kaif-local 62 | terraform init 63 | terraform apply 64 | ``` 65 | 66 | * go visit https://localdev.kaif.io:5443/ 67 | 68 | ### Production provision and deployment 69 | 70 | * see kaif-deploy/README.md for detail 71 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | group = 'io.kaif' 3 | // version = '0.0.1-SNAPSHOT' 4 | 5 | } 6 | 7 | apply plugin: 'idea' 8 | idea { 9 | project { 10 | jdkName = '11' 11 | languageLevel = '11' 12 | } 13 | } 14 | 15 | subprojects { 16 | repositories { 17 | mavenCentral() 18 | } 19 | } 20 | 21 | /** 22 | * to upgrade specific gradle version, run below command: 23 | * 24 | * ./gradlew wrapper --gradle-version 5.2.1 25 | */ 26 | wrapper { 27 | distributionType = Wrapper.DistributionType.ALL 28 | } 29 | -------------------------------------------------------------------------------- /buildJibToLocal.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 4 | 5 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 6 | jhome=$(dirname /usr/lib/jvm/*11*/bin) 7 | elif [[ "$OSTYPE" == "darwin"* ]]; then 8 | jhome=$(/usr/libexec/java_home -v 11) 9 | else 10 | echo "no java 11 found" 11 | exit 1 12 | fi 13 | 14 | JAVA_HOME=$jhome "$CUR_DIR"/gradlew clean jib 15 | -------------------------------------------------------------------------------- /buildJibToProd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) 4 | 5 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 6 | jhome=$(dirname /usr/lib/jvm/*11*/bin) 7 | elif [[ "$OSTYPE" == "darwin"* ]]; then 8 | jhome=$(/usr/libexec/java_home -v 11) 9 | else 10 | echo "no java 11 found" 11 | exit 1 12 | fi 13 | 14 | JAVA_HOME=$jhome "$CUR_DIR"/gradlew clean jib \ 15 | -Djib-json-key-file="$CUR_DIR/kaif-deploy/secret/kaif-id-93d44b502305.json" -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 2 | org.gradle.daemon=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaif-open/kaif/20de04b6335c4ea5347e1445386732d4fd228636/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /kaif-deploy/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | tmp/ 3 | *-cloudimg-console.log 4 | secret/vault_password_file 5 | secret/kaif_rsa 6 | secret/kaif_rsa.pub 7 | secret/kaif-id-*.json -------------------------------------------------------------------------------- /kaif-deploy/ctl/.gitignore: -------------------------------------------------------------------------------- 1 | secret/ -------------------------------------------------------------------------------- /kaif-deploy/ctl/README.md: -------------------------------------------------------------------------------- 1 | Prepare 2 | =================== 3 | 4 | * prepare files: 5 | 6 | - secret/kube_config 7 | * for k3d, it's from `k3d kubeconfig get kaif-local` 8 | 9 | Open ctl console 10 | ======================= 11 | 12 | * build and open ctl console: 13 | 14 | ``` 15 | kaif/kaif-deploy/ctl/kaif_ctl.sh 16 | ``` 17 | 18 | Misc command reference 19 | ======================== 20 | 21 | * to force container rebuild/restart: 22 | 23 | ``` 24 | kaif/kaif-deploy/ctl/kaif_ctl.sh restart 25 | ``` 26 | 27 | * to connect same container with another session: 28 | 29 | ``` 30 | docker exec -it kaif_ctl zsh 31 | ``` 32 | 33 | * use kube proxy via docker 34 | 35 | ``` 36 | kubectl proxy --address='0.0.0.0' --port=8009 37 | ``` 38 | 39 | now you can open browser to view k8s api server `http://127.0.0.1:8009/` 40 | 41 | -------------------------------------------------------------------------------- /kaif-deploy/ctl/k9s/black_and_wtf.yml: -------------------------------------------------------------------------------- 1 | # Styles... 2 | fg: &fg "white" 3 | bg: &bg "black" 4 | mark: &mark "darkgoldenrod" 5 | active: &active "dimgray" 6 | text: &text "navajowhite" 7 | white: &white "whitesmoke" 8 | ghost: &ghost "ghostwhite" 9 | dslate: &dslate "darkslategray" 10 | err: &err "pink" 11 | slate: &slate "slategray" 12 | gray: &gray "gray" 13 | 14 | # Skin 15 | k9s: 16 | body: 17 | fgColor: *fg 18 | bgColor: *bg 19 | logoColor: *fg 20 | info: 21 | fgColor: *text 22 | sectionColor: *fg 23 | frame: 24 | border: 25 | fgColor: *fg 26 | focusColor: *fg 27 | menu: 28 | fgColor: *fg 29 | keyColor: *fg 30 | numKeyColor: *text 31 | crumbs: 32 | fgColor: *fg 33 | bgColor: *bg 34 | activeColor: *active 35 | status: 36 | newColor: *white 37 | modifyColor: *text 38 | addColor: *ghost 39 | errorColor: *err 40 | highlightcolor: *dslate 41 | killColor: *slate 42 | completedColor: *gray 43 | title: 44 | fgColor: *fg 45 | highlightColor: *active 46 | counterColor: *text 47 | filterColor: *slate 48 | views: 49 | table: 50 | fgColor: *fg 51 | bgColor: *bg 52 | cursorColor: *fg 53 | markColor: *mark 54 | header: 55 | fgColor: *dslate 56 | bgColor: *bg 57 | sorterColor: *fg 58 | xray: 59 | fgColor: *fg 60 | bgColor: *bg 61 | cursorColor: *ghost 62 | graphicColor: gray 63 | showIcons: false 64 | yaml: 65 | keyColor: *ghost 66 | colorColor: *slate 67 | valueColor: *text 68 | logs: 69 | fgColor: *ghost 70 | bgColor: *bg 71 | indicator: 72 | fgColor: *ghost 73 | bgColor: *bg 74 | charts: 75 | bgColor: default 76 | defaultDialColors: 77 | - *white 78 | - *err 79 | defaultChartColors: 80 | - *white 81 | - *err -------------------------------------------------------------------------------- /kaif-deploy/ctl/secret/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaif-open/kaif/20de04b6335c4ea5347e1445386732d4fd228636/kaif-deploy/ctl/secret/.gitkeep -------------------------------------------------------------------------------- /kaif-deploy/ctl/zshrc: -------------------------------------------------------------------------------- 1 | ZSH=$HOME/.oh-my-zsh 2 | ZSH_THEME="evan" 3 | DISABLE_AUTO_UPDATE="true" 4 | 5 | plugins=(git kubectl aws) 6 | source $ZSH/oh-my-zsh.sh 7 | source <(kubectl completion zsh) 8 | 9 | export GOOGLE_APPLICATION_CREDENTIALS=`ls /root/.config/gcloud/legacy_credentials/*/*.json` 10 | 11 | alias tf=terraform 12 | 13 | PATH="$PATH:/opt/google-cloud-sdk/bin" 14 | 15 | PROMPT="%{$fg_bold[yellow]%}kaif_ctl%{$fg_bold[blue]%} %w %* % %{$reset_color%}$PROMPT"$'\n'"# " -------------------------------------------------------------------------------- /kaif-deploy/gce.ssh_config: -------------------------------------------------------------------------------- 1 | Host kaif 2 | HostName kaif.io 3 | StrictHostKeyChecking no 4 | IdentityFile secret/kaif_rsa 5 | User ubuntu 6 | -------------------------------------------------------------------------------- /kaif-deploy/k3d/README.md: -------------------------------------------------------------------------------- 1 | ### Installation 2 | 3 | ``` 4 | curl -s https://raw.githubusercontent.com/rancher/k3d/main/install.sh | TAG=v4.0.0 bash 5 | ``` 6 | 7 | ### Create cluster 8 | 9 | * with private registry 10 | 11 | ``` 12 | k3d registry create kaif-registry.localhost --port 5111 13 | k3d cluster create kaif-local \ 14 | --registry-use k3d-kaif-registry.localhost:5111 \ 15 | -p "30500-30600:30500-30600@server[0]" \ 16 | -p "5080:80@loadbalancer" \ 17 | -p "5443:443@loadbalancer" 18 | ``` 19 | 20 | * mapping ip in /etc/hosts 21 | 22 | ``` 23 | echo "127.0.0.1 kaif-local" | sudo tee -a /etc/hosts 24 | 25 | # for mac only: 26 | echo "127.0.0.1 k3d-kaif-registry.localhost" | sudo tee -a /etc/hosts 27 | ``` 28 | 29 | * configure k3d for kaif_ctl.sh 30 | 31 | * copy k3d kubeconfig to ctl/secret/kube_config 32 | * then update 0.0.0.0 address to internal `host.docker.internal` 33 | * following is example. but `sed` command may not work in MacOS 34 | 35 | ``` 36 | target_config=~/develop/kaif-all/kaif/kaif-deploy/ctl/secret/kube_config 37 | k3d kubeconfig get kaif-local > "$target_config" 38 | sed -i s/0.0.0.0/host.docker.internal/g "$target_config" 39 | ``` 40 | 41 | * k3d basic management 42 | 43 | 44 | ``` 45 | # shutdown 46 | k3d cluster stop kaif-local 47 | 48 | # start again 49 | k3d cluster start kaif-local 50 | 51 | # delete all 52 | k3d registry delete k3d-kaif-registry.localhost 53 | k3d cluster delete kaif-local 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | terraform.tfstate.backup -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/cert-manager.tf: -------------------------------------------------------------------------------- 1 | resource "helm_release" "cert-manager" { 2 | name = "cert-manager" 3 | replace = true 4 | 5 | repository = "https://charts.jetstack.io" 6 | chart = "cert-manager" 7 | create_namespace = true 8 | version = "v1.1.0" 9 | namespace = "cert-manager" 10 | 11 | set { 12 | name = "installCRDs" 13 | value = true 14 | } 15 | } 16 | 17 | ### create mkcert self-signed issuer (used for development only) 18 | # * follow kaif-deploy/mkcert/README.md to create root CA 19 | 20 | resource "kubernetes_secret" "mkcert-tls-secret" { 21 | depends_on = [ 22 | helm_release.cert-manager 23 | ] 24 | metadata { 25 | name = "mkcert-tls-secret" 26 | namespace = "kaif" 27 | } 28 | 29 | data = { 30 | "tls.crt" = file("../mkcert/rootCA.pem") 31 | "tls.key" = file("../mkcert/rootCA-key.pem") 32 | } 33 | 34 | type = "kubernetes.io/tls" 35 | } -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/kaif-web-secret.yaml: -------------------------------------------------------------------------------- 1 | kaif: 2 | secret: { 3 | POSTGRES_PASSWORD: "changeme", 4 | MAIL_AWS_SECRET_KEY: "false", 5 | MAIL_AWS_ACCESS_KEY: "false", 6 | ACCOUNT_MAC: "n7LOo9kKqSqM-fshf_T3xw", 7 | ACCOUNT_KEY: "dnXAz2u6mn4L_vZyiW0hHA", 8 | OAUTH_MAC: "n7LOo9kKqSqM-fshf_T3xw", 9 | OAUTH_KEY: "dnXAz2u6mn4L_vZyiW0hHA" 10 | } 11 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/kaif-web-values.yaml: -------------------------------------------------------------------------------- 1 | kaif: 2 | ## spring active profile, ex: 'prod,vn' 3 | springProfile: 'prod' 4 | javaOptions: '-XX:MinRAMPercentage=60.0 -XX:MaxRAMPercentage=85.0' 5 | siteServerName: localdev.kaif.io 6 | springApplicationJson: 7 | { 8 | "spring": { 9 | "mail": { 10 | "host": "smtp.gmail.com", 11 | "port": 587 12 | } 13 | } 14 | } 15 | 16 | springImage: 17 | repository: k3d-kaif-registry.localhost:5111/kaif/kaif-web 18 | tag: latest 19 | pullPolicy: Always 20 | 21 | mkcertIssuer: 22 | create: true 23 | 24 | ingress: 25 | enabled: true 26 | annotations: 27 | cert-manager.io/issuer: mkcert-issuer 28 | ingress.kubernetes.io/ssl-redirect: "false" 29 | hosts: 30 | - host: localdev.kaif.io 31 | paths: 32 | - path: "/" 33 | tls: 34 | - secretName: localdev-kaif-io-tls 35 | hosts: 36 | - localdev.kaif.io 37 | 38 | springResources: 39 | limits: 40 | cpu: 1000m 41 | memory: 768Mi 42 | requests: 43 | cpu: 1000m 44 | memory: 768Mi 45 | 46 | nginxResources: 47 | limits: 48 | cpu: 100m 49 | memory: 128Mi 50 | requests: 51 | cpu: 100m 52 | memory: 128Mi 53 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/kaif-web.tf: -------------------------------------------------------------------------------- 1 | resource "helm_release" "kaif-web" { 2 | name = "kaif-web" 3 | replace = true 4 | 5 | chart = "../kaif-web-chart" 6 | create_namespace = true 7 | namespace = "kaif" 8 | 9 | values = [ 10 | file("kaif-web-values.yaml"), 11 | file("kaif-web-secret.yaml") 12 | ] 13 | 14 | ## force use latest image in local registry 15 | recreate_pods = true 16 | set { 17 | name = "always-redeploy" 18 | value = timestamp() 19 | } 20 | 21 | depends_on = [ 22 | helm_release.kaif-db, 23 | helm_release.cert-manager 24 | ] 25 | } 26 | 27 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/main.tf: -------------------------------------------------------------------------------- 1 | provider "helm" { 2 | kubernetes { 3 | config_path = "~/.kube/config" 4 | } 5 | } 6 | 7 | provider "kubernetes" { 8 | config_path = "~/.kube/config" 9 | } -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/postgresql-values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | image: 3 | tag: '13.1.0-debian-10-r67' 4 | 5 | service: 6 | type: NodePort 7 | nodePort: 30598 8 | 9 | initdbScripts: 10 | recreate_db.sql: | 11 | DROP DATABASE if exists kaif; 12 | DROP ROLE if exists kaif; 13 | CREATE ROLE kaif LOGIN PASSWORD 'changeme' NOINHERIT VALID UNTIL 'infinity'; 14 | CREATE DATABASE kaif WITH ENCODING='UTF8' OWNER=kaif TEMPLATE=template1; 15 | \c kaif 16 | ALTER SCHEMA public OWNER TO kaif; 17 | 18 | recreate_db_ci.sql: | 19 | DROP DATABASE if exists ci_kaif; 20 | DROP ROLE if exists ci_kaif; 21 | CREATE ROLE ci_kaif LOGIN PASSWORD 'changeme' NOINHERIT VALID UNTIL 'infinity'; 22 | CREATE DATABASE ci_kaif WITH ENCODING='UTF8' OWNER=ci_kaif TEMPLATE=template1; 23 | \c ci_kaif 24 | ALTER SCHEMA public OWNER TO ci_kaif; 25 | 26 | postgresqlConfiguration: { 27 | "listenAddresses": "'*'" 28 | } 29 | 30 | ## below is local specific 31 | persistence: 32 | enabled: true 33 | #size: 8Gi 34 | 35 | volumePermissions: 36 | enabled: true 37 | 38 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-local/postgresql.tf: -------------------------------------------------------------------------------- 1 | resource "helm_release" "kaif-db" { 2 | name = "kaif-db" 3 | replace = true 4 | 5 | repository = "https://charts.bitnami.com/bitnami" 6 | chart = "postgresql" 7 | create_namespace = true 8 | version = "10.2.4" 9 | namespace = "kaif" 10 | 11 | values = [ 12 | file("postgresql-values.yaml") 13 | ] 14 | 15 | //lazy assign password because the password only known after deploy 16 | dynamic "set_sensitive" { 17 | for_each = try(data.kubernetes_secret.kaif-db-postgresql[*].data["postgresql-password"], []) 18 | content { 19 | name = "postgresqlPassword" 20 | value = set_sensitive.value 21 | } 22 | } 23 | } 24 | 25 | data "kubernetes_secret" "kaif-db-postgresql" { 26 | metadata { 27 | namespace = "kaif" 28 | name = "kaif-db-postgresql" 29 | } 30 | } -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | terraform.tfstate.backup -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/kaif-web-values.yaml: -------------------------------------------------------------------------------- 1 | imagePullSecrets: 2 | - name: gcr-secret 3 | 4 | initialDelaySeconds: 30 5 | 6 | kaif: 7 | springProfile: 'prod' 8 | javaOptions: '-XX:MinRAMPercentage=60.0 -XX:MaxRAMPercentage=85.0' 9 | siteServerName: kaif.io 10 | 11 | springImage: 12 | repository: gcr.io/kaif-id/kaif 13 | tag: latest 14 | pullPolicy: Always 15 | 16 | ingress: 17 | enabled: true 18 | annotations: 19 | kubernetes.io/ingress.class: nginx 20 | cert-manager.io/cluster-issuer: letsencrypt 21 | ingress.kubernetes.io/ssl-redirect: "true" 22 | hosts: 23 | - host: kaif.io 24 | paths: 25 | - path: "/" 26 | - host: www.kaif.io 27 | paths: 28 | - path: "/" 29 | tls: 30 | - secretName: letsencrypt-issuer 31 | hosts: 32 | - kaif.io 33 | - www.kaif.io 34 | 35 | springResources: 36 | limits: 37 | cpu: 1000m 38 | memory: 768Mi 39 | requests: 40 | cpu: 1000m 41 | memory: 768Mi 42 | 43 | nginxResources: 44 | limits: 45 | cpu: 100m 46 | memory: 128Mi 47 | requests: 48 | cpu: 100m 49 | memory: 128Mi 50 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/kaif-web-version.txt: -------------------------------------------------------------------------------- 1 | release-230123-0716 -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/kaif-web.tf.disabled: -------------------------------------------------------------------------------- 1 | resource "helm_release" "kaif-web" { 2 | name = "kaif-web" 3 | replace = true 4 | 5 | chart = "../kaif-web-chart" 6 | create_namespace = true 7 | namespace = "kaif" 8 | 9 | values = [ 10 | file("kaif-web-values.yaml"), 11 | yamlencode({ 12 | kaif = { 13 | secret = jsondecode(data.google_secret_manager_secret_version.secret-version.secret_data) 14 | } 15 | }) 16 | ] 17 | 18 | set { 19 | name = "springImage.tag" 20 | value = file("kaif-web-version.txt") 21 | } 22 | } 23 | 24 | data "google_secret_manager_secret_version" "secret-version" { 25 | secret = "kaif-secret-prod" 26 | project = "kaif-id" 27 | } 28 | 29 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/main.tf: -------------------------------------------------------------------------------- 1 | provider "google" { 2 | project = "kaif-id" 3 | } 4 | 5 | terraform { 6 | required_providers { 7 | google = { 8 | version = "~> 3.48.0" 9 | } 10 | } 11 | 12 | backend "gcs" { 13 | bucket = "ingram-deploy-tf" 14 | prefix = "kaif-prod" 15 | } 16 | } 17 | 18 | provider "helm" { 19 | kubernetes { 20 | config_path = "~/.kube/config-prod" 21 | } 22 | } 23 | 24 | provider "kubernetes" { 25 | config_path = "~/.kube/config-prod" 26 | } -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/postgresql-values.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | image: 3 | tag: '13.1.0-debian-10-r67' 4 | 5 | initdbScripts: 6 | recreate_db.sql: | 7 | DROP DATABASE if exists kaif; 8 | DROP ROLE if exists kaif; 9 | CREATE ROLE kaif LOGIN PASSWORD 'changeme' NOINHERIT VALID UNTIL 'infinity'; 10 | CREATE DATABASE kaif WITH ENCODING='UTF8' OWNER=kaif TEMPLATE=template1; 11 | \c kaif 12 | ALTER SCHEMA public OWNER TO kaif; 13 | 14 | ## the tuning is for 2GB ram in production 15 | ## see http://www.pgconfig.org 16 | postgresqlConfiguration: { 17 | "listenAddresses": "'*'", 18 | "sharedBuffers": '512MB', 19 | "effectiveCacheSize": "2GB", 20 | "workMem": "16MB", 21 | "checkpointCompletionTarget": "0.7", 22 | "maintenanceWorkMem": "128MB", 23 | "minWalSize": "80MB", 24 | "maxWalSize": "1GB", 25 | "walBuffers": "-1", 26 | "maxConnections": "100" 27 | } 28 | 29 | ## below is local specific 30 | persistence: 31 | enabled: true 32 | existingClaim: kaif-db-postgresq-pvc 33 | 34 | volumePermissions: 35 | enabled: true 36 | 37 | postgresqlDatabase: kaif 38 | 39 | metrics: 40 | enabled: true 41 | 42 | ## @formatter:off 43 | extraDeploy: 44 | - kind: PersistentVolume 45 | apiVersion: v1 46 | metadata: 47 | name: kaif-db-postgresq-pv 48 | labels: 49 | app: kaif-db 50 | spec: 51 | storageClassName: manual 52 | capacity: 53 | storage: 20Gi 54 | accessModes: 55 | - ReadWriteMany 56 | hostPath: 57 | path: "/volume/pg-data" 58 | - kind: PersistentVolumeClaim 59 | apiVersion: v1 60 | metadata: 61 | name: kaif-db-postgresq-pvc 62 | labels: 63 | app: kaif-db 64 | spec: 65 | storageClassName: manual 66 | accessModes: 67 | - ReadWriteMany 68 | resources: 69 | requests: 70 | storage: 20Gi 71 | selector: 72 | matchLabels: 73 | app: kaif-db 74 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-prod/postgresql.tf.disabled: -------------------------------------------------------------------------------- 1 | resource "helm_release" "kaif-db" { 2 | name = "kaif-db" 3 | replace = true 4 | 5 | repository = "https://charts.bitnami.com/bitnami" 6 | chart = "postgresql" 7 | create_namespace = true 8 | version = "10.2.4" 9 | namespace = "kaif" 10 | 11 | values = [ 12 | file("postgresql-values.yaml") 13 | ] 14 | 15 | //lazy assign password because the password only known after deploy 16 | dynamic "set_sensitive" { 17 | for_each = try(data.kubernetes_secret.kaif-db-postgresql[*].data["postgresql-password"], []) 18 | content { 19 | name = "postgresqlPassword" 20 | value = set_sensitive.value 21 | } 22 | } 23 | } 24 | 25 | data "kubernetes_secret" "kaif-db-postgresql" { 26 | metadata { 27 | namespace = "kaif" 28 | name = "kaif-db-postgresql" 29 | } 30 | } -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kaif-web 3 | description: kaif-web spring boot app 4 | 5 | # A chart can be either an 'application' or a 'library' chart. 6 | # 7 | # Application charts are a collection of templates that can be packaged into versioned archives 8 | # to be deployed. 9 | # 10 | # Library charts provide useful utilities or functions for the chart developer. They're included as 11 | # a dependency of application charts to inject those utilities and functions into the rendering 12 | # pipeline. Library charts do not define any templates and therefore cannot be deployed. 13 | type: application 14 | 15 | # This is the chart version. This version number should be incremented each time you make changes 16 | # to the chart and its templates, including the app version. 17 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 18 | version: 0.1.5 19 | 20 | # This is the version number of the application being deployed. This version number should be 21 | # incremented each time you make changes to the application. Versions are not expected to 22 | # follow Semantic Versioning. They should reflect the version the application is using. 23 | # It is recommended to use it with quotes. 24 | appVersion: "1.16.0" 25 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/README.md: -------------------------------------------------------------------------------- 1 | ### Installation 2 | 3 | ``` 4 | cd ../kaif-web-chart && \ 5 | helm upgrade --install \ 6 | kaif-web \ 7 | ../kaif-web-values \ 8 | --namespace kaif \ 9 | --create-namespace \ 10 | -f kaif-web-values.yaml 11 | ``` 12 | 13 | ### generate template: 14 | 15 | * evaluate k8s yaml 16 | 17 | ``` 18 | helm template \ 19 | -n kaif-web \ 20 | --namespace kaif \ 21 | ../kaif-web-charts \ 22 | -f kaif-web-values.yaml > tmp/render.yaml 23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "kaif-web.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "kaif-web.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "kaif-web.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "kaif-web.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "kaif-web.fullname" . -}} 3 | {{- $svcPort := .Values.service.port -}} 4 | {{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} 5 | apiVersion: networking.k8s.io/v1beta1 6 | {{- else -}} 7 | apiVersion: extensions/v1beta1 8 | {{- end }} 9 | kind: Ingress 10 | metadata: 11 | name: {{ $fullName }} 12 | labels: 13 | {{- include "kaif-web.labels" . | nindent 4 }} 14 | {{- with .Values.ingress.annotations }} 15 | annotations: 16 | {{- toYaml . | nindent 4 }} 17 | {{- end }} 18 | spec: 19 | {{- if .Values.ingress.tls }} 20 | tls: 21 | {{- range .Values.ingress.tls }} 22 | - hosts: 23 | {{- range .hosts }} 24 | - {{ . | quote }} 25 | {{- end }} 26 | secretName: {{ .secretName }} 27 | {{- end }} 28 | {{- end }} 29 | rules: 30 | {{- range .Values.ingress.hosts }} 31 | - host: {{ .host | quote }} 32 | http: 33 | paths: 34 | {{- range .paths }} 35 | - path: {{ .path }} 36 | backend: 37 | serviceName: {{ $fullName }} 38 | servicePort: {{ $svcPort }} 39 | {{- end }} 40 | {{- end }} 41 | {{- end }} 42 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/mkcert-issuer.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.mkcertIssuer.create -}} 2 | 3 | --- 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | name: mkcert-issuer 8 | labels: 9 | {{- include "kaif-web.labels" . | nindent 4 }} 10 | spec: 11 | ca: 12 | secretName: mkcert-tls-secret 13 | 14 | {{- end }} -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ .Release.Name }}-secret 5 | labels: 6 | {{- include "kaif-web.labels" . | nindent 4 }} 7 | type: Opaque 8 | stringData: {{ .Values.kaif.secret | toPrettyJson }} 9 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "kaif-web.fullname" . }} 5 | labels: 6 | {{- include "kaif-web.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | - port: 5980 15 | protocol: TCP 16 | name: spring 17 | targetPort: 5980 18 | selector: 19 | {{- include "kaif-web.selectorLabels" . | nindent 4 }} 20 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "kaif-web.serviceAccountName" . }} 6 | labels: 7 | {{- include "kaif-web.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /kaif-deploy/kaif-web-chart/templates/spring-json-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: "{{ .Chart.Name }}-spring" 6 | labels: 7 | {{- include "kaif-web.labels" . | nindent 4 }} 8 | data: 9 | ## gmail smtp sample 10 | ## spring.mail.host=smtp.gmail.com 11 | ## spring.mail.port=587 12 | ## spring.mail.username= 13 | ## spring.mail.password= 14 | ## spring.mail.properties.mail.smtp.auth=true 15 | ## spring.mail.properties.mail.smtp.starttls.enable=true 16 | SPRING_APPLICATION_JSON: {{ .Values.kaif.springApplicationJson | toJson | quote }} 17 | -------------------------------------------------------------------------------- /kaif-deploy/mkcert/.gitignore: -------------------------------------------------------------------------------- 1 | *.pem 2 | *.p12 -------------------------------------------------------------------------------- /kaif-deploy/mkcert/README.md: -------------------------------------------------------------------------------- 1 | Installation 2 | =============== 3 | 4 | * mac: 5 | 6 | ``` 7 | brew install mkcert 8 | ``` 9 | 10 | * linux: 11 | 12 | ``` 13 | sudo apt install libnss3-tools 14 | wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.1/mkcert-v1.4.1-linux-amd64 15 | mv mkcert-v1.4.1-linux-amd64 ~/app/mkcert 16 | ``` 17 | 18 | ROOT CA 19 | =============== 20 | 21 | ``` 22 | mkcert -install 23 | ``` 24 | 25 | COPY ROOT CA for k8s cert-manager issuer 26 | ========================================= 27 | 28 | ``` 29 | root_ca_dir=$(mkcert -CAROOT) 30 | cp $root_ca_dir/*.pem . 31 | ``` 32 | 33 | Localhost certificate 34 | ======================= 35 | 36 | * for fe dev server 37 | 38 | ``` 39 | mkcert localhost 40 | ``` 41 | 42 | * for spring boot server 43 | * password is `changeme` 44 | 45 | ``` 46 | mkcert -pkcs12 localhost 47 | ``` 48 | -------------------------------------------------------------------------------- /kaif-deploy/reference/cert-manager-README.md: -------------------------------------------------------------------------------- 1 | ### installation 2 | 3 | * install/upgrade 4 | 5 | ``` 6 | helm repo add jetstack https://charts.jetstack.io && \ 7 | cd ../cert-manager && \ 8 | helm upgrade --install \ 9 | cert-manager \ 10 | --namespace cert-manager \ 11 | --create-namespace \ 12 | jetstack/cert-manager \ 13 | --version v1.1.0 \ 14 | -f values.yaml 15 | ``` 16 | 17 | ### create mkcert self-signed issuer (used for development only) 18 | 19 | * follow kaif-deploy/mkcert/README.md to create root CA 20 | 21 | * apply following configuration: 22 | 23 | ``` 24 | kubectl -n kaif create secret tls mkcert-tls-secret \ 25 | --key=../mkcert/rootCA-key.pem \ 26 | --cert=../mkcert/rootCA.pem 27 | ``` 28 | 29 | * now you can use `mkcert-tls-secret` in issuer like: 30 | 31 | ``` 32 | apiVersion: cert-manager.io/v1 33 | kind: Issuer 34 | metadata: 35 | name: mkcert-issuer 36 | spec: 37 | ca: 38 | secretName: mkcert-tls-secret 39 | ``` 40 | 41 | -------------------------------------------------------------------------------- /kaif-deploy/reference/gce.md: -------------------------------------------------------------------------------- 1 | ### Mount gce disk to /volume 2 | 3 | * format disk (attach new disk to server first) 4 | 5 | ``` 6 | mkfs.ext4 /dev/sdb 7 | sudo mkdir -p /volume 8 | sudo mount -t ext4 -o noatime /dev/sdb /volume 9 | ``` 10 | 11 | * add to /etc/fstab 12 | 13 | ``` 14 | /dev/sdb /volume ext4 defaults,noatime,nodiratime 1 1 15 | ``` -------------------------------------------------------------------------------- /kaif-deploy/reference/k3s/ingress-nginx/README.md: -------------------------------------------------------------------------------- 1 | * install nginx ingress controller in k8s 2 | 3 | ``` 4 | helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 5 | helm upgrade --install \ 6 | ingress-nginx \ 7 | ingress-nginx/ingress-nginx \ 8 | --namespace kube-system \ 9 | --version 3.21.0 \ 10 | -f values.yaml 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /kaif-deploy/reference/k3s/ingress-nginx/values.yaml: -------------------------------------------------------------------------------- 1 | ## nginx configuration 2 | ## Ref: https://github.com/kubernetes/ingress-nginx 3 | ## 4 | controller: 5 | metrics: 6 | enabled: true 7 | -------------------------------------------------------------------------------- /kaif-deploy/reference/postgresql-README.md: -------------------------------------------------------------------------------- 1 | ### Installation 2 | 3 | ``` 4 | helm repo add bitnami https://charts.bitnami.com/bitnami 5 | 6 | cd ../postgresql && \ 7 | helm upgrade --install \ 8 | kaif-db \ 9 | bitnami/postgresql \ 10 | --namespace kaif \ 11 | --create-namespace \ 12 | --version 10.2.4 \ 13 | -f postgresql-values.yaml 14 | ``` 15 | 16 | ### check connection from local 17 | 18 | ``` 19 | [inside kaif_ctl] 20 | 21 | psql -h kaif-local -p 30598 -U kaif -d kaif 22 | ``` 23 | 24 | ### generate template: 25 | 26 | * evaluate k8s yaml 27 | 28 | ``` 29 | helm template \ 30 | -n kaif-db \ 31 | --namespace kaif \ 32 | bitnami/postgresql \ 33 | -f postgresql-values.yaml > tmp/render.yaml 34 | ``` -------------------------------------------------------------------------------- /kaif-fe/.gitignore: -------------------------------------------------------------------------------- 1 | packages/ 2 | packages 3 | build/ 4 | .pub/ 5 | /.dart_tool/ 6 | -------------------------------------------------------------------------------- /kaif-fe/build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | less_dart: 5 | options: 6 | entry_points: ['web/less/kaif.less'] 7 | include_path: 'web/less' 8 | cleancss: true 9 | compress: true 10 | build_web_compilers|entrypoint: 11 | options: 12 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/edit_description_form.dart: -------------------------------------------------------------------------------- 1 | library edit_description_form; 2 | 3 | import 'dart:html'; 4 | import 'dart:async'; 5 | import 'package:kaif_web/model.dart'; 6 | import 'package:kaif_web/comp/kmark/edit_kmark_form.dart'; 7 | 8 | class EditDescriptionForm extends EditKmarkForm { 9 | 10 | final AccountService _accountService; 11 | final Element _actionElem; 12 | 13 | EditDescriptionForm.placeHolder(Element contentEditElem, 14 | Element contentElement, 15 | this._actionElem, 16 | this._accountService) : super.placeHolder( 17 | contentEditElem, 18 | contentElement); 19 | 20 | @override 21 | Future preview(String rawInput) { 22 | return _accountService.previewDescription(rawInput); 23 | } 24 | 25 | @override 26 | Future submit(String rawInput) { 27 | return _accountService.updateDescription(rawInput); 28 | } 29 | 30 | @override 31 | String get submitSuccessMessageKey => 'account-setting.update-description-success'; 32 | 33 | @override 34 | String get contentTooShortMessageKey => 'account-setting.min-description'; 35 | 36 | @override 37 | int get minContentLength => 0; 38 | 39 | @override 40 | void hide() { 41 | super.hide(); 42 | _actionElem.classes.toggle('hidden', false); 43 | } 44 | 45 | @override 46 | void show() { 47 | super.show(); 48 | _actionElem.classes.toggle('hidden', true); 49 | } 50 | } -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/forget_password_form.dart: -------------------------------------------------------------------------------- 1 | library forget_password_form; 2 | 3 | import 'dart:html'; 4 | 5 | import 'package:kaif_web/model.dart'; 6 | import 'package:kaif_web/util.dart'; 7 | 8 | class ForgetPasswordForm { 9 | final Element elem; 10 | final AccountService accountService; 11 | late Alert alert; 12 | 13 | ForgetPasswordForm(this.elem, this.accountService) { 14 | elem.onSubmit.listen(_submit); 15 | alert = new Alert.append(elem); 16 | } 17 | 18 | void _submit(Event e) { 19 | e 20 | ..preventDefault() 21 | ..stopPropagation(); 22 | 23 | TextInputElement nameInput = 24 | elem.querySelector('#nameInput') as TextInputElement; 25 | TextInputElement emailInput = 26 | elem.querySelector('#emailInput') as TextInputElement; 27 | ButtonElement submit = elem.querySelector('[type=submit]') as ButtonElement; 28 | submit.disabled = true; 29 | 30 | alert.hide(); 31 | accountService 32 | .sendResetPassword(nameInput.value!, emailInput.value!) 33 | .then((_) { 34 | route.gotoSignInWithSendResetPasswordSuccess(); 35 | }).catchError((e) { 36 | alert.renderError('${e}'); 37 | }).whenComplete(() { 38 | submit.disabled = false; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/granted_client_app.dart: -------------------------------------------------------------------------------- 1 | library granted_client_app; 2 | 3 | import 'dart:async'; 4 | import 'dart:html'; 5 | 6 | import 'package:kaif_web/model.dart'; 7 | import 'package:kaif_web/util.dart'; 8 | 9 | class GrantedClientApp { 10 | final Element elem; 11 | final ClientAppService clientAppService; 12 | 13 | GrantedClientApp(this.elem, this.clientAppService) { 14 | elem.querySelectorAll('[data-client-id]').forEach((el) { 15 | el.onClick.listen((event) { 16 | event 17 | ..stopPropagation() 18 | ..preventDefault(); 19 | _onRevokeApp(el as ButtonElement); 20 | }); 21 | }); 22 | } 23 | 24 | Future _onRevokeApp(ButtonElement button) async { 25 | String clientId = button.dataset['client-id']!; 26 | button.disabled = true; 27 | var loading = new Loading.small()..renderAfter(button); 28 | try { 29 | await clientAppService.revoke(clientId); 30 | new FlashToast.success(i18n('success'), seconds: 2); 31 | route.reload(); 32 | } catch (e) { 33 | new Toast.error("$e").render(); 34 | } finally { 35 | button.disabled = false; 36 | loading.remove(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/news_feed.dart: -------------------------------------------------------------------------------- 1 | library news_feed; 2 | 3 | import 'dart:html'; 4 | 5 | import 'package:kaif_web/model.dart'; 6 | 7 | import '../server_part_loader.dart'; 8 | 9 | class NewsFeedComp { 10 | final Element elem; 11 | final ServerPartLoader serverPartLoader; 12 | final NewsFeedNotification notification; 13 | 14 | NewsFeedComp(this.elem, this.serverPartLoader, this.notification) { 15 | bool isFirstPage = elem.dataset['first-page']?.toLowerCase() == 'true'; 16 | List assets = 17 | elem.querySelectorAll('[feed-asset]').map((el) { 18 | return new FeedAssetComp(el); 19 | }).toList(); 20 | 21 | if (isFirstPage && assets.isNotEmpty) { 22 | //mark un-read for first page only 23 | _markUnread(assets); 24 | 25 | //acknowledge only meaningful for first page 26 | notification.acknowledge(assets.first.assetId); 27 | } 28 | 29 | new PartLoaderPager( 30 | elem, serverPartLoader, assets.isEmpty ? null : assets.last.assetId); 31 | } 32 | 33 | void _markUnread(List assets) { 34 | assets 35 | .takeWhile((asset) => !asset.acked) 36 | .forEach((asset) => asset.markAsUnread()); 37 | } 38 | } 39 | 40 | class FeedAssetComp { 41 | final Element elem; 42 | late String assetId; 43 | late bool acked; 44 | 45 | FeedAssetComp(this.elem) { 46 | assetId = elem.dataset['asset-id']!; 47 | acked = elem.dataset['asset-acked'] == 'true'; 48 | } 49 | 50 | void markAsUnread() { 51 | elem.classes.add('news-feed-unread'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/reset_password_form.dart: -------------------------------------------------------------------------------- 1 | library reset_password_form; 2 | 3 | import 'dart:html'; 4 | 5 | import 'package:kaif_web/model.dart'; 6 | import 'package:kaif_web/util.dart'; 7 | 8 | class ResetPasswordForm { 9 | final Element elem; 10 | final AccountService accountService; 11 | final AccountSession accountSession; 12 | late Alert alert; 13 | 14 | ResetPasswordForm(this.elem, this.accountService, this.accountSession) { 15 | alert = new Alert.append(elem); 16 | elem.onSubmit.listen(_onSubmit); 17 | } 18 | 19 | void _onSubmit(Event e) { 20 | e 21 | ..preventDefault() 22 | ..stopPropagation(); 23 | 24 | TextInputElement passwordInput = 25 | elem.querySelector('#passwordInput') as TextInputElement; 26 | TextInputElement confirmPasswordInput = 27 | elem.querySelector('#confirmPasswordInput') as TextInputElement; 28 | alert.hide(); 29 | 30 | if (passwordInput.value != confirmPasswordInput.value) { 31 | alert.renderError(i18n('sign-up.password-not-same')); 32 | return; 33 | } 34 | 35 | ButtonElement submit = elem.querySelector('[type=submit]') as ButtonElement; 36 | submit.disabled = true; 37 | 38 | var token = Uri.parse(window.location.href).queryParameters['key']; 39 | var loading = new Loading.small()..renderAfter(submit); 40 | accountService 41 | .updatePasswordWithToken(token!, passwordInput.value!) 42 | .then((_) { 43 | // force sign out because old token is stale because password changed 44 | accountSession.signOut(); 45 | route.gotoSignInWithUpdatePasswordSuccess(); 46 | }).catchError((e) { 47 | alert.renderError('${e}'); 48 | }).whenComplete(() { 49 | submit.disabled = false; 50 | loading.remove(); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/account/sign_in_form.dart: -------------------------------------------------------------------------------- 1 | library sign_in_form; 2 | 3 | import 'dart:html'; 4 | 5 | import 'package:kaif_web/model.dart'; 6 | import 'package:kaif_web/util.dart'; 7 | 8 | class SignInForm { 9 | final Element elem; 10 | final AccountService accountService; 11 | final AccountSession accountSession; 12 | late Alert alert; 13 | 14 | SignInForm(this.elem, this.accountService, this.accountSession) { 15 | elem.onSubmit.listen(_login); 16 | alert = new Alert.append(elem); 17 | } 18 | 19 | void _login(Event e) { 20 | e 21 | ..preventDefault() 22 | ..stopPropagation(); 23 | 24 | TextInputElement nameInput = 25 | elem.querySelector('#nameInput') as TextInputElement; 26 | TextInputElement passwordInput = 27 | elem.querySelector('#passwordInput') as TextInputElement; 28 | CheckboxInputElement rememberMeInput = 29 | elem.querySelector('#rememberMeInput') as CheckboxInputElement; 30 | ButtonElement submit = elem.querySelector('[type=submit]') as ButtonElement; 31 | submit.disabled = true; 32 | 33 | alert.hide(); 34 | accountService.authenticate(nameInput.value!, passwordInput.value!) // 35 | .then((AccountAuth accountAuth) { 36 | accountSession.save(accountAuth, 37 | rememberMe: rememberMeInput.checked ?? false); 38 | //TODO handle ?from= 39 | route.gotoHome(); 40 | }).catchError((e) { 41 | // server should use `account.AuthenticateFailException` too because domain exception 42 | // but if server has some internal runtime exception (should be bug). we still 43 | // need to hide the root cause (because use may find our weak point) 44 | alert.renderError(i18n('account.AuthenticateFailException')); 45 | }).whenComplete(() { 46 | submit.disabled = false; 47 | }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /kaif-fe/lib/comp/comp_template.dart: -------------------------------------------------------------------------------- 1 | library comp_template; 2 | 3 | import 'dart:html'; 4 | 5 | /** 6 | * 7 | * component template is rendered by server (invisible to user), it is use to create multiple 8 | * dart view component programmatically. 9 | * 10 | * you should not put any sensitive information on component template. 11 | * for sensitive information use ajax json or server_part_loader.dart instead. 12 | * 13 | * in server side ftl, the template should like: 14 | * 15 | * ``` 16 | *